1use std::{collections::BTreeMap, future::Future, sync::Arc, time::Instant};
2
3use alloy::primitives::Address;
4use anyhow::{Context, bail};
5use async_trait::async_trait;
6use committable::Committable;
7use derive_more::{Deref, DerefMut};
8use espresso_types::{
9 AuthenticatedValidatorMap, BackoffParams, Header, Leaf, Leaf2, NetworkConfig, Payload, PubKey,
10 RegisteredValidatorMap, StakeTableHash,
11 traits::{EventsPersistenceRead, MembershipPersistence, StakeTuple},
12 v0::traits::{
13 EventConsumer, PersistenceOptions, SequencerPersistence, StateCatchup, StateCertPersistence,
14 },
15 v0_3::{
16 AuthenticatedValidator, EventKey, IndexedStake, RegisteredValidator, RewardAmount,
17 StakeTableEvent,
18 },
19};
20use futures::stream::StreamExt;
21use hotshot_new_protocol::message::Certificate2;
22use hotshot_query_service::{
23 data_source::{
24 Transaction as _, VersionedDataSource,
25 storage::{SerializableRetry, pruning::PrunerCfg, sql::StorageConnectionType},
26 },
27 fetching::{
28 Provider,
29 request::{PayloadRequest, VidCommonRequest},
30 },
31 serializable_retry,
32};
33use hotshot_types::{
34 data::{
35 DaProposal, DaProposal2, EpochNumber, QuorumProposal, QuorumProposalWrapper,
36 QuorumProposalWrapperLegacy, VidCommitment, VidCommon, VidDisperseShare,
37 },
38 drb::{DrbInput, DrbResult},
39 event::{HotShotAction, LeafInfo},
40 initializer::InitializerEpochInfo,
41 message::{Proposal, convert_proposal},
42 new_protocol::CoordinatorEvent,
43 simple_certificate::{
44 CertificatePair, LightClientStateUpdateCertificateV1, LightClientStateUpdateCertificateV2,
45 NextEpochQuorumCertificate2, QuorumCertificate2, UpgradeCertificate,
46 },
47 simple_vote::LockView,
48 traits::{
49 block_contents::{BlockHeader, BlockPayload},
50 metrics::Metrics,
51 },
52 vote::HasViewNumber,
53};
54use indexmap::IndexMap;
55use itertools::Itertools;
56use light_client::storage::LightClientDbOptions;
57use sqlx::{Executor, QueryBuilder, Row, query};
58
59use super::{
60 super::{ConsensusPruningOptions, Options as SqlOptions, within_gap_fill_horizon},
61 qs::{
62 Backend as _, Config, Db, DbKind, Read, SerializableRetryConfig, SqlStorage, Transaction,
63 Write, query_as,
64 },
65};
66use crate::{
67 NodeType, RECENT_STAKE_TABLES_LIMIT, SeqTypes, ViewNumber,
68 api::options::Query,
69 catchup::SqlStateCatchup,
70 persistence::{
71 migrate_network_config, persistence_metrics::PersistenceMetricsValue,
72 storage_probe::StorageProbe,
73 },
74};
75
76pub(crate) trait BackendOptions {
78 fn apply(cfg: Config, opt: &SqlOptions) -> anyhow::Result<Config>;
79
80 fn light_client_db(cfg: &Config, query: &Query) -> LightClientDbOptions;
81
82 async fn reset_light_client(cfg: &Config) -> anyhow::Result<()>;
83
84 async fn storage_probe(
86 db: &SqlStorage,
87 opt: &SqlOptions,
88 ) -> anyhow::Result<Option<StorageProbe>>;
89}
90
91impl TryFrom<&SqlOptions> for Config {
92 type Error = anyhow::Error;
93
94 fn try_from(opt: &SqlOptions) -> Result<Self, Self::Error> {
95 let mut cfg = match &opt.uri {
96 Some(uri) => uri.parse()?,
97 None => Self::default(),
98 };
99
100 if let Some(pool) = opt.pool.clone() {
101 let pool = sqlx::Pool::<Db>::try_from(pool)
102 .ok()
103 .context("the connection pool is for the other SQL backend")?;
104 cfg = cfg.pool(pool);
105 }
106
107 cfg = cfg.max_connections(opt.max_connections);
108 cfg = cfg.idle_connection_timeout(opt.idle_connection_timeout);
109 cfg = cfg.min_connections(opt.min_connections);
110 cfg = cfg.connection_timeout(opt.connection_timeout);
111 cfg = cfg.slow_statement_threshold(opt.slow_statement_threshold);
112 cfg = cfg.statement_timeout(opt.statement_timeout);
113 cfg = Db::apply(cfg, opt)?;
114
115 if opt.prune {
116 cfg = cfg.pruner_cfg(PrunerCfg::from(opt.pruning))?;
117 }
118 if opt.archive {
119 cfg = cfg.archive();
120 }
121
122 let retry = &opt.serializable_retry;
123 cfg = cfg.serializable_retry(SerializableRetryConfig::new(
124 retry.backoff_base,
125 retry.backoff_max,
126 retry.backoff_factor,
127 retry.backoff_jitter.into(),
128 retry.retry_max,
129 retry.pg_stat_diag,
130 ));
131
132 Ok(cfg)
133 }
134}
135
136#[derive(Clone, Debug, Deref, DerefMut)]
139pub struct Options(pub(in crate::persistence::sql) SqlOptions);
140
141impl Options {
142 pub fn into_inner(self) -> SqlOptions {
143 self.0
144 }
145}
146
147impl TryFrom<SqlOptions> for Options {
148 type Error = anyhow::Error;
149
150 fn try_from(opt: SqlOptions) -> anyhow::Result<Options> {
151 anyhow::ensure!(
152 opt.embedded_db == (Db::KIND == DbKind::Sqlite),
153 "the SQL options select the other backend"
154 );
155 Ok(Options(opt))
156 }
157}
158
159#[async_trait]
160impl PersistenceOptions for Options {
161 type Persistence = Persistence;
162
163 fn set_view_retention(&mut self, view_retention: u64) {
164 self.0.consensus_pruning.target_retention = view_retention;
165 self.0.consensus_pruning.minimum_retention = view_retention;
166 }
167
168 fn set_consensus_only(&mut self) {
169 self.0.consensus_only = true;
170 }
171
172 async fn create(&mut self) -> anyhow::Result<Self::Persistence> {
173 let config = Config::try_from(&self.0)?;
174 let db = SqlStorage::connect(config, StorageConnectionType::Sequencer).await?;
175
176 let probe = Db::storage_probe(&db, &self.0).await?;
177
178 let persistence = Persistence {
179 db,
180 gc_opt: self.0.consensus_pruning,
181 last_usage_check: Arc::default(),
182 consensus_only: self.0.consensus_only,
183 internal_metrics: PersistenceMetricsValue::default(),
184 probe,
185 };
186 persistence.migrate_quorum_proposal_leaf_hashes().await?;
187 self.0.pool = Some(persistence.db.pool().into());
188
189 Ok(persistence)
190 }
191
192 async fn reset(self) -> anyhow::Result<()> {
193 let config = Config::try_from(&self.0)?;
194 Db::reset_light_client(&config).await?;
195 SqlStorage::connect(config.reset_schema(), StorageConnectionType::Sequencer).await?;
196 Ok(())
197 }
198}
199
200#[derive(Clone, Debug)]
202pub struct Persistence {
203 pub(super) db: SqlStorage,
204 gc_opt: ConsensusPruningOptions,
205 last_usage_check: Arc<parking_lot::Mutex<Option<Instant>>>,
207 consensus_only: bool,
208 internal_metrics: PersistenceMetricsValue,
210 pub(super) probe: Option<StorageProbe>,
212}
213
214const PG_SERIALIZATION_FAILURE_CODE: &str = "40001";
217
218#[derive(Debug)]
219struct DecidedLeaf {
220 info: LeafInfo<SeqTypes>,
221 cert: CertificatePair<SeqTypes>,
222}
223
224fn decide_event_from_chain(
226 chain: Vec<DecidedLeaf>,
227 cert2: Option<Certificate2<SeqTypes>>,
228) -> Option<CoordinatorEvent<SeqTypes>> {
229 let Some(newest) = chain.first() else {
230 if cert2.is_some() {
231 tracing::warn!(
232 "decide_event_from_chain called with cert2 but no leaves; cert2 will be dropped"
233 );
234 }
235 return None;
236 };
237 let cert1 = newest.cert.qc().clone();
238 let leaf_infos = chain.into_iter().map(|leaf| leaf.info).collect();
239 Some(CoordinatorEvent::NewDecide {
240 leaf_infos,
241 cert1,
242 cert2,
243 })
244}
245
246impl Persistence {
247 async fn serializable_retry<F, Fut, T>(&self, op: &'static str, f: F) -> anyhow::Result<T>
249 where
250 T: Send,
251 F: Fn() -> Fut + Send + Sync,
252 Fut: Future<Output = anyhow::Result<T>> + Send,
253 {
254 self.db.serializable_retry(op, f).await
255 }
256
257 async fn migrate_quorum_proposal_leaf_hashes(&self) -> anyhow::Result<()> {
264 serializable_retry!(self, || async {
265 let mut tx = self.db.write().await?;
266
267 let mut proposals = tx.fetch("SELECT * FROM quorum_proposals");
268
269 let mut updates = vec![];
270 while let Some(row) = proposals.next().await {
271 let row = row?;
272
273 let hash: Option<String> = row.try_get("leaf_hash")?;
274 if hash.is_none() {
275 let view: i64 = row.try_get("view")?;
276 let data: Vec<u8> = row.try_get("data")?;
277 let proposal: Proposal<SeqTypes, QuorumProposal<SeqTypes>> =
278 bincode::deserialize(&data)?;
279 let leaf = Leaf::from_quorum_proposal(&proposal.data);
280 let leaf_hash = Committable::commit(&leaf);
281 tracing::info!(view, %leaf_hash, "populating quorum proposal leaf hash");
282 updates.push((view, leaf_hash.to_string()));
283 }
284 }
285 drop(proposals);
286
287 tx.upsert("quorum_proposals", ["view", "leaf_hash"], ["view"], updates)
288 .await?;
289
290 tx.commit().await
291 })
292 .await
293 }
294
295 async fn load_processed_view(&self) -> anyhow::Result<Option<ViewNumber>> {
297 Ok(self
298 .db
299 .read()
300 .await?
301 .fetch_optional("SELECT last_processed_view FROM event_stream WHERE id = 1 LIMIT 1")
302 .await?
303 .map(|row| ViewNumber::new(row.get::<i64, _>("last_processed_view") as u64)))
304 }
305
306 async fn generate_decide_events(&self, consumer: &impl EventConsumer) -> anyhow::Result<()> {
307 let mut last_processed_view: Option<i64> = serializable_retry!(self, || async {
308 Ok(self
309 .db
310 .read()
311 .await?
312 .fetch_optional("SELECT last_processed_view FROM event_stream WHERE id = 1 LIMIT 1")
313 .await?
314 .map(|row| row.get("last_processed_view")))
315 })
316 .await?;
317 let mut last_processed_height: Option<u64> = match last_processed_view {
320 Some(view) => {
321 serializable_retry!(self, || async {
322 self.db
323 .read()
324 .await?
325 .fetch_optional(
326 query("SELECT leaf FROM anchor_leaf2 WHERE view = $1").bind(view),
327 )
328 .await?
329 .map(|row| -> anyhow::Result<u64> {
330 let leaf_data: Vec<u8> = row.get("leaf");
331 let leaf = bincode::deserialize::<Leaf2>(&leaf_data)?;
332 Ok(leaf.block_header().block_number())
333 })
334 .transpose()
335 })
336 .await?
337 },
338 None => None,
339 };
340 loop {
341 let Some((
352 from_view,
353 to_view,
354 leaves,
355 final_qc,
356 mut vid_shares,
357 mut da_proposals,
358 state_certs,
359 cert2,
360 )) = serializable_retry!(self, || async {
361 let mut tx = self.db.read().await?;
362
363 let from_view = match last_processed_view {
369 Some(v) => v + 1,
370 None => 0,
371 };
372 tracing::debug!(?from_view, "generate decide event");
373
374 let (watermark,): (Option<i64>,) = query_as("SELECT max(view) FROM anchor_leaf2")
376 .fetch_one(tx.as_mut())
377 .await?;
378
379 let mut parent = last_processed_height;
380 let mut rows = query(
381 "SELECT leaf, qc, next_epoch_qc FROM anchor_leaf2 WHERE view >= $1 ORDER BY \
382 view",
383 )
384 .bind(from_view)
385 .fetch(tx.as_mut());
386 let mut leaves: Vec<(Leaf2, CertificatePair<SeqTypes>)> = vec![];
387 let mut final_qc = None;
388 while let Some(row) = rows.next().await {
389 let row = match row {
390 Ok(row) => row,
391 Err(err) => {
392 if err.as_database_error().is_some_and(|e| {
393 e.code().as_deref() == Some(PG_SERIALIZATION_FAILURE_CODE)
394 }) {
395 drop(rows);
396 return Err(anyhow::Error::from(err));
397 }
398 tracing::warn!("error loading row: {err:#}");
401 break;
402 },
403 };
404
405 let leaf_data: Vec<u8> = row.get("leaf");
406 let leaf = bincode::deserialize::<Leaf2>(&leaf_data)?;
407 let qc_data: Vec<u8> = row.get("qc");
408 let qc = bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&qc_data)?;
409 let next_epoch_qc = match row.get::<Option<Vec<u8>>, _>("next_epoch_qc") {
410 Some(bytes) => Some(bincode::deserialize::<
411 NextEpochQuorumCertificate2<SeqTypes>,
412 >(&bytes)?),
413 None => None,
414 };
415 let height = leaf.block_header().block_number();
416
417 if let Some(parent) = parent
421 && height != parent + 1
422 {
423 if !leaves.is_empty() {
424 tracing::debug!(
425 height,
426 parent,
427 "ending decide event at non-consecutive leaf"
428 );
429 break;
430 }
431 let row_view = leaf.view_number().u64();
437 if height > parent + 1
438 && leaf.block_header().version() >= versions::NEW_PROTOCOL_VERSION
439 && watermark
440 .is_some_and(|w| within_gap_fill_horizon(row_view, w as u64))
441 {
442 tracing::info!(
443 height,
444 parent,
445 row_view,
446 ?watermark,
447 "waiting for gap-fill decide before advancing the decide cursor"
448 );
449 break;
450 }
451 }
453 parent = Some(height);
454 let cert = CertificatePair::new(qc, next_epoch_qc);
455 final_qc = Some(cert.clone());
456 leaves.push((leaf, cert));
457 }
458 drop(rows);
459
460 let Some(final_qc) = final_qc else {
461 tracing::debug!(from_view, "no new leaves at decide");
463 return Ok(None);
464 };
465
466 let from_view = leaves[0].0.view_number();
469 let to_view = leaves[leaves.len() - 1].0.view_number();
470
471 let vid_rows = tx
473 .fetch_all(
474 query("SELECT view, data FROM vid_share2 where view >= $1 AND view <= $2")
475 .bind(from_view.u64() as i64)
476 .bind(to_view.u64() as i64),
477 )
478 .await?;
479 let vid_shares = vid_rows
480 .into_iter()
481 .map(|row| {
482 let view: i64 = row.get("view");
483 let data: Vec<u8> = row.get("data");
484 let vid_proposal = bincode::deserialize::<
485 Proposal<SeqTypes, VidDisperseShare<SeqTypes>>,
486 >(&data)?;
487 Ok((view as u64, vid_proposal))
488 })
489 .collect::<anyhow::Result<BTreeMap<_, _>>>()?;
490
491 let da_rows = tx
493 .fetch_all(
494 query(
495 "SELECT view, data FROM da_proposal2 where view >= $1 AND view <= $2",
496 )
497 .bind(from_view.u64() as i64)
498 .bind(to_view.u64() as i64),
499 )
500 .await?;
501 let da_proposals = da_rows
502 .into_iter()
503 .map(|row| {
504 let view: i64 = row.get("view");
505 let data: Vec<u8> = row.get("data");
506 let da_proposal = bincode::deserialize::<
507 Proposal<SeqTypes, DaProposal2<SeqTypes>>,
508 >(&data)?;
509 Ok((view as u64, da_proposal.data))
510 })
511 .collect::<anyhow::Result<BTreeMap<_, _>>>()?;
512
513 let state_certs = Self::load_state_certs(&mut tx, from_view, to_view)
515 .await
516 .with_context(|| {
517 format!("load_state_certs from_view={from_view:?} to_view={to_view:?}")
518 })?;
519
520 let cert2_row = tx
521 .fetch_optional(
522 query("SELECT data FROM decided_cert2 WHERE view = $1")
523 .bind(to_view.u64() as i64),
524 )
525 .await?;
526 let cert2 = cert2_row
527 .map(|row| {
528 let bytes: Vec<u8> = row.get("data");
529 bincode::deserialize::<Certificate2<SeqTypes>>(&bytes)
530 .context("deserializing decided cert2")
531 })
532 .transpose()?;
533 drop(tx);
534 Ok(Some((
535 from_view,
536 to_view,
537 leaves,
538 final_qc,
539 vid_shares,
540 da_proposals,
541 state_certs,
542 cert2,
543 )))
544 })
545 .await?
546 else {
547 return Ok(());
548 };
549
550 let to_height = leaves
551 .last()
552 .map(|(leaf, _)| leaf.block_header().block_number());
553
554 let chain = leaves
556 .into_iter()
557 .rev()
559 .map(|(mut leaf, cert)| {
560 let view = leaf.view_number();
561
562 let vid_proposal = vid_shares.remove(&view);
564 if vid_proposal.is_none() {
565 tracing::debug!(?view, "VID share not available at decide");
566 }
567 let vid_share = vid_proposal.as_ref().map(|proposal| proposal.data.clone());
568
569 if let Some(proposal) = da_proposals.remove(&view) {
571 let payload =
572 Payload::from_bytes(&proposal.encoded_transactions, &proposal.metadata);
573 leaf.fill_block_payload_unchecked(payload);
574 } else if view == ViewNumber::genesis() {
575 leaf.fill_block_payload_unchecked(Payload::empty().0);
578 } else {
579 tracing::debug!(?view, "DA proposal not available at decide");
580 }
581
582 let state_cert = state_certs.get(&view).cloned();
583
584 let info = LeafInfo {
585 leaf,
586 vid_share,
587 state_cert,
588 state: Default::default(),
591 delta: Default::default(),
592 };
593 DecidedLeaf { info, cert }
594 })
595 .collect();
596
597 tracing::debug!(
598 ?from_view,
599 ?to_view,
600 ?final_qc,
601 ?chain,
602 "generating decide event"
603 );
604
605 if let Some(event) = decide_event_from_chain(chain, cert2) {
606 consumer.handle_event(&event).await?;
607 }
608
609 let from_view_i64 = from_view.u64() as i64;
610 let to_view_i64 = to_view.u64() as i64;
611 let serialized_state_certs = state_certs
612 .into_iter()
613 .map(|(epoch, cert)| Ok((epoch as i64, bincode::serialize(&cert)?)))
614 .collect::<anyhow::Result<Vec<(i64, Vec<u8>)>>>()?;
615
616 serializable_retry!(self, || async {
622 let mut tx = self.db.write().await?;
623 tx.upsert(
624 "event_stream",
625 ["id", "last_processed_view"],
626 ["id"],
627 [(1i32, to_view_i64)],
628 )
629 .await?;
630
631 for (epoch, state_cert_bytes) in &serialized_state_certs {
633 tx.upsert(
634 "finalized_state_cert",
635 ["epoch", "state_cert"],
636 ["epoch"],
637 [(*epoch, state_cert_bytes.clone())],
638 )
639 .await?;
640 }
641
642 tx.execute(
644 query("DELETE FROM vid_share2 where view >= $1 AND view <= $2")
645 .bind(from_view_i64)
646 .bind(to_view_i64),
647 )
648 .await?;
649 tx.execute(
650 query("DELETE FROM da_proposal2 where view >= $1 AND view <= $2")
651 .bind(from_view_i64)
652 .bind(to_view_i64),
653 )
654 .await?;
655 tx.execute(
656 query("DELETE FROM quorum_proposals2 where view >= $1 AND view <= $2")
657 .bind(from_view_i64)
658 .bind(to_view_i64),
659 )
660 .await?;
661 tx.execute(
662 query("DELETE FROM quorum_certificate2 where view >= $1 AND view <= $2")
663 .bind(from_view_i64)
664 .bind(to_view_i64),
665 )
666 .await?;
667 tx.execute(
668 query("DELETE FROM state_cert where view >= $1 AND view <= $2")
669 .bind(from_view_i64)
670 .bind(to_view_i64),
671 )
672 .await?;
673 tx.execute(
674 query("DELETE FROM decided_cert2 where view >= $1 AND view < $2")
675 .bind(from_view_i64)
676 .bind(to_view_i64),
677 )
678 .await?;
679
680 tx.execute(
684 query("DELETE FROM anchor_leaf2 WHERE view >= $1 AND view < $2")
685 .bind(from_view_i64)
686 .bind(to_view_i64),
687 )
688 .await?;
689
690 tx.commit().await?;
691 Ok(())
692 })
693 .await?;
694 last_processed_view = Some(to_view_i64);
695 last_processed_height = to_height;
696 }
697 }
698
699 async fn skip_decide_events(&self, view: ViewNumber) -> anyhow::Result<()> {
700 let processed = self.load_processed_view().await?;
701 if processed.is_some_and(|processed| processed >= view) {
704 return Ok(());
705 }
706 let from_view = processed.map_or(ViewNumber::genesis(), |processed| processed + 1);
707 let state_certs = serializable_retry!(self, || async {
708 let mut tx = self.db.read().await?;
709 Self::load_state_certs(&mut tx, from_view, view).await
710 })
711 .await?;
712
713 serializable_retry!(self, || async {
714 let mut tx = self.db.write().await?;
715 tx.upsert(
716 "event_stream",
717 ["id", "last_processed_view"],
718 ["id"],
719 [(1i32, view.u64() as i64)],
720 )
721 .await?;
722 for (epoch, cert) in &state_certs {
723 tx.upsert(
724 "finalized_state_cert",
725 ["epoch", "state_cert"],
726 ["epoch"],
727 [(*epoch as i64, bincode::serialize(cert)?)],
728 )
729 .await?;
730 }
731 prune_to_view(&mut tx, view.u64()).await?;
732 tx.commit().await
733 })
734 .await
735 }
736
737 async fn load_state_certs(
738 tx: &mut Transaction<Read>,
739 from_view: ViewNumber,
740 to_view: ViewNumber,
741 ) -> anyhow::Result<BTreeMap<u64, LightClientStateUpdateCertificateV2<SeqTypes>>> {
742 let rows = tx
743 .fetch_all(
744 query("SELECT view, state_cert FROM state_cert WHERE view >= $1 AND view <= $2")
745 .bind(from_view.u64() as i64)
746 .bind(to_view.u64() as i64),
747 )
748 .await?;
749
750 let mut result = BTreeMap::new();
751
752 for row in rows {
753 let data: Vec<u8> = row.get("state_cert");
754
755 let cert: LightClientStateUpdateCertificateV2<SeqTypes> = bincode::deserialize(&data)
756 .or_else(|err_v2| {
757 bincode::deserialize::<LightClientStateUpdateCertificateV1<SeqTypes>>(&data)
758 .map(Into::into)
759 .context(format!(
760 "Failed to deserialize LightClientStateUpdateCertificate: with v1 and v2. \
761 error: {err_v2}"
762 ))
763 })?;
764
765 result.insert(cert.epoch.u64(), cert);
766 }
767
768 Ok(result)
769 }
770
771 #[tracing::instrument(skip(self))]
774 async fn prune_to_retention(&self, cur_view: ViewNumber) -> anyhow::Result<()> {
775 self.prune_below(cur_view.u64().saturating_sub(self.gc_opt.target_retention))
776 .await?;
777
778 if !self.usage_check_due() {
779 return Ok(());
780 }
781 let usage = self.consensus_storage_usage().await?;
782 tracing::debug!(usage, "consensus storage usage after pruning");
783 if usage > self.gc_opt.target_usage {
784 tracing::warn!(
785 usage,
786 gc_opt = ?self.gc_opt,
787 "consensus storage is running out of space, pruning to minimum retention"
788 );
789 self.prune_below(cur_view.u64().saturating_sub(self.gc_opt.minimum_retention))
790 .await?;
791 }
792 Ok(())
793 }
794
795 async fn prune_below(&self, view: u64) -> anyhow::Result<()> {
796 if view == 0 {
798 return Ok(());
799 }
800 serializable_retry!(self, || async {
801 let mut tx = self.db.write().await?;
802 prune_to_view(&mut tx, view).await?;
803 tx.commit().await
804 })
805 .await
806 }
807
808 async fn consensus_storage_usage(&self) -> anyhow::Result<u64> {
811 let usage_query = match Db::KIND {
812 DbKind::Sqlite => format!(
813 "SELECT sum(pgsize) FROM dbstat WHERE name IN ({})",
814 PRUNE_TABLES
815 .iter()
816 .map(|table| format!("'{table}'"))
817 .join(",")
818 ),
819 DbKind::Postgres => {
820 let table_sizes = PRUNE_TABLES
821 .iter()
822 .map(|table| format!("pg_table_size('{table}')"))
823 .join(" + ");
824 format!("SELECT {table_sizes}")
825 },
826 };
827
828 let mut tx = self.db.read().await?;
829 let (usage,): (i64,) = query_as(&usage_query).fetch_one(tx.as_mut()).await?;
830 Ok(usage as u64)
831 }
832
833 fn usage_check_due(&self) -> bool {
834 let now = Instant::now();
835 let mut last = self.last_usage_check.lock();
836 if last.is_some_and(|last| now.duration_since(last) < self.gc_opt.usage_check_interval) {
837 return false;
838 }
839 *last = Some(now);
840 true
841 }
842}
843
844const PRUNE_TABLES: &[&str] = &[
845 "anchor_leaf2",
846 "vid_share2",
847 "da_proposal2",
848 "quorum_proposals2",
849 "quorum_certificate2",
850 "state_cert",
851 "decided_cert2",
852];
853
854async fn prune_to_view(tx: &mut Transaction<Write>, view: u64) -> anyhow::Result<()> {
855 tracing::debug!(view, "pruning consensus storage");
856
857 for table in PRUNE_TABLES {
858 let res = query(&format!("DELETE FROM {table} WHERE view < $1"))
859 .bind(view as i64)
860 .execute(tx.as_mut())
861 .await
862 .context(format!("pruning {table}"))?;
863 if res.rows_affected() > 0 {
864 tracing::info!(
865 "garbage collected {} rows from {table}",
866 res.rows_affected()
867 );
868 }
869 }
870
871 Ok(())
872}
873
874#[async_trait]
875impl SequencerPersistence for Persistence {
876 fn into_catchup_provider(
877 self,
878 backoff: BackoffParams,
879 ) -> anyhow::Result<Arc<dyn StateCatchup>> {
880 Ok(Arc::new(SqlStateCatchup::new(Arc::new(self.db), backoff)))
881 }
882
883 async fn load_config(&self) -> anyhow::Result<Option<NetworkConfig>> {
884 tracing::info!("loading config from Postgres");
885
886 serializable_retry!(self, || async {
887 let Some(row) = self
889 .db
890 .read()
891 .await?
892 .fetch_optional("SELECT config FROM network_config ORDER BY id DESC LIMIT 1")
893 .await?
894 else {
895 tracing::info!("config not found");
896 return Ok(None);
897 };
898 let json = row.try_get("config")?;
899
900 let json =
901 migrate_network_config(json).context("migration of network config failed")?;
902 let config = serde_json::from_value(json).context("malformed config file")?;
903
904 Ok(Some(config))
905 })
906 .await
907 }
908
909 async fn save_config(&self, cfg: &NetworkConfig) -> anyhow::Result<()> {
910 tracing::info!("saving config to database");
911 let json = serde_json::to_value(cfg)?;
912
913 serializable_retry!(self, || async {
914 let mut tx = self.db.write().await?;
915 tx.execute(query("INSERT INTO network_config (config) VALUES ($1)").bind(json.clone()))
916 .await?;
917 tx.commit().await
918 })
919 .await
920 }
921
922 async fn persist_decided_leaves(
923 &self,
924 leaf_chain: impl IntoIterator<Item = (&LeafInfo<SeqTypes>, CertificatePair<SeqTypes>)> + Send,
925 cert2: Option<&Certificate2<SeqTypes>>,
926 _consumer: &(impl EventConsumer + 'static),
927 ) -> anyhow::Result<()> {
928 let leaf_chain: Vec<_> = leaf_chain.into_iter().collect();
929 let newest = leaf_chain
931 .iter()
932 .map(|(info, _)| info.leaf.view_number())
933 .max();
934 if let Some(cert2) = cert2
935 && newest.is_none()
936 {
937 tracing::warn!(view = %cert2.view_number, "cert2 without a decided leaf; not stored");
938 }
939 let cert2_row = cert2
940 .zip(newest)
941 .map(|(cert2, newest)| {
942 let data = bincode::serialize(cert2).context("serializing cert2")?;
943 anyhow::Ok((newest.u64() as i64, data))
944 })
945 .transpose()?;
946 let values = leaf_chain
947 .into_iter()
948 .map(|(info, cert)| {
949 let mut leaf = info.leaf.clone();
954 leaf.unfill_block_payload();
955
956 let view = leaf.view_number().u64() as i64;
959 let leaf_bytes = bincode::serialize(&leaf)?;
960 let qc_bytes = bincode::serialize(cert.qc())?;
961 let next_epoch_qc_bytes = match cert.next_epoch_qc() {
962 Some(qc) => Some(bincode::serialize(qc)?),
963 None => None,
964 };
965 Ok((view, leaf_bytes, qc_bytes, next_epoch_qc_bytes))
966 })
967 .collect::<anyhow::Result<Vec<_>>>()?;
968
969 serializable_retry!(self, || async {
972 let mut tx = self.db.write().await?;
973 tx.upsert(
974 "anchor_leaf2",
975 ["view", "leaf", "qc", "next_epoch_qc"],
976 ["view"],
977 values.clone(),
978 )
979 .await?;
980 if let Some(row) = &cert2_row {
981 tx.upsert("decided_cert2", ["view", "data"], ["view"], [row.clone()])
982 .await?;
983 }
984 tx.commit().await
985 })
986 .await?;
987
988 Ok(())
989 }
990
991 async fn process_decided_events(
992 &self,
993 view: ViewNumber,
994 consumer: &(impl EventConsumer + 'static),
995 ) -> anyhow::Result<Option<ViewNumber>> {
996 let now = Instant::now();
997 if self.consensus_only {
998 self.skip_decide_events(view).await?;
999 } else {
1000 self.generate_decide_events(consumer).await?;
1003
1004 if let Err(err) = self.prune_to_retention(view).await {
1007 tracing::warn!(?view, "pruning failed: {err:#}");
1008 }
1009 }
1010 self.internal_metrics
1011 .internal_process_decided_events_duration
1012 .add_point(now.elapsed().as_secs_f64());
1013
1014 self.load_processed_view().await
1015 }
1016
1017 async fn load_latest_acted_view(&self) -> anyhow::Result<Option<ViewNumber>> {
1018 serializable_retry!(self, || async {
1019 Ok(self
1020 .db
1021 .read()
1022 .await?
1023 .fetch_optional(query("SELECT view FROM highest_voted_view WHERE id = 0"))
1024 .await?
1025 .map(|row| {
1026 let view: i64 = row.get("view");
1027 ViewNumber::new(view as u64)
1028 }))
1029 })
1030 .await
1031 }
1032
1033 async fn load_restart_view(&self) -> anyhow::Result<Option<ViewNumber>> {
1034 serializable_retry!(self, || async {
1035 Ok(self
1036 .db
1037 .read()
1038 .await?
1039 .fetch_optional(query("SELECT view FROM restart_view WHERE id = 0"))
1040 .await?
1041 .map(|row| {
1042 let view: i64 = row.get("view");
1043 ViewNumber::new(view as u64)
1044 }))
1045 })
1046 .await
1047 }
1048
1049 async fn load_anchor_leaf(&self) -> anyhow::Result<Option<(Leaf2, CertificatePair<SeqTypes>)>> {
1050 serializable_retry!(self, || async {
1051 let Some(row) = self
1052 .db
1053 .read()
1054 .await?
1055 .fetch_optional(
1056 "SELECT leaf, qc, next_epoch_qc FROM anchor_leaf2 ORDER BY view DESC LIMIT 1",
1057 )
1058 .await?
1059 else {
1060 return Ok(None);
1061 };
1062
1063 let leaf_bytes: Vec<u8> = row.get("leaf");
1064 let leaf2: Leaf2 = bincode::deserialize(&leaf_bytes)?;
1065
1066 let qc_bytes: Vec<u8> = row.get("qc");
1067 let qc2: QuorumCertificate2<SeqTypes> = bincode::deserialize(&qc_bytes)?;
1068
1069 let maybe_next_qc_bytes: Option<Vec<u8>> = row.try_get("next_epoch_qc").ok();
1070 let maybe_next_qc2 = maybe_next_qc_bytes
1071 .and_then(|next_qc_bytes| bincode::deserialize(&next_qc_bytes).ok());
1072
1073 let cert_pair = CertificatePair::new(qc2, maybe_next_qc2);
1074
1075 Ok(Some((leaf2, cert_pair)))
1076 })
1077 .await
1078 }
1079
1080 async fn load_anchor_view(&self) -> anyhow::Result<ViewNumber> {
1081 serializable_retry!(self, || async {
1082 let mut tx = self.db.read().await?;
1083 let (view,) = query_as::<(i64,)>("SELECT coalesce(max(view), 0) FROM anchor_leaf2")
1084 .fetch_one(tx.as_mut())
1085 .await?;
1086 Ok(ViewNumber::new(view as u64))
1087 })
1088 .await
1089 }
1090
1091 async fn load_da_proposal(
1092 &self,
1093 view: ViewNumber,
1094 ) -> anyhow::Result<Option<Proposal<SeqTypes, DaProposal2<SeqTypes>>>> {
1095 serializable_retry!(self, || async {
1096 let result = self
1097 .db
1098 .read()
1099 .await?
1100 .fetch_optional(
1101 query("SELECT data FROM da_proposal2 where view = $1").bind(view.u64() as i64),
1102 )
1103 .await?;
1104
1105 result
1106 .map(|row| {
1107 let bytes: Vec<u8> = row.get("data");
1108 anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?)
1109 })
1110 .transpose()
1111 })
1112 .await
1113 }
1114
1115 async fn load_vid_share(
1116 &self,
1117 view: ViewNumber,
1118 ) -> anyhow::Result<Option<Proposal<SeqTypes, VidDisperseShare<SeqTypes>>>> {
1119 serializable_retry!(self, || async {
1120 let result = self
1121 .db
1122 .read()
1123 .await?
1124 .fetch_optional(
1125 query("SELECT data FROM vid_share2 where view = $1").bind(view.u64() as i64),
1126 )
1127 .await?;
1128
1129 result
1130 .map(|row| {
1131 let bytes: Vec<u8> = row.get("data");
1132 anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?)
1133 })
1134 .transpose()
1135 })
1136 .await
1137 }
1138
1139 async fn load_quorum_proposals(
1140 &self,
1141 ) -> anyhow::Result<BTreeMap<ViewNumber, Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>>>>
1142 {
1143 serializable_retry!(self, || async {
1144 let rows = self
1145 .db
1146 .read()
1147 .await?
1148 .fetch_all("SELECT * FROM quorum_proposals2")
1149 .await?;
1150
1151 Ok(BTreeMap::from_iter(
1152 rows.into_iter()
1153 .map(|row| {
1154 let view: i64 = row.get("view");
1155 let view_number: ViewNumber = ViewNumber::new(view.try_into()?);
1156 let bytes: Vec<u8> = row.get("data");
1157 let proposal: Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>> =
1158 bincode::deserialize(&bytes).or_else(|error| {
1159 bincode::deserialize::<
1160 Proposal<SeqTypes, QuorumProposalWrapperLegacy<SeqTypes>>,
1161 >(&bytes)
1162 .map(convert_proposal)
1163 .inspect_err(|err_v3| {
1164 tracing::warn!(
1165 ?view_number,
1166 %error,
1167 %err_v3,
1168 "ignoring malformed quorum proposal DB row"
1169 );
1170 })
1171 })?;
1172 Ok((view_number, proposal))
1173 })
1174 .collect::<anyhow::Result<Vec<_>>>()?,
1175 ))
1176 })
1177 .await
1178 }
1179
1180 async fn load_quorum_proposal(
1181 &self,
1182 view: ViewNumber,
1183 ) -> anyhow::Result<Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>>> {
1184 serializable_retry!(self, || async {
1185 let mut tx = self.db.read().await?;
1186 let (data,) = query_as::<(Vec<u8>,)>(
1187 "SELECT data FROM quorum_proposals2 WHERE view = $1 LIMIT 1",
1188 )
1189 .bind(view.u64() as i64)
1190 .fetch_one(tx.as_mut())
1191 .await?;
1192 let proposal: Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>> =
1193 bincode::deserialize(&data).or_else(|error| {
1194 bincode::deserialize::<
1195 Proposal<SeqTypes, QuorumProposalWrapperLegacy<SeqTypes>>,
1196 >(&data)
1197 .map(convert_proposal)
1198 .context(format!(
1199 "Failed to deserialize quorum proposal for view {view}. \
1200 error={error}"
1201 ))
1202 })?;
1203 Ok(proposal)
1204 })
1205 .await
1206 }
1207
1208 async fn append_vid(
1209 &self,
1210 proposal: &Proposal<SeqTypes, VidDisperseShare<SeqTypes>>,
1211 ) -> anyhow::Result<()> {
1212 let view = proposal.data.view_number().u64();
1213 let payload_hash = proposal.data.payload_commitment();
1214 let data_bytes = bincode::serialize(proposal).unwrap();
1215
1216 let now = Instant::now();
1217 let res = serializable_retry!(self, || async {
1218 let mut tx = self.db.write().await?;
1219 tx.upsert(
1220 "vid_share2",
1221 ["view", "data", "payload_hash"],
1222 ["view"],
1223 [(view as i64, data_bytes.clone(), payload_hash.to_string())],
1224 )
1225 .await?;
1226 tx.commit().await
1227 })
1228 .await;
1229 self.internal_metrics
1230 .internal_append_vid_duration
1231 .add_point(now.elapsed().as_secs_f64());
1232 res
1233 }
1234
1235 async fn append_da(
1236 &self,
1237 proposal: &Proposal<SeqTypes, DaProposal<SeqTypes>>,
1238 vid_commit: VidCommitment,
1239 ) -> anyhow::Result<()> {
1240 let data = &proposal.data;
1241 let view = data.view_number().u64();
1242 let data_bytes = bincode::serialize(proposal).unwrap();
1243
1244 let now = Instant::now();
1245 let res = serializable_retry!(self, || async {
1246 let mut tx = self.db.write().await?;
1247 tx.upsert(
1248 "da_proposal",
1249 ["view", "data", "payload_hash"],
1250 ["view"],
1251 [(view as i64, data_bytes.clone(), vid_commit.to_string())],
1252 )
1253 .await?;
1254 tx.commit().await
1255 })
1256 .await;
1257 self.internal_metrics
1258 .internal_append_da_duration
1259 .add_point(now.elapsed().as_secs_f64());
1260 res
1261 }
1262
1263 async fn record_action(&self, view: ViewNumber, action: HotShotAction) -> anyhow::Result<()> {
1264 if !matches!(
1269 action,
1270 HotShotAction::Propose | HotShotAction::Vote | HotShotAction::TimeoutVote
1271 ) {
1272 return Ok(());
1273 }
1274
1275 serializable_retry!(self, || async {
1276 let stmt = format!(
1277 "INSERT INTO highest_voted_view (id, view) VALUES (0, $1)
1278 ON CONFLICT (id) DO UPDATE SET view = {MAX_FN}(highest_voted_view.view, \
1279 excluded.view)",
1280 MAX_FN = Db::MAX_FN,
1281 );
1282
1283 let mut tx = self.db.write().await?;
1284 tx.execute(query(&stmt).bind(view.u64() as i64)).await?;
1285
1286 if matches!(action, HotShotAction::Vote) {
1287 let restart_view = view + 1;
1288 let stmt = format!(
1289 "INSERT INTO restart_view (id, view) VALUES (0, $1)
1290 ON CONFLICT (id) DO UPDATE SET view = {MAX_FN}(restart_view.view, \
1291 excluded.view)",
1292 MAX_FN = Db::MAX_FN,
1293 );
1294 tx.execute(query(&stmt).bind(restart_view.u64() as i64))
1295 .await?;
1296 }
1297
1298 tx.commit().await
1299 })
1300 .await
1301 }
1302
1303 async fn append_quorum_proposal2(
1304 &self,
1305 proposal: &Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>>,
1306 ) -> anyhow::Result<()> {
1307 let view_number = proposal.data.view_number().u64();
1308
1309 let proposal_bytes = bincode::serialize(&proposal).context("serializing proposal")?;
1310 let leaf_hash = Committable::commit(&Leaf2::from_quorum_proposal(&proposal.data));
1311
1312 let justify_qc = proposal.data.justify_qc();
1314 let justify_qc_bytes = bincode::serialize(&justify_qc).context("serializing QC")?;
1315 let justify_qc_view = justify_qc.view_number.u64() as i64;
1316 let justify_qc_leaf_commit = justify_qc.data.leaf_commit.to_string();
1317
1318 let now = Instant::now();
1319 let res = serializable_retry!(self, || async {
1320 let mut tx = self.db.write().await?;
1321 tx.upsert(
1322 "quorum_proposals2",
1323 ["view", "leaf_hash", "data"],
1324 ["view"],
1325 [(
1326 view_number as i64,
1327 leaf_hash.to_string(),
1328 proposal_bytes.clone(),
1329 )],
1330 )
1331 .await?;
1332 tx.upsert(
1333 "quorum_certificate2",
1334 ["view", "leaf_hash", "data"],
1335 ["view"],
1336 [(
1337 justify_qc_view,
1338 justify_qc_leaf_commit.clone(),
1339 justify_qc_bytes.clone(),
1340 )],
1341 )
1342 .await?;
1343 tx.commit().await
1344 })
1345 .await;
1346 self.internal_metrics
1347 .internal_append_quorum2_duration
1348 .add_point(now.elapsed().as_secs_f64());
1349 res
1350 }
1351
1352 async fn append_cert2(
1353 &self,
1354 view: ViewNumber,
1355 cert2: Certificate2<SeqTypes>,
1356 ) -> anyhow::Result<()> {
1357 let data = bincode::serialize(&cert2).context("serializing cert2")?;
1358 let view_i64 = view.u64() as i64;
1359 serializable_retry!(self, || async {
1360 let mut tx = self.db.write().await?;
1361 tx.upsert(
1362 "decided_cert2",
1363 ["view", "data"],
1364 ["view"],
1365 [(view_i64, data.clone())],
1366 )
1367 .await?;
1368 tx.commit().await
1369 })
1370 .await
1371 }
1372
1373 async fn load_cert2(&self, view: ViewNumber) -> anyhow::Result<Option<Certificate2<SeqTypes>>> {
1374 let row = self
1375 .db
1376 .read()
1377 .await?
1378 .fetch_optional(
1379 query("SELECT data FROM decided_cert2 WHERE view = $1").bind(view.u64() as i64),
1380 )
1381 .await?;
1382 row.map(|row| {
1383 let bytes: Vec<u8> = row.get("data");
1384 bincode::deserialize::<Certificate2<SeqTypes>>(&bytes).context("deserializing cert2")
1385 })
1386 .transpose()
1387 }
1388
1389 async fn append_high_qc2(&self, high_qc: QuorumCertificate2<SeqTypes>) -> anyhow::Result<()> {
1390 let lock = LockView::of(&high_qc);
1392 let data = bincode::serialize(&high_qc).context("serializing high_qc2")?;
1393 serializable_retry!(self, || async {
1394 let mut tx = self.db.write().await?;
1395 let stored_view = query("SELECT data FROM high_qc2 WHERE id = true")
1400 .fetch_optional(tx.as_mut())
1401 .await?
1402 .map(|row| {
1403 let bytes: Vec<u8> = row.get("data");
1404 bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&bytes)
1405 .context("deserializing existing high_qc2")
1406 .map(|qc| LockView::of(&qc))
1407 })
1408 .transpose()?;
1409 if stored_view.is_some_and(|stored| stored >= lock) {
1410 return Ok(());
1411 }
1412 tx.upsert("high_qc2", ["id", "data"], ["id"], [(true, data.clone())])
1413 .await?;
1414 tx.commit().await
1415 })
1416 .await
1417 }
1418
1419 async fn load_high_qc2(&self) -> anyhow::Result<Option<QuorumCertificate2<SeqTypes>>> {
1420 let row = self
1421 .db
1422 .read()
1423 .await?
1424 .fetch_optional("SELECT data FROM high_qc2 WHERE id = true")
1425 .await?;
1426 row.map(|row| {
1427 let bytes: Vec<u8> = row.get("data");
1428 bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&bytes)
1429 .context("deserializing high_qc2")
1430 })
1431 .transpose()
1432 }
1433
1434 async fn append_boundary_qc2(&self, qc: QuorumCertificate2<SeqTypes>) -> anyhow::Result<()> {
1435 let lock = LockView::of(&qc);
1436 let data = bincode::serialize(&qc).context("serializing boundary_qc2")?;
1437 serializable_retry!(self, || async {
1438 let mut tx = self.db.write().await?;
1439 let stored = query("SELECT data FROM boundary_qc2 WHERE id = true")
1441 .fetch_optional(tx.as_mut())
1442 .await?
1443 .map(|row| {
1444 let bytes: Vec<u8> = row.get("data");
1445 bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&bytes)
1446 .context("deserializing existing boundary_qc2")
1447 .map(|qc| LockView::of(&qc))
1448 })
1449 .transpose()?;
1450 if stored.is_some_and(|stored| stored >= lock) {
1451 return Ok(());
1452 }
1453 tx.upsert(
1454 "boundary_qc2",
1455 ["id", "data"],
1456 ["id"],
1457 [(true, data.clone())],
1458 )
1459 .await?;
1460 tx.commit().await
1461 })
1462 .await
1463 }
1464
1465 async fn load_boundary_qc2(&self) -> anyhow::Result<Option<QuorumCertificate2<SeqTypes>>> {
1466 let row = self
1467 .db
1468 .read()
1469 .await?
1470 .fetch_optional("SELECT data FROM boundary_qc2 WHERE id = true")
1471 .await?;
1472 row.map(|row| {
1473 let bytes: Vec<u8> = row.get("data");
1474 bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&bytes)
1475 .context("deserializing boundary_qc2")
1476 })
1477 .transpose()
1478 }
1479
1480 async fn load_upgrade_certificate(
1481 &self,
1482 ) -> anyhow::Result<Option<UpgradeCertificate<SeqTypes>>> {
1483 let result = self
1484 .db
1485 .read()
1486 .await?
1487 .fetch_optional("SELECT * FROM upgrade_certificate where id = true")
1488 .await?;
1489
1490 result
1491 .map(|row| {
1492 let bytes: Vec<u8> = row.get("data");
1493 anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?)
1494 })
1495 .transpose()
1496 }
1497
1498 async fn store_upgrade_certificate(
1499 &self,
1500 decided_upgrade_certificate: Option<UpgradeCertificate<SeqTypes>>,
1501 ) -> anyhow::Result<()> {
1502 let certificate = match decided_upgrade_certificate {
1503 Some(cert) => cert,
1504 None => return Ok(()),
1505 };
1506 let upgrade_certificate_bytes =
1507 bincode::serialize(&certificate).context("serializing upgrade certificate")?;
1508 serializable_retry!(self, || async {
1509 let mut tx = self.db.write().await?;
1510 tx.upsert(
1511 "upgrade_certificate",
1512 ["id", "data"],
1513 ["id"],
1514 [(true, upgrade_certificate_bytes.clone())],
1515 )
1516 .await?;
1517 tx.commit().await
1518 })
1519 .await
1520 }
1521
1522 async fn append_next_epoch_high_qc2(
1523 &self,
1524 next_epoch_high_qc: NextEpochQuorumCertificate2<SeqTypes>,
1525 ) -> anyhow::Result<()> {
1526 let view = next_epoch_high_qc.view_number();
1527 let data =
1528 bincode::serialize(&next_epoch_high_qc).context("serializing next epoch high_qc2")?;
1529 serializable_retry!(self, || async {
1530 let mut tx = self.db.write().await?;
1531 let stored_view =
1532 query("SELECT data FROM next_epoch_quorum_certificate WHERE id = true")
1533 .fetch_optional(tx.as_mut())
1534 .await?
1535 .map(|row| {
1536 let bytes: Vec<u8> = row.get("data");
1537 bincode::deserialize::<NextEpochQuorumCertificate2<SeqTypes>>(&bytes)
1538 .context("deserializing existing next epoch high_qc2")
1539 .map(|qc| qc.view_number())
1540 })
1541 .transpose()?;
1542 if stored_view.is_some_and(|stored| stored >= view) {
1543 return Ok(());
1544 }
1545 tx.upsert(
1546 "next_epoch_quorum_certificate",
1547 ["id", "data"],
1548 ["id"],
1549 [(true, data.clone())],
1550 )
1551 .await?;
1552 tx.commit().await
1553 })
1554 .await
1555 }
1556
1557 async fn store_eqc(
1558 &self,
1559 high_qc: QuorumCertificate2<SeqTypes>,
1560 next_epoch_high_qc: NextEpochQuorumCertificate2<SeqTypes>,
1561 ) -> anyhow::Result<()> {
1562 let eqc_bytes =
1563 bincode::serialize(&(high_qc, next_epoch_high_qc)).context("serializing eqc")?;
1564 serializable_retry!(self, || async {
1565 let mut tx = self.db.write().await?;
1566 tx.upsert("eqc", ["id", "data"], ["id"], [(true, eqc_bytes.clone())])
1567 .await?;
1568 tx.commit().await
1569 })
1570 .await
1571 }
1572
1573 async fn load_eqc(
1574 &self,
1575 ) -> Option<(
1576 QuorumCertificate2<SeqTypes>,
1577 NextEpochQuorumCertificate2<SeqTypes>,
1578 )> {
1579 let result = self
1580 .db
1581 .read()
1582 .await
1583 .ok()?
1584 .fetch_optional("SELECT * FROM eqc where id = true")
1585 .await
1586 .ok()?;
1587
1588 result
1589 .map(|row| {
1590 let bytes: Vec<u8> = row.get("data");
1591 bincode::deserialize(&bytes)
1592 })
1593 .transpose()
1594 .ok()?
1595 }
1596
1597 async fn append_da2(
1598 &self,
1599 proposal: &Proposal<SeqTypes, DaProposal2<SeqTypes>>,
1600 vid_commit: VidCommitment,
1601 ) -> anyhow::Result<()> {
1602 if self.consensus_only {
1603 return Ok(());
1604 }
1605 let data = &proposal.data;
1606 let view = data.view_number().u64();
1607 let data_bytes = bincode::serialize(proposal).unwrap();
1608
1609 let now = Instant::now();
1610 let res = serializable_retry!(self, || async {
1611 let mut tx = self.db.write().await?;
1612 tx.upsert(
1613 "da_proposal2",
1614 ["view", "data", "payload_hash"],
1615 ["view"],
1616 [(view as i64, data_bytes.clone(), vid_commit.to_string())],
1617 )
1618 .await?;
1619 tx.commit().await
1620 })
1621 .await;
1622 self.internal_metrics
1623 .internal_append_da2_duration
1624 .add_point(now.elapsed().as_secs_f64());
1625 res
1626 }
1627
1628 async fn store_drb_result(
1629 &self,
1630 epoch: EpochNumber,
1631 drb_result: DrbResult,
1632 ) -> anyhow::Result<()> {
1633 let epoch_i64 = epoch.u64() as i64;
1634 let drb_result_vec = Vec::from(drb_result);
1635 serializable_retry!(self, || async {
1636 let mut tx = self.db.write().await?;
1637 tx.upsert(
1638 "epoch_drb_and_root",
1639 ["epoch", "drb_result"],
1640 ["epoch"],
1641 [(epoch_i64, drb_result_vec.clone())],
1642 )
1643 .await?;
1644 tx.commit().await
1645 })
1646 .await
1647 }
1648
1649 async fn store_drb_input(&self, drb_input: DrbInput) -> anyhow::Result<()> {
1650 if let Ok(loaded_drb_input) = self.load_drb_input(drb_input.epoch).await {
1651 if loaded_drb_input.difficulty_level != drb_input.difficulty_level {
1652 tracing::error!("Overwriting {loaded_drb_input:?} in storage with {drb_input:?}");
1653 } else if loaded_drb_input.iteration >= drb_input.iteration {
1654 anyhow::bail!(
1655 "DrbInput in storage {:?} is more recent than {:?}, refusing to update",
1656 loaded_drb_input,
1657 drb_input
1658 )
1659 }
1660 }
1661
1662 let drb_epoch_i64 = drb_input.epoch as i64;
1663 let drb_input_bytes = bincode::serialize(&drb_input)
1664 .context("Failed to serialize DrbInput. This is not fatal, but should never happen.")?;
1665
1666 serializable_retry!(self, || async {
1667 let mut tx = self.db.write().await?;
1668 tx.upsert(
1669 "drb",
1670 ["epoch", "drb_input"],
1671 ["epoch"],
1672 [(drb_epoch_i64, drb_input_bytes.clone())],
1673 )
1674 .await?;
1675 tx.commit().await
1676 })
1677 .await
1678 }
1679
1680 async fn load_drb_input(&self, epoch: u64) -> anyhow::Result<DrbInput> {
1681 let row = self
1682 .db
1683 .read()
1684 .await?
1685 .fetch_optional(query("SELECT drb_input FROM drb WHERE epoch = $1").bind(epoch as i64))
1686 .await?;
1687
1688 match row {
1689 None => anyhow::bail!("No DrbInput for epoch {} in storage", epoch),
1690 Some(row) => {
1691 let drb_input_bytes: Vec<u8> = row.try_get("drb_input")?;
1692 let drb_input = bincode::deserialize(&drb_input_bytes)
1693 .context("Failed to deserialize drb_input from storage")?;
1694
1695 Ok(drb_input)
1696 },
1697 }
1698 }
1699
1700 async fn add_state_cert(
1701 &self,
1702 state_cert: LightClientStateUpdateCertificateV2<SeqTypes>,
1703 ) -> anyhow::Result<()> {
1704 let view_number = state_cert.light_client_state.view_number as i64;
1705 let state_cert_bytes = bincode::serialize(&state_cert)
1706 .context("serializing light client state update certificate")?;
1707
1708 serializable_retry!(self, || async {
1709 let mut tx = self.db.write().await?;
1710 tx.upsert(
1711 "state_cert",
1712 ["view", "state_cert"],
1713 ["view"],
1714 [(view_number, state_cert_bytes.clone())],
1715 )
1716 .await?;
1717 tx.commit().await
1718 })
1719 .await
1720 }
1721
1722 async fn load_state_cert(
1723 &self,
1724 ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>> {
1725 let Some(row) = self
1726 .db
1727 .read()
1728 .await?
1729 .fetch_optional(
1730 "SELECT state_cert FROM finalized_state_cert ORDER BY epoch DESC LIMIT 1",
1731 )
1732 .await?
1733 else {
1734 return Ok(None);
1735 };
1736 let bytes: Vec<u8> = row.get("state_cert");
1737
1738 let cert = match bincode::deserialize(&bytes) {
1739 Ok(cert) => cert,
1740 Err(err) => {
1741 tracing::info!(
1742 error = %err,
1743 "Failed to deserialize state certificate with v2. attempting with v1"
1744 );
1745
1746 let v1_cert =
1747 bincode::deserialize::<LightClientStateUpdateCertificateV1<SeqTypes>>(&bytes)
1748 .with_context(|| {
1749 format!("Failed to deserialize using both v1 and v2. error: {err}")
1750 })?;
1751
1752 v1_cert.into()
1753 },
1754 };
1755
1756 Ok(Some(cert))
1757 }
1758
1759 async fn load_start_epoch_info(&self) -> anyhow::Result<Vec<InitializerEpochInfo<SeqTypes>>> {
1760 let rows = self
1761 .db
1762 .read()
1763 .await?
1764 .fetch_all(
1765 query("SELECT * from epoch_drb_and_root ORDER BY epoch DESC LIMIT $1")
1766 .bind(RECENT_STAKE_TABLES_LIMIT as i64),
1767 )
1768 .await?;
1769
1770 rows.into_iter()
1772 .rev()
1773 .map(|row| {
1774 let epoch: i64 = row.try_get("epoch")?;
1775 let drb_result: Option<Vec<u8>> = row.try_get("drb_result")?;
1776 let block_header: Option<Vec<u8>> = row.try_get("block_header")?;
1777 if let Some(drb_result) = drb_result {
1778 let drb_result_array = drb_result
1779 .try_into()
1780 .or_else(|_| bail!("invalid drb result"))?;
1781 let block_header: Option<<SeqTypes as NodeType>::BlockHeader> = block_header
1782 .map(|data| bincode::deserialize(&data))
1783 .transpose()?;
1784 Ok(Some(InitializerEpochInfo::<SeqTypes> {
1785 epoch: EpochNumber::new(epoch as u64),
1786 drb_result: drb_result_array,
1787 block_header,
1788 }))
1789 } else {
1790 Ok(None)
1793 }
1794 })
1795 .filter_map(|e| match e {
1796 Err(v) => Some(Err(v)),
1797 Ok(Some(v)) => Some(Ok(v)),
1798 Ok(None) => None,
1799 })
1800 .collect()
1801 }
1802
1803 fn enable_metrics(&mut self, metrics: &dyn Metrics) {
1804 self.internal_metrics = PersistenceMetricsValue::new(metrics);
1805
1806 if let Some(probe) = &self.probe {
1807 probe.register(&*metrics.subgroup("disk".into()));
1808 }
1809 }
1810}
1811
1812#[async_trait]
1813impl StateCertPersistence for Persistence {
1814 async fn get_state_cert_by_epoch(
1815 &self,
1816 epoch: u64,
1817 ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>> {
1818 let Some(row) = self
1819 .db
1820 .read()
1821 .await?
1822 .fetch_optional(
1823 query("SELECT state_cert FROM finalized_state_cert WHERE epoch = $1")
1824 .bind(epoch as i64),
1825 )
1826 .await?
1827 else {
1828 return Ok(None);
1829 };
1830 let bytes: Vec<u8> = row.get("state_cert");
1831
1832 let cert = match bincode::deserialize(&bytes) {
1833 Ok(cert) => cert,
1834 Err(err) => {
1835 tracing::info!(
1836 error = %err,
1837 "Failed to deserialize state certificate with v2. attempting with v1"
1838 );
1839
1840 let v1_cert =
1841 bincode::deserialize::<LightClientStateUpdateCertificateV1<SeqTypes>>(&bytes)
1842 .with_context(|| {
1843 format!("Failed to deserialize using both v1 and v2. error: {err}")
1844 })?;
1845
1846 v1_cert.into()
1847 },
1848 };
1849
1850 Ok(Some(cert))
1851 }
1852
1853 async fn insert_state_cert(
1854 &self,
1855 epoch: u64,
1856 cert: LightClientStateUpdateCertificateV2<SeqTypes>,
1857 ) -> anyhow::Result<()> {
1858 let epoch_i64 = epoch as i64;
1859 let bytes = bincode::serialize(&cert)
1860 .with_context(|| format!("Failed to serialize state cert for epoch {epoch}"))?;
1861
1862 serializable_retry!(self, || async {
1863 let mut tx = self.db.write().await?;
1864 tx.upsert(
1865 "finalized_state_cert",
1866 ["epoch", "state_cert"],
1867 ["epoch"],
1868 [(epoch_i64, bytes.clone())],
1869 )
1870 .await?;
1871 tx.commit().await
1872 })
1873 .await
1874 }
1875}
1876
1877fn deserialize_authenticated_validator_map(
1878 bytes: &[u8],
1879) -> anyhow::Result<AuthenticatedValidatorMap> {
1880 if let Ok(map) = bincode::deserialize::<AuthenticatedValidatorMap>(bytes) {
1881 return Ok(map);
1882 }
1883
1884 if let Ok(pre_schnorr) = bincode::deserialize::<
1886 IndexMap<Address, crate::persistence::RegisteredValidatorPreSchnorrOption>,
1887 >(bytes)
1888 {
1889 return pre_schnorr
1890 .into_iter()
1891 .map(|(addr, v)| {
1892 let registered = v.migrate();
1893 let authenticated = AuthenticatedValidator::try_from(registered)?;
1894 Ok((addr, authenticated))
1895 })
1896 .collect();
1897 }
1898
1899 let legacy: IndexMap<Address, crate::persistence::RegisteredValidatorPreOption> =
1901 bincode::deserialize(bytes).context("deserializing stake table")?;
1902 legacy
1903 .into_iter()
1904 .map(|(addr, v)| {
1905 let registered = v.migrate();
1906 let authenticated = AuthenticatedValidator::try_from(registered)?;
1907 Ok((addr, authenticated))
1908 })
1909 .collect()
1910}
1911
1912#[async_trait]
1913impl MembershipPersistence for Persistence {
1914 async fn load_stake(&self, epoch: EpochNumber) -> anyhow::Result<Option<StakeTuple>> {
1915 let result = self
1916 .db
1917 .read()
1918 .await?
1919 .fetch_optional(
1920 query(
1921 "SELECT stake, block_reward, stake_table_hash FROM epoch_drb_and_root WHERE \
1922 epoch = $1 AND stake IS NOT NULL",
1923 )
1924 .bind(epoch.u64() as i64),
1925 )
1926 .await?;
1927
1928 result
1929 .map(|row| {
1930 let stake_table_bytes: Vec<u8> = row.get("stake");
1931 let reward_bytes: Option<Vec<u8>> = row.get("block_reward");
1932 let stake_table_hash_bytes: Option<Vec<u8>> = row.get("stake_table_hash");
1933 let stake_table = deserialize_authenticated_validator_map(&stake_table_bytes)?;
1934 let reward: Option<RewardAmount> = reward_bytes
1935 .map(|b| bincode::deserialize(&b).context("deserializing block_reward"))
1936 .transpose()?;
1937 let stake_table_hash: Option<StakeTableHash> = stake_table_hash_bytes
1938 .map(|b| bincode::deserialize(&b).context("deserializing stake table hash"))
1939 .transpose()?;
1940
1941 Ok((stake_table, reward, stake_table_hash))
1942 })
1943 .transpose()
1944 }
1945
1946 async fn load_drb_result(&self, epoch: EpochNumber) -> anyhow::Result<Option<DrbResult>> {
1947 let result = self
1948 .db
1949 .read()
1950 .await?
1951 .fetch_optional(
1952 query(
1953 "SELECT drb_result FROM epoch_drb_and_root WHERE epoch = $1 AND drb_result IS \
1954 NOT NULL",
1955 )
1956 .bind(epoch.u64() as i64),
1957 )
1958 .await?;
1959
1960 result
1961 .map(|row| {
1962 let bytes: Vec<u8> = row.get("drb_result");
1963 bytes.try_into().or_else(|_| bail!("invalid drb result"))
1964 })
1965 .transpose()
1966 }
1967
1968 async fn load_epoch_root(&self, epoch: EpochNumber) -> anyhow::Result<Option<Header>> {
1969 let result = self
1970 .db
1971 .read()
1972 .await?
1973 .fetch_optional(
1974 query(
1975 "SELECT block_header FROM epoch_drb_and_root WHERE epoch = $1 AND \
1976 block_header IS NOT NULL",
1977 )
1978 .bind(epoch.u64() as i64),
1979 )
1980 .await?;
1981
1982 result
1983 .map(|row| {
1984 let bytes: Vec<u8> = row.get("block_header");
1985 bincode::deserialize(&bytes).context("deserializing block header")
1986 })
1987 .transpose()
1988 }
1989
1990 async fn store_epoch_root(
1991 &self,
1992 epoch: EpochNumber,
1993 block_header: Header,
1994 ) -> anyhow::Result<()> {
1995 let epoch_i64 = epoch.u64() as i64;
1996 let block_header_bytes =
1997 bincode::serialize(&block_header).context("serializing block header")?;
1998
1999 serializable_retry!(self, || async {
2000 let mut tx = self.db.write().await?;
2001 tx.upsert(
2002 "epoch_drb_and_root",
2003 ["epoch", "block_header"],
2004 ["epoch"],
2005 [(epoch_i64, block_header_bytes.clone())],
2006 )
2007 .await?;
2008 tx.commit().await
2009 })
2010 .await
2011 }
2012
2013 async fn load_latest_stake(&self, limit: u64) -> anyhow::Result<Option<Vec<IndexedStake>>> {
2014 let mut tx = self.db.read().await?;
2015
2016 let rows = match query_as::<(i64, Vec<u8>, Option<Vec<u8>>, Option<Vec<u8>>)>(
2017 "SELECT epoch, stake, block_reward, stake_table_hash FROM epoch_drb_and_root WHERE \
2018 stake is NOT NULL ORDER BY epoch DESC LIMIT $1",
2019 )
2020 .bind(limit as i64)
2021 .fetch_all(tx.as_mut())
2022 .await
2023 {
2024 Ok(bytes) => bytes,
2025 Err(err) => {
2026 tracing::error!("error loading stake tables: {err:#}");
2027 bail!("{err:#}");
2028 },
2029 };
2030
2031 let stakes: anyhow::Result<Vec<IndexedStake>> = rows
2032 .into_iter()
2033 .map(
2034 |(id, stake_bytes, reward_bytes_opt, stake_table_hash_bytes_opt)| {
2035 let stake_table = deserialize_authenticated_validator_map(&stake_bytes)?;
2036
2037 let block_reward: Option<RewardAmount> = reward_bytes_opt
2038 .map(|b| bincode::deserialize(&b).context("deserializing block_reward"))
2039 .transpose()?;
2040
2041 let stake_table_hash: Option<StakeTableHash> = stake_table_hash_bytes_opt
2042 .map(|b| bincode::deserialize(&b).context("deserializing stake table hash"))
2043 .transpose()?;
2044
2045 Ok((
2046 EpochNumber::new(id as u64),
2047 (stake_table, block_reward),
2048 stake_table_hash,
2049 ))
2050 },
2051 )
2052 .collect();
2053
2054 Ok(Some(stakes?))
2055 }
2056
2057 async fn store_stake(
2058 &self,
2059 epoch: EpochNumber,
2060 stake: AuthenticatedValidatorMap,
2061 block_reward: Option<RewardAmount>,
2062 stake_table_hash: Option<StakeTableHash>,
2063 ) -> anyhow::Result<()> {
2064 let epoch_i64 = epoch.u64() as i64;
2065 let stake_table_bytes = bincode::serialize(&stake).context("serializing stake table")?;
2066 let reward_bytes = block_reward
2067 .map(|r| bincode::serialize(&r).context("serializing block reward"))
2068 .transpose()?;
2069 let stake_table_hash_bytes = stake_table_hash
2070 .map(|h| bincode::serialize(&h).context("serializing stake table hash"))
2071 .transpose()?;
2072 serializable_retry!(self, || async {
2073 let mut tx = self.db.write().await?;
2074 tx.upsert(
2075 "epoch_drb_and_root",
2076 ["epoch", "stake", "block_reward", "stake_table_hash"],
2077 ["epoch"],
2078 [(
2079 epoch_i64,
2080 stake_table_bytes.clone(),
2081 reward_bytes.clone(),
2082 stake_table_hash_bytes.clone(),
2083 )],
2084 )
2085 .await?;
2086 tx.commit().await
2087 })
2088 .await
2089 }
2090
2091 async fn store_events(
2092 &self,
2093 l1_finalized: u64,
2094 events: Vec<(EventKey, StakeTableEvent)>,
2095 ) -> anyhow::Result<()> {
2096 let l1_finalized_i64: i64 = l1_finalized.try_into()?;
2097 let serialized_events = events
2098 .into_iter()
2099 .map(|((block_number, index), event)| {
2100 Ok((
2101 i64::try_from(block_number)?,
2102 i64::try_from(index)?,
2103 serde_json::to_value(event).context("l1 event to value")?,
2104 ))
2105 })
2106 .collect::<anyhow::Result<Vec<_>>>()?;
2107
2108 serializable_retry!(self, || async {
2109 let mut tx = self.db.write().await?;
2110
2111 let last_processed_l1_block = query_as::<(i64,)>(
2113 "SELECT last_l1_block FROM stake_table_events_l1_block where id = 0",
2114 )
2115 .fetch_optional(tx.as_mut())
2116 .await?
2117 .map(|(l1,)| l1);
2118
2119 tracing::debug!("last l1 finalizes in database = {last_processed_l1_block:?}");
2120
2121 let serialized_events_len = serialized_events.len();
2123 if last_processed_l1_block > Some(l1_finalized_i64) {
2124 tracing::debug!(
2125 ?last_processed_l1_block,
2126 l1_finalized,
2127 serialized_events_len,
2128 "last l1 finalized stored is already higher"
2129 );
2130 return Ok(());
2131 }
2132
2133 if !serialized_events.is_empty() {
2134 let mut query_builder: sqlx::QueryBuilder<Db> = sqlx::QueryBuilder::new(
2135 "INSERT INTO stake_table_events (l1_block, log_index, event) ",
2136 );
2137
2138 query_builder.push_values(
2139 serialized_events.iter().cloned(),
2140 |mut b, (l1_block, log_index, event)| {
2141 b.push_bind(l1_block).push_bind(log_index).push_bind(event);
2142 },
2143 );
2144
2145 query_builder.push(" ON CONFLICT DO NOTHING");
2146 let query = query_builder.build();
2147
2148 query.execute(tx.as_mut()).await?;
2149 }
2150
2151 tx.upsert(
2153 "stake_table_events_l1_block",
2154 ["id", "last_l1_block"],
2155 ["id"],
2156 [(0_i32, l1_finalized_i64)],
2157 )
2158 .await?;
2159
2160 tx.commit().await?;
2161
2162 Ok(())
2163 })
2164 .await
2165 }
2166
2167 async fn load_events(
2178 &self,
2179 from_l1_block: u64,
2180 to_l1_block: u64,
2181 ) -> anyhow::Result<(
2182 Option<EventsPersistenceRead>,
2183 Vec<(EventKey, StakeTableEvent)>,
2184 )> {
2185 let mut tx = self.db.read().await?;
2186
2187 let res = query_as::<(i64,)>(
2189 "SELECT last_l1_block FROM stake_table_events_l1_block where id = 0",
2190 )
2191 .fetch_optional(tx.as_mut())
2192 .await?;
2193
2194 let Some((last_processed_l1_block,)) = res else {
2195 return Ok((None, Vec::new()));
2197 };
2198
2199 let to_l1_block = to_l1_block.try_into()?;
2203 let query_l1_block = if last_processed_l1_block > to_l1_block {
2204 to_l1_block
2205 } else {
2206 last_processed_l1_block
2207 };
2208
2209 let rows = query(
2210 "SELECT l1_block, log_index, event FROM stake_table_events WHERE $1 <= l1_block AND \
2211 l1_block <= $2 ORDER BY l1_block ASC, log_index ASC",
2212 )
2213 .bind(i64::try_from(from_l1_block)?)
2214 .bind(query_l1_block)
2215 .fetch_all(tx.as_mut())
2216 .await?;
2217
2218 let events = rows
2219 .into_iter()
2220 .map(|row| {
2221 let l1_block: i64 = row.try_get("l1_block")?;
2222 let log_index: i64 = row.try_get("log_index")?;
2223 let event = serde_json::from_value(row.try_get("event")?)?;
2224
2225 Ok(((l1_block.try_into()?, log_index.try_into()?), event))
2226 })
2227 .collect::<anyhow::Result<Vec<_>>>()?;
2228
2229 if query_l1_block == to_l1_block {
2233 Ok((Some(EventsPersistenceRead::Complete), events))
2234 } else {
2235 Ok((
2236 Some(EventsPersistenceRead::UntilL1Block(
2237 query_l1_block.try_into()?,
2238 )),
2239 events,
2240 ))
2241 }
2242 }
2243
2244 async fn delete_stake_tables(&self) -> anyhow::Result<()> {
2245 serializable_retry!(self, || async {
2246 let mut tx = self.db.write().await?;
2247 if Db::KIND == DbKind::Postgres {
2248 query(
2249 "TRUNCATE stake_table_events, stake_table_events_l1_block, \
2250 epoch_drb_and_root, stake_table_validators",
2251 )
2252 .execute(tx.as_mut())
2253 .await?;
2254 } else {
2255 query("DELETE FROM stake_table_events")
2256 .execute(tx.as_mut())
2257 .await?;
2258 query("DELETE FROM stake_table_events_l1_block")
2259 .execute(tx.as_mut())
2260 .await?;
2261 query("DELETE FROM epoch_drb_and_root")
2262 .execute(tx.as_mut())
2263 .await?;
2264 query("DELETE FROM stake_table_validators")
2265 .execute(tx.as_mut())
2266 .await?;
2267 }
2268 tx.commit().await?;
2269 Ok(())
2270 })
2271 .await
2272 }
2273
2274 async fn store_all_validators(
2275 &self,
2276 epoch: EpochNumber,
2277 all_validators: RegisteredValidatorMap,
2278 ) -> anyhow::Result<()> {
2279 if all_validators.is_empty() {
2280 return Ok(());
2281 }
2282
2283 let epoch_i64 = epoch.u64() as i64;
2284 let serialized_validators = all_validators
2285 .into_iter()
2286 .map(|(address, validator)| {
2287 let validator_json =
2288 serde_json::to_value(&validator).context("serializing validator to json")?;
2289 Ok((address.to_string(), validator_json))
2290 })
2291 .collect::<anyhow::Result<Vec<_>>>()?;
2292
2293 serializable_retry!(self, || async {
2294 let mut tx = self.db.write().await?;
2295
2296 let mut query_builder = QueryBuilder::new(
2297 "INSERT INTO stake_table_validators (epoch, address, validator) ",
2298 );
2299
2300 query_builder.push_values(
2301 serialized_validators.iter().cloned(),
2302 |mut b, (address, validator)| {
2303 b.push_bind(epoch_i64)
2304 .push_bind(address)
2305 .push_bind(validator);
2306 },
2307 );
2308
2309 query_builder
2310 .push(" ON CONFLICT (epoch, address) DO UPDATE SET validator = EXCLUDED.validator");
2311
2312 let query = query_builder.build();
2313
2314 query.execute(tx.as_mut()).await?;
2315
2316 tx.commit().await?;
2317 Ok(())
2318 })
2319 .await
2320 }
2321
2322 async fn load_all_validators(
2323 &self,
2324 epoch: EpochNumber,
2325 offset: u64,
2326 limit: u64,
2327 ) -> anyhow::Result<Vec<RegisteredValidator<PubKey>>> {
2328 let mut tx = self.db.read().await?;
2329
2330 let rows = query(
2334 "SELECT address, validator
2335 FROM stake_table_validators
2336 WHERE epoch = $1
2337 ORDER BY LOWER(address) ASC
2338 LIMIT $2 OFFSET $3",
2339 )
2340 .bind(epoch.u64() as i64)
2341 .bind(limit as i64)
2342 .bind(offset as i64)
2343 .fetch_all(tx.as_mut())
2344 .await?;
2345 rows.into_iter()
2346 .map(|row| {
2347 let validator_json: serde_json::Value = row.try_get("validator")?;
2348 serde_json::from_value::<RegisteredValidator<PubKey>>(validator_json)
2349 .map_err(Into::into)
2350 })
2351 .collect()
2352 }
2353}
2354
2355#[async_trait]
2356impl Provider<SeqTypes, VidCommonRequest> for Persistence {
2357 #[tracing::instrument(skip(self))]
2358 async fn fetch(&self, req: VidCommonRequest) -> Option<VidCommon> {
2359 let mut tx = match self.db.read().await {
2360 Ok(tx) => tx,
2361 Err(err) => {
2362 tracing::warn!("could not open transaction: {err:#}");
2363 return None;
2364 },
2365 };
2366
2367 let bytes = match query_as::<(Vec<u8>,)>(
2368 "SELECT data FROM vid_share2 WHERE payload_hash = $1 LIMIT 1",
2369 )
2370 .bind(req.0.to_string())
2371 .fetch_optional(tx.as_mut())
2372 .await
2373 {
2374 Ok(Some((bytes,))) => bytes,
2375 Ok(None) => return None,
2376 Err(err) => {
2377 tracing::error!("error loading VID share: {err:#}");
2378 return None;
2379 },
2380 };
2381
2382 let share: Proposal<SeqTypes, VidDisperseShare<SeqTypes>> =
2383 match bincode::deserialize(&bytes) {
2384 Ok(share) => share,
2385 Err(err) => {
2386 tracing::warn!("error decoding VID share: {err:#}");
2387 return None;
2388 },
2389 };
2390
2391 match share.data {
2392 VidDisperseShare::V0(vid) => Some(VidCommon::V0(vid.common)),
2393 VidDisperseShare::V1(vid) => Some(VidCommon::V1(vid.common)),
2394 VidDisperseShare::V2(vid) => Some(VidCommon::V2(vid.common)),
2395 }
2396 }
2397}
2398
2399#[async_trait]
2400impl Provider<SeqTypes, PayloadRequest> for Persistence {
2401 #[tracing::instrument(skip(self))]
2402 async fn fetch(&self, req: PayloadRequest) -> Option<Payload> {
2403 let mut tx = match self.db.read().await {
2404 Ok(tx) => tx,
2405 Err(err) => {
2406 tracing::warn!("could not open transaction: {err:#}");
2407 return None;
2408 },
2409 };
2410
2411 let bytes = match query_as::<(Vec<u8>,)>(
2412 "SELECT data FROM da_proposal2 WHERE payload_hash = $1 LIMIT 1",
2413 )
2414 .bind(req.0.to_string())
2415 .fetch_optional(tx.as_mut())
2416 .await
2417 {
2418 Ok(Some((bytes,))) => bytes,
2419 Ok(None) => return None,
2420 Err(err) => {
2421 tracing::warn!("error loading DA proposal: {err:#}");
2422 return None;
2423 },
2424 };
2425
2426 let proposal: Proposal<SeqTypes, DaProposal2<SeqTypes>> = match bincode::deserialize(&bytes)
2427 {
2428 Ok(proposal) => proposal,
2429 Err(err) => {
2430 tracing::error!("error decoding DA proposal: {err:#}");
2431 return None;
2432 },
2433 };
2434
2435 Some(Payload::from_bytes(
2436 &proposal.data.encoded_transactions,
2437 &proposal.data.metadata,
2438 ))
2439 }
2440}
2441
2442#[cfg(test)]
2443mod testing {
2444 use super::{super::qs::testing::TmpDb, *};
2445 use crate::persistence::tests::TestablePersistence;
2446
2447 #[async_trait]
2448 impl TestablePersistence for Persistence {
2449 type Storage = Arc<TmpDb>;
2450
2451 async fn tmp_storage() -> Self::Storage {
2452 Arc::new(TmpDb::init().await)
2453 }
2454
2455 #[allow(refining_impl_trait)]
2456 fn options(db: &Self::Storage) -> Options {
2457 Options(super::super::tmp_options(db))
2458 }
2459 }
2460}
2461
2462#[cfg(test)]
2463mod test {
2464 use std::time::Duration;
2465
2466 use espresso_types::{Leaf, NodeState, PubKey, ValidatedState, traits::NullEventConsumer};
2467 use futures::stream::TryStreamExt;
2468 use hotshot_example_types::node_types::TEST_VERSIONS;
2469 use hotshot_types::{
2470 data::{
2471 EpochNumber, QuorumProposal2, ns_table::parse_ns_table,
2472 vid_disperse::AvidMDisperseShare,
2473 },
2474 message::convert_proposal,
2475 simple_certificate::QuorumCertificate,
2476 traits::{EncodeBytes, signature_key::SignatureKey},
2477 utils::EpochTransitionIndicator,
2478 vid::avidm::{AvidMScheme, init_avidm_param},
2479 };
2480
2481 use super::*;
2482 use crate::{BLSPubKey, persistence::tests::TestablePersistence as _};
2483
2484 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2485 async fn test_quorum_proposals_leaf_hash_migration() {
2486 let leaf = Leaf::genesis(
2488 &ValidatedState::default(),
2489 &NodeState::mock(),
2490 TEST_VERSIONS.test.base,
2491 )
2492 .await;
2493 let privkey = BLSPubKey::generated_from_seed_indexed([0; 32], 1).1;
2494 let signature = PubKey::sign(&privkey, &[]).unwrap();
2495 let mut quorum_proposal: Proposal<SeqTypes, QuorumProposal<SeqTypes>> = Proposal {
2496 data: QuorumProposal {
2497 block_header: leaf.block_header().clone(),
2498 view_number: ViewNumber::genesis(),
2499 justify_qc: QuorumCertificate::genesis(
2500 &ValidatedState::default(),
2501 &NodeState::mock(),
2502 TEST_VERSIONS.test,
2503 )
2504 .await,
2505 upgrade_certificate: None,
2506 proposal_certificate: None,
2507 },
2508 signature,
2509 _pd: Default::default(),
2510 };
2511
2512 let qp1 = quorum_proposal.clone();
2513 quorum_proposal.data.view_number = ViewNumber::new(1);
2514 let qp2 = quorum_proposal;
2515 let qps = [qp1, qp2];
2516
2517 let db = Persistence::tmp_storage().await;
2519 let persistence = Persistence::connect(&db).await;
2520 let mut tx = persistence.db.write().await.unwrap();
2521 let params = qps
2522 .iter()
2523 .map(|qp| {
2524 (
2525 qp.data.view_number.u64() as i64,
2526 bincode::serialize(&qp).unwrap(),
2527 )
2528 })
2529 .collect::<Vec<_>>();
2530 tx.upsert("quorum_proposals", ["view", "data"], ["view"], params)
2531 .await
2532 .unwrap();
2533 tx.commit().await.unwrap();
2534
2535 let persistence = Persistence::connect(&db).await;
2537 let mut tx = persistence.db.read().await.unwrap();
2538 let rows = tx
2539 .fetch("SELECT * FROM quorum_proposals ORDER BY view ASC")
2540 .try_collect::<Vec<_>>()
2541 .await
2542 .unwrap();
2543 assert_eq!(rows.len(), qps.len());
2544 for (row, qp) in rows.into_iter().zip(qps) {
2545 assert_eq!(row.get::<i64, _>("view"), qp.data.view_number.u64() as i64);
2546 assert_eq!(
2547 row.get::<Vec<u8>, _>("data"),
2548 bincode::serialize(&qp).unwrap()
2549 );
2550 assert_eq!(
2551 row.get::<String, _>("leaf_hash"),
2552 Committable::commit(&Leaf::from_quorum_proposal(&qp.data)).to_string()
2553 );
2554 }
2555 }
2556
2557 fn pre_option_validator(
2558 seed: u8,
2559 stake: u64,
2560 ) -> crate::persistence::RegisteredValidatorPreOption {
2561 use std::collections::HashMap;
2562
2563 use alloy::primitives::U256;
2564 use hotshot_types::light_client::StateVerKey;
2565
2566 crate::persistence::RegisteredValidatorPreOption {
2567 account: Address::random(),
2568 stake_table_key: BLSPubKey::generated_from_seed_indexed([seed; 32], 0).0,
2569 state_ver_key: StateVerKey::default(),
2570 stake: U256::from(stake),
2571 commission: 0,
2572 delegators: HashMap::new(),
2573 authenticated: true,
2574 x25519_key: None,
2575 p2p_addr: None,
2576 }
2577 }
2578
2579 async fn insert_legacy_stake_row(
2580 persistence: &Persistence,
2581 epoch: i64,
2582 validator: crate::persistence::RegisteredValidatorPreOption,
2583 ) {
2584 let mut map: IndexMap<Address, crate::persistence::RegisteredValidatorPreOption> =
2585 IndexMap::new();
2586 map.insert(validator.account, validator);
2587 let stake_bytes = bincode::serialize(&map).unwrap();
2588 let mut tx = persistence.db.write().await.unwrap();
2589 tx.execute(
2590 query("INSERT INTO epoch_drb_and_root (epoch, stake) VALUES ($1, $2)")
2591 .bind(epoch)
2592 .bind(&stake_bytes),
2593 )
2594 .await
2595 .unwrap();
2596 tx.commit().await.unwrap();
2597 }
2598
2599 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2600 async fn test_load_stake_legacy_storage() {
2601 let tmp = Persistence::tmp_storage().await;
2602 let persistence = Persistence::connect(&tmp).await;
2603
2604 let v1 = pre_option_validator(1, 100);
2605 let v2 = pre_option_validator(2, 200);
2606 let v1_addr = v1.account;
2607 let v2_addr = v2.account;
2608 insert_legacy_stake_row(&persistence, 1, v1).await;
2609 insert_legacy_stake_row(&persistence, 2, v2).await;
2610
2611 let (loaded1, ..) = persistence
2612 .load_stake(EpochNumber::new(1))
2613 .await
2614 .unwrap()
2615 .unwrap();
2616 assert_eq!(loaded1.len(), 1);
2617 assert!(loaded1.get(&v1_addr).unwrap().stake_table_key.is_some());
2618
2619 let (loaded2, ..) = persistence
2620 .load_stake(EpochNumber::new(2))
2621 .await
2622 .unwrap()
2623 .unwrap();
2624 assert_eq!(loaded2.len(), 1);
2625 assert!(loaded2.get(&v2_addr).unwrap().stake_table_key.is_some());
2626
2627 let latest = persistence.load_latest_stake(10).await.unwrap().unwrap();
2628 assert_eq!(latest.len(), 2);
2629 let epochs: Vec<_> = latest.iter().map(|(e, ..)| *e).collect();
2630 assert!(epochs.contains(&EpochNumber::new(1)));
2631 assert!(epochs.contains(&EpochNumber::new(2)));
2632 }
2633
2634 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2635 async fn test_load_stake_mixed_storage() {
2636 let tmp = Persistence::tmp_storage().await;
2637 let persistence = Persistence::connect(&tmp).await;
2638
2639 let legacy_v = pre_option_validator(3, 300);
2640 let legacy_addr = legacy_v.account;
2641 insert_legacy_stake_row(&persistence, 5, legacy_v).await;
2642
2643 let current_v = espresso_types::v0_3::AuthenticatedValidator::mock();
2644 let current_addr = current_v.account;
2645 let mut current_map = IndexMap::new();
2646 current_map.insert(current_addr, current_v);
2647 persistence
2648 .store_stake(EpochNumber::new(6), current_map, None, None)
2649 .await
2650 .unwrap();
2651
2652 let latest = persistence.load_latest_stake(10).await.unwrap().unwrap();
2653 assert_eq!(latest.len(), 2);
2654 let by_epoch: std::collections::HashMap<_, _> = latest
2655 .into_iter()
2656 .map(|(e, (map, _), _)| (e, map))
2657 .collect();
2658 assert!(
2659 by_epoch
2660 .get(&EpochNumber::new(5))
2661 .unwrap()
2662 .contains_key(&legacy_addr)
2663 );
2664 assert!(
2665 by_epoch
2666 .get(&EpochNumber::new(6))
2667 .unwrap()
2668 .contains_key(¤t_addr)
2669 );
2670 }
2671
2672 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2673 async fn test_store_all_validators_authenticated_and_unauthenticated() {
2674 use std::collections::HashMap;
2675
2676 use alloy::primitives::{Address, U256};
2677 use hotshot_types::light_client::StateVerKey;
2678 use indexmap::IndexMap;
2679
2680 let tmp = Persistence::tmp_storage().await;
2681 let storage = Persistence::connect(&tmp).await;
2682
2683 let authenticated_validator = RegisteredValidator {
2685 account: Address::random(),
2686 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([0u8; 32], 0).0),
2687 state_ver_key: Some(StateVerKey::default()),
2688 stake: U256::from(1000),
2689 commission: 100,
2690 delegators: HashMap::new(),
2691 authenticated: true,
2692 x25519_key: None,
2693 p2p_addr: None,
2694 };
2695
2696 let unauthenticated_validator = RegisteredValidator {
2698 account: Address::random(),
2699 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([0u8; 32], 1).0),
2700 state_ver_key: Some(StateVerKey::default()),
2701 stake: U256::from(2000),
2702 commission: 200,
2703 delegators: HashMap::new(),
2704 authenticated: false,
2705 x25519_key: None,
2706 p2p_addr: None,
2707 };
2708
2709 let mut validators: IndexMap<Address, RegisteredValidator<BLSPubKey>> = IndexMap::new();
2710 validators.insert(
2711 authenticated_validator.account,
2712 authenticated_validator.clone(),
2713 );
2714 validators.insert(
2715 unauthenticated_validator.account,
2716 unauthenticated_validator.clone(),
2717 );
2718
2719 storage
2721 .store_all_validators(EpochNumber::new(1), validators)
2722 .await
2723 .unwrap();
2724
2725 let loaded = storage
2727 .load_all_validators(EpochNumber::new(1), 0, 100)
2728 .await
2729 .unwrap();
2730 assert_eq!(loaded.len(), 2);
2731
2732 let loaded_auth = loaded
2734 .iter()
2735 .find(|v| v.account == authenticated_validator.account)
2736 .unwrap();
2737 assert!(
2738 loaded_auth.authenticated,
2739 "authenticated validator should remain authenticated"
2740 );
2741
2742 let loaded_unauth = loaded
2743 .iter()
2744 .find(|v| v.account == unauthenticated_validator.account)
2745 .unwrap();
2746 assert!(
2747 !loaded_unauth.authenticated,
2748 "unauthenticated validator should remain unauthenticated"
2749 );
2750 }
2751
2752 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2753 async fn test_fetching_providers() {
2754 let tmp = Persistence::tmp_storage().await;
2755 let storage = Persistence::connect(&tmp).await;
2756
2757 let leaf = Leaf2::genesis(
2759 &ValidatedState::default(),
2760 &NodeState::mock(),
2761 TEST_VERSIONS.test.base,
2762 )
2763 .await;
2764 let leaf_payload = leaf.block_payload().unwrap();
2765 let leaf_payload_bytes_arc = leaf_payload.encode();
2766
2767 let avidm_param = init_avidm_param(2).unwrap();
2768 let weights = vec![1u32; 2];
2769
2770 let ns_table = parse_ns_table(
2771 leaf_payload.byte_len().as_usize(),
2772 &leaf_payload.ns_table().encode(),
2773 );
2774 let (payload_commitment, shares) =
2775 AvidMScheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
2776 .unwrap();
2777 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
2778 let vid_share = convert_proposal(
2779 AvidMDisperseShare::<SeqTypes> {
2780 view_number: ViewNumber::new(0),
2781 payload_commitment,
2782 share: shares[0].clone(),
2783 recipient_key: pubkey,
2784 epoch: None,
2785 target_epoch: None,
2786 common: avidm_param.clone(),
2787 }
2788 .to_proposal(&privkey)
2789 .unwrap()
2790 .clone(),
2791 );
2792
2793 let quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
2794 proposal: QuorumProposal2::<SeqTypes> {
2795 block_header: leaf.block_header().clone(),
2796 view_number: leaf.view_number(),
2797 justify_qc: leaf.justify_qc().clone(),
2798 upgrade_certificate: None,
2799 view_change_evidence: None,
2800 next_drb_result: None,
2801 next_epoch_justify_qc: None,
2802 epoch: None,
2803 state_cert: None,
2804 },
2805 };
2806 let quorum_proposal_signature =
2807 BLSPubKey::sign(&privkey, &bincode::serialize(&quorum_proposal).unwrap())
2808 .expect("Failed to sign quorum proposal");
2809 let quorum_proposal = Proposal {
2810 data: quorum_proposal,
2811 signature: quorum_proposal_signature,
2812 _pd: Default::default(),
2813 };
2814
2815 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
2816 .expect("Failed to sign block payload");
2817 let da_proposal = Proposal {
2818 data: DaProposal2::<SeqTypes> {
2819 encoded_transactions: leaf_payload_bytes_arc,
2820 metadata: leaf_payload.ns_table().clone(),
2821 view_number: ViewNumber::new(0),
2822 epoch: None,
2823 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
2824 },
2825 signature: block_payload_signature,
2826 _pd: Default::default(),
2827 };
2828
2829 let mut next_quorum_proposal = quorum_proposal.clone();
2830 next_quorum_proposal.data.proposal.view_number += 1;
2831 next_quorum_proposal.data.proposal.justify_qc.view_number += 1;
2832 next_quorum_proposal
2833 .data
2834 .proposal
2835 .justify_qc
2836 .data
2837 .leaf_commit = Committable::commit(&leaf.clone());
2838
2839 storage
2841 .append_da2(&da_proposal, VidCommitment::V1(payload_commitment))
2842 .await
2843 .unwrap();
2844 storage.append_vid(&vid_share).await.unwrap();
2845 storage
2846 .append_quorum_proposal2(&quorum_proposal)
2847 .await
2848 .unwrap();
2849
2850 storage
2852 .append_quorum_proposal2(&next_quorum_proposal)
2853 .await
2854 .unwrap();
2855
2856 assert_eq!(
2858 Some(VidCommon::V1(avidm_param)),
2859 storage
2860 .fetch(VidCommonRequest(vid_share.data.payload_commitment()))
2861 .await
2862 );
2863 assert_eq!(
2864 leaf_payload,
2865 storage
2866 .fetch(PayloadRequest(vid_share.data.payload_commitment()))
2867 .await
2868 .unwrap()
2869 );
2870 }
2871
2872 async fn test_pruning_helper(pruning_opt: ConsensusPruningOptions) {
2880 let tmp = Persistence::tmp_storage().await;
2881 let mut opt = Persistence::options(&tmp);
2882 opt.consensus_pruning = pruning_opt;
2883 let storage = opt.create().await.unwrap();
2884
2885 let data_view = ViewNumber::new(1);
2886
2887 let leaf = Leaf2::genesis(
2889 &ValidatedState::default(),
2890 &NodeState::mock(),
2891 TEST_VERSIONS.test.base,
2892 )
2893 .await;
2894 let leaf_payload = leaf.block_payload().unwrap();
2895 let leaf_payload_bytes_arc = leaf_payload.encode();
2896
2897 let avidm_param = init_avidm_param(2).unwrap();
2898 let weights = vec![1u32; 2];
2899
2900 let ns_table = parse_ns_table(
2901 leaf_payload.byte_len().as_usize(),
2902 &leaf_payload.ns_table().encode(),
2903 );
2904 let (payload_commitment, shares) =
2905 AvidMScheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
2906 .unwrap();
2907
2908 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
2909 let vid = convert_proposal(
2910 AvidMDisperseShare::<SeqTypes> {
2911 view_number: data_view,
2912 payload_commitment,
2913 share: shares[0].clone(),
2914 recipient_key: pubkey,
2915 epoch: None,
2916 target_epoch: None,
2917 common: avidm_param,
2918 }
2919 .to_proposal(&privkey)
2920 .unwrap()
2921 .clone(),
2922 );
2923 let quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
2924 proposal: QuorumProposal2::<SeqTypes> {
2925 epoch: None,
2926 block_header: leaf.block_header().clone(),
2927 view_number: data_view,
2928 justify_qc: QuorumCertificate2::genesis(
2929 &ValidatedState::default(),
2930 &NodeState::mock(),
2931 TEST_VERSIONS.test,
2932 )
2933 .await,
2934 upgrade_certificate: None,
2935 view_change_evidence: None,
2936 next_drb_result: None,
2937 next_epoch_justify_qc: None,
2938 state_cert: None,
2939 },
2940 };
2941 let quorum_proposal_signature =
2942 BLSPubKey::sign(&privkey, &bincode::serialize(&quorum_proposal).unwrap())
2943 .expect("Failed to sign quorum proposal");
2944 let quorum_proposal = Proposal {
2945 data: quorum_proposal,
2946 signature: quorum_proposal_signature,
2947 _pd: Default::default(),
2948 };
2949
2950 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
2951 .expect("Failed to sign block payload");
2952 let da_proposal = Proposal {
2953 data: DaProposal2::<SeqTypes> {
2954 encoded_transactions: leaf_payload_bytes_arc.clone(),
2955 metadata: leaf_payload.ns_table().clone(),
2956 view_number: data_view,
2957 epoch: Some(EpochNumber::new(0)),
2958 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
2959 },
2960 signature: block_payload_signature,
2961 _pd: Default::default(),
2962 };
2963
2964 tracing::info!(?vid, ?da_proposal, ?quorum_proposal, "append data");
2965 storage.append_vid(&vid).await.unwrap();
2966 storage
2967 .append_da2(&da_proposal, VidCommitment::V1(payload_commitment))
2968 .await
2969 .unwrap();
2970 storage
2971 .append_quorum_proposal2(&quorum_proposal)
2972 .await
2973 .unwrap();
2974
2975 {
2978 let mut tx = storage.db.write().await.unwrap();
2979 tx.upsert(
2980 "state_cert",
2981 ["view", "state_cert"],
2982 ["view"],
2983 [(data_view.u64() as i64, b"state_cert".to_vec())],
2984 )
2985 .await
2986 .unwrap();
2987 tx.upsert(
2988 "decided_cert2",
2989 ["view", "data"],
2990 ["view"],
2991 [(data_view.u64() as i64, b"cert2".to_vec())],
2992 )
2993 .await
2994 .unwrap();
2995 tx.commit().await.unwrap();
2996 }
2997
2998 tracing::info!("decide view 1");
3001 storage
3002 .append_decided_leaves(data_view + 1, [], &NullEventConsumer)
3003 .await
3004 .unwrap();
3005 assert_eq!(
3006 storage.load_vid_share(data_view).await.unwrap().unwrap(),
3007 vid
3008 );
3009 assert_eq!(
3010 storage.load_da_proposal(data_view).await.unwrap().unwrap(),
3011 da_proposal
3012 );
3013 assert_eq!(
3014 storage.load_quorum_proposal(data_view).await.unwrap(),
3015 quorum_proposal
3016 );
3017 assert!(view_row_exists(&storage, "state_cert", data_view).await);
3018 assert!(view_row_exists(&storage, "decided_cert2", data_view).await);
3019
3020 tracing::info!("decide view 2");
3023 storage
3024 .append_decided_leaves(data_view + 2, [], &NullEventConsumer)
3025 .await
3026 .unwrap();
3027 assert!(storage.load_vid_share(data_view).await.unwrap().is_none(),);
3028 assert!(storage.load_da_proposal(data_view).await.unwrap().is_none());
3029 storage.load_quorum_proposal(data_view).await.unwrap_err();
3030 assert!(!view_row_exists(&storage, "state_cert", data_view).await);
3031 assert!(!view_row_exists(&storage, "decided_cert2", data_view).await);
3032 }
3033
3034 async fn view_row_exists(storage: &Persistence, table: &str, view: ViewNumber) -> bool {
3036 storage
3037 .db
3038 .read()
3039 .await
3040 .unwrap()
3041 .fetch_optional(
3042 query(&format!("SELECT view FROM {table} WHERE view = $1")).bind(view.u64() as i64),
3043 )
3044 .await
3045 .unwrap()
3046 .is_some()
3047 }
3048
3049 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3050 async fn test_pruning_minimum_retention() {
3051 test_pruning_helper(ConsensusPruningOptions {
3052 target_usage: 0,
3055 minimum_retention: 1,
3056 target_retention: u64::MAX,
3059 usage_check_interval: Duration::ZERO,
3060 })
3061 .await
3062 }
3063
3064 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3065 async fn test_pruning_target_retention() {
3066 test_pruning_helper(ConsensusPruningOptions {
3067 target_retention: 1,
3068 minimum_retention: 0,
3071 target_usage: u64::MAX,
3074 usage_check_interval: Duration::ZERO,
3075 })
3076 .await
3077 }
3078
3079 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3102 async fn test_store_events_empty() {
3103 let tmp = Persistence::tmp_storage().await;
3104 let mut opt = Persistence::options(&tmp);
3105 let storage = opt.create().await.unwrap();
3106
3107 assert_eq!(storage.load_events(0, 100).await.unwrap(), (None, vec![]));
3108
3109 for i in 1..=2 {
3111 tracing::info!(i, "update l1 height");
3112 storage.store_events(i, vec![]).await.unwrap();
3113 assert_eq!(
3114 storage.load_events(0, 100).await.unwrap(),
3115 (Some(EventsPersistenceRead::UntilL1Block(i)), vec![])
3116 );
3117 }
3118 }
3119
3120 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3123 async fn test_consensus_only_decide_never_rewinds_cursor() {
3124 let tmp = Persistence::tmp_storage().await;
3125 let mut opt = Persistence::options(&tmp);
3126 opt.set_consensus_only();
3127 let storage = opt.create().await.unwrap();
3128
3129 for view in [10, 5] {
3130 storage
3131 .append_decided_leaves(ViewNumber::new(view), [], &NullEventConsumer)
3132 .await
3133 .unwrap();
3134 }
3135 assert_eq!(
3136 storage.load_processed_view().await.unwrap(),
3137 Some(ViewNumber::new(10))
3138 );
3139 }
3140}