Skip to content

Commit

Permalink
teku-6734 Batch verify BlsToExecutionChange signatures (Consensys#6736)
Browse files Browse the repository at this point in the history
* teku-6734 Batch verify BlsToExecutionChange signatures
* Update bls_to_execution_change operation pool size
  • Loading branch information
lucassaldanha authored Jan 26, 2023
1 parent b3b08c8 commit be6bf0c
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 47 deletions.
1 change: 1 addition & 0 deletions eth-reference-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies {
referenceTestImplementation project(':ethereum:statetransition')
referenceTestImplementation project(':eth-tests')
referenceTestImplementation project(':infrastructure:bls')
referenceTestImplementation project(':infrastructure:serviceutils')
referenceTestImplementation project(':infrastructure:ssz')
referenceTestImplementation testFixtures(project(':infrastructure:ssz'))
referenceTestImplementation project(':storage')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import tech.pegasys.teku.statetransition.validation.ProposerSlashingValidator;
import tech.pegasys.teku.statetransition.validation.SignedBlsToExecutionChangeValidator;
import tech.pegasys.teku.statetransition.validation.VoluntaryExitValidator;
import tech.pegasys.teku.statetransition.validation.signatures.SimpleSignatureVerificationService;

public class OperationsTestExecutor<T extends SszData> implements TestExecutor {

Expand Down Expand Up @@ -385,7 +386,8 @@ public void checkBlockInclusionValidation(
break;
case BLS_TO_EXECUTION_CHANGE:
final SignedBlsToExecutionChangeValidator blsToExecutionChangeValidator =
new SignedBlsToExecutionChangeValidator(spec, new SystemTimeProvider(), null);
new SignedBlsToExecutionChangeValidator(
spec, new SystemTimeProvider(), null, new SimpleSignatureVerificationService());
final SignedBlsToExecutionChange blsToExecutionChange =
loadBlsToExecutionChange(testDefinition);
checkValidationForBlockInclusion(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.bls.BLSPublicKey;
import tech.pegasys.teku.bls.BLSSignature;
import tech.pegasys.teku.bls.BLSSignatureVerifier;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.spec.constants.Domain;
import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader;
import tech.pegasys.teku.spec.datastructures.operations.BlsToExecutionChange;
Expand All @@ -31,6 +33,7 @@
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateAccessors;
import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers;
import tech.pegasys.teku.spec.logic.common.util.AsyncBLSSignatureVerifier;

public class OperationSignatureVerifier {

Expand Down Expand Up @@ -116,12 +119,30 @@ public boolean verifyBlsToExecutionChangeSignature(
final SignedBlsToExecutionChange signedBlsToExecutionChange,
final BLSSignatureVerifier signatureVerifier) {
final BlsToExecutionChange addressChange = signedBlsToExecutionChange.getMessage();
final BLSPublicKey publicKey = addressChange.getFromBlsPubkey();
final Bytes signingRoot = calculateBlsToExecutionChangeSigningRoot(state, addressChange);
final BLSSignature signature = signedBlsToExecutionChange.getSignature();

return signatureVerifier.verify(publicKey, signingRoot, signature);
}

public SafeFuture<Boolean> verifyBlsToExecutionChangeSignatureAsync(
final BeaconState state,
final SignedBlsToExecutionChange signedBlsToExecutionChange,
final AsyncBLSSignatureVerifier signatureVerifier) {
final BlsToExecutionChange addressChange = signedBlsToExecutionChange.getMessage();
final BLSPublicKey publicKey = addressChange.getFromBlsPubkey();
final Bytes signingRoot = calculateBlsToExecutionChangeSigningRoot(state, addressChange);
final BLSSignature signature = signedBlsToExecutionChange.getSignature();

return signatureVerifier.verify(publicKey, signingRoot, signature);
}

private Bytes calculateBlsToExecutionChangeSigningRoot(
final BeaconState state, final BlsToExecutionChange addressChange) {
final Bytes32 domain =
miscHelpers.computeDomain(
Domain.DOMAIN_BLS_TO_EXECUTION_CHANGE, state.getGenesisValidatorsRoot());
final Bytes signingRoot = miscHelpers.computeSigningRoot(addressChange, domain);
return signatureVerifier.verify(
addressChange.getFromBlsPubkey(), signingRoot, signedBlsToExecutionChange.getSignature());
return miscHelpers.computeSigningRoot(addressChange, domain);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package tech.pegasys.teku.statetransition.validation;

import static tech.pegasys.teku.spec.config.Constants.VALID_VALIDATOR_SET_SIZE;
import static tech.pegasys.teku.statetransition.validation.InternalValidationResult.reject;
import static tech.pegasys.teku.statetransition.validation.ValidationResultCode.ACCEPT;
import static tech.pegasys.teku.statetransition.validation.ValidationResultCode.IGNORE;

import java.util.Optional;
Expand All @@ -31,6 +33,7 @@
import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.logic.common.operations.validation.OperationInvalidReason;
import tech.pegasys.teku.spec.logic.common.util.AsyncBLSSignatureVerifier;
import tech.pegasys.teku.storage.client.RecentChainData;

public class SignedBlsToExecutionChangeValidator
Expand All @@ -43,14 +46,21 @@ public class SignedBlsToExecutionChangeValidator
private final TimeProvider timeProvider;

private final RecentChainData recentChainData;

private final AsyncBLSSignatureVerifier blsSignatureVerifier;

private final Set<UInt64> seenBlsToExecutionChangeMessageFromValidators =
LimitedSet.createSynchronized(VALID_VALIDATOR_SET_SIZE);

public SignedBlsToExecutionChangeValidator(
final Spec spec, final TimeProvider timeProvider, RecentChainData recentChainData) {
final Spec spec,
final TimeProvider timeProvider,
final RecentChainData recentChainData,
final AsyncBLSSignatureVerifier blsSignatureVerifier) {
this.spec = spec;
this.timeProvider = timeProvider;
this.recentChainData = recentChainData;
this.blsSignatureVerifier = blsSignatureVerifier;
}

@Override
Expand All @@ -77,35 +87,68 @@ public SafeFuture<InternalValidationResult> validateForGossip(
validator with index signed_bls_to_execution_change.message.validator_index.
*/
if (!isFirstBlsToExecutionChangeForValidator(blsToExecutionChange)) {
final String logMessage =
String.format(
"BlsToExecutionChange is not the first one for validator %s.", validatorIndex);
LOG.trace(logMessage);
return SafeFuture.completedFuture(InternalValidationResult.create(IGNORE, logMessage));
return SafeFuture.completedFuture(rejectForDuplicatedMessage(validatorIndex));
}

/*
[REJECT] All of the conditions within process_bls_to_execution_change pass validation.
*/
return getMaybeFailureReason(operation)
.thenApply(
maybeFailureReason -> {
if (maybeFailureReason.isPresent()) {
return InternalValidationResult.reject(
"BlsToExecutionChange for validator %s is invalid: %s",
validatorIndex, maybeFailureReason.get().describe());
}
return getState()
.thenCompose(
state -> {
final SafeFuture<InternalValidationResult> messageValidation =
validateBlsMessage(state, blsToExecutionChange);
final SafeFuture<InternalValidationResult> signatureValidation =
validateBlsMessageSignature(state, operation);

return SafeFuture.collectAll(messageValidation, signatureValidation)
.thenApply(
results -> {
if (results.stream().allMatch(InternalValidationResult::isAccept)) {
if (seenBlsToExecutionChangeMessageFromValidators.add(validatorIndex)) {
return InternalValidationResult.ACCEPT;
} else {
return rejectForDuplicatedMessage(validatorIndex);
}
}

return results.stream()
.filter(r -> !r.equals(InternalValidationResult.ACCEPT))
.findFirst()
.orElse(reject("Rejected for unknown reason"));
});
});
}

private static InternalValidationResult rejectForDuplicatedMessage(final UInt64 validatorIndex) {
final String logMessage =
String.format(
"BlsToExecutionChange is not the first one for validator %s.", validatorIndex);
LOG.trace(logMessage);
return InternalValidationResult.create(IGNORE, logMessage);
}

@SuppressWarnings("FormatStringAnnotation")
private SafeFuture<InternalValidationResult> validateBlsMessage(
BeaconState state, BlsToExecutionChange operation) {
return spec.validateBlsToExecutionChange(state, timeProvider.getTimeInSeconds(), operation)
.map(reason -> reject(reason.describe()))
.map(SafeFuture::completedFuture)
.orElse(SafeFuture.completedFuture(InternalValidationResult.ACCEPT));
}

if (seenBlsToExecutionChangeMessageFromValidators.add(validatorIndex)) {
return InternalValidationResult.ACCEPT;
} else {
final String logMessage =
String.format(
"BlsToExecutionChange is not the first one for validator %s.",
validatorIndex);
LOG.trace(logMessage);
return InternalValidationResult.create(IGNORE, logMessage);
private SafeFuture<InternalValidationResult> validateBlsMessageSignature(
BeaconState state, SignedBlsToExecutionChange operation) {
return spec.atSlot(state.getSlot())
.operationSignatureVerifier()
.verifyBlsToExecutionChangeSignatureAsync(state, operation, blsSignatureVerifier)
.thenApply(
signatureValid -> {
if (!signatureValid) {
return reject(
"Rejecting bls_to_execution_change message because the signature is invalid");
}
return InternalValidationResult.create(ACCEPT);
});
}

Expand All @@ -120,25 +163,15 @@ private boolean isCapellaActive() {
@Override
public Optional<OperationInvalidReason> validateForBlockInclusion(
final BeaconState state, final SignedBlsToExecutionChange operation) {
return getMaybeFailureReason(state, operation);
}

private SafeFuture<Optional<OperationInvalidReason>> getMaybeFailureReason(
final SignedBlsToExecutionChange signedBlsToExecutionChange) {
return getState().thenApply(state -> getMaybeFailureReason(state, signedBlsToExecutionChange));
}

private Optional<OperationInvalidReason> getMaybeFailureReason(
final BeaconState state, final SignedBlsToExecutionChange signedBlsToExecutionChange) {
final Optional<OperationInvalidReason> invalidReason =
spec.validateBlsToExecutionChange(
state, timeProvider.getTimeInSeconds(), signedBlsToExecutionChange.getMessage());
state, timeProvider.getTimeInSeconds(), operation.getMessage());
if (invalidReason.isPresent()) {
return invalidReason;
}

if (!spec.verifyBlsToExecutionChangeSignature(
state, signedBlsToExecutionChange, BLSSignatureVerifier.SIMPLE)) {
if (!spec.verifyBlsToExecutionChangeSignature(state, operation, BLSSignatureVerifier.SIMPLE)) {
return Optional.of(() -> "Signature is invalid");
}
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ void emptyPoolShouldReturnEmptyList() {

@Test
void shouldAddMaxItemsInBigPool() {
final int largePoolSize = 300_000;
final int largePoolSize = 16_384;
OperationValidator<SszBytes4> validator = mock(OperationValidator.class);
OperationPool<SszBytes4> pool =
new OperationPool<>("Bytes4Pool", metricsSystem, (a) -> null, validator, largePoolSize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,37 @@
import static tech.pegasys.teku.statetransition.validation.ValidationResultCode.IGNORE;

import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import tech.pegasys.teku.bls.BLSPublicKey;
import tech.pegasys.teku.bls.BLSSignature;
import tech.pegasys.teku.bls.BLSSignatureVerifier;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.time.StubTimeProvider;
import tech.pegasys.teku.infrastructure.time.SystemTimeProvider;
import tech.pegasys.teku.infrastructure.time.TimeProvider;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.SpecVersion;
import tech.pegasys.teku.spec.TestSpecFactory;
import tech.pegasys.teku.spec.datastructures.operations.BlsToExecutionChange;
import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange;
import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState;
import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier;
import tech.pegasys.teku.spec.logic.common.operations.validation.OperationInvalidReason;
import tech.pegasys.teku.spec.util.DataStructureUtil;
import tech.pegasys.teku.statetransition.validation.signatures.SignatureVerificationService;
import tech.pegasys.teku.storage.client.RecentChainData;

class SignedBlsToExecutionChangeValidatorTest {

private final Spec spec = spy(TestSpecFactory.createMinimalCapella());
private final TimeProvider timeProvider = new SystemTimeProvider();
private final RecentChainData recentChainData = mock(RecentChainData.class);
private final SignatureVerificationService signatureVerificationService =
mock(SignatureVerificationService.class);
private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec);
private SignedBlsToExecutionChangeValidator validator;

Expand All @@ -54,9 +62,13 @@ public void beforeEach() {
Mockito.reset(spec, recentChainData);
when(recentChainData.getGenesisTime()).thenReturn(UInt64.ZERO);
when(recentChainData.getHeadSlot()).thenReturn(UInt64.ONE);
final BeaconState beaconState = mock(BeaconState.class);
when(beaconState.getSlot()).thenReturn(UInt64.ZERO);
when(recentChainData.getBestState())
.thenReturn(Optional.of(SafeFuture.completedFuture(mock(BeaconState.class))));
validator = new SignedBlsToExecutionChangeValidator(spec, timeProvider, recentChainData);
.thenReturn(Optional.of(SafeFuture.completedFuture(beaconState)));
validator =
new SignedBlsToExecutionChangeValidator(
spec, timeProvider, recentChainData, signatureVerificationService);
}

@Test
Expand Down Expand Up @@ -95,6 +107,7 @@ public void validateFullyShouldRejectMessageIfSpecValidationFails() {
dataStructureUtil.randomSignedBlsToExecutionChange();
final String expectedFailureDescription = "Spec validation failed";
mockSpecValidationFailed(expectedFailureDescription);
mockSignatureVerificationSucceeded(spec);

final SafeFuture<InternalValidationResult> validationResult =
validator.validateForGossip(signedBlsToExecutionChange);
Expand Down Expand Up @@ -133,7 +146,9 @@ public void validateForBlockInclusionShouldReturnEmptyIfSpecValidationSucceeds()
@Test
void validateForGossipShouldIgnoreGossipBeforeCapella() {
final Spec localSpec = TestSpecFactory.createMinimalBellatrix();
validator = new SignedBlsToExecutionChangeValidator(localSpec, timeProvider, recentChainData);
validator =
new SignedBlsToExecutionChangeValidator(
localSpec, timeProvider, recentChainData, signatureVerificationService);
SignedBlsToExecutionChange change = dataStructureUtil.randomSignedBlsToExecutionChange();

final SafeFuture<InternalValidationResult> future = validator.validateForGossip(change);
Expand All @@ -152,7 +167,8 @@ void validateForGossipShouldStartAcceptingGossipAfterCapellaUpgrade() {
final StubTimeProvider stubTimeProvider = StubTimeProvider.withTimeInSeconds(0);

validator =
new SignedBlsToExecutionChangeValidator(localSpec, stubTimeProvider, recentChainData);
new SignedBlsToExecutionChangeValidator(
localSpec, stubTimeProvider, recentChainData, signatureVerificationService);
final SignedBlsToExecutionChange change = dataStructureUtil.randomSignedBlsToExecutionChange();

// Ignore message because Capella has not activated yet
Expand Down Expand Up @@ -211,5 +227,17 @@ private void mockSignatureVerificationSucceeded(final Spec spec) {
any(BeaconState.class),
any(SignedBlsToExecutionChange.class),
eq(BLSSignatureVerifier.SIMPLE));

final SpecVersion specVersion = spy(spec.atSlot(UInt64.ZERO));
final OperationSignatureVerifier signatureVerifier = mock(OperationSignatureVerifier.class);

doReturn(specVersion).when(spec).atSlot(eq(UInt64.ZERO));
when(specVersion.operationSignatureVerifier()).thenReturn(signatureVerifier);
when(signatureVerifier.verifyBlsToExecutionChangeSignatureAsync(any(), any(), any()))
.thenReturn(SafeFuture.completedFuture(true));

doReturn(SafeFuture.completedFuture(true))
.when(signatureVerificationService)
.verify(any(BLSPublicKey.class), any(Bytes.class), any(BLSSignature.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ public void initAll() {
initForkChoice();
initBlockImporter();
initCombinedChainDataClient();
initSignatureVerificationService();
initAttestationPool();
initAttesterSlashingPool();
initProposerSlashingPool();
Expand All @@ -402,7 +403,6 @@ public void initAll() {
initEth1DataCache();
initDepositProvider();
initGenesisHandler();
initSignatureVerificationService();
initAttestationManager();
initPendingBlocks();
initBlockManager();
Expand Down Expand Up @@ -530,7 +530,8 @@ protected void initVoluntaryExitPool() {
protected void initSignedBlsToExecutionChangePool() {
LOG.debug("BeaconChainController.initSignedBlsToExecutionChangePool()");
final SignedBlsToExecutionChangeValidator validator =
new SignedBlsToExecutionChangeValidator(spec, timeProvider, recentChainData);
new SignedBlsToExecutionChangeValidator(
spec, timeProvider, recentChainData, signatureVerificationService);

blsToExecutionChangePool =
new OperationPool<>(
Expand All @@ -541,7 +542,7 @@ protected void initSignedBlsToExecutionChangePool() {
.andThen(Optional::orElseThrow)
.andThen(BeaconBlockBodySchemaCapella::getBlsToExecutionChangesSchema),
validator,
300_000);
16_384);
}

protected void initDataProvider() {
Expand Down

0 comments on commit be6bf0c

Please sign in to comment.