From 8ce81578b7ee61aad396fd2a62a82497139a2570 Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Tue, 24 Feb 2026 10:46:46 -0800 Subject: [PATCH] introduce a smalll refactor and unit test --- .../gossip_verified_envelope.rs | 237 ++++++-- .../src/payload_envelope_verification/mod.rs | 3 - .../payload_envelope_verification/tests.rs | 524 ------------------ 3 files changed, 202 insertions(+), 562 deletions(-) delete mode 100644 beacon_node/beacon_chain/src/payload_envelope_verification/tests.rs diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index 492b265fd0..9d555e8ad2 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -10,8 +10,8 @@ use state_processing::{ use store::DatabaseBlock; use tracing::{Span, debug}; use types::{ - ChainSpec, EthSpec, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope, - consts::gloas::BUILDER_INDEX_SELF_BUILD, + ChainSpec, EthSpec, ExecutionPayloadBid, ExecutionPayloadEnvelope, Hash256, SignedBeaconBlock, + SignedExecutionPayloadEnvelope, Slot, consts::gloas::BUILDER_INDEX_SELF_BUILD, }; use crate::{ @@ -38,6 +38,54 @@ pub struct GossipVerificationContext<'a, T: BeaconChainTypes> { pub genesis_validators_root: Hash256, } +/// Verify that an execution payload envelope is consistent with its beacon block +/// and execution bid. This checks: +/// - The envelope slot is not prior to finalization +/// - The envelope slot matches the block slot +/// - The builder index matches the committed bid +/// - The payload block hash matches the committed bid +pub(crate) fn verify_envelope_consistency( + envelope: &ExecutionPayloadEnvelope, + block: &SignedBeaconBlock, + execution_bid: &ExecutionPayloadBid, + latest_finalized_slot: Slot, +) -> Result<(), EnvelopeError> { + // Check that the envelope's slot isn't from a slot prior + // to the latest finalized slot. + if envelope.slot < latest_finalized_slot { + return Err(EnvelopeError::PriorToFinalization { + payload_slot: envelope.slot, + latest_finalized_slot, + }); + } + + // Check that the slot of the envelope matches the slot of the parent block. + if envelope.slot != block.slot() { + return Err(EnvelopeError::SlotMismatch { + block: block.slot(), + envelope: envelope.slot, + }); + } + + // Builder index matches committed bid. + if envelope.builder_index != execution_bid.builder_index { + return Err(EnvelopeError::BuilderIndexMismatch { + committed_bid: execution_bid.builder_index, + envelope: envelope.builder_index, + }); + } + + // The block hash should match the block hash of the execution bid. + if envelope.payload.block_hash != execution_bid.block_hash { + return Err(EnvelopeError::BlockHashMismatch { + committed_bid: execution_bid.block_hash, + envelope: envelope.payload.block_hash, + }); + } + + Ok(()) +} + /// A wrapper around a `SignedExecutionPayloadEnvelope` that indicates it has been approved for re-gossiping on /// the p2p network. #[derive(Educe)] @@ -54,7 +102,6 @@ impl GossipVerifiedEnvelope { ctx: &GossipVerificationContext<'_, T>, ) -> Result { let envelope = &signed_envelope.message; - let payload = &envelope.payload; let beacon_block_root = envelope.beacon_block_root; // Check that we've seen the beacon block for this envelope and that it passes validation. @@ -94,38 +141,7 @@ impl GossipVerifiedEnvelope { .signed_execution_payload_bid()? .message; - // check that the envelopes slot isnt from a slot prior - // to the latest finalized slot. - if envelope.slot < latest_finalized_slot { - return Err(EnvelopeError::PriorToFinalization { - payload_slot: envelope.slot, - latest_finalized_slot, - }); - } - - // check that the slot of the envelope matches the slot of the parent block - if envelope.slot != block.slot() { - return Err(EnvelopeError::SlotMismatch { - block: block.slot(), - envelope: envelope.slot, - }); - } - - // builder index matches committed bid - if envelope.builder_index != execution_bid.builder_index { - return Err(EnvelopeError::BuilderIndexMismatch { - committed_bid: execution_bid.builder_index, - envelope: envelope.builder_index, - }); - } - - // the block hash should match the block hash of the execution bid - if payload.block_hash != execution_bid.block_hash { - return Err(EnvelopeError::BlockHashMismatch { - committed_bid: execution_bid.block_hash, - envelope: payload.block_hash, - }); - } + verify_envelope_consistency(envelope, &block, execution_bid, latest_finalized_slot)?; // Verify the envelope signature. // @@ -353,3 +369,154 @@ impl BeaconChain { .map_err(BeaconChainError::TokioJoin)? } } + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use bls::Signature; + use ssz_types::VariableList; + use types::{ + BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, Eth1Data, ExecutionBlockHash, + ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, + Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock, SignedExecutionPayloadBid, Slot, + SyncAggregate, + }; + + use super::verify_envelope_consistency; + use crate::payload_envelope_verification::EnvelopeError; + + type E = MinimalEthSpec; + + fn make_envelope( + slot: Slot, + builder_index: u64, + block_hash: ExecutionBlockHash, + ) -> ExecutionPayloadEnvelope { + ExecutionPayloadEnvelope { + payload: ExecutionPayloadGloas { + block_hash, + ..ExecutionPayloadGloas::default() + }, + execution_requests: ExecutionRequests::default(), + builder_index, + beacon_block_root: Hash256::ZERO, + slot, + state_root: Hash256::ZERO, + } + } + + fn make_block(slot: Slot) -> SignedBeaconBlock { + let block = BeaconBlock::Gloas(BeaconBlockGloas { + slot, + proposer_index: 0, + parent_root: Hash256::ZERO, + state_root: Hash256::ZERO, + body: BeaconBlockBodyGloas { + randao_reveal: Signature::empty(), + eth1_data: Eth1Data { + deposit_root: Hash256::ZERO, + block_hash: Hash256::ZERO, + deposit_count: 0, + }, + graffiti: Graffiti::default(), + proposer_slashings: VariableList::empty(), + attester_slashings: VariableList::empty(), + attestations: VariableList::empty(), + deposits: VariableList::empty(), + voluntary_exits: VariableList::empty(), + sync_aggregate: SyncAggregate::empty(), + bls_to_execution_changes: VariableList::empty(), + signed_execution_payload_bid: SignedExecutionPayloadBid::empty(), + payload_attestations: VariableList::empty(), + _phantom: PhantomData, + }, + }); + SignedBeaconBlock::from_block(block, Signature::empty()) + } + + fn make_bid(builder_index: u64, block_hash: ExecutionBlockHash) -> ExecutionPayloadBid { + ExecutionPayloadBid { + builder_index, + block_hash, + ..ExecutionPayloadBid::default() + } + } + + #[test] + fn test_valid_envelope() { + let slot = Slot::new(10); + let builder_index = 5; + let block_hash = ExecutionBlockHash::repeat_byte(0xaa); + + let envelope = make_envelope(slot, builder_index, block_hash); + let block = make_block(slot); + let bid = make_bid(builder_index, block_hash); + + assert!(verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)).is_ok()); + } + + #[test] + fn test_prior_to_finalization() { + let slot = Slot::new(5); + let builder_index = 1; + let block_hash = ExecutionBlockHash::repeat_byte(0xbb); + + let envelope = make_envelope(slot, builder_index, block_hash); + let block = make_block(slot); + let bid = make_bid(builder_index, block_hash); + let latest_finalized_slot = Slot::new(10); + + let result = + verify_envelope_consistency::(&envelope, &block, &bid, latest_finalized_slot); + assert!(matches!( + result, + Err(EnvelopeError::PriorToFinalization { .. }) + )); + } + + #[test] + fn test_slot_mismatch() { + let builder_index = 1; + let block_hash = ExecutionBlockHash::repeat_byte(0xcc); + + let envelope = make_envelope(Slot::new(10), builder_index, block_hash); + let block = make_block(Slot::new(20)); + let bid = make_bid(builder_index, block_hash); + + let result = verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)); + assert!(matches!(result, Err(EnvelopeError::SlotMismatch { .. }))); + } + + #[test] + fn test_builder_index_mismatch() { + let slot = Slot::new(10); + let block_hash = ExecutionBlockHash::repeat_byte(0xdd); + + let envelope = make_envelope(slot, 1, block_hash); + let block = make_block(slot); + let bid = make_bid(2, block_hash); + + let result = verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)); + assert!(matches!( + result, + Err(EnvelopeError::BuilderIndexMismatch { .. }) + )); + } + + #[test] + fn test_block_hash_mismatch() { + let slot = Slot::new(10); + let builder_index = 1; + + let envelope = make_envelope(slot, builder_index, ExecutionBlockHash::repeat_byte(0xee)); + let block = make_block(slot); + let bid = make_bid(builder_index, ExecutionBlockHash::repeat_byte(0xff)); + + let result = verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)); + assert!(matches!( + result, + Err(EnvelopeError::BlockHashMismatch { .. }) + )); + } +} diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs index 38fdd9f425..ae5dbfccc0 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs @@ -49,9 +49,6 @@ pub mod gossip_verified_envelope; pub mod import; mod payload_notifier; -#[cfg(test)] -mod tests; - pub trait IntoExecutionPendingEnvelope: Sized { fn into_execution_pending_envelope( self, diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/tests.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/tests.rs deleted file mode 100644 index c362bc6180..0000000000 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/tests.rs +++ /dev/null @@ -1,524 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; - -use bls::{FixedBytesExtended, Keypair, Signature}; -use fork_choice::ForkChoice; -use parking_lot::{Mutex, RwLock}; -use ssz_types::VariableList; -use store::{HotColdDB, KeyValueStore, MemoryStore, StoreConfig}; -use types::consts::gloas::BUILDER_INDEX_SELF_BUILD; -use types::test_utils::generate_deterministic_keypairs; -use types::*; - -use crate::BeaconStore; -use crate::beacon_fork_choice_store::BeaconForkChoiceStore; -use crate::beacon_proposer_cache::BeaconProposerCache; -use crate::builder::Witness; -use crate::canonical_head::CanonicalHead; -use crate::payload_envelope_verification::EnvelopeError; -use crate::payload_envelope_verification::gossip_verified_envelope::{ - GossipVerificationContext, GossipVerifiedEnvelope, -}; -use crate::validator_pubkey_cache::ValidatorPubkeyCache; - -type TestEthSpec = MinimalEthSpec; -type TestTypes = Witness< - slot_clock::TestingSlotClock, - TestEthSpec, - MemoryStore, - MemoryStore, ->; - -/// Test context that holds the minimal state needed for gossip verification. -struct TestContext { - store: BeaconStore, - canonical_head: CanonicalHead, - beacon_proposer_cache: Mutex, - validator_pubkey_cache: RwLock>, - spec: Arc, - keypairs: Vec, - genesis_state: BeaconState, - genesis_block_root: Hash256, - genesis_validators_root: Hash256, -} - -impl TestContext { - fn new(validator_count: usize) -> Self { - let spec = Arc::new(ForkName::Gloas.make_genesis_spec(ChainSpec::minimal())); - let keypairs = generate_deterministic_keypairs(validator_count); - - let mut genesis_state = genesis::interop_genesis_state::( - &keypairs, - 0, // genesis_time - Hash256::from_slice(&[0x42; 32]), - None, // no execution payload header - &spec, - ) - .expect("should create genesis state"); - - let genesis_validators_root = genesis_state.genesis_validators_root(); - - let store: BeaconStore = Arc::new( - HotColdDB::open_ephemeral(StoreConfig::default(), spec.clone()) - .expect("should create ephemeral store"), - ); - - // Initialize store metadata. - let genesis_block = BeaconBlock::::empty(&spec); - let genesis_block_root = genesis_block.canonical_root(); - let signed_genesis_block = SignedBeaconBlock::from_block(genesis_block, Signature::empty()); - - // Build caches and compute state root before storing. - genesis_state - .build_caches(&spec) - .expect("should build caches"); - - // Initialize store metadata ops (must be done before put_state). - let ops = vec![ - store - .init_anchor_info( - signed_genesis_block.parent_root(), - signed_genesis_block.slot(), - Slot::new(0), - false, - ) - .expect("should init anchor info"), - store - .init_blob_info(signed_genesis_block.slot()) - .expect("should init blob info"), - store - .init_data_column_info(signed_genesis_block.slot()) - .expect("should init data column info"), - ]; - store - .hot_db - .do_atomically(ops) - .expect("should store metadata"); - - // Store the genesis block and state. - store - .put_block(&genesis_block_root, signed_genesis_block.clone()) - .expect("should store genesis block"); - let state_root = genesis_state - .update_tree_hash_cache() - .expect("should compute state root"); - store - .put_state(&state_root, &genesis_state) - .expect("should store genesis state"); - - // Create BeaconSnapshot and fork choice. - let snapshot = crate::BeaconSnapshot { - beacon_block: Arc::new(signed_genesis_block), - beacon_block_root: genesis_block_root, - beacon_state: genesis_state.clone(), - }; - - let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store.clone(), snapshot.clone()) - .expect("should create fork choice store"); - - let fork_choice = ForkChoice::from_anchor( - fc_store, - genesis_block_root, - &snapshot.beacon_block, - &snapshot.beacon_state, - None, - &spec, - ) - .expect("should create fork choice from anchor"); - - let canonical_head = CanonicalHead::new(fork_choice, Arc::new(snapshot)); - - let validator_pubkey_cache = ValidatorPubkeyCache::new(&genesis_state, store.clone()) - .expect("should create validator pubkey cache"); - - TestContext { - store, - canonical_head, - beacon_proposer_cache: Mutex::new(BeaconProposerCache::default()), - validator_pubkey_cache: RwLock::new(validator_pubkey_cache), - spec, - keypairs, - genesis_state, - genesis_block_root, - genesis_validators_root, - } - } - - fn gossip_verification_context(&self) -> GossipVerificationContext<'_, TestTypes> { - GossipVerificationContext { - canonical_head: &self.canonical_head, - store: &self.store, - spec: &self.spec, - beacon_proposer_cache: &self.beacon_proposer_cache, - validator_pubkey_cache: &self.validator_pubkey_cache, - genesis_validators_root: self.genesis_validators_root, - } - } - - /// Build a gloas block at `slot` with the given proposer, store it, add it to fork choice, - /// and return the signed block, block root, and post-state. - fn build_and_import_block( - &self, - slot: Slot, - proposer_index: usize, - execution_bid: ExecutionPayloadBid, - ) -> ( - Arc>, - Hash256, - BeaconState, - ) { - let mut state = self.genesis_state.clone(); - - // Advance the state to the target slot. - if slot > state.slot() { - state_processing::state_advance::complete_state_advance( - &mut state, None, slot, &self.spec, - ) - .expect("should advance state"); - } - - state.build_caches(&self.spec).expect("should build caches"); - - // Compute the state root so we can embed it in the block. - let state_root = state - .update_tree_hash_cache() - .expect("should compute state root"); - - let signed_bid = SignedExecutionPayloadBid { - message: execution_bid, - signature: Signature::infinity().expect("should create infinity signature"), - }; - - // Create the block body with the actual state root. - let block = BeaconBlock::Gloas(BeaconBlockGloas { - slot, - proposer_index: proposer_index as u64, - parent_root: self.genesis_block_root, - state_root, - body: BeaconBlockBodyGloas { - randao_reveal: Signature::empty(), - eth1_data: state.eth1_data().clone(), - graffiti: Graffiti::default(), - proposer_slashings: VariableList::empty(), - attester_slashings: VariableList::empty(), - attestations: VariableList::empty(), - deposits: VariableList::empty(), - voluntary_exits: VariableList::empty(), - sync_aggregate: SyncAggregate::empty(), - bls_to_execution_changes: VariableList::empty(), - signed_execution_payload_bid: signed_bid, - payload_attestations: VariableList::empty(), - _phantom: std::marker::PhantomData, - }, - }); - - let block_root = block.canonical_root(); - let proposer_sk = &self.keypairs[proposer_index].sk; - let fork = self - .spec - .fork_at_epoch(slot.epoch(TestEthSpec::slots_per_epoch())); - let signed_block = block.sign(proposer_sk, &fork, self.genesis_validators_root, &self.spec); - - // Store block and state. - self.store - .put_block(&block_root, signed_block.clone()) - .expect("should store block"); - self.store - .put_state(&state_root, &state) - .expect("should store state"); - - // Add block to fork choice. - let mut fork_choice = self.canonical_head.fork_choice_write_lock(); - fork_choice - .on_block( - slot, - signed_block.message(), - block_root, - Duration::from_secs(0), - &state, - crate::PayloadVerificationStatus::Verified, - &self.spec, - ) - .expect("should add block to fork choice"); - drop(fork_choice); - - (Arc::new(signed_block), block_root, state) - } - - /// Build a signed execution payload envelope for the given block. - fn build_signed_envelope( - &self, - block_root: Hash256, - slot: Slot, - builder_index: u64, - block_hash: ExecutionBlockHash, - signing_key: &bls::SecretKey, - ) -> Arc> { - let mut payload = ExecutionPayloadGloas::default(); - payload.block_hash = block_hash; - - let envelope = ExecutionPayloadEnvelope { - payload, - execution_requests: ExecutionRequests::default(), - builder_index, - beacon_block_root: block_root, - slot, - state_root: Hash256::zero(), - }; - - let fork = self - .spec - .fork_at_epoch(slot.epoch(TestEthSpec::slots_per_epoch())); - let domain = self.spec.get_domain( - slot.epoch(TestEthSpec::slots_per_epoch()), - Domain::BeaconBuilder, - &fork, - self.genesis_validators_root, - ); - let message = envelope.signing_root(domain); - let signature = signing_key.sign(message); - - Arc::new(SignedExecutionPayloadEnvelope { - message: envelope, - signature, - }) - } - - /// Helper: build a block and matching self-build envelope. - fn build_block_and_envelope( - &self, - ) -> ( - Arc>, - Hash256, - Arc>, - ) { - let slot = Slot::new(1); - let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32])); - - // Get proposer for slot 1. - let mut state = self.genesis_state.clone(); - state_processing::state_advance::complete_state_advance(&mut state, None, slot, &self.spec) - .expect("should advance state"); - state.build_caches(&self.spec).expect("should build caches"); - let proposer_index = state - .get_beacon_proposer_index(slot, &self.spec) - .expect("should get proposer index"); - - let bid = ExecutionPayloadBid { - builder_index: BUILDER_INDEX_SELF_BUILD, - block_hash, - slot, - ..Default::default() - }; - - let (signed_block, block_root, _post_state) = - self.build_and_import_block(slot, proposer_index, bid); - - let proposer_sk = &self.keypairs[proposer_index].sk; - let envelope = self.build_signed_envelope( - block_root, - slot, - BUILDER_INDEX_SELF_BUILD, - block_hash, - proposer_sk, - ); - - (signed_block, block_root, envelope) - } -} - -#[test] -fn test_valid_self_build_envelope() { - let ctx = TestContext::new(32); - let (_block, _block_root, envelope) = ctx.build_block_and_envelope(); - let gossip_ctx = ctx.gossip_verification_context(); - - let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx); - assert!( - result.is_ok(), - "valid self-build envelope should pass verification, got: {:?}", - result.err() - ); -} - -#[test] -fn test_unknown_block_root() { - let ctx = TestContext::new(32); - let gossip_ctx = ctx.gossip_verification_context(); - - // Build an envelope referencing a block root not in fork choice. - let unknown_root = Hash256::from_slice(&[0xff; 32]); - let envelope = ctx.build_signed_envelope( - unknown_root, - Slot::new(1), - BUILDER_INDEX_SELF_BUILD, - ExecutionBlockHash::from_root(Hash256::zero()), - &ctx.keypairs[0].sk, - ); - - let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx); - assert!( - matches!(result, Err(EnvelopeError::BlockRootUnknown { .. })), - "should reject envelope with unknown block root, got: {:?}", - result - ); -} - -#[test] -fn test_slot_mismatch() { - let ctx = TestContext::new(32); - let (_block, block_root, _good_envelope) = ctx.build_block_and_envelope(); - let gossip_ctx = ctx.gossip_verification_context(); - - // Build an envelope with a different slot than the block. - let wrong_slot = Slot::new(2); - let envelope = ctx.build_signed_envelope( - block_root, - wrong_slot, - BUILDER_INDEX_SELF_BUILD, - ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32])), - &ctx.keypairs[0].sk, - ); - - let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx); - assert!( - matches!(result, Err(EnvelopeError::SlotMismatch { .. })), - "should reject envelope with slot mismatch, got: {:?}", - result - ); -} - -#[test] -fn test_builder_index_mismatch() { - let ctx = TestContext::new(32); - let gossip_ctx = ctx.gossip_verification_context(); - - let slot = Slot::new(1); - let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32])); - - // Get proposer for slot 1. - let mut state = ctx.genesis_state.clone(); - state_processing::state_advance::complete_state_advance(&mut state, None, slot, &ctx.spec) - .expect("should advance state"); - state.build_caches(&ctx.spec).expect("should build caches"); - let proposer_index = state - .get_beacon_proposer_index(slot, &ctx.spec) - .expect("should get proposer index"); - - // Block has builder_index = BUILDER_INDEX_SELF_BUILD - let bid = ExecutionPayloadBid { - builder_index: BUILDER_INDEX_SELF_BUILD, - block_hash, - slot, - ..Default::default() - }; - let (_block, block_root, _post_state) = ctx.build_and_import_block(slot, proposer_index, bid); - - // Envelope has a different builder_index. - let wrong_builder_index = 999; - let envelope = ctx.build_signed_envelope( - block_root, - slot, - wrong_builder_index, - block_hash, - &ctx.keypairs[proposer_index].sk, - ); - - let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx); - assert!( - matches!(result, Err(EnvelopeError::BuilderIndexMismatch { .. })), - "should reject envelope with builder index mismatch, got: {:?}", - result - ); -} - -#[test] -fn test_block_hash_mismatch() { - let ctx = TestContext::new(32); - let gossip_ctx = ctx.gossip_verification_context(); - - let slot = Slot::new(1); - let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32])); - let wrong_block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xbb; 32])); - - // Get proposer for slot 1. - let mut state = ctx.genesis_state.clone(); - state_processing::state_advance::complete_state_advance(&mut state, None, slot, &ctx.spec) - .expect("should advance state"); - state.build_caches(&ctx.spec).expect("should build caches"); - let proposer_index = state - .get_beacon_proposer_index(slot, &ctx.spec) - .expect("should get proposer index"); - - // Block has block_hash = 0xaa - let bid = ExecutionPayloadBid { - builder_index: BUILDER_INDEX_SELF_BUILD, - block_hash, - slot, - ..Default::default() - }; - let (_block, block_root, _post_state) = ctx.build_and_import_block(slot, proposer_index, bid); - - // Envelope has a different block_hash. - let envelope = ctx.build_signed_envelope( - block_root, - slot, - BUILDER_INDEX_SELF_BUILD, - wrong_block_hash, - &ctx.keypairs[proposer_index].sk, - ); - - let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx); - assert!( - matches!(result, Err(EnvelopeError::BlockHashMismatch { .. })), - "should reject envelope with block hash mismatch, got: {:?}", - result - ); -} - -#[test] -fn test_bad_signature() { - let ctx = TestContext::new(32); - let gossip_ctx = ctx.gossip_verification_context(); - - let slot = Slot::new(1); - let block_hash = ExecutionBlockHash::from_root(Hash256::from_slice(&[0xaa; 32])); - - // Get proposer for slot 1. - let mut state = ctx.genesis_state.clone(); - state_processing::state_advance::complete_state_advance(&mut state, None, slot, &ctx.spec) - .expect("should advance state"); - state.build_caches(&ctx.spec).expect("should build caches"); - let proposer_index = state - .get_beacon_proposer_index(slot, &ctx.spec) - .expect("should get proposer index"); - - let bid = ExecutionPayloadBid { - builder_index: BUILDER_INDEX_SELF_BUILD, - block_hash, - slot, - ..Default::default() - }; - let (_block, block_root, _post_state) = ctx.build_and_import_block(slot, proposer_index, bid); - - // Sign the envelope with the wrong key (some other validator's key). - let wrong_key_index = if proposer_index == 0 { 1 } else { 0 }; - let envelope = ctx.build_signed_envelope( - block_root, - slot, - BUILDER_INDEX_SELF_BUILD, - block_hash, - &ctx.keypairs[wrong_key_index].sk, - ); - - let result = GossipVerifiedEnvelope::new(envelope, &gossip_ctx); - assert!( - matches!(result, Err(EnvelopeError::BadSignature)), - "should reject envelope with bad signature, got: {:?}", - result - ); -} - -// NOTE: `test_prior_to_finalization` is omitted here because advancing finalization requires -// attestation-based justification which needs the full `BeaconChainHarness`. The -// `PriorToFinalization` code path is tested in the integration tests.