Skip to content

Commit 532d891

Browse files
committed
Merge remote-tracking branch 'origin/release-v8.0' into unstable
2 parents 2ad0251 + 9ddd2d8 commit 532d891

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

beacon_node/beacon_chain/src/block_verification.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,11 +2148,13 @@ pub fn verify_header_signature<T: BeaconChainTypes, Err: BlockBlobError>(
21482148
.get(header.message.proposer_index as usize)
21492149
.cloned()
21502150
.ok_or(Err::unknown_validator_error(header.message.proposer_index))?;
2151-
let head_fork = chain.canonical_head.cached_head().head_fork();
2151+
let fork = chain
2152+
.spec
2153+
.fork_at_epoch(header.message.slot.epoch(T::EthSpec::slots_per_epoch()));
21522154

21532155
if header.verify_signature::<T::EthSpec>(
21542156
&proposer_pubkey,
2155-
&head_fork,
2157+
&fork,
21562158
chain.genesis_validators_root,
21572159
&chain.spec,
21582160
) {

beacon_node/beacon_chain/src/canonical_head.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
918918
.start_slot(T::EthSpec::slots_per_epoch()),
919919
);
920920

921+
self.observed_column_sidecars.write().prune(
922+
new_view
923+
.finalized_checkpoint
924+
.epoch
925+
.start_slot(T::EthSpec::slots_per_epoch()),
926+
);
927+
921928
self.observed_slashable.write().prune(
922929
new_view
923930
.finalized_checkpoint

beacon_node/beacon_chain/tests/column_verification.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,96 @@ async fn rpc_columns_with_invalid_header_signature() {
116116
BlockError::InvalidSignature(InvalidSignature::ProposerSignature)
117117
));
118118
}
119+
120+
// Regression test for verify_header_signature bug: it uses head_fork() which is wrong for fork blocks
121+
#[tokio::test]
122+
async fn verify_header_signature_fork_block_bug() {
123+
// Create a spec with all forks enabled at genesis except Fulu which is at epoch 1
124+
// This allows us to easily create the scenario where the head is at Electra
125+
// but we're trying to verify a block from Fulu epoch
126+
let mut spec = test_spec::<E>();
127+
128+
// Only run this test for FORK_NAME=fulu.
129+
if !spec.is_fulu_scheduled() || spec.is_gloas_scheduled() {
130+
return;
131+
}
132+
133+
spec.altair_fork_epoch = Some(Epoch::new(0));
134+
spec.bellatrix_fork_epoch = Some(Epoch::new(0));
135+
spec.capella_fork_epoch = Some(Epoch::new(0));
136+
spec.deneb_fork_epoch = Some(Epoch::new(0));
137+
spec.electra_fork_epoch = Some(Epoch::new(0));
138+
let fulu_fork_epoch = Epoch::new(1);
139+
spec.fulu_fork_epoch = Some(fulu_fork_epoch);
140+
141+
let spec = Arc::new(spec);
142+
let harness = get_harness(VALIDATOR_COUNT, spec.clone(), NodeCustodyType::Supernode);
143+
harness.execution_block_generator().set_min_blob_count(1);
144+
145+
// Add some blocks in epoch 0 (Electra)
146+
harness
147+
.extend_chain(
148+
E::slots_per_epoch() as usize - 1,
149+
BlockStrategy::OnCanonicalHead,
150+
AttestationStrategy::AllValidators,
151+
)
152+
.await;
153+
154+
// Verify we're still in epoch 0 (Electra)
155+
let pre_fork_state = harness.get_current_state();
156+
assert_eq!(pre_fork_state.current_epoch(), Epoch::new(0));
157+
assert!(matches!(pre_fork_state, BeaconState::Electra(_)));
158+
159+
// Now produce a block at the first slot of epoch 1 (Fulu fork).
160+
// make_block will advance the state which will trigger the Electra->Fulu upgrade.
161+
let fork_slot = fulu_fork_epoch.start_slot(E::slots_per_epoch());
162+
let ((signed_block, opt_blobs), _state_root) =
163+
harness.make_block(pre_fork_state.clone(), fork_slot).await;
164+
let (_, blobs) = opt_blobs.expect("Blobs should be present");
165+
assert!(!blobs.is_empty(), "Block should have blobs");
166+
let block_root = signed_block.canonical_root();
167+
168+
// Process the block WITHOUT blobs to make it unavailable.
169+
// The block will be accepted but won't become the head because it's not fully available.
170+
// This keeps the head at the pre-fork state (Electra).
171+
harness.advance_slot();
172+
let rpc_block = harness
173+
.build_rpc_block_from_blobs(block_root, signed_block.clone(), None)
174+
.expect("Should build RPC block");
175+
let availability = harness
176+
.chain
177+
.process_block(
178+
block_root,
179+
rpc_block,
180+
NotifyExecutionLayer::Yes,
181+
BlockImportSource::RangeSync,
182+
|| Ok(()),
183+
)
184+
.await
185+
.expect("Block should be processed");
186+
assert_eq!(
187+
availability,
188+
AvailabilityProcessingStatus::MissingComponents(fork_slot, block_root),
189+
"Block should be pending availability"
190+
);
191+
192+
// The head should still be in epoch 0 (Electra) because the fork block isn't available
193+
let current_head_state = harness.get_current_state();
194+
assert_eq!(current_head_state.current_epoch(), Epoch::new(0));
195+
assert!(matches!(current_head_state, BeaconState::Electra(_)));
196+
197+
// Now try to process columns for the fork block.
198+
// The bug: verify_header_signature previously used head_fork() which fetched the fork from
199+
// the head state (still Electra fork), but the block was signed with the Fulu fork version.
200+
// This caused an incorrect signature verification failure.
201+
let data_column_sidecars =
202+
generate_data_column_sidecars_from_block(&signed_block, &harness.chain.spec);
203+
204+
// Now that the bug is fixed, the block should import.
205+
let status = harness
206+
.chain
207+
.process_rpc_custody_columns(data_column_sidecars)
208+
.await
209+
.unwrap();
210+
assert_eq!(status, AvailabilityProcessingStatus::Imported(block_root));
211+
}

0 commit comments

Comments
 (0)