Skip to content

Commit d4ea816

Browse files
authored
feat(tck): Add getTopicInfo method to TCK (#2395)
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
1 parent 62c368e commit d4ea816

13 files changed

Lines changed: 278 additions & 121 deletions

File tree

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
"""
2828

2929
from hiero_sdk_python.account.account_id import AccountId
30+
from hiero_sdk_python.consensus.topic_id import TopicId
3031
from hiero_sdk_python.consensus.topic_info import TopicInfo
3132
from hiero_sdk_python.crypto.private_key import PrivateKey
3233
from hiero_sdk_python.Duration import Duration
33-
from hiero_sdk_python.hapi.services import consensus_topic_info_pb2
34+
from hiero_sdk_python.hapi.services import consensus_get_topic_info_pb2, consensus_topic_info_pb2
3435
from hiero_sdk_python.hapi.services.basic_types_pb2 import AccountID, Key
3536
from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp
3637
from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee
@@ -92,6 +93,7 @@ def mock_ledger_id() -> bytes:
9293
def build_mock_topic_info() -> TopicInfo:
9394
"""Manually construct a TopicInfo instance with mock data."""
9495
return TopicInfo(
96+
topic_id=TopicId.from_string("0.0.101"),
9597
memo="Example topic memo",
9698
running_hash=mock_running_hash(),
9799
sequence_number=42,
@@ -109,24 +111,30 @@ def build_mock_topic_info() -> TopicInfo:
109111

110112
def build_topic_info_from_proto() -> TopicInfo:
111113
"""Build a TopicInfo from a mocked protobuf message using _from_proto()."""
112-
proto = consensus_topic_info_pb2.ConsensusTopicInfo()
113-
proto.memo = "Topic from protobuf"
114-
proto.runningHash = mock_running_hash()
115-
proto.sequenceNumber = 100
116-
proto.expirationTime.CopyFrom(mock_expiration_time())
117-
proto.adminKey.CopyFrom(mock_admin_key())
118-
proto.submitKey.CopyFrom(mock_submit_key())
119-
proto.autoRenewPeriod.seconds = 7776000
120-
proto.autoRenewAccount.CopyFrom(mock_auto_renew_account())
121-
proto.ledger_id = mock_ledger_id()
122-
proto.custom_fees.append(mock_custom_fee()._to_topic_fee_proto())
114+
proto = consensus_get_topic_info_pb2.ConsensusGetTopicInfoResponse()
115+
proto.topicID.CopyFrom(TopicId.from_string("0.0.101")._to_proto())
116+
117+
topic_info_proto = consensus_topic_info_pb2.ConsensusTopicInfo()
118+
topic_info_proto.memo = "Topic from protobuf"
119+
topic_info_proto.runningHash = mock_running_hash()
120+
topic_info_proto.sequenceNumber = 100
121+
topic_info_proto.expirationTime.CopyFrom(mock_expiration_time())
122+
topic_info_proto.adminKey.CopyFrom(mock_admin_key())
123+
topic_info_proto.submitKey.CopyFrom(mock_submit_key())
124+
topic_info_proto.autoRenewPeriod.seconds = 7776000
125+
topic_info_proto.autoRenewAccount.CopyFrom(mock_auto_renew_account())
126+
topic_info_proto.ledger_id = mock_ledger_id()
127+
topic_info_proto.custom_fees.append(mock_custom_fee()._to_topic_fee_proto())
128+
129+
proto.topicInfo.CopyFrom(topic_info_proto)
123130

124131
return TopicInfo._from_proto(proto)
125132

126133

127134
def print_topic_info(topic: TopicInfo) -> None:
128135
"""Display the key attributes of a TopicInfo instance."""
129136
print("\nTopicInfo Details:")
137+
print(f" TopicId: {topic.topic_id}")
130138
print(f" Memo: {topic.memo}")
131139
print(f" Sequence Number: {topic.sequence_number}")
132140
print(f" Running Hash: {topic.running_hash.hex()}")

src/hiero_sdk_python/consensus/topic_info.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111

1212
from datetime import datetime, timezone
1313

14+
from hiero_sdk_python.account.account_id import AccountId
15+
from hiero_sdk_python.consensus.topic_id import TopicId
16+
from hiero_sdk_python.crypto.key import Key
1417
from hiero_sdk_python.crypto.public_key import PublicKey
1518
from hiero_sdk_python.Duration import Duration
16-
from hiero_sdk_python.hapi.services import consensus_topic_info_pb2
17-
from hiero_sdk_python.hapi.services.basic_types_pb2 import AccountID, Key
19+
from hiero_sdk_python.hapi.services import consensus_get_topic_info_pb2
20+
from hiero_sdk_python.hapi.services.basic_types_pb2 import AccountID
1821
from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp
1922
from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee
2023
from hiero_sdk_python.utils.key_format import format_key
@@ -31,6 +34,7 @@ class TopicInfo:
3134

3235
def __init__(
3336
self,
37+
topic_id: TopicId,
3438
memo: str,
3539
running_hash: bytes,
3640
sequence_number: int,
@@ -48,6 +52,7 @@ def __init__(
4852
Initializes a new instance of the TopicInfo class.
4953
5054
Args:
55+
topic_id (TopicId): The id of the topic.
5156
memo (str): The memo associated with the topic.
5257
running_hash (bytes): The current running hash of the topic.
5358
sequence_number (int): The sequence number of the topic.
@@ -61,21 +66,22 @@ def __init__(
6166
fee_exempt_keys (list[PublicKey]): The fee exempt keys for the topic.
6267
custom_fees (list[CustomFixedFee]): The custom fees for the topic.
6368
"""
69+
self.topic_id: TopicId = topic_id
6470
self.memo: str = memo
6571
self.running_hash: bytes = running_hash
6672
self.sequence_number: int = sequence_number
6773
self.expiration_time: Timestamp | None = expiration_time
6874
self.admin_key: Key | None = admin_key
6975
self.submit_key: Key | None = submit_key
7076
self.auto_renew_period: Duration | None = auto_renew_period
71-
self.auto_renew_account: AccountID | None = auto_renew_account
77+
self.auto_renew_account: AccountId | None = auto_renew_account
7278
self.ledger_id: bytes | None = ledger_id
73-
self.fee_schedule_key: PublicKey = fee_schedule_key
74-
self.fee_exempt_keys: list[PublicKey] = list(fee_exempt_keys) if fee_exempt_keys is not None else []
79+
self.fee_schedule_key: Key = fee_schedule_key
80+
self.fee_exempt_keys: list[Key] = list(fee_exempt_keys) if fee_exempt_keys is not None else []
7581
self.custom_fees: list[CustomFixedFee] = list(custom_fees) if custom_fees is not None else []
7682

7783
@classmethod
78-
def _from_proto(cls, topic_info_proto: consensus_topic_info_pb2.ConsensusTopicInfo) -> TopicInfo:
84+
def _from_proto(cls, topic_info_proto: consensus_get_topic_info_pb2.ConsensusGetTopicInfoResponse) -> TopicInfo:
7985
"""
8086
Constructs a TopicInfo object from a protobuf ConsensusTopicInfo message.
8187
@@ -85,29 +91,30 @@ def _from_proto(cls, topic_info_proto: consensus_topic_info_pb2.ConsensusTopicIn
8591
Returns:
8692
TopicInfo: The constructed TopicInfo object.
8793
"""
94+
topic_info = topic_info_proto.topicInfo
95+
8896
return cls(
89-
memo=topic_info_proto.memo,
90-
running_hash=topic_info_proto.runningHash,
91-
sequence_number=topic_info_proto.sequenceNumber,
92-
expiration_time=(topic_info_proto.expirationTime if topic_info_proto.HasField("expirationTime") else None),
93-
admin_key=(topic_info_proto.adminKey if topic_info_proto.HasField("adminKey") else None),
94-
submit_key=(topic_info_proto.submitKey if topic_info_proto.HasField("submitKey") else None),
97+
topic_id=TopicId._from_proto(topic_info_proto.topicID),
98+
memo=topic_info.memo,
99+
running_hash=topic_info.runningHash,
100+
sequence_number=topic_info.sequenceNumber,
101+
expiration_time=(topic_info.expirationTime if topic_info.HasField("expirationTime") else None),
102+
admin_key=(Key.from_proto_key(topic_info.adminKey) if topic_info.HasField("adminKey") else None),
103+
submit_key=(Key.from_proto_key(topic_info.submitKey) if topic_info.HasField("submitKey") else None),
95104
auto_renew_period=(
96-
Duration._from_proto(proto=topic_info_proto.autoRenewPeriod)
97-
if topic_info_proto.HasField("autoRenewPeriod")
105+
Duration._from_proto(proto=topic_info.autoRenewPeriod)
106+
if topic_info.HasField("autoRenewPeriod")
98107
else None
99108
),
100109
auto_renew_account=(
101-
topic_info_proto.autoRenewAccount if topic_info_proto.HasField("autoRenewAccount") else None
110+
AccountId._from_proto(topic_info.autoRenewAccount) if topic_info.HasField("autoRenewAccount") else None
102111
),
103-
ledger_id=getattr(topic_info_proto, "ledger_id", None),
112+
ledger_id=topic_info.ledger_id if topic_info.ledger_id else None,
104113
fee_schedule_key=(
105-
PublicKey._from_proto(topic_info_proto.fee_schedule_key)
106-
if topic_info_proto.HasField("fee_schedule_key")
107-
else None
114+
Key.from_proto_key(topic_info.fee_schedule_key) if topic_info.HasField("fee_schedule_key") else None
108115
),
109-
fee_exempt_keys=[PublicKey._from_proto(key) for key in topic_info_proto.fee_exempt_key_list],
110-
custom_fees=[CustomFixedFee._from_proto(fee) for fee in topic_info_proto.custom_fees],
116+
fee_exempt_keys=[Key.from_proto_key(key) for key in topic_info.fee_exempt_key_list],
117+
custom_fees=[CustomFixedFee._from_proto(fee) for fee in topic_info.custom_fees],
111118
)
112119

113120
def __repr__(self) -> str:
@@ -158,6 +165,7 @@ def __str__(self) -> str:
158165

159166
return (
160167
"TopicInfo(\n"
168+
f" topic_id={self.topic_id},\n"
161169
f" memo='{self.memo}',\n"
162170
f" running_hash={running_hash_str},\n"
163171
f" sequence_number={self.sequence_number},\n"

src/hiero_sdk_python/query/topic_info_query.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,15 @@ def _make_request(self) -> query_pb2.Query:
8383
query_pb2.Query: The protobuf query message.
8484
8585
Raises:
86-
ValueError: If the topic ID is not set.
8786
Exception: If any other error occurs during request construction.
8887
"""
8988
try:
90-
if not self.topic_id:
91-
raise ValueError("Topic ID must be set before making the request.")
92-
9389
query_header = self._make_request_header()
9490

9591
topic_info_query = consensus_get_topic_info_pb2.ConsensusGetTopicInfoQuery()
9692
topic_info_query.header.CopyFrom(query_header)
97-
topic_info_query.topicID.CopyFrom(self.topic_id._to_proto())
93+
if self.topic_id is not None:
94+
topic_info_query.topicID.CopyFrom(self.topic_id._to_proto())
9895

9996
query = query_pb2.Query()
10097
query.consensusGetTopicInfo.CopyFrom(topic_info_query)
@@ -168,7 +165,7 @@ def execute(self, client: Client, timeout: int | float | None = None) -> TopicIn
168165
self._before_execute(client)
169166
response = self._execute(client, timeout)
170167

171-
return TopicInfo._from_proto(response.consensusGetTopicInfo.topicInfo)
168+
return TopicInfo._from_proto(response.consensusGetTopicInfo)
172169

173170
def _get_query_response(self, response: Any) -> consensus_get_topic_info_pb2.ConsensusGetTopicInfoResponse:
174171
"""

tck/handlers/account.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
)
3636
from tck.util.client_utils import get_client
3737
from tck.util.constants import DEFAULT_GRPC_TIMEOUT
38-
from tck.util.key_utils import get_key_from_string
38+
from tck.util.key_utils import get_key_from_string, key_to_string
3939

4040

4141
def _build_create_account_transaction(params: CreateAccountParams) -> AccountCreateTransaction:
@@ -153,17 +153,6 @@ def _enum_name(value) -> str | None:
153153
return getattr(value, "name", str(value))
154154

155155

156-
def _serialize_key(key) -> str | None:
157-
if key is None:
158-
return None
159-
160-
to_string_der = getattr(key, "to_string_der", None)
161-
if callable(to_string_der):
162-
return to_string_der()
163-
164-
return key.to_bytes().hex()
165-
166-
167156
def _to_staking_info_response(info: AccountInfo) -> StakingInfoResponse | None:
168157
if info.staking_info is None:
169158
return None
@@ -211,7 +200,7 @@ def _build_account_info_response(info: AccountInfo) -> GetAccountInfoResponse:
211200
isDeleted=bool(info.is_deleted),
212201
proxyAccountId="",
213202
proxyReceived=str(info.proxy_received.to_tinybars()) if info.proxy_received is not None else "0",
214-
key=_serialize_key(info.key),
203+
key=key_to_string(info.key),
215204
balance=str(info.balance.to_tinybars()) if info.balance is not None else "0",
216205
sendRecordThreshold="0",
217206
receiveRecordThreshold="0",

tck/handlers/topic.py

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,28 @@
33
from hiero_sdk_python.account.account_id import AccountId
44
from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction
55
from hiero_sdk_python.consensus.topic_id import TopicId
6+
from hiero_sdk_python.consensus.topic_info import TopicInfo
67
from hiero_sdk_python.consensus.topic_message_submit_transaction import TopicMessageSubmitTransaction
8+
from hiero_sdk_python.hbar import Hbar
9+
from hiero_sdk_python.query.topic_info_query import TopicInfoQuery
710
from hiero_sdk_python.response_code import ResponseCode
811
from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee
912
from hiero_sdk_python.tokens.token_id import TokenId
1013
from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit
1114
from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt
1215
from tck.handlers.registry import rpc_method
1316
from tck.param.custom_fee import CustomFeeLimitParams, CustomFeeParams
14-
from tck.param.topic import CreateTopicParams, TopicMessageSubmitParams
15-
from tck.response.topic import CreateTopicResponse, TopicMessageSubmitResponse
17+
from tck.param.topic import CreateTopicParams, TopicMessageInfoParams, TopicMessageSubmitParams
18+
from tck.response.topic import (
19+
CreateTopicResponse,
20+
CustomFeeResponse,
21+
FixedFeeResponse,
22+
TopicInfoResponse,
23+
TopicMessageSubmitResponse,
24+
)
1625
from tck.util.client_utils import get_client
1726
from tck.util.constants import DEFAULT_GRPC_TIMEOUT
18-
from tck.util.key_utils import get_key_from_string
27+
from tck.util.key_utils import get_key_from_string, key_to_string
1928

2029

2130
def _build_custom_fee(custom_fee_params: CustomFeeParams) -> CustomFixedFee:
@@ -146,3 +155,60 @@ def submit_topic_message(params: TopicMessageSubmitParams) -> TopicMessageSubmit
146155
receipt: TransactionReceipt = response.get_receipt(client, validate_status=True)
147156

148157
return TopicMessageSubmitResponse(ResponseCode(receipt.status).name)
158+
159+
160+
@rpc_method("getTopicInfo")
161+
def get_topic_info(params: TopicMessageInfoParams) -> TopicInfoResponse:
162+
"""Get topic info."""
163+
client = get_client(params.sessionId)
164+
165+
query = TopicInfoQuery().set_grpc_deadline(DEFAULT_GRPC_TIMEOUT)
166+
167+
if params.topicId is not None:
168+
query.set_topic_id(TopicId.from_string(params.topicId))
169+
170+
if params.queryPayment is not None:
171+
query.set_query_payment(Hbar.from_tinybars(int(params.queryPayment)))
172+
173+
if params.maxQueryPayment is not None:
174+
query.set_max_query_payment(Hbar.from_tinybars(int(params.maxQueryPayment)))
175+
176+
topic_info = query.execute(client)
177+
return _map_topic_info_response(topic_info)
178+
179+
180+
def _map_topic_info_response(topic_info: TopicInfo) -> TopicInfoResponse:
181+
"""Map TopicInfo to JSON-RPC TopicInfoResponse."""
182+
return TopicInfoResponse(
183+
topicId=str(topic_info.topic_id),
184+
topicMemo=topic_info.memo,
185+
sequenceNumber=str(topic_info.sequence_number),
186+
runningHash=topic_info.running_hash.hex(),
187+
adminKey=key_to_string(topic_info.admin_key) if topic_info.admin_key is not None else None,
188+
submitKey=key_to_string(topic_info.submit_key) if topic_info.submit_key is not None else None,
189+
autoRenewAccountId=str(topic_info.auto_renew_account) if topic_info.auto_renew_account is not None else None,
190+
autoRenewPeriod=str(topic_info.auto_renew_period.seconds) if topic_info.auto_renew_period is not None else None,
191+
expirationTime=str(topic_info.expiration_time.seconds) if topic_info.expiration_time is not None else None,
192+
feeScheduleKey=key_to_string(topic_info.fee_schedule_key) if topic_info.fee_schedule_key is not None else None,
193+
feeExemptKeys=[key_to_string(key) for key in topic_info.fee_exempt_keys],
194+
customFees=[_map_custom_fee_response(fee) for fee in topic_info.custom_fees],
195+
ledgerId=topic_info.ledger_id.hex() if topic_info.ledger_id is not None else None,
196+
)
197+
198+
199+
def _map_custom_fee_response(custom_fee: CustomFixedFee) -> CustomFeeResponse:
200+
"""Map CustomFixedFee to JSON-RPC CustomFeeResponse."""
201+
fixed_fee = FixedFeeResponse(
202+
amount=str(custom_fee.amount),
203+
denominatingTokenId=str(custom_fee.denominating_token_id)
204+
if custom_fee.denominating_token_id is not None
205+
else None,
206+
)
207+
208+
return CustomFeeResponse(
209+
feeCollectorAccountId=str(custom_fee.fee_collector_account_id)
210+
if custom_fee.fee_collector_account_id is not None
211+
else None,
212+
allCollectorsAreExempt=custom_fee.all_collectors_are_exempt,
213+
fixedFee=fixed_fee,
214+
)

tck/param/topic.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44

5-
from tck.param.base import BaseTransactionParams
5+
from tck.param.base import BaseParams, BaseTransactionParams
66
from tck.param.custom_fee import CustomFeeLimitParams, CustomFeeParams
77
from tck.util.param_utils import (
88
non_empty_string_list,
@@ -91,3 +91,22 @@ def parse_json_params(cls, params: dict) -> TopicMessageSubmitParams:
9191
sessionId=parse_session_id(params),
9292
commonTransactionParams=parse_common_transaction_params(params),
9393
)
94+
95+
96+
@dataclass
97+
class TopicMessageInfoParams(BaseParams):
98+
"""Request parameters for getTopicInfo endpoint."""
99+
100+
topicId: str | None = None
101+
queryPayment: str | None = None
102+
maxQueryPayment: str | None = None
103+
104+
@classmethod
105+
def parse_json_params(cls, params: dict) -> TopicMessageInfoParams:
106+
"""Parse JSON-RPC params into a TopicMessageInfoParams instance."""
107+
return cls(
108+
topicId=params.get("topicId"),
109+
queryPayment=params.get("queryPayment"),
110+
maxQueryPayment=params.get("maxQueryPayment"),
111+
sessionId=parse_session_id(params),
112+
)

0 commit comments

Comments
 (0)