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}