reth_chain_state/
lazy_overlay.rs1use crate::DeferredTrieData;
8use alloy_primitives::B256;
9use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
10use std::sync::{Arc, OnceLock};
11use tracing::{debug, trace};
12
13#[derive(Clone)]
15struct LazyOverlayInputs {
16 anchor_hash: B256,
18 blocks: Vec<DeferredTrieData>,
20}
21
22#[derive(Clone)]
34pub struct LazyOverlay {
35 inner: Arc<OnceLock<TrieInputSorted>>,
37 inputs: LazyOverlayInputs,
39}
40
41impl std::fmt::Debug for LazyOverlay {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("LazyOverlay")
44 .field("anchor_hash", &self.inputs.anchor_hash)
45 .field("num_blocks", &self.inputs.blocks.len())
46 .field("computed", &self.inner.get().is_some())
47 .finish()
48 }
49}
50
51impl LazyOverlay {
52 pub fn new(anchor_hash: B256, blocks: Vec<DeferredTrieData>) -> Self {
59 Self { inner: Arc::new(OnceLock::new()), inputs: LazyOverlayInputs { anchor_hash, blocks } }
60 }
61
62 pub const fn anchor_hash(&self) -> B256 {
64 self.inputs.anchor_hash
65 }
66
67 pub const fn num_blocks(&self) -> usize {
69 self.inputs.blocks.len()
70 }
71
72 pub fn is_computed(&self) -> bool {
74 self.inner.get().is_some()
75 }
76
77 pub fn get(&self) -> &TrieInputSorted {
82 self.inner.get_or_init(|| self.compute())
83 }
84
85 pub fn as_overlay(&self) -> (Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>) {
87 let input = self.get();
88 (Arc::clone(&input.nodes), Arc::clone(&input.state))
89 }
90
91 fn compute(&self) -> TrieInputSorted {
93 let anchor_hash = self.inputs.anchor_hash;
94 let blocks = &self.inputs.blocks;
95
96 if blocks.is_empty() {
97 debug!(target: "chain_state::lazy_overlay", "No in-memory blocks, returning empty overlay");
98 return TrieInputSorted::default();
99 }
100
101 if let Some(tip) = blocks.first() {
104 let data = tip.wait_cloned();
105 if let Some(anchored) = &data.anchored_trie_input {
106 if anchored.anchor_hash == anchor_hash {
107 trace!(target: "chain_state::lazy_overlay", %anchor_hash, "Reusing tip block's cached overlay (fast path)");
108 return (*anchored.trie_input).clone();
109 }
110 debug!(
111 target: "chain_state::lazy_overlay",
112 computed_anchor = %anchored.anchor_hash,
113 %anchor_hash,
114 "Anchor mismatch, falling back to merge"
115 );
116 }
117 }
118
119 debug!(target: "chain_state::lazy_overlay", num_blocks = blocks.len(), "Merging blocks (slow path)");
121 Self::merge_blocks(blocks)
122 }
123
124 fn merge_blocks(blocks: &[DeferredTrieData]) -> TrieInputSorted {
128 if blocks.is_empty() {
129 return TrieInputSorted::default();
130 }
131
132 let state =
133 HashedPostStateSorted::merge_batch(blocks.iter().map(|b| b.wait_cloned().hashed_state));
134 let nodes =
135 TrieUpdatesSorted::merge_batch(blocks.iter().map(|b| b.wait_cloned().trie_updates));
136
137 TrieInputSorted { state, nodes, prefix_sets: Default::default() }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use reth_trie::{updates::TrieUpdates, HashedPostState};
145
146 fn empty_deferred(anchor: B256) -> DeferredTrieData {
147 DeferredTrieData::pending(
148 Arc::new(HashedPostState::default()),
149 Arc::new(TrieUpdates::default()),
150 anchor,
151 Vec::new(),
152 )
153 }
154
155 #[test]
156 fn empty_blocks_returns_default() {
157 let overlay = LazyOverlay::new(B256::ZERO, vec![]);
158 let result = overlay.get();
159 assert!(result.state.is_empty());
160 assert!(result.nodes.is_empty());
161 }
162
163 #[test]
164 fn single_block_uses_data_directly() {
165 let anchor = B256::random();
166 let deferred = empty_deferred(anchor);
167 let overlay = LazyOverlay::new(anchor, vec![deferred]);
168
169 assert!(!overlay.is_computed());
170 let _ = overlay.get();
171 assert!(overlay.is_computed());
172 }
173
174 #[test]
175 fn cached_after_first_access() {
176 let overlay = LazyOverlay::new(B256::ZERO, vec![]);
177
178 let _ = overlay.get();
180 assert!(overlay.is_computed());
181
182 let _ = overlay.get();
184 assert!(overlay.is_computed());
185 }
186}