Skip to content

Commit c79ddb3

Browse files
committed
Improve state summary dag compute logic
1 parent 29ad4fc commit c79ddb3

File tree

3 files changed

+158
-68
lines changed

3 files changed

+158
-68
lines changed

beacon_node/beacon_chain/src/migrate.rs

+29-27
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,6 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
462462
new_finalized_checkpoint: Checkpoint,
463463
log: &Logger,
464464
) -> Result<PruningOutcome, BeaconChainError> {
465-
let split_state_root = store.get_split_info().state_root;
466465
let new_finalized_slot = new_finalized_checkpoint
467466
.epoch
468467
.start_slot(E::slots_per_epoch());
@@ -493,8 +492,22 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
493492
let state_summaries = store
494493
.load_hot_state_summaries()?
495494
.into_iter()
496-
.map(|(state_root, summary)| (state_root, summary.into()))
497-
.collect::<Vec<(Hash256, DAGStateSummaryV22)>>();
495+
.map(|(state_root, summary)| {
496+
let block_root = summary.latest_block_root;
497+
let block = store
498+
.get_blinded_block(&block_root)?
499+
.ok_or(PruningError::MissingBlindedBlock(block_root))?;
500+
Ok((
501+
state_root,
502+
DAGStateSummaryV22 {
503+
slot: summary.slot,
504+
latest_block_root: summary.latest_block_root,
505+
block_slot: block.slot(),
506+
block_parent_root: block.parent_root(),
507+
},
508+
))
509+
})
510+
.collect::<Result<Vec<(Hash256, DAGStateSummaryV22)>, BeaconChainError>>()?;
498511

499512
// De-duplicate block roots to reduce block reads below
500513
let summary_block_roots = HashSet::<Hash256>::from_iter(
@@ -512,34 +525,23 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
512525
));
513526
}
514527

515-
let blocks = summary_block_roots
516-
.iter()
517-
.map(|block_root| {
518-
let block = store
519-
.get_blinded_block(block_root)?
520-
.ok_or(PruningError::MissingBlindedBlock(*block_root))?;
521-
Ok((
522-
*block_root,
528+
// Collect and de-duplicate block summaries from state summaries
529+
let unique_blocks = HashMap::<Hash256, DAGBlockSummary>::from_iter(
530+
state_summaries.iter().map(|(_, summary)| {
531+
(
532+
summary.latest_block_root,
523533
DAGBlockSummary {
524-
slot: block.slot(),
525-
parent_root: block.parent_root(),
534+
slot: summary.block_slot,
535+
parent_root: summary.block_parent_root,
526536
},
527-
))
528-
})
529-
.collect::<Result<Vec<_>, BeaconChainError>>()?;
530-
531-
let parent_block_roots = blocks
532-
.iter()
533-
.map(|(block_root, block)| (*block_root, block.parent_root))
534-
.collect::<HashMap<Hash256, Hash256>>();
537+
)
538+
}),
539+
);
540+
let blocks = unique_blocks.into_iter().collect::<Vec<_>>();
535541

536542
(
537-
StateSummariesDAG::new_from_v22(
538-
state_summaries,
539-
parent_block_roots,
540-
split_state_root,
541-
)
542-
.map_err(PruningError::SummariesDagError)?,
543+
StateSummariesDAG::new_from_v22(state_summaries)
544+
.map_err(PruningError::SummariesDagError)?,
543545
BlockSummariesDAG::new(&blocks),
544546
)
545547
};

beacon_node/beacon_chain/src/summaries_dag.rs

+122-37
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::{
22
cmp::Ordering,
33
collections::{BTreeMap, HashMap},
44
};
5-
use store::HotStateSummary;
65
use types::{Hash256, Slot};
76

87
#[derive(Debug, Clone, Copy)]
@@ -16,6 +15,8 @@ pub struct DAGStateSummary {
1615
pub struct DAGStateSummaryV22 {
1716
pub slot: Slot,
1817
pub latest_block_root: Hash256,
18+
pub block_slot: Slot,
19+
pub block_parent_root: Hash256,
1920
}
2021

2122
pub struct StateSummariesDAG {
@@ -27,12 +28,13 @@ pub struct StateSummariesDAG {
2728

2829
#[derive(Debug)]
2930
pub enum Error {
30-
MissingParentBlockRoot(Hash256),
3131
MissingStateSummary(Hash256),
32+
MissingStateSummaryByBlockRoot(Hash256),
3233
MissingStateSummaryAtSlot(Hash256, Slot),
3334
MissingChildBlockRoot(Hash256),
3435
MissingBlock(Hash256),
3536
RequestedSlotAboveSummary(Hash256, Slot),
37+
RootUnknownPreviousStateRoot(Hash256, Slot),
3638
}
3739

3840
impl StateSummariesDAG {
@@ -57,10 +59,15 @@ impl StateSummariesDAG {
5759
}
5860
}
5961

62+
/// Computes a DAG from a sequence of state summaries, including their parent block
63+
/// relationships.
64+
///
65+
/// - Expects summaries to be contiguous per slot: there must exist a summary at every slot
66+
/// of each tree branch
67+
/// - Maybe include multiple disjoint trees. The root of each tree will have a ZERO parent state
68+
/// root, which will error later when calling `previous_state_root`.
6069
pub fn new_from_v22(
6170
state_summaries_v22: Vec<(Hash256, DAGStateSummaryV22)>,
62-
parent_block_roots: HashMap<Hash256, Hash256>,
63-
base_root: Hash256,
6471
) -> Result<Self, Error> {
6572
// Group them by latest block root, and sorted state slot
6673
let mut state_summaries_by_block_root = HashMap::<_, BTreeMap<_, _>>::new();
@@ -76,32 +83,45 @@ impl StateSummariesDAG {
7683
let state_summaries = state_summaries_v22
7784
.iter()
7885
.map(|(state_root, summary)| {
79-
let previous_state_root = if summary.slot == 0 || *state_root == base_root {
86+
let previous_state_root = if summary.slot == 0 {
8087
Hash256::ZERO
8188
} else {
8289
let previous_slot = summary.slot - 1;
8390

8491
// Check the set of states in the same state's block root
8592
let same_block_root_summaries = state_summaries_by_block_root
8693
.get(&summary.latest_block_root)
87-
.ok_or(Error::MissingStateSummary(summary.latest_block_root))?;
94+
// Should never error: we construct the HashMap here and must have at least
95+
// one entry per block root
96+
.ok_or(Error::MissingStateSummaryByBlockRoot(
97+
summary.latest_block_root,
98+
))?;
8899
if let Some((state_root, _)) = same_block_root_summaries.get(&previous_slot) {
89100
// Skipped slot: block root at previous slot is the same as latest block root.
90101
**state_root
91102
} else {
92103
// Common case: not a skipped slot.
93-
let parent_block_root = parent_block_roots
94-
.get(&summary.latest_block_root)
95-
.ok_or(Error::MissingParentBlockRoot(summary.latest_block_root))?;
96-
*state_summaries_by_block_root
97-
.get(parent_block_root)
98-
.ok_or(Error::MissingStateSummary(*parent_block_root))?
99-
.get(&previous_slot)
100-
.ok_or(Error::MissingStateSummaryAtSlot(
101-
*parent_block_root,
102-
previous_slot,
103-
))?
104-
.0
104+
let parent_block_root = summary.block_parent_root;
105+
if let Some(parent_block_summaries) =
106+
state_summaries_by_block_root.get(&parent_block_root)
107+
{
108+
*parent_block_summaries
109+
.get(&previous_slot)
110+
// Should never error: summaries are continuous, so if there's an
111+
// entry it must contain at least one summary at the previous slot.
112+
.ok_or(Error::MissingStateSummaryAtSlot(
113+
parent_block_root,
114+
previous_slot,
115+
))?
116+
.0
117+
} else {
118+
// We don't know of any summary with this parent block root. We'll
119+
// consider this summary to be a root of `state_summaries_v22`
120+
// collection and mark it as zero.
121+
// The test store_tests::finalizes_non_epoch_start_slot manages to send two
122+
// disjoint trees on its second migration.
123+
Hash256::ZERO
124+
}
105125
}
106126
};
107127

@@ -142,11 +162,18 @@ impl StateSummariesDAG {
142162
}
143163

144164
pub fn previous_state_root(&self, state_root: Hash256) -> Result<Hash256, Error> {
145-
Ok(self
165+
let summary = self
146166
.state_summaries_by_state_root
147167
.get(&state_root)
148-
.ok_or(Error::MissingStateSummary(state_root))?
149-
.previous_state_root)
168+
.ok_or(Error::MissingStateSummary(state_root))?;
169+
if summary.previous_state_root == Hash256::ZERO {
170+
Err(Error::RootUnknownPreviousStateRoot(
171+
state_root,
172+
summary.slot,
173+
))
174+
} else {
175+
Ok(summary.previous_state_root)
176+
}
150177
}
151178

152179
pub fn ancestor_state_root_at_slot(
@@ -170,7 +197,14 @@ impl StateSummariesDAG {
170197
return Ok(state_root);
171198
}
172199
Ordering::Greater => {
173-
state_root = summary.previous_state_root;
200+
if summary.previous_state_root == Hash256::ZERO {
201+
return Err(Error::RootUnknownPreviousStateRoot(
202+
state_root,
203+
summary.slot,
204+
));
205+
} else {
206+
state_root = summary.previous_state_root;
207+
}
174208
}
175209
}
176210
}
@@ -196,15 +230,6 @@ impl StateSummariesDAG {
196230
}
197231
}
198232

199-
impl From<HotStateSummary> for DAGStateSummaryV22 {
200-
fn from(value: HotStateSummary) -> Self {
201-
Self {
202-
slot: value.slot,
203-
latest_block_root: value.latest_block_root,
204-
}
205-
}
206-
}
207-
208233
#[derive(Debug, Clone, Copy)]
209234
pub struct DAGBlockSummary {
210235
pub slot: Slot,
@@ -284,7 +309,6 @@ impl BlockSummariesDAG {
284309
mod tests {
285310
use super::{BlockSummariesDAG, DAGBlockSummary, DAGStateSummaryV22, Error, StateSummariesDAG};
286311
use bls::FixedBytesExtended;
287-
use std::collections::HashMap;
288312
use types::{Hash256, Slot};
289313

290314
fn root(n: u64) -> Hash256 {
@@ -300,7 +324,7 @@ mod tests {
300324

301325
#[test]
302326
fn new_from_v22_empty() {
303-
StateSummariesDAG::new_from_v22(vec![], HashMap::new(), Hash256::ZERO).unwrap();
327+
StateSummariesDAG::new_from_v22(vec![]).unwrap();
304328
}
305329

306330
#[test]
@@ -311,17 +335,78 @@ mod tests {
311335
let summary_1 = DAGStateSummaryV22 {
312336
slot: Slot::new(1),
313337
latest_block_root: root_1,
338+
block_parent_root: root_2,
339+
block_slot: Slot::new(1),
314340
};
315-
// (child, parent)
316-
let parents = HashMap::from_iter([(root_1, root_2)]);
317341

318-
let dag =
319-
StateSummariesDAG::new_from_v22(vec![(root_a, summary_1)], parents, root_a).unwrap();
342+
let dag = StateSummariesDAG::new_from_v22(vec![(root_a, summary_1)]).unwrap();
320343

321344
// The parent of the root summary is ZERO
322345
assert_eq!(dag.previous_state_root(root_a).unwrap(), Hash256::ZERO);
323346
}
324347

348+
#[test]
349+
fn new_from_v22_multiple_states() {
350+
let dag = StateSummariesDAG::new_from_v22(vec![
351+
(
352+
root(0xa),
353+
DAGStateSummaryV22 {
354+
slot: Slot::new(3),
355+
latest_block_root: root(3),
356+
block_parent_root: root(1),
357+
block_slot: Slot::new(3),
358+
},
359+
),
360+
(
361+
root(0xb),
362+
DAGStateSummaryV22 {
363+
slot: Slot::new(4),
364+
latest_block_root: root(4),
365+
block_parent_root: root(3),
366+
block_slot: Slot::new(4),
367+
},
368+
),
369+
// fork 1
370+
(
371+
root(0xc),
372+
DAGStateSummaryV22 {
373+
slot: Slot::new(5),
374+
latest_block_root: root(5),
375+
block_parent_root: root(4),
376+
block_slot: Slot::new(5),
377+
},
378+
),
379+
// fork 2
380+
// skipped slot
381+
(
382+
root(0xd),
383+
DAGStateSummaryV22 {
384+
slot: Slot::new(5),
385+
latest_block_root: root(4),
386+
block_parent_root: root(3),
387+
block_slot: Slot::new(4),
388+
},
389+
),
390+
// normal slot
391+
(
392+
root(0xe),
393+
DAGStateSummaryV22 {
394+
slot: Slot::new(6),
395+
latest_block_root: root(6),
396+
block_parent_root: root(4),
397+
block_slot: Slot::new(6),
398+
},
399+
),
400+
])
401+
.unwrap();
402+
403+
// The parent of the root summary is ZERO
404+
assert_eq!(dag.previous_state_root(root(0xa)).unwrap(), Hash256::ZERO);
405+
assert_eq!(dag.previous_state_root(root(0xc)).unwrap(), root(0xb));
406+
assert_eq!(dag.previous_state_root(root(0xd)).unwrap(), root(0xb));
407+
assert_eq!(dag.previous_state_root(root(0xe)).unwrap(), root(0xd));
408+
}
409+
325410
#[test]
326411
fn descendant_block_roots_of() {
327412
let root_1 = root(1);

beacon_node/store/src/hot_cold_store.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ pub enum HotColdDBError {
160160
MissingRestorePoint(Hash256),
161161
MissingColdStateSummary(Hash256),
162162
MissingHotStateSummary(Hash256),
163-
MissingEpochBoundaryState(Hash256),
163+
MissingEpochBoundaryState(Hash256, Hash256),
164164
MissingPrevState(Hash256),
165165
MissingSplitState(Hash256, Slot),
166166
MissingStateDiff(Hash256),
@@ -1133,7 +1133,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
11331133
{
11341134
// NOTE: minor inefficiency here because we load an unnecessary hot state summary
11351135
let (state, _) = self.load_hot_state(&epoch_boundary_state_root)?.ok_or(
1136-
HotColdDBError::MissingEpochBoundaryState(epoch_boundary_state_root),
1136+
HotColdDBError::MissingEpochBoundaryState(epoch_boundary_state_root, *state_root),
11371137
)?;
11381138
Ok(Some(state))
11391139
} else {
@@ -1463,7 +1463,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
14631463

14641464
// On the epoch boundary, store the full state.
14651465
if state.slot() % E::slots_per_epoch() == 0 {
1466-
trace!(
1466+
debug!(
14671467
self.log,
14681468
"Storing full state on epoch boundary";
14691469
"slot" => state.slot().as_u64(),
@@ -1543,7 +1543,10 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
15431543
{
15441544
let mut boundary_state =
15451545
get_full_state(&self.hot_db, &epoch_boundary_state_root, &self.spec)?.ok_or(
1546-
HotColdDBError::MissingEpochBoundaryState(epoch_boundary_state_root),
1546+
HotColdDBError::MissingEpochBoundaryState(
1547+
epoch_boundary_state_root,
1548+
*state_root,
1549+
),
15471550
)?;
15481551

15491552
// Immediately rebase the state from disk on the finalized state so that we can reuse

0 commit comments

Comments
 (0)