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
4 changes: 2 additions & 2 deletions bin/ream/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,11 @@ pub async fn run_lean_node(config: LeanNodeConfig, executor: ReamExecutor) {
let topics: Vec<LeanGossipTopic> = vec![
LeanGossipTopic {
fork: fork.clone(),
kind: LeanGossipTopicKind::LeanBlock,
kind: LeanGossipTopicKind::Block,
},
LeanGossipTopic {
fork,
kind: LeanGossipTopicKind::LeanVote,
kind: LeanGossipTopicKind::Vote,
},
];

Expand Down
22 changes: 15 additions & 7 deletions crates/common/chain/lean/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
use alloy_primitives::B256;
use ream_consensus_lean::{block::Block, state::LeanState};
use ream_consensus_lean::{
block::{Block, BlockBody},
state::LeanState,
};
use ream_network_spec::networks::lean_network_spec;
use ssz_types::VariableList;
use tree_hash::TreeHash;

fn genesis_block(state_root: B256) -> Block {
Block {
slot: 1,
parent: B256::ZERO,
votes: VariableList::empty(),
// Round-robin proposer selection for genesis
proposer_index: 1,
parent_root: B256::ZERO,
state_root,
body: BlockBody::default(),
}
}

fn genesis_state(num_validators: u64) -> LeanState {
LeanState::new(num_validators)
fn genesis_state(num_validators: u64, genesis_time: u64) -> LeanState {
LeanState::new(num_validators, genesis_time)
}

/// Setup the genesis block and state for the Lean chain.
///
/// Reference: https://github.com/ethereum/research/blob/d225a6775a9b184b5c1fd6c830cc58a375d9535f/3sf-mini/test_p2p.py#L119-L131
pub fn setup_genesis() -> (Block, LeanState) {
let mut genesis_state = genesis_state(lean_network_spec().num_validators);
let (num_validators, genesis_time) = {
let network_spec = lean_network_spec();
(network_spec.num_validators, network_spec.genesis_time)
};
let mut genesis_state = genesis_state(num_validators, genesis_time);
genesis_state
.historical_block_hashes
.push(B256::ZERO)
Expand Down
25 changes: 15 additions & 10 deletions crates/common/chain/lean/src/lean_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ use std::collections::HashMap;
use alloy_primitives::B256;
use anyhow::anyhow;
use ream_consensus_lean::{
block::Block, checkpoint::Checkpoint, get_fork_choice_head, get_latest_justified_hash,
is_justifiable_slot, process_block, state::LeanState, vote::Vote,
block::{Block, BlockBody},
checkpoint::Checkpoint,
get_fork_choice_head, get_latest_justified_hash, is_justifiable_slot, process_block,
state::LeanState,
vote::Vote,
};
use ream_metrics::{PROPOSE_BLOCK_TIME, start_timer_vec, stop_timer};
use ream_network_spec::networks::lean_network_spec;
use ream_sync::rwlock::{Reader, Writer};
use ssz_types::VariableList;
use tree_hash::TreeHash;

use crate::slot::get_current_slot;
Expand Down Expand Up @@ -108,10 +111,11 @@ impl LeanChain {
.ok_or_else(|| anyhow!("Post state not found for head: {}", self.head))?;
let mut new_block = Block {
slot,
parent: self.head,
votes: VariableList::empty(),
proposer_index: slot % lean_network_spec().num_validators,
parent_root: self.head,
// Diverged from Python implementation: Using `B256::ZERO` instead of `None`)
state_root: B256::ZERO,
body: BlockBody::default(),
};
stop_timer(initialize_block_timer);

Expand All @@ -127,7 +131,7 @@ impl LeanChain {
.clone()
.into_iter()
.filter(|vote| vote.source.root == state.latest_justified.root)
.filter(|vote| !new_block.votes.contains(vote))
.filter(|vote| !new_block.body.votes.contains(vote))
.collect::<Vec<_>>();

if new_votes_to_add.is_empty() {
Expand All @@ -136,6 +140,7 @@ impl LeanChain {

for vote in new_votes_to_add {
new_block
.body
.votes
.push(vote)
.map_err(|err| anyhow!("Failed to add vote to new_block: {err:?}"))?;
Expand Down Expand Up @@ -169,10 +174,10 @@ impl LeanChain {
anyhow!("Block not found for safe target hash: {}", self.safe_target)
})?;
if target_block.slot > safe_target_block.slot {
target_block = self.chain.get(&target_block.parent).ok_or_else(|| {
target_block = self.chain.get(&target_block.parent_root).ok_or_else(|| {
anyhow!(
"Block not found for target block's parent hash: {}",
target_block.parent
target_block.parent_root
)
})?;
}
Expand All @@ -181,10 +186,10 @@ 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) {
target_block = self.chain.get(&target_block.parent).ok_or_else(|| {
target_block = self.chain.get(&target_block.parent_root).ok_or_else(|| {
anyhow!(
"Block not found for target block's parent hash: {}",
target_block.parent
target_block.parent_root
)
})?;
}
Expand Down
8 changes: 4 additions & 4 deletions crates/common/chain/lean/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl LeanChainService {
let block_hash = block.tree_hash_root();
info!(
"Received block at slot {} with hash {block_hash:?} from parent {:?}",
block.slot, block.parent
block.slot, block.parent_root
);
self.handle_block(block).await
}
Expand Down Expand Up @@ -170,11 +170,11 @@ impl LeanChainService {
return Ok(());
}

match lean_chain.post_states.get(&block.parent) {
match lean_chain.post_states.get(&block.parent_root) {
Some(parent_state) => {
let state = process_block(parent_state, &block)?;

for vote in &block.votes {
for vote in &block.body.votes {
if !lean_chain.known_votes.contains(vote) {
lean_chain.known_votes.push(vote.clone());
}
Expand All @@ -199,7 +199,7 @@ impl LeanChainService {
// If we have not yet seen the block's parent, ignore for now,
// process later once we actually see the parent
self.dependencies
.entry(block.parent)
.entry(block.parent_root)
.or_default()
.push(QueueItem::Block(block));
}
Expand Down
26 changes: 23 additions & 3 deletions crates/common/consensus/lean/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,40 @@ use tree_hash_derive::TreeHash;

use crate::vote::Vote;

/// Represents a signed block in the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#signedblock)
/// for detailed protocol information.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct SignedBlock {
pub data: Block,
pub message: Block,
pub signature: PQSignature,
}

/// Represents a block in the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#block)
/// for detailed protocol information.
#[derive(
Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash,
)]
pub struct Block {
pub slot: u64,
pub proposer_index: u64,
// Diverged from Python implementation: Disallow `None` (uses `B256::ZERO` instead)
pub parent: B256,
pub votes: VariableList<Vote, U4096>,
pub parent_root: B256,
// Diverged from Python implementation: Disallow `None` (uses `B256::ZERO` instead)
pub state_root: B256,
pub body: BlockBody,
}

/// Represents the body of a block in the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#blockbody)
/// for detailed protocol information.
#[derive(
Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash,
)]
pub struct BlockBody {
pub votes: VariableList<Vote, U4096>,
}
4 changes: 4 additions & 0 deletions crates/common/consensus/lean/src/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use tree_hash_derive::TreeHash;

/// Represents a checkpoint in the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#checkpoint)
/// for detailed protocol information.
#[derive(
Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash,
)]
Expand Down
5 changes: 5 additions & 0 deletions crates/common/consensus/lean/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use tree_hash_derive::TreeHash;

/// Configuration for the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#config)
/// for detailed protocol information.
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct Config {
pub num_validators: u64,
pub genesis_time: u64,
}
17 changes: 11 additions & 6 deletions crates/common/consensus/lean/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ pub fn process_block(pre_state: &LeanState, block: &Block) -> anyhow::Result<Lea
// Track historical blocks in the state
state
.historical_block_hashes
.push(block.parent)
.map_err(|err| anyhow!("Failed to add block.parent to historical_block_hashes: {err:?}"))?;
.push(block.parent_root)
.map_err(|err| {
anyhow!("Failed to add block.parent_root to historical_block_hashes: {err:?}")
})?;
state
.justified_slots
.push(false)
Expand All @@ -69,7 +71,7 @@ pub fn process_block(pre_state: &LeanState, block: &Block) -> anyhow::Result<Lea
}

// Process votes
for vote in &block.votes {
for vote in &block.body.votes {
// Ignore votes whose source is not already justified,
// or whose target is not in the history, or whose target is not a
// valid justifiable slot
Expand Down Expand Up @@ -174,7 +176,7 @@ pub fn get_fork_choice_head(
vote_weights.insert(block_hash, current_weights + 1);
block_hash = blocks
.get(&block_hash)
.map(|block| block.parent)
.map(|block| block.parent_root)
.ok_or_else(|| anyhow!("Block not found for block parent: {block_hash}"))?;
}
}
Expand All @@ -186,8 +188,11 @@ pub fn get_fork_choice_head(
for (hash, block) in blocks {
// Original Python impl uses `block.parent` to imply that the block has a parent,
// So for Rust, we use `block.parent != B256::ZERO` instead.
if block.parent != B256::ZERO && *vote_weights.get(hash).unwrap_or(&0) >= min_score {
children_map.entry(block.parent).or_default().push(*hash);
if block.parent_root != B256::ZERO && *vote_weights.get(hash).unwrap_or(&0) >= min_score {
children_map
.entry(block.parent_root)
.or_default()
.push(*hash);
}
}

Expand Down
23 changes: 13 additions & 10 deletions crates/common/consensus/lean/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ use tree_hash_derive::TreeHash;

use crate::{checkpoint::Checkpoint, config::Config};

/// Represents the state of the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#state)
/// for detailed protocol information.
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct LeanState {
pub config: Config,
Expand All @@ -21,18 +25,17 @@ pub struct LeanState {
pub historical_block_hashes: VariableList<B256, U262144>,
pub justified_slots: VariableList<bool, U262144>,

// Diverged from Python implementation:
// Originally `justifications: Dict[str, List[bool]]`
pub justifications_roots: VariableList<B256, U262144>,
// The size is MAX_HISTORICAL_BLOCK_HASHES * VALIDATOR_REGISTRY_LIMIT
// to accommodate equivalent to `justifications[root][validator_id]`
pub justifications_roots_validators: BitList<U1073741824>,
}

impl LeanState {
pub fn new(num_validators: u64) -> LeanState {
pub fn new(num_validators: u64, genesis_time: u64) -> LeanState {
LeanState {
config: Config { num_validators },
config: Config {
num_validators,
genesis_time,
},

latest_justified: Checkpoint::default(),
latest_finalized: Checkpoint::default(),
Expand Down Expand Up @@ -171,7 +174,7 @@ mod test {

#[test]
fn initialize_justifications_for_root() {
let mut state = LeanState::new(1);
let mut state = LeanState::new(1, 0);

// Initialize 1st root
state
Expand Down Expand Up @@ -206,7 +209,7 @@ mod test {

#[test]
fn set_justification() {
let mut state = LeanState::new(1);
let mut state = LeanState::new(1, 0);
let root0 = B256::repeat_byte(1);
let root1 = B256::repeat_byte(2);
let validator_id = 7u64;
Expand Down Expand Up @@ -238,7 +241,7 @@ mod test {

#[test]
fn count_justifications() {
let mut state = LeanState::new(1);
let mut state = LeanState::new(1, 0);
let root0 = B256::repeat_byte(1);
let root1 = B256::repeat_byte(2);

Expand All @@ -265,7 +268,7 @@ mod test {
#[test]
fn remove_justifications() {
// Assuming 3 roots & 4 validators
let mut state = LeanState::new(3);
let mut state = LeanState::new(3, 0);
let root0 = B256::repeat_byte(1);
let root1 = B256::repeat_byte(2);
let root2 = B256::repeat_byte(3);
Expand Down
8 changes: 8 additions & 0 deletions crates/common/consensus/lean/src/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ use tree_hash_derive::TreeHash;

use crate::checkpoint::Checkpoint;

/// Represents a signed vote in the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#signedvote)
/// for detailed protocol information.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct SignedVote {
pub data: Vote,
pub signature: PQSignature,
}

/// Represents a vote in the Lean chain.
///
/// See the [Lean specification](https://github.com/leanEthereum/leanSpec/blob/main/docs/client/containers.md#vote)
/// for detailed protocol information.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct Vote {
pub validator_id: u64,
Expand Down
4 changes: 2 additions & 2 deletions crates/common/validator/lean/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ impl ValidatorService {
"Validator {} built block: slot={}, parent={:?}, votes={}, state_root={:?}",
keystore.id,
new_block.slot,
new_block.parent,
new_block.votes.len(),
new_block.parent_root,
new_block.body.votes.len(),
new_block.state_root
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Default for LeanGossipsubConfig {
.mesh_n_low(6)
.mesh_n_high(12)
.gossip_lazy(6)
.history_length(12)
.history_length(6)
.history_gossip(3)
.max_messages_per_rpc(Some(500))
.duplicate_cache_time(Duration::from_secs(
Expand Down
Loading