Skip to main content

hotmint_consensus/
application.rs

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