1use std::{
2 collections::BTreeMap,
3 future::Future,
4 path::PathBuf,
5 str::FromStr,
6 sync::Arc,
7 time::{Duration, Instant},
8};
9
10use alloy::primitives::Address;
11use anyhow::{Context, bail};
12use async_trait::async_trait;
13use clap::Parser;
14use committable::Committable;
15use derivative::Derivative;
16use derive_more::derive::{From, Into};
17use espresso_types::{
18 AuthenticatedValidatorMap, BackoffParams, BlockMerkleTree, FeeMerkleTree, Header, Leaf, Leaf2,
19 NetworkConfig, Payload, PubKey, Ratio, RegisteredValidatorMap, StakeTableHash, parse_duration,
20 parse_size,
21 traits::{EventsPersistenceRead, MembershipPersistence, StakeTuple},
22 v0::traits::{EventConsumer, PersistenceOptions, SequencerPersistence, StateCatchup},
23 v0_3::{
24 AuthenticatedValidator, EventKey, IndexedStake, RegisteredValidator, RewardAmount,
25 StakeTableEvent,
26 },
27};
28use futures::stream::StreamExt;
29use hotshot::InitializerEpochInfo;
30use hotshot_libp2p_networking::network::behaviours::dht::store::persistent::{
31 DhtPersistentStorage, SerializableRecord,
32};
33use hotshot_new_protocol::message::Certificate2;
34use hotshot_query_service::{
35 data_source::{
36 Transaction as _, VersionedDataSource,
37 storage::{
38 SerializableRetry,
39 pruning::PrunerCfg,
40 sql::{
41 Config, Db, Read, SerializableRetryConfig, SqlStorage, StorageConnectionType,
42 Transaction, Write, include_migrations, query_as, syntax_helpers::MAX_FN,
43 },
44 },
45 },
46 fetching::{
47 Provider,
48 request::{PayloadRequest, VidCommonRequest},
49 },
50 merklized_state::MerklizedState,
51 serializable_retry,
52};
53use hotshot_types::{
54 data::{
55 DaProposal, DaProposal2, EpochNumber, QuorumProposal, QuorumProposalWrapper,
56 QuorumProposalWrapperLegacy, VidCommitment, VidCommon, VidDisperseShare,
57 },
58 drb::{DrbInput, DrbResult},
59 event::{Event, EventType, HotShotAction, LeafInfo},
60 message::{Proposal, convert_proposal},
61 new_protocol::CoordinatorEvent,
62 simple_certificate::{
63 CertificatePair, LightClientStateUpdateCertificateV1, LightClientStateUpdateCertificateV2,
64 NextEpochQuorumCertificate2, QuorumCertificate2, UpgradeCertificate,
65 },
66 traits::{
67 block_contents::{BlockHeader, BlockPayload},
68 metrics::Metrics,
69 },
70 vote::HasViewNumber,
71};
72use indexmap::IndexMap;
73use itertools::Itertools;
74use sqlx::{Executor, QueryBuilder, Row, query};
75
76use crate::{
77 NodeType, RECENT_STAKE_TABLES_LIMIT, SeqTypes, ViewNumber,
78 catchup::SqlStateCatchup,
79 persistence::{migrate_network_config, persistence_metrics::PersistenceMetricsValue},
80};
81
82#[derive(Parser, Clone, Derivative)]
84#[derivative(Debug)]
85pub struct PostgresOptions {
86 #[clap(long, env = "ESPRESSO_NODE_POSTGRES_HOST")]
88 pub(crate) host: Option<String>,
89
90 #[clap(long, env = "ESPRESSO_NODE_POSTGRES_PORT")]
92 pub(crate) port: Option<u16>,
93
94 #[clap(long, env = "ESPRESSO_NODE_POSTGRES_DATABASE")]
96 pub(crate) database: Option<String>,
97
98 #[clap(long, env = "ESPRESSO_NODE_POSTGRES_USER")]
100 pub(crate) user: Option<String>,
101
102 #[clap(long, env = "ESPRESSO_NODE_POSTGRES_PASSWORD")]
104 #[derivative(Debug = "ignore")]
106 pub(crate) password: Option<String>,
107
108 #[clap(long, env = "ESPRESSO_NODE_POSTGRES_USE_TLS")]
110 pub(crate) use_tls: bool,
111
112 #[clap(
119 long,
120 env = "ESPRESSO_NODE_POSTGRES_NO_DEFERRABLE",
121 default_value_t = true
122 )]
123 pub(crate) no_deferrable: bool,
124}
125
126impl Default for PostgresOptions {
127 fn default() -> Self {
128 Self::parse_from(std::iter::empty::<String>())
129 }
130}
131
132#[derive(Parser, Clone, Derivative, Default, From, Into)]
133#[derivative(Debug)]
134pub struct SqliteOptions {
135 #[clap(
138 long,
139 env = "ESPRESSO_NODE_STORAGE_PATH",
140 value_parser = build_sqlite_path
141 )]
142 pub(crate) path: PathBuf,
143}
144
145pub fn build_sqlite_path(path: &str) -> anyhow::Result<PathBuf> {
146 let sub_dir = PathBuf::from_str(path)?.join("sqlite");
147
148 if !sub_dir.exists() {
150 std::fs::create_dir_all(&sub_dir)
151 .with_context(|| format!("failed to create directory: {sub_dir:?}"))?;
152 }
153
154 Ok(sub_dir.join("database"))
155}
156
157#[derive(Parser, Clone, Derivative, From, Into)]
159#[derivative(Debug)]
160pub struct Options {
161 #[cfg(not(feature = "embedded-db"))]
162 #[clap(flatten)]
163 pub(crate) postgres_options: PostgresOptions,
164
165 #[cfg(feature = "embedded-db")]
166 #[clap(flatten)]
167 pub(crate) sqlite_options: SqliteOptions,
168
169 #[derivative(Debug = "ignore")]
182 pub(crate) uri: Option<String>,
183
184 #[clap(long, env = "ESPRESSO_NODE_DATABASE_PRUNE")]
193 pub(crate) prune: bool,
194
195 #[clap(flatten)]
197 pub(crate) pruning: PruningOptions,
198
199 #[clap(flatten)]
201 pub(crate) consensus_pruning: ConsensusPruningOptions,
202
203 #[clap(flatten)]
205 pub(crate) serializable_retry: SerializableRetryOptions,
206
207 #[clap(long, env = "ESPRESSO_NODE_FETCH_RATE_LIMIT")]
209 pub(crate) fetch_rate_limit: Option<usize>,
210
211 #[clap(long, env = "ESPRESSO_NODE_ACTIVE_FETCH_DELAY", value_parser = parse_duration)]
213 pub(crate) active_fetch_delay: Option<Duration>,
214
215 #[clap(long, env = "ESPRESSO_NODE_CHUNK_FETCH_DELAY", value_parser = parse_duration)]
217 pub(crate) chunk_fetch_delay: Option<Duration>,
218
219 #[clap(long, env = "ESPRESSO_NODE_SYNC_STATUS_CHUNK_SIZE")]
222 pub(crate) sync_status_chunk_size: Option<usize>,
223
224 #[clap(long, env = "ESPRESSO_NODE_SYNC_STATUS_TTL", value_parser = parse_duration)]
226 pub(crate) sync_status_ttl: Option<Duration>,
227
228 #[clap(long, env = "ESPRESSO_NODE_PROACTIVE_SCAN_CHUNK_SIZE")]
230 pub(crate) proactive_scan_chunk_size: Option<usize>,
231
232 #[clap(long, env = "ESPRESSO_NODE_PROACTIVE_SCAN_INTERVAL", value_parser = parse_duration)]
234 pub(crate) proactive_scan_interval: Option<Duration>,
235
236 #[clap(long, env = "ESPRESSO_NODE_DISABLE_PROACTIVE_FETCHING")]
238 pub(crate) disable_proactive_fetching: bool,
239
240 #[clap(long, env = "ESPRESSO_NODE_ARCHIVE", conflicts_with = "prune")]
247 pub(crate) archive: bool,
248
249 #[clap(
251 long,
252 env = "ESPRESSO_NODE_LIGHTWEIGHT",
253 default_value_t = false,
254 conflicts_with = "archive"
255 )]
256 pub(crate) lightweight: bool,
257
258 #[clap(long, env = "ESPRESSO_NODE_DATABASE_IDLE_CONNECTION_TIMEOUT", value_parser = parse_duration, default_value = "10m")]
263 pub(crate) idle_connection_timeout: Duration,
264
265 #[clap(long, env = "ESPRESSO_NODE_DATABASE_CONNECTION_TIMEOUT", value_parser = parse_duration, default_value = "30m")]
272 pub(crate) connection_timeout: Duration,
273
274 #[clap(long, env = "ESPRESSO_NODE_DATABASE_SLOW_STATEMENT_THRESHOLD", value_parser = parse_duration, default_value = "1s")]
275 pub(crate) slow_statement_threshold: Duration,
276
277 #[clap(long, env = "ESPRESSO_NODE_DATABASE_STATEMENT_TIMEOUT", value_parser = parse_duration, default_value = "10m")]
282 pub(crate) statement_timeout: Duration,
283
284 #[clap(
290 long,
291 env = "ESPRESSO_NODE_DATABASE_MIN_CONNECTIONS",
292 default_value = "0"
293 )]
294 pub(crate) min_connections: u32,
295
296 #[cfg(not(feature = "embedded-db"))]
299 #[clap(long, env = "ESPRESSO_NODE_DATABASE_QUERY_MIN_CONNECTIONS", default_value = None)]
300 pub(crate) query_min_connections: Option<u32>,
301
302 #[clap(
307 long,
308 env = "ESPRESSO_NODE_DATABASE_MAX_CONNECTIONS",
309 default_value = "25"
310 )]
311 pub(crate) max_connections: u32,
312
313 #[cfg(not(feature = "embedded-db"))]
316 #[clap(long, env = "ESPRESSO_NODE_DATABASE_QUERY_MAX_CONNECTIONS", default_value = None)]
317 pub(crate) query_max_connections: Option<u32>,
318
319 #[clap(skip)]
327 pub(crate) pool: Option<sqlx::Pool<Db>>,
328}
329
330impl Default for Options {
331 fn default() -> Self {
332 Self::parse_from(std::iter::empty::<String>())
333 }
334}
335
336#[cfg(not(feature = "embedded-db"))]
337impl From<PostgresOptions> for Config {
338 fn from(opt: PostgresOptions) -> Self {
339 let mut cfg = Config::default();
340
341 if let Some(host) = opt.host {
342 cfg = cfg.host(host);
343 }
344
345 if let Some(port) = opt.port {
346 cfg = cfg.port(port);
347 }
348
349 if let Some(database) = &opt.database {
350 cfg = cfg.database(database);
351 }
352
353 if let Some(user) = &opt.user {
354 cfg = cfg.user(user);
355 }
356
357 if let Some(password) = &opt.password {
358 cfg = cfg.password(password);
359 }
360
361 if opt.use_tls {
362 cfg = cfg.tls();
363 }
364
365 cfg = cfg.max_connections(20);
366 cfg = cfg.idle_connection_timeout(Duration::from_secs(120));
367 cfg = cfg.connection_timeout(Duration::from_secs(10240));
368 cfg = cfg.slow_statement_threshold(Duration::from_secs(1));
369 cfg = cfg.statement_timeout(Duration::from_secs(600)); hotshot_query_service::data_source::storage::sql::set_no_deferrable_on_read(
372 opt.no_deferrable,
373 );
374
375 cfg
376 }
377}
378
379#[cfg(feature = "embedded-db")]
380impl From<SqliteOptions> for Config {
381 fn from(opt: SqliteOptions) -> Self {
382 let mut cfg = Config::default();
383
384 cfg = cfg.db_path(opt.path);
385
386 cfg = cfg.max_connections(20);
387 cfg = cfg.idle_connection_timeout(Duration::from_secs(120));
388 cfg = cfg.connection_timeout(Duration::from_secs(10240));
389 cfg = cfg.slow_statement_threshold(Duration::from_secs(2));
390 cfg = cfg.statement_timeout(Duration::from_secs(600));
391 cfg
392 }
393}
394
395#[cfg(not(feature = "embedded-db"))]
396impl From<PostgresOptions> for Options {
397 fn from(opt: PostgresOptions) -> Self {
398 Options {
399 postgres_options: opt,
400 max_connections: 20,
401 idle_connection_timeout: Duration::from_secs(120),
402 connection_timeout: Duration::from_secs(10240),
403 slow_statement_threshold: Duration::from_secs(1),
404 statement_timeout: Duration::from_secs(600),
405 ..Default::default()
406 }
407 }
408}
409
410#[cfg(feature = "embedded-db")]
411impl From<SqliteOptions> for Options {
412 fn from(opt: SqliteOptions) -> Self {
413 Options {
414 sqlite_options: opt,
415 max_connections: 5,
416 idle_connection_timeout: Duration::from_secs(120),
417 connection_timeout: Duration::from_secs(10240),
418 slow_statement_threshold: Duration::from_secs(1),
419 uri: None,
420 statement_timeout: Duration::from_secs(600),
421 prune: false,
422 pruning: Default::default(),
423 consensus_pruning: Default::default(),
424 fetch_rate_limit: None,
425 active_fetch_delay: None,
426 chunk_fetch_delay: None,
427 sync_status_chunk_size: None,
428 sync_status_ttl: None,
429 proactive_scan_chunk_size: None,
430 proactive_scan_interval: None,
431 disable_proactive_fetching: false,
432 archive: false,
433 lightweight: false,
434 min_connections: 0,
435 pool: None,
436 serializable_retry: SerializableRetryOptions::default(),
437 }
438 }
439}
440impl TryFrom<&Options> for Config {
441 type Error = anyhow::Error;
442
443 fn try_from(opt: &Options) -> Result<Self, Self::Error> {
444 let mut cfg = match &opt.uri {
445 Some(uri) => uri.parse()?,
446 None => Self::default(),
447 };
448
449 if let Some(pool) = &opt.pool {
450 cfg = cfg.pool(pool.clone());
451 }
452
453 cfg = cfg.max_connections(opt.max_connections);
454 cfg = cfg.idle_connection_timeout(opt.idle_connection_timeout);
455 cfg = cfg.min_connections(opt.min_connections);
456
457 #[cfg(not(feature = "embedded-db"))]
458 {
459 cfg =
460 cfg.query_max_connections(opt.query_max_connections.unwrap_or(opt.max_connections));
461 cfg =
462 cfg.query_min_connections(opt.query_min_connections.unwrap_or(opt.min_connections));
463
464 hotshot_query_service::data_source::storage::sql::set_no_deferrable_on_read(
465 opt.postgres_options.no_deferrable,
466 );
467 }
468
469 cfg = cfg.connection_timeout(opt.connection_timeout);
470 cfg = cfg.slow_statement_threshold(opt.slow_statement_threshold);
471 cfg = cfg.statement_timeout(opt.statement_timeout);
472
473 #[cfg(not(feature = "embedded-db"))]
474 {
475 cfg = cfg.migrations(include_migrations!(
476 "$CARGO_MANIFEST_DIR/api/migrations/postgres"
477 ));
478
479 let pg_options = &opt.postgres_options;
480
481 if let Some(host) = &pg_options.host {
482 cfg = cfg.host(host.clone());
483 }
484
485 if let Some(port) = pg_options.port {
486 cfg = cfg.port(port);
487 }
488
489 if let Some(database) = &pg_options.database {
490 cfg = cfg.database(database);
491 }
492
493 if let Some(user) = &pg_options.user {
494 cfg = cfg.user(user);
495 }
496
497 if let Some(password) = &pg_options.password {
498 cfg = cfg.password(password);
499 }
500
501 if pg_options.use_tls {
502 cfg = cfg.tls();
503 }
504 }
505
506 #[cfg(feature = "embedded-db")]
507 {
508 cfg = cfg.migrations(include_migrations!(
509 "$CARGO_MANIFEST_DIR/api/migrations/sqlite"
510 ));
511
512 cfg = cfg.db_path(opt.sqlite_options.path.clone());
513 }
514
515 if opt.prune {
516 cfg = cfg.pruner_cfg(PrunerCfg::from(opt.pruning))?;
517 }
518 if opt.archive {
519 cfg = cfg.archive();
520 }
521
522 cfg = cfg.serializable_retry(opt.serializable_retry.to_retry_config());
523
524 Ok(cfg)
525 }
526}
527
528#[derive(Parser, Clone, Copy, Debug)]
530pub struct PruningOptions {
531 #[clap(long, env = "ESPRESSO_NODE_PRUNER_PRUNING_THRESHOLD", value_parser = parse_size)]
535 pub(crate) pruning_threshold: Option<u64>,
536
537 #[clap(
540 long,
541 env = "ESPRESSO_NODE_PRUNER_MINIMUM_RETENTION",
542 value_parser = parse_duration,
543 )]
544 pub(crate) minimum_retention: Option<Duration>,
545
546 #[clap(
549 long,
550 env = "ESPRESSO_NODE_PRUNER_STATE_MINIMUM_RETENTION",
551 value_parser = parse_duration,
552 )]
553 state_minimum_retention: Option<Duration>,
554
555 #[clap(
558 long,
559 env = "ESPRESSO_NODE_PRUNER_TARGET_RETENTION",
560 value_parser = parse_duration,
561 )]
562 pub(crate) target_retention: Option<Duration>,
563
564 #[clap(
567 long,
568 env = "ESPRESSO_NODE_PRUNER_STATE_TARGET_RETENTION",
569 value_parser = parse_duration,
570 )]
571 state_target_retention: Option<Duration>,
572
573 #[clap(long, env = "ESPRESSO_NODE_PRUNER_BATCH_SIZE")]
576 pub(crate) batch_size: Option<u64>,
577
578 #[clap(long, env = "ESPRESSO_NODE_PRUNER_MAX_USAGE")]
584 pub(crate) max_usage: Option<u16>,
585
586 #[clap(
588 long,
589 env = "ESPRESSO_NODE_PRUNER_INTERVAL",
590 value_parser = parse_duration,
591 )]
592 pub(crate) interval: Option<Duration>,
593
594 #[clap(long, env = "ESPRESSO_NODE_PRUNER_INCREMENTAL_VACUUM_PAGES")]
598 pub(crate) pages: Option<u64>,
599}
600
601impl Default for PruningOptions {
602 fn default() -> Self {
603 Self::parse_from(std::iter::empty::<String>())
604 }
605}
606
607impl From<PruningOptions> for PrunerCfg {
608 fn from(opt: PruningOptions) -> Self {
609 let mut cfg = PrunerCfg::new();
610 if let Some(threshold) = opt.pruning_threshold {
611 cfg = cfg.with_pruning_threshold(threshold);
612 }
613 if let Some(min) = opt.minimum_retention {
614 cfg = cfg.with_minimum_retention(min);
615 }
616 if let Some(min) = opt.state_minimum_retention {
617 cfg = cfg.with_state_minimum_retention(min);
618 }
619 if let Some(target) = opt.target_retention {
620 cfg = cfg.with_target_retention(target);
621 }
622 if let Some(target) = opt.state_target_retention {
623 cfg = cfg.with_state_target_retention(target);
624 }
625 if let Some(batch) = opt.batch_size {
626 cfg = cfg.with_batch_size(batch);
627 }
628 if let Some(max) = opt.max_usage {
629 cfg = cfg.with_max_usage(max);
630 }
631 if let Some(interval) = opt.interval {
632 cfg = cfg.with_interval(interval);
633 }
634
635 if let Some(pages) = opt.pages {
636 cfg = cfg.with_incremental_vacuum_pages(pages)
637 }
638
639 cfg = cfg.with_state_tables(vec![
640 BlockMerkleTree::state_type().to_string(),
641 FeeMerkleTree::state_type().to_string(),
642 ]);
643
644 cfg
645 }
646}
647
648#[derive(Parser, Clone, Copy, Debug)]
650pub struct ConsensusPruningOptions {
651 #[clap(
667 name = "TARGET_RETENTION",
668 long = "consensus-storage-target-retention",
669 env = "ESPRESSO_NODE_CONSENSUS_STORAGE_TARGET_RETENTION",
670 default_value = "302000"
671 )]
672 pub(crate) target_retention: u64,
673
674 #[clap(
685 name = "MINIMUM_RETENTION",
686 long = "consensus-storage-minimum-retention",
687 env = "ESPRESSO_NODE_CONSENSUS_STORAGE_MINIMUM_RETENTION",
688 default_value = "130000"
689 )]
690 pub(crate) minimum_retention: u64,
691
692 #[clap(
697 name = "TARGET_USAGE",
698 long = "consensus-storage-target-usage",
699 env = "ESPRESSO_NODE_CONSENSUS_STORAGE_TARGET_USAGE",
700 default_value = "1000000000"
701 )]
702 pub(crate) target_usage: u64,
703}
704
705impl Default for ConsensusPruningOptions {
706 fn default() -> Self {
707 Self::parse_from(std::iter::empty::<String>())
708 }
709}
710
711#[derive(Parser, Clone, Copy, Debug)]
714pub struct SerializableRetryOptions {
715 #[clap(
717 long = "serializable-retry-max",
718 env = "ESPRESSO_NODE_SERIALIZABLE_RETRY_MAX",
719 default_value = "100"
720 )]
721 retry_max: u32,
722
723 #[clap(
725 long = "serializable-backoff-base",
726 env = "ESPRESSO_NODE_SERIALIZABLE_BACKOFF_BASE",
727 default_value = "10ms",
728 value_parser = parse_duration
729 )]
730 backoff_base: Duration,
731
732 #[clap(
734 long = "serializable-backoff-max",
735 env = "ESPRESSO_NODE_SERIALIZABLE_BACKOFF_MAX",
736 default_value = "500ms",
737 value_parser = parse_duration
738 )]
739 backoff_max: Duration,
740
741 #[clap(
743 long = "serializable-backoff-factor",
744 env = "ESPRESSO_NODE_SERIALIZABLE_BACKOFF_FACTOR",
745 default_value = "2"
746 )]
747 backoff_factor: u32,
748
749 #[clap(
751 long = "serializable-backoff-jitter",
752 env = "ESPRESSO_NODE_SERIALIZABLE_BACKOFF_JITTER",
753 default_value = "5:10"
754 )]
755 backoff_jitter: Ratio,
756
757 #[clap(
761 long = "serializable-pg-stat-diag",
762 env = "ESPRESSO_NODE_SERIALIZABLE_PG_STAT_DIAG"
763 )]
764 pg_stat_diag: bool,
765}
766
767impl Default for SerializableRetryOptions {
768 fn default() -> Self {
769 Self::parse_from(std::iter::empty::<String>())
770 }
771}
772
773impl SerializableRetryOptions {
774 fn to_retry_config(self) -> SerializableRetryConfig {
776 SerializableRetryConfig::new(
777 self.backoff_base,
778 self.backoff_max,
779 self.backoff_factor,
780 self.backoff_jitter.into(),
781 self.retry_max,
782 self.pg_stat_diag,
783 )
784 }
785}
786
787#[async_trait]
788impl PersistenceOptions for Options {
789 type Persistence = Persistence;
790
791 fn set_view_retention(&mut self, view_retention: u64) {
792 self.consensus_pruning.target_retention = view_retention;
793 self.consensus_pruning.minimum_retention = view_retention;
794 }
795
796 async fn create(&mut self) -> anyhow::Result<Self::Persistence> {
797 let config = (&*self).try_into()?;
798 let persistence = Persistence {
799 db: SqlStorage::connect(config, StorageConnectionType::Sequencer).await?,
800 gc_opt: self.consensus_pruning,
801 internal_metrics: PersistenceMetricsValue::default(),
802 };
803 persistence.migrate_quorum_proposal_leaf_hashes().await?;
804 self.pool = Some(persistence.db.pool());
805
806 Ok(persistence)
807 }
808
809 async fn reset(self) -> anyhow::Result<()> {
810 SqlStorage::connect(
811 Config::try_from(&self)?.reset_schema(),
812 StorageConnectionType::Sequencer,
813 )
814 .await?;
815 Ok(())
816 }
817}
818
819#[derive(Clone, Debug)]
821pub struct Persistence {
822 db: SqlStorage,
823 gc_opt: ConsensusPruningOptions,
824 internal_metrics: PersistenceMetricsValue,
826}
827
828const PG_SERIALIZATION_FAILURE_CODE: &str = "40001";
831
832pub(crate) const DECIDE_GAP_FILL_HORIZON: u64 = 20;
836
837pub(crate) fn within_gap_fill_horizon(view: u64, watermark: u64) -> bool {
841 view.saturating_sub(1) + DECIDE_GAP_FILL_HORIZON > watermark
842}
843
844#[derive(Debug)]
845struct DecidedLeaf {
846 info: LeafInfo<SeqTypes>,
847 cert: CertificatePair<SeqTypes>,
848}
849
850fn decide_events_from_chain(
851 mut chain: Vec<DecidedLeaf>,
852 cert2: Option<Certificate2<SeqTypes>>,
853 deciding_qc: Option<Arc<CertificatePair<SeqTypes>>>,
854) -> Vec<CoordinatorEvent<SeqTypes>> {
855 let split_idx = chain
856 .iter()
857 .position(|leaf| leaf.info.leaf.block_header().version() < versions::NEW_PROTOCOL_VERSION)
858 .unwrap_or(chain.len());
859 let legacy_leaves = chain.split_off(split_idx);
860 let new_leaves = chain;
861
862 let mut events = Vec::with_capacity(2);
863 if !legacy_leaves.is_empty() {
864 let committing_qc = legacy_leaves[0].cert.clone();
865 let deciding_qc = new_leaves
866 .is_empty()
867 .then_some(deciding_qc)
868 .flatten()
869 .filter(|qc| qc.view_number() == committing_qc.view_number() + 1);
870 let view_number = legacy_leaves[0].info.leaf.view_number();
871 let leaf_chain = legacy_leaves
872 .into_iter()
873 .map(|leaf| leaf.info)
874 .collect::<Vec<_>>();
875
876 events.push(CoordinatorEvent::LegacyEvent(Event {
877 view_number,
878 event: EventType::Decide {
879 leaf_chain: Arc::new(leaf_chain),
880 committing_qc: Arc::new(committing_qc),
881 deciding_qc,
882 block_size: None,
883 },
884 }));
885 }
886
887 if new_leaves.is_empty() && cert2.is_some() {
888 tracing::warn!(
889 "decide_events_from_chain called with cert2 but no new-protocol leaves; cert2 will be \
890 dropped"
891 );
892 }
893
894 if !new_leaves.is_empty() {
895 let cert1 = new_leaves[0].cert.qc().clone();
901 let leaf_infos = new_leaves.into_iter().map(|leaf| leaf.info).collect();
902
903 events.push(CoordinatorEvent::NewDecide {
904 leaf_infos,
905 cert1,
906 cert2,
907 });
908 }
909
910 events
911}
912
913impl Persistence {
914 async fn serializable_retry<F, Fut, T>(&self, op: &'static str, f: F) -> anyhow::Result<T>
916 where
917 T: Send,
918 F: Fn() -> Fut + Send + Sync,
919 Fut: Future<Output = anyhow::Result<T>> + Send,
920 {
921 self.db.serializable_retry(op, f).await
922 }
923
924 async fn migrate_quorum_proposal_leaf_hashes(&self) -> anyhow::Result<()> {
931 serializable_retry!(self, || async {
932 let mut tx = self.db.write().await?;
933
934 let mut proposals = tx.fetch("SELECT * FROM quorum_proposals");
935
936 let mut updates = vec![];
937 while let Some(row) = proposals.next().await {
938 let row = row?;
939
940 let hash: Option<String> = row.try_get("leaf_hash")?;
941 if hash.is_none() {
942 let view: i64 = row.try_get("view")?;
943 let data: Vec<u8> = row.try_get("data")?;
944 let proposal: Proposal<SeqTypes, QuorumProposal<SeqTypes>> =
945 bincode::deserialize(&data)?;
946 let leaf = Leaf::from_quorum_proposal(&proposal.data);
947 let leaf_hash = Committable::commit(&leaf);
948 tracing::info!(view, %leaf_hash, "populating quorum proposal leaf hash");
949 updates.push((view, leaf_hash.to_string()));
950 }
951 }
952 drop(proposals);
953
954 tx.upsert("quorum_proposals", ["view", "leaf_hash"], ["view"], updates)
955 .await?;
956
957 tx.commit().await
958 })
959 .await
960 }
961
962 async fn load_processed_view(&self) -> anyhow::Result<Option<ViewNumber>> {
964 Ok(self
965 .db
966 .read()
967 .await?
968 .fetch_optional("SELECT last_processed_view FROM event_stream WHERE id = 1 LIMIT 1")
969 .await?
970 .map(|row| ViewNumber::new(row.get::<i64, _>("last_processed_view") as u64)))
971 }
972
973 async fn generate_decide_events(
974 &self,
975 deciding_qc: Option<Arc<CertificatePair<SeqTypes>>>,
976 consumer: &impl EventConsumer,
977 ) -> anyhow::Result<()> {
978 let mut last_processed_view: Option<i64> = serializable_retry!(self, || async {
979 Ok(self
980 .db
981 .read()
982 .await?
983 .fetch_optional("SELECT last_processed_view FROM event_stream WHERE id = 1 LIMIT 1")
984 .await?
985 .map(|row| row.get("last_processed_view")))
986 })
987 .await?;
988 let mut last_processed_height: Option<u64> = match last_processed_view {
991 Some(view) => {
992 serializable_retry!(self, || async {
993 self.db
994 .read()
995 .await?
996 .fetch_optional(
997 query("SELECT leaf FROM anchor_leaf2 WHERE view = $1").bind(view),
998 )
999 .await?
1000 .map(|row| -> anyhow::Result<u64> {
1001 let leaf_data: Vec<u8> = row.get("leaf");
1002 let leaf = bincode::deserialize::<Leaf2>(&leaf_data)?;
1003 Ok(leaf.block_header().block_number())
1004 })
1005 .transpose()
1006 })
1007 .await?
1008 },
1009 None => None,
1010 };
1011 loop {
1012 let Some((
1023 from_view,
1024 to_view,
1025 leaves,
1026 final_qc,
1027 mut vid_shares,
1028 mut da_proposals,
1029 state_certs,
1030 cert2,
1031 )) = serializable_retry!(self, || async {
1032 let mut tx = self.db.read().await?;
1033
1034 let from_view = match last_processed_view {
1040 Some(v) => v + 1,
1041 None => 0,
1042 };
1043 tracing::debug!(?from_view, "generate decide event");
1044
1045 let (watermark,): (Option<i64>,) = query_as("SELECT max(view) FROM anchor_leaf2")
1047 .fetch_one(tx.as_mut())
1048 .await?;
1049
1050 let mut parent = last_processed_height;
1051 let mut rows = query(
1052 "SELECT leaf, qc, next_epoch_qc FROM anchor_leaf2 WHERE view >= $1 ORDER BY \
1053 view",
1054 )
1055 .bind(from_view)
1056 .fetch(tx.as_mut());
1057 let mut leaves: Vec<(Leaf2, CertificatePair<SeqTypes>)> = vec![];
1058 let mut final_qc = None;
1059 while let Some(row) = rows.next().await {
1060 let row = match row {
1061 Ok(row) => row,
1062 Err(err) => {
1063 if err.as_database_error().is_some_and(|e| {
1064 e.code().as_deref() == Some(PG_SERIALIZATION_FAILURE_CODE)
1065 }) {
1066 drop(rows);
1067 return Err(anyhow::Error::from(err));
1068 }
1069 tracing::warn!("error loading row: {err:#}");
1072 break;
1073 },
1074 };
1075
1076 let leaf_data: Vec<u8> = row.get("leaf");
1077 let leaf = bincode::deserialize::<Leaf2>(&leaf_data)?;
1078 let qc_data: Vec<u8> = row.get("qc");
1079 let qc = bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&qc_data)?;
1080 let next_epoch_qc = match row.get::<Option<Vec<u8>>, _>("next_epoch_qc") {
1081 Some(bytes) => Some(bincode::deserialize::<
1082 NextEpochQuorumCertificate2<SeqTypes>,
1083 >(&bytes)?),
1084 None => None,
1085 };
1086 let height = leaf.block_header().block_number();
1087
1088 if let Some(parent) = parent
1092 && height != parent + 1
1093 {
1094 if !leaves.is_empty() {
1095 tracing::debug!(
1096 height,
1097 parent,
1098 "ending decide event at non-consecutive leaf"
1099 );
1100 break;
1101 }
1102 let row_view = leaf.view_number().u64();
1108 if height > parent + 1
1109 && leaf.block_header().version() >= versions::NEW_PROTOCOL_VERSION
1110 && watermark
1111 .is_some_and(|w| within_gap_fill_horizon(row_view, w as u64))
1112 {
1113 tracing::info!(
1114 height,
1115 parent,
1116 row_view,
1117 ?watermark,
1118 "waiting for gap-fill decide before advancing the decide cursor"
1119 );
1120 break;
1121 }
1122 }
1124 parent = Some(height);
1125 let cert = CertificatePair::new(qc, next_epoch_qc);
1126 final_qc = Some(cert.clone());
1127 leaves.push((leaf, cert));
1128 }
1129 drop(rows);
1130
1131 let Some(final_qc) = final_qc else {
1132 tracing::debug!(from_view, "no new leaves at decide");
1134 return Ok(None);
1135 };
1136
1137 let from_view = leaves[0].0.view_number();
1140 let to_view = leaves[leaves.len() - 1].0.view_number();
1141
1142 let vid_rows = tx
1144 .fetch_all(
1145 query("SELECT view, data FROM vid_share2 where view >= $1 AND view <= $2")
1146 .bind(from_view.u64() as i64)
1147 .bind(to_view.u64() as i64),
1148 )
1149 .await?;
1150 let vid_shares = vid_rows
1151 .into_iter()
1152 .map(|row| {
1153 let view: i64 = row.get("view");
1154 let data: Vec<u8> = row.get("data");
1155 let vid_proposal = bincode::deserialize::<
1156 Proposal<SeqTypes, VidDisperseShare<SeqTypes>>,
1157 >(&data)?;
1158 Ok((view as u64, vid_proposal))
1159 })
1160 .collect::<anyhow::Result<BTreeMap<_, _>>>()?;
1161
1162 let da_rows = tx
1164 .fetch_all(
1165 query(
1166 "SELECT view, data FROM da_proposal2 where view >= $1 AND view <= $2",
1167 )
1168 .bind(from_view.u64() as i64)
1169 .bind(to_view.u64() as i64),
1170 )
1171 .await?;
1172 let da_proposals = da_rows
1173 .into_iter()
1174 .map(|row| {
1175 let view: i64 = row.get("view");
1176 let data: Vec<u8> = row.get("data");
1177 let da_proposal = bincode::deserialize::<
1178 Proposal<SeqTypes, DaProposal2<SeqTypes>>,
1179 >(&data)?;
1180 Ok((view as u64, da_proposal.data))
1181 })
1182 .collect::<anyhow::Result<BTreeMap<_, _>>>()?;
1183
1184 let state_certs = Self::load_state_certs(&mut tx, from_view, to_view)
1186 .await
1187 .with_context(|| {
1188 format!("load_state_certs from_view={from_view:?} to_view={to_view:?}")
1189 })?;
1190
1191 let cert2_row = tx
1192 .fetch_optional(
1193 query("SELECT data FROM decided_cert2 WHERE view = $1")
1194 .bind(to_view.u64() as i64),
1195 )
1196 .await?;
1197 let cert2 = cert2_row
1198 .map(|row| {
1199 let bytes: Vec<u8> = row.get("data");
1200 bincode::deserialize::<Certificate2<SeqTypes>>(&bytes)
1201 .context("deserializing decided cert2")
1202 })
1203 .transpose()?;
1204 drop(tx);
1205 Ok(Some((
1206 from_view,
1207 to_view,
1208 leaves,
1209 final_qc,
1210 vid_shares,
1211 da_proposals,
1212 state_certs,
1213 cert2,
1214 )))
1215 })
1216 .await?
1217 else {
1218 return Ok(());
1219 };
1220
1221 let to_height = leaves
1222 .last()
1223 .map(|(leaf, _)| leaf.block_header().block_number());
1224
1225 let chain = leaves
1227 .into_iter()
1228 .rev()
1230 .map(|(mut leaf, cert)| {
1231 let view = leaf.view_number();
1232
1233 let vid_proposal = vid_shares.remove(&view);
1235 if vid_proposal.is_none() {
1236 tracing::debug!(?view, "VID share not available at decide");
1237 }
1238 let vid_share = vid_proposal.as_ref().map(|proposal| proposal.data.clone());
1239
1240 if let Some(proposal) = da_proposals.remove(&view) {
1242 let payload =
1243 Payload::from_bytes(&proposal.encoded_transactions, &proposal.metadata);
1244 leaf.fill_block_payload_unchecked(payload);
1245 } else if view == ViewNumber::genesis() {
1246 leaf.fill_block_payload_unchecked(Payload::empty().0);
1249 } else {
1250 tracing::debug!(?view, "DA proposal not available at decide");
1251 }
1252
1253 let state_cert = state_certs.get(&view).cloned();
1254
1255 let info = LeafInfo {
1256 leaf,
1257 vid_share,
1258 state_cert,
1259 state: Default::default(),
1262 delta: Default::default(),
1263 };
1264 DecidedLeaf { info, cert }
1265 })
1266 .collect();
1267
1268 tracing::debug!(
1269 ?from_view,
1270 ?to_view,
1271 ?final_qc,
1272 ?chain,
1273 "generating decide event"
1274 );
1275
1276 for event in decide_events_from_chain(chain, cert2, deciding_qc.clone()) {
1277 consumer.handle_event(&event).await?;
1278 }
1279
1280 let from_view_i64 = from_view.u64() as i64;
1281 let to_view_i64 = to_view.u64() as i64;
1282 let serialized_state_certs = state_certs
1283 .into_iter()
1284 .map(|(epoch, cert)| Ok((epoch as i64, bincode::serialize(&cert)?)))
1285 .collect::<anyhow::Result<Vec<(i64, Vec<u8>)>>>()?;
1286
1287 serializable_retry!(self, || async {
1293 let mut tx = self.db.write().await?;
1294 tx.upsert(
1295 "event_stream",
1296 ["id", "last_processed_view"],
1297 ["id"],
1298 [(1i32, to_view_i64)],
1299 )
1300 .await?;
1301
1302 for (epoch, state_cert_bytes) in &serialized_state_certs {
1304 tx.upsert(
1305 "finalized_state_cert",
1306 ["epoch", "state_cert"],
1307 ["epoch"],
1308 [(*epoch, state_cert_bytes.clone())],
1309 )
1310 .await?;
1311 }
1312
1313 tx.execute(
1315 query("DELETE FROM vid_share2 where view >= $1 AND view <= $2")
1316 .bind(from_view_i64)
1317 .bind(to_view_i64),
1318 )
1319 .await?;
1320 tx.execute(
1321 query("DELETE FROM da_proposal2 where view >= $1 AND view <= $2")
1322 .bind(from_view_i64)
1323 .bind(to_view_i64),
1324 )
1325 .await?;
1326 tx.execute(
1327 query("DELETE FROM quorum_proposals2 where view >= $1 AND view <= $2")
1328 .bind(from_view_i64)
1329 .bind(to_view_i64),
1330 )
1331 .await?;
1332 tx.execute(
1333 query("DELETE FROM quorum_certificate2 where view >= $1 AND view <= $2")
1334 .bind(from_view_i64)
1335 .bind(to_view_i64),
1336 )
1337 .await?;
1338 tx.execute(
1339 query("DELETE FROM state_cert where view >= $1 AND view <= $2")
1340 .bind(from_view_i64)
1341 .bind(to_view_i64),
1342 )
1343 .await?;
1344 tx.execute(
1345 query("DELETE FROM decided_cert2 where view >= $1 AND view <= $2")
1346 .bind(from_view_i64)
1347 .bind(to_view_i64),
1348 )
1349 .await?;
1350
1351 tx.execute(
1355 query("DELETE FROM anchor_leaf2 WHERE view >= $1 AND view < $2")
1356 .bind(from_view_i64)
1357 .bind(to_view_i64),
1358 )
1359 .await?;
1360
1361 tx.commit().await?;
1362 Ok(())
1363 })
1364 .await?;
1365 last_processed_view = Some(to_view_i64);
1366 last_processed_height = to_height;
1367 }
1368 }
1369
1370 async fn load_state_certs(
1371 tx: &mut Transaction<Read>,
1372 from_view: ViewNumber,
1373 to_view: ViewNumber,
1374 ) -> anyhow::Result<BTreeMap<u64, LightClientStateUpdateCertificateV2<SeqTypes>>> {
1375 let rows = tx
1376 .fetch_all(
1377 query("SELECT view, state_cert FROM state_cert WHERE view >= $1 AND view <= $2")
1378 .bind(from_view.u64() as i64)
1379 .bind(to_view.u64() as i64),
1380 )
1381 .await?;
1382
1383 let mut result = BTreeMap::new();
1384
1385 for row in rows {
1386 let data: Vec<u8> = row.get("state_cert");
1387
1388 let cert: LightClientStateUpdateCertificateV2<SeqTypes> = bincode::deserialize(&data)
1389 .or_else(|err_v2| {
1390 bincode::deserialize::<LightClientStateUpdateCertificateV1<SeqTypes>>(&data)
1391 .map(Into::into)
1392 .context(format!(
1393 "Failed to deserialize LightClientStateUpdateCertificate: with v1 and v2. \
1394 error: {err_v2}"
1395 ))
1396 })?;
1397
1398 result.insert(cert.epoch.u64(), cert);
1399 }
1400
1401 Ok(result)
1402 }
1403
1404 #[tracing::instrument(skip(self))]
1405 async fn prune(&self, cur_view: ViewNumber) -> anyhow::Result<()> {
1406 serializable_retry!(self, || async {
1407 let mut tx = self.db.write().await?;
1408
1409 prune_to_view(
1411 &mut tx,
1412 cur_view.u64().saturating_sub(self.gc_opt.target_retention),
1413 )
1414 .await?;
1415
1416 #[cfg(feature = "embedded-db")]
1419 let usage_query = format!(
1420 "SELECT sum(pgsize) FROM dbstat WHERE name IN ({})",
1421 PRUNE_TABLES
1422 .iter()
1423 .map(|table| format!("'{table}'"))
1424 .join(",")
1425 );
1426
1427 #[cfg(not(feature = "embedded-db"))]
1428 let usage_query = {
1429 let table_sizes = PRUNE_TABLES
1430 .iter()
1431 .map(|table| format!("pg_table_size('{table}')"))
1432 .join(" + ");
1433 format!("SELECT {table_sizes}")
1434 };
1435
1436 let (usage,): (i64,) = query_as(&usage_query).fetch_one(tx.as_mut()).await?;
1437 tracing::debug!(usage, "consensus storage usage after pruning");
1438
1439 if (usage as u64) > self.gc_opt.target_usage {
1440 tracing::warn!(
1441 usage,
1442 gc_opt = ?self.gc_opt,
1443 "consensus storage is running out of space, pruning to minimum retention"
1444 );
1445 prune_to_view(
1446 &mut tx,
1447 cur_view.u64().saturating_sub(self.gc_opt.minimum_retention),
1448 )
1449 .await?;
1450 }
1451
1452 tx.commit().await
1453 })
1454 .await
1455 }
1456}
1457
1458const PRUNE_TABLES: &[&str] = &[
1459 "anchor_leaf2",
1460 "vid_share2",
1461 "da_proposal2",
1462 "quorum_proposals2",
1463 "quorum_certificate2",
1464 "state_cert",
1465 "decided_cert2",
1466];
1467
1468async fn prune_to_view(tx: &mut Transaction<Write>, view: u64) -> anyhow::Result<()> {
1469 if view == 0 {
1470 return Ok(());
1472 }
1473 tracing::debug!(view, "pruning consensus storage");
1474
1475 for table in PRUNE_TABLES {
1476 let res = query(&format!("DELETE FROM {table} WHERE view < $1"))
1477 .bind(view as i64)
1478 .execute(tx.as_mut())
1479 .await
1480 .context(format!("pruning {table}"))?;
1481 if res.rows_affected() > 0 {
1482 tracing::info!(
1483 "garbage collected {} rows from {table}",
1484 res.rows_affected()
1485 );
1486 }
1487 }
1488
1489 Ok(())
1490}
1491
1492#[async_trait]
1493impl SequencerPersistence for Persistence {
1494 fn into_catchup_provider(
1495 self,
1496 backoff: BackoffParams,
1497 ) -> anyhow::Result<Arc<dyn StateCatchup>> {
1498 Ok(Arc::new(SqlStateCatchup::new(Arc::new(self.db), backoff)))
1499 }
1500
1501 async fn load_config(&self) -> anyhow::Result<Option<NetworkConfig>> {
1502 tracing::info!("loading config from Postgres");
1503
1504 serializable_retry!(self, || async {
1505 let Some(row) = self
1507 .db
1508 .read()
1509 .await?
1510 .fetch_optional("SELECT config FROM network_config ORDER BY id DESC LIMIT 1")
1511 .await?
1512 else {
1513 tracing::info!("config not found");
1514 return Ok(None);
1515 };
1516 let json = row.try_get("config")?;
1517
1518 let json =
1519 migrate_network_config(json).context("migration of network config failed")?;
1520 let config = serde_json::from_value(json).context("malformed config file")?;
1521
1522 Ok(Some(config))
1523 })
1524 .await
1525 }
1526
1527 async fn save_config(&self, cfg: &NetworkConfig) -> anyhow::Result<()> {
1528 tracing::info!("saving config to database");
1529 let json = serde_json::to_value(cfg)?;
1530
1531 serializable_retry!(self, || async {
1532 let mut tx = self.db.write().await?;
1533 tx.execute(query("INSERT INTO network_config (config) VALUES ($1)").bind(json.clone()))
1534 .await?;
1535 tx.commit().await
1536 })
1537 .await
1538 }
1539
1540 async fn persist_decided_leaves(
1541 &self,
1542 _view: ViewNumber,
1543 leaf_chain: impl IntoIterator<Item = (&LeafInfo<SeqTypes>, CertificatePair<SeqTypes>)> + Send,
1544 _deciding_qc: Option<Arc<CertificatePair<SeqTypes>>>,
1545 _consumer: &(impl EventConsumer + 'static),
1546 ) -> anyhow::Result<()> {
1547 let values = leaf_chain
1548 .into_iter()
1549 .map(|(info, cert)| {
1550 let mut leaf = info.leaf.clone();
1555 leaf.unfill_block_payload();
1556
1557 let view = cert.view_number().u64() as i64;
1558 let leaf_bytes = bincode::serialize(&leaf)?;
1559 let qc_bytes = bincode::serialize(cert.qc())?;
1560 let next_epoch_qc_bytes = match cert.next_epoch_qc() {
1561 Some(qc) => Some(bincode::serialize(qc)?),
1562 None => None,
1563 };
1564 Ok((view, leaf_bytes, qc_bytes, next_epoch_qc_bytes))
1565 })
1566 .collect::<anyhow::Result<Vec<_>>>()?;
1567
1568 serializable_retry!(self, || async {
1571 let mut tx = self.db.write().await?;
1572 tx.upsert(
1573 "anchor_leaf2",
1574 ["view", "leaf", "qc", "next_epoch_qc"],
1575 ["view"],
1576 values.clone(),
1577 )
1578 .await?;
1579 tx.commit().await
1580 })
1581 .await?;
1582
1583 Ok(())
1584 }
1585
1586 async fn process_decided_events(
1587 &self,
1588 view: ViewNumber,
1589 deciding_qc: Option<Arc<CertificatePair<SeqTypes>>>,
1590 consumer: &(impl EventConsumer + 'static),
1591 ) -> anyhow::Result<Option<ViewNumber>> {
1592 let now = Instant::now();
1593 self.generate_decide_events(deciding_qc, consumer).await?;
1596
1597 if let Err(err) = self.prune(view).await {
1599 tracing::warn!(?view, "pruning failed: {err:#}");
1600 }
1601 self.internal_metrics
1602 .internal_process_decided_events_duration
1603 .add_point(now.elapsed().as_secs_f64());
1604
1605 self.load_processed_view().await
1606 }
1607
1608 async fn load_latest_acted_view(&self) -> anyhow::Result<Option<ViewNumber>> {
1609 serializable_retry!(self, || async {
1610 Ok(self
1611 .db
1612 .read()
1613 .await?
1614 .fetch_optional(query("SELECT view FROM highest_voted_view WHERE id = 0"))
1615 .await?
1616 .map(|row| {
1617 let view: i64 = row.get("view");
1618 ViewNumber::new(view as u64)
1619 }))
1620 })
1621 .await
1622 }
1623
1624 async fn load_restart_view(&self) -> anyhow::Result<Option<ViewNumber>> {
1625 serializable_retry!(self, || async {
1626 Ok(self
1627 .db
1628 .read()
1629 .await?
1630 .fetch_optional(query("SELECT view FROM restart_view WHERE id = 0"))
1631 .await?
1632 .map(|row| {
1633 let view: i64 = row.get("view");
1634 ViewNumber::new(view as u64)
1635 }))
1636 })
1637 .await
1638 }
1639
1640 async fn load_anchor_leaf(&self) -> anyhow::Result<Option<(Leaf2, CertificatePair<SeqTypes>)>> {
1641 serializable_retry!(self, || async {
1642 let Some(row) = self
1643 .db
1644 .read()
1645 .await?
1646 .fetch_optional(
1647 "SELECT leaf, qc, next_epoch_qc FROM anchor_leaf2 ORDER BY view DESC LIMIT 1",
1648 )
1649 .await?
1650 else {
1651 return Ok(None);
1652 };
1653
1654 let leaf_bytes: Vec<u8> = row.get("leaf");
1655 let leaf2: Leaf2 = bincode::deserialize(&leaf_bytes)?;
1656
1657 let qc_bytes: Vec<u8> = row.get("qc");
1658 let qc2: QuorumCertificate2<SeqTypes> = bincode::deserialize(&qc_bytes)?;
1659
1660 let maybe_next_qc_bytes: Option<Vec<u8>> = row.try_get("next_epoch_qc").ok();
1661 let maybe_next_qc2 = maybe_next_qc_bytes
1662 .and_then(|next_qc_bytes| bincode::deserialize(&next_qc_bytes).ok());
1663
1664 let cert_pair = CertificatePair::new(qc2, maybe_next_qc2);
1665
1666 Ok(Some((leaf2, cert_pair)))
1667 })
1668 .await
1669 }
1670
1671 async fn load_anchor_view(&self) -> anyhow::Result<ViewNumber> {
1672 serializable_retry!(self, || async {
1673 let mut tx = self.db.read().await?;
1674 let (view,) = query_as::<(i64,)>("SELECT coalesce(max(view), 0) FROM anchor_leaf2")
1675 .fetch_one(tx.as_mut())
1676 .await?;
1677 Ok(ViewNumber::new(view as u64))
1678 })
1679 .await
1680 }
1681
1682 async fn load_da_proposal(
1683 &self,
1684 view: ViewNumber,
1685 ) -> anyhow::Result<Option<Proposal<SeqTypes, DaProposal2<SeqTypes>>>> {
1686 serializable_retry!(self, || async {
1687 let result = self
1688 .db
1689 .read()
1690 .await?
1691 .fetch_optional(
1692 query("SELECT data FROM da_proposal2 where view = $1").bind(view.u64() as i64),
1693 )
1694 .await?;
1695
1696 result
1697 .map(|row| {
1698 let bytes: Vec<u8> = row.get("data");
1699 anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?)
1700 })
1701 .transpose()
1702 })
1703 .await
1704 }
1705
1706 async fn load_vid_share(
1707 &self,
1708 view: ViewNumber,
1709 ) -> anyhow::Result<Option<Proposal<SeqTypes, VidDisperseShare<SeqTypes>>>> {
1710 serializable_retry!(self, || async {
1711 let result = self
1712 .db
1713 .read()
1714 .await?
1715 .fetch_optional(
1716 query("SELECT data FROM vid_share2 where view = $1").bind(view.u64() as i64),
1717 )
1718 .await?;
1719
1720 result
1721 .map(|row| {
1722 let bytes: Vec<u8> = row.get("data");
1723 anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?)
1724 })
1725 .transpose()
1726 })
1727 .await
1728 }
1729
1730 async fn load_quorum_proposals(
1731 &self,
1732 ) -> anyhow::Result<BTreeMap<ViewNumber, Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>>>>
1733 {
1734 serializable_retry!(self, || async {
1735 let rows = self
1736 .db
1737 .read()
1738 .await?
1739 .fetch_all("SELECT * FROM quorum_proposals2")
1740 .await?;
1741
1742 Ok(BTreeMap::from_iter(
1743 rows.into_iter()
1744 .map(|row| {
1745 let view: i64 = row.get("view");
1746 let view_number: ViewNumber = ViewNumber::new(view.try_into()?);
1747 let bytes: Vec<u8> = row.get("data");
1748 let proposal: Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>> =
1749 bincode::deserialize(&bytes).or_else(|error| {
1750 bincode::deserialize::<
1751 Proposal<SeqTypes, QuorumProposalWrapperLegacy<SeqTypes>>,
1752 >(&bytes)
1753 .map(convert_proposal)
1754 .inspect_err(|err_v3| {
1755 tracing::warn!(
1756 ?view_number,
1757 %error,
1758 %err_v3,
1759 "ignoring malformed quorum proposal DB row"
1760 );
1761 })
1762 })?;
1763 Ok((view_number, proposal))
1764 })
1765 .collect::<anyhow::Result<Vec<_>>>()?,
1766 ))
1767 })
1768 .await
1769 }
1770
1771 async fn load_quorum_proposal(
1772 &self,
1773 view: ViewNumber,
1774 ) -> anyhow::Result<Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>>> {
1775 serializable_retry!(self, || async {
1776 let mut tx = self.db.read().await?;
1777 let (data,) = query_as::<(Vec<u8>,)>(
1778 "SELECT data FROM quorum_proposals2 WHERE view = $1 LIMIT 1",
1779 )
1780 .bind(view.u64() as i64)
1781 .fetch_one(tx.as_mut())
1782 .await?;
1783 let proposal: Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>> =
1784 bincode::deserialize(&data).or_else(|error| {
1785 bincode::deserialize::<
1786 Proposal<SeqTypes, QuorumProposalWrapperLegacy<SeqTypes>>,
1787 >(&data)
1788 .map(convert_proposal)
1789 .context(format!(
1790 "Failed to deserialize quorum proposal for view {view}. \
1791 error={error}"
1792 ))
1793 })?;
1794 Ok(proposal)
1795 })
1796 .await
1797 }
1798
1799 async fn append_vid(
1800 &self,
1801 proposal: &Proposal<SeqTypes, VidDisperseShare<SeqTypes>>,
1802 ) -> anyhow::Result<()> {
1803 let view = proposal.data.view_number().u64();
1804 let payload_hash = proposal.data.payload_commitment();
1805 let data_bytes = bincode::serialize(proposal).unwrap();
1806
1807 let now = Instant::now();
1808 let res = serializable_retry!(self, || async {
1809 let mut tx = self.db.write().await?;
1810 tx.upsert(
1811 "vid_share2",
1812 ["view", "data", "payload_hash"],
1813 ["view"],
1814 [(view as i64, data_bytes.clone(), payload_hash.to_string())],
1815 )
1816 .await?;
1817 tx.commit().await
1818 })
1819 .await;
1820 self.internal_metrics
1821 .internal_append_vid_duration
1822 .add_point(now.elapsed().as_secs_f64());
1823 res
1824 }
1825
1826 async fn append_da(
1827 &self,
1828 proposal: &Proposal<SeqTypes, DaProposal<SeqTypes>>,
1829 vid_commit: VidCommitment,
1830 ) -> anyhow::Result<()> {
1831 let data = &proposal.data;
1832 let view = data.view_number().u64();
1833 let data_bytes = bincode::serialize(proposal).unwrap();
1834
1835 let now = Instant::now();
1836 let res = serializable_retry!(self, || async {
1837 let mut tx = self.db.write().await?;
1838 tx.upsert(
1839 "da_proposal",
1840 ["view", "data", "payload_hash"],
1841 ["view"],
1842 [(view as i64, data_bytes.clone(), vid_commit.to_string())],
1843 )
1844 .await?;
1845 tx.commit().await
1846 })
1847 .await;
1848 self.internal_metrics
1849 .internal_append_da_duration
1850 .add_point(now.elapsed().as_secs_f64());
1851 res
1852 }
1853
1854 async fn record_action(
1855 &self,
1856 view: ViewNumber,
1857 _epoch: Option<EpochNumber>,
1858 action: HotShotAction,
1859 ) -> anyhow::Result<()> {
1860 if !matches!(action, HotShotAction::Propose | HotShotAction::Vote) {
1862 return Ok(());
1863 }
1864
1865 serializable_retry!(self, || async {
1866 let stmt = format!(
1867 "INSERT INTO highest_voted_view (id, view) VALUES (0, $1)
1868 ON CONFLICT (id) DO UPDATE SET view = {MAX_FN}(highest_voted_view.view, \
1869 excluded.view)"
1870 );
1871
1872 let mut tx = self.db.write().await?;
1873 tx.execute(query(&stmt).bind(view.u64() as i64)).await?;
1874
1875 if matches!(action, HotShotAction::Vote) {
1876 let restart_view = view + 1;
1877 let stmt = format!(
1878 "INSERT INTO restart_view (id, view) VALUES (0, $1)
1879 ON CONFLICT (id) DO UPDATE SET view = {MAX_FN}(restart_view.view, \
1880 excluded.view)"
1881 );
1882 tx.execute(query(&stmt).bind(restart_view.u64() as i64))
1883 .await?;
1884 }
1885
1886 tx.commit().await
1887 })
1888 .await
1889 }
1890
1891 async fn append_quorum_proposal2(
1892 &self,
1893 proposal: &Proposal<SeqTypes, QuorumProposalWrapper<SeqTypes>>,
1894 ) -> anyhow::Result<()> {
1895 let view_number = proposal.data.view_number().u64();
1896
1897 let proposal_bytes = bincode::serialize(&proposal).context("serializing proposal")?;
1898 let leaf_hash = Committable::commit(&Leaf2::from_quorum_proposal(&proposal.data));
1899
1900 let justify_qc = proposal.data.justify_qc();
1902 let justify_qc_bytes = bincode::serialize(&justify_qc).context("serializing QC")?;
1903 let justify_qc_view = justify_qc.view_number.u64() as i64;
1904 let justify_qc_leaf_commit = justify_qc.data.leaf_commit.to_string();
1905
1906 let now = Instant::now();
1907 let res = serializable_retry!(self, || async {
1908 let mut tx = self.db.write().await?;
1909 tx.upsert(
1910 "quorum_proposals2",
1911 ["view", "leaf_hash", "data"],
1912 ["view"],
1913 [(
1914 view_number as i64,
1915 leaf_hash.to_string(),
1916 proposal_bytes.clone(),
1917 )],
1918 )
1919 .await?;
1920 tx.upsert(
1921 "quorum_certificate2",
1922 ["view", "leaf_hash", "data"],
1923 ["view"],
1924 [(
1925 justify_qc_view,
1926 justify_qc_leaf_commit.clone(),
1927 justify_qc_bytes.clone(),
1928 )],
1929 )
1930 .await?;
1931 tx.commit().await
1932 })
1933 .await;
1934 self.internal_metrics
1935 .internal_append_quorum2_duration
1936 .add_point(now.elapsed().as_secs_f64());
1937 res
1938 }
1939
1940 async fn append_cert2(
1941 &self,
1942 view: ViewNumber,
1943 cert2: Certificate2<SeqTypes>,
1944 ) -> anyhow::Result<()> {
1945 let data = bincode::serialize(&cert2).context("serializing cert2")?;
1946 let view_i64 = view.u64() as i64;
1947 serializable_retry!(self, || async {
1948 let mut tx = self.db.write().await?;
1949 tx.upsert(
1950 "decided_cert2",
1951 ["view", "data"],
1952 ["view"],
1953 [(view_i64, data.clone())],
1954 )
1955 .await?;
1956 tx.commit().await
1957 })
1958 .await
1959 }
1960
1961 async fn load_cert2(&self, view: ViewNumber) -> anyhow::Result<Option<Certificate2<SeqTypes>>> {
1962 let row = self
1963 .db
1964 .read()
1965 .await?
1966 .fetch_optional(
1967 query("SELECT data FROM decided_cert2 WHERE view = $1").bind(view.u64() as i64),
1968 )
1969 .await?;
1970 row.map(|row| {
1971 let bytes: Vec<u8> = row.get("data");
1972 bincode::deserialize::<Certificate2<SeqTypes>>(&bytes).context("deserializing cert2")
1973 })
1974 .transpose()
1975 }
1976
1977 async fn append_high_qc2(&self, high_qc: QuorumCertificate2<SeqTypes>) -> anyhow::Result<()> {
1978 let view = high_qc.view_number();
1979 let data = bincode::serialize(&high_qc).context("serializing high_qc2")?;
1980 serializable_retry!(self, || async {
1981 let mut tx = self.db.write().await?;
1982 let stored_view = query("SELECT data FROM high_qc2 WHERE id = true")
1987 .fetch_optional(tx.as_mut())
1988 .await?
1989 .map(|row| {
1990 let bytes: Vec<u8> = row.get("data");
1991 bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&bytes)
1992 .context("deserializing existing high_qc2")
1993 .map(|qc| qc.view_number())
1994 })
1995 .transpose()?;
1996 if stored_view.is_some_and(|stored| stored >= view) {
1997 return Ok(());
1998 }
1999 tx.upsert("high_qc2", ["id", "data"], ["id"], [(true, data.clone())])
2000 .await?;
2001 tx.commit().await
2002 })
2003 .await
2004 }
2005
2006 async fn load_high_qc2(&self) -> anyhow::Result<Option<QuorumCertificate2<SeqTypes>>> {
2007 let row = self
2008 .db
2009 .read()
2010 .await?
2011 .fetch_optional("SELECT data FROM high_qc2 WHERE id = true")
2012 .await?;
2013 row.map(|row| {
2014 let bytes: Vec<u8> = row.get("data");
2015 bincode::deserialize::<QuorumCertificate2<SeqTypes>>(&bytes)
2016 .context("deserializing high_qc2")
2017 })
2018 .transpose()
2019 }
2020
2021 async fn load_upgrade_certificate(
2022 &self,
2023 ) -> anyhow::Result<Option<UpgradeCertificate<SeqTypes>>> {
2024 let result = self
2025 .db
2026 .read()
2027 .await?
2028 .fetch_optional("SELECT * FROM upgrade_certificate where id = true")
2029 .await?;
2030
2031 result
2032 .map(|row| {
2033 let bytes: Vec<u8> = row.get("data");
2034 anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?)
2035 })
2036 .transpose()
2037 }
2038
2039 async fn store_upgrade_certificate(
2040 &self,
2041 decided_upgrade_certificate: Option<UpgradeCertificate<SeqTypes>>,
2042 ) -> anyhow::Result<()> {
2043 let certificate = match decided_upgrade_certificate {
2044 Some(cert) => cert,
2045 None => return Ok(()),
2046 };
2047 let upgrade_certificate_bytes =
2048 bincode::serialize(&certificate).context("serializing upgrade certificate")?;
2049 serializable_retry!(self, || async {
2050 let mut tx = self.db.write().await?;
2051 tx.upsert(
2052 "upgrade_certificate",
2053 ["id", "data"],
2054 ["id"],
2055 [(true, upgrade_certificate_bytes.clone())],
2056 )
2057 .await?;
2058 tx.commit().await
2059 })
2060 .await
2061 }
2062
2063 async fn load_next_epoch_quorum_certificate(
2064 &self,
2065 ) -> anyhow::Result<Option<NextEpochQuorumCertificate2<SeqTypes>>> {
2066 let result = self
2067 .db
2068 .read()
2069 .await?
2070 .fetch_optional("SELECT * FROM next_epoch_quorum_certificate where id = true")
2071 .await?;
2072
2073 result
2074 .map(|row| {
2075 let bytes: Vec<u8> = row.get("data");
2076 anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?)
2077 })
2078 .transpose()
2079 }
2080
2081 async fn append_next_epoch_high_qc2(
2082 &self,
2083 next_epoch_high_qc: NextEpochQuorumCertificate2<SeqTypes>,
2084 ) -> anyhow::Result<()> {
2085 let view = next_epoch_high_qc.view_number();
2086 let data =
2087 bincode::serialize(&next_epoch_high_qc).context("serializing next epoch high_qc2")?;
2088 serializable_retry!(self, || async {
2089 let mut tx = self.db.write().await?;
2090 let stored_view =
2091 query("SELECT data FROM next_epoch_quorum_certificate WHERE id = true")
2092 .fetch_optional(tx.as_mut())
2093 .await?
2094 .map(|row| {
2095 let bytes: Vec<u8> = row.get("data");
2096 bincode::deserialize::<NextEpochQuorumCertificate2<SeqTypes>>(&bytes)
2097 .context("deserializing existing next epoch high_qc2")
2098 .map(|qc| qc.view_number())
2099 })
2100 .transpose()?;
2101 if stored_view.is_some_and(|stored| stored >= view) {
2102 return Ok(());
2103 }
2104 tx.upsert(
2105 "next_epoch_quorum_certificate",
2106 ["id", "data"],
2107 ["id"],
2108 [(true, data.clone())],
2109 )
2110 .await?;
2111 tx.commit().await
2112 })
2113 .await
2114 }
2115
2116 async fn store_eqc(
2117 &self,
2118 high_qc: QuorumCertificate2<SeqTypes>,
2119 next_epoch_high_qc: NextEpochQuorumCertificate2<SeqTypes>,
2120 ) -> anyhow::Result<()> {
2121 let eqc_bytes =
2122 bincode::serialize(&(high_qc, next_epoch_high_qc)).context("serializing eqc")?;
2123 serializable_retry!(self, || async {
2124 let mut tx = self.db.write().await?;
2125 tx.upsert("eqc", ["id", "data"], ["id"], [(true, eqc_bytes.clone())])
2126 .await?;
2127 tx.commit().await
2128 })
2129 .await
2130 }
2131
2132 async fn load_eqc(
2133 &self,
2134 ) -> Option<(
2135 QuorumCertificate2<SeqTypes>,
2136 NextEpochQuorumCertificate2<SeqTypes>,
2137 )> {
2138 let result = self
2139 .db
2140 .read()
2141 .await
2142 .ok()?
2143 .fetch_optional("SELECT * FROM eqc where id = true")
2144 .await
2145 .ok()?;
2146
2147 result
2148 .map(|row| {
2149 let bytes: Vec<u8> = row.get("data");
2150 bincode::deserialize(&bytes)
2151 })
2152 .transpose()
2153 .ok()?
2154 }
2155
2156 async fn append_da2(
2157 &self,
2158 proposal: &Proposal<SeqTypes, DaProposal2<SeqTypes>>,
2159 vid_commit: VidCommitment,
2160 ) -> anyhow::Result<()> {
2161 let data = &proposal.data;
2162 let view = data.view_number().u64();
2163 let data_bytes = bincode::serialize(proposal).unwrap();
2164
2165 let now = Instant::now();
2166 let res = serializable_retry!(self, || async {
2167 let mut tx = self.db.write().await?;
2168 tx.upsert(
2169 "da_proposal2",
2170 ["view", "data", "payload_hash"],
2171 ["view"],
2172 [(view as i64, data_bytes.clone(), vid_commit.to_string())],
2173 )
2174 .await?;
2175 tx.commit().await
2176 })
2177 .await;
2178 self.internal_metrics
2179 .internal_append_da2_duration
2180 .add_point(now.elapsed().as_secs_f64());
2181 res
2182 }
2183
2184 async fn store_drb_result(
2185 &self,
2186 epoch: EpochNumber,
2187 drb_result: DrbResult,
2188 ) -> anyhow::Result<()> {
2189 let epoch_i64 = epoch.u64() as i64;
2190 let drb_result_vec = Vec::from(drb_result);
2191 serializable_retry!(self, || async {
2192 let mut tx = self.db.write().await?;
2193 tx.upsert(
2194 "epoch_drb_and_root",
2195 ["epoch", "drb_result"],
2196 ["epoch"],
2197 [(epoch_i64, drb_result_vec.clone())],
2198 )
2199 .await?;
2200 tx.commit().await
2201 })
2202 .await
2203 }
2204
2205 async fn store_drb_input(&self, drb_input: DrbInput) -> anyhow::Result<()> {
2206 if let Ok(loaded_drb_input) = self.load_drb_input(drb_input.epoch).await {
2207 if loaded_drb_input.difficulty_level != drb_input.difficulty_level {
2208 tracing::error!("Overwriting {loaded_drb_input:?} in storage with {drb_input:?}");
2209 } else if loaded_drb_input.iteration >= drb_input.iteration {
2210 anyhow::bail!(
2211 "DrbInput in storage {:?} is more recent than {:?}, refusing to update",
2212 loaded_drb_input,
2213 drb_input
2214 )
2215 }
2216 }
2217
2218 let drb_epoch_i64 = drb_input.epoch as i64;
2219 let drb_input_bytes = bincode::serialize(&drb_input)
2220 .context("Failed to serialize DrbInput. This is not fatal, but should never happen.")?;
2221
2222 serializable_retry!(self, || async {
2223 let mut tx = self.db.write().await?;
2224 tx.upsert(
2225 "drb",
2226 ["epoch", "drb_input"],
2227 ["epoch"],
2228 [(drb_epoch_i64, drb_input_bytes.clone())],
2229 )
2230 .await?;
2231 tx.commit().await
2232 })
2233 .await
2234 }
2235
2236 async fn load_drb_input(&self, epoch: u64) -> anyhow::Result<DrbInput> {
2237 let row = self
2238 .db
2239 .read()
2240 .await?
2241 .fetch_optional(query("SELECT drb_input FROM drb WHERE epoch = $1").bind(epoch as i64))
2242 .await?;
2243
2244 match row {
2245 None => anyhow::bail!("No DrbInput for epoch {} in storage", epoch),
2246 Some(row) => {
2247 let drb_input_bytes: Vec<u8> = row.try_get("drb_input")?;
2248 let drb_input = bincode::deserialize(&drb_input_bytes)
2249 .context("Failed to deserialize drb_input from storage")?;
2250
2251 Ok(drb_input)
2252 },
2253 }
2254 }
2255
2256 async fn add_state_cert(
2257 &self,
2258 state_cert: LightClientStateUpdateCertificateV2<SeqTypes>,
2259 ) -> anyhow::Result<()> {
2260 let view_number = state_cert.light_client_state.view_number as i64;
2261 let state_cert_bytes = bincode::serialize(&state_cert)
2262 .context("serializing light client state update certificate")?;
2263
2264 serializable_retry!(self, || async {
2265 let mut tx = self.db.write().await?;
2266 tx.upsert(
2267 "state_cert",
2268 ["view", "state_cert"],
2269 ["view"],
2270 [(view_number, state_cert_bytes.clone())],
2271 )
2272 .await?;
2273 tx.commit().await
2274 })
2275 .await
2276 }
2277
2278 async fn load_state_cert(
2279 &self,
2280 ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>> {
2281 let Some(row) = self
2282 .db
2283 .read()
2284 .await?
2285 .fetch_optional(
2286 "SELECT state_cert FROM finalized_state_cert ORDER BY epoch DESC LIMIT 1",
2287 )
2288 .await?
2289 else {
2290 return Ok(None);
2291 };
2292 let bytes: Vec<u8> = row.get("state_cert");
2293
2294 let cert = match bincode::deserialize(&bytes) {
2295 Ok(cert) => cert,
2296 Err(err) => {
2297 tracing::info!(
2298 error = %err,
2299 "Failed to deserialize state certificate with v2. attempting with v1"
2300 );
2301
2302 let v1_cert =
2303 bincode::deserialize::<LightClientStateUpdateCertificateV1<SeqTypes>>(&bytes)
2304 .with_context(|| {
2305 format!("Failed to deserialize using both v1 and v2. error: {err}")
2306 })?;
2307
2308 v1_cert.into()
2309 },
2310 };
2311
2312 Ok(Some(cert))
2313 }
2314
2315 async fn get_state_cert_by_epoch(
2316 &self,
2317 epoch: u64,
2318 ) -> anyhow::Result<Option<LightClientStateUpdateCertificateV2<SeqTypes>>> {
2319 let Some(row) = self
2320 .db
2321 .read()
2322 .await?
2323 .fetch_optional(
2324 query("SELECT state_cert FROM finalized_state_cert WHERE epoch = $1")
2325 .bind(epoch as i64),
2326 )
2327 .await?
2328 else {
2329 return Ok(None);
2330 };
2331 let bytes: Vec<u8> = row.get("state_cert");
2332
2333 let cert = match bincode::deserialize(&bytes) {
2334 Ok(cert) => cert,
2335 Err(err) => {
2336 tracing::info!(
2337 error = %err,
2338 "Failed to deserialize state certificate with v2. attempting with v1"
2339 );
2340
2341 let v1_cert =
2342 bincode::deserialize::<LightClientStateUpdateCertificateV1<SeqTypes>>(&bytes)
2343 .with_context(|| {
2344 format!("Failed to deserialize using both v1 and v2. error: {err}")
2345 })?;
2346
2347 v1_cert.into()
2348 },
2349 };
2350
2351 Ok(Some(cert))
2352 }
2353
2354 async fn insert_state_cert(
2355 &self,
2356 epoch: u64,
2357 cert: LightClientStateUpdateCertificateV2<SeqTypes>,
2358 ) -> anyhow::Result<()> {
2359 let epoch_i64 = epoch as i64;
2360 let bytes = bincode::serialize(&cert)
2361 .with_context(|| format!("Failed to serialize state cert for epoch {epoch}"))?;
2362
2363 serializable_retry!(self, || async {
2364 let mut tx = self.db.write().await?;
2365 tx.upsert(
2366 "finalized_state_cert",
2367 ["epoch", "state_cert"],
2368 ["epoch"],
2369 [(epoch_i64, bytes.clone())],
2370 )
2371 .await?;
2372 tx.commit().await
2373 })
2374 .await
2375 }
2376
2377 async fn load_start_epoch_info(&self) -> anyhow::Result<Vec<InitializerEpochInfo<SeqTypes>>> {
2378 let rows = self
2379 .db
2380 .read()
2381 .await?
2382 .fetch_all(
2383 query("SELECT * from epoch_drb_and_root ORDER BY epoch DESC LIMIT $1")
2384 .bind(RECENT_STAKE_TABLES_LIMIT as i64),
2385 )
2386 .await?;
2387
2388 rows.into_iter()
2390 .rev()
2391 .map(|row| {
2392 let epoch: i64 = row.try_get("epoch")?;
2393 let drb_result: Option<Vec<u8>> = row.try_get("drb_result")?;
2394 let block_header: Option<Vec<u8>> = row.try_get("block_header")?;
2395 if let Some(drb_result) = drb_result {
2396 let drb_result_array = drb_result
2397 .try_into()
2398 .or_else(|_| bail!("invalid drb result"))?;
2399 let block_header: Option<<SeqTypes as NodeType>::BlockHeader> = block_header
2400 .map(|data| bincode::deserialize(&data))
2401 .transpose()?;
2402 Ok(Some(InitializerEpochInfo::<SeqTypes> {
2403 epoch: EpochNumber::new(epoch as u64),
2404 drb_result: drb_result_array,
2405 block_header,
2406 }))
2407 } else {
2408 Ok(None)
2411 }
2412 })
2413 .filter_map(|e| match e {
2414 Err(v) => Some(Err(v)),
2415 Ok(Some(v)) => Some(Ok(v)),
2416 Ok(None) => None,
2417 })
2418 .collect()
2419 }
2420
2421 fn enable_metrics(&mut self, metrics: &dyn Metrics) {
2422 self.internal_metrics = PersistenceMetricsValue::new(metrics);
2423 }
2424}
2425
2426fn deserialize_authenticated_validator_map(
2427 bytes: &[u8],
2428) -> anyhow::Result<AuthenticatedValidatorMap> {
2429 if let Ok(map) = bincode::deserialize::<AuthenticatedValidatorMap>(bytes) {
2430 return Ok(map);
2431 }
2432
2433 if let Ok(pre_schnorr) =
2435 bincode::deserialize::<IndexMap<Address, super::RegisteredValidatorPreSchnorrOption>>(bytes)
2436 {
2437 return pre_schnorr
2438 .into_iter()
2439 .map(|(addr, v)| {
2440 let registered = v.migrate();
2441 let authenticated = AuthenticatedValidator::try_from(registered)?;
2442 Ok((addr, authenticated))
2443 })
2444 .collect();
2445 }
2446
2447 let legacy: IndexMap<Address, super::RegisteredValidatorPreOption> =
2449 bincode::deserialize(bytes).context("deserializing stake table")?;
2450 legacy
2451 .into_iter()
2452 .map(|(addr, v)| {
2453 let registered = v.migrate();
2454 let authenticated = AuthenticatedValidator::try_from(registered)?;
2455 Ok((addr, authenticated))
2456 })
2457 .collect()
2458}
2459
2460#[async_trait]
2461impl MembershipPersistence for Persistence {
2462 async fn load_stake(&self, epoch: EpochNumber) -> anyhow::Result<Option<StakeTuple>> {
2463 let result = self
2464 .db
2465 .read()
2466 .await?
2467 .fetch_optional(
2468 query(
2469 "SELECT stake, block_reward, stake_table_hash FROM epoch_drb_and_root WHERE \
2470 epoch = $1 AND stake IS NOT NULL",
2471 )
2472 .bind(epoch.u64() as i64),
2473 )
2474 .await?;
2475
2476 result
2477 .map(|row| {
2478 let stake_table_bytes: Vec<u8> = row.get("stake");
2479 let reward_bytes: Option<Vec<u8>> = row.get("block_reward");
2480 let stake_table_hash_bytes: Option<Vec<u8>> = row.get("stake_table_hash");
2481 let stake_table = deserialize_authenticated_validator_map(&stake_table_bytes)?;
2482 let reward: Option<RewardAmount> = reward_bytes
2483 .map(|b| bincode::deserialize(&b).context("deserializing block_reward"))
2484 .transpose()?;
2485 let stake_table_hash: Option<StakeTableHash> = stake_table_hash_bytes
2486 .map(|b| bincode::deserialize(&b).context("deserializing stake table hash"))
2487 .transpose()?;
2488
2489 Ok((stake_table, reward, stake_table_hash))
2490 })
2491 .transpose()
2492 }
2493
2494 async fn load_drb_result(&self, epoch: EpochNumber) -> anyhow::Result<Option<DrbResult>> {
2495 let result = self
2496 .db
2497 .read()
2498 .await?
2499 .fetch_optional(
2500 query(
2501 "SELECT drb_result FROM epoch_drb_and_root WHERE epoch = $1 AND drb_result IS \
2502 NOT NULL",
2503 )
2504 .bind(epoch.u64() as i64),
2505 )
2506 .await?;
2507
2508 result
2509 .map(|row| {
2510 let bytes: Vec<u8> = row.get("drb_result");
2511 bytes.try_into().or_else(|_| bail!("invalid drb result"))
2512 })
2513 .transpose()
2514 }
2515
2516 async fn load_epoch_root(&self, epoch: EpochNumber) -> anyhow::Result<Option<Header>> {
2517 let result = self
2518 .db
2519 .read()
2520 .await?
2521 .fetch_optional(
2522 query(
2523 "SELECT block_header FROM epoch_drb_and_root WHERE epoch = $1 AND \
2524 block_header IS NOT NULL",
2525 )
2526 .bind(epoch.u64() as i64),
2527 )
2528 .await?;
2529
2530 result
2531 .map(|row| {
2532 let bytes: Vec<u8> = row.get("block_header");
2533 bincode::deserialize(&bytes).context("deserializing block header")
2534 })
2535 .transpose()
2536 }
2537
2538 async fn store_epoch_root(
2539 &self,
2540 epoch: EpochNumber,
2541 block_header: Header,
2542 ) -> anyhow::Result<()> {
2543 let epoch_i64 = epoch.u64() as i64;
2544 let block_header_bytes =
2545 bincode::serialize(&block_header).context("serializing block header")?;
2546
2547 serializable_retry!(self, || async {
2548 let mut tx = self.db.write().await?;
2549 tx.upsert(
2550 "epoch_drb_and_root",
2551 ["epoch", "block_header"],
2552 ["epoch"],
2553 [(epoch_i64, block_header_bytes.clone())],
2554 )
2555 .await?;
2556 tx.commit().await
2557 })
2558 .await
2559 }
2560
2561 async fn load_latest_stake(&self, limit: u64) -> anyhow::Result<Option<Vec<IndexedStake>>> {
2562 let mut tx = self.db.read().await?;
2563
2564 let rows = match query_as::<(i64, Vec<u8>, Option<Vec<u8>>, Option<Vec<u8>>)>(
2565 "SELECT epoch, stake, block_reward, stake_table_hash FROM epoch_drb_and_root WHERE \
2566 stake is NOT NULL ORDER BY epoch DESC LIMIT $1",
2567 )
2568 .bind(limit as i64)
2569 .fetch_all(tx.as_mut())
2570 .await
2571 {
2572 Ok(bytes) => bytes,
2573 Err(err) => {
2574 tracing::error!("error loading stake tables: {err:#}");
2575 bail!("{err:#}");
2576 },
2577 };
2578
2579 let stakes: anyhow::Result<Vec<IndexedStake>> = rows
2580 .into_iter()
2581 .map(
2582 |(id, stake_bytes, reward_bytes_opt, stake_table_hash_bytes_opt)| {
2583 let stake_table = deserialize_authenticated_validator_map(&stake_bytes)?;
2584
2585 let block_reward: Option<RewardAmount> = reward_bytes_opt
2586 .map(|b| bincode::deserialize(&b).context("deserializing block_reward"))
2587 .transpose()?;
2588
2589 let stake_table_hash: Option<StakeTableHash> = stake_table_hash_bytes_opt
2590 .map(|b| bincode::deserialize(&b).context("deserializing stake table hash"))
2591 .transpose()?;
2592
2593 Ok((
2594 EpochNumber::new(id as u64),
2595 (stake_table, block_reward),
2596 stake_table_hash,
2597 ))
2598 },
2599 )
2600 .collect();
2601
2602 Ok(Some(stakes?))
2603 }
2604
2605 async fn store_stake(
2606 &self,
2607 epoch: EpochNumber,
2608 stake: AuthenticatedValidatorMap,
2609 block_reward: Option<RewardAmount>,
2610 stake_table_hash: Option<StakeTableHash>,
2611 ) -> anyhow::Result<()> {
2612 let epoch_i64 = epoch.u64() as i64;
2613 let stake_table_bytes = bincode::serialize(&stake).context("serializing stake table")?;
2614 let reward_bytes = block_reward
2615 .map(|r| bincode::serialize(&r).context("serializing block reward"))
2616 .transpose()?;
2617 let stake_table_hash_bytes = stake_table_hash
2618 .map(|h| bincode::serialize(&h).context("serializing stake table hash"))
2619 .transpose()?;
2620 serializable_retry!(self, || async {
2621 let mut tx = self.db.write().await?;
2622 tx.upsert(
2623 "epoch_drb_and_root",
2624 ["epoch", "stake", "block_reward", "stake_table_hash"],
2625 ["epoch"],
2626 [(
2627 epoch_i64,
2628 stake_table_bytes.clone(),
2629 reward_bytes.clone(),
2630 stake_table_hash_bytes.clone(),
2631 )],
2632 )
2633 .await?;
2634 tx.commit().await
2635 })
2636 .await
2637 }
2638
2639 async fn store_events(
2640 &self,
2641 l1_finalized: u64,
2642 events: Vec<(EventKey, StakeTableEvent)>,
2643 ) -> anyhow::Result<()> {
2644 let l1_finalized_i64: i64 = l1_finalized.try_into()?;
2645 let serialized_events = events
2646 .into_iter()
2647 .map(|((block_number, index), event)| {
2648 Ok((
2649 i64::try_from(block_number)?,
2650 i64::try_from(index)?,
2651 serde_json::to_value(event).context("l1 event to value")?,
2652 ))
2653 })
2654 .collect::<anyhow::Result<Vec<_>>>()?;
2655
2656 serializable_retry!(self, || async {
2657 let mut tx = self.db.write().await?;
2658
2659 let last_processed_l1_block = query_as::<(i64,)>(
2661 "SELECT last_l1_block FROM stake_table_events_l1_block where id = 0",
2662 )
2663 .fetch_optional(tx.as_mut())
2664 .await?
2665 .map(|(l1,)| l1);
2666
2667 tracing::debug!("last l1 finalizes in database = {last_processed_l1_block:?}");
2668
2669 let serialized_events_len = serialized_events.len();
2671 if last_processed_l1_block > Some(l1_finalized_i64) {
2672 tracing::debug!(
2673 ?last_processed_l1_block,
2674 l1_finalized,
2675 serialized_events_len,
2676 "last l1 finalized stored is already higher"
2677 );
2678 return Ok(());
2679 }
2680
2681 if !serialized_events.is_empty() {
2682 let mut query_builder: sqlx::QueryBuilder<Db> = sqlx::QueryBuilder::new(
2683 "INSERT INTO stake_table_events (l1_block, log_index, event) ",
2684 );
2685
2686 query_builder.push_values(
2687 serialized_events.iter().cloned(),
2688 |mut b, (l1_block, log_index, event)| {
2689 b.push_bind(l1_block).push_bind(log_index).push_bind(event);
2690 },
2691 );
2692
2693 query_builder.push(" ON CONFLICT DO NOTHING");
2694 let query = query_builder.build();
2695
2696 query.execute(tx.as_mut()).await?;
2697 }
2698
2699 tx.upsert(
2701 "stake_table_events_l1_block",
2702 ["id", "last_l1_block"],
2703 ["id"],
2704 [(0_i32, l1_finalized_i64)],
2705 )
2706 .await?;
2707
2708 tx.commit().await?;
2709
2710 Ok(())
2711 })
2712 .await
2713 }
2714
2715 async fn load_events(
2726 &self,
2727 from_l1_block: u64,
2728 to_l1_block: u64,
2729 ) -> anyhow::Result<(
2730 Option<EventsPersistenceRead>,
2731 Vec<(EventKey, StakeTableEvent)>,
2732 )> {
2733 let mut tx = self.db.read().await?;
2734
2735 let res = query_as::<(i64,)>(
2737 "SELECT last_l1_block FROM stake_table_events_l1_block where id = 0",
2738 )
2739 .fetch_optional(tx.as_mut())
2740 .await?;
2741
2742 let Some((last_processed_l1_block,)) = res else {
2743 return Ok((None, Vec::new()));
2745 };
2746
2747 let to_l1_block = to_l1_block.try_into()?;
2751 let query_l1_block = if last_processed_l1_block > to_l1_block {
2752 to_l1_block
2753 } else {
2754 last_processed_l1_block
2755 };
2756
2757 let rows = query(
2758 "SELECT l1_block, log_index, event FROM stake_table_events WHERE $1 <= l1_block AND \
2759 l1_block <= $2 ORDER BY l1_block ASC, log_index ASC",
2760 )
2761 .bind(i64::try_from(from_l1_block)?)
2762 .bind(query_l1_block)
2763 .fetch_all(tx.as_mut())
2764 .await?;
2765
2766 let events = rows
2767 .into_iter()
2768 .map(|row| {
2769 let l1_block: i64 = row.try_get("l1_block")?;
2770 let log_index: i64 = row.try_get("log_index")?;
2771 let event = serde_json::from_value(row.try_get("event")?)?;
2772
2773 Ok(((l1_block.try_into()?, log_index.try_into()?), event))
2774 })
2775 .collect::<anyhow::Result<Vec<_>>>()?;
2776
2777 if query_l1_block == to_l1_block {
2781 Ok((Some(EventsPersistenceRead::Complete), events))
2782 } else {
2783 Ok((
2784 Some(EventsPersistenceRead::UntilL1Block(
2785 query_l1_block.try_into()?,
2786 )),
2787 events,
2788 ))
2789 }
2790 }
2791
2792 async fn delete_stake_tables(&self) -> anyhow::Result<()> {
2793 serializable_retry!(self, || async {
2794 let mut tx = self.db.write().await?;
2795 #[cfg(not(feature = "embedded-db"))]
2796 query(
2797 "TRUNCATE stake_table_events, stake_table_events_l1_block, epoch_drb_and_root, \
2798 stake_table_validators",
2799 )
2800 .execute(tx.as_mut())
2801 .await?;
2802 #[cfg(feature = "embedded-db")]
2803 {
2804 query("DELETE FROM stake_table_events")
2805 .execute(tx.as_mut())
2806 .await?;
2807 query("DELETE FROM stake_table_events_l1_block")
2808 .execute(tx.as_mut())
2809 .await?;
2810 query("DELETE FROM epoch_drb_and_root")
2811 .execute(tx.as_mut())
2812 .await?;
2813 query("DELETE FROM stake_table_validators")
2814 .execute(tx.as_mut())
2815 .await?;
2816 }
2817 tx.commit().await?;
2818 Ok(())
2819 })
2820 .await
2821 }
2822
2823 async fn store_all_validators(
2824 &self,
2825 epoch: EpochNumber,
2826 all_validators: RegisteredValidatorMap,
2827 ) -> anyhow::Result<()> {
2828 if all_validators.is_empty() {
2829 return Ok(());
2830 }
2831
2832 let epoch_i64 = epoch.u64() as i64;
2833 let serialized_validators = all_validators
2834 .into_iter()
2835 .map(|(address, validator)| {
2836 let validator_json =
2837 serde_json::to_value(&validator).context("serializing validator to json")?;
2838 Ok((address.to_string(), validator_json))
2839 })
2840 .collect::<anyhow::Result<Vec<_>>>()?;
2841
2842 serializable_retry!(self, || async {
2843 let mut tx = self.db.write().await?;
2844
2845 let mut query_builder = QueryBuilder::new(
2846 "INSERT INTO stake_table_validators (epoch, address, validator) ",
2847 );
2848
2849 query_builder.push_values(
2850 serialized_validators.iter().cloned(),
2851 |mut b, (address, validator)| {
2852 b.push_bind(epoch_i64)
2853 .push_bind(address)
2854 .push_bind(validator);
2855 },
2856 );
2857
2858 query_builder
2859 .push(" ON CONFLICT (epoch, address) DO UPDATE SET validator = EXCLUDED.validator");
2860
2861 let query = query_builder.build();
2862
2863 query.execute(tx.as_mut()).await?;
2864
2865 tx.commit().await?;
2866 Ok(())
2867 })
2868 .await
2869 }
2870
2871 async fn load_all_validators(
2872 &self,
2873 epoch: EpochNumber,
2874 offset: u64,
2875 limit: u64,
2876 ) -> anyhow::Result<Vec<RegisteredValidator<PubKey>>> {
2877 let mut tx = self.db.read().await?;
2878
2879 let rows = query(
2883 "SELECT address, validator
2884 FROM stake_table_validators
2885 WHERE epoch = $1
2886 ORDER BY LOWER(address) ASC
2887 LIMIT $2 OFFSET $3",
2888 )
2889 .bind(epoch.u64() as i64)
2890 .bind(limit as i64)
2891 .bind(offset as i64)
2892 .fetch_all(tx.as_mut())
2893 .await?;
2894 rows.into_iter()
2895 .map(|row| {
2896 let validator_json: serde_json::Value = row.try_get("validator")?;
2897 serde_json::from_value::<RegisteredValidator<PubKey>>(validator_json)
2898 .map_err(Into::into)
2899 })
2900 .collect()
2901 }
2902}
2903
2904#[async_trait]
2905impl DhtPersistentStorage for Persistence {
2906 async fn save(&self, records: Vec<SerializableRecord>) -> anyhow::Result<()> {
2912 let to_save =
2914 bincode::serialize(&records).with_context(|| "failed to serialize records")?;
2915
2916 let stmt = "INSERT INTO libp2p_dht (id, serialized_records) VALUES (0, $1) ON CONFLICT \
2918 (id) DO UPDATE SET serialized_records = $1";
2919
2920 serializable_retry!(self, || async {
2921 let mut tx = self
2923 .db
2924 .write()
2925 .await
2926 .with_context(|| "failed to start an atomic DB transaction")?;
2927 tx.execute(query(stmt).bind(to_save.clone()))
2928 .await
2929 .with_context(|| "failed to execute DB query")?;
2930
2931 tx.commit().await.with_context(|| "failed to commit to DB")
2933 })
2934 .await
2935 }
2936
2937 async fn load(&self) -> anyhow::Result<Vec<SerializableRecord>> {
2943 let result = self
2945 .db
2946 .read()
2947 .await
2948 .with_context(|| "failed to start a DB read transaction")?
2949 .fetch_one("SELECT * FROM libp2p_dht where id = 0")
2950 .await
2951 .with_context(|| "failed to fetch from DB")?;
2952
2953 let serialied_records: Vec<u8> = result.get("serialized_records");
2955
2956 let records: Vec<SerializableRecord> = bincode::deserialize(&serialied_records)
2958 .with_context(|| "Failed to deserialize records")?;
2959
2960 Ok(records)
2961 }
2962}
2963
2964#[async_trait]
2965impl Provider<SeqTypes, VidCommonRequest> for Persistence {
2966 #[tracing::instrument(skip(self))]
2967 async fn fetch(&self, req: VidCommonRequest) -> Option<VidCommon> {
2968 let mut tx = match self.db.read().await {
2969 Ok(tx) => tx,
2970 Err(err) => {
2971 tracing::warn!("could not open transaction: {err:#}");
2972 return None;
2973 },
2974 };
2975
2976 let bytes = match query_as::<(Vec<u8>,)>(
2977 "SELECT data FROM vid_share2 WHERE payload_hash = $1 LIMIT 1",
2978 )
2979 .bind(req.0.to_string())
2980 .fetch_optional(tx.as_mut())
2981 .await
2982 {
2983 Ok(Some((bytes,))) => bytes,
2984 Ok(None) => return None,
2985 Err(err) => {
2986 tracing::error!("error loading VID share: {err:#}");
2987 return None;
2988 },
2989 };
2990
2991 let share: Proposal<SeqTypes, VidDisperseShare<SeqTypes>> =
2992 match bincode::deserialize(&bytes) {
2993 Ok(share) => share,
2994 Err(err) => {
2995 tracing::warn!("error decoding VID share: {err:#}");
2996 return None;
2997 },
2998 };
2999
3000 match share.data {
3001 VidDisperseShare::V0(vid) => Some(VidCommon::V0(vid.common)),
3002 VidDisperseShare::V1(vid) => Some(VidCommon::V1(vid.common)),
3003 VidDisperseShare::V2(vid) => Some(VidCommon::V2(vid.common)),
3004 }
3005 }
3006}
3007
3008#[async_trait]
3009impl Provider<SeqTypes, PayloadRequest> for Persistence {
3010 #[tracing::instrument(skip(self))]
3011 async fn fetch(&self, req: PayloadRequest) -> Option<Payload> {
3012 let mut tx = match self.db.read().await {
3013 Ok(tx) => tx,
3014 Err(err) => {
3015 tracing::warn!("could not open transaction: {err:#}");
3016 return None;
3017 },
3018 };
3019
3020 let bytes = match query_as::<(Vec<u8>,)>(
3021 "SELECT data FROM da_proposal2 WHERE payload_hash = $1 LIMIT 1",
3022 )
3023 .bind(req.0.to_string())
3024 .fetch_optional(tx.as_mut())
3025 .await
3026 {
3027 Ok(Some((bytes,))) => bytes,
3028 Ok(None) => return None,
3029 Err(err) => {
3030 tracing::warn!("error loading DA proposal: {err:#}");
3031 return None;
3032 },
3033 };
3034
3035 let proposal: Proposal<SeqTypes, DaProposal2<SeqTypes>> = match bincode::deserialize(&bytes)
3036 {
3037 Ok(proposal) => proposal,
3038 Err(err) => {
3039 tracing::error!("error decoding DA proposal: {err:#}");
3040 return None;
3041 },
3042 };
3043
3044 Some(Payload::from_bytes(
3045 &proposal.data.encoded_transactions,
3046 &proposal.data.metadata,
3047 ))
3048 }
3049}
3050
3051#[cfg(test)]
3052mod testing {
3053 use hotshot_query_service::data_source::storage::sql::testing::TmpDb;
3054
3055 use super::*;
3056 use crate::persistence::tests::TestablePersistence;
3057
3058 #[async_trait]
3059 impl TestablePersistence for Persistence {
3060 type Storage = Arc<TmpDb>;
3061
3062 async fn tmp_storage() -> Self::Storage {
3063 Arc::new(TmpDb::init().await)
3064 }
3065
3066 #[allow(refining_impl_trait)]
3067 fn options(db: &Self::Storage) -> Options {
3068 #[cfg(not(feature = "embedded-db"))]
3069 {
3070 PostgresOptions {
3071 port: Some(db.port()),
3072 host: Some(db.host()),
3073 user: Some("postgres".into()),
3074 password: Some("password".into()),
3075 ..Default::default()
3076 }
3077 .into()
3078 }
3079
3080 #[cfg(feature = "embedded-db")]
3081 {
3082 SqliteOptions { path: db.path() }.into()
3083 }
3084 }
3085 }
3086}
3087
3088#[cfg(test)]
3089mod test {
3090 use espresso_types::{Leaf, NodeState, ValidatedState, traits::NullEventConsumer};
3091 use futures::stream::TryStreamExt;
3092 use hotshot_example_types::node_types::TEST_VERSIONS;
3093 use hotshot_types::{
3094 data::{
3095 EpochNumber, QuorumProposal2, ns_table::parse_ns_table,
3096 vid_disperse::AvidMDisperseShare,
3097 },
3098 message::convert_proposal,
3099 simple_certificate::QuorumCertificate,
3100 traits::{EncodeBytes, signature_key::SignatureKey},
3101 utils::EpochTransitionIndicator,
3102 vid::avidm::{AvidMScheme, init_avidm_param},
3103 };
3104
3105 use super::*;
3106 use crate::{BLSPubKey, PubKey, persistence::tests::TestablePersistence as _};
3107
3108 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3109 async fn test_quorum_proposals_leaf_hash_migration() {
3110 let leaf: Leaf2 = Leaf::genesis(
3112 &ValidatedState::default(),
3113 &NodeState::mock(),
3114 TEST_VERSIONS.test.base,
3115 )
3116 .await
3117 .into();
3118 let privkey = BLSPubKey::generated_from_seed_indexed([0; 32], 1).1;
3119 let signature = PubKey::sign(&privkey, &[]).unwrap();
3120 let mut quorum_proposal = Proposal {
3121 data: QuorumProposal2::<SeqTypes> {
3122 epoch: None,
3123 block_header: leaf.block_header().clone(),
3124 view_number: ViewNumber::genesis(),
3125 justify_qc: QuorumCertificate::genesis(
3126 &ValidatedState::default(),
3127 &NodeState::mock(),
3128 TEST_VERSIONS.test,
3129 )
3130 .await
3131 .to_qc2(),
3132 upgrade_certificate: None,
3133 view_change_evidence: None,
3134 next_drb_result: None,
3135 next_epoch_justify_qc: None,
3136 state_cert: None,
3137 },
3138 signature,
3139 _pd: Default::default(),
3140 };
3141
3142 let qp1: Proposal<SeqTypes, QuorumProposal<SeqTypes>> =
3143 convert_proposal(quorum_proposal.clone());
3144
3145 quorum_proposal.data.view_number = ViewNumber::new(1);
3146
3147 let qp2: Proposal<SeqTypes, QuorumProposal<SeqTypes>> =
3148 convert_proposal(quorum_proposal.clone());
3149 let qps = [qp1, qp2];
3150
3151 let db = Persistence::tmp_storage().await;
3153 let persistence = Persistence::connect(&db).await;
3154 let mut tx = persistence.db.write().await.unwrap();
3155 let params = qps
3156 .iter()
3157 .map(|qp| {
3158 (
3159 qp.data.view_number.u64() as i64,
3160 bincode::serialize(&qp).unwrap(),
3161 )
3162 })
3163 .collect::<Vec<_>>();
3164 tx.upsert("quorum_proposals", ["view", "data"], ["view"], params)
3165 .await
3166 .unwrap();
3167 tx.commit().await.unwrap();
3168
3169 let persistence = Persistence::connect(&db).await;
3171 let mut tx = persistence.db.read().await.unwrap();
3172 let rows = tx
3173 .fetch("SELECT * FROM quorum_proposals ORDER BY view ASC")
3174 .try_collect::<Vec<_>>()
3175 .await
3176 .unwrap();
3177 assert_eq!(rows.len(), qps.len());
3178 for (row, qp) in rows.into_iter().zip(qps) {
3179 assert_eq!(row.get::<i64, _>("view"), qp.data.view_number.u64() as i64);
3180 assert_eq!(
3181 row.get::<Vec<u8>, _>("data"),
3182 bincode::serialize(&qp).unwrap()
3183 );
3184 assert_eq!(
3185 row.get::<String, _>("leaf_hash"),
3186 Committable::commit(&Leaf::from_quorum_proposal(&qp.data)).to_string()
3187 );
3188 }
3189 }
3190
3191 fn pre_option_validator(seed: u8, stake: u64) -> super::super::RegisteredValidatorPreOption {
3192 use std::collections::HashMap;
3193
3194 use alloy::primitives::U256;
3195 use hotshot_types::light_client::StateVerKey;
3196
3197 super::super::RegisteredValidatorPreOption {
3198 account: Address::random(),
3199 stake_table_key: BLSPubKey::generated_from_seed_indexed([seed; 32], 0).0,
3200 state_ver_key: StateVerKey::default(),
3201 stake: U256::from(stake),
3202 commission: 0,
3203 delegators: HashMap::new(),
3204 authenticated: true,
3205 x25519_key: None,
3206 p2p_addr: None,
3207 }
3208 }
3209
3210 async fn insert_legacy_stake_row(
3211 persistence: &Persistence,
3212 epoch: i64,
3213 validator: super::super::RegisteredValidatorPreOption,
3214 ) {
3215 let mut map: IndexMap<Address, super::super::RegisteredValidatorPreOption> =
3216 IndexMap::new();
3217 map.insert(validator.account, validator);
3218 let stake_bytes = bincode::serialize(&map).unwrap();
3219 let mut tx = persistence.db.write().await.unwrap();
3220 tx.execute(
3221 query("INSERT INTO epoch_drb_and_root (epoch, stake) VALUES ($1, $2)")
3222 .bind(epoch)
3223 .bind(&stake_bytes),
3224 )
3225 .await
3226 .unwrap();
3227 tx.commit().await.unwrap();
3228 }
3229
3230 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3231 async fn test_load_stake_legacy_storage() {
3232 let tmp = Persistence::tmp_storage().await;
3233 let persistence = Persistence::connect(&tmp).await;
3234
3235 let v1 = pre_option_validator(1, 100);
3236 let v2 = pre_option_validator(2, 200);
3237 let v1_addr = v1.account;
3238 let v2_addr = v2.account;
3239 insert_legacy_stake_row(&persistence, 1, v1).await;
3240 insert_legacy_stake_row(&persistence, 2, v2).await;
3241
3242 let (loaded1, ..) = persistence
3243 .load_stake(EpochNumber::new(1))
3244 .await
3245 .unwrap()
3246 .unwrap();
3247 assert_eq!(loaded1.len(), 1);
3248 assert!(loaded1.get(&v1_addr).unwrap().stake_table_key.is_some());
3249
3250 let (loaded2, ..) = persistence
3251 .load_stake(EpochNumber::new(2))
3252 .await
3253 .unwrap()
3254 .unwrap();
3255 assert_eq!(loaded2.len(), 1);
3256 assert!(loaded2.get(&v2_addr).unwrap().stake_table_key.is_some());
3257
3258 let latest = persistence.load_latest_stake(10).await.unwrap().unwrap();
3259 assert_eq!(latest.len(), 2);
3260 let epochs: Vec<_> = latest.iter().map(|(e, ..)| *e).collect();
3261 assert!(epochs.contains(&EpochNumber::new(1)));
3262 assert!(epochs.contains(&EpochNumber::new(2)));
3263 }
3264
3265 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3266 async fn test_load_stake_mixed_storage() {
3267 let tmp = Persistence::tmp_storage().await;
3268 let persistence = Persistence::connect(&tmp).await;
3269
3270 let legacy_v = pre_option_validator(3, 300);
3271 let legacy_addr = legacy_v.account;
3272 insert_legacy_stake_row(&persistence, 5, legacy_v).await;
3273
3274 let current_v = espresso_types::v0_3::AuthenticatedValidator::mock();
3275 let current_addr = current_v.account;
3276 let mut current_map = IndexMap::new();
3277 current_map.insert(current_addr, current_v);
3278 persistence
3279 .store_stake(EpochNumber::new(6), current_map, None, None)
3280 .await
3281 .unwrap();
3282
3283 let latest = persistence.load_latest_stake(10).await.unwrap().unwrap();
3284 assert_eq!(latest.len(), 2);
3285 let by_epoch: std::collections::HashMap<_, _> = latest
3286 .into_iter()
3287 .map(|(e, (map, _), _)| (e, map))
3288 .collect();
3289 assert!(
3290 by_epoch
3291 .get(&EpochNumber::new(5))
3292 .unwrap()
3293 .contains_key(&legacy_addr)
3294 );
3295 assert!(
3296 by_epoch
3297 .get(&EpochNumber::new(6))
3298 .unwrap()
3299 .contains_key(¤t_addr)
3300 );
3301 }
3302
3303 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3304 async fn test_store_all_validators_authenticated_and_unauthenticated() {
3305 use std::collections::HashMap;
3306
3307 use alloy::primitives::{Address, U256};
3308 use hotshot_types::light_client::StateVerKey;
3309 use indexmap::IndexMap;
3310
3311 let tmp = Persistence::tmp_storage().await;
3312 let storage = Persistence::connect(&tmp).await;
3313
3314 let authenticated_validator = RegisteredValidator {
3316 account: Address::random(),
3317 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([0u8; 32], 0).0),
3318 state_ver_key: Some(StateVerKey::default()),
3319 stake: U256::from(1000),
3320 commission: 100,
3321 delegators: HashMap::new(),
3322 authenticated: true,
3323 x25519_key: None,
3324 p2p_addr: None,
3325 };
3326
3327 let unauthenticated_validator = RegisteredValidator {
3329 account: Address::random(),
3330 stake_table_key: Some(BLSPubKey::generated_from_seed_indexed([0u8; 32], 1).0),
3331 state_ver_key: Some(StateVerKey::default()),
3332 stake: U256::from(2000),
3333 commission: 200,
3334 delegators: HashMap::new(),
3335 authenticated: false,
3336 x25519_key: None,
3337 p2p_addr: None,
3338 };
3339
3340 let mut validators: IndexMap<Address, RegisteredValidator<BLSPubKey>> = IndexMap::new();
3341 validators.insert(
3342 authenticated_validator.account,
3343 authenticated_validator.clone(),
3344 );
3345 validators.insert(
3346 unauthenticated_validator.account,
3347 unauthenticated_validator.clone(),
3348 );
3349
3350 storage
3352 .store_all_validators(EpochNumber::new(1), validators)
3353 .await
3354 .unwrap();
3355
3356 let loaded = storage
3358 .load_all_validators(EpochNumber::new(1), 0, 100)
3359 .await
3360 .unwrap();
3361 assert_eq!(loaded.len(), 2);
3362
3363 let loaded_auth = loaded
3365 .iter()
3366 .find(|v| v.account == authenticated_validator.account)
3367 .unwrap();
3368 assert!(
3369 loaded_auth.authenticated,
3370 "authenticated validator should remain authenticated"
3371 );
3372
3373 let loaded_unauth = loaded
3374 .iter()
3375 .find(|v| v.account == unauthenticated_validator.account)
3376 .unwrap();
3377 assert!(
3378 !loaded_unauth.authenticated,
3379 "unauthenticated validator should remain unauthenticated"
3380 );
3381 }
3382
3383 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3384 async fn test_fetching_providers() {
3385 let tmp = Persistence::tmp_storage().await;
3386 let storage = Persistence::connect(&tmp).await;
3387
3388 let leaf = Leaf2::genesis(
3390 &ValidatedState::default(),
3391 &NodeState::mock(),
3392 TEST_VERSIONS.test.base,
3393 )
3394 .await;
3395 let leaf_payload = leaf.block_payload().unwrap();
3396 let leaf_payload_bytes_arc = leaf_payload.encode();
3397
3398 let avidm_param = init_avidm_param(2).unwrap();
3399 let weights = vec![1u32; 2];
3400
3401 let ns_table = parse_ns_table(
3402 leaf_payload.byte_len().as_usize(),
3403 &leaf_payload.ns_table().encode(),
3404 );
3405 let (payload_commitment, shares) =
3406 AvidMScheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
3407 .unwrap();
3408 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
3409 let vid_share = convert_proposal(
3410 AvidMDisperseShare::<SeqTypes> {
3411 view_number: ViewNumber::new(0),
3412 payload_commitment,
3413 share: shares[0].clone(),
3414 recipient_key: pubkey,
3415 epoch: None,
3416 target_epoch: None,
3417 common: avidm_param.clone(),
3418 }
3419 .to_proposal(&privkey)
3420 .unwrap()
3421 .clone(),
3422 );
3423
3424 let quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
3425 proposal: QuorumProposal2::<SeqTypes> {
3426 block_header: leaf.block_header().clone(),
3427 view_number: leaf.view_number(),
3428 justify_qc: leaf.justify_qc(),
3429 upgrade_certificate: None,
3430 view_change_evidence: None,
3431 next_drb_result: None,
3432 next_epoch_justify_qc: None,
3433 epoch: None,
3434 state_cert: None,
3435 },
3436 };
3437 let quorum_proposal_signature =
3438 BLSPubKey::sign(&privkey, &bincode::serialize(&quorum_proposal).unwrap())
3439 .expect("Failed to sign quorum proposal");
3440 let quorum_proposal = Proposal {
3441 data: quorum_proposal,
3442 signature: quorum_proposal_signature,
3443 _pd: Default::default(),
3444 };
3445
3446 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
3447 .expect("Failed to sign block payload");
3448 let da_proposal = Proposal {
3449 data: DaProposal2::<SeqTypes> {
3450 encoded_transactions: leaf_payload_bytes_arc,
3451 metadata: leaf_payload.ns_table().clone(),
3452 view_number: ViewNumber::new(0),
3453 epoch: None,
3454 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
3455 },
3456 signature: block_payload_signature,
3457 _pd: Default::default(),
3458 };
3459
3460 let mut next_quorum_proposal = quorum_proposal.clone();
3461 next_quorum_proposal.data.proposal.view_number += 1;
3462 next_quorum_proposal.data.proposal.justify_qc.view_number += 1;
3463 next_quorum_proposal
3464 .data
3465 .proposal
3466 .justify_qc
3467 .data
3468 .leaf_commit = Committable::commit(&leaf.clone());
3469
3470 storage
3472 .append_da2(&da_proposal, VidCommitment::V1(payload_commitment))
3473 .await
3474 .unwrap();
3475 storage.append_vid(&vid_share).await.unwrap();
3476 storage
3477 .append_quorum_proposal2(&quorum_proposal)
3478 .await
3479 .unwrap();
3480
3481 storage
3483 .append_quorum_proposal2(&next_quorum_proposal)
3484 .await
3485 .unwrap();
3486
3487 assert_eq!(
3489 Some(VidCommon::V1(avidm_param)),
3490 storage
3491 .fetch(VidCommonRequest(vid_share.data.payload_commitment()))
3492 .await
3493 );
3494 assert_eq!(
3495 leaf_payload,
3496 storage
3497 .fetch(PayloadRequest(vid_share.data.payload_commitment()))
3498 .await
3499 .unwrap()
3500 );
3501 }
3502
3503 async fn test_pruning_helper(pruning_opt: ConsensusPruningOptions) {
3511 let tmp = Persistence::tmp_storage().await;
3512 let mut opt = Persistence::options(&tmp);
3513 opt.consensus_pruning = pruning_opt;
3514 let storage = opt.create().await.unwrap();
3515
3516 let data_view = ViewNumber::new(1);
3517
3518 let leaf = Leaf2::genesis(
3520 &ValidatedState::default(),
3521 &NodeState::mock(),
3522 TEST_VERSIONS.test.base,
3523 )
3524 .await;
3525 let leaf_payload = leaf.block_payload().unwrap();
3526 let leaf_payload_bytes_arc = leaf_payload.encode();
3527
3528 let avidm_param = init_avidm_param(2).unwrap();
3529 let weights = vec![1u32; 2];
3530
3531 let ns_table = parse_ns_table(
3532 leaf_payload.byte_len().as_usize(),
3533 &leaf_payload.ns_table().encode(),
3534 );
3535 let (payload_commitment, shares) =
3536 AvidMScheme::ns_disperse(&avidm_param, &weights, &leaf_payload_bytes_arc, ns_table)
3537 .unwrap();
3538
3539 let (pubkey, privkey) = BLSPubKey::generated_from_seed_indexed([0; 32], 1);
3540 let vid = convert_proposal(
3541 AvidMDisperseShare::<SeqTypes> {
3542 view_number: data_view,
3543 payload_commitment,
3544 share: shares[0].clone(),
3545 recipient_key: pubkey,
3546 epoch: None,
3547 target_epoch: None,
3548 common: avidm_param,
3549 }
3550 .to_proposal(&privkey)
3551 .unwrap()
3552 .clone(),
3553 );
3554 let quorum_proposal = QuorumProposalWrapper::<SeqTypes> {
3555 proposal: QuorumProposal2::<SeqTypes> {
3556 epoch: None,
3557 block_header: leaf.block_header().clone(),
3558 view_number: data_view,
3559 justify_qc: QuorumCertificate2::genesis(
3560 &ValidatedState::default(),
3561 &NodeState::mock(),
3562 TEST_VERSIONS.test,
3563 )
3564 .await,
3565 upgrade_certificate: None,
3566 view_change_evidence: None,
3567 next_drb_result: None,
3568 next_epoch_justify_qc: None,
3569 state_cert: None,
3570 },
3571 };
3572 let quorum_proposal_signature =
3573 BLSPubKey::sign(&privkey, &bincode::serialize(&quorum_proposal).unwrap())
3574 .expect("Failed to sign quorum proposal");
3575 let quorum_proposal = Proposal {
3576 data: quorum_proposal,
3577 signature: quorum_proposal_signature,
3578 _pd: Default::default(),
3579 };
3580
3581 let block_payload_signature = BLSPubKey::sign(&privkey, &leaf_payload_bytes_arc)
3582 .expect("Failed to sign block payload");
3583 let da_proposal = Proposal {
3584 data: DaProposal2::<SeqTypes> {
3585 encoded_transactions: leaf_payload_bytes_arc.clone(),
3586 metadata: leaf_payload.ns_table().clone(),
3587 view_number: data_view,
3588 epoch: Some(EpochNumber::new(0)),
3589 epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
3590 },
3591 signature: block_payload_signature,
3592 _pd: Default::default(),
3593 };
3594
3595 tracing::info!(?vid, ?da_proposal, ?quorum_proposal, "append data");
3596 storage.append_vid(&vid).await.unwrap();
3597 storage
3598 .append_da2(&da_proposal, VidCommitment::V1(payload_commitment))
3599 .await
3600 .unwrap();
3601 storage
3602 .append_quorum_proposal2(&quorum_proposal)
3603 .await
3604 .unwrap();
3605
3606 {
3609 let mut tx = storage.db.write().await.unwrap();
3610 tx.upsert(
3611 "state_cert",
3612 ["view", "state_cert"],
3613 ["view"],
3614 [(data_view.u64() as i64, b"state_cert".to_vec())],
3615 )
3616 .await
3617 .unwrap();
3618 tx.upsert(
3619 "decided_cert2",
3620 ["view", "data"],
3621 ["view"],
3622 [(data_view.u64() as i64, b"cert2".to_vec())],
3623 )
3624 .await
3625 .unwrap();
3626 tx.commit().await.unwrap();
3627 }
3628
3629 tracing::info!("decide view 1");
3632 storage
3633 .append_decided_leaves(data_view + 1, [], None, &NullEventConsumer)
3634 .await
3635 .unwrap();
3636 assert_eq!(
3637 storage.load_vid_share(data_view).await.unwrap().unwrap(),
3638 vid
3639 );
3640 assert_eq!(
3641 storage.load_da_proposal(data_view).await.unwrap().unwrap(),
3642 da_proposal
3643 );
3644 assert_eq!(
3645 storage.load_quorum_proposal(data_view).await.unwrap(),
3646 quorum_proposal
3647 );
3648 assert!(view_row_exists(&storage, "state_cert", data_view).await);
3649 assert!(view_row_exists(&storage, "decided_cert2", data_view).await);
3650
3651 tracing::info!("decide view 2");
3654 storage
3655 .append_decided_leaves(data_view + 2, [], None, &NullEventConsumer)
3656 .await
3657 .unwrap();
3658 assert!(storage.load_vid_share(data_view).await.unwrap().is_none(),);
3659 assert!(storage.load_da_proposal(data_view).await.unwrap().is_none());
3660 storage.load_quorum_proposal(data_view).await.unwrap_err();
3661 assert!(!view_row_exists(&storage, "state_cert", data_view).await);
3662 assert!(!view_row_exists(&storage, "decided_cert2", data_view).await);
3663 }
3664
3665 async fn view_row_exists(storage: &Persistence, table: &str, view: ViewNumber) -> bool {
3667 storage
3668 .db
3669 .read()
3670 .await
3671 .unwrap()
3672 .fetch_optional(
3673 query(&format!("SELECT view FROM {table} WHERE view = $1")).bind(view.u64() as i64),
3674 )
3675 .await
3676 .unwrap()
3677 .is_some()
3678 }
3679
3680 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3681 async fn test_pruning_minimum_retention() {
3682 test_pruning_helper(ConsensusPruningOptions {
3683 target_usage: 0,
3686 minimum_retention: 1,
3687 target_retention: u64::MAX,
3690 })
3691 .await
3692 }
3693
3694 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3695 async fn test_pruning_target_retention() {
3696 test_pruning_helper(ConsensusPruningOptions {
3697 target_retention: 1,
3698 minimum_retention: 0,
3701 target_usage: u64::MAX,
3704 })
3705 .await
3706 }
3707
3708 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3731 async fn test_store_events_empty() {
3732 let tmp = Persistence::tmp_storage().await;
3733 let mut opt = Persistence::options(&tmp);
3734 let storage = opt.create().await.unwrap();
3735
3736 assert_eq!(storage.load_events(0, 100).await.unwrap(), (None, vec![]));
3737
3738 for i in 1..=2 {
3740 tracing::info!(i, "update l1 height");
3741 storage.store_events(i, vec![]).await.unwrap();
3742 assert_eq!(
3743 storage.load_events(0, 100).await.unwrap(),
3744 (Some(EventsPersistenceRead::UntilL1Block(i)), vec![])
3745 );
3746 }
3747 }
3748}
3749
3750#[cfg(test)]
3751#[cfg(not(feature = "embedded-db"))]
3752mod postgres_tests {
3753 use espresso_types::{FeeAccount, Header, Leaf, NodeState, Transaction as Tx};
3754 use hotshot_example_types::node_types::TEST_VERSIONS;
3755 use hotshot_query_service::{
3756 availability::{BlockQueryData, LeafQueryData},
3757 data_source::storage::UpdateAvailabilityStorage,
3758 };
3759 use hotshot_types::{
3760 data::vid_commitment,
3761 simple_certificate::QuorumCertificate,
3762 traits::{
3763 EncodeBytes,
3764 block_contents::{BlockHeader, BuilderFee, GENESIS_VID_NUM_STORAGE_NODES},
3765 election::Membership,
3766 signature_key::BuilderSignatureKey,
3767 },
3768 };
3769
3770 use super::*;
3771 use crate::persistence::tests::TestablePersistence as _;
3772
3773 async fn test_postgres_read_ns_table(instance_state: NodeState) {
3774 instance_state
3775 .coordinator
3776 .membership()
3777 .set_first_epoch(EpochNumber::genesis(), Default::default());
3778
3779 let tmp = Persistence::tmp_storage().await;
3780 let mut opt = Persistence::options(&tmp);
3781 let storage = opt.create().await.unwrap();
3782
3783 let txs = [
3784 Tx::new(10001u32.into(), vec![1, 2, 3]),
3785 Tx::new(10001u32.into(), vec![4, 5, 6]),
3786 Tx::new(10009u32.into(), vec![7, 8, 9]),
3787 ];
3788
3789 let validated_state = Default::default();
3790 let justify_qc =
3791 QuorumCertificate::genesis(&validated_state, &instance_state, TEST_VERSIONS.test).await;
3792 let view_number: ViewNumber = justify_qc.view_number + 1;
3793 let parent_leaf = Leaf::genesis(&validated_state, &instance_state, TEST_VERSIONS.test.base)
3794 .await
3795 .into();
3796
3797 let (payload, ns_table) =
3798 Payload::from_transactions(txs.clone(), &validated_state, &instance_state)
3799 .await
3800 .unwrap();
3801 let payload_bytes = payload.encode();
3802 let payload_commitment = vid_commitment(
3803 &payload_bytes,
3804 &ns_table.encode(),
3805 GENESIS_VID_NUM_STORAGE_NODES,
3806 instance_state.current_version,
3807 );
3808 let builder_commitment = payload.builder_commitment(&ns_table);
3809 let (fee_account, fee_key) = FeeAccount::generated_from_seed_indexed([0; 32], 0);
3810 let fee_amount = 0;
3811 let fee_signature = FeeAccount::sign_fee(&fee_key, fee_amount, &ns_table).unwrap();
3812 let block_header = Header::new(
3813 &validated_state,
3814 &instance_state,
3815 &parent_leaf,
3816 payload_commitment,
3817 builder_commitment,
3818 ns_table,
3819 BuilderFee {
3820 fee_amount,
3821 fee_account,
3822 fee_signature,
3823 },
3824 instance_state.current_version,
3825 view_number.u64(),
3826 )
3827 .await
3828 .unwrap();
3829 let proposal = QuorumProposal {
3830 block_header: block_header.clone(),
3831 view_number,
3832 justify_qc: justify_qc.clone(),
3833 upgrade_certificate: None,
3834 proposal_certificate: None,
3835 };
3836 let leaf: Leaf2 = Leaf::from_quorum_proposal(&proposal).into();
3837 let mut qc = justify_qc.to_qc2();
3838 qc.data.leaf_commit = leaf.commit();
3839 qc.view_number = view_number;
3840
3841 let mut tx = storage.db.write().await.unwrap();
3842 tx.insert_leaf(&LeafQueryData::new(leaf, qc).unwrap())
3843 .await
3844 .unwrap();
3845 tx.insert_block(&BlockQueryData::<SeqTypes>::new(block_header, payload))
3846 .await
3847 .unwrap();
3848 tx.commit().await.unwrap();
3849
3850 let mut tx = storage.db.read().await.unwrap();
3851 let rows = query(
3852 "
3853 SELECT ns_id, read_ns_id(get_ns_table(h.data), t.ns_index) AS read_ns_id
3854 FROM header AS h
3855 JOIN transactions AS t ON t.block_height = h.height
3856 ORDER BY t.ns_index, t.position
3857 ",
3858 )
3859 .fetch_all(tx.as_mut())
3860 .await
3861 .unwrap();
3862 assert_eq!(rows.len(), txs.len());
3863 for (i, row) in rows.into_iter().enumerate() {
3864 let ns = u64::from(txs[i].namespace()) as i64;
3865 assert_eq!(row.get::<i64, _>("ns_id"), ns);
3866 assert_eq!(row.get::<i64, _>("read_ns_id"), ns);
3867 }
3868 }
3869
3870 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3871 async fn test_postgres_read_ns_table_v0_1() {
3872 test_postgres_read_ns_table(NodeState::mock()).await;
3873 }
3874
3875 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3876 async fn test_postgres_read_ns_table_v0_2() {
3877 test_postgres_read_ns_table(NodeState::mock_v2()).await;
3878 }
3879
3880 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3881 async fn test_postgres_read_ns_table_v0_3() {
3882 test_postgres_read_ns_table(NodeState::mock_v3().with_epoch_height(0)).await;
3883 }
3884
3885 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3890 async fn test_record_action_concurrent() {
3891 let tmp = Persistence::tmp_storage().await;
3892 let storage = Arc::new(Persistence::connect(&tmp).await);
3893
3894 let handles: Vec<_> = (0u64..20)
3895 .map(|i| {
3896 let storage = Arc::clone(&storage);
3897 tokio::spawn(async move {
3898 storage
3899 .record_action(ViewNumber::new(i), None, HotShotAction::Vote)
3900 .await
3901 })
3902 })
3903 .collect();
3904
3905 for handle in handles {
3906 handle.await.unwrap().unwrap();
3907 }
3908
3909 let latest = storage.load_latest_acted_view().await.unwrap();
3910 assert_eq!(latest, Some(ViewNumber::new(19)));
3911 }
3912}