//! Stateful application that records each block's height in QMDB. use crate::types::{Block, Database, Scheme}; use commonware_consensus::{ Heightable as _, marshal::ancestry::Ancestry, simplex::types::Context, types::Height, }; use commonware_cryptography::{ Digestible as _, bls12381::primitives::variant::MinSig, ed25519, sha256, }; use commonware_glue::{ dkg::reshare::Input as ReshareInput, stateful::{ Application, Finality, Input, Proposed, db::{DatabaseSet, Merkleized as _, Unmerkleized as _}, }, }; use commonware_runtime::{Clock, Metrics, Spawner, Storage}; use commonware_storage::qmdb::sync::Target; use commonware_utils::{non_empty_range, sequence::U64}; use futures::StreamExt; const HEIGHT_KEY: U64 = U64::new(0); /// Application logic: every non-genesis block writes its height to one fixed key. #[derive(Clone)] pub struct App { genesis: Block, } impl App { /// Create the application with its genesis block. pub const fn new(genesis: Block) -> Self { Self { genesis } } async fn execute( height: Height, batches: as DatabaseSet>::Unmerkleized, ) -> as DatabaseSet>::Merkleized { batches .write(HEIGHT_KEY, Some(U64::new(height.get()))) .merkleize() .await .expect("height write must merkleize") } } impl Application for App where E: Spawner + Metrics + Clock + Storage, { type SigningScheme = Scheme; type Context = Context; type Block = Block; type Databases = Database; type Captured = (); type Provider = (); type Input = ReshareInput<(), MinSig, ed25519::PrivateKey>; async fn genesis(&mut self) -> Self::Block { self.genesis.clone() } async fn propose( &mut self, context: (E, Self::Context), mut ancestry: impl Ancestry, batches: >::Unmerkleized, input: Input, ) -> Option> { // The `reshare::Application` wrapper selected and fetched the payload. let payload = input.upstream.payload; let parent = ancestry.next().await?; let height = parent.height().next(); let merkleized = Self::execute(height, batches).await; let bounds = merkleized.bounds(); let block = Block::new( context.1, parent.digest(), height, Target::new( merkleized.root(), non_empty_range!(bounds.inactivity_floor, bounds.tip.size), ), payload, ); Some(Proposed { block, merkleized }) } async fn process( &mut self, _context: (E, Self::Context), ancestry: impl Ancestry, batches: >::Unmerkleized, _finality: Finality, ) -> Option<>::Merkleized> { // TODO (#5509): Explore direct composition of stateful and DKG validation. // // Validation from higher layers: // - Epoch validation is handled by `Deferred` // - QMDB root / range validation is handled by `stateful::Application` // - Reshare `Payload` validation is handled by `reshare::Application` let height = ancestry.peek()?.height(); Some(Self::execute(height, batches).await) } async fn capture( &mut self, _context: (E, Self::Context), _block: &Self::Block, _batches: &>::Merkleized, _readers: >::Readers, ) { } async fn finalized( &mut self, _context: (E, Self::Context), _block: &Self::Block, _captured: Self::Captured, _readers: >::Readers, ) { } fn sync_targets(block: &Self::Block) -> >::SyncTargets { Target::new(block.state_root, block.range.clone()) } }