From e3c46e32320d1eb9cdf332eb204cd688a7a83229 Mon Sep 17 00:00:00 2001 From: pls148 <184445976+pls148@users.noreply.github.com> Date: Tue, 4 Mar 2025 21:08:30 -0800 Subject: [PATCH] store epochs and drbs --- hotshot-example-types/src/storage_types.rs | 25 ++++ hotshot-task-impls/src/helpers.rs | 39 +++++- .../src/quorum_vote/handlers.rs | 40 ++++-- hotshot-testing/tests/tests_6/test_epochs.rs | 14 +- hotshot-types/src/traits/storage.rs | 9 ++ .../postgres/V502__epoch_drb_and_root.sql | 5 + .../sqlite/V302__epoch_drb_and_root.sql | 5 + sequencer/src/persistence/fs.rs | 123 +++++++++++++++++- sequencer/src/persistence/sql.rs | 81 +++++++++++- types/src/v0/traits.rs | 49 ++++++- 10 files changed, 362 insertions(+), 28 deletions(-) create mode 100644 sequencer/api/migrations/postgres/V502__epoch_drb_and_root.sql create mode 100644 sequencer/api/migrations/sqlite/V302__epoch_drb_and_root.sql diff --git a/hotshot-example-types/src/storage_types.rs b/hotshot-example-types/src/storage_types.rs index 3d7885973d..9084eb463d 100644 --- a/hotshot-example-types/src/storage_types.rs +++ b/hotshot-example-types/src/storage_types.rs @@ -12,6 +12,7 @@ use std::{ use anyhow::{bail, Result}; use async_lock::RwLock; use async_trait::async_trait; +use hotshot_types::drb::DrbResult; use hotshot_types::{ consensus::CommitmentMap, data::{ @@ -56,6 +57,8 @@ pub struct TestStorageState { Option>, action: TYPES::View, epoch: Option, + drb_results: BTreeMap, + epoch_roots: BTreeMap, } impl Default for TestStorageState { @@ -73,6 +76,8 @@ impl Default for TestStorageState { high_qc2: None, action: TYPES::View::genesis(), epoch: None, + drb_results: BTreeMap::new(), + epoch_roots: BTreeMap::new(), } } } @@ -373,4 +378,24 @@ impl Storage for TestStorage { Ok(()) } + + async fn add_drb_result(&self, epoch: TYPES::Epoch, drb_result: DrbResult) -> Result<()> { + let mut inner = self.inner.write().await; + + inner.drb_results.insert(epoch, drb_result); + + Ok(()) + } + + async fn add_epoch_root( + &self, + epoch: TYPES::Epoch, + block_header: TYPES::BlockHeader, + ) -> Result<()> { + let mut inner = self.inner.write().await; + + inner.epoch_roots.insert(epoch, block_header); + + Ok(()) + } } diff --git a/hotshot-task-impls/src/helpers.rs b/hotshot-task-impls/src/helpers.rs index 5524f1b25d..61f40340ac 100644 --- a/hotshot-task-impls/src/helpers.rs +++ b/hotshot-task-impls/src/helpers.rs @@ -9,6 +9,7 @@ use async_lock::RwLock; use committable::{Commitment, Committable}; use either::Either; use hotshot_task::dependency::{Dependency, EventDependency}; +use hotshot_types::traits::storage::Storage; use hotshot_types::{ consensus::OuterConsensus, data::{Leaf2, QuorumProposalWrapper, ViewChangeEvidence2}, @@ -180,10 +181,11 @@ pub(crate) async fn fetch_proposal( } /// Handles calling add_epoch_root and sync_l1 on Membership if necessary. -async fn decide_epoch_root( +async fn decide_epoch_root>( decided_leaf: &Leaf2, epoch_height: u64, membership: &Arc>, + storage: &Arc>, ) { let decided_block_number = decided_leaf.block_header().block_number(); @@ -192,6 +194,19 @@ async fn decide_epoch_root( let next_epoch_number = TYPES::Epoch::new(epoch_from_block_number(decided_block_number, epoch_height) + 2); + if let Err(e) = storage + .write() + .await + .add_epoch_root(next_epoch_number, decided_leaf.block_header().clone()) + .await + { + tracing::error!( + "Failed to store epoch root for epoch {:?}: {}", + next_epoch_number, + e + ); + } + let write_callback = { tracing::debug!("Calling add_epoch_root for epoch {:?}", next_epoch_number); let membership_reader = membership.read().await; @@ -251,13 +266,14 @@ impl Default for LeafChainTraversalOutcome { /// # Panics /// If the leaf chain contains no decided leaf while reaching a decided view, which should be /// impossible. -pub async fn decide_from_proposal_2( +pub async fn decide_from_proposal_2>( proposal: &QuorumProposalWrapper, consensus: OuterConsensus, existing_upgrade_cert: Arc>>>, public_key: &TYPES::SignatureKey, with_epochs: bool, membership: &Arc>, + storage: &Arc>, ) -> LeafChainTraversalOutcome { let mut res = LeafChainTraversalOutcome::default(); let consensus_reader = consensus.read().await; @@ -332,7 +348,13 @@ pub async fn decide_from_proposal_2( drop(consensus_reader); for decided_leaf_info in &res.leaf_views { - decide_epoch_root(&decided_leaf_info.leaf, epoch_height, membership).await; + decide_epoch_root::( + &decided_leaf_info.leaf, + epoch_height, + membership, + storage, + ) + .await; } } @@ -370,13 +392,14 @@ pub async fn decide_from_proposal_2( /// # Panics /// If the leaf chain contains no decided leaf while reaching a decided view, which should be /// impossible. -pub async fn decide_from_proposal( +pub async fn decide_from_proposal, V: Versions>( proposal: &QuorumProposalWrapper, consensus: OuterConsensus, existing_upgrade_cert: Arc>>>, public_key: &TYPES::SignatureKey, with_epochs: bool, membership: &Arc>, + storage: &Arc>, ) -> LeafChainTraversalOutcome { let consensus_reader = consensus.read().await; let existing_upgrade_cert_reader = existing_upgrade_cert.read().await; @@ -488,7 +511,13 @@ pub async fn decide_from_proposal( if with_epochs && res.new_decided_view_number.is_some() { for decided_leaf_info in &res.leaf_views { - decide_epoch_root(&decided_leaf_info.leaf, epoch_height, membership).await; + decide_epoch_root::( + &decided_leaf_info.leaf, + epoch_height, + membership, + storage, + ) + .await; } } diff --git a/hotshot-task-impls/src/quorum_vote/handlers.rs b/hotshot-task-impls/src/quorum_vote/handlers.rs index 19a926a0fd..d313eabb11 100644 --- a/hotshot-task-impls/src/quorum_vote/handlers.rs +++ b/hotshot-task-impls/src/quorum_vote/handlers.rs @@ -48,12 +48,27 @@ use crate::{ quorum_vote::Versions, }; -async fn notify_membership_of_drb_result( - membership: &EpochMembership, +async fn handle_drb_result>( + epoch_membership: &EpochMembership, + storage: &Arc>, drb_result: DrbResult, ) { tracing::debug!("Calling add_drb_result for epoch {:?}", membership.epoch()); - membership.add_drb_result(drb_result).await; + + if let Err(e) = storage + .write() + .await + .add_drb_result(membership.epoch(), drb_result) + .await + { + tracing::error!( + "Failed to store drb result for epoch {:?}: {}", + membership.epoch(), + e + ); + } + + membership.add_drb_result(drb_result); } /// Store the DRB result from the computation task to the shared `results` table. @@ -101,11 +116,12 @@ async fn store_and_get_computed_drb_result< .insert(epoch_number, result); drop(consensus_writer); - notify_membership_of_drb_result::( + handle_drb_result::( &task_state .membership .membership_for_epoch(Some(epoch_number)) .await?, + &task_state.storage, result, ) .await; @@ -221,7 +237,12 @@ async fn start_drb_task, V: Versio .drb_seeds_and_results .results .insert(*task_epoch, result); - notify_membership_of_drb_result::(&epoch_membership, result).await; + handle_drb_result::( + &epoch_membership, + &task_state.storage, + result, + ) + .await; task_state.drb_computation = None; } Err(e) => { @@ -335,11 +356,12 @@ async fn store_drb_seed_and_result .drb_seeds_and_results .results .insert(current_epoch_number + 1, result); - notify_membership_of_drb_result::( + handle_drb_result::( &task_state .membership .membership_for_epoch(Some(current_epoch_number + 1)) .await?, + &task_state.storage, result, ) .await; @@ -379,23 +401,25 @@ pub(crate) async fn handle_quorum_proposal_validated< included_txns, decided_upgrade_cert, } = if version >= V::Epochs::VERSION { - decide_from_proposal_2( + decide_from_proposal_2::( proposal, OuterConsensus::new(Arc::clone(&task_state.consensus.inner_consensus)), Arc::clone(&task_state.upgrade_lock.decided_upgrade_certificate), &task_state.public_key, version >= V::Epochs::VERSION, task_state.membership.membership(), + &task_state.storage, ) .await } else { - decide_from_proposal::( + decide_from_proposal::( proposal, OuterConsensus::new(Arc::clone(&task_state.consensus.inner_consensus)), Arc::clone(&task_state.upgrade_lock.decided_upgrade_certificate), &task_state.public_key, version >= V::Epochs::VERSION, task_state.membership.membership(), + &task_state.storage, ) .await }; diff --git a/hotshot-testing/tests/tests_6/test_epochs.rs b/hotshot-testing/tests/tests_6/test_epochs.rs index bed8d185ac..cd4031b534 100644 --- a/hotshot-testing/tests/tests_6/test_epochs.rs +++ b/hotshot-testing/tests/tests_6/test_epochs.rs @@ -8,8 +8,8 @@ use hotshot_example_types::{ node_types::{ CombinedImpl, EpochUpgradeTestVersions, EpochsTestVersions, Libp2pImpl, MemoryImpl, PushCdnImpl, RandomOverlapQuorumFilterConfig, StableQuorumFilterConfig, - TestConsecutiveLeaderTypes, TestTwoStakeTablesTypes, TestTypes, - TestTypesRandomizedCommitteeMembers, TestTypesRandomizedLeader, TestTypesEpochCatchupTypes + TestConsecutiveLeaderTypes, TestTwoStakeTablesTypes, TestTypes, TestTypesEpochCatchupTypes, + TestTypesRandomizedCommitteeMembers, TestTypesRandomizedLeader, }, testable_delay::{DelayConfig, DelayOptions, DelaySettings, SupportedTraitTypesForAsyncDelay}, }; @@ -505,23 +505,23 @@ cross_tests!( // }; // let mut metadata = TestDescription::default().set_num_nodes(20,20); // let mut catchup_nodes = vec![]; -// +// // for i in 0..20 { // catchup_nodes.push(ChangeNode { // idx: i, // updown: NodeAction::RestartDown(0), // }) // } -// +// // metadata.timing_data = timing_data; -// +// // metadata.spinning_properties = SpinningTaskDescription { // // Restart all the nodes in view 10 // node_changes: vec![(10, catchup_nodes)], // }; // metadata.view_sync_properties = // hotshot_testing::view_sync_task::ViewSyncTaskDescription::Threshold(0, 20); -// +// // metadata.completion_task_description = // CompletionTaskDescription::TimeBasedCompletionTaskBuilder( // TimeBasedCompletionTaskDescription { @@ -536,7 +536,7 @@ cross_tests!( // decide_timeout: Duration::from_secs(20), // ..Default::default() // }; -// +// // metadata // }, // ); diff --git a/hotshot-types/src/traits/storage.rs b/hotshot-types/src/traits/storage.rs index 991ba8282f..c2ac50fcf2 100644 --- a/hotshot-types/src/traits/storage.rs +++ b/hotshot-types/src/traits/storage.rs @@ -24,6 +24,7 @@ use crate::{ DaProposal, DaProposal2, Leaf, Leaf2, QuorumProposal, QuorumProposal2, QuorumProposalWrapper, VidDisperseShare, }, + drb::DrbResult, event::HotShotAction, message::{convert_proposal, Proposal}, simple_certificate::{ @@ -158,4 +159,12 @@ pub trait Storage: Send + Sync + Clone { async fn migrate_consensus(&self) -> Result<()> { Ok(()) } + /// Add a drb result + async fn add_drb_result(&self, epoch: TYPES::Epoch, drb_result: DrbResult) -> Result<()>; + /// Add an epoch block header + async fn add_epoch_root( + &self, + epoch: TYPES::Epoch, + block_header: TYPES::BlockHeader, + ) -> Result<()>; } diff --git a/sequencer/api/migrations/postgres/V502__epoch_drb_and_root.sql b/sequencer/api/migrations/postgres/V502__epoch_drb_and_root.sql new file mode 100644 index 0000000000..d35d757985 --- /dev/null +++ b/sequencer/api/migrations/postgres/V502__epoch_drb_and_root.sql @@ -0,0 +1,5 @@ +CREATE TABLE epoch_drb_and_root ( + epoch BIGINT PRIMARY KEY, + drb_result BYTEA, + block_header BYTEA, +); \ No newline at end of file diff --git a/sequencer/api/migrations/sqlite/V302__epoch_drb_and_root.sql b/sequencer/api/migrations/sqlite/V302__epoch_drb_and_root.sql new file mode 100644 index 0000000000..3d28ad8508 --- /dev/null +++ b/sequencer/api/migrations/sqlite/V302__epoch_drb_and_root.sql @@ -0,0 +1,5 @@ +CREATE TABLE epoch_drb_and_root ( + epoch BIGINT PRIMARY KEY, + drb_result BLOB, + block_header BLOB, +); \ No newline at end of file diff --git a/sequencer/src/persistence/fs.rs b/sequencer/src/persistence/fs.rs index 853142fa9a..9ee7110d9a 100644 --- a/sequencer/src/persistence/fs.rs +++ b/sequencer/src/persistence/fs.rs @@ -6,6 +6,7 @@ use espresso_types::{ v0::traits::{EventConsumer, PersistenceOptions, SequencerPersistence}, Leaf, Leaf2, NetworkConfig, Payload, SeqTypes, }; +use hotshot::InitializerEpochInfo; use hotshot_types::{ consensus::CommitmentMap, data::{ @@ -13,6 +14,7 @@ use hotshot_types::{ DaProposal, DaProposal2, EpochNumber, QuorumProposal, QuorumProposal2, QuorumProposalWrapper, VidCommitment, VidDisperseShare, }, + drb::DrbResult, event::{Event, EventType, HotShotAction, LeafInfo}, message::{convert_proposal, Proposal}, simple_certificate::{ @@ -20,7 +22,7 @@ use hotshot_types::{ }, traits::{ block_contents::{BlockHeader, BlockPayload}, - node_implementation::ConsensusTime, + node_implementation::{ConsensusTime, NodeType}, }, utils::View, vote::HasViewNumber, @@ -209,6 +211,14 @@ impl Inner { self.path.join("next_epoch_quorum_certificate") } + fn epoch_drb_result_dir_path(&self) -> PathBuf { + self.path.join("epoch_drb_result") + } + + fn epoch_root_block_header_dir_path(&self) -> PathBuf { + self.path.join("epoch_root_block_header") + } + fn update_migration(&mut self) -> anyhow::Result<()> { let path = self.migration(); let bytes = bincode::serialize(&self.migrated)?; @@ -1268,6 +1278,91 @@ impl SequencerPersistence for Persistence { async fn migrate_quorum_certificates(&self) -> anyhow::Result<()> { Ok(()) } + + async fn add_drb_result( + &self, + epoch: EpochNumber, + drb_result: DrbResult, + ) -> anyhow::Result<()> { + let inner = self.inner.write().await; + let dir_path = inner.epoch_drb_result_dir_path(); + + fs::create_dir_all(dir_path.clone()).context("failed to create epoch drb result dir")?; + + let drb_result_bytes = bincode::serialize(&drb_result).context("serialize drb result")?; + + let file_path = dir_path.join(epoch.to_string()).with_extension("txt"); + fs::write(file_path, drb_result_bytes) + .context(format!("writing epoch drb result file for epoch {epoch:?}"))?; + + Ok(()) + } + + async fn add_epoch_root( + &self, + epoch: EpochNumber, + block_header: ::BlockHeader, + ) -> anyhow::Result<()> { + let inner = self.inner.write().await; + let dir_path = inner.epoch_root_block_header_dir_path(); + + fs::create_dir_all(dir_path.clone()) + .context("failed to create epoch root block header dir")?; + + let block_header_bytes = + bincode::serialize(&block_header).context("serialize block header")?; + + let file_path = dir_path.join(epoch.to_string()).with_extension("txt"); + fs::write(file_path, block_header_bytes).context(format!( + "writing epoch root block header file for epoch {epoch:?}" + ))?; + + Ok(()) + } + + async fn load_start_epoch_info(&self) -> anyhow::Result>> { + let inner = self.inner.read().await; + let drb_dir_path = inner.epoch_drb_result_dir_path(); + let block_header_dir_path = inner.epoch_root_block_header_dir_path(); + + let mut result = Vec::new(); + + if drb_dir_path.is_dir() { + for (epoch, path) in epoch_files(drb_dir_path)? { + let bytes = fs::read(&path) + .context(format!("reading epoch drb result {}", path.display()))?; + let drb_result = bincode::deserialize::(&bytes) + .context(format!("parsing epoch drb result {}", path.display()))?; + + let block_header_path = block_header_dir_path + .join(epoch.to_string()) + .with_extension("txt"); + let block_header = if block_header_path.is_file() { + let bytes = fs::read(&path).context(format!( + "reading epoch root block header {}", + path.display() + ))?; + Some( + bincode::deserialize::<::BlockHeader>(&bytes) + .context(format!( + "parsing epoch root block header {}", + path.display() + ))?, + ) + } else { + None + }; + + result.push(InitializerEpochInfo:: { + epoch, + drb_result, + block_header, + }); + } + } + + Ok(result) + } } /// Update a `NetworkConfig` that may have originally been persisted with an old version. @@ -1355,6 +1450,32 @@ fn view_files( })) } +/// Get all paths under `dir` whose name is of the form .txt. +/// Should probably be made generic and merged with view_files. +fn epoch_files( + dir: impl AsRef, +) -> anyhow::Result> { + Ok(fs::read_dir(dir.as_ref())?.filter_map(move |entry| { + let dir = dir.as_ref().display(); + let entry = entry.ok()?; + if !entry.file_type().ok()?.is_file() { + tracing::debug!(%dir, ?entry, "ignoring non-file in data directory"); + return None; + } + let path = entry.path(); + if path.extension()? != "txt" { + tracing::debug!(%dir, ?entry, "ignoring non-text file in data directory"); + return None; + } + let file_name = path.file_stem()?; + let Ok(epoch_number) = file_name.to_string_lossy().parse::() else { + tracing::debug!(%dir, ?file_name, "ignoring extraneous file in data directory"); + return None; + }; + Some((EpochNumber::new(epoch_number), entry.path().to_owned())) + })) +} + #[cfg(test)] mod testing { use tempfile::TempDir; diff --git a/sequencer/src/persistence/sql.rs b/sequencer/src/persistence/sql.rs index e2383a81ca..6e37091434 100644 --- a/sequencer/src/persistence/sql.rs +++ b/sequencer/src/persistence/sql.rs @@ -1,4 +1,5 @@ -use anyhow::Context; +use crate::{catchup::SqlStateCatchup, NodeType, SeqTypes, ViewNumber}; +use anyhow::{bail, Context}; use async_trait::async_trait; use clap::Parser; use committable::Committable; @@ -10,6 +11,7 @@ use espresso_types::{ BackoffParams, BlockMerkleTree, FeeMerkleTree, Leaf, Leaf2, NetworkConfig, Payload, }; use futures::stream::StreamExt; +use hotshot::InitializerEpochInfo; use hotshot_query_service::{ availability::LeafQueryData, data_source::{ @@ -29,6 +31,7 @@ use hotshot_query_service::{ merklized_state::MerklizedState, VidCommon, }; +use hotshot_types::drb::DrbResult; use hotshot_types::{ consensus::CommitmentMap, data::{ @@ -53,8 +56,6 @@ use sqlx::Row; use sqlx::{query, Executor}; use std::{collections::BTreeMap, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; -use crate::{catchup::SqlStateCatchup, SeqTypes, ViewNumber}; - /// Options for Postgres-backed persistence. #[derive(Parser, Clone, Derivative)] #[derivative(Debug)] @@ -1873,6 +1874,80 @@ impl SequencerPersistence for Persistence { .await?; tx.commit().await } + + async fn add_drb_result( + &self, + epoch: EpochNumber, + drb_result: DrbResult, + ) -> anyhow::Result<()> { + let mut tx = self.db.write().await?; + tx.upsert( + "epoch_drb_and_root", + ["epoch", "drb_result"], + ["epoch"], + [(epoch.u64() as i64, drb_result)], + ) + .await?; + tx.commit().await + } + + async fn add_epoch_root( + &self, + epoch: EpochNumber, + block_header: ::BlockHeader, + ) -> anyhow::Result<()> { + let block_header_bytes = + bincode::serialize(&block_header).context("serializing block header")?; + + let mut tx = self.db.write().await?; + tx.upsert( + "epoch_drb_and_root", + ["epoch", "block_header"], + ["epoch"], + [(epoch.u64() as i64, block_header_bytes)], + ) + .await?; + tx.commit().await + } + + async fn load_start_epoch_info(&self) -> anyhow::Result>> { + let rows = self + .db + .read() + .await? + .fetch_all("SELECT * from epoch_drb_and_root ORDER BY epoch ASC") + .await?; + + rows.into_iter() + .map(|row| { + let epoch: i64 = row.get("epoch"); + let drb_result: Option> = row.get("drb_result"); + let block_header: Option> = row.get("block_header"); + if let Some(drb_result) = drb_result { + let drb_result_array = drb_result + .try_into() + .or_else(|_| bail!("invalid drb result"))?; + let block_header: Option<::BlockHeader> = block_header + .map(|data| bincode::deserialize(&data)) + .transpose()?; + Ok(Some(InitializerEpochInfo:: { + epoch: ::Epoch::new(epoch as u64), + drb_result: drb_result_array, + block_header, + })) + } else { + // Right now we skip the epoch_drb_and_root row if there is no drb result. + // This seems reasonable based on the expected order of events, but please double check! + Ok(None) + } + }) + .filter_map(|e| match e { + Err(v) => Some(Err(v)), + Ok(Some(v)) => Some(Ok(v)), + Ok(None) => None, + }) + .collect() + } } #[async_trait] diff --git a/types/src/v0/traits.rs b/types/src/v0/traits.rs index 610fc19e3f..930b7f9532 100644 --- a/types/src/v0/traits.rs +++ b/types/src/v0/traits.rs @@ -6,7 +6,9 @@ use anyhow::{bail, ensure, Context}; use async_trait::async_trait; use committable::{Commitment, Committable}; use futures::{FutureExt, TryFutureExt}; -use hotshot::{types::EventType, HotShotInitializer}; +use hotshot::{types::EventType, HotShotInitializer, InitializerEpochInfo}; +use hotshot_types::drb::DrbResult; +use hotshot_types::traits::node_implementation::NodeType; use hotshot_types::{ consensus::CommitmentMap, data::{ @@ -538,6 +540,7 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { async fn load_upgrade_certificate( &self, ) -> anyhow::Result>>; + async fn load_start_epoch_info(&self) -> anyhow::Result>>; /// Load the latest known consensus state. /// @@ -615,6 +618,12 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { // TODO: let epoch = genesis_epoch_from_version::(); + let config = self.load_config().await.context("loading config")?; + let epoch_height = config.map(|c| c.config.epoch_height).unwrap_or_default(); + let epoch_start_block = config + .map(|c| c.config.epoch_start_block) + .unwrap_or_default(); + let (undecided_leaves, undecided_state) = self .load_undecided_state() .await @@ -631,6 +640,11 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { .await .context("loading upgrade certificate")?; + let start_epoch_info = self + .load_start_epoch_info() + .await + .context("loading start epoch info")?; + tracing::info!( ?leaf, ?view, @@ -647,8 +661,8 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { Ok(( HotShotInitializer { instance_state: state, - epoch_height: 0, - epoch_start_block: 0, + epoch_height, + epoch_start_block, anchor_leaf: leaf, anchor_state: validated_state.unwrap_or_default(), anchor_state_delta: None, @@ -665,7 +679,7 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { .collect(), undecided_state, saved_vid_shares: Default::default(), // TODO: implement saved_vid_shares - start_epoch_info: Default::default(), // TODO: implement start_epoch_info + start_epoch_info, }, anchor_view, )) @@ -819,6 +833,17 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { ) -> anyhow::Result<()> { self.append_quorum_proposal2(proposal).await } + + async fn add_drb_result( + &self, + epoch: ::Epoch, + drb_result: DrbResult, + ) -> anyhow::Result<()>; + async fn add_epoch_root( + &self, + epoch: ::Epoch, + block_header: ::BlockHeader, + ) -> anyhow::Result<()>; } #[async_trait] @@ -947,6 +972,22 @@ impl Storage for Arc

{ ) -> anyhow::Result<()> { (**self).update_undecided_state2(leaves, state).await } + + async fn add_drb_result( + &self, + epoch: ::Epoch, + drb_result: DrbResult, + ) -> anyhow::Result<()> { + (**self).add_drb_result(epoch, drb_result).await + } + + async fn add_epoch_root( + &self, + epoch: ::Epoch, + block_header: ::BlockHeader, + ) -> anyhow::Result<()> { + (**self).add_epoch_root(epoch, block_header).await + } } /// Data that can be deserialized from a subslice of namespace payload bytes.