batch-verify BLS to execution change messages (#4637)

This commit is contained in:
tersec 2023-02-17 14:35:12 +01:00 committed by GitHub
parent 3d3d17aad9
commit a382498cfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 45 deletions

View File

@ -260,7 +260,7 @@ proc addHeadBlock*(
var sigs: seq[SignatureSet] var sigs: seq[SignatureSet]
if (let e = sigs.collectSignatureSets( if (let e = sigs.collectSignatureSets(
signedBlock, dag.db.immutableValidators, signedBlock, dag.db.immutableValidators,
dag.clearanceState, cache); e.isErr()): dag.clearanceState, dag.cfg.genesisFork(), cache); e.isErr()):
# A PublicKey or Signature isn't on the BLS12-381 curve # A PublicKey or Signature isn't on the BLS12-381 curve
info "Unable to load signature sets", info "Unable to load signature sets",
err = e.error() err = e.error()

View File

@ -15,6 +15,8 @@ import
../spec/[helpers, state_transition_block], ../spec/[helpers, state_transition_block],
"."/[attestation_pool, blockchain_dag] "."/[attestation_pool, blockchain_dag]
from ../spec/beaconstate import check_bls_to_execution_change
export base, deques, blockchain_dag export base, deques, blockchain_dag
const const
@ -184,7 +186,7 @@ proc validateValidatorChangeMessage(
proc validateValidatorChangeMessage( proc validateValidatorChangeMessage(
cfg: RuntimeConfig, state: ForkyBeaconState, cfg: RuntimeConfig, state: ForkyBeaconState,
msg: SignedBLSToExecutionChange): bool = msg: SignedBLSToExecutionChange): bool =
check_bls_to_execution_change(cfg, state, msg).isOk check_bls_to_execution_change(cfg.genesisFork, state, msg, {}).isOk
proc getValidatorChangeMessagesForBlock( proc getValidatorChangeMessagesForBlock(
subpool: var Deque, cfg: RuntimeConfig, state: ForkyBeaconState, subpool: var Deque, cfg: RuntimeConfig, state: ForkyBeaconState,

View File

@ -456,3 +456,39 @@ proc scheduleContributionChecks*(
contribution.beacon_block_root, contributionKey, contributionSig) contribution.beacon_block_root, contributionKey, contributionSig)
ok((aggregatorFut, proofFut, contributionFut, contributionSig)) ok((aggregatorFut, proofFut, contributionFut, contributionSig))
proc scheduleBlsToExecutionChangeCheck*(
batchCrypto: ref BatchCrypto,
genesisFork: Fork, genesis_validators_root: Eth2Digest,
signedBLSToExecutionChange: SignedBLSToExecutionChange): Result[tuple[
blsToExecutionFut: Future[BatchResult],
sig: CookedSig], cstring] =
## Schedule crypto verification of all signatures in a
## SignedBLSToExecutionChange message
##
## The buffer is processed:
## - when eager processing is enabled and the batch is full
## - otherwise after 10ms (BatchAttAccumTime)
##
## This returns an error if crypto sanity checks failed
## and a future with the deferred check otherwise.
# Must be genesis fork
doAssert genesis_fork.previous_version == genesis_fork.current_version
let
# Only called when matching already-known withdrawal credentials, so it's
# resistant to allowing loadWithCache DoSing
validatorChangePubkey =
signedBLSToExecutionChange.message.from_bls_pubkey.loadWithCache.valueOr:
return err("scheduleBlsToExecutionChangeCheck: cannot load BLS to withdrawals pubkey")
validatorChangeSig = signedBLSToExecutionChange.signature.load().valueOr:
return err("scheduleBlsToExecutionChangeCheck: invalid validator change signature")
validatorChangeFut = batchCrypto.withBatch("scheduleContributionAndProofChecks.contribution"):
bls_to_execution_change_signature_set(
genesis_fork, genesis_validators_root,
signedBLSToExecutionChange.message,
validatorChangePubkey, validatorChangeSig)
ok((validatorChangeFut, validatorChangeSig))

View File

@ -403,15 +403,17 @@ proc processSignedAggregateAndProof*(
err(v.error()) err(v.error())
proc processBlsToExecutionChange*( proc processBlsToExecutionChange*(
self: var Eth2Processor, src: MsgSource, self: ref Eth2Processor, src: MsgSource,
blsToExecutionChange: SignedBLSToExecutionChange): ValidationRes = blsToExecutionChange: SignedBLSToExecutionChange):
Future[ValidationRes] {.async.} =
logScope: logScope:
blsToExecutionChange = shortLog(blsToExecutionChange) blsToExecutionChange = shortLog(blsToExecutionChange)
debug "BLS to execution change received" debug "BLS to execution change received"
let v = self.validatorChangePool[].validateBlsToExecutionChange( let v = await self.validatorChangePool[].validateBlsToExecutionChange(
blsToExecutionChange, self.getCurrentBeaconTime().slotOrZero.epoch) self.batchCrypto, blsToExecutionChange,
self.getCurrentBeaconTime().slotOrZero.epoch)
if v.isOk(): if v.isOk():
trace "BLS to execution change validated" trace "BLS to execution change validated"
@ -422,7 +424,7 @@ proc processBlsToExecutionChange*(
debug "Dropping BLS to execution change", validationError = v.error debug "Dropping BLS to execution change", validationError = v.error
beacon_attester_slashings_dropped.inc(1, [$v.error[0]]) beacon_attester_slashings_dropped.inc(1, [$v.error[0]])
v return v
proc processAttesterSlashing*( proc processAttesterSlashing*(
self: var Eth2Processor, src: MsgSource, self: var Eth2Processor, src: MsgSource,

View File

@ -814,8 +814,9 @@ proc validateAggregate*(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/p2p-interface.md#bls_to_execution_change # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/p2p-interface.md#bls_to_execution_change
proc validateBlsToExecutionChange*( proc validateBlsToExecutionChange*(
pool: ValidatorChangePool, signed_address_change: SignedBLSToExecutionChange, pool: ValidatorChangePool, batchCrypto: ref BatchCrypto,
wallEpoch: Epoch): Result[void, ValidationError] = signed_address_change: SignedBLSToExecutionChange,
wallEpoch: Epoch): Future[Result[void, ValidationError]] {.async.} =
# [IGNORE] `current_epoch >= CAPELLA_FORK_EPOCH`, where `current_epoch` is # [IGNORE] `current_epoch >= CAPELLA_FORK_EPOCH`, where `current_epoch` is
# defined by the current wall-clock time. # defined by the current wall-clock time.
if not (wallEpoch >= pool.dag.cfg.CAPELLA_FORK_EPOCH): if not (wallEpoch >= pool.dag.cfg.CAPELLA_FORK_EPOCH):
@ -834,11 +835,28 @@ proc validateBlsToExecutionChange*(
return errIgnore("validateBlsToExecutionChange: can't validate against pre-Capella state") return errIgnore("validateBlsToExecutionChange: can't validate against pre-Capella state")
else: else:
let res = check_bls_to_execution_change( let res = check_bls_to_execution_change(
pool.dag.cfg, forkyState.data, signed_address_change) pool.dag.cfg.genesisFork, forkyState.data, signed_address_change,
{skipBlsValidation})
if res.isErr: if res.isErr:
return errReject(res.error) return errReject(res.error)
ok() # BLS to execution change signatures are batch-verified
let deferredCrypto = batchCrypto.scheduleBlsToExecutionChangeCheck(
pool.dag.cfg.genesisFork, pool.dag.genesis_validators_root,
signed_address_change)
if deferredCrypto.isErr():
return checkedReject(deferredCrypto.error)
let (cryptoFut, sig) = deferredCrypto.get()
case await cryptoFut
of BatchResult.Invalid:
return checkedReject("validateBlsToExecutionChange: invalid signature")
of BatchResult.Timeout:
return errIgnore("validateBlsToExecutionChange: timeout checking signature")
of BatchResult.Valid:
discard # keep going only in this case
return ok()
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/p2p-interface.md#attester_slashing # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/p2p-interface.md#attester_slashing
proc validateAttesterSlashing*( proc validateAttesterSlashing*(

View File

@ -1543,11 +1543,13 @@ proc installMessageValidators(node: BeaconNode) =
installSyncCommitteeeValidators(forkDigests.eip4844) installSyncCommitteeeValidators(forkDigests.eip4844)
template installBlsToExecutionChangeValidators(digest: auto) = template installBlsToExecutionChangeValidators(digest: auto) =
node.network.addValidator( node.network.addAsyncValidator(
getBlsToExecutionChangeTopic(digest), getBlsToExecutionChangeTopic(digest),
proc(msg: SignedBLSToExecutionChange): ValidationResult = proc(msg: SignedBLSToExecutionChange):
Future[ValidationResult] {.async.} =
return toValidationResult( return toValidationResult(
node.processor[].processBlsToExecutionChange(MsgSource.gossip, msg))) await node.processor.processBlsToExecutionChange(
MsgSource.gossip, msg)))
installBlsToExecutionChangeValidators(forkDigests.capella) installBlsToExecutionChangeValidators(forkDigests.capella)
if node.dag.cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH: if node.dag.cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH:

View File

@ -696,6 +696,35 @@ proc check_attestation*(
ok() ok()
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
proc check_bls_to_execution_change*(
genesisFork: Fork, state: capella.BeaconState | eip4844.BeaconState,
signed_address_change: SignedBLSToExecutionChange, flags: UpdateFlags):
Result[void, cstring] =
let address_change = signed_address_change.message
if not (address_change.validator_index < state.validators.lenu64):
return err("process_bls_to_execution_change: invalid validator index")
var withdrawal_credentials =
state.validators.item(address_change.validator_index).withdrawal_credentials
if not (withdrawal_credentials.data[0] == BLS_WITHDRAWAL_PREFIX):
return err("process_bls_to_execution_change: invalid withdrawal prefix")
if not (withdrawal_credentials.data.toOpenArray(1, 31) ==
eth2digest(address_change.from_bls_pubkey.blob).data.toOpenArray(1, 31)):
return err("process_bls_to_execution_change: invalid withdrawal credentials")
doAssert flags + {skipBlsValidation} == {skipBlsValidation}
if skipBlsValidation notin flags and
not verify_bls_to_execution_change_signature(
genesisFork, state.genesis_validators_root, signed_address_change,
address_change.from_bls_pubkey, signed_address_change.signature):
return err("process_bls_to_execution_change: invalid signature")
ok()
func get_proposer_reward*(state: ForkyBeaconState, func get_proposer_reward*(state: ForkyBeaconState,
attestation: SomeAttestation, attestation: SomeAttestation,
base_reward_per_increment: Gwei, base_reward_per_increment: Gwei,

View File

@ -368,12 +368,13 @@ proc verify_builder_signature*(
blsVerify(pubkey, signing_root.data, signature) blsVerify(pubkey, signing_root.data, signature)
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/beacon-chain.md#new-process_bls_to_execution_change # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
func compute_bls_to_execution_change_signing_root( func compute_bls_to_execution_change_signing_root*(
genesisFork: Fork, genesis_validators_root: Eth2Digest, genesisFork: Fork, genesis_validators_root: Eth2Digest,
msg: BLSToExecutionChange): Eth2Digest = msg: BLSToExecutionChange): Eth2Digest =
# So the epoch doesn't matter when calling get_domain # So the epoch doesn't matter when calling get_domain
doAssert genesisFork.previous_version == genesisFork.current_version doAssert genesisFork.previous_version == genesisFork.current_version
# Fork-agnostic domain since address changes are valid across forks
let domain = get_domain( let domain = get_domain(
genesisFork, DOMAIN_BLS_TO_EXECUTION_CHANGE, GENESIS_EPOCH, genesisFork, DOMAIN_BLS_TO_EXECUTION_CHANGE, GENESIS_EPOCH,
genesis_validators_root) genesis_validators_root)

View File

@ -202,11 +202,21 @@ func contribution_and_proof_signature_set*(
SignatureSet.init(pubkey, signing_root, signature) SignatureSet.init(pubkey, signing_root, signature)
func collectSignatureSets*( func bls_to_execution_change_signature_set*(
genesisFork: Fork, genesis_validators_root: Eth2Digest,
msg: BLSToExecutionChange,
pubkey: CookedPubKey, signature: CookedSig): SignatureSet =
let signing_root = compute_bls_to_execution_change_signing_root(
genesisFork, genesis_validators_root, msg)
SignatureSet.init(pubkey, signing_root, signature)
proc collectSignatureSets*(
sigs: var seq[SignatureSet], sigs: var seq[SignatureSet],
signed_block: ForkySignedBeaconBlock, signed_block: ForkySignedBeaconBlock,
validatorKeys: auto, validatorKeys: auto,
state: ForkedHashedBeaconState, state: ForkedHashedBeaconState,
genesis_fork: Fork,
cache: var StateCache): Result[void, cstring] = cache: var StateCache): Result[void, cstring] =
## Collect all signature verifications that process_block would normally do ## Collect all signature verifications that process_block would normally do
## except deposits, in one go. ## except deposits, in one go.
@ -218,7 +228,8 @@ func collectSignatureSets*(
## - Attester slashings ## - Attester slashings
## - Attestations ## - Attestations
## - VoluntaryExits ## - VoluntaryExits
## - SyncCommittee (altair+) ## - SyncCommittee (Altair+)
## - BLS to execution changes (Capella+)
## ##
## We do not include deposits as they can be invalid while still leaving the ## We do not include deposits as they can be invalid while still leaving the
## block valid ## block valid
@ -235,6 +246,8 @@ func collectSignatureSets*(
return err("collectSignatureSets: invalid proposer index") return err("collectSignatureSets: invalid proposer index")
epoch = signed_block.message.slot.epoch() epoch = signed_block.message.slot.epoch()
doAssert genesis_fork.previous_version == genesis_fork.current_version
# 1. Block proposer # 1. Block proposer
# ---------------------------------------------------- # ----------------------------------------------------
sigs.add block_signature_set( sigs.add block_signature_set(
@ -395,6 +408,27 @@ func collectSignatureSets*(
signed_block.message.body.sync_aggregate.sync_committee_signature.load().valueOr do: signed_block.message.body.sync_aggregate.sync_committee_signature.load().valueOr do:
return err("collectSignatureSets: cannot load signature")) return err("collectSignatureSets: cannot load signature"))
block:
# 8. BLS to execution changes
when typeof(signed_block).toFork() >= ConsensusFork.Capella:
withState(state):
when stateFork >= ConsensusFork.Capella:
for bls_change in signed_block.message.body.bls_to_execution_changes:
let sig = bls_change.signature.load.valueOr:
return err("collectSignatureSets: cannot load BLS to execution change signature")
# Otherwise, expensive loadWithCache can be spammed with irrelevant pubkeys
? check_bls_to_execution_change(
genesis_fork, forkyState.data, bls_change, {skipBlsValidation})
let validator_pubkey =
bls_change.message.from_bls_pubkey.loadWithCache.valueOr:
return err("collectSignatureSets: cannot load BLS to execution change pubkey")
sigs.add bls_to_execution_change_signature_set(
genesis_fork, genesis_validators_root, bls_change.message,
validator_pubkey, sig)
ok() ok()
proc batchVerify*(verifier: var BatchVerifier, sigs: openArray[SignatureSet]): bool = proc batchVerify*(verifier: var BatchVerifier, sigs: openArray[SignatureSet]): bool =

View File

@ -387,36 +387,11 @@ proc process_voluntary_exit*(
? initiate_validator_exit(cfg, state, exited_validator, cache) ? initiate_validator_exit(cfg, state, exited_validator, cache)
ok() ok()
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
proc check_bls_to_execution_change*(
cfg: RuntimeConfig, state: capella.BeaconState | eip4844.BeaconState,
signed_address_change: SignedBLSToExecutionChange): Result[void, cstring] =
let address_change = signed_address_change.message
if not (address_change.validator_index < state.validators.lenu64):
return err("process_bls_to_execution_change: invalid validator index")
var withdrawal_credentials =
state.validators.item(address_change.validator_index).withdrawal_credentials
if not (withdrawal_credentials.data[0] == BLS_WITHDRAWAL_PREFIX):
return err("process_bls_to_execution_change: invalid withdrawal prefix")
if not (withdrawal_credentials.data.toOpenArray(1, 31) ==
eth2digest(address_change.from_bls_pubkey.blob).data.toOpenArray(1, 31)):
return err("process_bls_to_execution_change: invalid withdrawal credentials")
if not verify_bls_to_execution_change_signature(
cfg.genesisFork, state.genesis_validators_root, signed_address_change,
address_change.from_bls_pubkey, signed_address_change.signature):
return err("process_bls_to_execution_change: invalid signature")
ok()
proc process_bls_to_execution_change*( proc process_bls_to_execution_change*(
cfg: RuntimeConfig, state: var (capella.BeaconState | eip4844.BeaconState), cfg: RuntimeConfig, state: var (capella.BeaconState | eip4844.BeaconState),
signed_address_change: SignedBLSToExecutionChange): Result[void, cstring] = signed_address_change: SignedBLSToExecutionChange): Result[void, cstring] =
? check_bls_to_execution_change(cfg, state, signed_address_change) ? check_bls_to_execution_change(
cfg.genesisFork, state, signed_address_change, {})
let address_change = signed_address_change.message let address_change = signed_address_change.message
var withdrawal_credentials = var withdrawal_credentials =
state.validators.item(address_change.validator_index).withdrawal_credentials state.validators.item(address_change.validator_index).withdrawal_credentials

View File

@ -459,7 +459,7 @@ proc routeBlsToExecutionChange*(
bls_to_execution_change: SignedBLSToExecutionChange): bls_to_execution_change: SignedBLSToExecutionChange):
Future[SendResult] {.async.} = Future[SendResult] {.async.} =
block: block:
let res = router[].processor[].processBlsToExecutionChange( let res = await router.processor.processBlsToExecutionChange(
MsgSource.api, bls_to_execution_change) MsgSource.api, bls_to_execution_change)
if not res.isGoodForSending: if not res.isGoodForSending:
warn "BLS to execution change request failed validation", warn "BLS to execution change request failed validation",