tenderdash_abci/
application.rs

1//! ABCI application interface.
2
3use tenderdash_proto::abci::{ExecTxResult, ValidatorSetUpdate};
4use tracing::{debug, error};
5
6use crate::proto::{
7    abci,
8    abci::{request, response},
9};
10
11/// An ABCI application.
12pub trait Application {
13    /// Echo back the same message as provided in the request.
14    fn echo(
15        &self,
16        request: abci::RequestEcho,
17    ) -> Result<abci::ResponseEcho, abci::ResponseException> {
18        Ok(abci::ResponseEcho {
19            message: request.message,
20        })
21    }
22
23    /// Signals that messages queued on the client should be flushed to the
24    /// server.
25    fn flush(
26        &self,
27        _request: abci::RequestFlush,
28    ) -> Result<abci::ResponseFlush, abci::ResponseException> {
29        Ok(Default::default())
30    }
31
32    /// Provide information about the ABCI application.
33    fn info(
34        &self,
35        request: abci::RequestInfo,
36    ) -> Result<abci::ResponseInfo, abci::ResponseException> {
37        if !check_version(&request.abci_version) {
38            return Err(abci::ResponseException {
39                error: format!(
40                    "version mismatch: tenderdash {} vs our {}",
41                    request.version,
42                    crate::proto::ABCI_VERSION
43                ),
44            });
45        }
46
47        Ok(Default::default())
48    }
49
50    /// Called once upon genesis.
51    fn init_chain(
52        &self,
53        _request: abci::RequestInitChain,
54    ) -> Result<abci::ResponseInitChain, abci::ResponseException> {
55        Ok(Default::default())
56    }
57
58    /// Query the application for data at the current or past height.
59    fn query(
60        &self,
61        _request: abci::RequestQuery,
62    ) -> Result<abci::ResponseQuery, abci::ResponseException> {
63        Ok(Default::default())
64    }
65
66    /// Check the given transaction before putting it into the local mempool.
67    fn check_tx(
68        &self,
69        _request: abci::RequestCheckTx,
70    ) -> Result<abci::ResponseCheckTx, abci::ResponseException> {
71        Ok(Default::default())
72    }
73
74    /// Used during state sync to discover available snapshots on peers.
75    fn list_snapshots(
76        &self,
77        _request: abci::RequestListSnapshots,
78    ) -> Result<abci::ResponseListSnapshots, abci::ResponseException> {
79        Ok(Default::default())
80    }
81
82    /// Called when bootstrapping the node using state sync.
83    fn offer_snapshot(
84        &self,
85        _request: abci::RequestOfferSnapshot,
86    ) -> Result<abci::ResponseOfferSnapshot, abci::ResponseException> {
87        Ok(Default::default())
88    }
89
90    /// Used during state sync to retrieve chunks of snapshots from peers.
91    fn load_snapshot_chunk(
92        &self,
93        _request: abci::RequestLoadSnapshotChunk,
94    ) -> Result<abci::ResponseLoadSnapshotChunk, abci::ResponseException> {
95        Ok(Default::default())
96    }
97
98    /// Apply the given snapshot chunk to the application's state.
99    fn apply_snapshot_chunk(
100        &self,
101        _request: abci::RequestApplySnapshotChunk,
102    ) -> Result<abci::ResponseApplySnapshotChunk, abci::ResponseException> {
103        Ok(Default::default())
104    }
105
106    fn extend_vote(
107        &self,
108        _request: abci::RequestExtendVote,
109    ) -> Result<abci::ResponseExtendVote, abci::ResponseException> {
110        Ok(Default::default())
111    }
112
113    fn finalize_block(
114        &self,
115        _request: abci::RequestFinalizeBlock,
116    ) -> Result<abci::ResponseFinalizeBlock, abci::ResponseException> {
117        Ok(Default::default())
118    }
119
120    fn prepare_proposal(
121        &self,
122        _request: abci::RequestPrepareProposal,
123    ) -> Result<abci::ResponsePrepareProposal, abci::ResponseException> {
124        Ok(Default::default())
125    }
126
127    fn process_proposal(
128        &self,
129        _request: abci::RequestProcessProposal,
130    ) -> Result<abci::ResponseProcessProposal, abci::ResponseException> {
131        Ok(Default::default())
132    }
133
134    fn verify_vote_extension(
135        &self,
136        _request: abci::RequestVerifyVoteExtension,
137    ) -> Result<abci::ResponseVerifyVoteExtension, abci::ResponseException> {
138        Ok(Default::default())
139    }
140}
141
142pub trait RequestDispatcher {
143    /// Executes the relevant application method based on the type of the
144    /// request, and produces the corresponding response.
145    ///
146    /// `RequestDispatcher` can indicate that it will no longer process new
147    /// requests by returning `None` variant.
148    fn handle(&self, request: abci::Request) -> Option<abci::Response>;
149}
150
151// Implement `RequestDispatcher` for all `Application`s.
152impl<A: Application> RequestDispatcher for A {
153    fn handle(&self, request: abci::Request) -> Option<abci::Response> {
154        #[cfg(feature = "tracing-span")]
155        let _span = super::tracing_span::span(request.clone().value?);
156        tracing::trace!(?request, "received ABCI request");
157
158        let response: response::Value = match request.value? {
159            request::Value::Echo(req) => self.echo(req).map(|v| v.into()),
160            request::Value::Flush(req) => self.flush(req).map(|v| v.into()),
161            request::Value::Info(req) => self.info(req).map(|v| v.into()),
162            request::Value::InitChain(req) => self.init_chain(req).map(|v| v.into()),
163            request::Value::Query(req) => self.query(req).map(|v| v.into()),
164            request::Value::CheckTx(req) => self.check_tx(req).map(|v| v.into()),
165            request::Value::OfferSnapshot(req) => self.offer_snapshot(req).map(|v| v.into()),
166            request::Value::LoadSnapshotChunk(req) => {
167                self.load_snapshot_chunk(req).map(|v| v.into())
168            },
169            request::Value::ApplySnapshotChunk(req) => {
170                self.apply_snapshot_chunk(req).map(|v| v.into())
171            },
172            request::Value::ListSnapshots(req) => self.list_snapshots(req).map(|v| v.into()),
173            request::Value::PrepareProposal(req) => self.prepare_proposal(req).map(|v| v.into()),
174            request::Value::ProcessProposal(req) => self.process_proposal(req).map(|v| v.into()),
175            request::Value::FinalizeBlock(req) => self.finalize_block(req).map(|v| v.into()),
176            request::Value::ExtendVote(req) => self.extend_vote(req).map(|v| v.into()),
177            request::Value::VerifyVoteExtension(req) => {
178                self.verify_vote_extension(req).map(|v| v.into())
179            },
180        }
181        .unwrap_or_else(|e| e.into());
182
183        if let response::Value::Exception(_) = response {
184            tracing::error!(?response, "sending ABCI exception");
185        } else {
186            let response_log = serialize_response_for_logging(&response);
187            tracing::trace!(?response_log, "sending ABCI response");
188        };
189
190        Some(abci::Response {
191            value: Some(response),
192        })
193    }
194}
195
196/// Serialize message for logging.
197///
198/// This macro is used to serialize the message for logging.
199/// When `serde` feature is enabled, it uses `serde_json`, otherwise, it uses
200/// `format!` macro.
201macro_rules! serialize {
202    ($($key:expr => $value:expr),* $(,)?) => {
203        {
204            #[cfg(feature = "serde")]
205            {
206                serde_json::json!({ $($key: $value),* }).to_string()
207            }
208
209            #[cfg(not(feature = "serde"))]
210            {
211                format!(stringify!($($key " {:?}",)*), $($value,)*)
212            }
213        }
214    };
215}
216
217fn serialize_response_for_logging(response: &response::Value) -> String {
218    match response {
219        response::Value::PrepareProposal(response) => {
220            let tx_records_hex: Vec<String> = response
221                .tx_records
222                .iter()
223                .map(|tx_record| {
224                    // Convert each byte array in tx_record to hex string
225                    let tx_hex = hex::encode(&tx_record.tx);
226                    serialize!(
227                        "action" => tx_record.action, // Adjust according to actual fields
228                        "tx" => tx_hex,
229                    )
230                    .to_string()
231                })
232                .collect();
233
234            let app_hash_hex = hex::encode(&response.app_hash);
235
236            let tx_results_hex: Vec<String> = exec_tx_results_to_string(&response.tx_results);
237
238            let consensus_params = format!("{:?}", response.consensus_param_updates);
239
240            let validator_set_update =
241                validator_set_update_to_string(response.validator_set_update.as_ref());
242
243            serialize!(
244                "tx_records" => tx_records_hex,
245                "app_hash" => app_hash_hex,
246                "tx_results" => tx_results_hex,
247                "consensus_param_updates" => consensus_params,
248                "core_chain_lock_update" => response.core_chain_lock_update,
249                "validator_set_update" => validator_set_update,
250            )
251            .to_string()
252        },
253        response::Value::ProcessProposal(response) => {
254            let status_string = match response.status {
255                0 => "Unknown",
256                1 => "Accepted",
257                2 => "Rejected",
258                _ => "Unknown(too high)",
259            };
260
261            let app_hash_hex = hex::encode(&response.app_hash);
262
263            let tx_results_hex: Vec<String> = exec_tx_results_to_string(&response.tx_results);
264
265            let consensus_params = format!("{:?}", response.consensus_param_updates);
266
267            let validator_set_update =
268                validator_set_update_to_string(response.validator_set_update.as_ref());
269
270            serialize!(
271                "status" => status_string,
272                "app_hash" => app_hash_hex,
273                "tx_results" => tx_results_hex,
274                "consensus_param_updates" => consensus_params,
275                "validator_set_update" => validator_set_update,
276            )
277            .to_string()
278        },
279
280        value => format!("{:?}", value),
281    }
282}
283
284fn exec_tx_results_to_string(tx_results: &[ExecTxResult]) -> Vec<String> {
285    tx_results
286        .iter()
287        .map(|tx_result| {
288            let data_hex = hex::encode(&tx_result.data);
289
290            // Assuming `Event` is another complex type, you would serialize it similarly.
291            // Here, we'll just represent events as an array of placeholders. You should
292            // replace this with the actual serialization of `Event`.
293            let events_serialized = format!("{:?}", tx_result.events);
294
295            serialize!(
296                "code" => tx_result.code,
297                "data" =>data_hex,
298                "log" => tx_result.log,
299                "info" => tx_result.info,
300                "gas_used" => tx_result.gas_used,
301                "events" => events_serialized,
302                "codespace" => tx_result.codespace,
303            )
304            .to_string()
305        })
306        .collect()
307}
308
309/// Serialize `ValidatorSetUpdate` to string for logging.
310fn validator_set_update_to_string(validator_set_update: Option<&ValidatorSetUpdate>) -> String {
311    validator_set_update
312        .as_ref()
313        .map(|validator_set_update| {
314            let quorum_hash_hex = hex::encode(&validator_set_update.quorum_hash);
315
316            let validator_updates_string: Vec<String> = validator_set_update
317                .validator_updates
318                .iter()
319                .map(|validator_update| {
320                    let pro_tx_hash_hex = hex::encode(&validator_update.pro_tx_hash);
321                    serialize!(
322                        "pub_key" => validator_update.pub_key,
323                        "power" => validator_update.power,
324                        "pro_tx_hash" => pro_tx_hash_hex,
325                        "node_address" => validator_update.node_address,
326                    )
327                    .to_string()
328                })
329                .collect();
330            serialize!(
331                "validator_updates" => validator_updates_string,
332                "threshold_public_key" => validator_set_update.threshold_public_key,
333                "quorum_hash" => quorum_hash_hex,
334            )
335            .to_string()
336        })
337        .unwrap_or("None".to_string())
338}
339
340/// Check if ABCI version sent by Tenderdash matches version of linked protobuf
341/// data objects.
342///
343/// You should use this function inside [Application::info()] handler, to ensure
344/// that the protocol versions match. Match is determined based on Semantic
345/// Versioning rules, as defined for '^' operator.
346///
347/// ## Examples
348///
349/// ### Using `check_version` in `Application::info` handler
350///
351/// ```should_panic
352/// use tenderdash_abci::{check_version, Application};
353/// use tenderdash_abci::proto::abci::{RequestInfo, ResponseInfo, ResponseException};
354///
355/// # let request = RequestInfo{
356/// #  abci_version: String::from("108.234.356"),
357/// #  ..Default::default()
358/// # };
359/// struct AbciApp{}
360///
361/// impl tenderdash_abci::Application for AbciApp {
362///   fn info(&self, request: RequestInfo) -> Result<ResponseInfo, ResponseException> {
363///     if !check_version(&request.abci_version) {
364///       panic!("abci version mismatch");
365///     }
366///     Ok(Default::default())
367///   }
368/// }
369///
370/// # let app = AbciApp{};
371/// # app.info(request);
372/// ```
373pub fn check_version(tenderdash_version: &str) -> bool {
374    match_versions(tenderdash_version, tenderdash_proto::ABCI_VERSION)
375}
376
377/// Check if Tenderdash provides ABCI interface compatible with our library.
378///
379/// Tenderdash is compatible if its abci version matches the abci version of
380/// linked protobuf data objects, eg. version provided in
381/// `rs_tenderdash_abci_version` argument. The PATCH level can be ignored, as is
382/// should be backwards-compatible.
383///
384/// For example, Tenderdash abci version `1.23.2` should work with
385/// rs-tenderdash-abci linked with abci version `1.23.1` and `1.22.1`, but not
386/// with `1.24.1` or `0.23.1`.
387fn match_versions(tenderdash_version: &str, rs_tenderdash_abci_version: &str) -> bool {
388    let rs_tenderdash_abci_version = semver::Version::parse(rs_tenderdash_abci_version)
389        .expect("cannot parse protobuf library version");
390    let tenderdash_version =
391        semver::Version::parse(tenderdash_version).expect("cannot parse tenderdash version");
392
393    let requirement = match rs_tenderdash_abci_version.pre.as_str() {
394        "" => format!(
395            "^{}.{}",
396            rs_tenderdash_abci_version.major, rs_tenderdash_abci_version.minor
397        ),
398        pre => format!(
399            "^{}.{}.0-{}",
400            rs_tenderdash_abci_version.major, rs_tenderdash_abci_version.minor, pre
401        ),
402    };
403
404    let matcher = semver::VersionReq::parse(&requirement).expect("cannot parse tenderdash version");
405
406    match matcher.matches(&tenderdash_version) {
407        true => {
408            debug!(
409                "version match(rs-tenderdash-abci proto version: {}), tenderdash server proto version {} = {}",
410                rs_tenderdash_abci_version, tenderdash_version, requirement
411            );
412            true
413        },
414        false => {
415            error!(
416                "version mismatch(rs-tenderdash-abci proto version: {}), tenderdash server proto version {} != {}",
417                rs_tenderdash_abci_version, tenderdash_version, requirement
418            );
419            false
420        },
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use super::match_versions;
427
428    fn setup_logs() {
429        #[cfg(feature = "server")]
430        tracing_subscriber::fmt()
431            .with_env_filter(tracing_subscriber::EnvFilter::new("trace"))
432            .try_init()
433            .ok();
434    }
435
436    /// test_versions! {} (td_version, our_version, expected); }
437    // Test if various combinations of versions match
438    //
439    // ## Arguments
440    //
441    // * `td_version` - Tenderdash version, as returned by the Tenderdash
442    // * `our_version` - our version - version of rs-tenderdash-abci library
443    // * `expected` - expected result - true or false
444    //
445    macro_rules! test_versions {
446        ($($name:ident: $value:expr,)*) => {
447        $(
448            #[test]
449            fn $name() {
450                setup_logs();
451                let (td, our, expect) = $value;
452                assert_eq!(match_versions(td, our),expect,
453                    "tenderdash version: {}, rs-tenderdash-abci version: {}, expect: {}", td, our,expect);
454            }
455        )*
456        }
457    }
458
459    test_versions! {
460        // rs-tenderdash-abci should be able to connect to any Tenderdash that is backwards-compatible
461        // It means that:
462        // * MAJOR of Tenderdash must match MAJOR of rs-tenderdash-abci
463        // * MINOR of Tenderdash must be greater or equal to MINOR of rs-tenderdash-abci
464        // * PATCH of Tenderdash can be anything
465
466        // MAJOR 0
467
468        // vesions match
469        test_major_0: ("0.23.1", "0.23.1", true),
470        // tenderdash is newer than our library, but it's backwards-compatible
471        test_major_0_old_minor: ("0.23.1", "0.22.1", false),
472        // tenderdash patch level is higher than ours; it should not matter
473        test_major_0_new_patch: ("0.23.2", "0.23.1", true),
474        // tenderdash patch level is lower than ours; it should not matter
475        test_major_0_old_patch: ("0.23.0", "0.23.1", true),
476        // tenderdash is older than our library, it should not match
477        test_major_0_new_minor: ("0.23.1", "0.24.1", false),
478        test_major_0_new_major: ("0.23.1", "1.23.1", false),
479
480        // MAJOR 1
481
482        test_major_1: ("1.23.1", "1.23.1", true),
483        // tenderdash is newer than our library, but it's backwards-compatible
484        test_major_1_old_minor: ("1.23.1", "1.22.1", true),
485        // tenderdash patch level is higher than ours; it should not matter
486        test_major_1_new_patch: ("1.23.2", "1.23.1", true),
487        // tenderdash patch level is lower than ours; it should not matter
488        test_major_1_old_patch: ("1.23.0", "1.23.1", true),
489        // tenderdash is older than our library, it should not match
490        test_major_1_new_minor: ("1.23.1", "1.24.1", false),
491        test_major_1_old_major: ("1.23.1", "0.23.1", false),
492
493        test_dev_td_newer: ("0.1.2-dev.1", "0.1.0", false),
494        test_dev_equal: ("0.1.0-dev.1","0.1.0-dev.1",true),
495        test_dev_our_newer_dev: ("0.1.0-dev.1", "0.1.0-dev.2",false),
496    }
497}