From d4e441e694c4de2dbc7b43f38a81163afc61ee99 Mon Sep 17 00:00:00 2001 From: tersec Date: Tue, 24 Sep 2024 09:01:40 +0000 Subject: [PATCH] add Electra attester slashing pool (#6579) --- AllTests-mainnet.md | 7 +- .../validator_change_pool.nim | 57 +++++++++++++---- .../gossip_processing/gossip_validation.nim | 6 +- beacon_chain/nimbus_beacon_node.nim | 8 ++- beacon_chain/rpc/rest_beacon_api.nim | 2 +- beacon_chain/spec/datatypes/capella.nim | 9 --- beacon_chain/spec/datatypes/electra.nim | 15 ++++- beacon_chain/spec/state_transition.nim | 6 +- tests/test_validator_change_pool.nim | 64 ++++++++++++++++--- 9 files changed, 131 insertions(+), 43 deletions(-) diff --git a/AllTests-mainnet.md b/AllTests-mainnet.md index d4135b985..8164a79cc 100644 --- a/AllTests-mainnet.md +++ b/AllTests-mainnet.md @@ -1008,14 +1008,15 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 OK: 14/14 Fail: 0/14 Skip: 0/14 ## Validator change pool testing suite ```diff -+ addValidatorChangeMessage/getAttesterSlashingMessage OK ++ addValidatorChangeMessage/getAttesterSlashingMessage (Electra) OK ++ addValidatorChangeMessage/getAttesterSlashingMessage (Phase 0) OK + addValidatorChangeMessage/getBlsToExecutionChange (post-capella) OK + addValidatorChangeMessage/getBlsToExecutionChange (pre-capella) OK + addValidatorChangeMessage/getProposerSlashingMessage OK + addValidatorChangeMessage/getVoluntaryExitMessage OK + pre-pre-fork voluntary exit OK ``` -OK: 6/6 Fail: 0/6 Skip: 0/6 +OK: 7/7 Fail: 0/7 Skip: 0/7 ## Validator pool ```diff + Doppelganger for genesis validator OK @@ -1125,4 +1126,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2 OK: 9/9 Fail: 0/9 Skip: 0/9 ---TOTAL--- -OK: 762/767 Fail: 0/767 Skip: 5/767 +OK: 763/768 Fail: 0/768 Skip: 5/768 diff --git a/beacon_chain/consensus_object_pools/validator_change_pool.nim b/beacon_chain/consensus_object_pools/validator_change_pool.nim index e369be853..f8c9bc68e 100644 --- a/beacon_chain/consensus_object_pools/validator_change_pool.nim +++ b/beacon_chain/consensus_object_pools/validator_change_pool.nim @@ -34,15 +34,20 @@ type proc(data: SignedBLSToExecutionChange) {.gcsafe, raises: [].} OnProposerSlashingCallback = proc(data: ProposerSlashing) {.gcsafe, raises: [].} - OnAttesterSlashingCallback = + OnPhase0AttesterSlashingCallback = proc(data: phase0.AttesterSlashing) {.gcsafe, raises: [].} + OnElectraAttesterSlashingCallback = + proc(data: electra.AttesterSlashing) {.gcsafe, raises: [].} ValidatorChangePool* = object ## The validator change pool tracks attester slashings, proposer slashings, ## voluntary exits, and BLS to execution changes that could be added to a ## proposed block. - attester_slashings*: Deque[phase0.AttesterSlashing] ## \ + phase0_attester_slashings*: Deque[phase0.AttesterSlashing] ## \ + ## Not a function of chain DAG branch; just used as a FIFO queue for blocks + + electra_attester_slashings*: Deque[electra.AttesterSlashing] ## \ ## Not a function of chain DAG branch; just used as a FIFO queue for blocks proposer_slashings*: Deque[ProposerSlashing] ## \ @@ -58,7 +63,8 @@ type ## Not a function of chain DAG branch; just used as a FIFO queue for blocks prior_seen_attester_slashed_indices: HashSet[uint64] ## \ - ## Records attester-slashed indices seen. + ## Records attester-slashed indices seen. Share these across attester + ## slashing types. prior_seen_proposer_slashed_indices: HashSet[uint64] ## \ ## Records proposer-slashed indices seen. @@ -74,20 +80,26 @@ type onVoluntaryExitReceived*: OnVoluntaryExitCallback onBLSToExecutionChangeReceived*: OnBLSToExecutionChangeCallback onProposerSlashingReceived*: OnProposerSlashingCallback - onAttesterSlashingReceived*: OnAttesterSlashingCallback + onPhase0AttesterSlashingReceived*: OnPhase0AttesterSlashingCallback + onElectraAttesterSlashingReceived*: OnElectraAttesterSlashingCallback func init*(T: type ValidatorChangePool, dag: ChainDAGRef, attestationPool: ref AttestationPool = nil, onVoluntaryExit: OnVoluntaryExitCallback = nil, onBLSToExecutionChange: OnBLSToExecutionChangeCallback = nil, onProposerSlashing: OnProposerSlashingCallback = nil, - onAttesterSlashing: OnAttesterSlashingCallback = nil): T = + onPhase0AttesterSlashing: OnPhase0AttesterSlashingCallback = nil, + onElectraAttesterSlashing: OnElectraAttesterSlashingCallback = nil): + T = ## Initialize an ValidatorChangePool from the dag `headState` T( # Allow filtering some validator change messages during block production - attester_slashings: + phase0_attester_slashings: initDeque[phase0.AttesterSlashing]( initialSize = ATTESTER_SLASHINGS_BOUND.int), + electra_attester_slashings: + initDeque[electra.AttesterSlashing]( + initialSize = ATTESTER_SLASHINGS_BOUND.int), proposer_slashings: initDeque[ProposerSlashing](initialSize = PROPOSER_SLASHINGS_BOUND.int), voluntary_exits: @@ -107,7 +119,8 @@ func init*(T: type ValidatorChangePool, dag: ChainDAGRef, onVoluntaryExitReceived: onVoluntaryExit, onBLSToExecutionChangeReceived: onBLSToExecutionChange, onProposerSlashingReceived: onProposerSlashing, - onAttesterSlashingReceived: onAttesterSlashing) + onPhase0AttesterSlashingReceived: onPhase0AttesterSlashing, + onElectraAttesterSlashingReceived: onElectraAttesterSlashing) func addValidatorChangeMessage( subpool: var auto, seenpool: var auto, validatorChangeMessage: auto, @@ -133,7 +146,9 @@ iterator getValidatorIndices( bls_to_execution_change: SignedBLSToExecutionChange): uint64 = yield bls_to_execution_change.message.validator_index -func isSeen*(pool: ValidatorChangePool, msg: phase0.AttesterSlashing): bool = +func isSeen*( + pool: ValidatorChangePool, + msg: phase0.AttesterSlashing | electra.AttesterSlashing): bool = for idx in getValidatorIndices(msg): # One index is enough! if idx notin pool.prior_seen_attester_slashed_indices: @@ -154,12 +169,23 @@ func isSeen*(pool: ValidatorChangePool, msg: SignedBLSToExecutionChange): bool = func addMessage*(pool: var ValidatorChangePool, msg: phase0.AttesterSlashing) = for idx in getValidatorIndices(msg): pool.prior_seen_attester_slashed_indices.incl idx - if pool.attestationPool != nil: + if not pool.attestationPool.isNil: let i = ValidatorIndex.init(idx).valueOr: continue pool.attestationPool.forkChoice.process_equivocation(i) - pool.attester_slashings.addValidatorChangeMessage( + pool.phase0_attester_slashings.addValidatorChangeMessage( + pool.prior_seen_attester_slashed_indices, msg, ATTESTER_SLASHINGS_BOUND) + +func addMessage*(pool: var ValidatorChangePool, msg: electra.AttesterSlashing) = + for idx in getValidatorIndices(msg): + pool.prior_seen_attester_slashed_indices.incl idx + if not pool.attestationPool.isNil: + let i = ValidatorIndex.init(idx).valueOr: + continue + pool.attestationPool.forkChoice.process_equivocation(i) + + pool.electra_attester_slashings.addValidatorChangeMessage( pool.prior_seen_attester_slashed_indices, msg, ATTESTER_SLASHINGS_BOUND) func addMessage*(pool: var ValidatorChangePool, msg: ProposerSlashing) = @@ -193,7 +219,7 @@ proc validateValidatorChangeMessage( check_proposer_slashing(state, msg, {}).isOk proc validateValidatorChangeMessage( cfg: RuntimeConfig, state: ForkyBeaconState, msg: - phase0.AttesterSlashing): bool = + phase0.AttesterSlashing | electra.AttesterSlashing): bool = check_attester_slashing(state, msg, {}).isOk proc validateValidatorChangeMessage( cfg: RuntimeConfig, state: ForkyBeaconState, msg: SignedVoluntaryExit): @@ -252,11 +278,13 @@ proc getBeaconBlockValidatorChanges*( res: BeaconBlockValidatorChanges getValidatorChangeMessagesForBlock( - pool.attester_slashings, cfg, state, indices, res.attester_slashings) + pool.phase0_attester_slashings, cfg, state, indices, + res.phase0_attester_slashings) getValidatorChangeMessagesForBlock( pool.proposer_slashings, cfg, state, indices, res.proposer_slashings) getValidatorChangeMessagesForBlock( pool.voluntary_exits, cfg, state, indices, res.voluntary_exits) + when typeof(state).kind >= ConsensusFork.Capella: # Prioritize these getValidatorChangeMessagesForBlock( @@ -267,4 +295,9 @@ proc getBeaconBlockValidatorChanges*( pool.bls_to_execution_changes_gossip, cfg, state, indices, res.bls_to_execution_changes) + when typeof(state).kind >= ConsensusFork.Electra: + getValidatorChangeMessagesForBlock( + pool.electra_attester_slashings, cfg, state, indices, + res.electra_attester_slashings) + res diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index 9382b173f..f8126c762 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -1246,8 +1246,10 @@ proc validateAttesterSlashing*( return pool.checkedReject(attester_slashing_validity.error) # Send notification about new attester slashing via callback - if not(isNil(pool.onAttesterSlashingReceived)): - pool.onAttesterSlashingReceived(attester_slashing) + if not(isNil(pool.onPhase0AttesterSlashingReceived)): + pool.onPhase0AttesterSlashingReceived(attester_slashing) + + debugComment "apparently there's no gopssip validation in place for electra attslashings" ok() diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index bf23606a3..482169dde 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -291,8 +291,11 @@ proc initFullNode( node.eventBus.blsToExecQueue.emit(data) proc onProposerSlashingAdded(data: ProposerSlashing) = node.eventBus.propSlashQueue.emit(data) - proc onAttesterSlashingAdded(data: phase0.AttesterSlashing) = + proc onPhase0AttesterSlashingAdded(data: phase0.AttesterSlashing) = node.eventBus.attSlashQueue.emit(data) + proc onElectraAttesterSlashingAdded(data: electra.AttesterSlashing) = + debugComment "electra att slasher queue" + discard proc onBlobSidecarAdded(data: BlobSidecarInfoObject) = node.eventBus.blobSidecarQueue.emit(data) proc onBlockAdded(data: ForkedTrustedSignedBeaconBlock) = @@ -392,7 +395,8 @@ proc initFullNode( LightClientPool()) validatorChangePool = newClone(ValidatorChangePool.init( dag, attestationPool, onVoluntaryExitAdded, onBLSToExecutionChangeAdded, - onProposerSlashingAdded, onAttesterSlashingAdded)) + onProposerSlashingAdded, onPhase0AttesterSlashingAdded, + onElectraAttesterSlashingAdded)) blobQuarantine = newClone(BlobQuarantine.init(onBlobSidecarAdded)) consensusManager = ConsensusManager.new( dag, attestationPool, quarantine, node.elManager, diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index 2808bf914..51640cf31 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -1353,7 +1353,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) = router.api2(MethodGet, "/eth/v1/beacon/pool/attester_slashings") do ( ) -> RestApiResponse: RestApiResponse.jsonResponse( - toSeq(node.validatorChangePool.attester_slashings)) + toSeq(node.validatorChangePool.phase0_attester_slashings)) # https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttesterSlashings router.api(MethodPost, "/eth/v1/beacon/pool/attester_slashings") do ( diff --git a/beacon_chain/spec/datatypes/capella.nim b/beacon_chain/spec/datatypes/capella.nim index 144a10e50..eac3c52bc 100644 --- a/beacon_chain/spec/datatypes/capella.nim +++ b/beacon_chain/spec/datatypes/capella.nim @@ -498,15 +498,6 @@ type SigVerifiedBeaconBlockBody | TrustedBeaconBlockBody - BeaconBlockValidatorChanges* = object - # Collection of exits that are suitable for block production - proposer_slashings*: List[ProposerSlashing, Limit MAX_PROPOSER_SLASHINGS] - attester_slashings*: - List[phase0.AttesterSlashing, Limit MAX_ATTESTER_SLASHINGS] - voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS] - bls_to_execution_changes*: - List[SignedBLSToExecutionChange, Limit MAX_BLS_TO_EXECUTION_CHANGES] - BeaconStateDiffPreSnapshot* = object eth1_data_votes_recent*: seq[Eth1Data] eth1_data_votes_len*: int diff --git a/beacon_chain/spec/datatypes/electra.nim b/beacon_chain/spec/datatypes/electra.nim index 49d5b70ca..42b6a356d 100644 --- a/beacon_chain/spec/datatypes/electra.nim +++ b/beacon_chain/spec/datatypes/electra.nim @@ -32,8 +32,8 @@ from ./altair import TrustedSyncAggregate, num_active_participants from ./bellatrix import BloomLogs, ExecutionAddress, Transaction from ./capella import - ExecutionBranch, HistoricalSummary, SignedBLSToExecutionChangeList, - Withdrawal, EXECUTION_PAYLOAD_GINDEX + ExecutionBranch, HistoricalSummary, SignedBLSToExecutionChange, + SignedBLSToExecutionChangeList, Withdrawal, EXECUTION_PAYLOAD_GINDEX from ./deneb import Blobs, BlobsBundle, KzgCommitments, KzgProofs export json_serialization, base, kzg4844 @@ -630,6 +630,17 @@ type kzg_proofs*: KzgProofs blobs*: Blobs + BeaconBlockValidatorChanges* = object + # Collection of exits that are suitable for block production + proposer_slashings*: List[ProposerSlashing, Limit MAX_PROPOSER_SLASHINGS] + phase0_attester_slashings*: + List[phase0.AttesterSlashing, Limit MAX_ATTESTER_SLASHINGS] + electra_attester_slashings*: + List[electra.AttesterSlashing, Limit MAX_ATTESTER_SLASHINGS_ELECTRA] + voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS] + bls_to_execution_changes*: + List[SignedBLSToExecutionChange, Limit MAX_BLS_TO_EXECUTION_CHANGES] + # TODO: There should be only a single generic HashedBeaconState definition func initHashedBeaconState*(s: BeaconState): HashedBeaconState = HashedBeaconState(data: s) diff --git a/beacon_chain/spec/state_transition.nim b/beacon_chain/spec/state_transition.nim index 568ae2158..a577ac896 100644 --- a/beacon_chain/spec/state_transition.nim +++ b/beacon_chain/spec/state_transition.nim @@ -375,7 +375,7 @@ func partialBeaconBlock*( eth1_data: eth1_data, graffiti: graffiti, proposer_slashings: validator_changes.proposer_slashings, - attester_slashings: validator_changes.attester_slashings, + attester_slashings: validator_changes.phase0_attester_slashings, attestations: List[phase0.Attestation, Limit MAX_ATTESTATIONS](attestations), deposits: List[Deposit, Limit MAX_DEPOSITS](deposits), @@ -500,7 +500,7 @@ proc makeBeaconBlockWithRewards*( hash_tree_root(eth1_data), hash_tree_root(graffiti), hash_tree_root(validator_changes.proposer_slashings), - hash_tree_root(validator_changes.attester_slashings), + hash_tree_root(validator_changes.phase0_attester_slashings), hash_tree_root( List[phase0.Attestation, Limit MAX_ATTESTATIONS]( attestations)), @@ -524,7 +524,7 @@ proc makeBeaconBlockWithRewards*( hash_tree_root(eth1_data), hash_tree_root(graffiti), hash_tree_root(validator_changes.proposer_slashings), - hash_tree_root(validator_changes.attester_slashings), + hash_tree_root(validator_changes.electra_attester_slashings), hash_tree_root( List[electra.Attestation, Limit MAX_ATTESTATIONS]( attestations)), diff --git a/tests/test_validator_change_pool.nim b/tests/test_validator_change_pool.nim index b78aa8cfc..9beb63658 100644 --- a/tests/test_validator_change_pool.nim +++ b/tests/test_validator_change_pool.nim @@ -27,7 +27,7 @@ func makeSignedBeaconBlockHeader( fork, genesis_validators_root, slot, hash_tree_root(tmp), MockPrivKeys[proposer_index]).toValidatorSig()) -func makeIndexedAttestation( +func makePhase0IndexedAttestation( fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot, validator_index: uint64, beacon_block_root: Eth2Digest): phase0.IndexedAttestation = @@ -41,6 +41,21 @@ func makeIndexedAttestation( fork, genesis_validators_root, tmp, MockPrivKeys[validator_index]).toValidatorSig) +func makeElectraIndexedAttestation( + fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot, + validator_index: uint64, beacon_block_root: Eth2Digest): + electra.IndexedAttestation = + let tmp = AttestationData(slot: slot, beacon_block_root: beacon_block_root) + + electra.IndexedAttestation( + data: tmp, + attesting_indices: + List[uint64, Limit MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]( + @[validator_index]), + signature: get_attestation_signature( + fork, genesis_validators_root, tmp, + MockPrivKeys[validator_index]).toValidatorSig) + func makeSignedVoluntaryExit( fork: Fork, genesis_validators_root: Eth2Digest, epoch: Epoch, validator_index: uint64): SignedVoluntaryExit = @@ -62,6 +77,8 @@ suite "Validator change pool testing suite": tmp.ALTAIR_FORK_EPOCH = Epoch(tmp.SHARD_COMMITTEE_PERIOD) tmp.BELLATRIX_FORK_EPOCH = Epoch(tmp.SHARD_COMMITTEE_PERIOD) + 1 tmp.CAPELLA_FORK_EPOCH = Epoch(tmp.SHARD_COMMITTEE_PERIOD) + 2 + tmp.DENEB_FORK_EPOCH = Epoch(tmp.SHARD_COMMITTEE_PERIOD) + 3 + tmp.ELECTRA_FORK_EPOCH = Epoch(tmp.SHARD_COMMITTEE_PERIOD) + 4 tmp validatorMonitor = newClone(ValidatorMonitor.init()) @@ -95,14 +112,14 @@ suite "Validator change pool testing suite": cfg, forkyState.data).proposer_slashings.lenu64 == min(i + 1, MAX_PROPOSER_SLASHINGS) - test "addValidatorChangeMessage/getAttesterSlashingMessage": + test "addValidatorChangeMessage/getAttesterSlashingMessage (Phase 0)": for i in 0'u64 .. MAX_ATTESTER_SLASHINGS + 5: for j in 0'u64 .. i: let msg = phase0.AttesterSlashing( - attestation_1: makeIndexedAttestation( + attestation_1: makePhase0IndexedAttestation( fork, genesis_validators_root, Slot(1), j, makeFakeHash(0)), - attestation_2: makeIndexedAttestation( + attestation_2: makePhase0IndexedAttestation( fork, genesis_validators_root, Slot(1), j, makeFakeHash(1))) if i == 0: @@ -113,9 +130,39 @@ suite "Validator change pool testing suite": withState(dag.headState): check: pool[].getBeaconBlockValidatorChanges( - cfg, forkyState.data).attester_slashings.lenu64 == + cfg, forkyState.data).phase0_attester_slashings.lenu64 == min(i + 1, MAX_ATTESTER_SLASHINGS) + test "addValidatorChangeMessage/getAttesterSlashingMessage (Electra)": + var + cache: StateCache + info: ForkedEpochInfo + process_slots( + dag.cfg, dag.headState, + Epoch(dag.cfg.SHARD_COMMITTEE_PERIOD).start_slot + 1 + SLOTS_PER_EPOCH * 5, + cache, info, {}).expect("ok") + let fork = dag.forkAtEpoch(dag.headState.get_current_epoch()) + + for i in 0'u64 .. MAX_ATTESTER_SLASHINGS_ELECTRA + 5: + for j in 0'u64 .. i: + let + msg = electra.AttesterSlashing( + attestation_1: makeElectraIndexedAttestation( + fork, genesis_validators_root, Slot(1), j, makeFakeHash(0)), + attestation_2: makeElectraIndexedAttestation( + fork, genesis_validators_root, Slot(1), j, makeFakeHash(1))) + + if i == 0: + check not pool[].isSeen(msg) + + pool[].addMessage(msg) + check: pool[].isSeen(msg) + withState(dag.headState): + check: + pool[].getBeaconBlockValidatorChanges( + cfg, forkyState.data).electra_attester_slashings.lenu64 == + min(i + 1, MAX_ATTESTER_SLASHINGS_ELECTRA) + test "addValidatorChangeMessage/getVoluntaryExitMessage": # Need to advance state or it will not accept voluntary exits var @@ -125,8 +172,7 @@ suite "Validator change pool testing suite": dag.cfg, dag.headState, Epoch(dag.cfg.SHARD_COMMITTEE_PERIOD).start_slot + 1, cache, info, {}).expect("ok") - let - fork = dag.forkAtEpoch(dag.headState.get_current_epoch()) + let fork = dag.forkAtEpoch(dag.headState.get_current_epoch()) for i in 0'u64 .. MAX_VOLUNTARY_EXITS + 5: for j in 0'u64 .. i: @@ -145,7 +191,7 @@ suite "Validator change pool testing suite": min(i + 1, MAX_VOLUNTARY_EXITS) test "addValidatorChangeMessage/getBlsToExecutionChange (pre-capella)": - # Need to advance state or it will not accept voluntary exits + # Need to advance state or it will not accept execution changes var cache: StateCache info: ForkedEpochInfo @@ -176,7 +222,7 @@ suite "Validator change pool testing suite": cfg, forkyState.data).bls_to_execution_changes.len == 0 test "addValidatorChangeMessage/getBlsToExecutionChange (post-capella)": - # Need to advance state or it will not accept voluntary exits + # Need to advance state or it will not accept execution changes var cache: StateCache info: ForkedEpochInfo