Skip to content

Commit 828027e

Browse files
feat: implement child transaction records for #1512
Signed-off-by: mukundkumarjha <[email protected]>
1 parent 2363a2a commit 828027e

File tree

4 files changed

+169
-81
lines changed

4 files changed

+169
-81
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
2323
- Formatted client_test.py using Black.
2424

2525
### Added
26+
- Added support for include_children to the TransactionRecordQuery class
2627
- Added Windows setup guide for SDK developers (`docs/sdk_developers/training/setup/setup_windows.md`) with PowerShell installation instructions. (#1570)
2728
- Added a beginner assignment guard that requires completion of a Good First Issue. (#1484)
2829
- Added `/unassign` command allowing contributors to remove themselves from assigned issues.(#1472)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import os
2+
from hiero_sdk_python.client import Client
3+
from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery
4+
5+
def main():
6+
# Setup client
7+
client = Client.for_testnet()
8+
# ... (add operator credentials) ...
9+
10+
# 1. Execute a transaction that is likely to have child records
11+
# (e.g., a complex Smart Contract call)
12+
print("Executing transaction...")
13+
14+
# 2. Query the record and explicitly request children
15+
tx_id = "..." # your transaction id
16+
17+
query = (
18+
TransactionRecordQuery()
19+
.set_transaction_id(tx_id)
20+
.set_include_children(True) # The new feature!
21+
)
22+
23+
record = query.execute(client)
24+
25+
# 3. Demonstrate accessing the children
26+
print(f"Parent Transaction ID: {record.transaction_id}")
27+
print(f"Number of child records found: {len(record.children)}")
28+
29+
for i, child in enumerate(record.children):
30+
print(f"Child {i+1} Status: {child.receipt.status}")
31+
32+
if __name__ == "__main__":
33+
main()

src/hiero_sdk_python/query/transaction_record_query.py

Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from typing import Optional, Any, Union, List
2-
from hiero_sdk_python.hapi.services import query_header_pb2, transaction_get_record_pb2, query_pb2
2+
from hiero_sdk_python.hapi.services import (
3+
query_header_pb2,
4+
transaction_get_record_pb2,
5+
query_pb2,
6+
)
37
from hiero_sdk_python.query.query import Query
48
from hiero_sdk_python.response_code import ResponseCode
59
from hiero_sdk_python.transaction.transaction_id import TransactionId
@@ -16,34 +20,38 @@ class TransactionRecordQuery(Query):
1620
Represents a query for a transaction record on the Hedera network.
1721
"""
1822

19-
def __init__(self, transaction_id: Optional[TransactionId] = None, include_children: bool = False):
23+
def __init__(
24+
self,
25+
transaction_id: Optional[TransactionId] = None,
26+
include_children: bool = False,
27+
):
2028
"""
2129
Initializes the TransactionRecordQuery with the provided transaction ID.
2230
"""
2331
super().__init__()
24-
self.transaction_id : Optional[TransactionId] = transaction_id
32+
self.transaction_id: Optional[TransactionId] = transaction_id
2533
self.include_children = include_children
26-
27-
34+
2835
def set_transaction_id(self, transaction_id: TransactionId):
2936
"""
3037
Sets the transaction ID for the query.
31-
38+
3239
Args:
3340
transaction_id (TransactionId): The ID of the transaction to query.
3441
Returns:
3542
TransactionRecordQuery: This query instance.
3643
"""
3744
self.transaction_id = transaction_id
3845
return self
39-
46+
4047
def set_include_children(self, include_children):
4148
self.include_children = include_children
49+
return self
4250

4351
def _make_request(self):
4452
"""
4553
Constructs the protobuf request for the transaction record query.
46-
54+
4755
Builds a TransactionGetRecordQuery protobuf message with the
4856
appropriate header and transaction ID.
4957
@@ -57,19 +65,27 @@ def _make_request(self):
5765
"""
5866
try:
5967
if not self.transaction_id:
60-
raise ValueError("Transaction ID must be set before making the request.")
68+
raise ValueError(
69+
"Transaction ID must be set before making the request."
70+
)
6171

6272
query_header = self._make_request_header()
63-
transaction_get_record = transaction_get_record_pb2.TransactionGetRecordQuery()
73+
transaction_get_record = (
74+
transaction_get_record_pb2.TransactionGetRecordQuery()
75+
)
6476
transaction_get_record.header.CopyFrom(query_header)
77+
78+
transaction_get_record.transactionID.CopyFrom(
79+
self.transaction_id._to_proto()
80+
)
6581
transaction_get_record.include_child_records = self.include_children
66-
transaction_get_record.transactionID.CopyFrom(self.transaction_id._to_proto())
67-
6882
query = query_pb2.Query()
69-
if not hasattr(query, 'transactionGetRecord'):
70-
raise AttributeError("Query object has no attribute 'transactionGetRecord'")
83+
if not hasattr(query, "transactionGetRecord"):
84+
raise AttributeError(
85+
"Query object has no attribute 'transactionGetRecord'"
86+
)
7187
query.transactionGetRecord.CopyFrom(transaction_get_record)
72-
88+
7389
return query
7490
except Exception as e:
7591
print(f"Exception in _make_request: {e}")
@@ -78,7 +94,7 @@ def _make_request(self):
7894
def _get_method(self, channel: _Channel) -> _Method:
7995
"""
8096
Returns the appropriate gRPC method for the transaction receipt query.
81-
97+
8298
Implements the abstract method from Query to provide the specific
8399
gRPC method for getting transaction receipts.
84100
@@ -89,14 +105,13 @@ def _get_method(self, channel: _Channel) -> _Method:
89105
_Method: The method wrapper containing the query function
90106
"""
91107
return _Method(
92-
transaction_func=None,
93-
query_func=channel.crypto.getTxRecordByTxID
108+
transaction_func=None, query_func=channel.crypto.getTxRecordByTxID
94109
)
95110

96111
def _should_retry(self, response: Any) -> _ExecutionState:
97112
"""
98113
Determines whether the query should be retried based on the response.
99-
114+
100115
Implements the abstract method from Query to decide whether to retry
101116
the query based on the response status code. First checks the header status,
102117
then the receipt status.
@@ -108,37 +123,45 @@ def _should_retry(self, response: Any) -> _ExecutionState:
108123
_ExecutionState: The execution state indicating what to do next
109124
"""
110125
status = response.transactionGetRecord.header.nodeTransactionPrecheckCode
111-
126+
112127
retryable_statuses = {
113128
ResponseCode.UNKNOWN,
114129
ResponseCode.BUSY,
115130
ResponseCode.RECEIPT_NOT_FOUND,
116131
ResponseCode.RECORD_NOT_FOUND,
117-
ResponseCode.PLATFORM_NOT_ACTIVE
132+
ResponseCode.PLATFORM_NOT_ACTIVE,
118133
}
119-
134+
120135
if status == ResponseCode.OK:
121-
if response.transactionGetRecord.header.responseType == query_header_pb2.ResponseType.COST_ANSWER:
136+
if (
137+
response.transactionGetRecord.header.responseType
138+
== query_header_pb2.ResponseType.COST_ANSWER
139+
):
122140
return _ExecutionState.FINISHED
123141
pass
124-
elif status in retryable_statuses or status == ResponseCode.PLATFORM_TRANSACTION_NOT_CREATED:
142+
elif (
143+
status in retryable_statuses
144+
or status == ResponseCode.PLATFORM_TRANSACTION_NOT_CREATED
145+
):
125146
return _ExecutionState.RETRY
126147
else:
127148
return _ExecutionState.ERROR
128-
149+
129150
status = response.transactionGetRecord.transactionRecord.receipt.status
130-
151+
131152
if status in retryable_statuses or status == ResponseCode.OK:
132153
return _ExecutionState.RETRY
133154
elif status == ResponseCode.SUCCESS:
134155
return _ExecutionState.FINISHED
135156
else:
136157
return _ExecutionState.ERROR
137-
138-
def _map_status_error(self, response: Any) -> Union[PrecheckError,ReceiptStatusError]:
158+
159+
def _map_status_error(
160+
self, response: Any
161+
) -> Union[PrecheckError, ReceiptStatusError]:
139162
"""
140163
Maps a response status code to an appropriate error object.
141-
164+
142165
Implements the abstract method from Executable to create error objects
143166
from response status codes. Checks both the header status and receipt status.
144167
@@ -156,23 +179,37 @@ def _map_status_error(self, response: Any) -> Union[PrecheckError,ReceiptStatusE
156179
ResponseCode.UNKNOWN,
157180
ResponseCode.RECEIPT_NOT_FOUND,
158181
ResponseCode.RECORD_NOT_FOUND,
159-
ResponseCode.OK
182+
ResponseCode.OK,
160183
}
161-
184+
162185
if status not in retryable_statuses:
163186
return PrecheckError(status)
164-
187+
165188
receipt = response.transactionGetRecord.transactionRecord.receipt
166-
167-
return ReceiptStatusError(status, self.transaction_id, TransactionReceipt._from_proto(receipt, self.transaction_id))
168-
189+
190+
return ReceiptStatusError(
191+
status,
192+
self.transaction_id,
193+
TransactionReceipt._from_proto(receipt, self.transaction_id),
194+
)
195+
196+
def _map_record_list(
197+
self,
198+
proto_records: List[transaction_get_record_pb2.TransactionGetRecordResponse],
199+
) -> List[TransactionRecord]:
200+
records: List[TransactionRecord] = []
201+
for record in proto_records:
202+
records.append(TransactionRecord._from_proto(record, self.transaction_id))
203+
204+
return records
205+
169206
def execute(self, client):
170207
"""
171208
Executes the transaction record query.
172-
209+
173210
Sends the query to the Hedera network and processes the response
174211
to return a TransactionRecord object.
175-
212+
176213
This function delegates the core logic to `_execute()`, and may propagate exceptions raised by it.
177214
178215
Args:
@@ -188,27 +225,27 @@ def execute(self, client):
188225
"""
189226
self._before_execute(client)
190227
response = self._execute(client)
191-
#childrens = self._map_record_list(response.child_transaction_records)
192-
return TransactionRecord._from_proto(response.transactionGetRecord.transactionRecord, self.transaction_id)
228+
if not response.HasField("transactionGetRecord"):
229+
raise AttributeError("Response does not contain 'transactionGetRecord'")
230+
record_response = response.transactionGetRecord
231+
children = self._map_record_list(record_response.child_transaction_records)
232+
return TransactionRecord._from_proto(
233+
response.transactionGetRecord.transactionRecord,
234+
self.transaction_id,
235+
children=children,
236+
)
193237

194238
def _get_query_response(self, response: Any):
195239
"""
196240
Extracts the transaction record response from the full response.
197-
241+
198242
Implements the abstract method from Query to extract the
199243
specific transaction record response object.
200-
244+
201245
Args:
202246
response: The full response from the network
203-
247+
204248
Returns:
205249
The transaction get record response object
206250
"""
207251
return response.transactionGetRecord
208-
209-
def _map_record_list(self, proto_records: List[transaction_get_record_pb2.TransactionGetRecordResponse]) -> List[TransactionRecord]:
210-
records: List[TransactionRecord] = []
211-
for record in proto_records:
212-
records.append(TransactionRecord._from_proto(record, self.transaction_id))
213-
214-
return records

0 commit comments

Comments
 (0)