Skip to main content

drive_abci/abci/app/
check_tx.rs

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