From 198e9002f443d64816bf6ba6489b35b69c7d8c15 Mon Sep 17 00:00:00 2001 From: Jun Song Date: Thu, 14 Aug 2025 11:33:06 +0900 Subject: [PATCH] feat: add Checkpoint container and match with spec --- crates/common/chain/lean/src/lean_chain.rs | 28 +++++--- crates/common/chain/lean/src/service.rs | 8 +-- crates/common/consensus/lean/src/block.rs | 2 +- .../common/consensus/lean/src/checkpoint.rs | 12 ++++ crates/common/consensus/lean/src/lib.rs | 68 +++++++++---------- crates/common/consensus/lean/src/state.rs | 14 ++-- crates/common/consensus/lean/src/vote.rs | 12 ++-- crates/common/validator/lean/src/service.rs | 2 +- 8 files changed, 77 insertions(+), 69 deletions(-) create mode 100644 crates/common/consensus/lean/src/checkpoint.rs diff --git a/crates/common/chain/lean/src/lean_chain.rs b/crates/common/chain/lean/src/lean_chain.rs index af241fae7..8c8893602 100644 --- a/crates/common/chain/lean/src/lean_chain.rs +++ b/crates/common/chain/lean/src/lean_chain.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use alloy_primitives::B256; use anyhow::anyhow; use ream_consensus_lean::{ - block::Block, get_fork_choice_head, get_latest_justified_hash, is_justifiable_slot, - process_block, state::LeanState, vote::Vote, + block::Block, checkpoint::Checkpoint, get_fork_choice_head, get_latest_justified_hash, + is_justifiable_slot, process_block, state::LeanState, vote::Vote, }; use ssz_types::VariableList; use tree_hash::TreeHash; @@ -58,7 +58,7 @@ impl LeanChain { pub fn latest_finalized_hash(&self) -> Option { self.post_states .get(&self.head) - .map(|state| state.latest_finalized_hash) + .map(|state| state.latest_finalized.root) } /// Compute the latest block that the staker is allowed to choose as the target @@ -119,7 +119,7 @@ impl LeanChain { .known_votes .clone() .into_iter() - .filter(|vote| vote.source == state.latest_justified_hash) + .filter(|vote| vote.source.root == state.latest_justified.root) .filter(|vote| !new_block.votes.contains(vote)) .collect::>(); @@ -173,7 +173,7 @@ impl LeanChain { // If the latest finalized slot is very far back, then only some slots are // valid to justify, make sure the target is one of those - while !is_justifiable_slot(&state.latest_finalized_slot, &target_block.slot) { + while !is_justifiable_slot(&state.latest_finalized.slot, &target_block.slot) { target_block = self.chain.get(&target_block.parent).ok_or_else(|| { anyhow!( "Block not found for target block's parent hash: {}", @@ -193,12 +193,18 @@ impl LeanChain { // IDs. validator_id: 0, slot: get_current_slot(), - head: self.head, - head_slot: head_block.slot, - target: target_block.tree_hash_root(), - target_slot: target_block.slot, - source: state.latest_justified_hash, - source_slot: state.latest_justified_slot, + head: Checkpoint { + root: self.head, + slot: head_block.slot, + }, + target: Checkpoint { + root: target_block.tree_hash_root(), + slot: target_block.slot, + }, + source: Checkpoint { + root: state.latest_justified.root, + slot: state.latest_justified.slot, + }, }) } diff --git a/crates/common/chain/lean/src/service.rs b/crates/common/chain/lean/src/service.rs index 08f7dac31..b7064ad87 100644 --- a/crates/common/chain/lean/src/service.rs +++ b/crates/common/chain/lean/src/service.rs @@ -104,13 +104,13 @@ impl LeanChainService { let vote = &signed_vote.data; info!( "Received signed vote from validator {} for head {:?} / source_slot {:?} at slot {}", - vote.validator_id, vote.head, vote.source_slot, vote.slot + vote.validator_id, vote.head, vote.source.slot, vote.slot ); } VoteItem::Unsigned(vote) => { info!( "Received unsigned vote from validator {} for head {:?} / source_slot {:?} at slot {}", - vote.validator_id, vote.head, vote.source_slot, vote.slot + vote.validator_id, vote.head, vote.source.slot, vote.slot ); } } @@ -183,7 +183,7 @@ impl LeanChainService { if is_known_vote || is_new_vote { // Do nothing - } else if lean_chain.chain.contains_key(&vote.head) { + } else if lean_chain.chain.contains_key(&vote.head.root) { drop(lean_chain); // We should acquire another write lock @@ -191,7 +191,7 @@ impl LeanChainService { lean_chain.new_votes.push(vote); } else { self.dependencies - .entry(vote.head) + .entry(vote.head.root) .or_default() .push(QueueItem::VoteItem(VoteItem::Unsigned(vote))); } diff --git a/crates/common/consensus/lean/src/block.rs b/crates/common/consensus/lean/src/block.rs index 0c55eb0f0..20a18fc7e 100644 --- a/crates/common/consensus/lean/src/block.rs +++ b/crates/common/consensus/lean/src/block.rs @@ -9,7 +9,7 @@ use crate::vote::Vote; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] pub struct SignedBlock { - pub message: Block, + pub data: Block, pub signature: PQSignature, } diff --git a/crates/common/consensus/lean/src/checkpoint.rs b/crates/common/consensus/lean/src/checkpoint.rs new file mode 100644 index 000000000..63445af38 --- /dev/null +++ b/crates/common/consensus/lean/src/checkpoint.rs @@ -0,0 +1,12 @@ +use alloy_primitives::B256; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, +)] +pub struct Checkpoint { + pub root: B256, + pub slot: u64, +} diff --git a/crates/common/consensus/lean/src/lib.rs b/crates/common/consensus/lean/src/lib.rs index 9762d0fe0..1883b3037 100644 --- a/crates/common/consensus/lean/src/lib.rs +++ b/crates/common/consensus/lean/src/lib.rs @@ -1,4 +1,5 @@ pub mod block; +pub mod checkpoint; pub mod config; pub mod state; pub mod vote; @@ -75,37 +76,37 @@ pub fn process_block(pre_state: &LeanState, block: &Block) -> anyhow::Result anyhow::Result) -> Option { post_states .values() - .max_by_key(|state| state.latest_justified_slot) - .map(|state| state.latest_justified_hash) + .max_by_key(|state| state.latest_justified.slot) + .map(|state| state.latest_justified.root) } /// Use LMD GHOST to get the head, given a particular root (usually the @@ -159,8 +160,8 @@ pub fn get_fork_choice_head( let mut vote_weights = HashMap::::new(); for vote in latest_votes.values() { - if blocks.contains_key(&vote.head) { - let mut block_hash = vote.head; + if blocks.contains_key(&vote.head.root) { + let mut block_hash = vote.head.root; while { let current_block = blocks .get(&block_hash) @@ -195,21 +196,16 @@ pub fn get_fork_choice_head( // choose the child with the most latest votes, tiebreaking by slot then hash let mut current_root = root; - loop { - match children_map.get(¤t_root) { - None => { - break Ok(current_root); - } - Some(children) => { - current_root = *children - .iter() - .max_by_key(|child_hash| { - let vote_weight = vote_weights.get(*child_hash).unwrap_or(&0); - let slot = blocks.get(*child_hash).map(|block| block.slot).unwrap_or(0); - (*vote_weight, slot, *(*child_hash)) - }) - .ok_or_else(|| anyhow!("No children found for current root: {current_root}"))?; - } - } + while let Some(children) = children_map.get(¤t_root) { + current_root = *children + .iter() + .max_by_key(|child_hash| { + let vote_weight = vote_weights.get(*child_hash).unwrap_or(&0); + let slot = blocks.get(*child_hash).map(|block| block.slot).unwrap_or(0); + (*vote_weight, slot, *(*child_hash)) + }) + .ok_or_else(|| anyhow!("No children found for current root: {current_root}"))?; } + + Ok(current_root) } diff --git a/crates/common/consensus/lean/src/state.rs b/crates/common/consensus/lean/src/state.rs index be08b46e9..8a228d6db 100644 --- a/crates/common/consensus/lean/src/state.rs +++ b/crates/common/consensus/lean/src/state.rs @@ -9,16 +9,14 @@ use ssz_types::{ }; use tree_hash_derive::TreeHash; -use crate::config::Config; +use crate::{checkpoint::Checkpoint, config::Config}; #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] pub struct LeanState { pub config: Config, - pub latest_justified_hash: B256, - pub latest_justified_slot: u64, - pub latest_finalized_hash: B256, - pub latest_finalized_slot: u64, + pub latest_justified: Checkpoint, + pub latest_finalized: Checkpoint, pub historical_block_hashes: VariableList, pub justified_slots: VariableList, @@ -36,10 +34,8 @@ impl LeanState { LeanState { config: Config { num_validators }, - latest_justified_hash: B256::ZERO, - latest_justified_slot: 0, - latest_finalized_hash: B256::ZERO, - latest_finalized_slot: 0, + latest_justified: Checkpoint::default(), + latest_finalized: Checkpoint::default(), historical_block_hashes: VariableList::empty(), justified_slots: VariableList::empty(), diff --git a/crates/common/consensus/lean/src/vote.rs b/crates/common/consensus/lean/src/vote.rs index 1169b5076..1c1664152 100644 --- a/crates/common/consensus/lean/src/vote.rs +++ b/crates/common/consensus/lean/src/vote.rs @@ -1,9 +1,10 @@ -use alloy_primitives::B256; use ream_pqc::PQSignature; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use tree_hash_derive::TreeHash; +use crate::checkpoint::Checkpoint; + #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] pub struct SignedVote { pub data: Vote, @@ -14,10 +15,7 @@ pub struct SignedVote { pub struct Vote { pub validator_id: u64, pub slot: u64, - pub head: B256, - pub head_slot: u64, - pub target: B256, - pub target_slot: u64, - pub source: B256, - pub source_slot: u64, + pub head: Checkpoint, + pub target: Checkpoint, + pub source: Checkpoint, } diff --git a/crates/common/validator/lean/src/service.rs b/crates/common/validator/lean/src/service.rs index c3aa21a01..4c86042fc 100644 --- a/crates/common/validator/lean/src/service.rs +++ b/crates/common/validator/lean/src/service.rs @@ -110,7 +110,7 @@ impl ValidatorService { // Build the vote from LeanChain, and modify its validator ID let vote_template = self.lean_chain.read().await.build_vote().expect("Failed to build vote"); - info!("Built vote template for head {:?} at slot {} with target {:?}", vote_template.head, vote_template.slot, vote_template.target); + info!("Built vote template for head {:?} at slot {} with target {:?}", vote_template.head, vote_template.slot, vote_template.target.slot); let votes = self.keystores.iter().map(|keystore| { let mut vote = vote_template.clone();