From 0fa1f3f69e65e21c2c1461658f96702db47d7b0f Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Fri, 20 Dec 2024 18:25:13 +0500 Subject: [PATCH] store next epoch qc --- .../postgres/V402__next_epoch_qc.sql | 5 ++ .../migrations/sqlite/V203__next_epoch_qc.sql | 4 ++ sequencer/src/persistence.rs | 71 +++++++++++++++++-- sequencer/src/persistence/fs.rs | 43 ++++++++++- sequencer/src/persistence/no_storage.rs | 15 +++- sequencer/src/persistence/sql.rs | 38 +++++++++- types/src/v0/traits.rs | 20 ++++-- 7 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 sequencer/api/migrations/postgres/V402__next_epoch_qc.sql create mode 100644 sequencer/api/migrations/sqlite/V203__next_epoch_qc.sql diff --git a/sequencer/api/migrations/postgres/V402__next_epoch_qc.sql b/sequencer/api/migrations/postgres/V402__next_epoch_qc.sql new file mode 100644 index 000000000..3995e6881 --- /dev/null +++ b/sequencer/api/migrations/postgres/V402__next_epoch_qc.sql @@ -0,0 +1,5 @@ +CREATE TABLE next_epoch_quorum_certificate ( + id bool PRIMARY KEY DEFAULT true, + data BYTEA +); +REVOKE DELETE, TRUNCATE ON next_epoch_quorum_certificate FROM public; diff --git a/sequencer/api/migrations/sqlite/V203__next_epoch_qc.sql b/sequencer/api/migrations/sqlite/V203__next_epoch_qc.sql new file mode 100644 index 000000000..debf4b2f9 --- /dev/null +++ b/sequencer/api/migrations/sqlite/V203__next_epoch_qc.sql @@ -0,0 +1,4 @@ +CREATE TABLE next_epoch_quorum_certificate ( + id bool PRIMARY KEY DEFAULT true, + data BLOB +); \ No newline at end of file diff --git a/sequencer/src/persistence.rs b/sequencer/src/persistence.rs index a93b51e22..67d515566 100644 --- a/sequencer/src/persistence.rs +++ b/sequencer/src/persistence.rs @@ -43,11 +43,11 @@ mod testing { #[cfg(test)] #[espresso_macros::generic_tests] mod persistence_tests { - use std::collections::BTreeMap; + use std::{collections::BTreeMap, marker::PhantomData}; use anyhow::bail; use async_lock::RwLock; - use committable::Committable; + use committable::{Commitment, Committable}; use espresso_types::{ traits::{EventConsumer, NullEventConsumer, PersistenceOptions}, Event, Leaf, Leaf2, NodeState, PubKey, SeqTypes, ValidatedState, @@ -58,9 +58,9 @@ mod persistence_tests { data::{DaProposal, EpochNumber, QuorumProposal2, VidDisperseShare, ViewNumber}, drb::{INITIAL_DRB_RESULT, INITIAL_DRB_SEED_INPUT}, event::{EventType, HotShotAction, LeafInfo}, - message::Proposal, - simple_certificate::{QuorumCertificate, UpgradeCertificate}, - simple_vote::UpgradeProposalData, + message::{Proposal, UpgradeLock}, + simple_certificate::{NextEpochQuorumCertificate2, QuorumCertificate, UpgradeCertificate}, + simple_vote::{NextEpochQuorumData2, QuorumData2, UpgradeProposalData, VersionedVoteData}, traits::{block_contents::vid_commitment, node_implementation::ConsensusTime, EncodeBytes}, vid::vid_scheme, }; @@ -560,6 +560,67 @@ mod persistence_tests { assert_eq!(view_number, new_view_number_for_certificate); } + #[tokio::test(flavor = "multi_thread")] + pub async fn test_next_epoch_quorum_certificate() { + setup_test(); + + let tmp = P::tmp_storage().await; + let storage = P::connect(&tmp).await; + + // test that next epoch qc2 does not exist + assert_eq!( + storage.load_next_epoch_quorum_certificate().await.unwrap(), + None + ); + + let upgrade_lock = UpgradeLock::::new(); + + let genesis_view = ViewNumber::genesis(); + + let data: NextEpochQuorumData2 = QuorumData2 { + leaf_commit: Leaf2::genesis(&ValidatedState::default(), &NodeState::default()) + .await + .commit(), + epoch: EpochNumber::new(1), + } + .into(); + + let versioned_data = + VersionedVoteData::new_infallible(data.clone(), genesis_view, &upgrade_lock).await; + + let bytes: [u8; 32] = versioned_data.commit().into(); + + let next_epoch_qc = NextEpochQuorumCertificate2::new( + data, + Commitment::from_raw(bytes), + genesis_view, + None, + PhantomData, + ); + + let res = storage + .store_next_epoch_quorum_certificate(next_epoch_qc.clone()) + .await; + assert!(res.is_ok()); + + let res = storage.load_next_epoch_quorum_certificate().await.unwrap(); + let view_number = res.unwrap().view_number; + assert_eq!(view_number, ViewNumber::genesis()); + + let new_view_number_for_qc = ViewNumber::new(50); + let mut new_qc = next_epoch_qc.clone(); + new_qc.view_number = new_view_number_for_qc; + + let res = storage + .store_next_epoch_quorum_certificate(new_qc.clone()) + .await; + assert!(res.is_ok()); + + let res = storage.load_next_epoch_quorum_certificate().await.unwrap(); + let view_number = res.unwrap().view_number; + assert_eq!(view_number, new_view_number_for_qc); + } + #[tokio::test(flavor = "multi_thread")] pub async fn test_decide_with_failing_event_consumer() { #[derive(Clone, Copy, Debug)] diff --git a/sequencer/src/persistence/fs.rs b/sequencer/src/persistence/fs.rs index b0c65815d..b31bf4ba3 100644 --- a/sequencer/src/persistence/fs.rs +++ b/sequencer/src/persistence/fs.rs @@ -11,7 +11,9 @@ use hotshot_types::{ data::{DaProposal, QuorumProposal, QuorumProposal2, VidDisperseShare}, event::{Event, EventType, HotShotAction, LeafInfo}, message::{convert_proposal, Proposal}, - simple_certificate::{QuorumCertificate, QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{ + NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, UpgradeCertificate, + }, traits::{ block_contents::{BlockHeader, BlockPayload}, node_implementation::ConsensusTime, @@ -166,6 +168,10 @@ impl Inner { self.path.join("upgrade_certificate") } + fn next_epoch_qc(&self) -> PathBuf { + self.path.join("next_epoch_quorum_certificate") + } + /// Overwrite a file if a condition is met. /// /// The file at `path`, if it exists, is opened in read mode and passed to `pred`. If `pred` @@ -841,6 +847,41 @@ impl SequencerPersistence for Persistence { ) } + async fn store_next_epoch_quorum_certificate( + &self, + high_qc: NextEpochQuorumCertificate2, + ) -> anyhow::Result<()> { + let mut inner = self.inner.write().await; + let path = &inner.next_epoch_qc(); + + inner.replace( + path, + |_| { + // Always overwrite the previous file. + Ok(true) + }, + |mut file| { + let bytes = bincode::serialize(&high_qc).context("serializing next epoch qc")?; + file.write_all(&bytes)?; + Ok(()) + }, + ) + } + + async fn load_next_epoch_quorum_certificate( + &self, + ) -> anyhow::Result>> { + let inner = self.inner.read().await; + let path = inner.next_epoch_qc(); + if !path.is_file() { + return Ok(None); + } + let bytes = fs::read(&path).context("read")?; + Ok(Some( + bincode::deserialize(&bytes).context("deserialize next epoch qc")?, + )) + } + async fn migrate_consensus( &self, _migrate_leaf: fn(Leaf) -> Leaf2, diff --git a/sequencer/src/persistence/no_storage.rs b/sequencer/src/persistence/no_storage.rs index e80e89205..697fa8bbd 100644 --- a/sequencer/src/persistence/no_storage.rs +++ b/sequencer/src/persistence/no_storage.rs @@ -12,7 +12,7 @@ use hotshot_types::{ data::{DaProposal, QuorumProposal, QuorumProposal2, VidDisperseShare}, event::{Event, EventType, HotShotAction, LeafInfo}, message::Proposal, - simple_certificate::{QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{NextEpochQuorumCertificate2, QuorumCertificate2, UpgradeCertificate}, utils::View, vid::VidSchemeType, }; @@ -170,4 +170,17 @@ impl SequencerPersistence for NoStorage { ) -> anyhow::Result<()> { Ok(()) } + + async fn store_next_epoch_quorum_certificate( + &self, + _high_qc: NextEpochQuorumCertificate2, + ) -> anyhow::Result<()> { + Ok(()) + } + + async fn load_next_epoch_quorum_certificate( + &self, + ) -> anyhow::Result>> { + Ok(None) + } } diff --git a/sequencer/src/persistence/sql.rs b/sequencer/src/persistence/sql.rs index ec64b2e6d..4348b72e7 100644 --- a/sequencer/src/persistence/sql.rs +++ b/sequencer/src/persistence/sql.rs @@ -32,7 +32,9 @@ use hotshot_types::{ data::{DaProposal, QuorumProposal, QuorumProposal2, VidDisperseShare}, event::{Event, EventType, HotShotAction, LeafInfo}, message::{convert_proposal, Proposal}, - simple_certificate::{QuorumCertificate, QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{ + NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, UpgradeCertificate, + }, traits::{ block_contents::{BlockHeader, BlockPayload}, node_implementation::ConsensusTime, @@ -1308,6 +1310,40 @@ impl SequencerPersistence for Persistence { // TODO: https://github.com/EspressoSystems/espresso-sequencer/issues/2357 Ok(()) } + + async fn store_next_epoch_quorum_certificate( + &self, + high_qc: NextEpochQuorumCertificate2, + ) -> anyhow::Result<()> { + let qc2_bytes = bincode::serialize(&high_qc).context("serializing next epoch qc")?; + let mut tx = self.db.write().await?; + tx.upsert( + "next_epoch_quorum_certificate", + ["id", "data"], + ["id"], + [(true, qc2_bytes)], + ) + .await?; + tx.commit().await + } + + async fn load_next_epoch_quorum_certificate( + &self, + ) -> anyhow::Result>> { + let result = self + .db + .read() + .await? + .fetch_optional("SELECT * FROM next_epoch_quorum_certificate where id = true") + .await?; + + result + .map(|row| { + let bytes: Vec = row.get("data"); + anyhow::Result::<_>::Ok(bincode::deserialize(&bytes)?) + }) + .transpose() + } } #[async_trait] diff --git a/types/src/v0/traits.rs b/types/src/v0/traits.rs index 4728c644e..749f1e6cb 100644 --- a/types/src/v0/traits.rs +++ b/types/src/v0/traits.rs @@ -497,8 +497,10 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { } }; - // TODO load from storage - let next_epoch_high_qc = None; + let next_epoch_high_qc = self + .load_next_epoch_quorum_certificate() + .await + .context("loading next epoch qc")?; let (leaf, high_qc, anchor_view) = match self .load_anchor_leaf() .await @@ -697,6 +699,15 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { None => Ok(ViewNumber::genesis()), } } + + async fn store_next_epoch_quorum_certificate( + &self, + high_qc: NextEpochQuorumCertificate2, + ) -> anyhow::Result<()>; + + async fn load_next_epoch_quorum_certificate( + &self, + ) -> anyhow::Result>>; } #[async_trait] @@ -836,10 +847,9 @@ impl Storage for Arc

{ async fn update_next_epoch_high_qc2( &self, - _high_qc: NextEpochQuorumCertificate2, + high_qc: NextEpochQuorumCertificate2, ) -> anyhow::Result<()> { - // TODO - Ok(()) + (**self).store_next_epoch_quorum_certificate(high_qc).await } }