Skip to main content

hiero_sdk/
transaction_receipt_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    Query,
18    Status,
19    ToProtobuf,
20    TransactionId,
21    TransactionReceipt,
22    ValidateChecksums,
23};
24
25/// Get the receipt of a transaction, given its transaction ID.
26///
27/// Once a transaction reaches consensus, then information about whether it succeeded or failed
28/// will be available until the end of the receipt period.
29///
30pub type TransactionReceiptQuery = Query<TransactionReceiptQueryData>;
31
32#[derive(Default, Clone, Debug)]
33pub struct TransactionReceiptQueryData {
34    transaction_id: Option<TransactionId>,
35    include_children: bool,
36    include_duplicates: bool,
37    validate_status: bool,
38}
39
40impl From<TransactionReceiptQueryData> for AnyQueryData {
41    #[inline]
42    fn from(data: TransactionReceiptQueryData) -> Self {
43        Self::TransactionReceipt(data)
44    }
45}
46
47impl TransactionReceiptQuery {
48    /// Get the ID of the transaction for which the receipt is being requested.
49    #[must_use]
50    pub fn get_transaction_id(&self) -> Option<TransactionId> {
51        self.data.transaction_id
52    }
53
54    /// Sets the ID of the transaction for which the receipt is being requested.
55    pub fn transaction_id(&mut self, transaction_id: TransactionId) -> &mut Self {
56        self.data.transaction_id = Some(transaction_id);
57        self
58    }
59
60    /// Whether the response should include the receipts of any child transactions spawned by the
61    /// top-level transaction with the given transaction.
62    #[must_use]
63    pub fn get_include_children(&self) -> bool {
64        self.data.include_children
65    }
66
67    /// Whether the response should include the receipts of any child transactions spawned by the
68    /// top-level transaction with the given transaction.
69    pub fn include_children(&mut self, include: bool) -> &mut Self {
70        self.data.include_children = include;
71        self
72    }
73
74    /// Whether receipts of processing duplicate transactions should be returned.
75    #[must_use]
76    pub fn get_include_duplicates(&self) -> bool {
77        self.data.include_duplicates
78    }
79
80    /// Whether receipts of processing duplicate transactions should be returned.
81    pub fn include_duplicates(&mut self, include: bool) -> &mut Self {
82        self.data.include_duplicates = include;
83        self
84    }
85
86    /// Whether the receipt status should be validated.
87    #[must_use]
88    pub fn get_validate_status(&self) -> bool {
89        self.data.validate_status
90    }
91
92    /// Whether the receipt status should be validated.
93    pub fn validate_status(&mut self, validate: bool) -> &mut Self {
94        self.data.validate_status = validate;
95        self
96    }
97}
98
99impl ToQueryProtobuf for TransactionReceiptQueryData {
100    fn to_query_protobuf(&self, header: services::QueryHeader) -> services::Query {
101        let transaction_id = self.transaction_id.to_protobuf();
102
103        services::Query {
104            query: Some(services::query::Query::TransactionGetReceipt(
105                services::TransactionGetReceiptQuery {
106                    header: Some(header),
107                    transaction_id,
108                    include_child_receipts: self.include_children,
109                    include_duplicates: self.include_duplicates,
110                },
111            )),
112        }
113    }
114}
115
116impl QueryExecute for TransactionReceiptQueryData {
117    type Response = TransactionReceipt;
118
119    fn is_payment_required(&self) -> bool {
120        false
121    }
122
123    fn transaction_id(&self) -> Option<TransactionId> {
124        self.transaction_id
125    }
126
127    fn execute(
128        &self,
129        channel: Channel,
130        request: services::Query,
131    ) -> BoxGrpcFuture<'_, services::Response> {
132        Box::pin(async {
133            CryptoServiceClient::new(channel).get_transaction_receipts(request).await
134        })
135    }
136
137    fn should_retry_pre_check(&self, status: Status) -> bool {
138        matches!(status, Status::ReceiptNotFound | Status::RecordNotFound)
139    }
140
141    fn should_retry(&self, response: &services::Response) -> bool {
142        // extract the receipt status from the receipt
143        // without altering or freeing the memory from the response
144
145        let receipt_status = {
146            let Some(services::response::Response::TransactionGetReceipt(r)) = &response.response
147            else {
148                return false;
149            };
150
151            match r.receipt.as_ref().and_then(|it| Some(Status::try_from(it.status))) {
152                Some(receipt_status) => receipt_status,
153                None => return false,
154            }
155        };
156
157        matches!(receipt_status, Ok(Status::Unknown))
158    }
159
160    fn make_response(&self, response: Response) -> crate::Result<Self::Response> {
161        let receipt =
162            TransactionReceipt::from_response_protobuf(response, self.transaction_id.as_ref())?;
163
164        if self.validate_status && receipt.status != Status::Success {
165            return Err(Error::ReceiptStatus {
166                transaction_id: self.transaction_id.map(Box::new),
167                status: receipt.status,
168            });
169        }
170
171        Ok(receipt)
172    }
173}
174
175impl ValidateChecksums for TransactionReceiptQueryData {
176    fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
177        self.transaction_id.validate_checksums(ledger_id)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use expect_test::expect;
184
185    use crate::query::ToQueryProtobuf;
186    use crate::transaction::test_helpers::TEST_TX_ID;
187    use crate::TransactionReceiptQuery;
188
189    #[test]
190    fn serialize() {
191        expect![[r#"
192            Query {
193                query: Some(
194                    TransactionGetReceipt(
195                        TransactionGetReceiptQuery {
196                            header: Some(
197                                QueryHeader {
198                                    payment: None,
199                                    response_type: AnswerOnly,
200                                },
201                            ),
202                            transaction_id: Some(
203                                TransactionId {
204                                    transaction_valid_start: Some(
205                                        Timestamp {
206                                            seconds: 1554158542,
207                                            nanos: 0,
208                                        },
209                                    ),
210                                    account_id: Some(
211                                        AccountId {
212                                            shard_num: 0,
213                                            realm_num: 0,
214                                            account: Some(
215                                                AccountNum(
216                                                    5006,
217                                                ),
218                                            ),
219                                        },
220                                    ),
221                                    scheduled: false,
222                                    nonce: 0,
223                                },
224                            ),
225                            include_duplicates: false,
226                            include_child_receipts: false,
227                        },
228                    ),
229                ),
230            }
231        "#]]
232        .assert_debug_eq(
233            &TransactionReceiptQuery::new()
234                .transaction_id(TEST_TX_ID)
235                .data
236                .to_query_protobuf(Default::default()),
237        )
238    }
239
240    #[test]
241    fn get_set_transaction_id() {
242        let mut query = TransactionReceiptQuery::new();
243        query.transaction_id(TEST_TX_ID);
244
245        assert_eq!(query.get_transaction_id(), Some(TEST_TX_ID));
246    }
247
248    // default is false for all of these, so setting it to `true` is the "interesting" state.
249    #[test]
250    fn get_set_include_children() {
251        let mut query = TransactionReceiptQuery::new();
252        query.include_children(true);
253
254        assert_eq!(query.get_include_children(), true);
255    }
256
257    #[test]
258    fn get_set_include_duplicates() {
259        let mut query = TransactionReceiptQuery::new();
260        query.include_duplicates(true);
261
262        assert_eq!(query.get_include_duplicates(), true);
263    }
264
265    #[test]
266    fn get_set_validate_status() {
267        let mut query = TransactionReceiptQuery::new();
268        query.validate_status(true);
269
270        assert_eq!(query.get_validate_status(), true);
271    }
272}