1use std::collections::{HashMap, HashSet, VecDeque};
2
3use anyhow::{Context, bail, ensure};
4use async_trait::async_trait;
5use committable::{Commitment, Committable};
6use espresso_types::{
7 BlockMerkleTree, ChainConfig, FeeAccount, FeeMerkleTree, Leaf2, NodeState, ValidatedState,
8 get_l1_deposits,
9 v0_1::IterableFeeInfo,
10 v0_3::{
11 REWARD_MERKLE_TREE_V1_HEIGHT, RewardAccountProofV1, RewardAccountQueryDataV1,
12 RewardAccountV1, RewardMerkleTreeV1,
13 },
14 v0_4::{PermittedRewardMerkleTreeV2, RewardAccountV2, RewardMerkleTreeV2},
15 v0_6::RewardAccountQueryDataV2,
16};
17use futures::future::Future;
18use hotshot::traits::ValidatedState as _;
19use hotshot_query_service::{
20 Resolvable,
21 availability::LeafId,
22 data_source::{
23 VersionedDataSource,
24 sql::{Config, SqlDataSource, Transaction},
25 storage::{
26 AvailabilityStorage, MerklizedStateStorage, NodeStorage, SqlStorage,
27 pruning::PrunerConfig,
28 sql::{Db, TransactionMode, Write, query_as},
29 },
30 },
31 merklized_state::Snapshot,
32};
33use hotshot_types::{
34 data::{EpochNumber, QuorumProposalWrapper, ViewNumber},
35 message::Proposal,
36 traits::election::MembershipSnapshot,
37 utils::{epoch_from_block_number, is_last_block},
38 vote::HasViewNumber,
39};
40use jf_merkle_tree_compat::{
41 ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, LookupResult,
42 MerkleTreeScheme, prelude::MerkleNode,
43};
44use sqlx::{Encode, Row, Type};
45use vbs::version::Version;
46use versions::{
47 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION,
48};
49
50use super::{
51 BlocksFrontier,
52 data_source::{Provider, SequencerDataSource},
53};
54use crate::{
55 SeqTypes,
56 api::RewardMerkleTreeDataSource,
57 catchup::{CatchupStorage, NullStateCatchup},
58 persistence::{ChainConfigPersistence, sql::Options},
59 state::compute_state_update,
60 util::BoundedJoinSet,
61};
62
63pub type DataSource = SqlDataSource<SeqTypes, Provider>;
64
65#[async_trait]
66impl SequencerDataSource for DataSource {
67 type Options = Options;
68
69 async fn create(opt: Self::Options, provider: Provider, reset: bool) -> anyhow::Result<Self> {
70 let fetch_limit = opt.fetch_rate_limit;
71 let active_fetch_delay = opt.active_fetch_delay;
72 let chunk_fetch_delay = opt.chunk_fetch_delay;
73 let mut cfg = Config::try_from(&opt)?;
74
75 if reset {
76 cfg = cfg.reset_schema();
77 }
78
79 let mut builder = cfg.builder(provider).await?;
80
81 if let Some(limit) = fetch_limit {
82 builder = builder.with_rate_limit(limit);
83 }
84
85 if opt.lightweight {
86 tracing::warn!("enabling light weight mode..");
87 builder = builder.leaf_only();
88 }
89
90 if let Some(delay) = active_fetch_delay {
91 builder = builder.with_active_fetch_delay(delay);
92 }
93 if let Some(delay) = chunk_fetch_delay {
94 builder = builder.with_chunk_fetch_delay(delay);
95 }
96 if let Some(chunk_size) = opt.sync_status_chunk_size {
97 builder = builder.with_sync_status_chunk_size(chunk_size);
98 }
99 if let Some(ttl) = opt.sync_status_ttl {
100 builder = builder.with_sync_status_ttl(ttl);
101 }
102 if let Some(chunk_size) = opt.proactive_scan_chunk_size {
103 builder = builder.with_proactive_range_chunk_size(chunk_size);
104 }
105 if let Some(interval) = opt.proactive_scan_interval {
106 builder = builder.with_proactive_interval(interval);
107 }
108 if opt.disable_proactive_fetching {
109 builder = builder.disable_proactive_fetching();
110 }
111
112 builder.build().await
113 }
114}
115
116impl RewardMerkleTreeDataSource for SqlStorage {
117 async fn load_v1_reward_account_proof(
118 &self,
119 height: u64,
120 account: RewardAccountV1,
121 ) -> anyhow::Result<RewardAccountQueryDataV1> {
122 let mut tx = self.read().await.context(format!(
123 "opening transaction to fetch v1 reward account {account:?}; height {height}"
124 ))?;
125
126 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
127 .await
128 .context("getting block height")? as u64;
129 ensure!(
130 block_height > 0,
131 "cannot get accounts for height {height}: no blocks available"
132 );
133
134 if height < block_height {
137 let (tree, _) = load_v1_reward_accounts(self, height, &[account])
138 .await
139 .with_context(|| {
140 format!("failed to load v1 reward account {account:?} at height {height}")
141 })?;
142
143 let (proof, balance) = RewardAccountProofV1::prove(&tree, account.into())
144 .with_context(|| {
145 format!("reward account {account:?} not available at height {height}")
146 })?;
147
148 Ok(RewardAccountQueryDataV1 { balance, proof })
149 } else {
150 bail!(
151 "requested height {height} is not yet available (latest block height: \
152 {block_height})"
153 );
154 }
155 }
156
157 fn persist_tree(
158 &self,
159 height: u64,
160 merkle_tree: Vec<u8>,
161 ) -> impl Send + Future<Output = anyhow::Result<()>> {
162 async move {
163 let mut tx = self
164 .write()
165 .await
166 .context("opening transaction for reward merkle tree v2")?;
167
168 tx.upsert(
169 "reward_merkle_tree_v2_data",
170 ["height", "balances"],
171 ["height"],
172 [(height as i64, merkle_tree)],
173 )
174 .await?;
175
176 hotshot_query_service::data_source::Transaction::commit(tx)
177 .await
178 .context("Transaction to store reward merkle tree v2 failed.")
179 }
180 }
181
182 fn load_tree(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
183 async move {
184 let mut tx = self
185 .read()
186 .await
187 .context("opening transaction for state update")?;
188
189 let row = sqlx::query(
190 r#"
191 SELECT balances
192 FROM reward_merkle_tree_v2_data
193 WHERE height = $1
194 "#,
195 )
196 .bind(height as i64)
197 .fetch_optional(tx.as_mut())
198 .await?
199 .context(format!(
200 "No reward merkle tree for height {} in storage",
201 height
202 ))?;
203
204 row.try_get::<Vec<u8>, _>("balances")
205 .context("Missing field balances from row; this should never happen")
206 }
207 }
208
209 fn load_latest_tree(
210 &self,
211 height: u64,
212 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
213 async move {
214 let mut tx = self
215 .read()
216 .await
217 .context("opening transaction for load_latest_tree")?;
218
219 let row = sqlx::query(
220 r#"
221 SELECT balances
222 FROM reward_merkle_tree_v2_data
223 WHERE height <= $1
224 ORDER BY height DESC
225 LIMIT 1
226 "#,
227 )
228 .bind(height as i64)
229 .fetch_optional(tx.as_mut())
230 .await?
231 .context(format!(
232 "No reward merkle tree at or below height {} in storage",
233 height
234 ))?;
235
236 row.try_get::<Vec<u8>, _>("balances")
237 .context("Missing field balances from row; this should never happen")
238 }
239 }
240
241 fn persist_proofs(
242 &self,
243 height: u64,
244 proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
245 ) -> impl Send + Future<Output = anyhow::Result<()>> {
246 async move {
247 let mut iter = proofs.map(|(account, proof)| (height as i64, account, proof));
248
249 loop {
250 let mut chunk = Vec::with_capacity(20);
251
252 for _ in 0..20 {
253 let Some(row) = iter.next() else {
254 continue;
255 };
256 chunk.push(row);
257 }
258
259 if chunk.is_empty() {
260 break;
261 }
262
263 let mut tx = self
264 .write()
265 .await
266 .context("opening transaction for state update")?;
267
268 tokio::spawn(async move {
269 tx.upsert(
270 "reward_merkle_tree_v2_proofs",
271 ["height", "account", "proof"],
272 ["height", "account"],
273 chunk,
274 )
275 .await?;
276
277 hotshot_query_service::data_source::Transaction::commit(tx)
278 .await
279 .context("Transaction to store reward merkle tree failed.")?;
280
281 Ok::<_, anyhow::Error>(())
282 });
283 }
284 Ok(())
285 }
286 }
287
288 fn load_proof(
295 &self,
296 height: u64,
297 account: Vec<u8>,
298 epoch_height: u64,
299 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
300 async move {
301 let mut tx = self
302 .read()
303 .await
304 .context("opening transaction for load_proof")?;
305
306 let leaf = tx
307 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
308 .await
309 .context(format!("leaf {height} not available"))?;
310
311 let proof_height = if leaf.header().version() < EPOCH_REWARD_VERSION
314 || is_last_block(height, epoch_height)
315 {
316 height
317 } else {
318 let prev_epoch_last_block =
319 epoch_from_block_number(height, epoch_height).saturating_sub(1) * epoch_height;
320 let boundary_leaf = tx
321 .get_leaf(LeafId::<SeqTypes>::from(prev_epoch_last_block as usize))
322 .await
323 .context(format!(
324 "leaf at epoch boundary {prev_epoch_last_block} not available"
325 ))?;
326 ensure!(
327 boundary_leaf.header().version() >= EPOCH_REWARD_VERSION,
328 "no epoch reward proofs available at boundary {prev_epoch_last_block}"
329 );
330 prev_epoch_last_block
331 };
332
333 sqlx::query_scalar(
334 "SELECT proof FROM reward_merkle_tree_v2_proofs WHERE height = $1 AND account = $2",
335 )
336 .bind(proof_height as i64)
337 .bind(account)
338 .fetch_optional(tx.as_mut())
339 .await?
340 .context(format!(
341 "Missing proofs at height {proof_height} (resolved from {height})"
342 ))
343 }
344 }
345
346 fn load_latest_proof(
347 &self,
348 account: Vec<u8>,
349 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
350 async move {
351 let mut tx = self
352 .read()
353 .await
354 .context("opening transaction for state update")?;
355
356 let row = sqlx::query(
357 r#"
358 SELECT proof
359 FROM reward_merkle_tree_v2_proofs
360 WHERE account = $1
361 ORDER BY height DESC
362 LIMIT 1
363 "#,
364 )
365 .bind(account)
366 .fetch_optional(tx.as_mut())
367 .await?
368 .context("Missing proofs")?;
369
370 row.try_get::<Vec<u8>, _>("proof")
371 .context("Missing field proof from row; this should never happen")
372 }
373 }
374
375 fn garbage_collect(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<()>> {
376 async move {
377 let batch_size = self.get_pruning_config().unwrap_or_default().batch_size();
378
379 #[cfg(not(feature = "embedded-db"))]
383 let for_update = "FOR UPDATE";
384 #[cfg(feature = "embedded-db")]
385 let for_update = "";
386
387 loop {
389 let mut tx = self
390 .write()
391 .await
392 .context("opening transaction for state update")?;
393
394 let res = sqlx::query(&format!(
395 "
396 WITH delete_batch AS (
397 SELECT d.height FROM reward_merkle_tree_v2_data AS d
398 WHERE d.height < $1
399 ORDER BY d.height DESC
400 LIMIT $2
401 {for_update}
402 )
403 DELETE FROM reward_merkle_tree_v2_data AS del
404 WHERE del.height IN (SELECT * FROM delete_batch)
405 "
406 ))
407 .bind(height as i64)
408 .bind(batch_size as i64)
409 .execute(tx.as_mut())
410 .await?;
411
412 hotshot_query_service::data_source::Transaction::commit(tx)
413 .await
414 .context(
415 "Transaction to garbage collect reward merkle trees from storage failed.",
416 )?;
417
418 if res.rows_affected() == 0 {
419 break;
420 } else {
421 tracing::debug!(
422 "deleted {} rows from reward_merkle_tree_v2_data",
423 res.rows_affected()
424 );
425 }
426 }
427
428 loop {
430 let mut tx = self
431 .write()
432 .await
433 .context("opening transaction for state update")?;
434
435 let res = sqlx::query(&format!(
436 "
437 WITH delete_batch AS (
438 SELECT d.height, d.account FROM reward_merkle_tree_v2_proofs AS d
439 WHERE d.height < $1
440 ORDER BY d.height, d.account DESC
441 LIMIT $2
442 {for_update}
443 )
444 DELETE FROM reward_merkle_tree_v2_proofs AS del
445 WHERE (del.height, del.account) IN (SELECT * FROM delete_batch)
446 ",
447 ))
448 .bind(height as i64)
449 .bind(batch_size as i64)
450 .execute(tx.as_mut())
451 .await?;
452
453 hotshot_query_service::data_source::Transaction::commit(tx)
454 .await
455 .context("Transaction to garbage collect proofs from storage failed.")?;
456
457 if res.rows_affected() == 0 {
458 break;
459 } else {
460 tracing::debug!(
461 "deleted {} rows from reward_merkle_tree_v2_proofs",
462 res.rows_affected()
463 );
464 }
465 }
466
467 Ok(())
468 }
469 }
470
471 fn proof_exists(&self, height: u64) -> impl Send + Future<Output = bool> {
472 async move {
473 let Ok(mut tx) = self.write().await else {
474 return false;
475 };
476
477 sqlx::query_as(
478 r#"
479 SELECT EXISTS(
480 SELECT 1 FROM reward_merkle_tree_v2_proofs
481 WHERE height = $1
482 )
483 "#,
484 )
485 .bind(height as i64)
486 .fetch_one(tx.as_mut())
487 .await
488 .ok()
489 .unwrap_or((false,))
490 .0
491 }
492 }
493 fn persist_reward_proofs(
500 &self,
501 node_state: &NodeState,
502 height: u64,
503 version: Version,
504 ) -> impl Send + Future<Output = anyhow::Result<()>> {
505 async move {
506 if !cfg!(any(test, feature = "testing"))
512 && !(height + node_state.node_id).is_multiple_of(30)
513 {
514 return Ok(());
515 }
516
517 let finalized_hotshot_height = if cfg!(any(test, feature = "testing")) {
521 height
522 } else {
523 match node_state.finalized_hotshot_height().await {
524 Ok(h) => h,
525 Err(err) => {
526 tracing::warn!("failed to get finalized hotshot height: {err:#}");
527 return Ok(());
528 },
529 }
530 };
531
532 let mut tree_height = finalized_hotshot_height;
535 let mut proof_height = finalized_hotshot_height;
536 if version >= EPOCH_REWARD_VERSION {
537 let epoch_height = node_state
538 .epoch_height
539 .context("epoch_height not set in node state")?;
540 if !is_last_block(finalized_hotshot_height, epoch_height) {
541 tree_height = epoch_from_block_number(finalized_hotshot_height, epoch_height)
542 .saturating_sub(1)
543 * epoch_height;
544 if tree_height == 0 {
545 return Ok(());
546 }
547 let mut tx = self.read().await.context("opening read transaction")?;
549 let leaf = tx
550 .get_leaf(LeafId::<SeqTypes>::from(tree_height as usize))
551 .await
552 .context(format!(
553 "leaf at epoch boundary {tree_height} not available"
554 ))?;
555 if leaf.header().version() < EPOCH_REWARD_VERSION {
556 return Ok(());
557 }
558 }
559 proof_height = tree_height;
560 }
561
562 if self.proof_exists(proof_height).await {
563 return Ok(());
564 }
565
566 let permitted_tree = match self.load_reward_merkle_tree_v2(tree_height).await {
567 Ok(tree) => tree,
568 Err(err) => {
569 tracing::warn!(tree_height, "failed to load reward merkle tree: {err:#}");
570 return Ok(());
571 },
572 };
573
574 let tree = permitted_tree.tree;
575 let iter = tree
576 .iter()
577 .filter_map(|(account, balance): (&RewardAccountV2, _)| {
578 let proof = espresso_types::v0_6::RewardAccountProofV2::prove(
579 &tree,
580 (*account).into(),
581 )?;
582 let proof = RewardAccountQueryDataV2 {
583 balance: (*balance).into(),
584 proof: proof.0,
585 };
586 Some((
587 bincode::serialize(&account).ok()?,
588 bincode::serialize(&proof).ok()?,
589 ))
590 });
591
592 if let Err(err) = self.persist_proofs(proof_height, iter).await {
593 tracing::warn!(proof_height, "failed to persist proofs: {err:#}");
594 }
595
596 Ok(())
597 }
598 }
599}
600
601impl CatchupStorage for SqlStorage {
602 async fn get_reward_accounts_v1(
603 &self,
604 instance: &NodeState,
605 height: u64,
606 view: ViewNumber,
607 accounts: &[RewardAccountV1],
608 ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
609 let mut tx = self.read().await.context(format!(
610 "opening transaction to fetch v1 reward account {accounts:?}; height {height}"
611 ))?;
612
613 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
614 .await
615 .context("getting block height")? as u64;
616 ensure!(
617 block_height > 0,
618 "cannot get accounts for height {height}: no blocks available"
619 );
620
621 if height < block_height {
624 load_v1_reward_accounts(self, height, accounts).await
625 } else {
626 let accounts: Vec<_> = accounts
627 .iter()
628 .map(|acct| RewardAccountV2::from(*acct))
629 .collect();
630 let (state, leaf) = reconstruct_state(
633 instance,
634 self,
635 &mut tx,
636 block_height - 1,
637 view,
638 &[],
639 &accounts,
640 )
641 .await?;
642 Ok((state.reward_merkle_tree_v1, leaf))
643 }
644 }
645
646 async fn get_reward_accounts_v2(
647 &self,
648 instance: &NodeState,
649 height: u64,
650 view: ViewNumber,
651 accounts: &[RewardAccountV2],
652 ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
653 let mut tx = self.read().await.context(format!(
654 "opening transaction to fetch reward account {accounts:?}; height {height}"
655 ))?;
656
657 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
658 .await
659 .context("getting block height")? as u64;
660 ensure!(
661 block_height > 0,
662 "cannot get accounts for height {height}: no blocks available"
663 );
664
665 if height < block_height {
668 load_reward_merkle_tree_v2(self, height)
669 .await
670 .map(|(permitted_tree, leaf)| (permitted_tree.tree, leaf))
671 } else {
672 let (state, leaf) = reconstruct_state(
675 instance,
676 self,
677 &mut tx,
678 block_height - 1,
679 view,
680 &[],
681 accounts,
682 )
683 .await?;
684 Ok((state.reward_merkle_tree_v2, leaf))
685 }
686 }
687
688 async fn get_accounts(
689 &self,
690 instance: &NodeState,
691 height: u64,
692 view: ViewNumber,
693 accounts: &[FeeAccount],
694 ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
695 let mut tx = self.read().await.context(format!(
696 "opening transaction to fetch account {accounts:?}; height {height}"
697 ))?;
698
699 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
700 .await
701 .context("getting block height")? as u64;
702 ensure!(
703 block_height > 0,
704 "cannot get accounts for height {height}: no blocks available"
705 );
706
707 if height < block_height {
710 load_accounts(&mut tx, height, accounts).await
711 } else {
712 let (state, leaf) = reconstruct_state(
715 instance,
716 self,
717 &mut tx,
718 block_height - 1,
719 view,
720 accounts,
721 &[],
722 )
723 .await?;
724 Ok((state.fee_merkle_tree, leaf))
725 }
726 }
727
728 async fn get_frontier(
729 &self,
730 instance: &NodeState,
731 height: u64,
732 view: ViewNumber,
733 ) -> anyhow::Result<BlocksFrontier> {
734 let mut tx = self.read().await.context(format!(
735 "opening transaction to fetch frontier at height {height}"
736 ))?;
737
738 let block_height = NodeStorage::<SeqTypes>::block_height(&mut tx)
739 .await
740 .context("getting block height")? as u64;
741 ensure!(
742 block_height > 0,
743 "cannot get frontier for height {height}: no blocks available"
744 );
745
746 if height < block_height {
749 load_frontier(&mut tx, height).await
750 } else {
751 let (state, _) =
754 reconstruct_state(instance, self, &mut tx, block_height - 1, view, &[], &[])
755 .await?;
756 match state.block_merkle_tree.lookup(height - 1) {
757 LookupResult::Ok(_, proof) => Ok(proof),
758 _ => {
759 bail!(
760 "state snapshot {view:?},{height} was found but does not contain frontier \
761 at height {}; this should not be possible",
762 height - 1
763 );
764 },
765 }
766 }
767 }
768
769 async fn get_chain_config(
770 &self,
771 commitment: Commitment<ChainConfig>,
772 ) -> anyhow::Result<ChainConfig> {
773 let mut tx = self.read().await.context(format!(
774 "opening transaction to fetch chain config {commitment}"
775 ))?;
776 load_chain_config(&mut tx, commitment).await
777 }
778
779 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
780 let mut tx = self
781 .read()
782 .await
783 .context(format!("opening transaction to fetch leaf at {height}"))?;
784 let leaf: Leaf2 = tx
785 .get_leaf((height as usize).into())
786 .await
787 .context(format!("leaf {height} not available"))?
788 .leaf()
789 .clone();
790
791 if leaf.block_header().version() >= NEW_PROTOCOL_VERSION {
794 let cert2: espresso_types::Certificate2<SeqTypes> =
795 tx.load_earliest_cert2(height).await?.context(format!(
796 "no cert2 available for new-protocol leaf at {height}"
797 ))?;
798 let cert2_height = cert2.data.block_number;
799 let mut leaves = vec![leaf];
800 if height < cert2_height {
801 let descendants = tx
802 .get_leaf_range((height as usize + 1)..=cert2_height as usize)
803 .await?;
804 leaves.extend(descendants.into_iter().flatten().map(|l| l.leaf().clone()));
805 }
806 return Ok(leaves);
807 }
808
809 let mut last_leaf = leaf;
811 let mut chain = vec![last_leaf.clone()];
812 let mut h = height + 1;
813
814 loop {
815 let lqd = tx.get_leaf((h as usize).into()).await?;
816 let leaf = lqd.leaf();
817
818 if leaf.justify_qc().view_number() == last_leaf.view_number() {
819 chain.push(leaf.clone());
820 } else {
821 h += 1;
822 continue;
823 }
824
825 if leaf.view_number() == last_leaf.view_number() + 1 {
827 last_leaf = leaf.clone();
828 h += 1;
829 break;
830 }
831 h += 1;
832 last_leaf = leaf.clone();
833 }
834
835 loop {
836 let lqd = tx.get_leaf((h as usize).into()).await?;
837 let leaf = lqd.leaf();
838 if leaf.justify_qc().view_number() == last_leaf.view_number() {
839 chain.push(leaf.clone());
840 break;
841 }
842 h += 1;
843 }
844
845 Ok(chain)
846 }
847
848 async fn load_cert2(
849 &self,
850 height: u64,
851 ) -> anyhow::Result<Option<espresso_types::Certificate2<SeqTypes>>> {
852 let mut tx = self
853 .read()
854 .await
855 .context("opening transaction to fetch cert2")?;
856 Ok(tx.load_cert2(height).await?)
857 }
858
859 async fn get_leaf(&self, height: u64) -> anyhow::Result<Leaf2> {
860 let mut tx = self
861 .read()
862 .await
863 .context(format!("opening transaction to fetch leaf at {height}"))?;
864 let lqd = tx
865 .get_leaf((height as usize).into())
866 .await
867 .context(format!("leaf {height} not available"))?;
868 Ok(lqd.leaf().clone())
869 }
870}
871
872impl RewardMerkleTreeDataSource for DataSource {
873 async fn load_v1_reward_account_proof(
874 &self,
875 height: u64,
876 account: RewardAccountV1,
877 ) -> anyhow::Result<RewardAccountQueryDataV1> {
878 self.as_ref()
879 .load_v1_reward_account_proof(height, account)
880 .await
881 }
882
883 fn persist_tree(
884 &self,
885 height: u64,
886 merkle_tree: Vec<u8>,
887 ) -> impl Send + Future<Output = anyhow::Result<()>> {
888 async move { self.as_ref().persist_tree(height, merkle_tree).await }
889 }
890
891 fn load_tree(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
892 async move { self.as_ref().load_tree(height).await }
893 }
894
895 fn load_latest_tree(
896 &self,
897 height: u64,
898 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
899 async move { self.as_ref().load_latest_tree(height).await }
900 }
901
902 fn garbage_collect(&self, height: u64) -> impl Send + Future<Output = anyhow::Result<()>> {
903 async move { self.as_ref().garbage_collect(height).await }
904 }
905
906 fn persist_proofs(
907 &self,
908 height: u64,
909 proofs: impl Iterator<Item = (Vec<u8>, Vec<u8>)> + Send,
910 ) -> impl Send + Future<Output = anyhow::Result<()>> {
911 async move { self.as_ref().persist_proofs(height, proofs).await }
912 }
913
914 fn load_proof(
915 &self,
916 height: u64,
917 account: Vec<u8>,
918 epoch_height: u64,
919 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
920 async move {
921 self.as_ref()
922 .load_proof(height, account, epoch_height)
923 .await
924 }
925 }
926
927 fn load_latest_proof(
928 &self,
929 account: Vec<u8>,
930 ) -> impl Send + Future<Output = anyhow::Result<Vec<u8>>> {
931 async move { self.as_ref().load_latest_proof(account).await }
932 }
933
934 fn proof_exists(&self, height: u64) -> impl Send + Future<Output = bool> {
935 async move { self.as_ref().proof_exists(height).await }
936 }
937
938 fn persist_reward_proofs(
939 &self,
940 node_state: &NodeState,
941 height: u64,
942 version: Version,
943 ) -> impl Send + Future<Output = anyhow::Result<()>> {
944 async move {
945 self.as_ref()
946 .persist_reward_proofs(node_state, height, version)
947 .await
948 }
949 }
950}
951
952impl CatchupStorage for DataSource {
953 async fn get_accounts(
954 &self,
955 instance: &NodeState,
956 height: u64,
957 view: ViewNumber,
958 accounts: &[FeeAccount],
959 ) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
960 self.as_ref()
961 .get_accounts(instance, height, view, accounts)
962 .await
963 }
964
965 async fn get_reward_accounts_v2(
966 &self,
967 instance: &NodeState,
968 height: u64,
969 view: ViewNumber,
970 accounts: &[RewardAccountV2],
971 ) -> anyhow::Result<(RewardMerkleTreeV2, Leaf2)> {
972 self.as_ref()
973 .get_reward_accounts_v2(instance, height, view, accounts)
974 .await
975 }
976
977 async fn get_reward_accounts_v1(
978 &self,
979 instance: &NodeState,
980 height: u64,
981 view: ViewNumber,
982 accounts: &[RewardAccountV1],
983 ) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
984 self.as_ref()
985 .get_reward_accounts_v1(instance, height, view, accounts)
986 .await
987 }
988
989 async fn get_frontier(
990 &self,
991 instance: &NodeState,
992 height: u64,
993 view: ViewNumber,
994 ) -> anyhow::Result<BlocksFrontier> {
995 self.as_ref().get_frontier(instance, height, view).await
996 }
997
998 async fn get_chain_config(
999 &self,
1000 commitment: Commitment<ChainConfig>,
1001 ) -> anyhow::Result<ChainConfig> {
1002 self.as_ref().get_chain_config(commitment).await
1003 }
1004 async fn get_leaf_chain(&self, height: u64) -> anyhow::Result<Vec<Leaf2>> {
1005 self.as_ref().get_leaf_chain(height).await
1006 }
1007
1008 async fn load_cert2(
1009 &self,
1010 height: u64,
1011 ) -> anyhow::Result<Option<espresso_types::Certificate2<SeqTypes>>> {
1012 self.as_ref().load_cert2(height).await
1013 }
1014
1015 async fn get_leaf(&self, height: u64) -> anyhow::Result<Leaf2> {
1016 self.as_ref().get_leaf(height).await
1017 }
1018}
1019
1020#[async_trait]
1021impl ChainConfigPersistence for Transaction<Write> {
1022 async fn insert_chain_config(&mut self, chain_config: ChainConfig) -> anyhow::Result<()> {
1023 let commitment = chain_config.commitment();
1024 let data = bincode::serialize(&chain_config)?;
1025 self.upsert(
1026 "chain_config",
1027 ["commitment", "data"],
1028 ["commitment"],
1029 [(commitment.to_string(), data)],
1030 )
1031 .await
1032 }
1033}
1034
1035impl super::data_source::PruningDataSource for SqlStorage {
1036 async fn get_oldest_block(
1037 &self,
1038 ) -> anyhow::Result<Option<hotshot_query_service::availability::BlockQueryData<SeqTypes>>> {
1039 let mut tx = self
1040 .read()
1041 .await
1042 .context("opening transaction to fetch oldest block")?;
1043 let row = sqlx::query("SELECT MIN(height) AS height FROM header")
1044 .fetch_one(tx.as_mut())
1045 .await
1046 .context("failed to query oldest block height")?;
1047 let height: Option<i64> = row.try_get("height")?;
1048 match height {
1049 None => Ok(None),
1050 Some(h) => {
1051 let h = usize::try_from(h).context("block height out of range")?;
1052 Ok(Some(
1053 tx.get_block(hotshot_query_service::availability::BlockId::<SeqTypes>::from(h))
1054 .await
1055 .context(format!("block {h} not available"))?,
1056 ))
1057 },
1058 }
1059 }
1060
1061 async fn get_oldest_leaf(
1062 &self,
1063 ) -> anyhow::Result<Option<hotshot_query_service::availability::LeafQueryData<SeqTypes>>> {
1064 let mut tx = self
1065 .read()
1066 .await
1067 .context("opening transaction to fetch oldest leaf")?;
1068 let row = sqlx::query("SELECT MIN(height) AS height FROM leaf2")
1069 .fetch_one(tx.as_mut())
1070 .await
1071 .context("failed to query oldest leaf height")?;
1072 let height: Option<i64> = row.try_get("height")?;
1073 match height {
1074 None => Ok(None),
1075 Some(h) => {
1076 let h = usize::try_from(h).context("leaf height out of range")?;
1077 Ok(Some(
1078 tx.get_leaf(LeafId::<SeqTypes>::from(h))
1079 .await
1080 .context(format!("leaf {h} not available"))?,
1081 ))
1082 },
1083 }
1084 }
1085}
1086
1087impl super::data_source::DatabaseMetadataSource for SqlStorage {
1088 async fn get_table_sizes(&self) -> anyhow::Result<Vec<super::data_source::TableSize>> {
1089 let mut tx = self
1090 .read()
1091 .await
1092 .context("opening transaction to fetch table sizes")?;
1093
1094 #[cfg(not(feature = "embedded-db"))]
1095 {
1096 let query = r#"
1097 SELECT
1098 schemaname || '.' || relname AS table_name,
1099 n_live_tup AS row_count,
1100 pg_total_relation_size(relid) AS total_size_bytes
1101 FROM pg_stat_user_tables
1102 ORDER BY pg_total_relation_size(relid) DESC
1103 "#;
1104
1105 let rows = sqlx::query(query)
1106 .fetch_all(tx.as_mut())
1107 .await
1108 .context("failed to query table sizes")?;
1109
1110 let mut table_sizes = Vec::new();
1111 for row in rows {
1112 let table_name: String = row.try_get("table_name")?;
1113 let row_count: i64 = row.try_get("row_count").unwrap_or(-1);
1114 let total_size_bytes: Option<i64> = row.try_get("total_size_bytes").ok();
1115
1116 table_sizes.push(super::data_source::TableSize {
1117 table_name,
1118 row_count,
1119 total_size_bytes,
1120 });
1121 }
1122
1123 Ok(table_sizes)
1124 }
1125
1126 #[cfg(feature = "embedded-db")]
1127 {
1128 let table_names_query = r#"
1130 SELECT name
1131 FROM sqlite_master
1132 WHERE type = 'table'
1133 AND name NOT LIKE 'sqlite_%'
1134 ORDER BY name
1135 "#;
1136
1137 let table_rows = sqlx::query(table_names_query)
1138 .fetch_all(tx.as_mut())
1139 .await
1140 .context("failed to query table names")?;
1141
1142 let mut table_sizes = Vec::new();
1143
1144 for row in table_rows {
1146 let table_name: String = row.try_get("name")?;
1147
1148 let count_query = format!("SELECT COUNT(*) as count FROM \"{}\"", table_name);
1151 let count_row = sqlx::query(&count_query)
1152 .fetch_one(tx.as_mut())
1153 .await
1154 .context(format!(
1155 "failed to query row count for table {}",
1156 table_name
1157 ))?;
1158
1159 let row_count: i64 = count_row.try_get("count").unwrap_or(0);
1160
1161 table_sizes.push(super::data_source::TableSize {
1162 table_name,
1163 row_count,
1164 total_size_bytes: None,
1165 });
1166 }
1167
1168 Ok(table_sizes)
1169 }
1170 }
1171
1172 async fn get_migration_status(
1173 &self,
1174 ) -> anyhow::Result<Vec<super::data_source::MigrationStatus>> {
1175 let mut tx = self
1176 .read()
1177 .await
1178 .context("opening transaction to fetch migration status")?;
1179
1180 type MigrationStatusRow = (
1181 String,
1182 chrono::DateTime<chrono::Utc>,
1183 Option<chrono::DateTime<chrono::Utc>>,
1184 Option<i64>,
1185 );
1186
1187 let rows: Vec<MigrationStatusRow> = sqlx::query_as(
1188 "SELECT name, started_at, completed_at, last_offset FROM deferred_migrations ORDER BY \
1189 started_at",
1190 )
1191 .fetch_all(tx.as_mut())
1192 .await
1193 .context("failed to query deferred_migrations")?;
1194
1195 Ok(rows
1196 .into_iter()
1197 .map(|(name, started_at, completed_at, last_offset)| {
1198 super::data_source::MigrationStatus {
1199 name,
1200 started_at,
1201 completed_at,
1202 last_offset,
1203 }
1204 })
1205 .collect())
1206 }
1207}
1208
1209impl super::data_source::DatabaseMetadataSource for DataSource {
1210 async fn get_table_sizes(&self) -> anyhow::Result<Vec<super::data_source::TableSize>> {
1211 self.as_ref().get_table_sizes().await
1212 }
1213
1214 async fn get_migration_status(
1215 &self,
1216 ) -> anyhow::Result<Vec<super::data_source::MigrationStatus>> {
1217 self.as_ref().get_migration_status().await
1218 }
1219}
1220
1221impl super::data_source::PruningDataSource for DataSource {
1222 async fn get_oldest_block(
1223 &self,
1224 ) -> anyhow::Result<Option<hotshot_query_service::availability::BlockQueryData<SeqTypes>>> {
1225 self.as_ref().get_oldest_block().await
1226 }
1227
1228 async fn get_oldest_leaf(
1229 &self,
1230 ) -> anyhow::Result<Option<hotshot_query_service::availability::LeafQueryData<SeqTypes>>> {
1231 self.as_ref().get_oldest_leaf().await
1232 }
1233}
1234
1235async fn load_frontier<Mode: TransactionMode>(
1236 tx: &mut Transaction<Mode>,
1237 height: u64,
1238) -> anyhow::Result<BlocksFrontier> {
1239 tx.get_path(
1240 Snapshot::<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>::Index(height),
1241 height
1242 .checked_sub(1)
1243 .ok_or(anyhow::anyhow!("Subtract with overflow ({height})!"))?,
1244 )
1245 .await
1246 .context(format!("fetching frontier at height {height}"))
1247}
1248
1249async fn load_v1_reward_accounts(
1250 db: &SqlStorage,
1251 height: u64,
1252 accounts: &[RewardAccountV1],
1253) -> anyhow::Result<(RewardMerkleTreeV1, Leaf2)> {
1254 let mut tx = db
1256 .read()
1257 .await
1258 .with_context(|| "failed to open read transaction")?;
1259
1260 let leaf = tx
1262 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
1263 .await
1264 .context(format!("leaf {height} not available"))?;
1265 let header = leaf.header();
1266
1267 if header.version() < EPOCH_VERSION || header.version() >= DRB_AND_HEADER_UPGRADE_VERSION {
1268 return Ok((
1269 RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT),
1270 leaf.leaf().clone(),
1271 ));
1272 }
1273
1274 let merkle_root = header.reward_merkle_tree_root().unwrap_left();
1276 let mut snapshot = RewardMerkleTreeV1::from_commitment(merkle_root);
1277
1278 let mut join_set = BoundedJoinSet::new(10);
1280
1281 let mut task_id_to_account = HashMap::new();
1283
1284 for account in accounts {
1286 let db_clone = db.clone();
1288 let account_clone = *account;
1289 let header_height = header.height();
1290
1291 let func = async move {
1293 let mut tx = db_clone
1295 .read()
1296 .await
1297 .with_context(|| "failed to open read transaction")?;
1298
1299 let proof = tx
1301 .get_path(
1302 Snapshot::<SeqTypes, RewardMerkleTreeV1, { RewardMerkleTreeV1::ARITY }>::Index(
1303 header_height,
1304 ),
1305 account_clone,
1306 )
1307 .await
1308 .with_context(|| {
1309 format!(
1310 "failed to get path for v1 reward account {account_clone:?}; height \
1311 {height}"
1312 )
1313 })?;
1314
1315 Ok::<_, anyhow::Error>(proof)
1316 };
1317
1318 let id = join_set.spawn(func).id();
1320
1321 task_id_to_account.insert(id, account);
1323 }
1324
1325 while let Some(result) = join_set.join_next_with_id().await {
1327 let (id, result) = result.with_context(|| "failed to join task")?;
1329
1330 let proof = result?;
1332
1333 let account = task_id_to_account
1335 .remove(&id)
1336 .with_context(|| "task ID for spawned task not found")?;
1337
1338 match proof.proof.first().with_context(|| {
1339 format!("empty proof for v1 reward account {account:?}; height {height}")
1340 })? {
1341 MerkleNode::Leaf { pos, elem, .. } => {
1342 snapshot.remember(*pos, *elem, proof)?;
1343 },
1344 MerkleNode::Empty => {
1345 snapshot.non_membership_remember(*account, proof)?;
1346 },
1347 _ => {
1348 bail!("invalid proof for v1 reward account {account:?}; height {height}");
1349 },
1350 }
1351 }
1352
1353 Ok((snapshot, leaf.leaf().clone()))
1354}
1355
1356async fn load_reward_merkle_tree_v2(
1358 db: &SqlStorage,
1359 height: u64,
1360) -> anyhow::Result<(PermittedRewardMerkleTreeV2, Leaf2)> {
1361 let mut tx = db
1363 .read()
1364 .await
1365 .with_context(|| "failed to open read transaction")?;
1366
1367 let leaf = tx
1369 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
1370 .await
1371 .with_context(|| format!("leaf {height} not available"))?;
1372
1373 let snapshot = db.load_reward_merkle_tree_v2(height).await?;
1374
1375 Ok((snapshot, leaf.leaf().clone()))
1376}
1377
1378async fn load_accounts<Mode: TransactionMode>(
1379 tx: &mut Transaction<Mode>,
1380 height: u64,
1381 accounts: &[FeeAccount],
1382) -> anyhow::Result<(FeeMerkleTree, Leaf2)> {
1383 let leaf = tx
1384 .get_leaf(LeafId::<SeqTypes>::from(height as usize))
1385 .await
1386 .context(format!("leaf {height} not available"))?;
1387 let header = leaf.header();
1388
1389 let mut snapshot = FeeMerkleTree::from_commitment(header.fee_merkle_tree_root());
1390 for account in accounts {
1391 let proof = tx
1392 .get_path(
1393 Snapshot::<SeqTypes, FeeMerkleTree, { FeeMerkleTree::ARITY }>::Index(
1394 header.height(),
1395 ),
1396 *account,
1397 )
1398 .await
1399 .context(format!(
1400 "fetching account {account}; height {}",
1401 header.height()
1402 ))?;
1403 match proof.proof.first().context(format!(
1404 "empty proof for account {account}; height {}",
1405 header.height()
1406 ))? {
1407 MerkleNode::Leaf { pos, elem, .. } => {
1408 snapshot.remember(*pos, *elem, proof)?;
1409 },
1410 MerkleNode::Empty => {
1411 snapshot.non_membership_remember(*account, proof)?;
1412 },
1413 _ => {
1414 bail!("Invalid proof");
1415 },
1416 }
1417 }
1418
1419 Ok((snapshot, leaf.leaf().clone()))
1420}
1421
1422async fn load_chain_config<Mode: TransactionMode>(
1423 tx: &mut Transaction<Mode>,
1424 commitment: Commitment<ChainConfig>,
1425) -> anyhow::Result<ChainConfig> {
1426 let (data,) = query_as::<(Vec<u8>,)>("SELECT data from chain_config where commitment = $1")
1427 .bind(commitment.to_string())
1428 .fetch_one(tx.as_mut())
1429 .await
1430 .unwrap();
1431
1432 bincode::deserialize(&data[..]).context("failed to deserialize")
1433}
1434
1435#[tracing::instrument(skip(instance, db, tx))]
1446pub(crate) async fn reconstruct_state<Mode: TransactionMode>(
1447 instance: &NodeState,
1448 db: &SqlStorage,
1449 tx: &mut Transaction<Mode>,
1450 from_height: u64,
1451 to_view: ViewNumber,
1452 fee_accounts: &[FeeAccount],
1453 reward_accounts: &[RewardAccountV2],
1454) -> anyhow::Result<(ValidatedState, Leaf2)> {
1455 tracing::info!("attempting to reconstruct fee state");
1456 let from_leaf = tx
1457 .get_leaf((from_height as usize).into())
1458 .await
1459 .context(format!("leaf {from_height} not available"))?;
1460 let from_leaf: Leaf2 = from_leaf.leaf().clone();
1461 ensure!(
1462 from_leaf.view_number() < to_view,
1463 "state reconstruction: starting state {:?} must be before ending state {to_view:?}",
1464 from_leaf.view_number(),
1465 );
1466
1467 let mut leaves = VecDeque::new();
1469 let to_leaf = get_leaf_from_proposal(tx, "view = $1", &(to_view.u64() as i64))
1470 .await
1471 .context(format!(
1472 "unable to reconstruct state because leaf {to_view:?} is not available"
1473 ))?;
1474 let mut parent = to_leaf.parent_commitment();
1475 tracing::debug!(?to_leaf, ?parent, view = ?to_view, "have required leaf");
1476 leaves.push_front(to_leaf.clone());
1477 while parent != Committable::commit(&from_leaf) {
1478 let leaf = get_leaf_from_proposal(tx, "leaf_hash = $1", &parent.to_string())
1479 .await
1480 .context(format!(
1481 "unable to reconstruct state because leaf {parent} is not available"
1482 ))?;
1483 parent = leaf.parent_commitment();
1484 tracing::debug!(?leaf, ?parent, "have required leaf");
1485 leaves.push_front(leaf);
1486 }
1487
1488 let mut parent = from_leaf;
1490 let mut state = ValidatedState::from_header(parent.block_header());
1491
1492 let mut catchup = NullStateCatchup::default();
1495
1496 let mut fee_accounts = fee_accounts.iter().copied().collect::<HashSet<_>>();
1497 tracing::info!(
1500 "reconstructing fee accounts state for from height {from_height} to view {to_view}"
1501 );
1502
1503 let dependencies =
1504 fee_header_dependencies(&mut catchup, tx, instance, &parent, &leaves).await?;
1505 fee_accounts.extend(dependencies);
1506 let fee_accounts = fee_accounts.into_iter().collect::<Vec<_>>();
1507 state.fee_merkle_tree = load_accounts(tx, from_height, &fee_accounts)
1508 .await
1509 .context("unable to reconstruct state because accounts are not available at origin")?
1510 .0;
1511 ensure!(
1512 state.fee_merkle_tree.commitment() == parent.block_header().fee_merkle_tree_root(),
1513 "loaded fee state does not match parent header"
1514 );
1515
1516 tracing::info!(
1517 "reconstructing reward accounts for from height {from_height} to view {to_view}"
1518 );
1519
1520 let mut reward_accounts = reward_accounts.iter().copied().collect::<HashSet<_>>();
1521
1522 let dependencies = reward_header_dependencies(instance, &leaves).await?;
1525 reward_accounts.extend(dependencies);
1526 let reward_accounts = reward_accounts.into_iter().collect::<Vec<_>>();
1527
1528 match parent.block_header().reward_merkle_tree_root() {
1530 either::Either::Left(expected_root) => {
1531 let accts = reward_accounts
1532 .into_iter()
1533 .map(RewardAccountV1::from)
1534 .collect::<Vec<_>>();
1535 state.reward_merkle_tree_v1 = load_v1_reward_accounts(db, from_height, &accts)
1536 .await
1537 .context(
1538 "unable to reconstruct state because v1 reward accounts are not available at \
1539 origin",
1540 )?
1541 .0;
1542 ensure!(
1543 state.reward_merkle_tree_v1.commitment() == expected_root,
1544 "loaded v1 reward state does not match parent header"
1545 );
1546 },
1547 either::Either::Right(expected_root) => {
1548 let version = parent.block_header().version();
1549 let epoch_height = instance
1550 .epoch_height
1551 .context("epoch_height not set but parent has V2 reward tree")?;
1552
1553 if version >= EPOCH_REWARD_VERSION && !is_last_block(from_height, epoch_height) {
1560 let tree = db
1561 .load_latest_reward_merkle_tree_v2(from_height)
1562 .await
1563 .context("RewardMerkleTreeV2 not available at or below origin")?;
1564 state.reward_merkle_tree_v2 = tree.tree;
1565 } else {
1566 state.reward_merkle_tree_v2 = load_reward_merkle_tree_v2(db, from_height)
1567 .await
1568 .context("RewardMerkleTreeV2 not available at origin")?
1569 .0
1570 .tree;
1571 }
1572 ensure!(
1573 state.reward_merkle_tree_v2.commitment() == expected_root,
1574 "loaded reward state does not match parent header"
1575 );
1576 },
1577 }
1578
1579 let frontier = load_frontier(tx, from_height)
1581 .await
1582 .context("unable to reconstruct state because frontier is not available at origin")?;
1583 match frontier
1584 .proof
1585 .first()
1586 .context("empty proof for frontier at origin")?
1587 {
1588 MerkleNode::Leaf { pos, elem, .. } => state
1589 .block_merkle_tree
1590 .remember(*pos, *elem, frontier)
1591 .context("failed to remember frontier")?,
1592 _ => bail!("invalid frontier proof"),
1593 }
1594
1595 for proposal in leaves {
1597 state = compute_state_update(&state, instance, &catchup, &parent, &proposal)
1598 .await
1599 .context(format!(
1600 "unable to reconstruct state because state update {} failed",
1601 proposal.height(),
1602 ))?
1603 .0;
1604 parent = proposal;
1605 }
1606
1607 tracing::info!(from_height, ?to_view, "successfully reconstructed state");
1608 Ok((state, to_leaf))
1609}
1610
1611async fn fee_header_dependencies<Mode: TransactionMode>(
1618 catchup: &mut NullStateCatchup,
1619 tx: &mut Transaction<Mode>,
1620 instance: &NodeState,
1621 mut parent: &Leaf2,
1622 leaves: impl IntoIterator<Item = &Leaf2>,
1623) -> anyhow::Result<HashSet<FeeAccount>> {
1624 let mut accounts = HashSet::default();
1625
1626 for proposal in leaves {
1627 let header = proposal.block_header();
1628 let height = header.height();
1629 let view = proposal.view_number();
1630 tracing::debug!(height, ?view, "fetching dependencies for proposal");
1631
1632 let header_cf = header.chain_config();
1633 let chain_config = if header_cf.commit() == instance.chain_config.commit() {
1634 instance.chain_config
1635 } else {
1636 match header_cf.resolve() {
1637 Some(cf) => cf,
1638 None => {
1639 tracing::info!(
1640 height,
1641 ?view,
1642 commit = %header_cf.commit(),
1643 "chain config not available, attempting to load from storage",
1644 );
1645 let cf = load_chain_config(tx, header_cf.commit())
1646 .await
1647 .context(format!(
1648 "loading chain config {} for header {},{:?}",
1649 header_cf.commit(),
1650 header.height(),
1651 proposal.view_number()
1652 ))?;
1653
1654 catchup.add_chain_config(cf);
1657 cf
1658 },
1659 }
1660 };
1661
1662 accounts.insert(chain_config.fee_recipient);
1663 accounts.extend(
1664 get_l1_deposits(instance, header, parent, chain_config.fee_contract)
1665 .await
1666 .into_iter()
1667 .map(|fee| fee.account()),
1668 );
1669 accounts.extend(header.fee_info().accounts());
1670 parent = proposal;
1671 }
1672 Ok(accounts)
1673}
1674
1675async fn reward_header_dependencies(
1679 instance: &NodeState,
1680 leaves: impl IntoIterator<Item = &Leaf2>,
1681) -> anyhow::Result<HashSet<RewardAccountV2>> {
1682 let mut reward_accounts = HashSet::default();
1683 let epoch_height = instance.epoch_height;
1684
1685 let Some(epoch_height) = epoch_height else {
1686 tracing::info!("epoch height is not set. returning empty reward_header_dependencies");
1687 return Ok(HashSet::new());
1688 };
1689
1690 let coordinator = instance.coordinator.clone();
1691 let first_epoch = coordinator.membership().first_epoch();
1692 for proposal in leaves {
1694 let header = proposal.block_header();
1695
1696 let height = header.height();
1697 let view = proposal.view_number();
1698 tracing::debug!(height, ?view, "fetching dependencies for proposal");
1699
1700 let version = header.version();
1701 if version < EPOCH_VERSION || version >= EPOCH_REWARD_VERSION {
1703 continue;
1704 }
1705
1706 let first_epoch = first_epoch.context("first epoch not found")?;
1707
1708 let proposal_epoch = EpochNumber::new(epoch_from_block_number(height, epoch_height));
1709
1710 if proposal_epoch <= first_epoch + 1 {
1712 continue;
1713 }
1714
1715 let epoch_membership = match coordinator.membership_for_epoch(Some(proposal_epoch)) {
1716 Ok(e) => e,
1717 Err(err) => {
1718 tracing::info!(
1719 "failed to get membership for epoch={proposal_epoch:?}. err={err:#}"
1720 );
1721
1722 coordinator
1723 .wait_for_catchup(proposal_epoch)
1724 .await
1725 .context(format!("failed to catchup for epoch={proposal_epoch}"))?
1726 },
1727 };
1728
1729 let snapshot = epoch_membership
1730 .snapshot()
1731 .with_context(|| format!("no committee for epoch={proposal_epoch}"))?;
1732 let leader = snapshot.lookup_leader(proposal.view_number())?;
1733 let validator = snapshot.validator_config(&leader)?;
1734
1735 reward_accounts.insert(RewardAccountV2(validator.account));
1736
1737 let delegators: Vec<RewardAccountV2> = validator
1738 .delegators
1739 .keys()
1740 .map(|d| RewardAccountV2(*d))
1741 .collect();
1742
1743 reward_accounts.extend(delegators);
1744 }
1745 Ok(reward_accounts)
1746}
1747
1748async fn get_leaf_from_proposal<Mode, P>(
1749 tx: &mut Transaction<Mode>,
1750 where_clause: &str,
1751 param: P,
1752) -> anyhow::Result<Leaf2>
1753where
1754 P: Type<Db> + for<'q> Encode<'q, Db>,
1755{
1756 let (data,) = query_as::<(Vec<u8>,)>(&format!(
1757 "SELECT data FROM quorum_proposals2 WHERE {where_clause} LIMIT 1",
1758 ))
1759 .bind(param)
1760 .fetch_one(tx.as_mut())
1761 .await?;
1762 let proposal: Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>> =
1763 bincode::deserialize(&data)?;
1764 Ok(Leaf2::from_quorum_proposal(&proposal.data))
1765}
1766
1767#[cfg(any(test, feature = "testing"))]
1768pub(crate) mod impl_testable_data_source {
1769
1770 use hotshot_query_service::data_source::storage::sql::testing::TmpDb;
1771
1772 use super::*;
1773 use crate::api::{self, data_source::testing::TestableSequencerDataSource, options::Query};
1774
1775 pub fn tmp_options(db: &TmpDb) -> Options {
1776 #[cfg(not(feature = "embedded-db"))]
1777 {
1778 let opt = crate::persistence::sql::PostgresOptions {
1779 port: Some(db.port()),
1780 host: Some(db.host()),
1781 user: Some("postgres".into()),
1782 password: Some("password".into()),
1783 ..Default::default()
1784 };
1785
1786 opt.into()
1787 }
1788
1789 #[cfg(feature = "embedded-db")]
1790 {
1791 let opt = crate::persistence::sql::SqliteOptions { path: db.path() };
1792 opt.into()
1793 }
1794 }
1795
1796 #[async_trait]
1797 impl TestableSequencerDataSource for DataSource {
1798 type Storage = TmpDb;
1799
1800 async fn create_storage() -> Self::Storage {
1801 TmpDb::init().await
1802 }
1803
1804 fn persistence_options(storage: &Self::Storage) -> Self::Options {
1805 tmp_options(storage)
1806 }
1807
1808 fn leaf_only_ds_options(
1809 storage: &Self::Storage,
1810 opt: api::Options,
1811 ) -> anyhow::Result<api::Options> {
1812 let mut ds_opts = tmp_options(storage);
1813 ds_opts.lightweight = true;
1814 Ok(opt.query_sql(Default::default(), ds_opts))
1815 }
1816
1817 fn options(storage: &Self::Storage, opt: api::Options) -> api::Options {
1818 opt.query_sql(Query::default(), tmp_options(storage))
1819 }
1820 }
1821}
1822
1823#[cfg(test)]
1824mod tests {
1825 use espresso_types::v0_4::{REWARD_MERKLE_TREE_V2_HEIGHT, RewardMerkleTreeV2};
1826 use hotshot_query_service::{
1827 data_source::{
1828 Transaction, VersionedDataSource,
1829 sql::Config,
1830 storage::{
1831 UpdateAvailabilityStorage,
1832 sql::{
1833 SqlStorage, StorageConnectionType, Transaction as SqlTransaction, Write,
1834 testing::TmpDb,
1835 },
1836 },
1837 },
1838 merklized_state::MerklizedState,
1839 };
1840 use jf_merkle_tree_compat::MerkleTreeScheme;
1841 use light_client::testing::{leaf_chain, leaf_chain_with_upgrade};
1842 use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, Upgrade};
1843
1844 use super::impl_testable_data_source::tmp_options;
1845 use crate::api::RewardMerkleTreeDataSource;
1846
1847 async fn insert_test_header(
1848 tx: &mut SqlTransaction<Write>,
1849 block_height: u64,
1850 reward_tree: &RewardMerkleTreeV2,
1851 ) {
1852 let reward_commitment = serde_json::to_value(reward_tree.commitment()).unwrap();
1853 let test_data = serde_json::json!({
1854 "block_merkle_tree_root": format!("block_root_{}", block_height),
1855 "fee_merkle_tree_root": format!("fee_root_{}", block_height),
1856 "fields": {
1857 RewardMerkleTreeV2::header_state_commitment_field(): reward_commitment
1858 }
1859 });
1860 tx.upsert(
1861 "header",
1862 [
1863 "height",
1864 "hash",
1865 "payload_hash",
1866 "timestamp",
1867 "data",
1868 "ns_table",
1869 ],
1870 ["height"],
1871 [(
1872 block_height as i64,
1873 format!("hash_{}", block_height),
1874 format!("payload_{}", block_height),
1875 block_height as i64,
1876 test_data,
1877 "ns_table".to_string(),
1878 )],
1879 )
1880 .await
1881 .unwrap();
1882 }
1883
1884 #[tokio::test]
1885 #[test_log::test]
1886 async fn test_merkle_proof_gc() {
1887 let db = TmpDb::init().await;
1888 let opt = tmp_options(&db);
1889 let cfg = Config::try_from(&opt).expect("failed to create config from options");
1890 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
1891 .await
1892 .expect("failed to connect to storage");
1893
1894 let account = vec![0; 32];
1895
1896 let leaves = leaf_chain(0..=2, DRB_AND_HEADER_UPGRADE_VERSION).await;
1898 let mut tx = storage.write().await.unwrap();
1899 for leaf in &leaves {
1900 tx.insert_leaf(leaf).await.unwrap();
1901 }
1902 Transaction::commit(tx).await.unwrap();
1903
1904 for h in 0..=2 {
1906 storage
1907 .persist_proofs(h, [(account.clone(), h.to_le_bytes().to_vec())].into_iter())
1908 .await
1909 .unwrap();
1910 }
1911
1912 storage.garbage_collect(2).await.unwrap();
1914
1915 assert_eq!(
1917 storage.load_proof(2, account.clone(), 0).await.unwrap(),
1918 2u64.to_le_bytes()
1919 );
1920
1921 for h in 0..2 {
1923 let err = storage.load_proof(h, account.clone(), 0).await.unwrap_err();
1924 assert!(err.to_string().contains("Missing proof"), "{err:#}");
1925 }
1926
1927 storage.garbage_collect(3).await.unwrap();
1929 let err = storage.load_proof(2, account, 0).await.unwrap_err();
1930 assert!(err.to_string().contains("Missing proof"), "{err:#}");
1931 }
1932
1933 #[tokio::test]
1934 #[test_log::test]
1935 async fn test_load_proof_v5_epoch_boundary() {
1936 let db = TmpDb::init().await;
1937 let opt = tmp_options(&db);
1938 let cfg = Config::try_from(&opt).expect("failed to create config from options");
1939 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
1940 .await
1941 .expect("failed to connect to storage");
1942
1943 let epoch_height = 10u64;
1944 let account = vec![0; 32];
1945
1946 let leaves = leaf_chain(0..=15, EPOCH_REWARD_VERSION).await;
1948 let mut tx = storage.write().await.unwrap();
1949 for leaf in &leaves {
1950 tx.insert_leaf(leaf).await.unwrap();
1951 }
1952 Transaction::commit(tx).await.unwrap();
1953
1954 let boundary_proof = b"proof_at_10".to_vec();
1956 {
1957 let mut tx = storage.write().await.unwrap();
1958 tx.upsert(
1959 "reward_merkle_tree_v2_proofs",
1960 ["height", "account", "proof"],
1961 ["height", "account"],
1962 [(10i64, account.clone(), boundary_proof.clone())],
1963 )
1964 .await
1965 .unwrap();
1966 Transaction::commit(tx).await.unwrap();
1967 }
1968
1969 assert_eq!(
1971 storage
1972 .load_proof(10, account.clone(), epoch_height)
1973 .await
1974 .unwrap(),
1975 boundary_proof,
1976 );
1977
1978 assert_eq!(
1981 storage
1982 .load_proof(15, account.clone(), epoch_height)
1983 .await
1984 .unwrap(),
1985 boundary_proof,
1986 );
1987 }
1988
1989 #[tokio::test]
1990 #[test_log::test]
1991 async fn test_load_proof_v4_to_v5_upgrade_boundary() {
1992 let db = TmpDb::init().await;
1993 let opt = tmp_options(&db);
1994 let cfg = Config::try_from(&opt).expect("failed to create config from options");
1995 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
1996 .await
1997 .expect("failed to connect to storage");
1998
1999 let epoch_height = 10u64;
2000 let account = vec![0; 32];
2001
2002 let upgrade = Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION);
2005 let leaves = leaf_chain_with_upgrade(0..=15, 11, upgrade).await;
2006 {
2007 let mut tx = storage.write().await.unwrap();
2008 for leaf in &leaves {
2009 tx.insert_leaf(leaf).await.unwrap();
2010 }
2011 Transaction::commit(tx).await.unwrap();
2012 }
2013
2014 storage
2017 .load_proof(15, account.clone(), epoch_height)
2018 .await
2019 .unwrap_err();
2020
2021 let v4_proof = b"v4_proof_at_5".to_vec();
2022 {
2023 let mut tx = storage.write().await.unwrap();
2024 tx.upsert(
2025 "reward_merkle_tree_v2_proofs",
2026 ["height", "account", "proof"],
2027 ["height", "account"],
2028 [(5i64, account.clone(), v4_proof.clone())],
2029 )
2030 .await
2031 .unwrap();
2032 Transaction::commit(tx).await.unwrap();
2033 }
2034 assert_eq!(
2035 storage.load_proof(5, account.clone(), 0).await.unwrap(),
2036 v4_proof,
2037 );
2038 }
2039
2040 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2041 async fn test_get_table_sizes() {
2042 use super::super::data_source::DatabaseMetadataSource;
2043
2044 let db = TmpDb::init().await;
2045 let opt = tmp_options(&db);
2046 let cfg = Config::try_from(&opt).expect("failed to create config from options");
2047 let storage = SqlStorage::connect(cfg, StorageConnectionType::Query)
2048 .await
2049 .expect("failed to connect to storage");
2050
2051 let mut tx = storage.write().await.unwrap();
2053
2054 let reward_tree = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
2056 insert_test_header(&mut tx, 1, &reward_tree).await;
2057
2058 tx.commit().await.unwrap();
2059
2060 let table_sizes = storage
2062 .get_table_sizes()
2063 .await
2064 .expect("get_table_sizes should succeed");
2065
2066 assert!(
2068 !table_sizes.is_empty(),
2069 "should have at least one table in the database"
2070 );
2071 }
2072}