1use std::{
2 collections::{HashMap, HashSet, VecDeque},
3 sync::Arc,
4};
5
6use anyhow::{Context, bail, ensure};
7use async_trait::async_trait;
8use committable::{Commitment, Committable};
9use espresso_types::{
10 BlockMerkleCommitment, BlockMerkleTree, ChainConfig, FeeAccount, FeeMerkleCommitment,
11 FeeMerkleTree, Leaf2, NodeState, ValidatedState, get_l1_deposits,
12 v0::traits::PersistenceOptions as _,
13 v0_1::IterableFeeInfo,
14 v0_3::{
15 REWARD_MERKLE_TREE_V1_HEIGHT, RewardAccountProofV1, RewardAccountQueryDataV1,
16 RewardAccountV1, RewardMerkleTreeV1,
17 },
18 v0_4::{PermittedRewardMerkleTreeV2, RewardAccountV2, RewardMerkleTreeV2},
19 v0_6::RewardAccountQueryDataV2,
20};
21use futures::future::Future;
22use hotshot_query_service::{
23 Resolvable,
24 availability::LeafId,
25 data_source::{
26 VersionedDataSource,
27 storage::{
28 AvailabilityStorage, MerklizedStateStorage, NodeStorage,
29 pruning::{PrunerCfg, PrunerConfig},
30 },
31 },
32 merklized_state::Snapshot,
33 status::HasMetrics as _,
34};
35use hotshot_types::{
36 data::{EpochNumber, QuorumProposalWrapper, ViewNumber},
37 message::Proposal,
38 traits::{ValidatedState as _, election::MembershipSnapshot, metrics::Metrics},
39 utils::{epoch_from_block_number, is_last_block},
40 vote::HasViewNumber,
41};
42use jf_merkle_tree_compat::{
43 ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, LookupResult,
44 MerkleTreeScheme, prelude::MerkleNode,
45};
46use light_client::storage::LightClientDbOptions;
47use process_metrics::ProcessMetrics;
48use sqlx::{Encode, Row, Type};
49use tokio::time::sleep;
50use vbs::version::Version;
51use versions::{
52 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION,
53};
54
55use super::{
56 persistence::{BackendOptions as _, Options},
57 qs::{
58 Backend as _, Config, Db, DbKind, SqlDataSource, SqlStorage, Transaction, TransactionMode,
59 Write, query_as,
60 },
61};
62use crate::{
63 SeqTypes,
64 api::{
65 ApiState, BlocksFrontier, LightClientProvider, RewardMerkleTreeDataSource,
66 RewardMerkleTreeV2Data,
67 data_source::{DataSourceOptions, NodeStateDataSource as _, Provider, SequencerDataSource},
68 options::{self, Query, get_public_env_vars, init_query_data_source},
69 state::NodeApiStateImpl,
70 update::{ApiEventConsumer, ApiSink},
71 },
72 catchup::{CatchupStorage, NullStateCatchup},
73 context::TaskList,
74 persistence::{ChainConfigPersistence, sql::Options as SqlOptions},
75 request_response::data_source::Storage as RequestResponseStorage,
76 state::{EmptyStateStart, compute_state_update, update_state_storage_loop},
77 util::BoundedJoinSet,
78};
79
80pub type DataSource = SqlDataSource<SeqTypes, Provider>;
81
82impl DataSourceOptions for Options {
83 type DataSource = DataSource;
84
85 fn enable_query_module(&self, opt: options::Options, query: Query) -> options::Options {
86 opt.query_sql(query, self.clone().into_inner())
87 }
88
89 fn light_client_db(&self, query: &Query) -> anyhow::Result<LightClientDbOptions> {
90 Ok(Db::light_client_db(&Config::try_from(&**self)?, query))
91 }
92}
93
94#[async_trait]
95impl SequencerDataSource for DataSource {
96 type Options = Options;
97
98 async fn create(opt: Self::Options, provider: Provider, reset: bool) -> anyhow::Result<Self> {
99 let fetch_limit = opt.fetch_rate_limit;
100 let active_fetch_delay = opt.active_fetch_delay;
101 let chunk_fetch_delay = opt.chunk_fetch_delay;
102 let mut cfg = Config::try_from(&*opt)?;
103
104 if reset {
105 cfg = cfg.reset_schema();
106 }
107
108 let mut builder = cfg.builder(provider).await?;
109
110 if let Some(limit) = fetch_limit {
111 builder = builder.with_rate_limit(limit);
112 }
113
114 if opt.lightweight {
115 tracing::warn!("enabling light weight mode..");
116 builder = builder.leaf_only();
117 }
118
119 if opt.prune {
122 builder = builder.disable_aggregator();
123 }
124
125 if let Some(delay) = active_fetch_delay {
126 builder = builder.with_active_fetch_delay(delay);
127 }
128 if let Some(delay) = chunk_fetch_delay {
129 builder = builder.with_chunk_fetch_delay(delay);
130 }
131 if let Some(chunk_size) = opt.sync_status_chunk_size {
132 builder = builder.with_sync_status_chunk_size(chunk_size);
133 }
134 if let Some(ttl) = opt.sync_status_ttl {
135 builder = builder.with_sync_status_ttl(ttl);
136 }
137 if let Some(chunk_size) = opt.proactive_scan_chunk_size {
138 builder = builder.with_proactive_range_chunk_size(chunk_size);
139 }
140 if let Some(timeout) = opt.proactive_fetch_timeout {
141 builder = builder.with_proactive_fetch_timeout(timeout);
142 }
143 if let Some(interval) = opt.proactive_scan_interval {
144 builder = builder.with_proactive_interval(interval);
145 }
146 if opt.disable_proactive_fetching {
147 builder = builder.disable_proactive_fetching();
148 }
149
150 builder.build().await
151 }
152}
153
154pub(in crate::api) async fn init_query_module(
156 api: &options::Options,
157 query_opt: Query,
158 mod_opt: Options,
159 state: ApiState,
160 tasks: &mut TaskList,
161) -> anyhow::Result<(
162 Box<dyn Metrics>,
163 Box<dyn ApiSink>,
164 Option<RequestResponseStorage>,
165)> {
166 let mut provider = Provider::default();
167
168 let db_provider = mod_opt.clone().create().await?;
171 provider = provider
172 .with_block_provider(db_provider.clone())
173 .with_vid_common_provider(db_provider);
174 let light_client_db = mod_opt.light_client_db(&query_opt)?;
176 let light_client = Arc::new(LightClientProvider::new(
177 query_opt.peers,
178 state.clone(),
179 query_opt.light_client,
180 light_client_db,
181 )?);
182 provider = provider.with_provider(Arc::clone(&light_client));
183
184 let ranges_concurrency = mod_opt.ranges_concurrency;
185 let ds = DataSource::create(mod_opt.clone(), provider, false).await?;
186 let inner_storage = ds.inner();
187 tasks.spawn("process_metrics", ProcessMetrics::new(ds.metrics()).run());
188 let (metrics, ds) = init_query_data_source(ds, state.clone());
189
190 let get_node_state = {
191 let state = state.clone();
192 async move { state.node_state().await }
193 };
194 let empty_state_start = if mod_opt.prune {
195 EmptyStateStart::RecentBoundary
196 } else {
197 EmptyStateStart::Genesis
198 };
199 tasks.spawn(
200 "merklized state storage update loop",
201 update_state_storage_loop(ds.clone(), get_node_state, light_client, empty_state_start),
202 );
203
204 if mod_opt.archive && !mod_opt.archive_full_state {
206 let get_node_state = {
207 let state = state.clone();
208 async move { state.node_state().await }
209 };
210 tasks.spawn(
211 "archive state garbage collector",
212 ArchiveStateGc::new(&mod_opt).run(inner_storage.clone(), get_node_state),
213 );
214 }
215
216 let port = api.http.port;
217 let env_vars = get_public_env_vars().unwrap_or_default();
218 let node_cfg = api.public_node_config.as_deref().cloned();
219 let modules = espresso_api::OptionalModules {
220 submit: api.submit.is_some(),
221 config: api.config.is_some(),
222 explorer: api.explorer.is_some(),
223 ..Default::default()
224 };
225 let max_connections = api.http.max_connections;
226 let mut api_state = NodeApiStateImpl::new(ds.clone())
229 .with_env_vars(env_vars)
230 .with_public_node_config(node_cfg);
231 if let Some(ranges_concurrency) = ranges_concurrency {
232 api_state = api_state.with_ranges_concurrency(ranges_concurrency);
233 }
234 let tonic_state = api_state.clone();
235 tasks.spawn("API server", async move {
236 if let Err(e) = espresso_api::serve_axum(port, api_state, modules, max_connections).await {
237 tracing::error!("Axum server error: {}", e);
238 }
239 anyhow::Ok(())
240 });
241
242 if let Some(tonic_port) = api.http.tonic_port {
243 tasks.spawn("Tonic gRPC server", async move {
244 if let Err(e) = espresso_api::serve_tonic(tonic_port, tonic_state, modules).await {
245 tracing::error!("Tonic gRPC server error: {}", e);
246 }
247 });
248 }
249
250 Ok((
251 metrics,
252 Box::new(ApiEventConsumer::from(ds)),
253 Some(RequestResponseStorage::Sql(inner_storage)),
254 ))
255}
256
257#[derive(Clone, Debug)]
262pub(crate) struct ArchiveStateGc {
263 min_retention: u64,
265 cfg: PrunerCfg,
266}
267
268impl ArchiveStateGc {
269 pub(crate) fn new(opt: &SqlOptions) -> Self {
270 Self {
271 min_retention: opt.archive_state_min_retention,
272 cfg: PrunerCfg::from(opt.pruning),
273 }
274 }
275
276 pub(crate) async fn run(
277 self,
278 storage: Arc<SqlStorage>,
279 instance: impl Future<Output = NodeState>,
280 ) {
281 let node_state = instance.await;
282 loop {
283 sleep(self.cfg.interval()).await;
284 if let Err(err) = self.collect(&storage, &node_state).await {
285 tracing::warn!(
286 err = %format!("{err:#}"),
287 "archive state garbage collection failed"
288 );
289 }
290 }
291 }
292
293 async fn collect(&self, storage: &SqlStorage, node_state: &NodeState) -> anyhow::Result<()> {
294 let Some(history_start) = node_state
295 .light_client_history_start()
296 .await
297 .context("reading the oldest light client root")?
298 else {
299 tracing::info!("light client reports no retained history; retaining all state");
300 return Ok(());
301 };
302 storage
303 .prune_state_below(history_start, self.min_retention, &self.cfg)
304 .await
305 }
306}
307
308impl RewardMerkleTreeDataSource for SqlStorage {
309 async fn load_v1_reward_account_proof(
310 &self,
311 height: u64,
312 account: RewardAccountV1,
313 ) -> anyhow::Result<RewardAccountQueryDataV1> {
314 let mut tx = self.read().await.context(format!(
315 "opening transaction to fetch v1 reward account {account:?}; height {height}"
316 ))?;
317
318 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
319 .await
320 .context("getting block height")? as u64;
321 ensure!(
322 block_height > 0,
323 "cannot get accounts for height {height}: no blocks available"
324 );
325
326 if height < block_height {
329 let (tree, _) = load_v1_reward_accounts(self, height, &[account])
330 .await
331 .with_context(|| {
332 format!("failed to load v1 reward account {account:?} at height {height}")
333 })?;
334
335 let (proof, balance) = RewardAccountProofV1::prove(&tree, account.into())
336 .with_context(|| {
337 format!("reward account {account:?} not available at height {height}")
338 })?;
339
340 Ok(RewardAccountQueryDataV1 { balance, proof })
341 } else {
342 bail!(
343 "requested height {height} is not yet available (latest block height: \
344 {block_height})"
345 );
346 }
347 }
348
349 fn persist_tree(
350 &self,
351 height: u64,
352 merkle_tree: Vec<u8>,
353 ) -> impl Send + Future<Output = anyhow::Result<()>> {
354 async move {
355 let mut tx = self
356 .write()
357 .await
358 .context("opening transaction for reward merkle tree v2")?;
359
360 tx.upsert(
361 "reward_merkle_tree_v2_data",
362 ["height", "balances"],
363 ["height"],
364 [(height as i64, merkle_tree)],
365 )
366 .await?;
367
368 hotshot_query_service::data_source::Transaction::commit(tx)
369 .await
370 .context("Transaction to store reward merkle tree v2 failed.")
371 }
372 }
373
374 fn load_tree(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
375 async move {
376 let mut tx = self
377 .read()
378 .await
379 .context("opening transaction for state update")?;
380
381 let row = sqlx::query(
382 r#"
383 SELECT balances
384 FROM reward_merkle_tree_v2_data
385 WHERE height = $1
386 "#,
387 )
388 .bind(height as i64)
389 .fetch_optional(tx.as_mut())
390 .await?
391 .context(format!(
392 "No reward merkle tree for height {} in storage",
393 height
394 ))?;
395
396 row.try_get::<Vec<u8>, _>("balances")
397 .context("Missing field balances from row; this should never happen")
398 }
399 }
400
401 fn load_latest_tree(
402 &self,
403 height: u64,
404 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
405 async move {
406 let mut tx = self
407 .read()
408 .await
409 .context("opening transaction for load_latest_tree")?;
410
411 let row = sqlx::query(
412 r#"
413 SELECT balances
414 FROM reward_merkle_tree_v2_data
415 WHERE height <= $1
416 ORDER BY height DESC
417 LIMIT 1
418 "#,
419 )
420 .bind(height as i64)
421 .fetch_optional(tx.as_mut())
422 .await?
423 .context(format!(
424 "No reward merkle tree at or below height {} in storage",
425 height
426 ))?;
427
428 row.try_get::<Vec<u8>, _>("balances")
429 .context("Missing field balances from row; this should never happen")
430 }
431 }
432
433 fn persist_proofs(
434 &self,
435 height: u64,
436 proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
437 ) -> impl Send + Future<Output = anyhow::Result<()>> {
438 async move {
439 let mut iter = proofs.map(|(account, proof)| (height as i64, account, proof));
440
441 loop {
442 let mut chunk = Vec::with_capacity(20);
443
444 for _ in 0..20 {
445 let Some(row) = iter.next() else {
446 continue;
447 };
448 chunk.push(row);
449 }
450
451 if chunk.is_empty() {
452 break;
453 }
454
455 let mut tx = self
456 .write()
457 .await
458 .context("opening transaction for state update")?;
459
460 tokio::spawn(async move {
461 tx.upsert(
462 "reward_merkle_tree_v2_proofs",
463 ["height", "account", "proof"],
464 ["height", "account"],
465 chunk,
466 )
467 .await?;
468
469 hotshot_query_service::data_source::Transaction::commit(tx)
470 .await
471 .context("Transaction to store reward merkle tree failed.")?;
472
473 Ok::<_, anyhow::Error>(())
474 });
475 }
476 Ok(())
477 }
478 }
479
480 fn load_proof(
487 &self,
488 height: u64,
489 account: Vec<u8>,
490 epoch_height: u64,
491 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
492 async move {
493 let mut tx = self
494 .read()
495 .await
496 .context("opening transaction for load_proof")?;
497
498 let leaf = tx
499 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
500 .await
501 .context(format!("leaf {height} not available"))?;
502
503 let proof_height = if leaf.header().version() < EPOCH_REWARD_VERSION
506 || is_last_block(height, epoch_height)
507 {
508 height
509 } else {
510 let prev_epoch_last_block =
511 epoch_from_block_number(height, epoch_height).saturating_sub(1) * epoch_height;
512 let boundary_leaf = tx
513 .get_leaf(LeafId::<SeqTypes>::from(prev_epoch_last_block as usize))
514 .await
515 .context(format!(
516 "leaf at epoch boundary {prev_epoch_last_block} not available"
517 ))?;
518 ensure!(
519 boundary_leaf.header().version() >= EPOCH_REWARD_VERSION,
520 "no epoch reward proofs available at boundary {prev_epoch_last_block}"
521 );
522 prev_epoch_last_block
523 };
524
525 sqlx::query_scalar(
526 "SELECT proof FROM reward_merkle_tree_v2_proofs WHERE height = $1 AND account = $2",
527 )
528 .bind(proof_height as i64)
529 .bind(account)
530 .fetch_optional(tx.as_mut())
531 .await?
532 .context(format!(
533 "Missing proofs at height {proof_height} (resolved from {height})"
534 ))
535 }
536 }
537
538 fn load_latest_proof(
539 &self,
540 account: Vec<u8>,
541 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
542 async move {
543 let mut tx = self
544 .read()
545 .await
546 .context("opening transaction for state update")?;
547
548 let row = sqlx::query(
549 r#"
550 SELECT proof
551 FROM reward_merkle_tree_v2_proofs
552 WHERE account = $1
553 ORDER BY height DESC
554 LIMIT 1
555 "#,
556 )
557 .bind(account)
558 .fetch_optional(tx.as_mut())
559 .await?
560 .context("Missing proofs")?;
561
562 row.try_get::<Vec<u8>, _>("proof")
563 .context("Missing field proof from row; this should never happen")
564 }
565 }
566
567 fn garbage_collect(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<()>> {
568 async move {
569 let batch_size = self.get_pruning_config().unwrap_or_default().batch_size();
570
571 let for_update = match Db::KIND {
575 DbKind::Sqlite => "",
576 DbKind::Postgres => "FOR UPDATE",
577 };
578
579 loop {
581 let mut tx = self
582 .write()
583 .await
584 .context("opening transaction for state update")?;
585
586 let res = sqlx::query(&format!(
587 "
588 WITH delete_batch AS (
589 SELECT d.height FROM reward_merkle_tree_v2_data AS d
590 WHERE d.height < $1
591 ORDER BY d.height DESC
592 LIMIT $2
593 {for_update}
594 )
595 DELETE FROM reward_merkle_tree_v2_data AS del
596 WHERE del.height IN (SELECT * FROM delete_batch)
597 "
598 ))
599 .bind(height as i64)
600 .bind(batch_size as i64)
601 .execute(tx.as_mut())
602 .await?;
603
604 hotshot_query_service::data_source::Transaction::commit(tx)
605 .await
606 .context(
607 "Transaction to garbage collect reward merkle trees from storage failed.",
608 )?;
609
610 if res.rows_affected() == 0 {
611 break;
612 } else {
613 tracing::debug!(
614 "deleted {} rows from reward_merkle_tree_v2_data",
615 res.rows_affected()
616 );
617 }
618 }
619
620 loop {
622 let mut tx = self
623 .write()
624 .await
625 .context("opening transaction for state update")?;
626
627 let res = sqlx::query(&format!(
628 "
629 WITH delete_batch AS (
630 SELECT d.height, d.account FROM reward_merkle_tree_v2_proofs AS d
631 WHERE d.height < $1
632 ORDER BY d.height, d.account DESC
633 LIMIT $2
634 {for_update}
635 )
636 DELETE FROM reward_merkle_tree_v2_proofs AS del
637 WHERE (del.height, del.account) IN (SELECT * FROM delete_batch)
638 ",
639 ))
640 .bind(height as i64)
641 .bind(batch_size as i64)
642 .execute(tx.as_mut())
643 .await?;
644
645 hotshot_query_service::data_source::Transaction::commit(tx)
646 .await
647 .context("Transaction to garbage collect proofs from storage failed.")?;
648
649 if res.rows_affected() == 0 {
650 break;
651 } else {
652 tracing::debug!(
653 "deleted {} rows from reward_merkle_tree_v2_proofs",
654 res.rows_affected()
655 );
656 }
657 }
658
659 Ok(())
660 }
661 }
662
663 fn proof_exists(&self, height: u64) -> impl Send + Future<Output = bool> {
664 async move {
665 let Ok(mut tx) = self.write().await else {
666 return false;
667 };
668
669 sqlx::query_as(
670 r#"
671 SELECT EXISTS(
672 SELECT 1 FROM reward_merkle_tree_v2_proofs
673 WHERE height = $1
674 )
675 "#,
676 )
677 .bind(height as i64)
678 .fetch_one(tx.as_mut())
679 .await
680 .ok()
681 .unwrap_or((false,))
682 .0
683 }
684 }
685 fn persist_reward_proofs(
692 &self,
693 node_state: &NodeState,
694 height: u64,
695 version: Version,
696 ) -> impl Send + Future<Output = anyhow::Result<()>> {
697 async move {
698 if !cfg!(any(test, feature = "testing"))
704 && !(height + node_state.node_id).is_multiple_of(30)
705 {
706 return Ok(());
707 }
708
709 let finalized_hotshot_height = if cfg!(any(test, feature = "testing")) {
713 height
714 } else {
715 match node_state.finalized_hotshot_height().await {
716 Ok(h) => h,
717 Err(err) => {
718 tracing::warn!("failed to get finalized hotshot height: {err:#}");
719 return Ok(());
720 },
721 }
722 };
723
724 let mut tree_height = finalized_hotshot_height;
727 let mut proof_height = finalized_hotshot_height;
728 if version >= EPOCH_REWARD_VERSION {
729 let epoch_height = node_state
730 .epoch_height
731 .context("epoch_height not set in node state")?;
732 if !is_last_block(finalized_hotshot_height, epoch_height) {
733 tree_height = epoch_from_block_number(finalized_hotshot_height, epoch_height)
734 .saturating_sub(1)
735 * epoch_height;
736 if tree_height == 0 {
737 return Ok(());
738 }
739 if height < tree_height {
742 return Ok(());
743 }
744 let mut tx = self.read().await.context("opening read transaction")?;
747 if let Ok(leaf) = tx
748 .get_leaf(LeafId::<SeqTypes>::from(tree_height as usize))
749 .await
750 && leaf.header().version() < EPOCH_REWARD_VERSION
751 {
752 return Ok(());
753 }
754 }
755 proof_height = tree_height;
756 }
757
758 if height < tree_height {
761 return Ok(());
762 }
763
764 if self.proof_exists(proof_height).await {
765 return Ok(());
766 }
767
768 let permitted_tree = match self.load_reward_merkle_tree_v2(tree_height).await {
769 Ok(tree) => tree,
770 Err(err) => {
771 tracing::warn!(tree_height, "failed to load reward merkle tree: {err:#}");
772 return Ok(());
773 },
774 };
775
776 let tree = permitted_tree.tree;
777 let iter = tree
778 .iter()
779 .filter_map(|(account, balance): (&RewardAccountV2, _)| {
780 let proof = espresso_types::v0_6::RewardAccountProofV2::prove(
781 &tree,
782 (*account).into(),
783 )?;
784 let proof = RewardAccountQueryDataV2 {
785 balance: (*balance).into(),
786 proof: proof.0,
787 };
788 Some((
789 bincode::serialize(&account).ok()?,
790 bincode::serialize(&proof).ok()?,
791 ))
792 });
793
794 if let Err(err) = self.persist_proofs(proof_height, iter).await {
795 tracing::warn!(proof_height, "failed to persist proofs: {err:#}");
796 }
797
798 Ok(())
799 }
800 }
801}
802
803impl CatchupStorage for SqlStorage {
804 async fn get_reward_accounts_v1(
805 &self,
806 instance: &NodeState,
807 height: u64,
808 view: ViewNumber,
809 accounts: &[RewardAccountV1],
810 ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
811 let mut tx = self.read().await.context(format!(
812 "opening transaction to fetch v1 reward account {accounts:?}; height {height}"
813 ))?;
814
815 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
816 .await
817 .context("getting block height")? as u64;
818 ensure!(
819 block_height > 0,
820 "cannot get accounts for height {height}: no blocks available"
821 );
822
823 if height < block_height {
826 load_v1_reward_accounts(self, height, accounts).await
827 } else {
828 let accounts: Vec<_> = accounts
829 .iter()
830 .map(|acct| RewardAccountV2::from(*acct))
831 .collect();
832 let (state, leaf) = reconstruct_state(
835 instance,
836 self,
837 &mut tx,
838 block_height - 1,
839 view,
840 &[],
841 &accounts,
842 )
843 .await?;
844 Ok((state.reward_merkle_tree_v1, leaf))
845 }
846 }
847
848 async fn get_reward_accounts_v2(
849 &self,
850 instance: &NodeState,
851 height: u64,
852 view: ViewNumber,
853 accounts: &[RewardAccountV2],
854 ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
855 let mut tx = self.read().await.context(format!(
856 "opening transaction to fetch reward account {accounts:?}; height {height}"
857 ))?;
858
859 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
860 .await
861 .context("getting block height")? as u64;
862 ensure!(
863 block_height > 0,
864 "cannot get accounts for height {height}: no blocks available"
865 );
866
867 if height < block_height {
870 load_reward_merkle_tree_v2(self, height)
871 .await
872 .map(|(permitted_tree, leaf)| (permitted_tree.tree, leaf))
873 } else {
874 let (state, leaf) = reconstruct_state(
877 instance,
878 self,
879 &mut tx,
880 block_height - 1,
881 view,
882 &[],
883 accounts,
884 )
885 .await?;
886 Ok((state.reward_merkle_tree_v2, leaf))
887 }
888 }
889
890 async fn get_accounts(
891 &self,
892 instance: &NodeState,
893 height: u64,
894 view: ViewNumber,
895 accounts: &[FeeAccount],
896 ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
897 let mut tx = self.read().await.context(format!(
898 "opening transaction to fetch account {accounts:?}; height {height}"
899 ))?;
900
901 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
902 .await
903 .context("getting block height")? as u64;
904 ensure!(
905 block_height > 0,
906 "cannot get accounts for height {height}: no blocks available"
907 );
908
909 if height < block_height {
912 load_accounts(&mut tx, height, accounts).await
913 } else {
914 let (state, leaf) = reconstruct_state(
917 instance,
918 self,
919 &mut tx,
920 block_height - 1,
921 view,
922 accounts,
923 &[],
924 )
925 .await?;
926 Ok((state.fee_merkle_tree, leaf))
927 }
928 }
929
930 async fn get_accounts_at(
931 &self,
932 height: u64,
933 root: FeeMerkleCommitment,
934 accounts: &[FeeAccount],
935 ) -> anyhow::Result<FeeMerkleTree> {
936 let mut tx = self.read().await.context(format!(
937 "opening transaction to fetch account {accounts:?}; height {height}"
938 ))?;
939 load_accounts_at(&mut tx, height, root, accounts).await
940 }
941
942 async fn get_frontier(
943 &self,
944 instance: &NodeState,
945 height: u64,
946 view: ViewNumber,
947 ) -> anyhow::Result<BlocksFrontier> {
948 let mut tx = self.read().await.context(format!(
949 "opening transaction to fetch frontier at height {height}"
950 ))?;
951
952 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
953 .await
954 .context("getting block height")? as u64;
955 ensure!(
956 block_height > 0,
957 "cannot get frontier for height {height}: no blocks available"
958 );
959
960 if height < block_height {
963 load_frontier(&mut tx, height).await
964 } else {
965 let (state, _) =
968 reconstruct_state(instance, self, &mut tx, block_height - 1, view, &[], &[])
969 .await?;
970 match state.block_merkle_tree.lookup(height - 1) {
971 LookupResult::Ok(_, proof) => Ok(proof),
972 _ => {
973 bail!(
974 "state snapshot {view:?},{height} was found but does not contain frontier \
975 at height {}; this should not be possible",
976 height - 1
977 );
978 },
979 }
980 }
981 }
982
983 async fn get_frontier_at(
984 &self,
985 height: u64,
986 root: BlockMerkleCommitment,
987 ) -> anyhow::Result<BlocksFrontier> {
988 let mut tx = self.read().await.context(format!(
989 "opening transaction to fetch frontier at height {height}"
990 ))?;
991 load_frontier_at(&mut tx, height, root).await
992 }
993
994 async fn get_chain_config(
995 &self,
996 commitment: Commitment<ChainConfig>,
997 ) -> anyhow::Result<ChainConfig> {
998 let mut tx = self.read().await.context(format!(
999 "opening transaction to fetch chain config {commitment}"
1000 ))?;
1001 load_chain_config(&mut tx, commitment).await
1002 }
1003
1004 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
1005 let mut tx = self
1006 .read()
1007 .await
1008 .context(format!("opening transaction to fetch leaf at {height}"))?;
1009 let leaf: Leaf2 = tx
1010 .get_leaf((height as usize).into())
1011 .await
1012 .context(format!("leaf {height} not available"))?
1013 .leaf()
1014 .clone();
1015
1016 if leaf.block_header().version() >= NEW_PROTOCOL_VERSION {
1019 let cert2: espresso_types::Certificate2<SeqTypes> =
1020 tx.load_earliest_cert2(height).await?.context(format!(
1021 "no cert2 available for new-protocol leaf at {height}"
1022 ))?;
1023 let cert2_height = cert2.data.block_number;
1024 let mut leaves = vec![leaf];
1025 if height < cert2_height {
1026 let descendants = tx
1027 .get_leaf_range((height as usize + 1)..=cert2_height as usize)
1028 .await?;
1029 leaves.extend(descendants.into_iter().flatten().map(|l| l.leaf().clone()));
1030 }
1031 return Ok(leaves);
1032 }
1033
1034 let mut last_leaf = leaf;
1036 let mut chain = vec![last_leaf.clone()];
1037 let mut h = height + 1;
1038
1039 loop {
1040 let lqd = tx.get_leaf((h as usize).into()).await?;
1041 let leaf = lqd.leaf();
1042
1043 if leaf.justify_qc().view_number() == last_leaf.view_number() {
1044 chain.push(leaf.clone());
1045 } else {
1046 h += 1;
1047 continue;
1048 }
1049
1050 if leaf.view_number() == last_leaf.view_number() + 1 {
1052 last_leaf = leaf.clone();
1053 h += 1;
1054 break;
1055 }
1056 h += 1;
1057 last_leaf = leaf.clone();
1058 }
1059
1060 loop {
1061 let lqd = tx.get_leaf((h as usize).into()).await?;
1062 let leaf = lqd.leaf();
1063 if leaf.justify_qc().view_number() == last_leaf.view_number() {
1064 chain.push(leaf.clone());
1065 break;
1066 }
1067 h += 1;
1068 }
1069
1070 Ok(chain)
1071 }
1072
1073 async fn load_cert2(
1074 &self,
1075 height: u64,
1076 ) -> anyhow::Result<Option<espresso_types::Certificate2<SeqTypes>>> {
1077 let mut tx = self
1078 .read()
1079 .await
1080 .context("opening transaction to fetch cert2")?;
1081 Ok(tx.load_cert2(height).await?)
1082 }
1083
1084 async fn get_leaf(&self, height: u64) -> anyhow::Result<Leaf2> {
1085 let mut tx = self
1086 .read()
1087 .await
1088 .context(format!("opening transaction to fetch leaf at {height}"))?;
1089 let lqd = tx
1090 .get_leaf((height as usize).into())
1091 .await
1092 .context(format!("leaf {height} not available"))?;
1093 Ok(lqd.leaf().clone())
1094 }
1095
1096 async fn load_serialized_reward_merkle_tree_v2(&self, height: u64) -> anyhow::Result<Vec<u8>> {
1099 let tree_bytes = self.load_latest_tree(height).await?;
1100 let tree_data = bincode::deserialize::<RewardMerkleTreeV2Data>(&tree_bytes)
1101 .context("Failed to deserialize RewardMerkleTreeV2 from storage")?;
1102 let tree = PermittedRewardMerkleTreeV2::try_from_kv_set(tree_data.balances)
1103 .await
1104 .context("Failed to reconstruct reward merkle tree from storage")?;
1105 let leaf = CatchupStorage::get_leaf(self, height).await?;
1106 let either::Either::Right(expected_root) = leaf.block_header().reward_merkle_tree_root()
1107 else {
1108 bail!("header {height} has no V2 reward merkle tree");
1109 };
1110 ensure!(
1111 tree.tree.commitment() == expected_root,
1112 "the reward merkle tree in storage at or below height {height} is from an older epoch"
1113 );
1114 Ok(tree_bytes)
1115 }
1116}
1117
1118impl RewardMerkleTreeDataSource for DataSource {
1119 async fn load_v1_reward_account_proof(
1120 &self,
1121 height: u64,
1122 account: RewardAccountV1,
1123 ) -> anyhow::Result<RewardAccountQueryDataV1> {
1124 self.as_ref()
1125 .load_v1_reward_account_proof(height, account)
1126 .await
1127 }
1128
1129 fn persist_tree(
1130 &self,
1131 height: u64,
1132 merkle_tree: Vec<u8>,
1133 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1134 async move { self.as_ref().persist_tree(height, merkle_tree).await }
1135 }
1136
1137 fn load_tree(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1138 async move { self.as_ref().load_tree(height).await }
1139 }
1140
1141 fn load_latest_tree(
1142 &self,
1143 height: u64,
1144 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1145 async move { self.as_ref().load_latest_tree(height).await }
1146 }
1147
1148 fn garbage_collect(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<()>> {
1149 async move { self.as_ref().garbage_collect(height).await }
1150 }
1151
1152 fn persist_proofs(
1153 &self,
1154 height: u64,
1155 proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
1156 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1157 async move { self.as_ref().persist_proofs(height, proofs).await }
1158 }
1159
1160 fn load_proof(
1161 &self,
1162 height: u64,
1163 account: Vec<u8>,
1164 epoch_height: u64,
1165 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1166 async move {
1167 self.as_ref()
1168 .load_proof(height, account, epoch_height)
1169 .await
1170 }
1171 }
1172
1173 fn load_latest_proof(
1174 &self,
1175 account: Vec<u8>,
1176 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
1177 async move { self.as_ref().load_latest_proof(account).await }
1178 }
1179
1180 fn proof_exists(&self, height: u64) -> impl Send + Future<Output = bool> {
1181 async move { self.as_ref().proof_exists(height).await }
1182 }
1183
1184 fn persist_reward_proofs(
1185 &self,
1186 node_state: &NodeState,
1187 height: u64,
1188 version: Version,
1189 ) -> impl Send + Future<Output = anyhow::Result<()>> {
1190 async move {
1191 self.as_ref()
1192 .persist_reward_proofs(node_state, height, version)
1193 .await
1194 }
1195 }
1196}
1197
1198mod request_storage {
1201 use async_trait::async_trait;
1202 use espresso_types::{
1203 Certificate2, ChainConfig, FeeAccount, FeeMerkleTree, Leaf2, NodeState,
1204 v0_3::{RewardAccountV1, RewardMerkleTreeV1},
1205 v0_4::{RewardAccountV2, RewardMerkleTreeV2},
1206 };
1207 use hotshot_types::data::{VidShare, ViewNumber};
1208
1209 use super::SqlStorage;
1210 use crate::{
1211 SeqTypes,
1212 api::{BlocksFrontier, RewardMerkleTreeDataSource},
1213 catchup::CatchupStorage,
1214 request_response::data_source::RequestStorage,
1215 };
1216
1217 #[async_trait]
1218 impl RequestStorage for SqlStorage {
1219 async fn get_accounts(
1220 &self,
1221 instance: &NodeState,
1222 height: u64,
1223 view: ViewNumber,
1224 accounts: &[FeeAccount],
1225 ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
1226 CatchupStorage::get_accounts(self, instance, height, view, accounts).await
1227 }
1228
1229 async fn get_reward_accounts_v1(
1230 &self,
1231 instance: &NodeState,
1232 height: u64,
1233 view: ViewNumber,
1234 accounts: &[RewardAccountV1],
1235 ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
1236 CatchupStorage::get_reward_accounts_v1(self, instance, height, view, accounts).await
1237 }
1238
1239 async fn get_reward_accounts_v2(
1240 &self,
1241 instance: &NodeState,
1242 height: u64,
1243 view: ViewNumber,
1244 accounts: &[RewardAccountV2],
1245 ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
1246 CatchupStorage::get_reward_accounts_v2(self, instance, height, view, accounts).await
1247 }
1248
1249 async fn get_frontier(
1250 &self,
1251 instance: &NodeState,
1252 height: u64,
1253 view: ViewNumber,
1254 ) -> anyhow::Result<BlocksFrontier> {
1255 CatchupStorage::get_frontier(self, instance, height, view).await
1256 }
1257
1258 async fn get_chain_config(
1259 &self,
1260 commitment: committable::Commitment<ChainConfig>,
1261 ) -> anyhow::Result<ChainConfig> {
1262 CatchupStorage::get_chain_config(self, commitment).await
1263 }
1264
1265 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
1266 CatchupStorage::get_leaf_chain(self, height).await
1267 }
1268
1269 async fn load_cert2(&self, height: u64) -> anyhow::Result<Option<Certificate2<SeqTypes>>> {
1270 CatchupStorage::load_cert2(self, height).await
1271 }
1272
1273 async fn load_tree(&self, height: u64) -> anyhow::Result<Vec<u8>> {
1274 RewardMerkleTreeDataSource::load_tree(self, height).await
1275 }
1276
1277 async fn get_vid_share(&self, block_number: u64) -> anyhow::Result<VidShare> {
1278 let id = hotshot_query_service::node::BlockId::Number(block_number as usize);
1279 Ok(SqlStorage::get_vid_share::<SeqTypes>(self, id).await?)
1280 }
1281 }
1282}
1283
1284impl CatchupStorage for DataSource {
1285 async fn get_accounts(
1286 &self,
1287 instance: &NodeState,
1288 height: u64,
1289 view: ViewNumber,
1290 accounts: &[FeeAccount],
1291 ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
1292 self.as_ref()
1293 .get_accounts(instance, height, view, accounts)
1294 .await
1295 }
1296
1297 async fn get_accounts_at(
1298 &self,
1299 height: u64,
1300 root: FeeMerkleCommitment,
1301 accounts: &[FeeAccount],
1302 ) -> anyhow::Result<FeeMerkleTree> {
1303 self.as_ref().get_accounts_at(height, root, accounts).await
1304 }
1305
1306 async fn get_reward_accounts_v2(
1307 &self,
1308 instance: &NodeState,
1309 height: u64,
1310 view: ViewNumber,
1311 accounts: &[RewardAccountV2],
1312 ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
1313 self.as_ref()
1314 .get_reward_accounts_v2(instance, height, view, accounts)
1315 .await
1316 }
1317
1318 async fn get_reward_accounts_v1(
1319 &self,
1320 instance: &NodeState,
1321 height: u64,
1322 view: ViewNumber,
1323 accounts: &[RewardAccountV1],
1324 ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
1325 self.as_ref()
1326 .get_reward_accounts_v1(instance, height, view, accounts)
1327 .await
1328 }
1329
1330 async fn get_frontier(
1331 &self,
1332 instance: &NodeState,
1333 height: u64,
1334 view: ViewNumber,
1335 ) -> anyhow::Result<BlocksFrontier> {
1336 self.as_ref().get_frontier(instance, height, view).await
1337 }
1338
1339 async fn get_frontier_at(
1340 &self,
1341 height: u64,
1342 root: BlockMerkleCommitment,
1343 ) -> anyhow::Result<BlocksFrontier> {
1344 self.as_ref().get_frontier_at(height, root).await
1345 }
1346
1347 async fn get_chain_config(
1348 &self,
1349 commitment: Commitment<ChainConfig>,
1350 ) -> anyhow::Result<ChainConfig> {
1351 self.as_ref().get_chain_config(commitment).await
1352 }
1353 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
1354 self.as_ref().get_leaf_chain(height).await
1355 }
1356
1357 async fn load_cert2(
1358 &self,
1359 height: u64,
1360 ) -> anyhow::Result<Option<espresso_types::Certificate2<SeqTypes>>> {
1361 self.as_ref().load_cert2(height).await
1362 }
1363
1364 async fn get_leaf(&self, height: u64) -> anyhow::Result<Leaf2> {
1365 self.as_ref().get_leaf(height).await
1366 }
1367
1368 async fn load_serialized_reward_merkle_tree_v2(&self, height: u64) -> anyhow::Result<Vec<u8>> {
1369 self.as_ref()
1370 .load_serialized_reward_merkle_tree_v2(height)
1371 .await
1372 }
1373}
1374
1375#[async_trait]
1376impl ChainConfigPersistence for Transaction<Write> {
1377 async fn insert_chain_config(&mut self, chain_config: ChainConfig) -> anyhow::Result<()> {
1378 let commitment = chain_config.commitment();
1379 let data = bincode::serialize(&chain_config)?;
1380 self.upsert(
1381 "chain_config",
1382 ["commitment", "data"],
1383 ["commitment"],
1384 [(commitment.to_string(), data)],
1385 )
1386 .await
1387 }
1388}
1389
1390impl crate::api::data_source::PruningDataSource for SqlStorage {
1391 async fn get_oldest_block(
1392 &self,
1393 ) -> anyhow::Result<Option<hotshot_query_service::availability::BlockQueryData<SeqTypes>>> {
1394 let mut tx = self
1395 .read()
1396 .await
1397 .context("opening transaction to fetch oldest block")?;
1398 let row = sqlx::query("SELECT MIN(height) AS height FROM header")
1399 .fetch_one(tx.as_mut())
1400 .await
1401 .context("failed to query oldest block height")?;
1402 let height: Option<i64> = row.try_get("height")?;
1403 match height {
1404 None => Ok(None),
1405 Some(h) => {
1406 let h = usize::try_from(h).context("block height out of range")?;
1407 Ok(Some(
1408 tx.get_block(hotshot_query_service::availability::BlockId::<SeqTypes>::from(h))
1409 .await
1410 .context(format!("block {h} not available"))?,
1411 ))
1412 },
1413 }
1414 }
1415
1416 async fn get_oldest_leaf(
1417 &self,
1418 ) -> anyhow::Result<Option<hotshot_query_service::availability::LeafQueryData<SeqTypes>>> {
1419 let mut tx = self
1420 .read()
1421 .await
1422 .context("opening transaction to fetch oldest leaf")?;
1423 let row = sqlx::query("SELECT MIN(height) AS height FROM leaf2")
1424 .fetch_one(tx.as_mut())
1425 .await
1426 .context("failed to query oldest leaf height")?;
1427 let height: Option<i64> = row.try_get("height")?;
1428 match height {
1429 None => Ok(None),
1430 Some(h) => {
1431 let h = usize::try_from(h).context("leaf height out of range")?;
1432 Ok(Some(
1433 tx.get_leaf(LeafId::<SeqTypes>::from(h))
1434 .await
1435 .context(format!("leaf {h} not available"))?,
1436 ))
1437 },
1438 }
1439 }
1440}
1441
1442impl crate::api::data_source::DatabaseMetadataSource for SqlStorage {
1443 async fn get_table_sizes(&self) -> anyhow::Result<Vec<espresso_api::v1::database::TableSize>> {
1444 let mut tx = self
1445 .read()
1446 .await
1447 .context("opening transaction to fetch table sizes")?;
1448
1449 if Db::KIND == DbKind::Postgres {
1450 let query = r#"
1451 SELECT
1452 schemaname || '.' || relname AS table_name,
1453 n_live_tup AS row_count,
1454 pg_total_relation_size(relid) AS total_size_bytes
1455 FROM pg_stat_user_tables
1456 ORDER BY pg_total_relation_size(relid) DESC
1457 "#;
1458
1459 let rows = sqlx::query(query)
1460 .fetch_all(tx.as_mut())
1461 .await
1462 .context("failed to query table sizes")?;
1463
1464 let mut table_sizes = Vec::new();
1465 for row in rows {
1466 let table_name: String = row.try_get("table_name")?;
1467 let row_count: i64 = row.try_get("row_count").unwrap_or(-1);
1468 let total_size_bytes: Option<i64> = row.try_get("total_size_bytes").ok();
1469
1470 table_sizes.push(espresso_api::v1::database::TableSize {
1471 table_name,
1472 row_count,
1473 total_size_bytes,
1474 });
1475 }
1476
1477 Ok(table_sizes)
1478 } else {
1479 let table_names_query = r#"
1481 SELECT name
1482 FROM sqlite_master
1483 WHERE type = 'table'
1484 AND name NOT LIKE 'sqlite_%'
1485 ORDER BY name
1486 "#;
1487
1488 let table_rows = sqlx::query(table_names_query)
1489 .fetch_all(tx.as_mut())
1490 .await
1491 .context("failed to query table names")?;
1492
1493 let mut table_sizes = Vec::new();
1494
1495 for row in table_rows {
1497 let table_name: String = row.try_get("name")?;
1498
1499 let count_query = format!("SELECT COUNT(*) as count FROM \"{}\"", table_name);
1502 let count_row = sqlx::query(&count_query)
1503 .fetch_one(tx.as_mut())
1504 .await
1505 .context(format!(
1506 "failed to query row count for table {}",
1507 table_name
1508 ))?;
1509
1510 let row_count: i64 = count_row.try_get("count").unwrap_or(0);
1511
1512 table_sizes.push(espresso_api::v1::database::TableSize {
1513 table_name,
1514 row_count,
1515 total_size_bytes: None,
1516 });
1517 }
1518
1519 Ok(table_sizes)
1520 }
1521 }
1522
1523 async fn get_migration_status(
1524 &self,
1525 ) -> anyhow::Result<Vec<espresso_api::v1::database::MigrationStatus>> {
1526 let mut tx = self
1527 .read()
1528 .await
1529 .context("opening transaction to fetch migration status")?;
1530
1531 type MigrationStatusRow = (
1532 String,
1533 chrono::DateTime<chrono::Utc>,
1534 Option<chrono::DateTime<chrono::Utc>>,
1535 Option<i64>,
1536 );
1537
1538 let rows: Vec<MigrationStatusRow> = sqlx::query_as(
1539 "SELECT name, started_at, completed_at, last_offset FROM deferred_migrations ORDER BY \
1540 started_at",
1541 )
1542 .fetch_all(tx.as_mut())
1543 .await
1544 .context("failed to query deferred_migrations")?;
1545
1546 Ok(rows
1547 .into_iter()
1548 .map(|(name, started_at, completed_at, last_offset)| {
1549 espresso_api::v1::database::MigrationStatus {
1550 name,
1551 started_at,
1552 completed_at,
1553 last_offset,
1554 }
1555 })
1556 .collect())
1557 }
1558}
1559
1560impl crate::api::data_source::DatabaseMetadataSource for DataSource {
1561 async fn get_table_sizes(&self) -> anyhow::Result<Vec<espresso_api::v1::database::TableSize>> {
1562 self.as_ref().get_table_sizes().await
1563 }
1564
1565 async fn get_migration_status(
1566 &self,
1567 ) -> anyhow::Result<Vec<espresso_api::v1::database::MigrationStatus>> {
1568 self.as_ref().get_migration_status().await
1569 }
1570}
1571
1572impl crate::api::data_source::PruningDataSource for DataSource {
1573 async fn get_oldest_block(
1574 &self,
1575 ) -> anyhow::Result<Option<hotshot_query_service::availability::BlockQueryData<SeqTypes>>> {
1576 self.as_ref().get_oldest_block().await
1577 }
1578
1579 async fn get_oldest_leaf(
1580 &self,
1581 ) -> anyhow::Result<Option<hotshot_query_service::availability::LeafQueryData<SeqTypes>>> {
1582 self.as_ref().get_oldest_leaf().await
1583 }
1584}
1585
1586async fn load_frontier<Mode: TransactionMode>(
1587 tx: &mut Transaction<Mode>,
1588 height: u64,
1589) -> anyhow::Result<BlocksFrontier> {
1590 tx.get_path(
1591 Snapshot::<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>::Index(height),
1592 height
1593 .checked_sub(1)
1594 .ok_or(anyhow::anyhow!("Subtract with overflow ({height})!"))?,
1595 )
1596 .await
1597 .context(format!("fetching frontier at height {height}"))
1598}
1599
1600async fn load_frontier_at<Mode: TransactionMode>(
1603 tx: &mut Transaction<Mode>,
1604 height: u64,
1605 root: BlockMerkleCommitment,
1606) -> anyhow::Result<BlocksFrontier> {
1607 tx.get_path_at::<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>(
1608 height,
1609 root,
1610 height
1611 .checked_sub(1)
1612 .ok_or(anyhow::anyhow!("Subtract with overflow ({height})!"))?,
1613 )
1614 .await
1615 .context(format!("fetching frontier at height {height}"))
1616}
1617
1618async fn load_v1_reward_accounts(
1619 db: &SqlStorage,
1620 height: u64,
1621 accounts: &[RewardAccountV1],
1622) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
1623 let mut tx = db
1625 .read()
1626 .await
1627 .with_context(|| "failed to open read transaction")?;
1628
1629 let leaf = tx
1631 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
1632 .await
1633 .context(format!("leaf {height} not available"))?;
1634 let header = leaf.header();
1635
1636 if header.version() < EPOCH_VERSION || header.version() >= DRB_AND_HEADER_UPGRADE_VERSION {
1637 return Ok((
1638 RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT),
1639 leaf.leaf().clone(),
1640 ));
1641 }
1642
1643 let merkle_root = header.reward_merkle_tree_root().unwrap_left();
1645 let mut snapshot = RewardMerkleTreeV1::from_commitment(merkle_root);
1646
1647 let mut join_set = BoundedJoinSet::new(10);
1649
1650 let mut task_id_to_account = HashMap::new();
1652
1653 for account in accounts {
1655 let db_clone = db.clone();
1657 let account_clone = *account;
1658 let header_height = header.height();
1659
1660 let func = async move {
1662 let mut tx = db_clone
1664 .read()
1665 .await
1666 .with_context(|| "failed to open read transaction")?;
1667
1668 let proof = tx
1670 .get_path(
1671 Snapshot::<SeqTypes, RewardMerkleTreeV1, { RewardMerkleTreeV1::ARITY }>::Index(
1672 header_height,
1673 ),
1674 account_clone,
1675 )
1676 .await
1677 .with_context(|| {
1678 format!(
1679 "failed to get path for v1 reward account {account_clone:?}; height \
1680 {height}"
1681 )
1682 })?;
1683
1684 Ok::<_, anyhow::Error>(proof)
1685 };
1686
1687 let id = join_set.spawn(func).id();
1689
1690 task_id_to_account.insert(id, account);
1692 }
1693
1694 while let Some(result) = join_set.join_next_with_id().await {
1696 let (id, result) = result.with_context(|| "failed to join task")?;
1698
1699 let proof = result?;
1701
1702 let account = task_id_to_account
1704 .remove(&id)
1705 .with_context(|| "task ID for spawned task not found")?;
1706
1707 match proof.proof.first().with_context(|| {
1708 format!("empty proof for v1 reward account {account:?}; height {height}")
1709 })? {
1710 MerkleNode::Leaf { pos, elem, .. } => {
1711 snapshot.remember(*pos, *elem, proof)?;
1712 },
1713 MerkleNode::Empty => {
1714 snapshot.non_membership_remember(*account, proof)?;
1715 },
1716 _ => {
1717 bail!("invalid proof for v1 reward account {account:?}; height {height}");
1718 },
1719 }
1720 }
1721
1722 Ok((snapshot, leaf.leaf().clone()))
1723}
1724
1725async fn load_reward_merkle_tree_v2(
1727 db: &SqlStorage,
1728 height: u64,
1729) -> anyhow::Result<(PermittedRewardMerkleTreeV2, Leaf2)> {
1730 let mut tx = db
1732 .read()
1733 .await
1734 .with_context(|| "failed to open read transaction")?;
1735
1736 let leaf = tx
1738 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
1739 .await
1740 .with_context(|| format!("leaf {height} not available"))?;
1741
1742 let snapshot = db.load_reward_merkle_tree_v2(height).await?;
1743
1744 Ok((snapshot, leaf.leaf().clone()))
1745}
1746
1747async fn load_accounts<Mode: TransactionMode>(
1748 tx: &mut Transaction<Mode>,
1749 height: u64,
1750 accounts: &[FeeAccount],
1751) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
1752 let leaf = tx
1753 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
1754 .await
1755 .context(format!("leaf {height} not available"))?;
1756 let header = leaf.header();
1757 let snapshot =
1758 load_accounts_at(tx, header.height(), header.fee_merkle_tree_root(), accounts).await?;
1759 Ok((snapshot, leaf.leaf().clone()))
1760}
1761
1762async fn load_accounts_at<Mode: TransactionMode>(
1765 tx: &mut Transaction<Mode>,
1766 height: u64,
1767 root: FeeMerkleCommitment,
1768 accounts: &[FeeAccount],
1769) -> anyhow::Result<FeeMerkleTree> {
1770 let mut snapshot = FeeMerkleTree::from_commitment(root);
1771 for account in accounts {
1772 let proof = tx
1773 .get_path_at::<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>(
1774 height, root, *account,
1775 )
1776 .await
1777 .context(format!("fetching account {account}; height {height}"))?;
1778 match proof.proof.first().context(format!(
1779 "empty proof for account {account}; height {height}"
1780 ))? {
1781 MerkleNode::Leaf { pos, elem, .. } => {
1782 snapshot.remember(*pos, *elem, proof)?;
1783 },
1784 MerkleNode::Empty => {
1785 snapshot.non_membership_remember(*account, proof)?;
1786 },
1787 _ => {
1788 bail!("Invalid proof");
1789 },
1790 }
1791 }
1792
1793 Ok(snapshot)
1794}
1795
1796async fn load_chain_config<Mode: TransactionMode>(
1797 tx: &mut Transaction<Mode>,
1798 commitment: Commitment<ChainConfig>,
1799) -> anyhow::Result<ChainConfig> {
1800 let (data,) = query_as::<(Vec<u8>,)>("SELECT data from chain_config where commitment = $1")
1801 .bind(commitment.to_string())
1802 .fetch_one(tx.as_mut())
1803 .await
1804 .unwrap();
1805
1806 bincode::deserialize(&data[..]).context("failed to deserialize")
1807}
1808
1809#[tracing::instrument(skip(instance, db, tx))]
1820pub(crate) async fn reconstruct_state<Mode: TransactionMode>(
1821 instance: &NodeState,
1822 db: &SqlStorage,
1823 tx: &mut Transaction<Mode>,
1824 from_height: u64,
1825 to_view: ViewNumber,
1826 fee_accounts: &[FeeAccount],
1827 reward_accounts: &[RewardAccountV2],
1828) -> anyhow::Result<(ValidatedState, Leaf2)> {
1829 tracing::info!("attempting to reconstruct fee state");
1830 let from_leaf = tx
1831 .get_leaf((from_height as usize).into())
1832 .await
1833 .context(format!("leaf {from_height} not available"))?;
1834 let from_leaf: Leaf2 = from_leaf.leaf().clone();
1835 ensure!(
1836 from_leaf.view_number() < to_view,
1837 "state reconstruction: starting state {:?} must be before ending state {to_view:?}",
1838 from_leaf.view_number(),
1839 );
1840
1841 let mut leaves = VecDeque::new();
1843 let to_leaf = get_leaf_from_proposal(tx, "view = $1", &(to_view.u64() as i64))
1844 .await
1845 .context(format!(
1846 "unable to reconstruct state because leaf {to_view:?} is not available"
1847 ))?;
1848 let mut parent = to_leaf.parent_commitment();
1849 tracing::debug!(?to_leaf, ?parent, view = ?to_view, "have required leaf");
1850 leaves.push_front(to_leaf.clone());
1851 while parent != Committable::commit(&from_leaf) {
1852 let leaf = get_leaf_from_proposal(tx, "leaf_hash = $1", &parent.to_string())
1853 .await
1854 .context(format!(
1855 "unable to reconstruct state because leaf {parent} is not available"
1856 ))?;
1857 parent = leaf.parent_commitment();
1858 tracing::debug!(?leaf, ?parent, "have required leaf");
1859 leaves.push_front(leaf);
1860 }
1861
1862 let mut parent = from_leaf;
1864 let mut state = ValidatedState::from_header(parent.block_header());
1865
1866 let mut catchup = NullStateCatchup::default();
1869
1870 let mut fee_accounts = fee_accounts.iter().copied().collect::<HashSet<_>>();
1871 tracing::info!(
1874 "reconstructing fee accounts state for from height {from_height} to view {to_view}"
1875 );
1876
1877 let dependencies =
1878 fee_header_dependencies(&mut catchup, tx, instance, &parent, &leaves).await?;
1879 fee_accounts.extend(dependencies);
1880 let fee_accounts = fee_accounts.into_iter().collect::<Vec<_>>();
1881 state.fee_merkle_tree = load_accounts(tx, from_height, &fee_accounts)
1882 .await
1883 .context("unable to reconstruct state because accounts are not available at origin")?
1884 .0;
1885 ensure!(
1886 state.fee_merkle_tree.commitment() == parent.block_header().fee_merkle_tree_root(),
1887 "loaded fee state does not match parent header"
1888 );
1889
1890 tracing::info!(
1891 "reconstructing reward accounts for from height {from_height} to view {to_view}"
1892 );
1893
1894 let mut reward_accounts = reward_accounts.iter().copied().collect::<HashSet<_>>();
1895
1896 let dependencies = reward_header_dependencies(instance, &leaves).await?;
1899 reward_accounts.extend(dependencies);
1900 let reward_accounts = reward_accounts.into_iter().collect::<Vec<_>>();
1901
1902 match parent.block_header().reward_merkle_tree_root() {
1904 either::Either::Left(expected_root) => {
1905 let accts = reward_accounts
1906 .into_iter()
1907 .map(RewardAccountV1::from)
1908 .collect::<Vec<_>>();
1909 state.reward_merkle_tree_v1 = load_v1_reward_accounts(db, from_height, &accts)
1910 .await
1911 .context(
1912 "unable to reconstruct state because v1 reward accounts are not available at \
1913 origin",
1914 )?
1915 .0;
1916 ensure!(
1917 state.reward_merkle_tree_v1.commitment() == expected_root,
1918 "loaded v1 reward state does not match parent header"
1919 );
1920 },
1921 either::Either::Right(expected_root) => {
1922 let version = parent.block_header().version();
1923 let epoch_height = instance
1924 .epoch_height
1925 .context("epoch_height not set but parent has V2 reward tree")?;
1926
1927 if version >= EPOCH_REWARD_VERSION && !is_last_block(from_height, epoch_height) {
1934 let tree = db
1935 .load_latest_reward_merkle_tree_v2(from_height)
1936 .await
1937 .context("RewardMerkleTreeV2 not available at or below origin")?;
1938 state.reward_merkle_tree_v2 = tree.tree;
1939 } else {
1940 state.reward_merkle_tree_v2 = load_reward_merkle_tree_v2(db, from_height)
1941 .await
1942 .context("RewardMerkleTreeV2 not available at origin")?
1943 .0
1944 .tree;
1945 }
1946 ensure!(
1947 state.reward_merkle_tree_v2.commitment() == expected_root,
1948 "loaded reward state does not match parent header"
1949 );
1950 },
1951 }
1952
1953 let frontier = load_frontier(tx, from_height)
1955 .await
1956 .context("unable to reconstruct state because frontier is not available at origin")?;
1957 match frontier
1958 .proof
1959 .first()
1960 .context("empty proof for frontier at origin")?
1961 {
1962 MerkleNode::Leaf { pos, elem, .. } => state
1963 .block_merkle_tree
1964 .remember(*pos, *elem, frontier)
1965 .context("failed to remember frontier")?,
1966 _ => bail!("invalid frontier proof"),
1967 }
1968
1969 for proposal in leaves {
1971 state = compute_state_update(&state, instance, &catchup, &parent, &proposal)
1972 .await
1973 .context(format!(
1974 "unable to reconstruct state because state update {} failed",
1975 proposal.height(),
1976 ))?
1977 .0;
1978 parent = proposal;
1979 }
1980
1981 tracing::info!(from_height, ?to_view, "successfully reconstructed state");
1982 Ok((state, to_leaf))
1983}
1984
1985async fn fee_header_dependencies<Mode: TransactionMode>(
1992 catchup: &mut NullStateCatchup,
1993 tx: &mut Transaction<Mode>,
1994 instance: &NodeState,
1995 mut parent: &Leaf2,
1996 leaves: impl IntoIterator<Item = &Leaf2>,
1997) -> anyhow::Result<HashSet<FeeAccount>> {
1998 let mut accounts = HashSet::default();
1999
2000 for proposal in leaves {
2001 let header = proposal.block_header();
2002 let height = header.height();
2003 let view = proposal.view_number();
2004 tracing::debug!(height, ?view, "fetching dependencies for proposal");
2005
2006 let header_cf = header.chain_config();
2007 let chain_config = if header_cf.commit() == instance.chain_config.commit() {
2008 instance.chain_config
2009 } else {
2010 match header_cf.resolve() {
2011 Some(cf) => cf,
2012 None => {
2013 tracing::info!(
2014 height,
2015 ?view,
2016 commit = %header_cf.commit(),
2017 "chain config not available, attempting to load from storage",
2018 );
2019 let cf = load_chain_config(tx, header_cf.commit())
2020 .await
2021 .context(format!(
2022 "loading chain config {} for header {},{:?}",
2023 header_cf.commit(),
2024 header.height(),
2025 proposal.view_number()
2026 ))?;
2027
2028 catchup.add_chain_config(cf);
2031 cf
2032 },
2033 }
2034 };
2035
2036 accounts.insert(chain_config.fee_recipient);
2037 accounts.extend(
2038 get_l1_deposits(instance, header, parent, chain_config.fee_contract)
2039 .await
2040 .into_iter()
2041 .map(|fee| fee.account()),
2042 );
2043 accounts.extend(header.fee_info().accounts());
2044 parent = proposal;
2045 }
2046 Ok(accounts)
2047}
2048
2049async fn reward_header_dependencies(
2053 instance: &NodeState,
2054 leaves: impl IntoIterator<Item = &Leaf2>,
2055) -> anyhow::Result<HashSet<RewardAccountV2>> {
2056 let mut reward_accounts = HashSet::default();
2057 let epoch_height = instance.epoch_height;
2058
2059 let Some(epoch_height) = epoch_height else {
2060 tracing::info!("epoch height is not set. returning empty reward_header_dependencies");
2061 return Ok(HashSet::new());
2062 };
2063
2064 let coordinator = instance.coordinator.clone();
2065 let first_epoch = coordinator.membership().first_epoch();
2066 for proposal in leaves {
2068 let header = proposal.block_header();
2069
2070 let height = header.height();
2071 let view = proposal.view_number();
2072 tracing::debug!(height, ?view, "fetching dependencies for proposal");
2073
2074 let version = header.version();
2075 if version < EPOCH_VERSION || version >= EPOCH_REWARD_VERSION {
2077 continue;
2078 }
2079
2080 let proposal_epoch = EpochNumber::new(epoch_from_block_number(height, epoch_height));
2081
2082 if proposal_epoch <= first_epoch + 1 {
2084 continue;
2085 }
2086
2087 let epoch_membership = match coordinator.membership_for_epoch(Some(proposal_epoch)) {
2088 Ok(e) => e,
2089 Err(err) => {
2090 tracing::info!(
2091 "failed to get membership for epoch={proposal_epoch:?}. err={err:#}"
2092 );
2093
2094 coordinator
2095 .wait_for_catchup(proposal_epoch)
2096 .await
2097 .context(format!("failed to catchup for epoch={proposal_epoch}"))?
2098 },
2099 };
2100
2101 let snapshot = epoch_membership
2102 .snapshot()
2103 .with_context(|| format!("no committee for epoch={proposal_epoch}"))?;
2104 let leader = snapshot.lookup_leader(proposal.view_number())?;
2105 let validator = snapshot.validator_config(&leader)?;
2106
2107 reward_accounts.insert(RewardAccountV2(validator.account));
2108
2109 let delegators: Vec<RewardAccountV2> = validator
2110 .delegators
2111 .keys()
2112 .map(|d| RewardAccountV2(*d))
2113 .collect();
2114
2115 reward_accounts.extend(delegators);
2116 }
2117 Ok(reward_accounts)
2118}
2119
2120async fn get_leaf_from_proposal<Mode, P>(
2121 tx: &mut Transaction<Mode>,
2122 where_clause: &str,
2123 param: P,
2124) -> anyhow::Result<Leaf2>
2125where
2126 P: Type<Db> + for<'q> Encode<'q, Db>,
2127{
2128 let (data,) = query_as::<(Vec<u8>,)>(&format!(
2129 "SELECT data FROM quorum_proposals2 WHERE {where_clause} LIMIT 1",
2130 ))
2131 .bind(param)
2132 .fetch_one(tx.as_mut())
2133 .await?;
2134 let proposal: Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>> =
2135 bincode::deserialize(&data)?;
2136 Ok(Leaf2::from_quorum_proposal(&proposal.data))
2137}
2138
2139#[cfg(any(test, feature = "testing"))]
2140pub(crate) mod impl_testable_data_source {
2141
2142 use hotshot_query_service::{
2143 availability::LeafQueryData,
2144 data_source::{
2145 Transaction as _,
2146 storage::{
2147 MerklizedStateHeightStorage as _, SerializableRetry as _,
2148 UpdateAvailabilityStorage as _, pruning::PrunedHeightStorage as _,
2149 sql::StorageConnectionType,
2150 },
2151 },
2152 merklized_state::UpdateStateData,
2153 serializable_retry,
2154 };
2155
2156 pub(crate) use super::super::persistence::tmp_options;
2157 use super::{super::qs::testing::TmpDb, *};
2158 use crate::api::{
2159 self,
2160 data_source::testing::{
2161 DeferredMigration, TestableSequencerDataSource, TestableSqlDataSource,
2162 },
2163 };
2164
2165 #[async_trait]
2166 impl TestableSequencerDataSource for DataSource {
2167 type Storage = TmpDb;
2168
2169 async fn create_storage() -> Self::Storage {
2170 TmpDb::init().await
2171 }
2172
2173 fn persistence_options(storage: &Self::Storage) -> Self::Options {
2174 Options::try_from(tmp_options(storage)).unwrap()
2175 }
2176
2177 fn leaf_only_ds_options(
2178 storage: &Self::Storage,
2179 opt: api::Options,
2180 ) -> anyhow::Result<api::Options> {
2181 let mut ds_opts = tmp_options(storage);
2182 ds_opts.lightweight = true;
2183 Ok(opt.query_sql(Default::default(), ds_opts))
2184 }
2185
2186 fn options(storage: &Self::Storage, opt: api::Options) -> api::Options {
2187 opt.query_sql(Query::default(), tmp_options(storage))
2188 }
2189 }
2190
2191 #[async_trait]
2192 impl TestableSqlDataSource for DataSource {
2193 type SqlStorage = SqlStorage;
2194
2195 fn tmp_options(storage: &Self::Storage) -> SqlOptions {
2196 tmp_options(storage)
2197 }
2198
2199 fn sql_persistence_options(opt: SqlOptions) -> Self::Options {
2200 Options::try_from(opt).unwrap()
2201 }
2202
2203 async fn on_request_data_source(
2204 storage: &Self::Storage,
2205 provider: Provider,
2206 ) -> anyhow::Result<Self> {
2207 Config::try_from(&tmp_options(storage))?
2208 .builder(provider)
2209 .await?
2210 .disable_proactive_fetching()
2211 .disable_aggregator()
2212 .build()
2213 .await
2214 }
2215
2216 async fn prune_state_below(ds: &Self, height: u64, cfg: &PrunerCfg) -> anyhow::Result<()> {
2217 let sql: &SqlStorage = ds.as_ref();
2218 sql.prune_state_below(height, 0, cfg).await
2219 }
2220
2221 async fn connect(
2222 opt: &SqlOptions,
2223 connection_type: StorageConnectionType,
2224 ) -> anyhow::Result<SqlStorage> {
2225 Ok(SqlStorage::connect(Config::try_from(opt)?, connection_type).await?)
2226 }
2227
2228 async fn local_block_height(db: &SqlStorage) -> anyhow::Result<u64> {
2229 let mut tx = db.read().await?;
2230 Ok(NodeStorage::<SeqTypes>::block_height(&mut tx).await? as u64)
2231 }
2232
2233 async fn state_heights(db: &SqlStorage) -> anyhow::Result<(Option<u64>, u64)> {
2237 let mut tx = db.read().await?;
2238 let pruned = tx.load_state_pruned_height().await?;
2239 let head = tx.get_last_state_height().await? as u64;
2240 Ok((pruned, head))
2241 }
2242
2243 async fn state_start_height(db: &SqlStorage) -> anyhow::Result<Option<u64>> {
2244 Ok(db.read().await?.load_state_start_height().await?)
2245 }
2246
2247 async fn lowest_block_tree_node(db: &SqlStorage) -> anyhow::Result<u64> {
2248 let mut tx = db.read().await?;
2249 let lowest: Option<i64> =
2250 sqlx::query_scalar("SELECT min(created) FROM block_merkle_tree")
2251 .fetch_one(tx.as_mut())
2252 .await?;
2253 lowest
2254 .map(|height| height as u64)
2255 .context("no block tree nodes")
2256 }
2257
2258 async fn insert_leaf(
2259 db: &SqlStorage,
2260 leaf: &LeafQueryData<SeqTypes>,
2261 ) -> anyhow::Result<()> {
2262 let mut tx = db.write().await?;
2263 tx.insert_leaf(leaf).await?;
2264 tx.commit().await
2265 }
2266
2267 async fn set_last_state_height(db: &SqlStorage, height: u64) -> anyhow::Result<()> {
2268 let mut tx = db.write().await?;
2269 UpdateStateData::<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>::set_last_state_height(
2270 &mut tx,
2271 height as usize,
2272 )
2273 .await?;
2274 tx.commit().await
2275 }
2276
2277 async fn stamp_state_pruned_height(db: &SqlStorage, height: u64) -> anyhow::Result<()> {
2278 let mut tx = db.write().await?;
2279 tx.upsert(
2280 "pruned_height",
2281 ["id", "last_height"],
2282 ["id"],
2283 [(Transaction::<Write>::STATE_PRUNED_HEIGHT_ID, height as i64)],
2284 )
2285 .await?;
2286 tx.commit().await
2287 }
2288
2289 async fn prune_data_to(db: &SqlStorage, pruned: u64) -> anyhow::Result<()> {
2291 serializable_retry!(db, || async {
2292 let mut tx = db.write().await?;
2293 tx.upsert(
2294 "pruned_height",
2295 ["id", "last_height"],
2296 ["id"],
2297 [(Transaction::<Write>::PRUNED_HEIGHT_ID, pruned as i64)],
2298 )
2299 .await?;
2300 for statement in [
2301 "DELETE FROM transactions WHERE block_height <= $1",
2302 "DELETE FROM leaf2 WHERE height <= $1",
2303 "DELETE FROM header WHERE height <= $1",
2304 ] {
2305 sqlx::query(statement)
2306 .bind(pruned as i64)
2307 .execute(tx.as_mut())
2308 .await?;
2309 }
2310 tx.commit().await
2311 })
2312 .await
2313 }
2314
2315 async fn delete_data_between(db: &SqlStorage, from: u64, to: u64) -> anyhow::Result<()> {
2316 let mut tx = db.write().await?;
2317 for statement in [
2318 "DELETE FROM transactions WHERE block_height > $1 AND block_height <= $2",
2319 "DELETE FROM leaf2 WHERE height > $1 AND height <= $2",
2320 "DELETE FROM header WHERE height > $1 AND height <= $2",
2321 ] {
2322 sqlx::query(statement)
2323 .bind(from as i64)
2324 .bind(to as i64)
2325 .execute(tx.as_mut())
2326 .await?;
2327 }
2328 tx.commit().await
2329 }
2330
2331 async fn count_leaves_up_to(db: &SqlStorage, height: u64) -> anyhow::Result<i64> {
2332 let mut tx = db.read().await?;
2333 Ok(
2334 sqlx::query_scalar("SELECT count(*) FROM leaf2 WHERE height <= $1")
2335 .bind(height as i64)
2336 .fetch_one(tx.as_mut())
2337 .await?,
2338 )
2339 }
2340
2341 async fn data_heights(db: &SqlStorage) -> anyhow::Result<(Option<u64>, Option<u64>)> {
2342 let mut tx = db.read().await?;
2343 let pruned = tx.load_pruned_height().await?;
2344 let lowest: Option<i64> =
2345 sqlx::query_scalar("SELECT min(height) FROM leaf2 WHERE height > 0")
2346 .fetch_one(tx.as_mut())
2347 .await?;
2348 Ok((pruned, lowest.map(|height| height as u64)))
2349 }
2350
2351 async fn insert_deferred_migration(
2352 db: &SqlStorage,
2353 migration: DeferredMigration,
2354 ) -> anyhow::Result<()> {
2355 let DeferredMigration {
2356 name,
2357 started_at,
2358 completed_at,
2359 last_offset,
2360 } = migration;
2361 let mut tx = db.write().await?;
2362 sqlx::query(
2363 "INSERT INTO deferred_migrations (name, started_at, completed_at, last_offset)
2364 VALUES ($1, $2, $3, $4)",
2365 )
2366 .bind(name)
2367 .bind(started_at)
2368 .bind(completed_at)
2369 .bind(last_offset)
2370 .execute(tx.as_mut())
2371 .await?;
2372 tx.commit().await
2373 }
2374
2375 async fn reconstruct_state(
2376 db: &SqlStorage,
2377 instance: &NodeState,
2378 from_height: u64,
2379 to_view: ViewNumber,
2380 fee_accounts: &[FeeAccount],
2381 reward_accounts: &[RewardAccountV2],
2382 ) -> anyhow::Result<(ValidatedState, Leaf2)> {
2383 let mut tx = db.read().await?;
2384 reconstruct_state(
2385 instance,
2386 db,
2387 &mut tx,
2388 from_height,
2389 to_view,
2390 fee_accounts,
2391 reward_accounts,
2392 )
2393 .await
2394 }
2395 }
2396}
2397
2398#[cfg(test)]
2399mod tests {
2400 use std::sync::Arc;
2401
2402 use alloy::primitives::{Address, U256};
2403 use espresso_types::{
2404 BLOCK_MERKLE_TREE_HEIGHT, BackoffParams, BlockMerkleTree, FEE_MERKLE_TREE_HEIGHT,
2405 FeeAccount, FeeAmount, FeeMerkleTree, NodeState,
2406 v0::traits::StateCatchup,
2407 v0_4::{REWARD_MERKLE_TREE_V2_HEIGHT, RewardMerkleTreeV2},
2408 };
2409 use hotshot_query_service::{
2410 QueryError,
2411 data_source::{
2412 Transaction, VersionedDataSource,
2413 storage::{
2414 UpdateAvailabilityStorage, pruning::PrunedHeightStorage, sql::StorageConnectionType,
2415 },
2416 },
2417 merklized_state::{MerklizedState, UpdateStateData},
2418 node::NodeDataSource,
2419 };
2420 use hotshot_types::data::ViewNumber;
2421 use jf_merkle_tree_compat::{
2422 AppendableMerkleTreeScheme, ForgetableMerkleTreeScheme, LookupResult, MerkleTreeScheme,
2423 ToTraversalPath, UniversalMerkleTreeScheme,
2424 };
2425 use light_client::testing::{leaf_chain, leaf_chain_with_upgrade};
2426 use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, Upgrade};
2427
2428 use super::{
2429 super::qs::{Config, SqlStorage, Transaction as SqlTransaction, Write, testing::TmpDb},
2430 ArchiveStateGc, SeqTypes,
2431 impl_testable_data_source::tmp_options,
2432 query_as,
2433 };
2434 use crate::{
2435 api::{RewardMerkleTreeDataSource, data_source::SequencerDataSource},
2436 catchup::{CatchupStorage, SqlStateCatchup},
2437 };
2438
2439 async fn write_fee_state(storage: &SqlStorage, account: FeeAccount, balance: u64, height: u64) {
2440 let mut tree = FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT);
2441 tree.update(account, FeeAmount::from(balance)).unwrap();
2442 let proof = match tree.universal_lookup(account) {
2443 LookupResult::Ok(_, proof) => proof,
2444 _ => panic!("account not in tree"),
2445 };
2446 let path = <FeeAccount as ToTraversalPath<{ FeeMerkleTree::ARITY }>>::to_traversal_path(
2447 &account,
2448 tree.height(),
2449 );
2450
2451 let mut tx = storage.write().await.unwrap();
2452 UpdateStateData::<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>::insert_merkle_nodes(
2453 &mut tx, proof, path, height,
2454 )
2455 .await
2456 .unwrap();
2457 UpdateStateData::<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>::set_last_state_height(
2458 &mut tx,
2459 height as usize,
2460 )
2461 .await
2462 .unwrap();
2463 Transaction::commit(tx).await.unwrap();
2464 }
2465
2466 async fn fee_state_heights(storage: &SqlStorage) -> Vec<u64> {
2467 let mut tx = storage.read().await.unwrap();
2468 query_as::<(i64,)>(&format!(
2469 "SELECT DISTINCT created FROM {} ORDER BY created",
2470 FeeMerkleTree::state_type()
2471 ))
2472 .fetch_all(tx.as_mut())
2473 .await
2474 .unwrap()
2475 .into_iter()
2476 .map(|(height,)| height as u64)
2477 .collect()
2478 }
2479
2480 #[tokio::test]
2482 #[test_log::test]
2483 async fn test_archive_state_gc_prunes_fee_state() {
2484 let db = TmpDb::init().await;
2485 let mut opt = tmp_options(&db);
2486 opt.archive_state_min_retention = 2;
2487 let storage = SqlStorage::connect(
2488 Config::try_from(&opt).unwrap(),
2489 StorageConnectionType::Query,
2490 )
2491 .await
2492 .unwrap();
2493
2494 let account = FeeAccount::from(Address::repeat_byte(0x42));
2495 for height in 1..=5 {
2496 write_fee_state(&storage, account, 100 * height, height).await;
2497 }
2498
2499 let gc = ArchiveStateGc::new(&opt);
2501 storage
2502 .prune_state_below(4, gc.min_retention, &gc.cfg)
2503 .await
2504 .unwrap();
2505 assert_eq!(fee_state_heights(&storage).await, [2, 3, 4, 5]);
2506 let mut tx = storage.read().await.unwrap();
2507 assert_eq!(tx.load_state_pruned_height().await.unwrap(), Some(2));
2508 assert_eq!(tx.load_pruned_height().await.unwrap(), None);
2509 }
2510
2511 #[tokio::test]
2515 #[test_log::test]
2516 async fn test_catchup_reads_snapshot_without_header() {
2517 let db = TmpDb::init().await;
2518 let storage = Arc::new(
2519 SqlStorage::connect(
2520 Config::try_from(&tmp_options(&db)).unwrap(),
2521 StorageConnectionType::Query,
2522 )
2523 .await
2524 .unwrap(),
2525 );
2526
2527 let height = 3;
2529 let leaves = leaf_chain(0..=height + 1, DRB_AND_HEADER_UPGRADE_VERSION).await;
2530 let mut tx = storage.write().await.unwrap();
2531 tx.insert_leaf(&leaves[height as usize + 1]).await.unwrap();
2532 Transaction::commit(tx).await.unwrap();
2533
2534 let account = FeeAccount::from(Address::repeat_byte(0x42));
2535 let absent = FeeAccount::from(Address::repeat_byte(0x43));
2536 write_fee_state(&storage, account, 100, height).await;
2537 let mut fee_tree = FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT);
2538 fee_tree.update(account, FeeAmount::from(100)).unwrap();
2539 let fee_root = fee_tree.commitment();
2540
2541 let mut block_tree = BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT);
2542 for leaf in &leaves[..height as usize] {
2543 block_tree.push(leaf.block_hash()).unwrap();
2544 }
2545 let frontier = block_tree.lookup(height - 1).expect_ok().unwrap().1;
2546 let mut tx = storage.write().await.unwrap();
2547 UpdateStateData::<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>::insert_merkle_nodes(
2548 &mut tx,
2549 frontier,
2550 ToTraversalPath::<{ BlockMerkleTree::ARITY }>::to_traversal_path(
2551 &(height - 1),
2552 BLOCK_MERKLE_TREE_HEIGHT,
2553 ),
2554 height,
2555 )
2556 .await
2557 .unwrap();
2558 Transaction::commit(tx).await.unwrap();
2559
2560 let instance = NodeState::mock();
2561 let view = ViewNumber::new(height);
2562
2563 let err = storage
2565 .get_accounts(&instance, height, view, &[account])
2566 .await
2567 .unwrap_err();
2568 assert!(
2569 format!("{err:#}").contains(&format!("leaf {height} not available")),
2570 "{err:#}"
2571 );
2572 storage
2573 .get_frontier(&instance, height, view)
2574 .await
2575 .unwrap_err();
2576
2577 let catchup = SqlStateCatchup::new(storage, BackoffParams::disabled());
2578
2579 let proofs = catchup
2580 .try_fetch_accounts(0, &instance, height, view, fee_root, &[account, absent])
2581 .await
2582 .unwrap();
2583 let balances = proofs
2584 .iter()
2585 .map(|proof| proof.verify(&fee_root).unwrap())
2586 .collect::<Vec<_>>();
2587 assert_eq!(balances, [U256::from(100), U256::ZERO]);
2588
2589 let mut block_tree_from_root = BlockMerkleTree::from_commitment(block_tree.commitment());
2590 catchup
2591 .try_remember_blocks_merkle_tree(0, &instance, height, view, &mut block_tree_from_root)
2592 .await
2593 .unwrap();
2594 assert!(block_tree_from_root.lookup(height - 1).expect_ok().is_ok());
2595
2596 let wrong_root = FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT).commitment();
2598 catchup
2599 .try_fetch_accounts(0, &instance, height, view, wrong_root, &[account])
2600 .await
2601 .unwrap_err();
2602 }
2603
2604 async fn insert_test_header(
2605 tx: &mut SqlTransaction<Write>,
2606 block_height: u64,
2607 reward_tree: &RewardMerkleTreeV2,
2608 ) {
2609 let reward_commitment = serde_json::to_value(reward_tree.commitment()).unwrap();
2610 let test_data = serde_json::json!({
2611 "block_merkle_tree_root": format!("block_root_{}", block_height),
2612 "fee_merkle_tree_root": format!("fee_root_{}", block_height),
2613 "fields": {
2614 RewardMerkleTreeV2::header_state_commitment_field(): reward_commitment
2615 }
2616 });
2617 tx.upsert(
2618 "header",
2619 [
2620 "height",
2621 "hash",
2622 "payload_hash",
2623 "timestamp",
2624 "data",
2625 "ns_table",
2626 ],
2627 ["height"],
2628 [(
2629 block_height as i64,
2630 format!("hash_{}", block_height),
2631 format!("payload_{}", block_height),
2632 block_height as i64,
2633 test_data,
2634 "ns_table".to_string(),
2635 )],
2636 )
2637 .await
2638 .unwrap();
2639 }
2640
2641 #[tokio::test]
2642 #[test_log::test]
2643 async fn test_merkle_proof_gc() {
2644 let db = TmpDb::init().await;
2645 let opt = tmp_options(&db);
2646 let cfg = Config::try_from(&opt).expect("failed to create config from options");
2647 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
2648 .await
2649 .expect("failed to connect to storage");
2650
2651 let account = vec![0; 32];
2652
2653 let leaves = leaf_chain(0..=2, DRB_AND_HEADER_UPGRADE_VERSION).await;
2655 let mut tx = storage.write().await.unwrap();
2656 for leaf in &leaves {
2657 tx.insert_leaf(leaf).await.unwrap();
2658 }
2659 Transaction::commit(tx).await.unwrap();
2660
2661 for h in 0..=2 {
2663 storage
2664 .persist_proofs(h, [(account.clone(), h.to_le_bytes().to_vec())].into_iter())
2665 .await
2666 .unwrap();
2667 }
2668
2669 storage.garbage_collect(2).await.unwrap();
2671
2672 assert_eq!(
2674 storage.load_proof(2, account.clone(), 0).await.unwrap(),
2675 2u64.to_le_bytes()
2676 );
2677
2678 for h in 0..2 {
2680 let err = storage.load_proof(h, account.clone(), 0).await.unwrap_err();
2681 assert!(err.to_string().contains("Missing proof"), "{err:#}");
2682 }
2683
2684 storage.garbage_collect(3).await.unwrap();
2686 let err = storage.load_proof(2, account, 0).await.unwrap_err();
2687 assert!(err.to_string().contains("Missing proof"), "{err:#}");
2688 }
2689
2690 #[tokio::test]
2691 #[test_log::test]
2692 async fn test_load_proof_v5_epoch_boundary() {
2693 let db = TmpDb::init().await;
2694 let opt = tmp_options(&db);
2695 let cfg = Config::try_from(&opt).expect("failed to create config from options");
2696 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
2697 .await
2698 .expect("failed to connect to storage");
2699
2700 let epoch_height = 10u64;
2701 let account = vec![0; 32];
2702
2703 let leaves = leaf_chain(0..=15, EPOCH_REWARD_VERSION).await;
2705 let mut tx = storage.write().await.unwrap();
2706 for leaf in &leaves {
2707 tx.insert_leaf(leaf).await.unwrap();
2708 }
2709 Transaction::commit(tx).await.unwrap();
2710
2711 let boundary_proof = b"proof_at_10".to_vec();
2713 {
2714 let mut tx = storage.write().await.unwrap();
2715 tx.upsert(
2716 "reward_merkle_tree_v2_proofs",
2717 ["height", "account", "proof"],
2718 ["height", "account"],
2719 [(10i64, account.clone(), boundary_proof.clone())],
2720 )
2721 .await
2722 .unwrap();
2723 Transaction::commit(tx).await.unwrap();
2724 }
2725
2726 assert_eq!(
2728 storage
2729 .load_proof(10, account.clone(), epoch_height)
2730 .await
2731 .unwrap(),
2732 boundary_proof,
2733 );
2734
2735 assert_eq!(
2738 storage
2739 .load_proof(15, account.clone(), epoch_height)
2740 .await
2741 .unwrap(),
2742 boundary_proof,
2743 );
2744 }
2745
2746 #[tokio::test]
2747 #[test_log::test]
2748 async fn test_load_proof_v4_to_v5_upgrade_boundary() {
2749 let db = TmpDb::init().await;
2750 let opt = tmp_options(&db);
2751 let cfg = Config::try_from(&opt).expect("failed to create config from options");
2752 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
2753 .await
2754 .expect("failed to connect to storage");
2755
2756 let epoch_height = 10u64;
2757 let account = vec![0; 32];
2758
2759 let upgrade = Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION);
2762 let leaves = leaf_chain_with_upgrade(0..=15, 11, upgrade).await;
2763 {
2764 let mut tx = storage.write().await.unwrap();
2765 for leaf in &leaves {
2766 tx.insert_leaf(leaf).await.unwrap();
2767 }
2768 Transaction::commit(tx).await.unwrap();
2769 }
2770
2771 storage
2774 .load_proof(15, account.clone(), epoch_height)
2775 .await
2776 .unwrap_err();
2777
2778 let v4_proof = b"v4_proof_at_5".to_vec();
2779 {
2780 let mut tx = storage.write().await.unwrap();
2781 tx.upsert(
2782 "reward_merkle_tree_v2_proofs",
2783 ["height", "account", "proof"],
2784 ["height", "account"],
2785 [(5i64, account.clone(), v4_proof.clone())],
2786 )
2787 .await
2788 .unwrap();
2789 Transaction::commit(tx).await.unwrap();
2790 }
2791 assert_eq!(
2792 storage.load_proof(5, account.clone(), 0).await.unwrap(),
2793 v4_proof,
2794 );
2795 }
2796
2797 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2798 async fn test_get_table_sizes() {
2799 use crate::api::data_source::DatabaseMetadataSource;
2800
2801 let db = TmpDb::init().await;
2802 let opt = tmp_options(&db);
2803 let cfg = Config::try_from(&opt).expect("failed to create config from options");
2804 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
2805 .await
2806 .expect("failed to connect to storage");
2807
2808 let mut tx = storage.write().await.unwrap();
2810
2811 let reward_tree = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
2813 insert_test_header(&mut tx, 1, &reward_tree).await;
2814
2815 tx.commit().await.unwrap();
2816
2817 let table_sizes = storage
2819 .get_table_sizes()
2820 .await
2821 .expect("get_table_sizes should succeed");
2822
2823 assert!(
2825 !table_sizes.is_empty(),
2826 "should have at least one table in the database"
2827 );
2828 }
2829
2830 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2832 async fn test_pruned_node_has_no_totals() {
2833 for prune in [false, true] {
2834 let storage = TmpDb::init().await;
2835 let mut opt = tmp_options(&storage);
2836 opt.prune = prune;
2837 let ds = super::DataSource::create(
2838 opt.try_into().unwrap(),
2839 super::Provider::default(),
2840 false,
2841 )
2842 .await
2843 .unwrap();
2844
2845 let res = NodeDataSource::count_transactions_in_range(&ds, .., None).await;
2846 if prune {
2847 assert!(matches!(res, Err(QueryError::NotFound)), "{res:?}");
2848 } else {
2849 assert_eq!(res.unwrap(), 0);
2850 }
2851 }
2852 }
2853}