Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/blockchain/state_transition/src/beacon/fork_choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5639,7 +5639,7 @@ pub fn on_attester_slashing(store: &mut Store, attester_slashing: &AttesterSlash
}

#[cfg(test)]
mod tests {
pub(crate) mod tests {
use ethlambda_storage::backend::InMemoryBackend;

use super::*;
Expand All @@ -5653,7 +5653,7 @@ mod tests {
/// A store backed by a fresh in-memory backend, with every checkpoint at
/// its default (genesis) value and no anchor block or state written.
/// Tests populate only what the function under test actually reads.
fn empty_store() -> Store {
pub(crate) fn empty_store() -> Store {
store_anchored_at(Root::ZERO)
}

Expand Down Expand Up @@ -5688,7 +5688,7 @@ mod tests {
/// A signed block with an empty body and a zero signature, for tests that
/// only care about `slot` and `parent_root`. Phase0-shaped since nothing
/// under test here reads anything fork-specific.
fn block(slot: Slot, parent_root: Root) -> SignedBeaconBlock {
pub(crate) fn block(slot: Slot, parent_root: Root) -> SignedBeaconBlock {
SignedBeaconBlock::Phase0(phase0::SignedBeaconBlock {
message: phase0::BeaconBlock {
slot,
Expand Down
93 changes: 92 additions & 1 deletion crates/blockchain/state_transition/src/beacon/inclusion_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,31 @@ pub fn transactions_size(inclusion_list: &InclusionList) -> u64 {
/// head is `head_root`: `get_shuffling_dependent_root(store, head_root,
/// epoch(slot))`. What a committee member signs over, and the key a proposer
/// or builder reads the previous slot's lists under.
///
/// The walk continues past the live-chain index through the stored blocks.
/// The index starts at the finalized block, while the lists of an epoch's last
/// slot depend on the block just below it once the previous epoch finalizes at
/// the boundary, so the index walk alone answered with the finalized block and
/// every epoch's first slot read no lists. Stored blocks are never pruned, so
/// they can finish the walk. A step onto a root the store does not hold (a
/// checkpoint anchor's parent) ends it there, which is the true ancestor when
/// the dependent slot is the one just below the anchor.
pub fn inclusion_list_dependent_root(store: &Store, head_root: Root, slot: Slot) -> Root {
let epoch = compute_epoch_at_slot(slot);
let dependent_slot = compute_shuffling_dependent_slot(epoch);
let index = store.block_index();
get_shuffling_dependent_root(&index, head_root, compute_epoch_at_slot(slot))
let mut root = get_shuffling_dependent_root(&index, head_root, epoch);
loop {
// The index first: `block_entry` decodes the whole block.
let entry = index
.get(&root)
.copied()
.or_else(|| store.block_entry(&root));
match entry {
Some((block_slot, parent_root)) if block_slot > dependent_slot => root = parent_root,
_ => return root,
}
}
}

/// A state that answers `get_inclusion_list_committee` for a list at `slot`
Expand Down Expand Up @@ -295,3 +317,72 @@ pub fn payload_inclusion_list_transactions(
.transactions(slot, dependent_root, true),
)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::beacon::fork_choice::tests::{block, empty_store};

/// The slot finality prunes the live chain to, and the last slot of the
/// epoch two past it, whose dependent slot is the one just below it.
const FINALIZED: Slot = 128;
const LAST_SLOT: Slot = 191;

/// A store holding one block per `slots` entry, each the parent of the
/// next, written the way fork choice writes them (`BlockHeaders` and
/// `LiveChain` both get a row). Returns the roots in the order given.
fn chain_store(slots: &[Slot]) -> (Store, Vec<Root>) {
let mut store = empty_store();
let mut parent = Root::ZERO;
let mut roots = Vec::new();
for (position, &slot) in slots.iter().enumerate() {
let signed = block(slot, parent);
let root = Root::repeat_byte(position as u8 + 1);
store
.insert_signed_block(root, signed)
.expect("insert block");
roots.push(root);
parent = root;
}
(store, roots)
}

#[test]
fn the_dependent_root_below_a_pruned_live_chain_is_the_real_ancestor() {
let (mut store, roots) = chain_store(&[0, 100, 127, 128, 150, 191]);
assert_eq!(
compute_shuffling_dependent_slot(compute_epoch_at_slot(LAST_SLOT)),
FINALIZED - 1
);
store.prune_live_chain(FINALIZED).expect("prune");

let dependent = inclusion_list_dependent_root(&store, roots[5], LAST_SLOT);

assert_eq!(dependent, roots[2], "the block at the dependent slot");
}

#[test]
fn a_skipped_dependent_slot_resolves_to_the_block_before_it_past_the_prune() {
let (mut store, roots) = chain_store(&[0, 100, 120, 128, 150, 191]);
store.prune_live_chain(FINALIZED).expect("prune");

let dependent = inclusion_list_dependent_root(&store, roots[5], LAST_SLOT);

assert_eq!(dependent, roots[2], "the latest block at or below the slot");
}

#[test]
fn an_unpruned_live_chain_answers_as_the_index_walk_does() {
let (store, roots) = chain_store(&[0, 100, 127, 128, 150, 191]);
let index = store.block_index();
let epoch = compute_epoch_at_slot(LAST_SLOT);

let dependent = inclusion_list_dependent_root(&store, roots[5], LAST_SLOT);

assert_eq!(dependent, roots[2]);
assert_eq!(
dependent,
get_shuffling_dependent_root(&index, roots[5], epoch)
);
}
}
Loading