merge conflicts

This commit is contained in:
Eitan Seri-Levi
2025-05-27 14:56:02 -07:00
358 changed files with 11552 additions and 6768 deletions

View File

@@ -147,7 +147,7 @@ fn build_rpc_block(
RpcBlock::new_with_custody_columns(None, block, columns.clone(), columns.len(), spec)
.unwrap()
}
None => RpcBlock::new_without_blobs(None, block),
None => RpcBlock::new_without_blobs(None, block, 0),
}
}
@@ -370,6 +370,7 @@ async fn chain_segment_non_linear_parent_roots() {
blocks[3] = RpcBlock::new_without_blobs(
None,
Arc::new(SignedBeaconBlock::from_block(block, signature)),
harness.sampling_column_count,
);
assert!(
@@ -407,6 +408,7 @@ async fn chain_segment_non_linear_slots() {
blocks[3] = RpcBlock::new_without_blobs(
None,
Arc::new(SignedBeaconBlock::from_block(block, signature)),
harness.sampling_column_count,
);
assert!(
@@ -434,6 +436,7 @@ async fn chain_segment_non_linear_slots() {
blocks[3] = RpcBlock::new_without_blobs(
None,
Arc::new(SignedBeaconBlock::from_block(block, signature)),
harness.sampling_column_count,
);
assert!(
@@ -575,11 +578,16 @@ async fn invalid_signature_gossip_block() {
.into_block_error()
.expect("should import all blocks prior to the one being tested");
let signed_block = SignedBeaconBlock::from_block(block, junk_signature());
let rpc_block = RpcBlock::new_without_blobs(
None,
Arc::new(signed_block),
harness.sampling_column_count,
);
let process_res = harness
.chain
.process_block(
signed_block.canonical_root(),
Arc::new(signed_block),
rpc_block.block_root(),
rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -1550,12 +1558,13 @@ async fn add_base_block_to_altair_chain() {
));
// Ensure that it would be impossible to import via `BeaconChain::process_block`.
let base_rpc_block = RpcBlock::new_without_blobs(None, Arc::new(base_block.clone()), 0);
assert!(matches!(
harness
.chain
.process_block(
base_block.canonical_root(),
Arc::new(base_block.clone()),
base_rpc_block.block_root(),
base_rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -1573,7 +1582,7 @@ async fn add_base_block_to_altair_chain() {
harness
.chain
.process_chain_segment(
vec![RpcBlock::new_without_blobs(None, Arc::new(base_block))],
vec![RpcBlock::new_without_blobs(None, Arc::new(base_block), 0)],
NotifyExecutionLayer::Yes,
)
.await,
@@ -1686,12 +1695,13 @@ async fn add_altair_block_to_base_chain() {
));
// Ensure that it would be impossible to import via `BeaconChain::process_block`.
let altair_rpc_block = RpcBlock::new_without_blobs(None, Arc::new(altair_block.clone()), 0);
assert!(matches!(
harness
.chain
.process_block(
altair_block.canonical_root(),
Arc::new(altair_block.clone()),
altair_rpc_block.block_root(),
altair_rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -1709,7 +1719,7 @@ async fn add_altair_block_to_base_chain() {
harness
.chain
.process_chain_segment(
vec![RpcBlock::new_without_blobs(None, Arc::new(altair_block))],
vec![RpcBlock::new_without_blobs(None, Arc::new(altair_block), 0)],
NotifyExecutionLayer::Yes
)
.await,
@@ -1770,11 +1780,16 @@ async fn import_duplicate_block_unrealized_justification() {
// Create two verified variants of the block, representing the same block being processed in
// parallel.
let notify_execution_layer = NotifyExecutionLayer::Yes;
let verified_block1 = block
let rpc_block = RpcBlock::new_without_blobs(
Some(block_root),
block.clone(),
harness.sampling_column_count,
);
let verified_block1 = rpc_block
.clone()
.into_execution_pending_block(block_root, chain, notify_execution_layer)
.unwrap();
let verified_block2 = block
let verified_block2 = rpc_block
.into_execution_pending_block(block_root, chain, notify_execution_layer)
.unwrap();

View File

@@ -1,5 +1,6 @@
#![cfg(not(debug_assertions))]
use beacon_chain::block_verification_types::RpcBlock;
use beacon_chain::{
canonical_head::{CachedHead, CanonicalHead},
test_utils::{BeaconChainHarness, EphemeralHarnessType},
@@ -507,13 +508,11 @@ async fn justified_checkpoint_becomes_invalid() {
let is_valid = Payload::Invalid {
latest_valid_hash: Some(parent_hash_of_justified),
};
rig.import_block_parametric(is_valid, is_valid, None, |error| {
matches!(
error,
// The block import should fail since the beacon chain knows the justified payload
// is invalid.
BlockError::BeaconChainError(BeaconChainError::JustifiedPayloadInvalid { .. })
)
rig.import_block_parametric(is_valid, is_valid, None, |error| match error {
BlockError::BeaconChainError(e) => {
matches!(e.as_ref(), BeaconChainError::JustifiedPayloadInvalid { .. })
}
_ => false,
})
.await;
@@ -687,12 +686,14 @@ async fn invalidates_all_descendants() {
assert_eq!(fork_parent_state.slot(), fork_parent_slot);
let ((fork_block, _), _fork_post_state) =
rig.harness.make_block(fork_parent_state, fork_slot).await;
let fork_rpc_block =
RpcBlock::new_without_blobs(None, fork_block.clone(), rig.harness.sampling_column_count);
let fork_block_root = rig
.harness
.chain
.process_block(
fork_block.canonical_root(),
fork_block,
fork_rpc_block.block_root(),
fork_rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -788,12 +789,14 @@ async fn switches_heads() {
let ((fork_block, _), _fork_post_state) =
rig.harness.make_block(fork_parent_state, fork_slot).await;
let fork_parent_root = fork_block.parent_root();
let fork_rpc_block =
RpcBlock::new_without_blobs(None, fork_block.clone(), rig.harness.sampling_column_count);
let fork_block_root = rig
.harness
.chain
.process_block(
fork_block.canonical_root(),
fork_block,
fork_rpc_block.block_root(),
fork_rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -1057,8 +1060,10 @@ async fn invalid_parent() {
));
// Ensure the block built atop an invalid payload is invalid for import.
let rpc_block =
RpcBlock::new_without_blobs(None, block.clone(), rig.harness.sampling_column_count);
assert!(matches!(
rig.harness.chain.process_block(block.canonical_root(), block.clone(), NotifyExecutionLayer::Yes, BlockImportSource::Lookup,
rig.harness.chain.process_block(rpc_block.block_root(), rpc_block, NotifyExecutionLayer::Yes, BlockImportSource::Lookup,
|| Ok(()),
).await,
Err(BlockError::ParentExecutionPayloadInvalid { parent_root: invalid_root })
@@ -1380,11 +1385,13 @@ async fn recover_from_invalid_head_by_importing_blocks() {
} = InvalidHeadSetup::new().await;
// Import the fork block, it should become the head.
let fork_rpc_block =
RpcBlock::new_without_blobs(None, fork_block.clone(), rig.harness.sampling_column_count);
rig.harness
.chain
.process_block(
fork_block.canonical_root(),
fork_block.clone(),
fork_rpc_block.block_root(),
fork_rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -1419,8 +1426,8 @@ async fn recover_from_invalid_head_after_persist_and_reboot() {
let slot_clock = rig.harness.chain.slot_clock.clone();
// Forcefully persist the head and fork choice.
rig.harness.chain.persist_head_and_fork_choice().unwrap();
// Forcefully persist fork choice.
rig.harness.chain.persist_fork_choice().unwrap();
let resumed = BeaconChainHarness::builder(MainnetEthSpec)
.default_spec()

View File

@@ -254,6 +254,35 @@ async fn test_rewards_base_inactivity_leak_justification_epoch() {
);
}
#[tokio::test]
async fn test_rewards_electra_slashings() {
let spec = ForkName::Electra.make_genesis_spec(E::default_spec());
let harness = get_electra_harness(spec);
let state = harness.get_current_state();
harness.extend_slots(E::slots_per_epoch() as usize).await;
let mut initial_balances = harness.get_current_state().balances().to_vec();
// add an attester slashing and calculate slashing penalties
harness.add_attester_slashing(vec![0]).unwrap();
let slashed_balance_1 = initial_balances.get_mut(0).unwrap();
let validator_1_effective_balance = state.get_effective_balance(0).unwrap();
let delta_1 = validator_1_effective_balance
/ harness.spec.min_slashing_penalty_quotient_for_state(&state);
*slashed_balance_1 -= delta_1;
// add a proposer slashing and calculating slashing penalties
harness.add_proposer_slashing(1).unwrap();
let slashed_balance_2 = initial_balances.get_mut(1).unwrap();
let validator_2_effective_balance = state.get_effective_balance(1).unwrap();
let delta_2 = validator_2_effective_balance
/ harness.spec.min_slashing_penalty_quotient_for_state(&state);
*slashed_balance_2 -= delta_2;
check_all_electra_rewards(&harness, initial_balances).await;
}
#[tokio::test]
async fn test_rewards_base_slashings() {
let spec = ForkName::Base.make_genesis_spec(E::default_spec());
@@ -693,6 +722,75 @@ async fn test_rewards_base_subset_only() {
check_all_base_rewards_for_subset(&harness, initial_balances, validators_subset).await;
}
async fn check_all_electra_rewards(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
mut balances: Vec<u64>,
) {
let mut proposal_rewards_map = HashMap::new();
let mut sync_committee_rewards_map = HashMap::new();
for _ in 0..E::slots_per_epoch() {
let state = harness.get_current_state();
let slot = state.slot() + Slot::new(1);
// calculate beacon block rewards / penalties
let ((signed_block, _maybe_blob_sidecars), mut state) =
harness.make_block_return_pre_state(state, slot).await;
let beacon_block_reward = harness
.chain
.compute_beacon_block_reward(signed_block.message(), &mut state)
.unwrap();
let total_proposer_reward = proposal_rewards_map
.entry(beacon_block_reward.proposer_index)
.or_insert(0);
*total_proposer_reward += beacon_block_reward.total as i64;
// calculate sync committee rewards / penalties
let reward_payload = harness
.chain
.compute_sync_committee_rewards(signed_block.message(), &mut state)
.unwrap();
for reward in reward_payload {
let total_sync_reward = sync_committee_rewards_map
.entry(reward.validator_index)
.or_insert(0);
*total_sync_reward += reward.reward;
}
harness.extend_slots(1).await;
}
// compute reward deltas for all validators in epoch 0
let StandardAttestationRewards {
ideal_rewards,
total_rewards,
} = harness
.chain
.compute_attestation_rewards(Epoch::new(0), vec![])
.unwrap();
// assert ideal rewards are greater than 0
assert_eq!(
ideal_rewards.len() as u64,
harness.spec.max_effective_balance_electra / harness.spec.effective_balance_increment
);
assert!(ideal_rewards
.iter()
.all(|reward| reward.head > 0 && reward.target > 0 && reward.source > 0));
// apply attestation, proposal, and sync committee rewards and penalties to initial balances
apply_attestation_rewards(&mut balances, total_rewards);
apply_other_rewards(&mut balances, &proposal_rewards_map);
apply_other_rewards(&mut balances, &sync_committee_rewards_map);
// verify expected balances against actual balances
let actual_balances: Vec<u64> = harness.get_current_state().balances().to_vec();
assert_eq!(balances, actual_balances);
}
async fn check_all_base_rewards(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
balances: Vec<u64>,

View File

@@ -1,6 +1,7 @@
#![cfg(not(debug_assertions))]
use beacon_chain::attestation_verification::Error as AttnError;
use beacon_chain::block_verification_types::RpcBlock;
use beacon_chain::builder::BeaconChainBuilder;
use beacon_chain::data_availability_checker::AvailableBlock;
use beacon_chain::schema_change::migrate_schema;
@@ -16,6 +17,7 @@ use beacon_chain::{
};
use logging::create_test_tracing_subscriber;
use maplit::hashset;
use rand::rngs::StdRng;
use rand::Rng;
use slot_clock::{SlotClock, TestingSlotClock};
use state_processing::{state_advance::complete_state_advance, BlockReplayer};
@@ -31,7 +33,6 @@ use store::{
BlobInfo, DBColumn, HotColdDB, StoreConfig,
};
use tempfile::{tempdir, TempDir};
use tokio::time::sleep;
use types::test_utils::{SeedableRng, XorShiftRng};
use types::*;
@@ -120,6 +121,17 @@ fn get_harness_generic(
harness
}
fn count_states_descendant_of_block(
store: &HotColdDB<E, BeaconNodeBackend<E>, BeaconNodeBackend<E>>,
block_root: Hash256,
) -> usize {
let summaries = store.load_hot_state_summaries().unwrap();
summaries
.iter()
.filter(|(_, s)| s.latest_block_root == block_root)
.count()
}
#[tokio::test]
async fn light_client_bootstrap_test() {
let spec = test_spec::<E>();
@@ -166,7 +178,6 @@ async fn light_client_bootstrap_test() {
LightClientBootstrap::Capella(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
LightClientBootstrap::Deneb(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
LightClientBootstrap::Electra(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
LightClientBootstrap::Eip7805(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
LightClientBootstrap::Fulu(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
};
@@ -1226,7 +1237,7 @@ async fn prunes_abandoned_fork_between_two_finalized_checkpoints() {
assert_eq!(rig.get_finalized_checkpoints(), hashset! {});
assert!(rig.chain.knows_head(&stray_head));
rig.assert_knows_head(stray_head.into());
// Trigger finalization
let finalization_slots: Vec<Slot> = ((canonical_chain_slot + 1)
@@ -1274,7 +1285,7 @@ async fn prunes_abandoned_fork_between_two_finalized_checkpoints() {
);
}
assert!(!rig.chain.knows_head(&stray_head));
assert!(!rig.knows_head(&stray_head));
}
#[tokio::test]
@@ -1400,7 +1411,7 @@ async fn pruning_does_not_touch_abandoned_block_shared_with_canonical_chain() {
);
}
assert!(!rig.chain.knows_head(&stray_head));
assert!(!rig.knows_head(&stray_head));
let chain_dump = rig.chain.chain_dump().unwrap();
assert!(get_blocks(&chain_dump).contains(&shared_head));
}
@@ -1493,7 +1504,7 @@ async fn pruning_does_not_touch_blocks_prior_to_finalization() {
);
}
assert!(rig.chain.knows_head(&stray_head));
rig.assert_knows_head(stray_head.into());
}
#[tokio::test]
@@ -1577,7 +1588,7 @@ async fn prunes_fork_growing_past_youngest_finalized_checkpoint() {
// Precondition: Nothing is finalized yet
assert_eq!(rig.get_finalized_checkpoints(), hashset! {},);
assert!(rig.chain.knows_head(&stray_head));
rig.assert_knows_head(stray_head.into());
// Trigger finalization
let canonical_slots: Vec<Slot> = (rig.epoch_start_slot(2)..=rig.epoch_start_slot(6))
@@ -1632,7 +1643,7 @@ async fn prunes_fork_growing_past_youngest_finalized_checkpoint() {
);
}
assert!(!rig.chain.knows_head(&stray_head));
assert!(!rig.knows_head(&stray_head));
}
// This is to check if state outside of normal block processing are pruned correctly.
@@ -2151,64 +2162,6 @@ async fn pruning_test(
check_no_blocks_exist(&harness, stray_blocks.values());
}
#[tokio::test]
async fn garbage_collect_temp_states_from_failed_block_on_startup() {
let db_path = tempdir().unwrap();
// Wrap these functions to ensure the variables are dropped before we try to open another
// instance of the store.
let mut store = {
let store = get_store(&db_path);
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
let slots_per_epoch = E::slots_per_epoch();
let genesis_state = harness.get_current_state();
let block_slot = Slot::new(2 * slots_per_epoch);
let ((signed_block, _), state) = harness.make_block(genesis_state, block_slot).await;
let (mut block, _) = (*signed_block).clone().deconstruct();
// Mutate the block to make it invalid, and re-sign it.
*block.state_root_mut() = Hash256::repeat_byte(0xff);
let proposer_index = block.proposer_index() as usize;
let block = Arc::new(block.sign(
&harness.validator_keypairs[proposer_index].sk,
&state.fork(),
state.genesis_validators_root(),
&harness.spec,
));
// The block should be rejected, but should store a bunch of temporary states.
harness.set_current_slot(block_slot);
harness
.process_block_result((block, None))
.await
.unwrap_err();
assert_eq!(
store.iter_temporary_state_roots().count(),
block_slot.as_usize() - 1
);
store
};
// Wait until all the references to the store have been dropped, this helps ensure we can
// re-open the store later.
loop {
store = if let Err(store_arc) = Arc::try_unwrap(store) {
sleep(Duration::from_millis(500)).await;
store_arc
} else {
break;
}
}
// On startup, the store should garbage collect all the temporary states.
let store = get_store(&db_path);
assert_eq!(store.iter_temporary_state_roots().count(), 0);
}
#[tokio::test]
async fn garbage_collect_temp_states_from_failed_block_on_finalization() {
let db_path = tempdir().unwrap();
@@ -2223,6 +2176,7 @@ async fn garbage_collect_temp_states_from_failed_block_on_finalization() {
let ((signed_block, _), state) = harness.make_block(genesis_state, block_slot).await;
let (mut block, _) = (*signed_block).clone().deconstruct();
let bad_block_parent_root = block.parent_root();
// Mutate the block to make it invalid, and re-sign it.
*block.state_root_mut() = Hash256::repeat_byte(0xff);
@@ -2241,9 +2195,11 @@ async fn garbage_collect_temp_states_from_failed_block_on_finalization() {
.await
.unwrap_err();
// The bad block parent root is the genesis block root. There's `block_slot - 1` temporary
// states to remove + the genesis state = block_slot.
assert_eq!(
store.iter_temporary_state_roots().count(),
block_slot.as_usize() - 1
count_states_descendant_of_block(&store, bad_block_parent_root),
block_slot.as_usize(),
);
// Finalize the chain without the block, which should result in pruning of all temporary states.
@@ -2260,8 +2216,12 @@ async fn garbage_collect_temp_states_from_failed_block_on_finalization() {
// Check that the finalization migration ran.
assert_ne!(store.get_split_slot(), 0);
// Check that temporary states have been pruned.
assert_eq!(store.iter_temporary_state_roots().count(), 0);
// Check that temporary states have been pruned. The genesis block is not a descendant of the
// latest finalized checkpoint, so all its states have been pruned from the hot DB, = 0.
assert_eq!(
count_states_descendant_of_block(&store, bad_block_parent_root),
0
);
}
#[tokio::test]
@@ -2415,6 +2375,7 @@ async fn weak_subjectivity_sync_test(slots: Vec<Slot>, checkpoint_slot: Slot) {
.chain_config(ChainConfig::default())
.event_handler(Some(ServerSentEventHandler::new_with_capacity(1)))
.execution_layer(Some(mock.el))
.rng(Box::new(StdRng::seed_from_u64(42)))
.build()
.expect("should build");
@@ -2683,12 +2644,17 @@ async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
assert_eq!(split.block_root, valid_fork_block.parent_root());
assert_ne!(split.state_root, unadvanced_split_state_root);
let invalid_fork_rpc_block = RpcBlock::new_without_blobs(
None,
invalid_fork_block.clone(),
harness.sampling_column_count,
);
// Applying the invalid block should fail.
let err = harness
.chain
.process_block(
invalid_fork_block.canonical_root(),
invalid_fork_block.clone(),
invalid_fork_rpc_block.block_root(),
invalid_fork_rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -2698,11 +2664,16 @@ async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
assert!(matches!(err, BlockError::WouldRevertFinalizedSlot { .. }));
// Applying the valid block should succeed, but it should not become head.
let valid_fork_rpc_block = RpcBlock::new_without_blobs(
None,
valid_fork_block.clone(),
harness.sampling_column_count,
);
harness
.chain
.process_block(
valid_fork_block.canonical_root(),
valid_fork_block.clone(),
valid_fork_rpc_block.block_root(),
valid_fork_rpc_block,
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -2786,8 +2757,8 @@ async fn finalizes_after_resuming_from_db() {
harness
.chain
.persist_head_and_fork_choice()
.expect("should persist the head and fork choice");
.persist_fork_choice()
.expect("should persist fork choice");
harness
.chain
.persist_op_pool()
@@ -3000,11 +2971,13 @@ async fn revert_minority_fork_on_resume() {
resumed_harness.chain.recompute_head_at_current_slot().await;
assert_eq!(resumed_harness.head_slot(), fork_slot - 1);
// Head track should know the canonical head and the rogue head.
assert_eq!(resumed_harness.chain.heads().len(), 2);
assert!(resumed_harness
.chain
.knows_head(&resumed_harness.head_block_root().into()));
// Fork choice should only know the canonical head. When we reverted the head we also should
// have called `reset_fork_choice_to_finalization` which rebuilds fork choice from scratch
// without the reverted block.
assert_eq!(
resumed_harness.chain.heads(),
vec![(resumed_harness.head_block_root(), fork_slot - 1)]
);
// Apply blocks from the majority chain and trigger finalization.
let initial_split_slot = resumed_harness.chain.store.get_split_slot();