Skip to main content

hotmint_consensus/
application.rs

1use ruc::*;
2
3use hotmint_types::Block;
4use hotmint_types::Height;
5use hotmint_types::block::BlockHash;
6use hotmint_types::context::{BlockContext, TxContext};
7use hotmint_types::evidence::EquivocationProof;
8use hotmint_types::validator::ValidatorId;
9use hotmint_types::validator_update::EndBlockResponse;
10
11/// Result of transaction validation, including priority and gas for mempool ordering.
12#[derive(Debug, Clone)]
13pub struct TxValidationResult {
14    /// Whether the transaction is valid.
15    pub valid: bool,
16    /// Priority for mempool ordering (higher = included first).
17    /// Applications typically derive this from gas price / fee.
18    pub priority: u64,
19    /// Gas units this transaction will consume. Used by `collect_payload`
20    /// to enforce `max_gas_per_block` limits.  Default: 0 (no gas accounting).
21    pub gas_wanted: u64,
22}
23
24impl TxValidationResult {
25    pub fn accept(priority: u64) -> Self {
26        Self {
27            valid: true,
28            priority,
29            gas_wanted: 0,
30        }
31    }
32
33    pub fn accept_with_gas(priority: u64, gas_wanted: u64) -> Self {
34        Self {
35            valid: true,
36            priority,
37            gas_wanted,
38        }
39    }
40
41    pub fn reject() -> Self {
42        Self {
43            valid: false,
44            priority: 0,
45            gas_wanted: 0,
46        }
47    }
48}
49
50/// Application info returned by [`Application::info`].
51///
52/// Used on startup to reconcile the application's last committed state with
53/// the consensus engine's persisted state.
54#[derive(Debug, Clone, Default)]
55pub struct AppInfo {
56    /// The height of the last block the application has committed.
57    pub last_block_height: Height,
58    /// The app_hash after the last committed block.
59    pub last_block_app_hash: BlockHash,
60}
61
62/// Application interface for the consensus engine.
63///
64/// The lifecycle for each committed block:
65/// 1. `execute_block` — receives all decoded transactions at once; returns
66///    validator updates and events
67/// 2. `on_commit` — notification after the block is finalized
68///
69/// For block proposal:
70/// - `create_payload` — build the payload bytes for a new block
71///
72/// For validation (before voting):
73/// - `validate_block` — full block validation
74/// - `validate_tx` — individual transaction validation for mempool
75///
76/// For evidence:
77/// - `on_evidence` — called when equivocation is detected
78///
79/// All methods have default no-op implementations.
80pub trait Application: Send + Sync {
81    /// Return the application's last committed height and app_hash.
82    ///
83    /// Called on startup so the consensus engine can detect state divergence
84    /// between its persisted state and the application. Equivalent to
85    /// CometBFT's `Info` RPC.
86    fn info(&self) -> AppInfo {
87        AppInfo::default()
88    }
89
90    /// Initialize the application with genesis state.
91    ///
92    /// Called once before the first block when the chain starts from height 0.
93    /// The application should use this to set its initial state (e.g. genesis
94    /// accounts, initial parameters). Returns the initial app_hash.
95    ///
96    /// Equivalent to CometBFT's `InitChain`.
97    fn init_chain(&self, _app_state: &[u8]) -> Result<BlockHash> {
98        Ok(BlockHash::GENESIS)
99    }
100
101    /// Create a payload for a new block proposal.
102    /// Typically pulls transactions from the mempool.
103    ///
104    /// If your mempool is async, use `tokio::runtime::Handle::current().block_on(..)`
105    /// to bridge into this synchronous callback.
106    fn create_payload(&self, _ctx: &BlockContext) -> Vec<u8> {
107        vec![]
108    }
109
110    /// Validate a proposed block before voting.
111    fn validate_block(&self, _block: &Block, _ctx: &BlockContext) -> bool {
112        true
113    }
114
115    /// Validate a single transaction for mempool admission.
116    ///
117    /// Returns a [`TxValidationResult`] with `valid` and `priority`.
118    /// Priority determines ordering in the mempool (higher = included first).
119    ///
120    /// An optional [`TxContext`] provides the current chain height and epoch,
121    /// which can be useful for state-dependent validation (nonce checks, etc.).
122    fn validate_tx(&self, _tx: &[u8], _ctx: Option<&TxContext>) -> TxValidationResult {
123        TxValidationResult::accept(0)
124    }
125
126    /// Execute an entire block in one call.
127    ///
128    /// Receives all decoded transactions from the block payload at once,
129    /// allowing batch-optimised processing (bulk DB writes, parallel
130    /// signature verification, etc.).
131    ///
132    /// Return [`EndBlockResponse`] with `validator_updates` to schedule an
133    /// epoch transition, and/or `events` to emit application-defined events.
134    fn execute_block(&self, _txs: &[&[u8]], _ctx: &BlockContext) -> Result<EndBlockResponse> {
135        Ok(EndBlockResponse::default())
136    }
137
138    /// Called when a block is committed to the chain (notification).
139    fn on_commit(&self, _block: &Block, _ctx: &BlockContext) -> Result<()> {
140        Ok(())
141    }
142
143    /// Called when equivocation (double-voting) is detected.
144    /// The application can use this to implement slashing.
145    fn on_evidence(&self, _proof: &EquivocationProof) -> Result<()> {
146        Ok(())
147    }
148
149    /// Called at epoch boundaries with validators whose commit-QC sign rate
150    /// fell below the liveness threshold (>50% missed).
151    ///
152    /// The application can use this to apply downtime slashing.
153    /// Each entry contains `(validator_id, missed_commits, total_commits)`.
154    fn on_offline_validators(&self, _offline: &[crate::liveness::OfflineEvidence]) -> Result<()> {
155        Ok(())
156    }
157
158    /// Generate a vote extension for the given block (ABCI++ Vote Extensions).
159    /// Called before casting a Vote2 (second-phase vote).
160    /// Returns None to skip extension (default behavior).
161    fn extend_vote(&self, _block: &Block, _ctx: &BlockContext) -> Option<Vec<u8>> {
162        None
163    }
164
165    /// Verify a vote extension received from another validator.
166    /// Called when processing Vote2 messages that carry extensions.
167    /// Returns true if the extension is valid (default: accept all).
168    fn verify_vote_extension(
169        &self,
170        _extension: &[u8],
171        _block_hash: &BlockHash,
172        _validator: ValidatorId,
173    ) -> bool {
174        true
175    }
176
177    /// Query application state.
178    ///
179    /// Returns a [`hotmint_types::QueryResponse`] containing the result data and an optional
180    /// Merkle proof that allows light clients to verify the result against the
181    /// block's `app_hash` without trusting the full node.
182    fn query(&self, _path: &str, _data: &[u8]) -> Result<hotmint_types::QueryResponse> {
183        Ok(hotmint_types::QueryResponse::default())
184    }
185
186    /// List available state snapshots for state sync.
187    fn list_snapshots(&self) -> Vec<hotmint_types::sync::SnapshotInfo> {
188        vec![]
189    }
190
191    /// Load a chunk of a snapshot at the given height.
192    fn load_snapshot_chunk(&self, _height: hotmint_types::Height, _chunk_index: u32) -> Vec<u8> {
193        vec![]
194    }
195
196    /// Offer a snapshot to the application for state sync.
197    fn offer_snapshot(
198        &self,
199        _snapshot: &hotmint_types::sync::SnapshotInfo,
200    ) -> hotmint_types::sync::SnapshotOfferResult {
201        hotmint_types::sync::SnapshotOfferResult::Reject
202    }
203
204    /// Apply a snapshot chunk received during state sync.
205    fn apply_snapshot_chunk(
206        &self,
207        _chunk: Vec<u8>,
208        _chunk_index: u32,
209    ) -> hotmint_types::sync::ChunkApplyResult {
210        hotmint_types::sync::ChunkApplyResult::Abort
211    }
212
213    /// Whether this application produces and verifies `app_hash` state roots.
214    ///
215    /// Applications that do not maintain a deterministic state root (e.g. the
216    /// embedded [`NoopApplication`] used by fullnodes without an ABCI backend)
217    /// should return `false`.  Sync will then bypass the app_hash equality
218    /// check and accept the chain's authoritative value, allowing the node to
219    /// follow a chain produced by peers running a real application.
220    fn tracks_app_hash(&self) -> bool {
221        true
222    }
223}
224
225/// No-op application stub for testing and fullnode-without-ABCI mode.
226pub struct NoopApplication;
227
228impl Application for NoopApplication {
229    /// NoopApplication does not maintain state, so app_hash tracking is skipped.
230    fn tracks_app_hash(&self) -> bool {
231        false
232    }
233}