|
| 1 | +# Understanding Queries in Hiero SDK |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +In the Hiero SDK, **Query** is a fundamental concept. It represents a request to the Hedera network to retrieve data without changing the network state. Unlike transactions, queries typically do not alter the ledger, although they may require a small payment to cover the cost of processing. |
| 6 | + |
| 7 | +This guide explains how the `Query` class works, its relationship with `_Executable`, and how to implement your own custom queries. |
| 8 | + |
| 9 | +## Contents |
| 10 | + |
| 11 | +- [What is a Query?](#what-is-a-query) |
| 12 | +- [Architecture](#architecture) |
| 13 | + - [Inheritance Hierarchy](#inheritance-hierarchy) |
| 14 | + - [Key Concepts](#key-concepts) |
| 15 | +- [Execution Flow](#execution-flow) |
| 16 | +- [Query Payment](#query-payment) |
| 17 | + - [Automatic Cost Determination](#automatic-cost-determination) |
| 18 | +- [Retry Logic](#retry-logic) |
| 19 | +- [Building a Custom Query](#building-a-custom-query) |
| 20 | + - [Abstract Methods](#abstract-methods) |
| 21 | + - [Example Implementation](#example-implementation) |
| 22 | +- [Summary](#summary) |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## What is a Query? |
| 27 | + |
| 28 | +The `Query` class is the base class for all Hedera network queries. Queries allow you to request data from the Hedera network, such as: |
| 29 | + |
| 30 | +- Account balances |
| 31 | +- Transaction records |
| 32 | +- Token information |
| 33 | +- Topic messages |
| 34 | +- File contents |
| 35 | + |
| 36 | +Unlike transactions, queries do not change the network state. However, because processing a query consumes resources on the node, some queries require a payment in HBAR. |
| 37 | + |
| 38 | +## Architecture |
| 39 | + |
| 40 | +### Inheritance Hierarchy |
| 41 | + |
| 42 | +All queries in the SDK inherit from the `Query` class, which in turn inherits from `_Executable`. |
| 43 | + |
| 44 | +``` |
| 45 | +_Executable |
| 46 | + └── Query |
| 47 | + ├── AccountBalanceQuery |
| 48 | + ├── TransactionRecordQuery |
| 49 | + ├── TokenInfoQuery |
| 50 | + └── ... (other specific queries) |
| 51 | +``` |
| 52 | + |
| 53 | +This inheritance structure means that `Query` benefits from the unified execution engine provided by `_Executable`, including: |
| 54 | + |
| 55 | +- **Automatic retries**: Handles temporary network failures. |
| 56 | +- **Node selection and rotation**: Automatically selects healthy nodes. |
| 57 | +- **gRPC network call orchestration**: Manages the underlying communication. |
| 58 | +- **Logging and error handling**: Provides consistent diagnostics. |
| 59 | + |
| 60 | +### Key Concepts |
| 61 | + |
| 62 | +`Query` acts as a bridge between the high-level user request and the low-level gRPC calls. It handles: |
| 63 | + |
| 64 | +1. **Payment Management**: Ensuring the node is paid for the query. |
| 65 | +2. **Request Construction**: Building the Protobuf messages. |
| 66 | +3. **Response Parsing**: Converting Protobuf responses into SDK objects. |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +## Execution Flow |
| 71 | + |
| 72 | +When you call `.execute(client)` on a query object, the following steps occur: |
| 73 | + |
| 74 | +1. **Pre-execution setup (`_before_execute`)**: |
| 75 | + - Assigns nodes and an operator from the client. |
| 76 | + - Determines if payment is required. |
| 77 | + - If no payment is set, it may query the network for the cost. |
| 78 | + |
| 79 | +2. **Request building (`_make_request`)**: |
| 80 | + - Constructs the Protobuf request for the specific query type. |
| 81 | + - Includes the payment transaction (if required) in the request header. |
| 82 | + |
| 83 | +3. **gRPC call (`_get_method` + `_execute_method`)**: |
| 84 | + - Sends the request to the selected node using the appropriate gRPC method. |
| 85 | + - Handles retries and backoff strategies for temporary failures. |
| 86 | + |
| 87 | +4. **Response mapping (`_map_response`)**: |
| 88 | + - Receives the raw Protobuf response. |
| 89 | + - Converts it into a usable SDK object (or extracts the relevant part). |
| 90 | + |
| 91 | +5. **Retry handling (`_should_retry`)**: |
| 92 | + - Checks the response status code. |
| 93 | + - Automatically retries queries with statuses like `BUSY` or `PLATFORM_NOT_ACTIVE`. |
| 94 | + |
| 95 | +6. **Error mapping (`_map_status_error`)**: |
| 96 | + - If the query fails (e.g., `INVALID_ACCOUNT_ID`), converts the status into a Python exception like `PrecheckError` or `ReceiptStatusError`. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Query Payment |
| 101 | + |
| 102 | +Some queries require a payment to the node. This payment is handled via a small `CryptoTransfer` transaction attached to the query request header. |
| 103 | + |
| 104 | +### Setting Custom Payment |
| 105 | + |
| 106 | +You can manually set the payment amount if you know the cost or want to offer a specific fee: |
| 107 | + |
| 108 | +```python |
| 109 | +from hiero_sdk_python.hbar import Hbar |
| 110 | + |
| 111 | +query = AccountBalanceQuery(account_id) |
| 112 | +query.set_query_payment(Hbar(1)) # Set custom payment to 1 Hbar |
| 113 | +``` |
| 114 | + |
| 115 | +### Automatic Cost Determination |
| 116 | + |
| 117 | +If no payment is set, the SDK can automatically query the cost before executing the actual query. |
| 118 | + |
| 119 | +1. The SDK sends a lightweight "Cost Query" (`COST_ANSWER` mode). |
| 120 | +2. The network returns the required fee. |
| 121 | +3. The SDK sets this fee as the payment and executes the actual query (`ANSWER_ONLY` mode). |
| 122 | + |
| 123 | +You can also manually fetch the cost: |
| 124 | + |
| 125 | +```python |
| 126 | +cost = query.get_cost(client) |
| 127 | +print(f"Query cost: {cost} Hbar") |
| 128 | +``` |
| 129 | + |
| 130 | +The SDK constructs a signed payment transaction using the operator’s private key to pay for the query. |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## Retry Logic |
| 135 | + |
| 136 | +Queries automatically handle retries for certain network issues. The base `_should_retry` implementation handles these statuses: |
| 137 | + |
| 138 | +- `BUSY`: The node is busy. |
| 139 | +- `PLATFORM_TRANSACTION_NOT_CREATED`: The transaction wasn't created on the platform. |
| 140 | +- `PLATFORM_NOT_ACTIVE`: The platform is not active. |
| 141 | + |
| 142 | +Subclasses can override `_should_retry` to add custom retry logic if needed. |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## Building a Custom Query |
| 147 | + |
| 148 | +If you need to implement a new type of query (e.g., for a new Hedera service), you can subclass `Query`. |
| 149 | + |
| 150 | +### Abstract Methods |
| 151 | + |
| 152 | +Subclasses **must** implement the following methods: |
| 153 | + |
| 154 | +| Method | Purpose | |
| 155 | +| :--- | :--- | |
| 156 | +| `_make_request()` | Builds the Protobuf request for the specific query. | |
| 157 | +| `_get_query_response(response)` | Extracts the specific query response field from the full network response. | |
| 158 | +| `_get_method(channel)` | Returns the gRPC method wrapper (`_Method`) to call for this query. | |
| 159 | + |
| 160 | +Optionally, you can override: |
| 161 | + |
| 162 | +| Method | Purpose | |
| 163 | +| :--- | :--- | |
| 164 | +| `_map_response(response, node_id, proto_request)` | Customize how the response is returned to the user. | |
| 165 | +| `_should_retry(response)` | Customize retry logic. | |
| 166 | +| `_map_status_error(response)` | Customize error mapping. | |
| 167 | + |
| 168 | +### Example Implementation |
| 169 | + |
| 170 | +Here is an example of how to implement a simple `AccountBalanceQuery`: |
| 171 | + |
| 172 | +```python |
| 173 | +from hiero_sdk_python.query.query import Query |
| 174 | +from hiero_sdk_python.executable import _Method |
| 175 | +from hiero_sdk_python.hapi.services import query_pb2, crypto_get_account_balance_pb2 |
| 176 | + |
| 177 | +class CustomAccountBalanceQuery(Query): |
| 178 | + def __init__(self, account_id): |
| 179 | + super().__init__() |
| 180 | + self.account_id = account_id |
| 181 | + |
| 182 | + def _make_request(self): |
| 183 | + # Create the header (includes payment) |
| 184 | + header = self._make_request_header() |
| 185 | + |
| 186 | + # Create the specific query body |
| 187 | + body = crypto_get_account_balance_pb2.CryptoGetAccountBalanceQuery( |
| 188 | + header=header, |
| 189 | + accountID=self.account_id._to_proto() |
| 190 | + ) |
| 191 | + |
| 192 | + # Wrap it in the main Query object |
| 193 | + return query_pb2.Query(cryptoGetAccountBalance=body) |
| 194 | + |
| 195 | + def _get_query_response(self, response): |
| 196 | + # Extract the balance response from the main response |
| 197 | + return response.cryptoGetAccountBalance |
| 198 | + |
| 199 | + def _get_method(self, channel): |
| 200 | + # Return the gRPC method to call |
| 201 | + return _Method(query_func=channel.crypto.get_account_balance) |
| 202 | + |
| 203 | +# Usage |
| 204 | +# query = CustomAccountBalanceQuery(my_account_id) |
| 205 | +# balance = query.execute(client) |
| 206 | +``` |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +## Summary |
| 211 | + |
| 212 | +- **Query** handles all network-level logic; subclasses only implement query-specific behavior. |
| 213 | +- **Payment** is handled transparently, with support for automatic cost fetching. |
| 214 | +- **Reliability** is ensured through automatic retry and error handling. |
| 215 | +- **Extensibility** is achieved by subclassing `Query` and implementing the required abstract methods. |
0 commit comments