Skip to main content

hiero_sdk/
transaction_record_query.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::crypto_service_client::CryptoServiceClient;
5use hiero_sdk_proto::services::response::Response;
6use tonic::transport::Channel;
7
8use crate::ledger_id::RefLedgerId;
9use crate::query::{
10    AnyQueryData,
11    QueryExecute,
12    ToQueryProtobuf,
13};
14use crate::{
15    BoxGrpcFuture,
16    Error,
17    FromProtobuf,
18    Query,
19    Status,
20    ToProtobuf,
21    TransactionId,
22    TransactionRecord,
23    ValidateChecksums,
24};
25
26/// Get the record of a transaction, given its transaction ID.
27///
28pub type TransactionRecordQuery = Query<TransactionRecordQueryData>;
29
30#[derive(Default, Clone, Debug)]
31pub struct TransactionRecordQueryData {
32    transaction_id: Option<TransactionId>,
33    include_children: bool,
34    include_duplicates: bool,
35    validate_status: bool,
36}
37
38impl From<TransactionRecordQueryData> for AnyQueryData {
39    #[inline]
40    fn from(data: TransactionRecordQueryData) -> Self {
41        Self::TransactionRecord(data)
42    }
43}
44
45impl TransactionRecordQuery {
46    /// Get the ID of the transaction for which the record is being requested.
47    #[must_use]
48    pub fn get_transaction_id(&self) -> Option<TransactionId> {
49        self.data.transaction_id
50    }
51
52    /// Sets the ID of the transaction for which the record is being requested.
53    pub fn transaction_id(&mut self, transaction_id: TransactionId) -> &mut Self {
54        self.data.transaction_id = Some(transaction_id);
55        self
56    }
57
58    /// Whether the response should include the records of any child transactions spawned by the
59    /// top-level transaction with the given transaction.
60    #[must_use]
61    pub fn get_include_children(&self) -> bool {
62        self.data.include_children
63    }
64
65    /// Whether the response should include the records of any child transactions spawned by the
66    /// top-level transaction with the given transaction.
67    pub fn include_children(&mut self, include: bool) -> &mut Self {
68        self.data.include_children = include;
69        self
70    }
71
72    /// Whether records of processing duplicate transactions should be returned.
73    #[must_use]
74    pub fn get_include_duplicates(&self) -> bool {
75        self.data.include_duplicates
76    }
77
78    /// Whether records of processing duplicate transactions should be returned.
79    pub fn include_duplicates(&mut self, include: bool) -> &mut Self {
80        self.data.include_duplicates = include;
81        self
82    }
83
84    /// Whether records of processing duplicate transactions should be returned.
85    #[must_use]
86    pub fn get_validate_status(&self) -> bool {
87        self.data.validate_status
88    }
89
90    /// Whether the record status should be validated.
91    pub fn validate_status(&mut self, validate: bool) -> &mut Self {
92        self.data.validate_status = validate;
93        self
94    }
95}
96
97impl ToQueryProtobuf for TransactionRecordQueryData {
98    fn to_query_protobuf(&self, header: services::QueryHeader) -> services::Query {
99        let transaction_id = self.transaction_id.to_protobuf();
100
101        services::Query {
102            query: Some(services::query::Query::TransactionGetRecord(
103                services::TransactionGetRecordQuery {
104                    header: Some(header),
105                    transaction_id,
106                    include_child_records: self.include_children,
107                    include_duplicates: self.include_duplicates,
108                },
109            )),
110        }
111    }
112}
113
114impl QueryExecute for TransactionRecordQueryData {
115    type Response = TransactionRecord;
116
117    fn transaction_id(&self) -> Option<TransactionId> {
118        self.transaction_id
119    }
120
121    fn execute(
122        &self,
123        channel: Channel,
124        request: services::Query,
125    ) -> BoxGrpcFuture<'_, services::Response> {
126        Box::pin(async { CryptoServiceClient::new(channel).get_tx_record_by_tx_id(request).await })
127    }
128
129    fn should_retry_pre_check(&self, status: Status) -> bool {
130        matches!(status, Status::ReceiptNotFound | Status::RecordNotFound)
131    }
132
133    fn make_response(&self, response: Response) -> crate::Result<Self::Response> {
134        let record = TransactionRecord::from_protobuf(response)?;
135
136        if self.validate_status && record.receipt.status != Status::Success {
137            return Err(Error::ReceiptStatus {
138                transaction_id: self.transaction_id.map(Box::new),
139                status: record.receipt.status,
140            });
141        }
142
143        Ok(record)
144    }
145}
146
147impl ValidateChecksums for TransactionRecordQueryData {
148    fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
149        self.transaction_id.validate_checksums(ledger_id)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use expect_test::expect;
156
157    use crate::query::ToQueryProtobuf;
158    use crate::transaction::test_helpers::TEST_TX_ID;
159    use crate::TransactionRecordQuery;
160
161    #[test]
162    fn serialize() {
163        expect![[r#"
164            Query {
165                query: Some(
166                    TransactionGetRecord(
167                        TransactionGetRecordQuery {
168                            header: Some(
169                                QueryHeader {
170                                    payment: None,
171                                    response_type: AnswerOnly,
172                                },
173                            ),
174                            transaction_id: Some(
175                                TransactionId {
176                                    transaction_valid_start: Some(
177                                        Timestamp {
178                                            seconds: 1554158542,
179                                            nanos: 0,
180                                        },
181                                    ),
182                                    account_id: Some(
183                                        AccountId {
184                                            shard_num: 0,
185                                            realm_num: 0,
186                                            account: Some(
187                                                AccountNum(
188                                                    5006,
189                                                ),
190                                            ),
191                                        },
192                                    ),
193                                    scheduled: false,
194                                    nonce: 0,
195                                },
196                            ),
197                            include_duplicates: true,
198                            include_child_records: true,
199                        },
200                    ),
201                ),
202            }
203        "#]]
204        .assert_debug_eq(
205            &TransactionRecordQuery::new()
206                .transaction_id(TEST_TX_ID)
207                .include_children(true)
208                .include_duplicates(true)
209                .data
210                .to_query_protobuf(Default::default()),
211        )
212    }
213
214    #[test]
215    fn get_set_transaction_id() {
216        let mut query = TransactionRecordQuery::new();
217        query.transaction_id(TEST_TX_ID);
218
219        assert_eq!(query.get_transaction_id(), Some(TEST_TX_ID));
220    }
221
222    // default is false for all of these, so setting it to `true` is the "interesting" state.
223    #[test]
224    fn get_set_include_children() {
225        let mut query = TransactionRecordQuery::new();
226        query.include_children(true);
227
228        assert_eq!(query.get_include_children(), true);
229    }
230
231    #[test]
232    fn get_set_include_duplicates() {
233        let mut query = TransactionRecordQuery::new();
234        query.include_duplicates(true);
235
236        assert_eq!(query.get_include_duplicates(), true);
237    }
238
239    #[test]
240    fn get_set_validate_status() {
241        let mut query = TransactionRecordQuery::new();
242        query.validate_status(true);
243
244        assert_eq!(query.get_validate_status(), true);
245    }
246}