Skip to content

Commit 39e7db5

Browse files
syjn99allnil
authored andcommitted
feat: add Checkpoint container and match with spec (ReamLabs#701)
1 parent 4fe26c2 commit 39e7db5

File tree

8 files changed

+77
-69
lines changed

8 files changed

+77
-69
lines changed

crates/common/chain/lean/src/lean_chain.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::collections::HashMap;
33
use alloy_primitives::B256;
44
use anyhow::anyhow;
55
use ream_consensus_lean::{
6-
block::Block, get_fork_choice_head, get_latest_justified_hash, is_justifiable_slot,
7-
process_block, state::LeanState, vote::Vote,
6+
block::Block, checkpoint::Checkpoint, get_fork_choice_head, get_latest_justified_hash,
7+
is_justifiable_slot, process_block, state::LeanState, vote::Vote,
88
};
99
use ssz_types::VariableList;
1010
use tree_hash::TreeHash;
@@ -58,7 +58,7 @@ impl LeanChain {
5858
pub fn latest_finalized_hash(&self) -> Option<B256> {
5959
self.post_states
6060
.get(&self.head)
61-
.map(|state| state.latest_finalized_hash)
61+
.map(|state| state.latest_finalized.root)
6262
}
6363

6464
/// Compute the latest block that the staker is allowed to choose as the target
@@ -119,7 +119,7 @@ impl LeanChain {
119119
.known_votes
120120
.clone()
121121
.into_iter()
122-
.filter(|vote| vote.source == state.latest_justified_hash)
122+
.filter(|vote| vote.source.root == state.latest_justified.root)
123123
.filter(|vote| !new_block.votes.contains(vote))
124124
.collect::<Vec<_>>();
125125

@@ -173,7 +173,7 @@ impl LeanChain {
173173

174174
// If the latest finalized slot is very far back, then only some slots are
175175
// valid to justify, make sure the target is one of those
176-
while !is_justifiable_slot(&state.latest_finalized_slot, &target_block.slot) {
176+
while !is_justifiable_slot(&state.latest_finalized.slot, &target_block.slot) {
177177
target_block = self.chain.get(&target_block.parent).ok_or_else(|| {
178178
anyhow!(
179179
"Block not found for target block's parent hash: {}",
@@ -193,12 +193,18 @@ impl LeanChain {
193193
// IDs.
194194
validator_id: 0,
195195
slot: get_current_slot(),
196-
head: self.head,
197-
head_slot: head_block.slot,
198-
target: target_block.tree_hash_root(),
199-
target_slot: target_block.slot,
200-
source: state.latest_justified_hash,
201-
source_slot: state.latest_justified_slot,
196+
head: Checkpoint {
197+
root: self.head,
198+
slot: head_block.slot,
199+
},
200+
target: Checkpoint {
201+
root: target_block.tree_hash_root(),
202+
slot: target_block.slot,
203+
},
204+
source: Checkpoint {
205+
root: state.latest_justified.root,
206+
slot: state.latest_justified.slot,
207+
},
202208
})
203209
}
204210

crates/common/chain/lean/src/service.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,13 @@ impl LeanChainService {
104104
let vote = &signed_vote.data;
105105
info!(
106106
"Received signed vote from validator {} for head {:?} / source_slot {:?} at slot {}",
107-
vote.validator_id, vote.head, vote.source_slot, vote.slot
107+
vote.validator_id, vote.head, vote.source.slot, vote.slot
108108
);
109109
}
110110
VoteItem::Unsigned(vote) => {
111111
info!(
112112
"Received unsigned vote from validator {} for head {:?} / source_slot {:?} at slot {}",
113-
vote.validator_id, vote.head, vote.source_slot, vote.slot
113+
vote.validator_id, vote.head, vote.source.slot, vote.slot
114114
);
115115
}
116116
}
@@ -183,15 +183,15 @@ impl LeanChainService {
183183

184184
if is_known_vote || is_new_vote {
185185
// Do nothing
186-
} else if lean_chain.chain.contains_key(&vote.head) {
186+
} else if lean_chain.chain.contains_key(&vote.head.root) {
187187
drop(lean_chain);
188188

189189
// We should acquire another write lock
190190
let mut lean_chain = self.lean_chain.write().await;
191191
lean_chain.new_votes.push(vote);
192192
} else {
193193
self.dependencies
194-
.entry(vote.head)
194+
.entry(vote.head.root)
195195
.or_default()
196196
.push(QueueItem::VoteItem(VoteItem::Unsigned(vote)));
197197
}

crates/common/consensus/lean/src/block.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::vote::Vote;
99

1010
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
1111
pub struct SignedBlock {
12-
pub message: Block,
12+
pub data: Block,
1313
pub signature: PQSignature,
1414
}
1515

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use alloy_primitives::B256;
2+
use serde::{Deserialize, Serialize};
3+
use ssz_derive::{Decode, Encode};
4+
use tree_hash_derive::TreeHash;
5+
6+
#[derive(
7+
Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash,
8+
)]
9+
pub struct Checkpoint {
10+
pub root: B256,
11+
pub slot: u64,
12+
}

crates/common/consensus/lean/src/lib.rs

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod block;
2+
pub mod checkpoint;
23
pub mod config;
34
pub mod state;
45
pub mod vote;
@@ -75,37 +76,37 @@ pub fn process_block(pre_state: &LeanState, block: &Block) -> anyhow::Result<Lea
7576
// Ignore votes whose source is not already justified,
7677
// or whose target is not in the history, or whose target is not a
7778
// valid justifiable slot
78-
if !state.justified_slots[vote.source_slot as usize]
79-
|| vote.source != state.historical_block_hashes[vote.source_slot as usize]
80-
|| vote.target != state.historical_block_hashes[vote.target_slot as usize]
81-
|| vote.target_slot <= vote.source_slot
82-
|| !is_justifiable_slot(&state.latest_finalized_slot, &vote.target_slot)
79+
if !state.justified_slots[vote.source.slot as usize]
80+
|| vote.source.root != state.historical_block_hashes[vote.source.slot as usize]
81+
|| vote.target.root != state.historical_block_hashes[vote.target.slot as usize]
82+
|| vote.target.slot <= vote.source.slot
83+
|| !is_justifiable_slot(&state.latest_finalized.slot, &vote.target.slot)
8384
{
8485
continue;
8586
}
8687

8788
// Track attempts to justify new hashes
88-
state.initialize_justifications_for_root(&vote.target)?;
89-
state.set_justification(&vote.target, &vote.validator_id, true)?;
89+
state.initialize_justifications_for_root(&vote.target.root)?;
90+
state.set_justification(&vote.target.root, &vote.validator_id, true)?;
9091

91-
let count = state.count_justifications(&vote.target)?;
92+
let count = state.count_justifications(&vote.target.root)?;
9293

9394
// If 2/3 voted for the same new valid hash to justify
9495
if count == (2 * state.config.num_validators) / 3 {
95-
state.latest_justified_hash = vote.target;
96-
state.latest_justified_slot = vote.target_slot;
97-
state.justified_slots[vote.target_slot as usize] = true;
96+
state.latest_justified.root = vote.target.root;
97+
state.latest_justified.slot = vote.target.slot;
98+
state.justified_slots[vote.target.slot as usize] = true;
9899

99-
state.remove_justifications(&vote.target)?;
100+
state.remove_justifications(&vote.target.root)?;
100101

101102
// Finalization: if the target is the next valid justifiable
102103
// hash after the source
103-
let is_target_next_valid_justifiable_slot = !((vote.source_slot + 1)..vote.target_slot)
104-
.any(|slot| is_justifiable_slot(&state.latest_finalized_slot, &slot));
104+
let is_target_next_valid_justifiable_slot = !((vote.source.slot + 1)..vote.target.slot)
105+
.any(|slot| is_justifiable_slot(&state.latest_finalized.slot, &slot));
105106

106107
if is_target_next_valid_justifiable_slot {
107-
state.latest_finalized_hash = vote.source;
108-
state.latest_finalized_slot = vote.source_slot;
108+
state.latest_finalized.root = vote.source.root;
109+
state.latest_finalized.slot = vote.source.slot;
109110
}
110111
}
111112
}
@@ -117,8 +118,8 @@ pub fn process_block(pre_state: &LeanState, block: &Block) -> anyhow::Result<Lea
117118
pub fn get_latest_justified_hash(post_states: &HashMap<B256, LeanState>) -> Option<B256> {
118119
post_states
119120
.values()
120-
.max_by_key(|state| state.latest_justified_slot)
121-
.map(|state| state.latest_justified_hash)
121+
.max_by_key(|state| state.latest_justified.slot)
122+
.map(|state| state.latest_justified.root)
122123
}
123124

124125
/// Use LMD GHOST to get the head, given a particular root (usually the
@@ -159,8 +160,8 @@ pub fn get_fork_choice_head(
159160
let mut vote_weights = HashMap::<B256, u64>::new();
160161

161162
for vote in latest_votes.values() {
162-
if blocks.contains_key(&vote.head) {
163-
let mut block_hash = vote.head;
163+
if blocks.contains_key(&vote.head.root) {
164+
let mut block_hash = vote.head.root;
164165
while {
165166
let current_block = blocks
166167
.get(&block_hash)
@@ -195,21 +196,16 @@ pub fn get_fork_choice_head(
195196
// choose the child with the most latest votes, tiebreaking by slot then hash
196197
let mut current_root = root;
197198

198-
loop {
199-
match children_map.get(&current_root) {
200-
None => {
201-
break Ok(current_root);
202-
}
203-
Some(children) => {
204-
current_root = *children
205-
.iter()
206-
.max_by_key(|child_hash| {
207-
let vote_weight = vote_weights.get(*child_hash).unwrap_or(&0);
208-
let slot = blocks.get(*child_hash).map(|block| block.slot).unwrap_or(0);
209-
(*vote_weight, slot, *(*child_hash))
210-
})
211-
.ok_or_else(|| anyhow!("No children found for current root: {current_root}"))?;
212-
}
213-
}
199+
while let Some(children) = children_map.get(&current_root) {
200+
current_root = *children
201+
.iter()
202+
.max_by_key(|child_hash| {
203+
let vote_weight = vote_weights.get(*child_hash).unwrap_or(&0);
204+
let slot = blocks.get(*child_hash).map(|block| block.slot).unwrap_or(0);
205+
(*vote_weight, slot, *(*child_hash))
206+
})
207+
.ok_or_else(|| anyhow!("No children found for current root: {current_root}"))?;
214208
}
209+
210+
Ok(current_root)
215211
}

crates/common/consensus/lean/src/state.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,14 @@ use ssz_types::{
99
};
1010
use tree_hash_derive::TreeHash;
1111

12-
use crate::config::Config;
12+
use crate::{checkpoint::Checkpoint, config::Config};
1313

1414
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
1515
pub struct LeanState {
1616
pub config: Config,
1717

18-
pub latest_justified_hash: B256,
19-
pub latest_justified_slot: u64,
20-
pub latest_finalized_hash: B256,
21-
pub latest_finalized_slot: u64,
18+
pub latest_justified: Checkpoint,
19+
pub latest_finalized: Checkpoint,
2220

2321
pub historical_block_hashes: VariableList<B256, U262144>,
2422
pub justified_slots: VariableList<bool, U262144>,
@@ -36,10 +34,8 @@ impl LeanState {
3634
LeanState {
3735
config: Config { num_validators },
3836

39-
latest_justified_hash: B256::ZERO,
40-
latest_justified_slot: 0,
41-
latest_finalized_hash: B256::ZERO,
42-
latest_finalized_slot: 0,
37+
latest_justified: Checkpoint::default(),
38+
latest_finalized: Checkpoint::default(),
4339

4440
historical_block_hashes: VariableList::empty(),
4541
justified_slots: VariableList::empty(),
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use alloy_primitives::B256;
21
use ream_pqc::PQSignature;
32
use serde::{Deserialize, Serialize};
43
use ssz_derive::{Decode, Encode};
54
use tree_hash_derive::TreeHash;
65

6+
use crate::checkpoint::Checkpoint;
7+
78
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
89
pub struct SignedVote {
910
pub data: Vote,
@@ -14,10 +15,7 @@ pub struct SignedVote {
1415
pub struct Vote {
1516
pub validator_id: u64,
1617
pub slot: u64,
17-
pub head: B256,
18-
pub head_slot: u64,
19-
pub target: B256,
20-
pub target_slot: u64,
21-
pub source: B256,
22-
pub source_slot: u64,
18+
pub head: Checkpoint,
19+
pub target: Checkpoint,
20+
pub source: Checkpoint,
2321
}

crates/common/validator/lean/src/service.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ impl ValidatorService {
110110
// Build the vote from LeanChain, and modify its validator ID
111111
let vote_template = self.lean_chain.read().await.build_vote().expect("Failed to build vote");
112112

113-
info!("Built vote template for head {:?} at slot {} with target {:?}", vote_template.head, vote_template.slot, vote_template.target);
113+
info!("Built vote template for head {:?} at slot {} with target {:?}", vote_template.head, vote_template.slot, vote_template.target.slot);
114114

115115
let votes = self.keystores.iter().map(|keystore| {
116116
let mut vote = vote_template.clone();

0 commit comments

Comments
 (0)