Skip to main content

drive_abci/abci/app/
check_tx.rs

1use crate::abci::app::PlatformApplication;
2use crate::abci::handler;
3use crate::error::Error;
4use crate::platform_types::platform::Platform;
5use crate::rpc::core::CoreRPCLike;
6use crate::utils::spawn_blocking_task_with_name_if_supported;
7use async_trait::async_trait;
8use std::fmt::Debug;
9use std::sync::Arc;
10use tenderdash_abci::proto::abci as proto;
11use tenderdash_abci::proto::abci::abci_application_server as grpc_abci_server;
12use tenderdash_abci::proto::tonic;
13
14/// AbciApp is an implementation of gRPC ABCI Application, as defined by Tenderdash.
15///
16/// AbciApp implements logic that should be triggered when Tenderdash performs various operations, like
17/// creating new proposal or finalizing new block.
18pub struct CheckTxAbciApplication<C>
19where
20    C: CoreRPCLike + Send + Sync + 'static,
21{
22    /// Platform
23    platform: Arc<Platform<C>>,
24    core_rpc: Arc<C>,
25}
26
27impl<C> PlatformApplication<C> for CheckTxAbciApplication<C>
28where
29    C: CoreRPCLike + Send + Sync + 'static,
30{
31    fn platform(&self) -> &Platform<C> {
32        self.platform.as_ref()
33    }
34}
35
36impl<C> CheckTxAbciApplication<C>
37where
38    C: CoreRPCLike + Send + Sync + 'static,
39{
40    /// Create new ABCI app
41    pub fn new(platform: Arc<Platform<C>>, core_rpc: Arc<C>) -> Self {
42        Self { platform, core_rpc }
43    }
44}
45
46impl<C> Debug for CheckTxAbciApplication<C>
47where
48    C: CoreRPCLike + Send + Sync + 'static,
49{
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "<CheckTxAbciApplication>")
52    }
53}
54
55#[async_trait]
56impl<C> grpc_abci_server::AbciApplication for CheckTxAbciApplication<C>
57where
58    C: CoreRPCLike + Send + Sync + 'static,
59{
60    async fn echo(
61        &self,
62        request: tonic::Request<proto::RequestEcho>,
63    ) -> Result<tonic::Response<proto::ResponseEcho>, tonic::Status> {
64        let response = handler::echo(self, request.into_inner()).map_err(error_into_status)?;
65
66        Ok(tonic::Response::new(response))
67    }
68
69    async fn check_tx(
70        &self,
71        request: tonic::Request<proto::RequestCheckTx>,
72    ) -> Result<tonic::Response<proto::ResponseCheckTx>, tonic::Status> {
73        let platform = Arc::clone(&self.platform);
74        let core_rpc = Arc::clone(&self.core_rpc);
75
76        let proto_request = request.into_inner();
77
78        let check_tx_type = proto::CheckTxType::try_from(proto_request.r#type)
79            .map_err(|_| tonic::Status::invalid_argument("invalid check tx type"))?;
80
81        let thread_name = match check_tx_type {
82            proto::CheckTxType::New => "check_tx",
83            proto::CheckTxType::Recheck => "re_check_tx",
84        };
85
86        spawn_blocking_task_with_name_if_supported(thread_name, move || {
87            let response = handler::check_tx(&platform, &core_rpc, proto_request)
88                .map_err(error_into_status)?;
89
90            Ok(tonic::Response::new(response))
91        })?
92        .await
93        .map_err(|error| tonic::Status::internal(format!("check tx panics: {}", error)))?
94    }
95}
96
97pub fn error_into_status(error: Error) -> tonic::Status {
98    match error {
99        Error::Execution(crate::error::execution::ExecutionError::CheckTxProofVerificationBusy) => {
100            tonic::Status::resource_exhausted(
101                "check tx verification capacity is temporarily unavailable",
102            )
103        }
104        error => tonic::Status::internal(error.to_string()),
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::error::execution::ExecutionError;
112    use crate::rpc::core::MockCoreRPCLike;
113
114    #[test]
115    fn error_into_status_produces_internal_status() {
116        let error = Error::Execution(ExecutionError::CorruptedCodeExecution("test error message"));
117        let status = error_into_status(error);
118
119        assert_eq!(status.code(), tonic::Code::Internal);
120        assert!(status.message().contains("test error message"));
121    }
122
123    #[test]
124    fn error_into_status_preserves_error_message() {
125        let error = Error::Execution(ExecutionError::NotInTransaction("no active transaction"));
126        let status = error_into_status(error);
127
128        assert!(status.message().contains("no active transaction"));
129    }
130
131    #[test]
132    fn error_into_status_marks_proof_capacity_as_retryable() {
133        let status = error_into_status(Error::Execution(
134            ExecutionError::CheckTxProofVerificationBusy,
135        ));
136
137        assert_eq!(status.code(), tonic::Code::ResourceExhausted);
138        assert!(!status.message().contains("proof"));
139    }
140
141    #[test]
142    fn check_tx_abci_application_debug_format() {
143        // Verify the Debug implementation for CheckTxAbciApplication produces expected output
144        let platform =
145            crate::test::helpers::setup::TestPlatformBuilder::new().build_with_mock_rpc();
146
147        let core_rpc = MockCoreRPCLike::new();
148
149        let app = CheckTxAbciApplication::new(Arc::new(platform.platform), Arc::new(core_rpc));
150
151        let debug_str = format!("{:?}", app);
152        assert_eq!(debug_str, "<CheckTxAbciApplication>");
153    }
154
155    #[test]
156    fn check_tx_abci_application_platform_returns_platform() {
157        let platform =
158            crate::test::helpers::setup::TestPlatformBuilder::new().build_with_mock_rpc();
159
160        let core_rpc = MockCoreRPCLike::new();
161
162        let app = CheckTxAbciApplication::new(Arc::new(platform.platform), Arc::new(core_rpc));
163
164        // Just verify we can call platform() without panicking
165        let _platform_ref = app.platform();
166    }
167}