Skip to main content

hiero_sdk/query/
execute.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::fmt::Debug;
4
5use hiero_sdk_proto::services;
6use tonic::transport::Channel;
7
8use crate::entity_id::ValidateChecksums;
9use crate::execute::Execute;
10use crate::query::{
11    AnyQueryData,
12    ToQueryProtobuf,
13};
14use crate::{
15    AccountId,
16    BoxGrpcFuture,
17    Error,
18    FromProtobuf,
19    Hbar,
20    Query,
21    Status,
22    TransactionId,
23};
24
25/// Describes a specific query that can be executed on the Hiero network.
26pub trait QueryExecute:
27    Sync + Send + Into<AnyQueryData> + Clone + Debug + ToQueryProtobuf + ValidateChecksums
28{
29    type Response: FromProtobuf<services::response::Response>;
30
31    /// Returns `true` if this query requires a payment to be submitted.
32    fn is_payment_required(&self) -> bool {
33        true
34    }
35
36    /// Alter the required payment amount in arbitrary ways after `get_cost` has returned.
37    fn map_cost(&self, cost: Hbar) -> Hbar {
38        cost
39    }
40
41    /// Returns `true` if this query should be retried after a back-off from the result
42    /// of a pre-check.
43    fn should_retry_pre_check(&self, _status: Status) -> bool {
44        false
45    }
46
47    /// Check whether we should retry an otherwise successful response.
48    #[allow(unused_variables)]
49    fn should_retry(&self, response: &services::Response) -> bool {
50        false
51    }
52
53    /// Returns the transaction ID that this query is for, if this query is about a transaction.
54    fn transaction_id(&self) -> Option<TransactionId> {
55        None
56    }
57
58    fn make_response(
59        &self,
60        response: services::response::Response,
61    ) -> crate::Result<Self::Response> {
62        <Self::Response as FromProtobuf<services::response::Response>>::from_protobuf(response)
63    }
64
65    /// Execute the prepared query request against the provided GRPC channel.
66    fn execute(
67        &self,
68        channel: Channel,
69        request: services::Query,
70    ) -> BoxGrpcFuture<'_, services::Response>;
71}
72
73impl<D> Execute for Query<D>
74where
75    D: QueryExecute,
76{
77    type GrpcRequest = services::Query;
78
79    type GrpcResponse = services::Response;
80
81    type Response = D::Response;
82
83    type Context = ();
84
85    fn node_account_ids(&self) -> Option<&[AccountId]> {
86        self.payment.node_account_ids()
87    }
88
89    fn transaction_id(&self) -> Option<TransactionId> {
90        self.payment.transaction_id()
91    }
92
93    fn requires_transaction_id(&self) -> bool {
94        self.data.is_payment_required()
95    }
96
97    fn operator_account_id(&self) -> Option<&AccountId> {
98        self.payment.operator_account_id()
99    }
100
101    fn should_retry_pre_check(&self, status: Status) -> bool {
102        self.data.should_retry_pre_check(status)
103    }
104
105    fn should_retry(&self, response: &Self::GrpcResponse) -> bool {
106        self.data.should_retry(response)
107    }
108
109    fn make_request(
110        &self,
111        transaction_id: Option<&TransactionId>,
112        node_account_id: AccountId,
113    ) -> crate::Result<(Self::GrpcRequest, Self::Context)> {
114        let payment = if self.data.is_payment_required() {
115            Some(self.payment.make_request(transaction_id, node_account_id)?.0)
116        } else {
117            None
118        };
119
120        let header = services::QueryHeader { response_type: 0, payment };
121
122        Ok((self.data.to_query_protobuf(header), ()))
123    }
124
125    fn execute(
126        &self,
127        channel: Channel,
128        request: Self::GrpcRequest,
129    ) -> BoxGrpcFuture<'_, Self::GrpcResponse> {
130        self.data.execute(channel, request)
131    }
132
133    fn make_response(
134        &self,
135        response: Self::GrpcResponse,
136        _context: Self::Context,
137        _node_account_id: AccountId,
138        _transaction_id: Option<&TransactionId>,
139    ) -> crate::Result<Self::Response> {
140        pb_getf!(response, response).and_then(|response| self.data.make_response(response))
141    }
142
143    fn make_error_pre_check(
144        &self,
145        status: crate::Status,
146        transaction_id: Option<&TransactionId>,
147        _response: Self::GrpcResponse,
148    ) -> crate::Error {
149        if let Some(transaction_id) = self.data.transaction_id() {
150            crate::Error::QueryPreCheckStatus { status, transaction_id: Box::new(transaction_id) }
151        } else if let Some(transaction_id) = transaction_id {
152            crate::Error::QueryPaymentPreCheckStatus {
153                status,
154                transaction_id: Box::new(*transaction_id),
155            }
156        } else {
157            crate::Error::QueryNoPaymentPreCheckStatus { status }
158        }
159    }
160
161    fn response_pre_check_status(response: &Self::GrpcResponse) -> crate::Result<i32> {
162        Ok(response_header(&response.response)?.node_transaction_precheck_code)
163    }
164}
165
166impl<D: QueryExecute + ValidateChecksums> ValidateChecksums for Query<D> {
167    fn validate_checksums(&self, ledger_id: &crate::ledger_id::RefLedgerId) -> Result<(), Error> {
168        self.data.validate_checksums(ledger_id)?;
169        self.payment.validate_checksums(ledger_id)
170    }
171}
172
173pub(crate) fn response_header(
174    response: &Option<services::response::Response>,
175) -> crate::Result<&services::ResponseHeader> {
176    use services::response::Response::*;
177
178    let header = match response {
179        Some(CryptogetAccountBalance(response)) => &response.header,
180        Some(GetByKey(response)) => &response.header,
181        Some(GetBySolidityId(response)) => &response.header,
182        Some(ContractCallLocal(response)) => &response.header,
183        Some(ContractGetBytecodeResponse(response)) => &response.header,
184        Some(ContractGetInfo(response)) => &response.header,
185        Some(ContractGetRecordsResponse(response)) => &response.header,
186        Some(CryptoGetAccountRecords(response)) => &response.header,
187        Some(CryptoGetInfo(response)) => &response.header,
188        Some(CryptoGetLiveHash(response)) => &response.header,
189        Some(CryptoGetProxyStakers(response)) => &response.header,
190        Some(FileGetContents(response)) => &response.header,
191        Some(FileGetInfo(response)) => &response.header,
192        Some(TransactionGetReceipt(response)) => &response.header,
193        Some(TransactionGetRecord(response)) => &response.header,
194        Some(TransactionGetFastRecord(response)) => &response.header,
195        Some(ConsensusGetTopicInfo(response)) => &response.header,
196        Some(NetworkGetVersionInfo(response)) => &response.header,
197        Some(TokenGetInfo(response)) => &response.header,
198        Some(ScheduleGetInfo(response)) => &response.header,
199        Some(TokenGetAccountNftInfos(response)) => &response.header,
200        Some(TokenGetNftInfo(response)) => &response.header,
201        Some(TokenGetNftInfos(response)) => &response.header,
202        Some(NetworkGetExecutionTime(response)) => &response.header,
203        Some(AccountDetails(response)) => &response.header,
204        None => &None,
205    };
206
207    header.as_ref().ok_or_else(|| Error::from_protobuf("unexpected missing `header` in `Response`"))
208}