1use core::fmt::Debug;
2use std::{cmp::max, sync::Arc, time::Duration};
3
4use anyhow::{Context, bail, ensure};
5use async_lock::Mutex;
6use either::Either;
7use espresso_types::{
8 BlockMerkleTree, EpochRewardsCalculator, FeeAccount, FeeMerkleTree, Leaf2, ValidatedState,
9 traits::StateCatchup,
10 v0_3::{ChainConfig, RewardMerkleTreeV1},
11 v0_4::Delta,
12};
13use futures::{StreamExt, future::Future};
14use hotshot::traits::ValidatedState as HotShotState;
15use hotshot_query_service::{
16 availability::{AvailabilityDataSource, LeafQueryData},
17 data_source::{Transaction, VersionedDataSource, storage::pruning::PrunedHeightDataSource},
18 merklized_state::{MerklizedStateHeightPersistence, UpdateStateData},
19 status::StatusDataSource,
20 types::HeightIndexed,
21};
22use hotshot_types::utils::is_last_block;
23use jf_merkle_tree_compat::{
24 LookupResult, MerkleTreeScheme, ToTraversalPath, UniversalMerkleTreeScheme,
25};
26use tokio::time::sleep;
27use vbs::version::Version;
28use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION};
29
30use crate::{
31 NodeState, SeqTypes,
32 api::{RewardMerkleTreeDataSource, RewardMerkleTreeV2Data},
33 catchup::{CatchupStorage, SqlStateCatchup},
34 persistence::ChainConfigPersistence,
35};
36
37pub(crate) async fn compute_state_update(
38 parent_state: &ValidatedState,
39 instance: &NodeState,
40 peers: &impl StateCatchup,
41 parent_leaf: &Leaf2,
42 proposed_leaf: &Leaf2,
43) -> anyhow::Result<(ValidatedState, Delta)> {
44 let header = proposed_leaf.block_header();
45
46 let mut parent_state = parent_state.clone();
47
48 if proposed_leaf.block_header().version() > parent_leaf.block_header().version() {
56 parent_state.chain_config = proposed_leaf.block_header().chain_config()
57 }
58
59 let (state, delta, total_rewards_distributed) = parent_state
60 .apply_header(
61 instance,
62 peers,
63 parent_leaf,
64 header,
65 header.version(),
66 proposed_leaf.view_number(),
67 )
68 .await?;
69
70 ensure!(
72 state.chain_config.commit() == header.chain_config().commit(),
73 "internal error! in-memory chain config {:?} does not match header {:?}",
74 state.chain_config,
75 header.chain_config(),
76 );
77 ensure!(
78 state.block_merkle_tree.commitment() == header.block_merkle_tree_root(),
79 "internal error! in-memory block tree {} does not match header {}",
80 state.block_merkle_tree.commitment(),
81 header.block_merkle_tree_root()
82 );
83 ensure!(
84 state.fee_merkle_tree.commitment() == header.fee_merkle_tree_root(),
85 "internal error! in-memory fee tree {} does not match header {}",
86 state.fee_merkle_tree.commitment(),
87 header.fee_merkle_tree_root()
88 );
89
90 match header.reward_merkle_tree_root() {
91 Either::Left(v1_root) => {
92 ensure!(
93 state.reward_merkle_tree_v1.commitment() == v1_root,
94 "internal error! in-memory v1 reward tree {} does not match header {}",
95 state.reward_merkle_tree_v1.commitment(),
96 v1_root
97 )
98 },
99 Either::Right(v2_root) => {
100 ensure!(
101 state.reward_merkle_tree_v2.commitment() == v2_root,
102 "internal error! in-memory v2 reward tree {} does not match header {}",
103 state.reward_merkle_tree_v2.commitment(),
104 v2_root
105 )
106 },
107 }
108
109 if header.version() >= DRB_AND_HEADER_UPGRADE_VERSION {
110 let Some(actual_total) = total_rewards_distributed else {
111 bail!(
112 "internal error! total_rewards_distributed is None for version {:?}",
113 header.version()
114 );
115 };
116
117 let Some(proposed_total) = header.total_reward_distributed() else {
118 bail!(
119 "internal error! proposed header.total_reward_distributed() is None for version \
120 {:?}",
121 header.version()
122 );
123 };
124
125 ensure!(
126 proposed_total == actual_total,
127 "Total rewards mismatch: proposed header has {proposed_total} but actual total is \
128 {actual_total}",
129 );
130 }
131
132 Ok((state, delta))
133}
134
135async fn store_state_update(
136 tx: &mut impl SequencerStateUpdate,
137 block_number: u64,
138 _version: Version,
139 state: &ValidatedState,
140 delta: &Delta,
141) -> anyhow::Result<()> {
142 let ValidatedState {
143 fee_merkle_tree,
144 block_merkle_tree,
145 ..
146 } = state;
147 let Delta { fees_delta, .. } = delta;
148
149 let fee_proofs: Vec<_> = fees_delta
151 .iter()
152 .map(|delta| {
153 let proof = match fee_merkle_tree.universal_lookup(*delta) {
154 LookupResult::Ok(_, proof) => proof,
155 LookupResult::NotFound(proof) => proof,
156 LookupResult::NotInMemory => bail!("missing merkle path for fee account {delta}"),
157 };
158 let path = FeeAccount::to_traversal_path(delta, fee_merkle_tree.height());
159 Ok((proof, path))
160 })
161 .collect::<anyhow::Result<Vec<_>>>()?;
162
163 tracing::debug!(count = fee_proofs.len(), "inserting fee accounts in batch");
164 UpdateStateData::<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>::insert_merkle_nodes_batch(
165 tx,
166 fee_proofs,
167 block_number,
168 )
169 .await
170 .context("failed to store fee merkle nodes")?;
171
172 let (_, proof) = block_merkle_tree
174 .lookup(block_number - 1)
175 .expect_ok()
176 .context("getting blocks frontier")?;
177 let path = <u64 as ToTraversalPath<{ BlockMerkleTree::ARITY }>>::to_traversal_path(
178 &(block_number - 1),
179 block_merkle_tree.height(),
180 );
181
182 {
183 tracing::debug!("inserting blocks frontier");
184 UpdateStateData::<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>::insert_merkle_nodes(
185 tx,
186 proof,
187 path,
188 block_number,
189 )
190 .await
191 .context("failed to store block merkle nodes")?;
192 }
193
194 Ok(())
195}
196
197#[tracing::instrument(
198 skip_all,
199 fields(
200 node_id = instance.node_id,
201 view = ?parent_leaf.leaf().view_number(),
202 height = parent_leaf.height(),
203 ),
204)]
205async fn update_state_storage<T>(
206 parent_state: &ValidatedState,
207 storage: &Arc<T>,
208 instance: &NodeState,
209 peers: &impl StateCatchup,
210 parent_leaf: &LeafQueryData<SeqTypes>,
211 proposed_leaf: &LeafQueryData<SeqTypes>,
212) -> anyhow::Result<ValidatedState>
213where
214 T: SequencerStateDataSource,
215 for<'a> T::Transaction<'a>: SequencerStateUpdate,
216{
217 let parent_chain_config = parent_state.chain_config;
218 let block_number = proposed_leaf.height();
219 let version = proposed_leaf.header().version();
220
221 let (state, delta) = compute_state_update(
222 parent_state,
223 instance,
224 peers,
225 &parent_leaf.leaf().clone(),
226 &proposed_leaf.leaf().clone(),
227 )
228 .await
229 .context("computing state update")?;
230
231 let has_changed_accounts = version > EPOCH_VERSION && !delta.rewards_delta.is_empty();
232 let is_epoch_boundary = version >= EPOCH_REWARD_VERSION
247 && is_last_block(
248 block_number,
249 instance
250 .epoch_height
251 .expect("epoch_height should be set for version > V3"),
252 );
253
254 if has_changed_accounts || is_epoch_boundary {
255 storage
256 .save_and_gc_reward_tree_v2(
257 instance,
258 block_number,
259 version,
260 &state.reward_merkle_tree_v2,
261 )
262 .await
263 .context("failed to save and gc reward merkle tree v2")?;
264 }
265
266 storage
267 .persist_reward_proofs(instance, block_number, version)
268 .await
269 .context("failed to persist reward proofs")?;
270
271 tracing::debug!("storing state update");
272 let mut tx = storage
273 .write()
274 .await
275 .context("opening transaction for state update")?;
276
277 store_state_update(&mut tx, block_number, version, &state, &delta).await?;
278
279 tx.commit().await?;
280
281 let mut tx = storage
282 .write()
283 .await
284 .context("opening transaction for state update")?;
285
286 if parent_chain_config != state.chain_config {
287 let cf = state
288 .chain_config
289 .resolve()
290 .context("failed to resolve to chain config")?;
291
292 tx.insert_chain_config(cf).await?;
293 }
294
295 tracing::debug!(block_number, "updating state height");
296 UpdateStateData::<SeqTypes, _, { BlockMerkleTree::ARITY }>::set_last_state_height(
297 &mut tx,
298 block_number as usize,
299 )
300 .await
301 .context("setting state height")?;
302
303 tx.commit().await?;
304
305 Ok(state)
306}
307
308async fn store_genesis_state<S>(
309 storage: &S,
310 chain_config: ChainConfig,
311 state: &ValidatedState,
312) -> anyhow::Result<()>
313where
314 S: SequencerStateDataSource,
315 for<'a> S::Transaction<'a>: SequencerStateUpdate,
316{
317 ensure!(
318 state.block_merkle_tree.num_leaves() == 0,
319 "genesis state with non-empty block tree is unsupported"
320 );
321
322 let mut tx = storage
323 .write()
324 .await
325 .context("starting transaction for genesis state")?;
326
327 for (account, _) in state.fee_merkle_tree.iter() {
329 let proof = match state.fee_merkle_tree.universal_lookup(account) {
330 LookupResult::Ok(_, proof) => proof,
331 LookupResult::NotFound(proof) => proof,
332 LookupResult::NotInMemory => bail!("missing merkle path for fee account {account}"),
333 };
334 let path: Vec<usize> =
335 <FeeAccount as ToTraversalPath<{ FeeMerkleTree::ARITY }>>::to_traversal_path(
336 account,
337 state.fee_merkle_tree.height(),
338 );
339
340 UpdateStateData::<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>::insert_merkle_nodes(
341 &mut tx, proof, path, 0,
342 )
343 .await
344 .context("failed to store fee merkle nodes")?;
345 }
346
347 tx.insert_chain_config(chain_config).await?;
348
349 tx.commit().await?;
350
351 let tree_data: RewardMerkleTreeV2Data = (&state.reward_merkle_tree_v2)
353 .try_into()
354 .context("serializing genesis reward tree")?;
355 let tree_bytes = bincode::serialize(&tree_data).context("serializing genesis reward tree")?;
356 storage
357 .persist_tree(0, tree_bytes)
358 .await
359 .context("storing genesis reward merkle tree")?;
360
361 Ok(())
362}
363
364#[tracing::instrument(skip_all)]
365pub(crate) async fn update_state_storage_loop<T>(
366 storage: Arc<T>,
367 instance: impl Future<Output = NodeState>,
368) -> anyhow::Result<()>
369where
370 T: SequencerStateDataSource,
371 for<'a> T::Transaction<'a>: SequencerStateUpdate,
372{
373 let mut instance = instance.await;
374 instance.epoch_rewards_calculator = Arc::new(Mutex::new(EpochRewardsCalculator::new()));
377 let peers = SqlStateCatchup::new(storage.clone(), Default::default());
378
379 let (last_height, parent_leaf, mut leaves) = {
381 let last_height = storage.get_last_state_height().await?;
382 let pruned_height = storage.load_state_pruned_height().await?;
383
384 let height = match pruned_height {
385 Some(pruned_height) => max(last_height, pruned_height as usize + 1),
390 None => last_height,
392 };
393
394 let height =
396 if let Ok(env_height) = std::env::var("ESPRESSO_NODE_STATE_STORAGE_INITIAL_HEIGHT") {
397 match env_height.parse::<usize>() {
398 Ok(override_height) => {
399 tracing::error!(
400 node_id = instance.node_id,
401 calculated_height = height,
402 override_height,
403 "overriding initial state storage height from environment variable"
404 );
405 override_height
406 },
407 Err(e) => {
408 tracing::error!(
409 "failed to parse ESPRESSO_NODE_STATE_STORAGE_INITIAL_HEIGHT: {e}, \
410 using calculated height {height}"
411 );
412 height
413 },
414 }
415 } else {
416 height
417 };
418
419 let current_height = storage.block_height().await?;
420 tracing::info!(
421 node_id = instance.node_id,
422 last_height,
423 height,
424 current_height,
425 "updating state storage"
426 );
427
428 let parent_leaf = AvailabilityDataSource::get_leaf(&*storage, height).await;
429 let leaves = storage.subscribe_leaves(height + 1).await;
430 (last_height, parent_leaf, leaves)
431 };
432 let mut parent_leaf = parent_leaf.await;
435 let mut parent_state = ValidatedState::from_header(parent_leaf.header());
436
437 if parent_leaf.header().version() > EPOCH_VERSION && parent_leaf.height() > 0 {
451 let reward_merkle_tree_v2 = match storage
454 .load_reward_merkle_tree_v2(parent_leaf.height())
455 .await
456 {
457 Ok(tree) => tree,
458 Err(_) => storage
459 .load_latest_reward_merkle_tree_v2(parent_leaf.height())
460 .await
461 .context(
462 "Error starting the state storage update loop: failed to load \
463 RewardMerkleTreeV2 for the previous height",
464 )?,
465 };
466
467 let parent_reward_root = parent_leaf
471 .header()
472 .reward_merkle_tree_root()
473 .right()
474 .context("V5+ parent header must have a v2 reward root")?;
475 ensure!(
476 reward_merkle_tree_v2.tree.commitment() == parent_reward_root,
477 "loaded reward tree root {} does not match parent header {parent_reward_root}",
478 reward_merkle_tree_v2.tree.commitment(),
479 );
480
481 parent_state.reward_merkle_tree_v2 = reward_merkle_tree_v2.tree;
482 }
483
484 if last_height == 0 {
485 tracing::info!("storing genesis merklized state");
488 store_genesis_state(&*storage, instance.chain_config, &instance.genesis_state)
489 .await
490 .context("storing genesis state")?;
491 }
492
493 while let Some(leaf) = leaves.next().await {
494 loop {
495 tracing::debug!(
496 height = leaf.height(),
497 node_id = instance.node_id,
498 ?leaf,
499 "updating persistent merklized state"
500 );
501 match update_state_storage(
502 &parent_state,
503 &storage,
504 &instance,
505 &peers,
506 &parent_leaf,
507 &leaf,
508 )
509 .await
510 {
511 Ok(state) => {
512 parent_leaf = leaf;
513 parent_state = state;
514 break;
515 },
516 Err(err) => {
517 tracing::error!(height = leaf.height(), "failed to update state: {err:#}");
518 sleep(Duration::from_secs(1)).await;
520 },
521 }
522 }
523 }
524
525 Ok(())
526}
527
528pub(crate) trait SequencerStateDataSource:
529 'static
530 + Debug
531 + AvailabilityDataSource<SeqTypes>
532 + StatusDataSource
533 + VersionedDataSource
534 + CatchupStorage
535 + RewardMerkleTreeDataSource
536 + PrunedHeightDataSource
537 + MerklizedStateHeightPersistence
538{
539}
540
541impl<T> SequencerStateDataSource for T where
542 T: 'static
543 + Debug
544 + AvailabilityDataSource<SeqTypes>
545 + StatusDataSource
546 + VersionedDataSource
547 + CatchupStorage
548 + RewardMerkleTreeDataSource
549 + PrunedHeightDataSource
550 + MerklizedStateHeightPersistence
551{
552}
553
554pub(crate) trait SequencerStateUpdate:
555 Transaction
556 + UpdateStateData<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>
557 + UpdateStateData<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>
558 + UpdateStateData<SeqTypes, RewardMerkleTreeV1, { RewardMerkleTreeV1::ARITY }>
559 + ChainConfigPersistence
560{
561}
562
563impl<T> SequencerStateUpdate for T where
564 T: Transaction
565 + UpdateStateData<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>
566 + UpdateStateData<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>
567 + UpdateStateData<SeqTypes, RewardMerkleTreeV1, { RewardMerkleTreeV1::ARITY }>
568 + ChainConfigPersistence
569{
570}