From d021f5b497c7fee72b9282393c2daef878b775df Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Sat, 22 Nov 2025 20:11:12 +0530 Subject: [PATCH 01/14] feat: impl cunked transaction for the topic_message Signed-off-by: Manish Dait --- examples/topic_message_submit_chunked.py | 124 +++++++++ .../topic_message_submit_transaction.py | 188 ++++++++++++- ...pic_message_submit_transaction_e2e_test.py | 255 ++++++++++-------- 3 files changed, 442 insertions(+), 125 deletions(-) create mode 100644 examples/topic_message_submit_chunked.py diff --git a/examples/topic_message_submit_chunked.py b/examples/topic_message_submit_chunked.py new file mode 100644 index 000000000..dc3a4c2f3 --- /dev/null +++ b/examples/topic_message_submit_chunked.py @@ -0,0 +1,124 @@ +""" +uv run examples/topic_message_submit.py +python examples/topic_message_submit.py + +""" +import os +import sys +from dotenv import load_dotenv + +from hiero_sdk_python import ( + Client, + AccountId, + PrivateKey, + Network, + TopicMessageSubmitTransaction, + TopicCreateTransaction, + ResponseCode +) +from hiero_sdk_python.consensus.topic_id import TopicId +from hiero_sdk_python.query.topic_info_query import TopicInfoQuery +from hiero_sdk_python.query.topic_message_query import TopicMessageQuery + +load_dotenv() +network_name = os.getenv('NETWORK', 'testnet').lower() +def setup_client(): + """Initialize and set up the client with operator account""" + network = Network(network_name) + print(f"Connecting to Hedera {network_name} network!") + client = Client(network) + + + try: + operator_id = AccountId.from_string(os.getenv('OPERATOR_ID', '')) + operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY', '')) + client.set_operator(operator_id, operator_key) + print(f"Client set up with operator id {client.operator_account_id}") + + return client, operator_id, operator_key + except (TypeError, ValueError): + print("❌ Error: Creating client, Please check your .env file") + sys.exit(1) + + +def submit_topic_message_transaction(client, topic_id, message, operator_key): + """Submit a message to the specified topic""" + print("\nSTEP 2: Submitting message...") + transaction = ( + TopicMessageSubmitTransaction(topic_id=topic_id, message=message) + .freeze_with(client) + .sign(operator_key) + ) + + try: + receipt = transaction.execute(client) + print(f"Message Submit Transaction completed: " + f"(status: {ResponseCode(receipt.status).name}, " + f"transaction_id: {receipt.transaction_id})") + print(f"✅ Success! Message submitted to topic {topic_id}: {message}") + except Exception as e: + print(f"❌ Error: Message submission failed: {str(e)}") + sys.exit(1) + + +def main(): + """ + A example to create a topic and then submit a message to it. + """ + client, _, key = setup_client() + message = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur aliquam augue sem, ut mattis dui laoreet a. Curabitur consequat est euismod, scelerisque metus et, tristique dui. Nulla commodo mauris ut faucibus ultricies. Quisque venenatis nisl nec augue tempus, at efficitur elit eleifend. Duis pharetra felis metus, sed dapibus urna vehicula id. Duis non venenatis turpis, sit amet ornare orci. Donec non interdum quam. Sed finibus nunc et risus finibus, non sagittis lorem cursus. Proin pellentesque tempor aliquam. Sed congue nisl in enim bibendum, condimentum vehicula nisi feugiat. + +Suspendisse non sodales arcu. Suspendisse sodales, lorem ac mollis blandit, ipsum neque porttitor nulla, et sodales arcu ante fermentum tellus. Integer sagittis dolor sed augue fringilla accumsan. Cras vitae finibus arcu, sit amet varius dolor. Etiam id finibus dolor, vitae luctus velit. Proin efficitur augue nec pharetra accumsan. Aliquam lobortis nisl diam, vel fermentum purus finibus id. Etiam at finibus orci, et tincidunt turpis. Aliquam imperdiet congue lacus vel facilisis. Phasellus id magna vitae enim dapibus vestibulum vitae quis augue. Morbi eu consequat enim. Maecenas neque nulla, pulvinar sit amet consequat sed, tempor sed magna. Mauris lacinia sem feugiat faucibus aliquet. Etiam congue non turpis at commodo. Nulla facilisi. + +Nunc velit turpis, cursus ornare fringilla eu, lacinia posuere leo. Mauris rutrum ultricies dui et suscipit. Curabitur in euismod ligula. Curabitur vitae faucibus orci. Phasellus volutpat vestibulum diam sit amet vestibulum. In vel purus leo. Nulla condimentum lectus vestibulum lectus faucibus, id lobortis eros consequat. Proin mollis libero elit, vel aliquet nisi imperdiet et. Morbi ornare est velit, in vehicula nunc malesuada quis. Donec vehicula convallis interdum. + +Integer pellentesque in nibh vitae aliquet. Ut at justo id libero dignissim hendrerit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent quis ornare lectus. Nam malesuada non diam quis cursus. Phasellus a libero ligula. Suspendisse ligula elit, congue et nisi sit amet, cursus euismod dolor. Morbi aliquam, nulla a posuere pellentesque, diam massa ornare risus, nec eleifend neque eros et elit. + +Pellentesque a sodales dui. Sed in efficitur ante. Duis eget volutpat elit, et ornare est. Nam felis dolor, placerat mattis diam id, maximus lobortis quam. Sed pellentesque lobortis sem sed placerat. Pellentesque augue odio, molestie sed lectus sit amet, congue volutpat massa. Quisque congue consequat nunc id fringilla. Duis semper nulla eget enim venenatis dapibus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque varius turpis nibh, sit amet malesuada mauris malesuada quis. Vivamus dictum egestas placerat. Vivamus id augue elit. + +Cras fermentum volutpat eros, vitae euismod lorem viverra nec. Donec lectus augue, porta eleifend odio sit amet, condimentum rhoncus urna. Nunc sed odio velit. Morbi non cursus odio, eget imperdiet erat. Nunc rhoncus massa non neque volutpat, sit amet faucibus ante congue. Phasellus nec lorem vel leo accumsan lobortis. Maecenas id ligula bibendum purus suscipit posuere ac eget diam. Nam in quam pretium, semper erat auctor, iaculis odio. Maecenas placerat, nisi ac elementum tempor, felis nulla porttitor orci, ac rhoncus diam justo in elit. Etiam lobortis fermentum ligula in tincidunt. Donec quis vestibulum nunc. Sed eros diam, interdum in porta lobortis, gravida eu magna. Donec diam purus, finibus et sollicitudin quis, fringilla nec nisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur ultricies sagittis dapibus. Etiam ullamcorper aliquet libero, eu venenatis mauris suscipit id. + +Ut interdum eleifend sem, vel bibendum ipsum volutpat eget. Nunc ac dignissim augue. Aliquam ornare aliquet magna id dignissim. Vestibulum velit sem, lacinia eu rutrum in, rhoncus vitae mauris. Pellentesque scelerisque pulvinar mauris non cursus. Integer id dolor porta, bibendum sem vel, pretium tortor. Fusce a nisi convallis, interdum quam condimentum, suscipit dolor. Sed magna diam, efficitur non nunc in, tincidunt varius mi. Aliquam ullamcorper nulla eu fermentum bibendum. Vivamus a felis pretium, hendrerit enim vitae, hendrerit leo. Suspendisse lacinia lectus a diam consectetur suscipit. Aenean hendrerit nisl sed diam venenatis pellentesque. Nullam egestas lectus a consequat pharetra. Vivamus ornare tellus auctor, facilisis lacus id, feugiat dui. Nam id est ut est rhoncus varius. + +Aenean vel vehicula erat. Aenean gravida risus vitae purus sodales, quis dictum enim porta. Ut elit elit, fermentum sed posuere non, accumsan eu justo. Integer porta malesuada quam, et elementum massa suscipit nec. Donec in elit diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis suscipit vel ante volutpat vestibulum. + +Pellentesque ex arcu, euismod et sapien ut, vulputate suscipit enim. Donec mattis sagittis augue, et mattis lacus. Cras placerat consequat lorem sed luctus. Nam suscipit aliquam sem ac imperdiet. Mauris a semper augue, pulvinar fringilla magna. Integer pretium massa non risus commodo hendrerit. Sed dictum libero id erat sodales mattis. Etiam auctor dolor lectus, ut sagittis enim lobortis vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur nec orci lobortis, cursus risus eget, sollicitudin massa. Integer vel tincidunt mi, id eleifend quam. Nullam facilisis nisl eu mauris congue, vitae euismod nisi malesuada. Vivamus sit amet urna et libero sagittis dictum. + +In hac habitasse platea dictumst. Aliquam erat volutpat. Ut dictum, mi a viverra venenatis, mi urna pulvinar nisi, nec gravida lectus diam eget urna. Ut dictum sit amet nisl ut dignissim. Sed sed mauris scelerisque, efficitur augue in, vulputate turpis. Proin dolor justo, bibendum et sollicitudin feugiat, imperdiet sed mi. Sed elementum vitae massa vel lobortis. Cras vitae massa sit amet libero dictum tempus. + +Vivamus ut mauris lectus. Curabitur placerat ornare scelerisque. Cras malesuada nunc quis tortor pretium bibendum vel sed dui. Cras lobortis nibh eu erat blandit, sit amet consequat neque fermentum. Phasellus imperdiet molestie tristique. Cras auctor purus erat, id mollis ligula porttitor eget. Mauris porta pharetra odio et finibus. Suspendisse eu est a ligula bibendum cursus. Mauris ac laoreet libero. Nullam volutpat sem quis rhoncus gravida. + +Donec malesuada lacus ac iaculis cursus. Sed sem orci, feugiat ac est ut, ultricies posuere nisi. Suspendisse potenti. Phasellus ut ultricies purus. Etiam sem tortor, fermentum quis aliquam eget, consequat ut nulla. Aliquam dictum metus in mi fringilla, vel gravida nulla accumsan. Cras aliquam eget leo vel posuere. Vivamus vitae malesuada nunc. Morbi placerat magna mi, id suscipit lacus auctor quis. Nam at lorem vel odio finibus fringilla ut ac velit. Donec laoreet at nibh quis vehicula. + +Fusce ac tristique nisi. Donec leo nisi, consectetur at tellus sit amet, consectetur ultrices dui. Quisque gravida mollis tempor. Maecenas semper, sapien ut dignissim feugiat, massa enim viverra dolor, non varius eros nulla nec felis. Nunc massa lacus, ornare et feugiat id, bibendum quis purus. Praesent viverra elit sit amet purus consectetur venenatis. Maecenas nibh risus, elementum sit amet enim sagittis, ultrices malesuada lectus. Vivamus non felis ante. Ut vulputate ex arcu. Aliquam porta gravida porta. Aliquam eros leo, malesuada quis eros non, maximus tristique neque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ligula orci, mollis vel luctus nec, venenatis vitae est. Fusce rutrum convallis nisi. + +Nunc laoreet eget nibh in feugiat. Pellentesque nec arcu cursus, gravida dolor a, pellentesque nisi. Praesent vel justo blandit, placerat risus eget, consectetur orci. Sed maximus metus mi, ut euismod augue ultricies in. Nunc id risus hendrerit, aliquet lorem nec, congue justo. Vestibulum vel nunc ac est euismod mattis ac vitae nulla. Donec blandit luctus mauris, sit amet bibendum dui egestas et. Aenean nec lorem nec elit ornare rutrum nec eget ligula. Fusce a ipsum vitae neque elementum pharetra. Pellentesque ullamcorper ullamcorper libero, vitae porta sem sagittis vel. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Duis at massa sit amet risus pellentesque varius sit amet vitae eros. Cras tempor aliquet sapien, vehicula varius neque volutpat et. Donec purus nibh, pellentesque ut lobortis nec, ultricies ultricies nisl. Sed accumsan ut dui sit amet vulputate. Suspendisse eu facilisis massa, a hendrerit mauris. Nulla elementum molestie tincidunt. Donec mi justo, ornare vel tempor id, gravida et orci. Nam molestie erat nec nisi bibendum accumsan. Duis vitae tempor ante. Morbi congue mauris vel sagittis facilisis. Vivamus vehicula odio orci, a tempor nibh euismod in. Proin mattis, nibh at feugiat porta, purus velit posuere felis, quis volutpat sapien dui vel odio. Nam fermentum sem nec euismod aliquet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. + +Mauris congue lacus tortor. Pellentesque arcu eros, accumsan imperdiet porttitor vitae, mattis nec justo. Nullam ac aliquam mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse potenti. Fusce accumsan tempus felis a sagittis. Maecenas et velit odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam eros lacus, volutpat non urna sed, tincidunt ullamcorper elit. Sed sit amet gravida libero. In varius mi vel diam sollicitudin mollis. + +Aenean varius, diam vitae hendrerit feugiat, libero augue ultrices odio, eget consequat sem tellus eu nisi. Nam dapibus enim et auctor sollicitudin. Nunc iaculis eros orci, ac accumsan eros malesuada ut. Ut semper augue felis, nec sodales lorem consectetur non. Cras gravida eleifend est, et sagittis eros imperdiet congue. Fusce id tellus dapibus nunc scelerisque tempus. Donec tempor placerat libero, in commodo nisi bibendum eu. Donec id eros non est sollicitudin luctus. Duis bibendum bibendum tellus nec viverra. Aliquam non faucibus ex, nec luctus dui. Curabitur efficitur varius urna non dignissim. Suspendisse elit elit, ultrices in elementum eu, tempor at magna. + +Nunc in purus sit amet mi laoreet pulvinar placerat eget sapien. Donec vel felis at dui ultricies euismod quis vel neque. Donec tincidunt urna non eros pretium blandit. Nullam congue tincidunt condimentum. Curabitur et libero nibh. Proin ultricies risus id imperdiet scelerisque. Suspendisse purus mi, viverra vitae risus ut, tempus tincidunt enim. Ut luctus lobortis nisl, eget venenatis tortor cursus non. Mauris finibus nisl quis gravida ultricies. Fusce elementum lacus sit amet nunc congue, in porta nulla tincidunt. + +Mauris ante risus, imperdiet blandit posuere in, blandit eu ipsum. Integer et auctor arcu. Integer quis elementum purus. Nunc in ultricies nisl, sed rutrum risus. Suspendisse venenatis eros nec lorem rhoncus, in scelerisque velit condimentum. Etiam condimentum quam felis, in elementum odio mattis et. In ut nibh porttitor, hendrerit tellus vel, luctus magna. Vestibulum et ligula et dolor pellentesque porta. Aenean efficitur porta massa et bibendum. Nulla vehicula sem in risus volutpat, a eleifend elit maximus. + +Proin orci lorem, auctor a felis eu, pretium lobortis nulla. Phasellus aliquam efficitur interdum. Sed sit amet velit rutrum est dictum facilisis. Duis cursus enim at nisl tincidunt, eu molestie elit vehicula. Cras pellentesque nisl id enim feugiat fringilla. In quis tincidunt neque. Nam eu consectetur dolor. Ut id interdum mauris. Mauris nunc tortor, placerat in tempor ut, vestibulum eu nisl. Integer vel dapibus ipsum. Nunc id erat pulvinar, tincidunt magna id, condimentum massa. Pellentesque consequat est eget odio placerat vehicula. Etiam augue neque, sagittis non leo eu, tristique scelerisque dui. Ut dui urna, blandit quis urna ac, tincidunt tristique turpis. + +Suspendisse venenatis rhoncus ligula ultrices condimentum. In id laoreet eros. Suspendisse suscipit fringilla leo id euismod. Sed in quam libero. Ut at ligula tellus. Sed tristique gravida dui, at egestas odio aliquam iaculis. Praesent imperdiet velit quis nibh consequat, quis pretium sem sagittis. Donec tortor ex, congue sit amet pulvinar ac, rutrum non est. Praesent ipsum magna, venenatis sit amet vulputate id, eleifend ac ipsum. + +In consequat, nisi iaculis laoreet elementum, massa mauris varius nisi, et porta nisi velit at urna. Maecenas sit amet aliquet eros, a rhoncus nisl. Maecenas convallis enim nunc. Morbi purus nisl, aliquam ac tincidunt sed, mattis in augue. Quisque et elementum quam, vitae maximus orci. Suspendisse hendrerit risus nec vehicula placerat. Nulla et lectus nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Etiam ut sodales ex. Nulla luctus, magna eu scelerisque sagittis, nibh quam consectetur neque, non rutrum dolor metus nec ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed egestas augue elit, sollicitudin accumsan massa lobortis ac. Curabitur placerat, dolor a aliquam maximus, velit ipsum laoreet ligula, id ullamcorper lacus nibh eget nisl. Donec eget lacus venenatis enim consequat auctor vel in. +""" + topic_id = TopicId.from_string("0.0.7301777") + # Create a new Topic + submit_topic_message_transaction(client, topic_id, message, key) + + info = TopicInfoQuery().set_topic_id(topic_id).execute(client) + print(info) +if __name__ == "__main__": + main() + + \ No newline at end of file diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index 84ec53c4a..adafb9bf6 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -1,19 +1,18 @@ -""" -This module provides the `TopicMessageSubmitTransaction` class for submitting -messages to Hedera Consensus Service topics using the Hiero SDK. -""" -from typing import Optional - +import math +from typing import List, Optional +from hiero_sdk_python.client.client import Client from hiero_sdk_python.consensus.topic_id import TopicId +from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.transaction.transaction import Transaction from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit -from hiero_sdk_python.hapi.services import consensus_submit_message_pb2 +from hiero_sdk_python.hapi.services import consensus_submit_message_pb2, timestamp_pb2 from hiero_sdk_python.hapi.services import transaction_pb2 from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import ( SchedulableTransactionBody, ) from hiero_sdk_python.channels import _Channel from hiero_sdk_python.executable import _Method +from hiero_sdk_python.transaction.transaction_id import TransactionId class TopicMessageSubmitTransaction(Transaction): @@ -28,6 +27,8 @@ def __init__( self, topic_id: Optional[TopicId] = None, message: Optional[str] = None, + chunk_size: Optional[int] = None, + max_chunks: Optional[int] = None ) -> None: """ Initializes a new TopicMessageSubmitTransaction instance. @@ -38,6 +39,26 @@ def __init__( super().__init__() self.topic_id: Optional[TopicId] = topic_id self.message: Optional[str] = message + self.chunk_size: int = chunk_size or 1024 + self.max_chunks: int = max_chunks or 20 + + self._current_index = 0 + self._total_chunks = self.get_required_chunks() + self._transaction_ids: List[TransactionId] = [] + self._signing_keys: List["PrivateKey"] = [] + + def get_required_chunks(self) -> int: + """ + Returns the number of chunks required for the current message. + + Returns: + int: Number of chunks required. + """ + if not self.message: + return 1 + + content = self.message.encode("utf-8") + return math.ceil(len(content) / self.chunk_size) def set_topic_id( self, topic_id: TopicId @@ -67,6 +88,35 @@ def set_message(self, message: str) -> "TopicMessageSubmitTransaction": """ self._require_not_frozen() self.message = message + self._total_chunks = self.get_required_chunks() + return self + + def set_chunk_size(self, chunk_size: int) -> "TopicMessageSubmitTransaction": + """ + Set maximum chunk size in bytes. + + Args: + chunck_size (int): The size of each chunk in bytes. + + Returns: + TopicMessageSubmitTransaction: This transaction instance (for chaining). + """ + self._require_not_frozen() + self.chunk_size = chunk_size + return self + + def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": + """ + Set maximum allowed chunks. + + Args: + mac_chunks (int): The maximum number of chunks allowed. + + Returns: + TopicMessageSubmitTransaction: This transaction instance (for chaining). + """ + self._require_not_frozen() + self.max_chunks = max_chunks return self def set_custom_fee_limits( @@ -100,10 +150,23 @@ def add_custom_fee_limit( self._require_not_frozen() self.custom_fee_limits.append(custom_fee_limit) return self + + def _validate_chunking(self) -> None: + """ + Validates that chunk count does not exceed max_chunks. - def _build_proto_body( - self, - ) -> consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody: + Raises: + ValueError: If chunk count exceeds `max_chunks`. + """ + required = self.get_required_chunks() + + if self.max_chunks and required > self.max_chunks: + raise ValueError( + f"Message requires {required} chunks but max_chunks={self.max_chunks}. " + f"Increase limit with set_max_chunks()." + ) + + def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody: """ Returns the protobuf body for the topic message submit transaction. @@ -117,11 +180,29 @@ def _build_proto_body( raise ValueError("Missing required fields: topic_id.") if self.message is None: raise ValueError("Missing required fields: message.") + + content = self.message.encode("utf-8") + + start_index = self._current_index * self.chunk_size + end_index = min(start_index + self.chunk_size, len(content)) + chunk_content = content[start_index:end_index] + - return consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody( + body = consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody( topicID=self.topic_id._to_proto(), - message=bytes(self.message, "utf-8"), + message=chunk_content ) + + # Multi-chunk metadata + if self._total_chunks > 1: + body.chunkInfo.CopyFrom(consensus_submit_message_pb2.ConsensusMessageChunkInfo( + initialTransactionID=self.transaction_id._to_proto(), + total=self._total_chunks, + number=self._current_index + 1 + )) + + return body + def build_transaction_body(self) -> transaction_pb2.TransactionBody: """ Builds and returns the protobuf transaction body for message submission. @@ -160,3 +241,86 @@ def _get_method(self, channel: _Channel) -> _Method: transaction_func=channel.topic.submitMessage, query_func=None ) + + def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": + if self._transaction_body_bytes: + return self + + if self.transaction_id is None: + self.transaction_id = client.generate_transaction_id() + + self._transaction_ids = [] + + base_timestamp = self.transaction_id.valid_start + + for i in range(self.get_required_chunks()): + if i == 0: + # First chunk uses the original transaction ID + chunk_transaction_id = self.transaction_id + else: + chunk_valid_start = timestamp_pb2.Timestamp( + seconds=base_timestamp.seconds, + nanos=base_timestamp.nanos + i + ) + chunk_transaction_id = TransactionId( + account_id=self.transaction_id.account_id, + valid_start=chunk_valid_start + ) + + self._transaction_ids.append(chunk_transaction_id) + + for node in client.network.nodes: + self.node_account_id = node._account_id + transaction_body = self.build_transaction_body() + self._transaction_body_bytes[node._account_id] = transaction_body.SerializeToString() + + self.node_account_id = client.network.current_node._account_id + + return self + + def execute(self, client: "Client"): + self._validate_chunking() + + if self.get_required_chunks() == 1: + return super().execute(client) + + # Multi-chunk transaction - execute all chunks + responses = [] + print(self.get_required_chunks()) + + for chunk_index in range(self.get_required_chunks()): + print(chunk_index) + self._current_index = chunk_index + + if self._transaction_ids and chunk_index < len(self._transaction_ids): + self.transaction_id = self._transaction_ids[chunk_index] + + self._transaction_body_bytes.clear() + self._signature_map.clear() + + self.freeze_with(client) + + for signing_key in self._signing_keys: + super().sign(signing_key) + + # Execute the chunk + response = super().execute(client) + responses.append(response) + + # Return the first response JS SDK do + return responses[0] if responses else None + + def sign(self, private_key: "PrivateKey"): + """ + Signs the transaction using the provided private key. + + For multi-chunk transactions, this stores the signing key for later use. + + Args: + private_key (PrivateKey): The private key to sign the transaction with. + """ + if private_key not in self._signing_keys: + self._signing_keys.append(private_key) + + super().sign(private_key) + return self \ No newline at end of file diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index 818689afd..cb2579252 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -11,157 +11,186 @@ ) from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery +from hiero_sdk_python.query.topic_info_query import TopicInfoQuery from hiero_sdk_python.response_code import ResponseCode from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit -from tests.integration.utils_for_test import IntegrationTestEnv +from tests.integration.utils_for_test import env + +def get_topic_info(client, topic_id): + return TopicInfoQuery().set_topic_id(topic_id).execute(client) @pytest.mark.integration -def test_integration_topic_message_submit_transaction_can_execute(): +def test_integration_topic_message_submit_transaction_can_execute(env): """Test that a topic message submit transaction executes.""" - env = IntegrationTestEnv() - - try: - create_transaction = TopicCreateTransaction( - memo="Python SDK topic", admin_key=env.public_operator_key - ) + create_transaction = TopicCreateTransaction( + memo="Python SDK topic", admin_key=env.public_operator_key + ) - create_transaction.freeze_with(env.client) - create_receipt = create_transaction.execute(env.client) - topic_id = create_receipt.topic_id + create_transaction.freeze_with(env.client) + create_receipt = create_transaction.execute(env.client) + topic_id = create_receipt.topic_id - message_transaction = TopicMessageSubmitTransaction( - topic_id=topic_id, message="Hello, Python SDK!" - ) + message_transaction = TopicMessageSubmitTransaction( + topic_id=topic_id, message="Hello, Python SDK!" + ) - message_transaction.freeze_with(env.client) - message_receipt = message_transaction.execute(env.client) + message_transaction.freeze_with(env.client) + message_receipt = message_transaction.execute(env.client) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert ( + message_receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" - delete_transaction = TopicDeleteTransaction(topic_id=topic_id) - delete_transaction.freeze_with(env.client) - delete_receipt = delete_transaction.execute(env.client) + delete_transaction = TopicDeleteTransaction(topic_id=topic_id) + delete_transaction.freeze_with(env.client) + delete_receipt = delete_transaction.execute(env.client) - assert ( - delete_receipt.status == ResponseCode.SUCCESS - ), f"Topic deletion failed with status: {ResponseCode(delete_receipt.status).name}" - finally: - env.close() + assert ( + delete_receipt.status == ResponseCode.SUCCESS + ), f"Topic deletion failed with status: {ResponseCode(delete_receipt.status).name}" @pytest.mark.integration -def test_integration_topic_message_submit_transaction_can_execute_with_custom_fee_limit(): +def test_integration_topic_message_submit_transaction_can_execute_with_custom_fee_limit(env): """Test that a topic message submit transaction executes with a custom fee limit.""" - env = IntegrationTestEnv() - + account = env.create_account(3) # Create an account with 3 Hbar balance - try: - topic_fee = ( - CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) - ) + topic_fee = ( + CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) + ) - receipt = ( - TopicCreateTransaction() - .set_memo("Python SDK topic") - .set_custom_fees([topic_fee]) - .execute(env.client) - ) + receipt = ( + TopicCreateTransaction() + .set_memo("Python SDK topic") + .set_custom_fees([topic_fee]) + .execute(env.client) + ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - topic_id = receipt.topic_id + assert ( + receipt.status == ResponseCode.SUCCESS + ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + topic_id = receipt.topic_id - balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() == Hbar(3).to_tinybars() - ), f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" + balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) + assert ( + balance.hbars.to_tinybars() == Hbar(3).to_tinybars() + ), f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" - env.client.set_operator(account.id, account.key) # Set the operator to the account + env.client.set_operator(account.id, account.key) # Set the operator to the account - topic_message_submit_fee_limit = ( - CustomFeeLimit().set_payer_id(account.id).add_custom_fee(topic_fee) - ) # Create a custom limit for the topic message submit transaction + topic_message_submit_fee_limit = ( + CustomFeeLimit().set_payer_id(account.id).add_custom_fee(topic_fee) + ) # Create a custom limit for the topic message submit transaction - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message("Hello, Python SDK!") - .add_custom_fee_limit(topic_message_submit_fee_limit) - ) + tx = ( + TopicMessageSubmitTransaction() + .set_topic_id(topic_id) + .set_message("Hello, Python SDK!") + .add_custom_fee_limit(topic_message_submit_fee_limit) + ) - tx.transaction_fee = Hbar(2).to_tinybars() - receipt = tx.execute(env.client) + tx.transaction_fee = Hbar(2).to_tinybars() + receipt = tx.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" + assert ( + receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" - balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() < Hbar(2).to_tinybars() - ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" - finally: - env.close() + balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) + assert ( + balance.hbars.to_tinybars() < Hbar(2).to_tinybars() + ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" + @pytest.mark.integration -def test_integration_scheduled_topic_message_submit_transaction_can_execute_with_custom_fee_limit(): +def test_integration_scheduled_topic_message_submit_transaction_can_execute_with_custom_fee_limit(env): """Test that a scheduled topic message submit transaction executes with a custom fee limit.""" - env = IntegrationTestEnv() - account = env.create_account(3) # Create an account with 3 Hbar balance - try: - topic_fee = ( - CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) + topic_fee = ( + CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) + ) + + receipt = ( + TopicCreateTransaction() + .set_memo("Python SDK topic") + .set_custom_fees([topic_fee]) + .execute(env.client) + ) + + assert ( + receipt.status == ResponseCode.SUCCESS + ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" + topic_id = receipt.topic_id + + balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) + assert ( + balance.hbars.to_tinybars() == Hbar(3).to_tinybars() + ), f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" + + env.client.set_operator(account.id, account.key) # Set the operator to the account + + topic_message_submit_fee_limit = ( + CustomFeeLimit().set_payer_id(account.id).add_custom_fee(topic_fee) + ) # Create a custom limit for the topic message submit transaction + + tx = ( + TopicMessageSubmitTransaction() + .set_topic_id(topic_id) + .set_message("Hello, Python SDK!") + .add_custom_fee_limit(topic_message_submit_fee_limit) + .schedule() + ) + tx.transaction_fee = Hbar(2).to_tinybars() + receipt = tx.execute(env.client) + + assert ( + receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" + + balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) + assert ( + balance.hbars.to_tinybars() < Hbar(2).to_tinybars() + ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" + + +def test_topic_message_submit_transation_can_submit_a_large_message(env): + """Test topic message submit transaction can submit large message.""" + topic_tx = ( + TopicCreateTransaction( + memo="Python SDK topic", + admin_key=env.operator_key ) + .freeze_with(env.client) + ) - receipt = ( - TopicCreateTransaction() - .set_memo("Python SDK topic") - .set_custom_fees([topic_fee]) - .execute(env.client) - ) + topic_receipt = topic_tx.execute(env.client) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - topic_id = receipt.topic_id + assert topic_receipt.status == ResponseCode.SUCCESS + + topic_id = topic_receipt.topic_id - balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() == Hbar(3).to_tinybars() - ), f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" + info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) - env.client.set_operator(account.id, account.key) # Set the operator to the account + # Check no message has been submited + assert info.sequence_number == 0 - topic_message_submit_fee_limit = ( - CustomFeeLimit().set_payer_id(account.id).add_custom_fee(topic_fee) - ) # Create a custom limit for the topic message submit transaction + message = "A" * (1024 * 14) - tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message("Hello, Python SDK!") - .add_custom_fee_limit(topic_message_submit_fee_limit) - .schedule() - ) - tx.transaction_fee = Hbar(2).to_tinybars() - receipt = tx.execute(env.client) - - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" - - balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) - assert ( - balance.hbars.to_tinybars() < Hbar(2).to_tinybars() - ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" - finally: - env.close() + message_tx = ( + TopicMessageSubmitTransaction() + .set_topic_id(topic_id) + .set_message(message) + .freeze_with(env.client) + ) + + message_receipt = message_tx.execute(env.client) + + assert message_receipt.status == ResponseCode.SUCCESS + + info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) + assert info.sequence_number == 14 \ No newline at end of file From 6f7f49d9a47cd56a9aebc383cc72f830c09a7fa1 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Sat, 22 Nov 2025 22:58:54 +0530 Subject: [PATCH 02/14] feat: added unit and integration test for chuck message Signed-off-by: Manish Dait --- .../topic_message_submit_chunked.py | 0 .../consensus/topic_message.py | 8 +- .../topic_message_submit_transaction.py | 1 - .../query/topic_message_query.py | 15 +- .../topic_message_qurey_e2e_test.py | 183 ++++++++++++++++++ 5 files changed, 199 insertions(+), 8 deletions(-) rename examples/{ => consensus}/topic_message_submit_chunked.py (100%) create mode 100644 tests/integration/topic_message_qurey_e2e_test.py diff --git a/examples/topic_message_submit_chunked.py b/examples/consensus/topic_message_submit_chunked.py similarity index 100% rename from examples/topic_message_submit_chunked.py rename to examples/consensus/topic_message_submit_chunked.py diff --git a/src/hiero_sdk_python/consensus/topic_message.py b/src/hiero_sdk_python/consensus/topic_message.py index b655b6eb0..0ea6fe84f 100644 --- a/src/hiero_sdk_python/consensus/topic_message.py +++ b/src/hiero_sdk_python/consensus/topic_message.py @@ -103,11 +103,13 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic chunks: List[TopicMessageChunk] = [] total_size: int = 0 transaction_id: Optional[str] = None - + for r in sorted_responses: c = TopicMessageChunk(r) chunks.append(c) + total_size += len(r.message) + if ( transaction_id is None @@ -116,11 +118,13 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic ): tx_id = r.chunkInfo.initialTransactionID transaction_id = ( - f"{tx_id.shardNum}.{tx_id.realmNum}.{tx_id.accountNum}-" + f"{tx_id.accountID.shardNum, '0'}.{tx_id.accountID.realmNum, '0'}.{tx_id.accountID.accountNum}-" f"{tx_id.transactionValidStart.seconds}.{tx_id.transactionValidStart.nanos}" ) + print(transaction_id) contents = bytearray(total_size) + offset: int = 0 for r in sorted_responses: end = offset + len(r.message) diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index adafb9bf6..633988fa3 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -289,7 +289,6 @@ def execute(self, client: "Client"): print(self.get_required_chunks()) for chunk_index in range(self.get_required_chunks()): - print(chunk_index) self._current_index = chunk_index if self._transaction_ids and chunk_index < len(self._transaction_ids): diff --git a/src/hiero_sdk_python/query/topic_message_query.py b/src/hiero_sdk_python/query/topic_message_query.py index ce895ce1a..fa9db5db7 100644 --- a/src/hiero_sdk_python/query/topic_message_query.py +++ b/src/hiero_sdk_python/query/topic_message_query.py @@ -144,17 +144,21 @@ def run_stream(): continue initial_tx_id = response.chunkInfo.initialTransactionID - tx_id_str = (f"{initial_tx_id.shardNum}." - f"{initial_tx_id.realmNum}." - f"{initial_tx_id.accountNum}-" + tx_id_str = (f"{initial_tx_id.accountID.shardNum or '0'}." + f"{initial_tx_id.accountID.realmNum or '0'}." + f"{initial_tx_id.accountID.accountNum}-" f"{initial_tx_id.transactionValidStart.seconds}." f"{initial_tx_id.transactionValidStart.nanos}") + if tx_id_str not in pending_chunks: pending_chunks[tx_id_str] = [] + pending_chunks[tx_id_str].append(response) - if len(pending_chunks[tx_id_str]) == response.chunkInfo.total: - chunk_list = pending_chunks.pop(tx_id_str) + if len(pending_chunks) == response.chunkInfo.total: + chunk_list = [resp for chunk_list in pending_chunks.values() for resp in chunk_list] + pending_chunks.clear() + msg_obj = TopicMessage.of_many(chunk_list) on_message(msg_obj) @@ -164,6 +168,7 @@ def run_stream(): except Exception as e: if subscription_handle.is_cancelled(): + return attempt += 1 diff --git a/tests/integration/topic_message_qurey_e2e_test.py b/tests/integration/topic_message_qurey_e2e_test.py new file mode 100644 index 000000000..4a5a32435 --- /dev/null +++ b/tests/integration/topic_message_qurey_e2e_test.py @@ -0,0 +1,183 @@ +""" +Integration tests for the TopicMessageSubmitTransaction class. +""" + +from datetime import datetime, timezone +import time +from typing import Any, List +import pytest + +from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction +from hiero_sdk_python.consensus.topic_delete_transaction import TopicDeleteTransaction +from hiero_sdk_python.consensus.topic_message import TopicMessage +from hiero_sdk_python.consensus.topic_message_submit_transaction import ( + TopicMessageSubmitTransaction, +) +from hiero_sdk_python.hbar import Hbar +from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery +from hiero_sdk_python.query.topic_message_query import TopicMessageQuery +from hiero_sdk_python.response_code import ResponseCode +from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee +from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit +from tests.integration.utils_for_test import IntegrationTestEnv + + +@pytest.mark.integration +def test_integration_topic_message_submit_transaction_can_execute(): + """Test that a topic message submit transaction executes.""" + env = IntegrationTestEnv() + + try: + create_transaction = TopicCreateTransaction( + memo="Python SDK topic", admin_key=env.public_operator_key + ) + + create_transaction.freeze_with(env.client) + create_receipt = create_transaction.execute(env.client) + topic_id = create_receipt.topic_id + + + message: List[Any] = [] + + def get_message(m: TopicMessage): + print("message_reviver") + message.append(m) + + def on_error_handler(e): + print(f"Subscription error: {e}") + + query = TopicMessageQuery( + topic_id=topic_id, + start_time=datetime.now(timezone.utc), + limit=0, + chunking_enabled=True + ) + + handle = query.subscribe( + env.client, + on_message=get_message, + on_error=on_error_handler + ) + + message_transaction = TopicMessageSubmitTransaction( + topic_id=topic_id, message="Hello, Python SDK!" + ) + + message_transaction.freeze_with(env.client) + message_receipt = message_transaction.execute(env.client) + + assert ( + message_receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + + + while len(message) == 0: + time.sleep(2) + + assert message == "Hello, Python SDK!" + + finally: + env.close() + + +@pytest.mark.integration +def test_integration_topic_message_submit_large_transaction_can_execute(): + """Test that a topic message submit transaction executes.""" + env = IntegrationTestEnv() + + try: + create_transaction = TopicCreateTransaction( + memo="Python SDK topic", admin_key=env.public_operator_key + ) + + create_transaction.freeze_with(env.client) + create_receipt = create_transaction.execute(env.client) + topic_id = create_receipt.topic_id + + + message: List[Any] = [] + + def get_message(m: TopicMessage): + print("message_reviver") + message.append(m) + + def on_error_handler(e): + print(f"Subscription error: {e}") + + query = TopicMessageQuery( + topic_id=topic_id, + start_time=datetime.now(timezone.utc), + limit=0, + chunking_enabled=True + ) + + handle = query.subscribe( + env.client, + on_message=get_message, + on_error=on_error_handler + ) + content = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur aliquam augue sem, ut mattis dui laoreet a. Curabitur consequat est euismod, scelerisque metus et, tristique dui. Nulla commodo mauris ut faucibus ultricies. Quisque venenatis nisl nec augue tempus, at efficitur elit eleifend. Duis pharetra felis metus, sed dapibus urna vehicula id. Duis non venenatis turpis, sit amet ornare orci. Donec non interdum quam. Sed finibus nunc et risus finibus, non sagittis lorem cursus. Proin pellentesque tempor aliquam. Sed congue nisl in enim bibendum, condimentum vehicula nisi feugiat. + +Suspendisse non sodales arcu. Suspendisse sodales, lorem ac mollis blandit, ipsum neque porttitor nulla, et sodales arcu ante fermentum tellus. Integer sagittis dolor sed augue fringilla accumsan. Cras vitae finibus arcu, sit amet varius dolor. Etiam id finibus dolor, vitae luctus velit. Proin efficitur augue nec pharetra accumsan. Aliquam lobortis nisl diam, vel fermentum purus finibus id. Etiam at finibus orci, et tincidunt turpis. Aliquam imperdiet congue lacus vel facilisis. Phasellus id magna vitae enim dapibus vestibulum vitae quis augue. Morbi eu consequat enim. Maecenas neque nulla, pulvinar sit amet consequat sed, tempor sed magna. Mauris lacinia sem feugiat faucibus aliquet. Etiam congue non turpis at commodo. Nulla facilisi. + +Nunc velit turpis, cursus ornare fringilla eu, lacinia posuere leo. Mauris rutrum ultricies dui et suscipit. Curabitur in euismod ligula. Curabitur vitae faucibus orci. Phasellus volutpat vestibulum diam sit amet vestibulum. In vel purus leo. Nulla condimentum lectus vestibulum lectus faucibus, id lobortis eros consequat. Proin mollis libero elit, vel aliquet nisi imperdiet et. Morbi ornare est velit, in vehicula nunc malesuada quis. Donec vehicula convallis interdum. + +Integer pellentesque in nibh vitae aliquet. Ut at justo id libero dignissim hendrerit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent quis ornare lectus. Nam malesuada non diam quis cursus. Phasellus a libero ligula. Suspendisse ligula elit, congue et nisi sit amet, cursus euismod dolor. Morbi aliquam, nulla a posuere pellentesque, diam massa ornare risus, nec eleifend neque eros et elit. + +Pellentesque a sodales dui. Sed in efficitur ante. Duis eget volutpat elit, et ornare est. Nam felis dolor, placerat mattis diam id, maximus lobortis quam. Sed pellentesque lobortis sem sed placerat. Pellentesque augue odio, molestie sed lectus sit amet, congue volutpat massa. Quisque congue consequat nunc id fringilla. Duis semper nulla eget enim venenatis dapibus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque varius turpis nibh, sit amet malesuada mauris malesuada quis. Vivamus dictum egestas placerat. Vivamus id augue elit. + +Cras fermentum volutpat eros, vitae euismod lorem viverra nec. Donec lectus augue, porta eleifend odio sit amet, condimentum rhoncus urna. Nunc sed odio velit. Morbi non cursus odio, eget imperdiet erat. Nunc rhoncus massa non neque volutpat, sit amet faucibus ante congue. Phasellus nec lorem vel leo accumsan lobortis. Maecenas id ligula bibendum purus suscipit posuere ac eget diam. Nam in quam pretium, semper erat auctor, iaculis odio. Maecenas placerat, nisi ac elementum tempor, felis nulla porttitor orci, ac rhoncus diam justo in elit. Etiam lobortis fermentum ligula in tincidunt. Donec quis vestibulum nunc. Sed eros diam, interdum in porta lobortis, gravida eu magna. Donec diam purus, finibus et sollicitudin quis, fringilla nec nisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur ultricies sagittis dapibus. Etiam ullamcorper aliquet libero, eu venenatis mauris suscipit id. + +Ut interdum eleifend sem, vel bibendum ipsum volutpat eget. Nunc ac dignissim augue. Aliquam ornare aliquet magna id dignissim. Vestibulum velit sem, lacinia eu rutrum in, rhoncus vitae mauris. Pellentesque scelerisque pulvinar mauris non cursus. Integer id dolor porta, bibendum sem vel, pretium tortor. Fusce a nisi convallis, interdum quam condimentum, suscipit dolor. Sed magna diam, efficitur non nunc in, tincidunt varius mi. Aliquam ullamcorper nulla eu fermentum bibendum. Vivamus a felis pretium, hendrerit enim vitae, hendrerit leo. Suspendisse lacinia lectus a diam consectetur suscipit. Aenean hendrerit nisl sed diam venenatis pellentesque. Nullam egestas lectus a consequat pharetra. Vivamus ornare tellus auctor, facilisis lacus id, feugiat dui. Nam id est ut est rhoncus varius. + +Aenean vel vehicula erat. Aenean gravida risus vitae purus sodales, quis dictum enim porta. Ut elit elit, fermentum sed posuere non, accumsan eu justo. Integer porta malesuada quam, et elementum massa suscipit nec. Donec in elit diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis suscipit vel ante volutpat vestibulum. + +Pellentesque ex arcu, euismod et sapien ut, vulputate suscipit enim. Donec mattis sagittis augue, et mattis lacus. Cras placerat consequat lorem sed luctus. Nam suscipit aliquam sem ac imperdiet. Mauris a semper augue, pulvinar fringilla magna. Integer pretium massa non risus commodo hendrerit. Sed dictum libero id erat sodales mattis. Etiam auctor dolor lectus, ut sagittis enim lobortis vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur nec orci lobortis, cursus risus eget, sollicitudin massa. Integer vel tincidunt mi, id eleifend quam. Nullam facilisis nisl eu mauris congue, vitae euismod nisi malesuada. Vivamus sit amet urna et libero sagittis dictum. + +In hac habitasse platea dictumst. Aliquam erat volutpat. Ut dictum, mi a viverra venenatis, mi urna pulvinar nisi, nec gravida lectus diam eget urna. Ut dictum sit amet nisl ut dignissim. Sed sed mauris scelerisque, efficitur augue in, vulputate turpis. Proin dolor justo, bibendum et sollicitudin feugiat, imperdiet sed mi. Sed elementum vitae massa vel lobortis. Cras vitae massa sit amet libero dictum tempus. + +Vivamus ut mauris lectus. Curabitur placerat ornare scelerisque. Cras malesuada nunc quis tortor pretium bibendum vel sed dui. Cras lobortis nibh eu erat blandit, sit amet consequat neque fermentum. Phasellus imperdiet molestie tristique. Cras auctor purus erat, id mollis ligula porttitor eget. Mauris porta pharetra odio et finibus. Suspendisse eu est a ligula bibendum cursus. Mauris ac laoreet libero. Nullam volutpat sem quis rhoncus gravida. + +Donec malesuada lacus ac iaculis cursus. Sed sem orci, feugiat ac est ut, ultricies posuere nisi. Suspendisse potenti. Phasellus ut ultricies purus. Etiam sem tortor, fermentum quis aliquam eget, consequat ut nulla. Aliquam dictum metus in mi fringilla, vel gravida nulla accumsan. Cras aliquam eget leo vel posuere. Vivamus vitae malesuada nunc. Morbi placerat magna mi, id suscipit lacus auctor quis. Nam at lorem vel odio finibus fringilla ut ac velit. Donec laoreet at nibh quis vehicula. + +Fusce ac tristique nisi. Donec leo nisi, consectetur at tellus sit amet, consectetur ultrices dui. Quisque gravida mollis tempor. Maecenas semper, sapien ut dignissim feugiat, massa enim viverra dolor, non varius eros nulla nec felis. Nunc massa lacus, ornare et feugiat id, bibendum quis purus. Praesent viverra elit sit amet purus consectetur venenatis. Maecenas nibh risus, elementum sit amet enim sagittis, ultrices malesuada lectus. Vivamus non felis ante. Ut vulputate ex arcu. Aliquam porta gravida porta. Aliquam eros leo, malesuada quis eros non, maximus tristique neque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ligula orci, mollis vel luctus nec, venenatis vitae est. Fusce rutrum convallis nisi. + +Nunc laoreet eget nibh in feugiat. Pellentesque nec arcu cursus, gravida dolor a, pellentesque nisi. Praesent vel justo blandit, placerat risus eget, consectetur orci. Sed maximus metus mi, ut euismod augue ultricies in. Nunc id risus hendrerit, aliquet lorem nec, congue justo. Vestibulum vel nunc ac est euismod mattis ac vitae nulla. Donec blandit luctus mauris, sit amet bibendum dui egestas et. Aenean nec lorem nec elit ornare rutrum nec eget ligula. Fusce a ipsum vitae neque elementum pharetra. Pellentesque ullamcorper ullamcorper libero, vitae porta sem sagittis vel. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Duis at massa sit amet risus pellentesque varius sit amet vitae eros. Cras tempor aliquet sapien, vehicula varius neque volutpat et. Donec purus nibh, pellentesque ut lobortis nec, ultricies ultricies nisl. Sed accumsan ut dui sit amet vulputate. Suspendisse eu facilisis massa, a hendrerit mauris. Nulla elementum molestie tincidunt. Donec mi justo, ornare vel tempor id, gravida et orci. Nam molestie erat nec nisi bibendum accumsan. Duis vitae tempor ante. Morbi congue mauris vel sagittis facilisis. Vivamus vehicula odio orci, a tempor nibh euismod in. Proin mattis, nibh at feugiat porta, purus velit posuere felis, quis volutpat sapien dui vel odio. Nam fermentum sem nec euismod aliquet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. + +Mauris congue lacus tortor. Pellentesque arcu eros, accumsan imperdiet porttitor vitae, mattis nec justo. Nullam ac aliquam mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse potenti. Fusce accumsan tempus felis a sagittis. Maecenas et velit odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam eros lacus, volutpat non urna sed, tincidunt ullamcorper elit. Sed sit amet gravida libero. In varius mi vel diam sollicitudin mollis. + +Aenean varius, diam vitae hendrerit feugiat, libero augue ultrices odio, eget consequat sem tellus eu nisi. Nam dapibus enim et auctor sollicitudin. Nunc iaculis eros orci, ac accumsan eros malesuada ut. Ut semper augue felis, nec sodales lorem consectetur non. Cras gravida eleifend est, et sagittis eros imperdiet congue. Fusce id tellus dapibus nunc scelerisque tempus. Donec tempor placerat libero, in commodo nisi bibendum eu. Donec id eros non est sollicitudin luctus. Duis bibendum bibendum tellus nec viverra. Aliquam non faucibus ex, nec luctus dui. Curabitur efficitur varius urna non dignissim. Suspendisse elit elit, ultrices in elementum eu, tempor at magna. + +Nunc in purus sit amet mi laoreet pulvinar placerat eget sapien. Donec vel felis at dui ultricies euismod quis vel neque. Donec tincidunt urna non eros pretium blandit. Nullam congue tincidunt condimentum. Curabitur et libero nibh. Proin ultricies risus id imperdiet scelerisque. Suspendisse purus mi, viverra vitae risus ut, tempus tincidunt enim. Ut luctus lobortis nisl, eget venenatis tortor cursus non. Mauris finibus nisl quis gravida ultricies. Fusce elementum lacus sit amet nunc congue, in porta nulla tincidunt. + +Mauris ante risus, imperdiet blandit posuere in, blandit eu ipsum. Integer et auctor arcu. Integer quis elementum purus. Nunc in ultricies nisl, sed rutrum risus. Suspendisse venenatis eros nec lorem rhoncus, in scelerisque velit condimentum. Etiam condimentum quam felis, in elementum odio mattis et. In ut nibh porttitor, hendrerit tellus vel, luctus magna. Vestibulum et ligula et dolor pellentesque porta. Aenean efficitur porta massa et bibendum. Nulla vehicula sem in risus volutpat, a eleifend elit maximus. + +Proin orci lorem, auctor a felis eu, pretium lobortis nulla. Phasellus aliquam efficitur interdum. Sed sit amet velit rutrum est dictum facilisis. Duis cursus enim at nisl tincidunt, eu molestie elit vehicula. Cras pellentesque nisl id enim feugiat fringilla. In quis tincidunt neque. Nam eu consectetur dolor. Ut id interdum mauris. Mauris nunc tortor, placerat in tempor ut, vestibulum eu nisl. Integer vel dapibus ipsum. Nunc id erat pulvinar, tincidunt magna id, condimentum massa. Pellentesque consequat est eget odio placerat vehicula. Etiam augue neque, sagittis non leo eu, tristique scelerisque dui. Ut dui urna, blandit quis urna ac, tincidunt tristique turpis. + +Suspendisse venenatis rhoncus ligula ultrices condimentum. In id laoreet eros. Suspendisse suscipit fringilla leo id euismod. Sed in quam libero. Ut at ligula tellus. Sed tristique gravida dui, at egestas odio aliquam iaculis. Praesent imperdiet velit quis nibh consequat, quis pretium sem sagittis. Donec tortor ex, congue sit amet pulvinar ac, rutrum non est. Praesent ipsum magna, venenatis sit amet vulputate id, eleifend ac ipsum. + +In consequat, nisi iaculis laoreet elementum, massa mauris varius nisi, et porta nisi velit at urna. Maecenas sit amet aliquet eros, a rhoncus nisl. Maecenas convallis enim nunc. Morbi purus nisl, aliquam ac tincidunt sed, mattis in augue. Quisque et elementum quam, vitae maximus orci. Suspendisse hendrerit risus nec vehicula placerat. Nulla et lectus nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Etiam ut sodales ex. Nulla luctus, magna eu scelerisque sagittis, nibh quam consectetur neque, non rutrum dolor metus nec ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed egestas augue elit, sollicitudin accumsan massa lobortis ac. Curabitur placerat, dolor a aliquam maximus, velit ipsum laoreet ligula, id ullamcorper lacus nibh eget nisl. Donec eget lacus venenatis enim consequat auctor vel in. +""" + + message_transaction = TopicMessageSubmitTransaction( + topic_id=topic_id, message=content + ) + + message_transaction.freeze_with(env.client) + message_receipt = message_transaction.execute(env.client) + + assert ( + message_receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + + + while len(message) == 0: + time.sleep(2) + + assert message[0] == content + finally: + env.close() From bb039e865ee365be56ddb68fdacb366e04cb2c80 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Sat, 22 Nov 2025 23:02:54 +0530 Subject: [PATCH 03/14] chore: update changelog.md Signed-off-by: Manish Dait --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f0c5f72..60cbf7a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,8 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. - Added `docs\sdk_developers\training\receipts.md` as a training guide for users to understand hedera receipts. - Add `set_token_ids`, `_from_proto`, `_validate_checksum` to TokenAssociateTransaction [#795] - docs: added `network_and_client.md` with a table of contents, and added external example scripts (`client.py`). +- Add support for chunk message in `TopicSubmitMessageTransaction`. + ### Changed From 31384ce4e8ab76b51f780ae0f4eaf1b17b0fd856 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Sun, 23 Nov 2025 22:44:59 +0530 Subject: [PATCH 04/14] feat: improve the integration test_case Signed-off-by: Manish Dait --- .../consensus/topic_message.py | 24 +- .../topic_message_submit_transaction.py | 33 +-- .../query/topic_message_query.py | 21 +- .../topic_message_qurey_e2e_test.py | 232 ++++++++--------- ...pic_message_submit_transaction_e2e_test.py | 233 ++++++++++++------ 5 files changed, 299 insertions(+), 244 deletions(-) diff --git a/src/hiero_sdk_python/consensus/topic_message.py b/src/hiero_sdk_python/consensus/topic_message.py index 0ea6fe84f..4f25ebb54 100644 --- a/src/hiero_sdk_python/consensus/topic_message.py +++ b/src/hiero_sdk_python/consensus/topic_message.py @@ -8,6 +8,7 @@ from hiero_sdk_python.timestamp import Timestamp from hiero_sdk_python.hapi.mirror import consensus_service_pb2 as mirror_proto +from hiero_sdk_python.transaction.transaction_id import TransactionId class TopicMessageChunk: @@ -40,7 +41,7 @@ def __init__( consensus_timestamp: datetime, message_data: Dict[str, Union[bytes, int]], chunks: List[TopicMessageChunk], - transaction_id: Optional[str] = None, + transaction_id: Optional[TransactionId] = None, ) -> None: """ Args: @@ -52,14 +53,14 @@ def __init__( "sequence_number": int } chunks (List[TopicMessageChunk]): All individual chunks that form this message. - transaction_id (Optional[str]): The transaction ID string if available. + transaction_id (Optional[Transaction]): The transaction ID if available. """ self.consensus_timestamp: datetime = consensus_timestamp self.contents: Union[bytes, int] = message_data["contents"] self.running_hash: Union[bytes, int] = message_data["running_hash"] self.sequence_number: Union[bytes, int] = message_data["sequence_number"] self.chunks: List[TopicMessageChunk] = chunks - self.transaction_id: Optional[str] = transaction_id + self.transaction_id: Optional[TransactionId] = transaction_id @classmethod def of_single(cls, response: mirror_proto.ConsensusTopicResponse) -> "TopicMessage": # type: ignore @@ -72,13 +73,9 @@ def of_single(cls, response: mirror_proto.ConsensusTopicResponse) -> "TopicMessa running_hash: Union[bytes, int] = response.runningHash sequence_number: Union[bytes, int] = chunk.sequence_number - transaction_id: Optional[str] = None + transaction_id: Optional[TransactionId] = None if response.HasField("chunkInfo") and response.chunkInfo.HasField("initialTransactionID"): - tx_id = response.chunkInfo.initialTransactionID - transaction_id = ( - f"{tx_id.shardNum}.{tx_id.realmNum}.{tx_id.accountNum}-" - f"{tx_id.transactionValidStart.seconds}.{tx_id.transactionValidStart.nanos}" - ) + transaction_id = TransactionId._from_proto(response.chunkInfo.initialTransactionID) return cls( consensus_timestamp, @@ -102,7 +99,7 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic chunks: List[TopicMessageChunk] = [] total_size: int = 0 - transaction_id: Optional[str] = None + transaction_id: Optional[TransactionId] = None for r in sorted_responses: c = TopicMessageChunk(r) @@ -116,12 +113,7 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic and r.HasField("chunkInfo") and r.chunkInfo.HasField("initialTransactionID") ): - tx_id = r.chunkInfo.initialTransactionID - transaction_id = ( - f"{tx_id.accountID.shardNum, '0'}.{tx_id.accountID.realmNum, '0'}.{tx_id.accountID.accountNum}-" - f"{tx_id.transactionValidStart.seconds}.{tx_id.transactionValidStart.nanos}" - ) - print(transaction_id) + transaction_id = TransactionId._from_proto(r.chunkInfo.initialTransactionID) contents = bytearray(total_size) diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index 633988fa3..b3bbc23f4 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -44,7 +44,8 @@ def __init__( self._current_index = 0 self._total_chunks = self.get_required_chunks() - self._transaction_ids: List[TransactionId] = [] + self._intial_transaction_id: Optional[TransactionId] = None + self._initial_transaction_id: List[TransactionId] = [] self._signing_keys: List["PrivateKey"] = [] def get_required_chunks(self) -> int: @@ -96,13 +97,17 @@ def set_chunk_size(self, chunk_size: int) -> "TopicMessageSubmitTransaction": Set maximum chunk size in bytes. Args: - chunck_size (int): The size of each chunk in bytes. + chunk_size (int): The size of each chunk in bytes. Returns: TopicMessageSubmitTransaction: This transaction instance (for chaining). """ self._require_not_frozen() + if chunk_size <= 0: + raise ValueError("chunk_size must be positive") + self.chunk_size = chunk_size + self._total_chunks = self.get_required_chunks() return self def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": @@ -116,6 +121,9 @@ def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": TopicMessageSubmitTransaction: This transaction instance (for chaining). """ self._require_not_frozen() + if max_chunks <= 0: + raise ValueError("max_chunks must be positive") + self.max_chunks = max_chunks return self @@ -193,10 +201,10 @@ def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessa message=chunk_content ) - # Multi-chunk metadata + # Multi-chunk metadata if self._total_chunks > 1: body.chunkInfo.CopyFrom(consensus_submit_message_pb2.ConsensusMessageChunkInfo( - initialTransactionID=self.transaction_id._to_proto(), + initialTransactionID=self._intial_transaction_id._to_proto(), total=self._total_chunks, number=self._current_index + 1 )) @@ -255,7 +263,9 @@ def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": for i in range(self.get_required_chunks()): if i == 0: - # First chunk uses the original transaction ID + if self._intial_transaction_id is None: + self._intial_transaction_id = self.transaction_id + chunk_transaction_id = self.transaction_id else: chunk_valid_start = timestamp_pb2.Timestamp( @@ -268,15 +278,9 @@ def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": ) self._transaction_ids.append(chunk_transaction_id) - - for node in client.network.nodes: - self.node_account_id = node._account_id - transaction_body = self.build_transaction_body() - self._transaction_body_bytes[node._account_id] = transaction_body.SerializeToString() - - self.node_account_id = client.network.current_node._account_id + + return super().freeze_with(client) - return self def execute(self, client: "Client"): self._validate_chunking() @@ -286,14 +290,13 @@ def execute(self, client: "Client"): # Multi-chunk transaction - execute all chunks responses = [] - print(self.get_required_chunks()) for chunk_index in range(self.get_required_chunks()): self._current_index = chunk_index if self._transaction_ids and chunk_index < len(self._transaction_ids): self.transaction_id = self._transaction_ids[chunk_index] - + self._transaction_body_bytes.clear() self._signature_map.clear() diff --git a/src/hiero_sdk_python/query/topic_message_query.py b/src/hiero_sdk_python/query/topic_message_query.py index fa9db5db7..fe23c0ed7 100644 --- a/src/hiero_sdk_python/query/topic_message_query.py +++ b/src/hiero_sdk_python/query/topic_message_query.py @@ -1,12 +1,13 @@ import time import threading from datetime import datetime -from typing import Optional, Callable, Union, Dict, List, Any +from typing import Optional, Callable, Union, Dict, List from hiero_sdk_python.hapi.mirror import consensus_service_pb2 as mirror_proto from hiero_sdk_python.hapi.services import basic_types_pb2, timestamp_pb2 from hiero_sdk_python.consensus.topic_id import TopicId from hiero_sdk_python.consensus.topic_message import TopicMessage +from hiero_sdk_python.transaction.transaction_id import TransactionId from hiero_sdk_python.utils.subscription_handle import SubscriptionHandle from hiero_sdk_python.client.client import Client @@ -143,21 +144,15 @@ def run_stream(): on_message(msg_obj) continue - initial_tx_id = response.chunkInfo.initialTransactionID - tx_id_str = (f"{initial_tx_id.accountID.shardNum or '0'}." - f"{initial_tx_id.accountID.realmNum or '0'}." - f"{initial_tx_id.accountID.accountNum}-" - f"{initial_tx_id.transactionValidStart.seconds}." - f"{initial_tx_id.transactionValidStart.nanos}") + initial_tx_id = TransactionId._from_proto(response.chunkInfo.initialTransactionID) - if tx_id_str not in pending_chunks: - pending_chunks[tx_id_str] = [] + if initial_tx_id not in pending_chunks: + pending_chunks[initial_tx_id] = [] - pending_chunks[tx_id_str].append(response) + pending_chunks[initial_tx_id].append(response) - if len(pending_chunks) == response.chunkInfo.total: - chunk_list = [resp for chunk_list in pending_chunks.values() for resp in chunk_list] - pending_chunks.clear() + if len(pending_chunks[initial_tx_id]) == response.chunkInfo.total: + chunk_list = pending_chunks.pop(initial_tx_id) msg_obj = TopicMessage.of_many(chunk_list) on_message(msg_obj) diff --git a/tests/integration/topic_message_qurey_e2e_test.py b/tests/integration/topic_message_qurey_e2e_test.py index 4a5a32435..422916022 100644 --- a/tests/integration/topic_message_qurey_e2e_test.py +++ b/tests/integration/topic_message_qurey_e2e_test.py @@ -1,183 +1,155 @@ """ Integration tests for the TopicMessageSubmitTransaction class. """ - -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone import time -from typing import Any, List +from typing import List import pytest from hiero_sdk_python.consensus.topic_create_transaction import TopicCreateTransaction -from hiero_sdk_python.consensus.topic_delete_transaction import TopicDeleteTransaction from hiero_sdk_python.consensus.topic_message import TopicMessage from hiero_sdk_python.consensus.topic_message_submit_transaction import ( TopicMessageSubmitTransaction, ) -from hiero_sdk_python.hbar import Hbar -from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery from hiero_sdk_python.query.topic_message_query import TopicMessageQuery from hiero_sdk_python.response_code import ResponseCode -from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee -from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit -from tests.integration.utils_for_test import IntegrationTestEnv +from tests.integration.utils_for_test import env +BIG_CONTENT = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur aliquam augue sem, ut mattis dui laoreet a. Curabitur consequat est euismod, scelerisque metus et, tristique dui. Nulla commodo mauris ut faucibus ultricies. Quisque venenatis nisl nec augue tempus, at efficitur elit eleifend. Duis pharetra felis metus, sed dapibus urna vehicula id. Duis non venenatis turpis, sit amet ornare orci. Donec non interdum quam. Sed finibus nunc et risus finibus, non sagittis lorem cursus. Proin pellentesque tempor aliquam. Sed congue nisl in enim bibendum, condimentum vehicula nisi feugiat. +Suspendisse non sodales arcu. Suspendisse sodales, lorem ac mollis blandit, ipsum neque porttitor nulla, et sodales arcu ante fermentum tellus. Integer sagittis dolor sed augue fringilla accumsan. Cras vitae finibus arcu, sit amet varius dolor. Etiam id finibus dolor, vitae luctus velit. Proin efficitur augue nec pharetra accumsan. Aliquam lobortis nisl diam, vel fermentum purus finibus id. Etiam at finibus orci, et tincidunt turpis. Aliquam imperdiet congue lacus vel facilisis. Phasellus id magna vitae enim dapibus vestibulum vitae quis augue. Morbi eu consequat enim. Maecenas neque nulla, pulvinar sit amet consequat sed, tempor sed magna. Mauris lacinia sem feugiat faucibus aliquet. Etiam congue non turpis at commodo. Nulla facilisi. +Nunc velit turpis, cursus ornare fringilla eu, lacinia posuere leo. Mauris rutrum ultricies dui et suscipit. Curabitur in euismod ligula. Curabitur vitae faucibus orci. Phasellus volutpat vestibulum diam sit amet vestibulum. In vel purus leo. Nulla condimentum lectus vestibulum lectus faucibus, id lobortis eros consequat. Proin mollis libero elit, vel aliquet nisi imperdiet et. Morbi ornare est velit, in vehicula nunc malesuada quis. Donec vehicula convallis interdum. +Integer pellentesque in nibh vitae aliquet. Ut at justo id libero dignissim hendrerit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent quis ornare lectus. Nam malesuada non diam quis cursus. Phasellus a libero ligula. Suspendisse ligula elit, congue et nisi sit amet, cursus euismod dolor. Morbi aliquam, nulla a posuere pellentesque, diam massa ornare risus, nec eleifend neque eros et elit. +Pellentesque a sodales dui. Sed in efficitur ante. Duis eget volutpat elit, et ornare est. Nam felis dolor, placerat mattis diam id, maximus lobortis quam. Sed pellentesque lobortis sem sed placerat. Pellentesque augue odio, molestie sed lectus sit amet, congue volutpat massa. Quisque congue consequat nunc id fringilla. Duis semper nulla eget enim venenatis dapibus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque varius turpis nibh, sit amet malesuada mauris malesuada quis. Vivamus dictum egestas placerat. Vivamus id augue elit. +Cras fermentum volutpat eros, vitae euismod lorem viverra nec. Donec lectus augue, porta eleifend odio sit amet, condimentum rhoncus urna. Nunc sed odio velit. Morbi non cursus odio, eget imperdiet erat. Nunc rhoncus massa non neque volutpat, sit amet faucibus ante congue. Phasellus nec lorem vel leo accumsan lobortis. Maecenas id ligula bibendum purus suscipit posuere ac eget diam. Nam in quam pretium, semper erat auctor, iaculis odio. Maecenas placerat, nisi ac elementum tempor, felis nulla porttitor orci, ac rhoncus diam justo in elit. Etiam lobortis fermentum ligula in tincidunt. Donec quis vestibulum nunc. Sed eros diam, interdum in porta lobortis, gravida eu magna. Donec diam purus, finibus et sollicitudin quis, fringilla nec nisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur ultricies sagittis dapibus. Etiam ullamcorper aliquet libero, eu venenatis mauris suscipit id. +Ut interdum eleifend sem, vel bibendum ipsum volutpat eget. Nunc ac dignissim augue. Aliquam ornare aliquet magna id dignissim. Vestibulum velit sem, lacinia eu rutrum in, rhoncus vitae mauris. Pellentesque scelerisque pulvinar mauris non cursus. Integer id dolor porta, bibendum sem vel, pretium tortor. Fusce a nisi convallis, interdum quam condimentum, suscipit dolor. Sed magna diam, efficitur non nunc in, tincidunt varius mi. Aliquam ullamcorper nulla eu fermentum bibendum. Vivamus a felis pretium, hendrerit enim vitae, hendrerit leo. Suspendisse lacinia lectus a diam consectetur suscipit. Aenean hendrerit nisl sed diam venenatis pellentesque. Nullam egestas lectus a consequat pharetra. Vivamus ornare tellus auctor, facilisis lacus id, feugiat dui. Nam id est ut est rhoncus varius. +Aenean vel vehicula erat. Aenean gravida risus vitae purus sodales, quis dictum enim porta. Ut elit elit, fermentum sed posuere non, accumsan eu justo. Integer porta malesuada quam, et elementum massa suscipit nec. Donec in elit diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis suscipit vel ante volutpat vestibulum. +Pellentesque ex arcu, euismod et sapien ut, vulputate suscipit enim. Donec mattis sagittis augue, et mattis lacus. Cras placerat consequat lorem sed luctus. Nam suscipit aliquam sem ac imperdiet. Mauris a semper augue, pulvinar fringilla magna. Integer pretium massa non risus commodo hendrerit. Sed dictum libero id erat sodales mattis. Etiam auctor dolor lectus, ut sagittis enim lobortis vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur nec orci lobortis, cursus risus eget, sollicitudin massa. Integer vel tincidunt mi, id eleifend quam. Nullam facilisis nisl eu mauris congue, vitae euismod nisi malesuada. Vivamus sit amet urna et libero sagittis dictum. +In hac habitasse platea dictumst. Aliquam erat volutpat. Ut dictum, mi a viverra venenatis, mi urna pulvinar nisi, nec gravida lectus diam eget urna. Ut dictum sit amet nisl ut dignissim. Sed sed mauris scelerisque, efficitur augue in, vulputate turpis. Proin dolor justo, bibendum et sollicitudin feugiat, imperdiet sed mi. Sed elementum vitae massa vel lobortis. Cras vitae massa sit amet libero dictum tempus. +Vivamus ut mauris lectus. Curabitur placerat ornare scelerisque. Cras malesuada nunc quis tortor pretium bibendum vel sed dui. Cras lobortis nibh eu erat blandit, sit amet consequat neque fermentum. Phasellus imperdiet molestie tristique. Cras auctor purus erat, id mollis ligula porttitor eget. Mauris porta pharetra odio et finibus. Suspendisse eu est a ligula bibendum cursus. Mauris ac laoreet libero. Nullam volutpat sem quis rhoncus gravida. +Donec malesuada lacus ac iaculis cursus. Sed sem orci, feugiat ac est ut, ultricies posuere nisi. Suspendisse potenti. Phasellus ut ultricies purus. Etiam sem tortor, fermentum quis aliquam eget, consequat ut nulla. Aliquam dictum metus in mi fringilla, vel gravida nulla accumsan. Cras aliquam eget leo vel posuere. Vivamus vitae malesuada nunc. Morbi placerat magna mi, id suscipit lacus auctor quis. Nam at lorem vel odio finibus fringilla ut ac velit. Donec laoreet at nibh quis vehicula. +Fusce ac tristique nisi. Donec leo nisi, consectetur at tellus sit amet, consectetur ultrices dui. Quisque gravida mollis tempor. Maecenas semper, sapien ut dignissim feugiat, massa enim viverra dolor, non varius eros nulla nec felis. Nunc massa lacus, ornare et feugiat id, bibendum quis purus. Praesent viverra elit sit amet purus consectetur venenatis. Maecenas nibh risus, elementum sit amet enim sagittis, ultrices malesuada lectus. Vivamus non felis ante. Ut vulputate ex arcu. Aliquam porta gravida porta. Aliquam eros leo, malesuada quis eros non, maximus tristique neque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ligula orci, mollis vel luctus nec, venenatis vitae est. Fusce rutrum convallis nisi. +Nunc laoreet eget nibh in feugiat. Pellentesque nec arcu cursus, gravida dolor a, pellentesque nisi. Praesent vel justo blandit, placerat risus eget, consectetur orci. Sed maximus metus mi, ut euismod augue ultricies in. Nunc id risus hendrerit, aliquet lorem nec, congue justo. Vestibulum vel nunc ac est euismod mattis ac vitae nulla. Donec blandit luctus mauris, sit amet bibendum dui egestas et. Aenean nec lorem nec elit ornare rutrum nec eget ligula. Fusce a ipsum vitae neque elementum pharetra. Pellentesque ullamcorper ullamcorper libero, vitae porta sem sagittis vel. Interdum et malesuada fames ac ante ipsum primis in faucibus. +Duis at massa sit amet risus pellentesque varius sit amet vitae eros. Cras tempor aliquet sapien, vehicula varius neque volutpat et. Donec purus nibh, pellentesque ut lobortis nec, ultricies ultricies nisl. Sed accumsan ut dui sit amet vulputate. Suspendisse eu facilisis massa, a hendrerit mauris. Nulla elementum molestie tincidunt. Donec mi justo, ornare vel tempor id, gravida et orci. Nam molestie erat nec nisi bibendum accumsan. Duis vitae tempor ante. Morbi congue mauris vel sagittis facilisis. Vivamus vehicula odio orci, a tempor nibh euismod in. Proin mattis, nibh at feugiat porta, purus velit posuere felis, quis volutpat sapien dui vel odio. Nam fermentum sem nec euismod aliquet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. +Mauris congue lacus tortor. Pellentesque arcu eros, accumsan imperdiet porttitor vitae, mattis nec justo. Nullam ac aliquam mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse potenti. Fusce accumsan tempus felis a sagittis. Maecenas et velit odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam eros lacus, volutpat non urna sed, tincidunt ullamcorper elit. Sed sit amet gravida libero. In varius mi vel diam sollicitudin mollis. +Aenean varius, diam vitae hendrerit feugiat, libero augue ultrices odio, eget consequat sem tellus eu nisi. Nam dapibus enim et auctor sollicitudin. Nunc iaculis eros orci, ac accumsan eros malesuada ut. Ut semper augue felis, nec sodales lorem consectetur non. Cras gravida eleifend est, et sagittis eros imperdiet congue. Fusce id tellus dapibus nunc scelerisque tempus. Donec tempor placerat libero, in commodo nisi bibendum eu. Donec id eros non est sollicitudin luctus. Duis bibendum bibendum tellus nec viverra. Aliquam non faucibus ex, nec luctus dui. Curabitur efficitur varius urna non dignissim. Suspendisse elit elit, ultrices in elementum eu, tempor at magna. +Nunc in purus sit amet mi laoreet pulvinar placerat eget sapien. Donec vel felis at dui ultricies euismod quis vel neque. Donec tincidunt urna non eros pretium blandit. Nullam congue tincidunt condimentum. Curabitur et libero nibh. Proin ultricies risus id imperdiet scelerisque. Suspendisse purus mi, viverra vitae risus ut, tempus tincidunt enim. Ut luctus lobortis nisl, eget venenatis tortor cursus non. Mauris finibus nisl quis gravida ultricies. Fusce elementum lacus sit amet nunc congue, in porta nulla tincidunt. +Mauris ante risus, imperdiet blandit posuere in, blandit eu ipsum. Integer et auctor arcu. Integer quis elementum purus. Nunc in ultricies nisl, sed rutrum risus. Suspendisse venenatis eros nec lorem rhoncus, in scelerisque velit condimentum. Etiam condimentum quam felis, in elementum odio mattis et. In ut nibh porttitor, hendrerit tellus vel, luctus magna. Vestibulum et ligula et dolor pellentesque porta. Aenean efficitur porta massa et bibendum. Nulla vehicula sem in risus volutpat, a eleifend elit maximus. +Proin orci lorem, auctor a felis eu, pretium lobortis nulla. Phasellus aliquam efficitur interdum. Sed sit amet velit rutrum est dictum facilisis. Duis cursus enim at nisl tincidunt, eu molestie elit vehicula. Cras pellentesque nisl id enim feugiat fringilla. In quis tincidunt neque. Nam eu consectetur dolor. Ut id interdum mauris. Mauris nunc tortor, placerat in tempor ut, vestibulum eu nisl. Integer vel dapibus ipsum. Nunc id erat pulvinar, tincidunt magna id, condimentum massa. Pellentesque consequat est eget odio placerat vehicula. Etiam augue neque, sagittis non leo eu, tristique scelerisque dui. Ut dui urna, blandit quis urna ac, tincidunt tristique turpis. +Suspendisse venenatis rhoncus ligula ultrices condimentum. In id laoreet eros. Suspendisse suscipit fringilla leo id euismod. Sed in quam libero. Ut at ligula tellus. Sed tristique gravida dui, at egestas odio aliquam iaculis. Praesent imperdiet velit quis nibh consequat, quis pretium sem sagittis. Donec tortor ex, congue sit amet pulvinar ac, rutrum non est. Praesent ipsum magna, venenatis sit amet vulputate id, eleifend ac ipsum. +In consequat, nisi iaculis laoreet elementum, massa mauris varius nisi, et porta nisi velit at urna. Maecenas sit amet aliquet eros, a rhoncus nisl. Maecenas convallis enim nunc. Morbi purus nisl, aliquam ac tincidunt sed, mattis in augue. Quisque et elementum quam, vitae maximus orci. Suspendisse hendrerit risus nec vehicula placerat. Nulla et lectus nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. +Etiam ut sodales ex. Nulla luctus, magna eu scelerisque sagittis, nibh quam consectetur neque, non rutrum dolor metus nec ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed egestas augue elit, sollicitudin accumsan massa lobortis ac. Curabitur placerat, dolor a aliquam maximus, velit ipsum laoreet ligula, id ullamcorper lacus nibh eget nisl. Donec eget lacus venenatis enim consequat auctor vel in. +""" -@pytest.mark.integration -def test_integration_topic_message_submit_transaction_can_execute(): - """Test that a topic message submit transaction executes.""" - env = IntegrationTestEnv() +def create_topic(client): + """Helper transaction for creating a topic.""" + receipt = ( + TopicCreateTransaction() + .execute(client) + ) - try: - create_transaction = TopicCreateTransaction( - memo="Python SDK topic", admin_key=env.public_operator_key - ) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed: {ResponseCode(receipt.status).name}" + ) + return receipt.topic_id - create_transaction.freeze_with(env.client) - create_receipt = create_transaction.execute(env.client) - topic_id = create_receipt.topic_id - - message: List[Any] = [] +@pytest.mark.integration +def test_integration_can_s(env): + """Test that a topic message submit transaction executes.""" + topic_id = create_topic(env.client) + + messages: List[str] = [] - def get_message(m: TopicMessage): - print("message_reviver") - message.append(m) + def get_message(m: TopicMessage): + messages.append(m.contents.decode('utf-8')) - def on_error_handler(e): - print(f"Subscription error: {e}") + def on_error_handler(e): + raise RuntimeError(f"Subscription error: {e}") - query = TopicMessageQuery( + query = TopicMessageQuery( topic_id=topic_id, start_time=datetime.now(timezone.utc), limit=0, chunking_enabled=True - ) + ) - handle = query.subscribe( + handle = query.subscribe( env.client, - on_message=get_message, - on_error=on_error_handler - ) + on_message=get_message,on_error=on_error_handler + ) - message_transaction = TopicMessageSubmitTransaction( - topic_id=topic_id, message="Hello, Python SDK!" + message_receipt = ( + TopicMessageSubmitTransaction( + topic_id=topic_id, + message="Hello, Python SDK!" ) + .freeze_with(env.client) + .execute(env.client) + ) - message_transaction.freeze_with(env.client) - message_receipt = message_transaction.execute(env.client) - - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert ( + message_receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" - while len(message) == 0: - time.sleep(2) + start = datetime.now() - assert message == "Hello, Python SDK!" + while len(messages) == 0: + if datetime.now() - start > timedelta(seconds=60): + raise TimeoutError("TopicMessage was not received in time") + time.sleep(1) - finally: - env.close() + assert messages[0] == "Hello, Python SDK!" + handle.cancel() @pytest.mark.integration def test_integration_topic_message_submit_large_transaction_can_execute(): - """Test that a topic message submit transaction executes.""" - env = IntegrationTestEnv() - - try: - create_transaction = TopicCreateTransaction( - memo="Python SDK topic", admin_key=env.public_operator_key - ) - - create_transaction.freeze_with(env.client) - create_receipt = create_transaction.execute(env.client) - topic_id = create_receipt.topic_id - - - message: List[Any] = [] + """Test that a topic message submit transaction executes with large message.""" + topic_id = create_topic(env.client) + messages: List[str] = [] - def get_message(m: TopicMessage): - print("message_reviver") - message.append(m) + def get_message(m: TopicMessage): + messages.append(m.contents.decode('utf-8')) - def on_error_handler(e): - print(f"Subscription error: {e}") + def on_error_handler(e): + raise RuntimeError(f"Subscription error: {e}") - query = TopicMessageQuery( + query = TopicMessageQuery( topic_id=topic_id, start_time=datetime.now(timezone.utc), limit=0, chunking_enabled=True - ) + ) - handle = query.subscribe( + handle = query.subscribe( env.client, on_message=get_message, on_error=on_error_handler + ) + + message_receipt = ( + TopicMessageSubmitTransaction( + topic_id=topic_id, + message=BIG_CONTENT ) - content = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur aliquam augue sem, ut mattis dui laoreet a. Curabitur consequat est euismod, scelerisque metus et, tristique dui. Nulla commodo mauris ut faucibus ultricies. Quisque venenatis nisl nec augue tempus, at efficitur elit eleifend. Duis pharetra felis metus, sed dapibus urna vehicula id. Duis non venenatis turpis, sit amet ornare orci. Donec non interdum quam. Sed finibus nunc et risus finibus, non sagittis lorem cursus. Proin pellentesque tempor aliquam. Sed congue nisl in enim bibendum, condimentum vehicula nisi feugiat. - -Suspendisse non sodales arcu. Suspendisse sodales, lorem ac mollis blandit, ipsum neque porttitor nulla, et sodales arcu ante fermentum tellus. Integer sagittis dolor sed augue fringilla accumsan. Cras vitae finibus arcu, sit amet varius dolor. Etiam id finibus dolor, vitae luctus velit. Proin efficitur augue nec pharetra accumsan. Aliquam lobortis nisl diam, vel fermentum purus finibus id. Etiam at finibus orci, et tincidunt turpis. Aliquam imperdiet congue lacus vel facilisis. Phasellus id magna vitae enim dapibus vestibulum vitae quis augue. Morbi eu consequat enim. Maecenas neque nulla, pulvinar sit amet consequat sed, tempor sed magna. Mauris lacinia sem feugiat faucibus aliquet. Etiam congue non turpis at commodo. Nulla facilisi. - -Nunc velit turpis, cursus ornare fringilla eu, lacinia posuere leo. Mauris rutrum ultricies dui et suscipit. Curabitur in euismod ligula. Curabitur vitae faucibus orci. Phasellus volutpat vestibulum diam sit amet vestibulum. In vel purus leo. Nulla condimentum lectus vestibulum lectus faucibus, id lobortis eros consequat. Proin mollis libero elit, vel aliquet nisi imperdiet et. Morbi ornare est velit, in vehicula nunc malesuada quis. Donec vehicula convallis interdum. - -Integer pellentesque in nibh vitae aliquet. Ut at justo id libero dignissim hendrerit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent quis ornare lectus. Nam malesuada non diam quis cursus. Phasellus a libero ligula. Suspendisse ligula elit, congue et nisi sit amet, cursus euismod dolor. Morbi aliquam, nulla a posuere pellentesque, diam massa ornare risus, nec eleifend neque eros et elit. - -Pellentesque a sodales dui. Sed in efficitur ante. Duis eget volutpat elit, et ornare est. Nam felis dolor, placerat mattis diam id, maximus lobortis quam. Sed pellentesque lobortis sem sed placerat. Pellentesque augue odio, molestie sed lectus sit amet, congue volutpat massa. Quisque congue consequat nunc id fringilla. Duis semper nulla eget enim venenatis dapibus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque varius turpis nibh, sit amet malesuada mauris malesuada quis. Vivamus dictum egestas placerat. Vivamus id augue elit. - -Cras fermentum volutpat eros, vitae euismod lorem viverra nec. Donec lectus augue, porta eleifend odio sit amet, condimentum rhoncus urna. Nunc sed odio velit. Morbi non cursus odio, eget imperdiet erat. Nunc rhoncus massa non neque volutpat, sit amet faucibus ante congue. Phasellus nec lorem vel leo accumsan lobortis. Maecenas id ligula bibendum purus suscipit posuere ac eget diam. Nam in quam pretium, semper erat auctor, iaculis odio. Maecenas placerat, nisi ac elementum tempor, felis nulla porttitor orci, ac rhoncus diam justo in elit. Etiam lobortis fermentum ligula in tincidunt. Donec quis vestibulum nunc. Sed eros diam, interdum in porta lobortis, gravida eu magna. Donec diam purus, finibus et sollicitudin quis, fringilla nec nisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur ultricies sagittis dapibus. Etiam ullamcorper aliquet libero, eu venenatis mauris suscipit id. - -Ut interdum eleifend sem, vel bibendum ipsum volutpat eget. Nunc ac dignissim augue. Aliquam ornare aliquet magna id dignissim. Vestibulum velit sem, lacinia eu rutrum in, rhoncus vitae mauris. Pellentesque scelerisque pulvinar mauris non cursus. Integer id dolor porta, bibendum sem vel, pretium tortor. Fusce a nisi convallis, interdum quam condimentum, suscipit dolor. Sed magna diam, efficitur non nunc in, tincidunt varius mi. Aliquam ullamcorper nulla eu fermentum bibendum. Vivamus a felis pretium, hendrerit enim vitae, hendrerit leo. Suspendisse lacinia lectus a diam consectetur suscipit. Aenean hendrerit nisl sed diam venenatis pellentesque. Nullam egestas lectus a consequat pharetra. Vivamus ornare tellus auctor, facilisis lacus id, feugiat dui. Nam id est ut est rhoncus varius. - -Aenean vel vehicula erat. Aenean gravida risus vitae purus sodales, quis dictum enim porta. Ut elit elit, fermentum sed posuere non, accumsan eu justo. Integer porta malesuada quam, et elementum massa suscipit nec. Donec in elit diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis suscipit vel ante volutpat vestibulum. - -Pellentesque ex arcu, euismod et sapien ut, vulputate suscipit enim. Donec mattis sagittis augue, et mattis lacus. Cras placerat consequat lorem sed luctus. Nam suscipit aliquam sem ac imperdiet. Mauris a semper augue, pulvinar fringilla magna. Integer pretium massa non risus commodo hendrerit. Sed dictum libero id erat sodales mattis. Etiam auctor dolor lectus, ut sagittis enim lobortis vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur nec orci lobortis, cursus risus eget, sollicitudin massa. Integer vel tincidunt mi, id eleifend quam. Nullam facilisis nisl eu mauris congue, vitae euismod nisi malesuada. Vivamus sit amet urna et libero sagittis dictum. - -In hac habitasse platea dictumst. Aliquam erat volutpat. Ut dictum, mi a viverra venenatis, mi urna pulvinar nisi, nec gravida lectus diam eget urna. Ut dictum sit amet nisl ut dignissim. Sed sed mauris scelerisque, efficitur augue in, vulputate turpis. Proin dolor justo, bibendum et sollicitudin feugiat, imperdiet sed mi. Sed elementum vitae massa vel lobortis. Cras vitae massa sit amet libero dictum tempus. - -Vivamus ut mauris lectus. Curabitur placerat ornare scelerisque. Cras malesuada nunc quis tortor pretium bibendum vel sed dui. Cras lobortis nibh eu erat blandit, sit amet consequat neque fermentum. Phasellus imperdiet molestie tristique. Cras auctor purus erat, id mollis ligula porttitor eget. Mauris porta pharetra odio et finibus. Suspendisse eu est a ligula bibendum cursus. Mauris ac laoreet libero. Nullam volutpat sem quis rhoncus gravida. - -Donec malesuada lacus ac iaculis cursus. Sed sem orci, feugiat ac est ut, ultricies posuere nisi. Suspendisse potenti. Phasellus ut ultricies purus. Etiam sem tortor, fermentum quis aliquam eget, consequat ut nulla. Aliquam dictum metus in mi fringilla, vel gravida nulla accumsan. Cras aliquam eget leo vel posuere. Vivamus vitae malesuada nunc. Morbi placerat magna mi, id suscipit lacus auctor quis. Nam at lorem vel odio finibus fringilla ut ac velit. Donec laoreet at nibh quis vehicula. - -Fusce ac tristique nisi. Donec leo nisi, consectetur at tellus sit amet, consectetur ultrices dui. Quisque gravida mollis tempor. Maecenas semper, sapien ut dignissim feugiat, massa enim viverra dolor, non varius eros nulla nec felis. Nunc massa lacus, ornare et feugiat id, bibendum quis purus. Praesent viverra elit sit amet purus consectetur venenatis. Maecenas nibh risus, elementum sit amet enim sagittis, ultrices malesuada lectus. Vivamus non felis ante. Ut vulputate ex arcu. Aliquam porta gravida porta. Aliquam eros leo, malesuada quis eros non, maximus tristique neque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ligula orci, mollis vel luctus nec, venenatis vitae est. Fusce rutrum convallis nisi. - -Nunc laoreet eget nibh in feugiat. Pellentesque nec arcu cursus, gravida dolor a, pellentesque nisi. Praesent vel justo blandit, placerat risus eget, consectetur orci. Sed maximus metus mi, ut euismod augue ultricies in. Nunc id risus hendrerit, aliquet lorem nec, congue justo. Vestibulum vel nunc ac est euismod mattis ac vitae nulla. Donec blandit luctus mauris, sit amet bibendum dui egestas et. Aenean nec lorem nec elit ornare rutrum nec eget ligula. Fusce a ipsum vitae neque elementum pharetra. Pellentesque ullamcorper ullamcorper libero, vitae porta sem sagittis vel. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Duis at massa sit amet risus pellentesque varius sit amet vitae eros. Cras tempor aliquet sapien, vehicula varius neque volutpat et. Donec purus nibh, pellentesque ut lobortis nec, ultricies ultricies nisl. Sed accumsan ut dui sit amet vulputate. Suspendisse eu facilisis massa, a hendrerit mauris. Nulla elementum molestie tincidunt. Donec mi justo, ornare vel tempor id, gravida et orci. Nam molestie erat nec nisi bibendum accumsan. Duis vitae tempor ante. Morbi congue mauris vel sagittis facilisis. Vivamus vehicula odio orci, a tempor nibh euismod in. Proin mattis, nibh at feugiat porta, purus velit posuere felis, quis volutpat sapien dui vel odio. Nam fermentum sem nec euismod aliquet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. - -Mauris congue lacus tortor. Pellentesque arcu eros, accumsan imperdiet porttitor vitae, mattis nec justo. Nullam ac aliquam mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse potenti. Fusce accumsan tempus felis a sagittis. Maecenas et velit odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam eros lacus, volutpat non urna sed, tincidunt ullamcorper elit. Sed sit amet gravida libero. In varius mi vel diam sollicitudin mollis. - -Aenean varius, diam vitae hendrerit feugiat, libero augue ultrices odio, eget consequat sem tellus eu nisi. Nam dapibus enim et auctor sollicitudin. Nunc iaculis eros orci, ac accumsan eros malesuada ut. Ut semper augue felis, nec sodales lorem consectetur non. Cras gravida eleifend est, et sagittis eros imperdiet congue. Fusce id tellus dapibus nunc scelerisque tempus. Donec tempor placerat libero, in commodo nisi bibendum eu. Donec id eros non est sollicitudin luctus. Duis bibendum bibendum tellus nec viverra. Aliquam non faucibus ex, nec luctus dui. Curabitur efficitur varius urna non dignissim. Suspendisse elit elit, ultrices in elementum eu, tempor at magna. - -Nunc in purus sit amet mi laoreet pulvinar placerat eget sapien. Donec vel felis at dui ultricies euismod quis vel neque. Donec tincidunt urna non eros pretium blandit. Nullam congue tincidunt condimentum. Curabitur et libero nibh. Proin ultricies risus id imperdiet scelerisque. Suspendisse purus mi, viverra vitae risus ut, tempus tincidunt enim. Ut luctus lobortis nisl, eget venenatis tortor cursus non. Mauris finibus nisl quis gravida ultricies. Fusce elementum lacus sit amet nunc congue, in porta nulla tincidunt. - -Mauris ante risus, imperdiet blandit posuere in, blandit eu ipsum. Integer et auctor arcu. Integer quis elementum purus. Nunc in ultricies nisl, sed rutrum risus. Suspendisse venenatis eros nec lorem rhoncus, in scelerisque velit condimentum. Etiam condimentum quam felis, in elementum odio mattis et. In ut nibh porttitor, hendrerit tellus vel, luctus magna. Vestibulum et ligula et dolor pellentesque porta. Aenean efficitur porta massa et bibendum. Nulla vehicula sem in risus volutpat, a eleifend elit maximus. - -Proin orci lorem, auctor a felis eu, pretium lobortis nulla. Phasellus aliquam efficitur interdum. Sed sit amet velit rutrum est dictum facilisis. Duis cursus enim at nisl tincidunt, eu molestie elit vehicula. Cras pellentesque nisl id enim feugiat fringilla. In quis tincidunt neque. Nam eu consectetur dolor. Ut id interdum mauris. Mauris nunc tortor, placerat in tempor ut, vestibulum eu nisl. Integer vel dapibus ipsum. Nunc id erat pulvinar, tincidunt magna id, condimentum massa. Pellentesque consequat est eget odio placerat vehicula. Etiam augue neque, sagittis non leo eu, tristique scelerisque dui. Ut dui urna, blandit quis urna ac, tincidunt tristique turpis. - -Suspendisse venenatis rhoncus ligula ultrices condimentum. In id laoreet eros. Suspendisse suscipit fringilla leo id euismod. Sed in quam libero. Ut at ligula tellus. Sed tristique gravida dui, at egestas odio aliquam iaculis. Praesent imperdiet velit quis nibh consequat, quis pretium sem sagittis. Donec tortor ex, congue sit amet pulvinar ac, rutrum non est. Praesent ipsum magna, venenatis sit amet vulputate id, eleifend ac ipsum. - -In consequat, nisi iaculis laoreet elementum, massa mauris varius nisi, et porta nisi velit at urna. Maecenas sit amet aliquet eros, a rhoncus nisl. Maecenas convallis enim nunc. Morbi purus nisl, aliquam ac tincidunt sed, mattis in augue. Quisque et elementum quam, vitae maximus orci. Suspendisse hendrerit risus nec vehicula placerat. Nulla et lectus nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Etiam ut sodales ex. Nulla luctus, magna eu scelerisque sagittis, nibh quam consectetur neque, non rutrum dolor metus nec ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed egestas augue elit, sollicitudin accumsan massa lobortis ac. Curabitur placerat, dolor a aliquam maximus, velit ipsum laoreet ligula, id ullamcorper lacus nibh eget nisl. Donec eget lacus venenatis enim consequat auctor vel in. -""" - - message_transaction = TopicMessageSubmitTransaction( - topic_id=topic_id, message=content - ) - - message_transaction.freeze_with(env.client) - message_receipt = message_transaction.execute(env.client) + .freeze_with(env.client) + .execute(env.client) + ) - assert ( - message_receipt.status == ResponseCode.SUCCESS - ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + assert ( + message_receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" - while len(message) == 0: - time.sleep(2) - - assert message[0] == content - finally: - env.close() + + start = datetime.now() + + while len(messages) == 0: + if datetime.now() - start > timedelta(seconds=60): + raise TimeoutError("TopicMessage was not received in time") + time.sleep(1) + + assert messages[0] == BIG_CONTENT + handle.cancel() diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index cb2579252..6b30c556a 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -9,6 +9,7 @@ from hiero_sdk_python.consensus.topic_message_submit_transaction import ( TopicMessageSubmitTransaction, ) +from hiero_sdk_python.crypto.private_key import PrivateKey from hiero_sdk_python.hbar import Hbar from hiero_sdk_python.query.account_balance_query import CryptoGetAccountBalanceQuery from hiero_sdk_python.query.topic_info_query import TopicInfoQuery @@ -17,23 +18,51 @@ from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit from tests.integration.utils_for_test import env -def get_topic_info(client, topic_id): - return TopicInfoQuery().set_topic_id(topic_id).execute(client) +def create_topic(client, admin_key=None, submit_key=None, custom_fees=None): + """Helper transaction for creating a topic.""" + tx = TopicCreateTransaction(memo="Python SDK topic") + + if admin_key: + tx.set_admin_key(admin_key) + if submit_key: + tx.set_submit_key(submit_key) + if custom_fees: + tx.set_custom_fees(custom_fees) + + receipt = tx.execute(client) + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic creation failed: {ResponseCode(receipt.status).name}" + ) + return receipt.topic_id + + +def delete_topic(client, topic_id): + """Helper transaction to delete a topic.""" + receipt = ( + TopicDeleteTransaction(topic_id=topic_id) + .execute(client) + ) + + assert receipt.status == ResponseCode.SUCCESS, ( + f"Topic deletion failed with status: {ResponseCode(receipt.status).name}" + ) @pytest.mark.integration def test_integration_topic_message_submit_transaction_can_execute(env): """Test that a topic message submit transaction executes.""" - create_transaction = TopicCreateTransaction( - memo="Python SDK topic", admin_key=env.public_operator_key + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key ) - create_transaction.freeze_with(env.client) - create_receipt = create_transaction.execute(env.client) - topic_id = create_receipt.topic_id + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + # Check that no message is submited + assert info.sequence_number == 0 message_transaction = TopicMessageSubmitTransaction( - topic_id=topic_id, message="Hello, Python SDK!" + topic_id=topic_id, + message="Hello, Python SDK!" ) message_transaction.freeze_with(env.client) @@ -43,36 +72,128 @@ def test_integration_topic_message_submit_transaction_can_execute(env): message_receipt.status == ResponseCode.SUCCESS ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" - delete_transaction = TopicDeleteTransaction(topic_id=topic_id) - delete_transaction.freeze_with(env.client) - delete_receipt = delete_transaction.execute(env.client) + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + # Check that one message is submited + assert info.sequence_number == 1 + + delete_topic(env.client, topic_id) + + +@pytest.mark.integration +def test_topic_message_submit_transation_can_submit_a_large_message(env): + """Test topic message submit transaction can submit large message.""" + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key + ) + + info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) + assert info.sequence_number == 0 + + message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks + + message_tx = ( + TopicMessageSubmitTransaction() + .set_topic_id(topic_id) + .set_message(message) + .freeze_with(env.client) + ) + + message_receipt = message_tx.execute(env.client) + + assert message_receipt.status == ResponseCode.SUCCESS + + info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) + assert info.sequence_number == 14 + + delete_topic(env.client, topic_id) + + +@pytest.mark.integration +def test_integration_topic_message_submit_transaction_with_submit_key(env): + """Test that a topic message submit transaction executes with submit key.""" + submit_key = PrivateKey.generate() + + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key, + submit_key=submit_key + ) + + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + # Check that no message is submited + assert info.sequence_number == 0 + + message_transaction = TopicMessageSubmitTransaction( + topic_id=topic_id, + message="Hello, Python SDK!" + ) + + message_transaction.freeze_with(env.client) + # Sign with submit key + message_transaction.sign(submit_key) + message_receipt = message_transaction.execute(env.client) assert ( - delete_receipt.status == ResponseCode.SUCCESS - ), f"Topic deletion failed with status: {ResponseCode(delete_receipt.status).name}" + message_receipt.status == ResponseCode.SUCCESS + ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" + + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + # Check that one message is submited + assert info.sequence_number == 1 + + delete_topic(env.client, topic_id) + + +@pytest.mark.integration +def test_integration_topic_message_submit_transaction_without_submit_key_fails(env): + """Test that a topic message fails submitting transaction without submit key.""" + submit_key = PrivateKey.generate() + + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key, + submit_key=submit_key + ) + + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + # Check that no message is submited + assert info.sequence_number == 0 + + message_transaction = TopicMessageSubmitTransaction( + topic_id=topic_id, + message="Hello, Python SDK!" + ) + + message_transaction.freeze_with(env.client) + message_receipt = message_transaction.execute(env.client) + + assert ( + message_receipt.status == ResponseCode.INVALID_SIGNATURE + ), f"Message submission must fail with status: {ResponseCode.INVALID_SIGNATURE}" + + delete_topic(env.client, topic_id) @pytest.mark.integration def test_integration_topic_message_submit_transaction_can_execute_with_custom_fee_limit(env): """Test that a topic message submit transaction executes with a custom fee limit.""" - + operator_id, operator_key = env.operator_id, env.operator_key + account = env.create_account(3) # Create an account with 3 Hbar balance topic_fee = ( CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) ) - receipt = ( - TopicCreateTransaction() - .set_memo("Python SDK topic") - .set_custom_fees([topic_fee]) - .execute(env.client) + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key, + custom_fees=[topic_fee] ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - topic_id = receipt.topic_id + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + assert info.sequence_number == 0 balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) assert ( @@ -99,33 +220,37 @@ def test_integration_topic_message_submit_transaction_can_execute_with_custom_fe receipt.status == ResponseCode.SUCCESS ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + assert info.sequence_number == 1 + balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) assert ( balance.hbars.to_tinybars() < Hbar(2).to_tinybars() ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" - + + env.client.set_operator(operator_id, operator_key) + delete_topic(env.client, topic_id) @pytest.mark.integration def test_integration_scheduled_topic_message_submit_transaction_can_execute_with_custom_fee_limit(env): """Test that a scheduled topic message submit transaction executes with a custom fee limit.""" + operator_id, operator_key = env.operator_id, env.operator_key + account = env.create_account(3) # Create an account with 3 Hbar balance topic_fee = ( CustomFixedFee().set_hbar_amount(Hbar(1)).set_fee_collector_account_id(env.operator_id) ) - receipt = ( - TopicCreateTransaction() - .set_memo("Python SDK topic") - .set_custom_fees([topic_fee]) - .execute(env.client) + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key, + custom_fees=[topic_fee] ) - assert ( - receipt.status == ResponseCode.SUCCESS - ), f"Topic creation failed with status: {ResponseCode(receipt.status).name}" - topic_id = receipt.topic_id + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + assert info.sequence_number == 0 balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) assert ( @@ -152,45 +277,13 @@ def test_integration_scheduled_topic_message_submit_transaction_can_execute_with receipt.status == ResponseCode.SUCCESS ), f"Message submission failed with status: {ResponseCode(receipt.status).name}" + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + assert info.sequence_number == 1 + balance = CryptoGetAccountBalanceQuery().set_account_id(account.id).execute(env.client) assert ( balance.hbars.to_tinybars() < Hbar(2).to_tinybars() ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" - -def test_topic_message_submit_transation_can_submit_a_large_message(env): - """Test topic message submit transaction can submit large message.""" - topic_tx = ( - TopicCreateTransaction( - memo="Python SDK topic", - admin_key=env.operator_key - ) - .freeze_with(env.client) - ) - - topic_receipt = topic_tx.execute(env.client) - - assert topic_receipt.status == ResponseCode.SUCCESS - - topic_id = topic_receipt.topic_id - - info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) - - # Check no message has been submited - assert info.sequence_number == 0 - - message = "A" * (1024 * 14) - - message_tx = ( - TopicMessageSubmitTransaction() - .set_topic_id(topic_id) - .set_message(message) - .freeze_with(env.client) - ) - - message_receipt = message_tx.execute(env.client) - - assert message_receipt.status == ResponseCode.SUCCESS - - info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) - assert info.sequence_number == 14 \ No newline at end of file + env.client.set_operator(operator_id, operator_key) # Set the operator to the account + delete_topic(env.client, topic_id) From b3385cfa046abe096b525a56ff45d083284d09ca Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Mon, 24 Nov 2025 19:38:29 +0530 Subject: [PATCH 05/14] chore: updated examples and tests Signed-off-by: Manish Dait --- examples/consensus/topic_message.py | 21 +- .../consensus/topic_message_submit_chunked.py | 185 ++++++++++-------- .../topic_message_submit_transaction.py | 47 ++--- .../topic_message_qurey_e2e_test.py | 55 +++++- ...pic_message_submit_transaction_e2e_test.py | 27 +++ .../test_topic_message_submit_transaction.py | 42 +++- 6 files changed, 256 insertions(+), 121 deletions(-) diff --git a/examples/consensus/topic_message.py b/examples/consensus/topic_message.py index 6be51abf4..722ca143d 100644 --- a/examples/consensus/topic_message.py +++ b/examples/consensus/topic_message.py @@ -20,14 +20,21 @@ def __init__(self, seconds: int, nanos: int = 0): self.seconds = seconds self.nanos = nanos +class MockAccountID: + """Mocks the protobuf AccountID object.""" + def __init__(self, shard, realm, num): + self.shardNum = shard + self.realmNum = realm + self.accountNum = num + self.alias = None + class MockTransactionID: """Mocks the protobuf TransactionID object.""" def __init__(self, account_id, seconds, nanos): - self.shardNum = account_id.shard - self.realmNum = account_id.realm - self.accountNum = account_id.num + self.accountID = account_id self.transactionValidStart = MockTimestamp(seconds, nanos) + self.scheduled = False class MockChunkInfo: """Mocks the protobuf ChunkInfo object.""" @@ -72,13 +79,7 @@ def mock_consensus_response( chunk_info = None if is_chunked: - - class MockAcct: - shard = 0 - realm = 0 - num = 10 - - tx_id = MockTransactionID(MockAcct(), 1736539100, 1) if has_tx_id else None + tx_id = MockTransactionID(MockAccountID(0,0,10), 1736539100, 1) if has_tx_id else None chunk_info = MockChunkInfo(seq, total_chunks, tx_id) return MockResponse(message, seq, timestamp, chunk_info) diff --git a/examples/consensus/topic_message_submit_chunked.py b/examples/consensus/topic_message_submit_chunked.py index dc3a4c2f3..857b7eedb 100644 --- a/examples/consensus/topic_message_submit_chunked.py +++ b/examples/consensus/topic_message_submit_chunked.py @@ -14,110 +14,139 @@ Network, TopicMessageSubmitTransaction, TopicCreateTransaction, - ResponseCode + ResponseCode, + TopicInfoQuery ) -from hiero_sdk_python.consensus.topic_id import TopicId -from hiero_sdk_python.query.topic_info_query import TopicInfoQuery -from hiero_sdk_python.query.topic_message_query import TopicMessageQuery - -load_dotenv() -network_name = os.getenv('NETWORK', 'testnet').lower() -def setup_client(): - """Initialize and set up the client with operator account""" - network = Network(network_name) - print(f"Connecting to Hedera {network_name} network!") - client = Client(network) - - - try: - operator_id = AccountId.from_string(os.getenv('OPERATOR_ID', '')) - operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY', '')) - client.set_operator(operator_id, operator_key) - print(f"Client set up with operator id {client.operator_account_id}") - - return client, operator_id, operator_key - except (TypeError, ValueError): - print("❌ Error: Creating client, Please check your .env file") - sys.exit(1) - - -def submit_topic_message_transaction(client, topic_id, message, operator_key): - """Submit a message to the specified topic""" - print("\nSTEP 2: Submitting message...") - transaction = ( - TopicMessageSubmitTransaction(topic_id=topic_id, message=message) - .freeze_with(client) - .sign(operator_key) - ) - - try: - receipt = transaction.execute(client) - print(f"Message Submit Transaction completed: " - f"(status: {ResponseCode(receipt.status).name}, " - f"transaction_id: {receipt.transaction_id})") - print(f"✅ Success! Message submitted to topic {topic_id}: {message}") - except Exception as e: - print(f"❌ Error: Message submission failed: {str(e)}") - sys.exit(1) - - -def main(): - """ - A example to create a topic and then submit a message to it. - """ - client, _, key = setup_client() - message = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur aliquam augue sem, ut mattis dui laoreet a. Curabitur consequat est euismod, scelerisque metus et, tristique dui. Nulla commodo mauris ut faucibus ultricies. Quisque venenatis nisl nec augue tempus, at efficitur elit eleifend. Duis pharetra felis metus, sed dapibus urna vehicula id. Duis non venenatis turpis, sit amet ornare orci. Donec non interdum quam. Sed finibus nunc et risus finibus, non sagittis lorem cursus. Proin pellentesque tempor aliquam. Sed congue nisl in enim bibendum, condimentum vehicula nisi feugiat. +BIG_CONTENT = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur aliquam augue sem, ut mattis dui laoreet a. Curabitur consequat est euismod, scelerisque metus et, tristique dui. Nulla commodo mauris ut faucibus ultricies. Quisque venenatis nisl nec augue tempus, at efficitur elit eleifend. Duis pharetra felis metus, sed dapibus urna vehicula id. Duis non venenatis turpis, sit amet ornare orci. Donec non interdum quam. Sed finibus nunc et risus finibus, non sagittis lorem cursus. Proin pellentesque tempor aliquam. Sed congue nisl in enim bibendum, condimentum vehicula nisi feugiat. Suspendisse non sodales arcu. Suspendisse sodales, lorem ac mollis blandit, ipsum neque porttitor nulla, et sodales arcu ante fermentum tellus. Integer sagittis dolor sed augue fringilla accumsan. Cras vitae finibus arcu, sit amet varius dolor. Etiam id finibus dolor, vitae luctus velit. Proin efficitur augue nec pharetra accumsan. Aliquam lobortis nisl diam, vel fermentum purus finibus id. Etiam at finibus orci, et tincidunt turpis. Aliquam imperdiet congue lacus vel facilisis. Phasellus id magna vitae enim dapibus vestibulum vitae quis augue. Morbi eu consequat enim. Maecenas neque nulla, pulvinar sit amet consequat sed, tempor sed magna. Mauris lacinia sem feugiat faucibus aliquet. Etiam congue non turpis at commodo. Nulla facilisi. - Nunc velit turpis, cursus ornare fringilla eu, lacinia posuere leo. Mauris rutrum ultricies dui et suscipit. Curabitur in euismod ligula. Curabitur vitae faucibus orci. Phasellus volutpat vestibulum diam sit amet vestibulum. In vel purus leo. Nulla condimentum lectus vestibulum lectus faucibus, id lobortis eros consequat. Proin mollis libero elit, vel aliquet nisi imperdiet et. Morbi ornare est velit, in vehicula nunc malesuada quis. Donec vehicula convallis interdum. - Integer pellentesque in nibh vitae aliquet. Ut at justo id libero dignissim hendrerit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent quis ornare lectus. Nam malesuada non diam quis cursus. Phasellus a libero ligula. Suspendisse ligula elit, congue et nisi sit amet, cursus euismod dolor. Morbi aliquam, nulla a posuere pellentesque, diam massa ornare risus, nec eleifend neque eros et elit. - Pellentesque a sodales dui. Sed in efficitur ante. Duis eget volutpat elit, et ornare est. Nam felis dolor, placerat mattis diam id, maximus lobortis quam. Sed pellentesque lobortis sem sed placerat. Pellentesque augue odio, molestie sed lectus sit amet, congue volutpat massa. Quisque congue consequat nunc id fringilla. Duis semper nulla eget enim venenatis dapibus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque varius turpis nibh, sit amet malesuada mauris malesuada quis. Vivamus dictum egestas placerat. Vivamus id augue elit. - Cras fermentum volutpat eros, vitae euismod lorem viverra nec. Donec lectus augue, porta eleifend odio sit amet, condimentum rhoncus urna. Nunc sed odio velit. Morbi non cursus odio, eget imperdiet erat. Nunc rhoncus massa non neque volutpat, sit amet faucibus ante congue. Phasellus nec lorem vel leo accumsan lobortis. Maecenas id ligula bibendum purus suscipit posuere ac eget diam. Nam in quam pretium, semper erat auctor, iaculis odio. Maecenas placerat, nisi ac elementum tempor, felis nulla porttitor orci, ac rhoncus diam justo in elit. Etiam lobortis fermentum ligula in tincidunt. Donec quis vestibulum nunc. Sed eros diam, interdum in porta lobortis, gravida eu magna. Donec diam purus, finibus et sollicitudin quis, fringilla nec nisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur ultricies sagittis dapibus. Etiam ullamcorper aliquet libero, eu venenatis mauris suscipit id. - Ut interdum eleifend sem, vel bibendum ipsum volutpat eget. Nunc ac dignissim augue. Aliquam ornare aliquet magna id dignissim. Vestibulum velit sem, lacinia eu rutrum in, rhoncus vitae mauris. Pellentesque scelerisque pulvinar mauris non cursus. Integer id dolor porta, bibendum sem vel, pretium tortor. Fusce a nisi convallis, interdum quam condimentum, suscipit dolor. Sed magna diam, efficitur non nunc in, tincidunt varius mi. Aliquam ullamcorper nulla eu fermentum bibendum. Vivamus a felis pretium, hendrerit enim vitae, hendrerit leo. Suspendisse lacinia lectus a diam consectetur suscipit. Aenean hendrerit nisl sed diam venenatis pellentesque. Nullam egestas lectus a consequat pharetra. Vivamus ornare tellus auctor, facilisis lacus id, feugiat dui. Nam id est ut est rhoncus varius. - Aenean vel vehicula erat. Aenean gravida risus vitae purus sodales, quis dictum enim porta. Ut elit elit, fermentum sed posuere non, accumsan eu justo. Integer porta malesuada quam, et elementum massa suscipit nec. Donec in elit diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis suscipit vel ante volutpat vestibulum. - Pellentesque ex arcu, euismod et sapien ut, vulputate suscipit enim. Donec mattis sagittis augue, et mattis lacus. Cras placerat consequat lorem sed luctus. Nam suscipit aliquam sem ac imperdiet. Mauris a semper augue, pulvinar fringilla magna. Integer pretium massa non risus commodo hendrerit. Sed dictum libero id erat sodales mattis. Etiam auctor dolor lectus, ut sagittis enim lobortis vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur nec orci lobortis, cursus risus eget, sollicitudin massa. Integer vel tincidunt mi, id eleifend quam. Nullam facilisis nisl eu mauris congue, vitae euismod nisi malesuada. Vivamus sit amet urna et libero sagittis dictum. - In hac habitasse platea dictumst. Aliquam erat volutpat. Ut dictum, mi a viverra venenatis, mi urna pulvinar nisi, nec gravida lectus diam eget urna. Ut dictum sit amet nisl ut dignissim. Sed sed mauris scelerisque, efficitur augue in, vulputate turpis. Proin dolor justo, bibendum et sollicitudin feugiat, imperdiet sed mi. Sed elementum vitae massa vel lobortis. Cras vitae massa sit amet libero dictum tempus. - Vivamus ut mauris lectus. Curabitur placerat ornare scelerisque. Cras malesuada nunc quis tortor pretium bibendum vel sed dui. Cras lobortis nibh eu erat blandit, sit amet consequat neque fermentum. Phasellus imperdiet molestie tristique. Cras auctor purus erat, id mollis ligula porttitor eget. Mauris porta pharetra odio et finibus. Suspendisse eu est a ligula bibendum cursus. Mauris ac laoreet libero. Nullam volutpat sem quis rhoncus gravida. - Donec malesuada lacus ac iaculis cursus. Sed sem orci, feugiat ac est ut, ultricies posuere nisi. Suspendisse potenti. Phasellus ut ultricies purus. Etiam sem tortor, fermentum quis aliquam eget, consequat ut nulla. Aliquam dictum metus in mi fringilla, vel gravida nulla accumsan. Cras aliquam eget leo vel posuere. Vivamus vitae malesuada nunc. Morbi placerat magna mi, id suscipit lacus auctor quis. Nam at lorem vel odio finibus fringilla ut ac velit. Donec laoreet at nibh quis vehicula. - Fusce ac tristique nisi. Donec leo nisi, consectetur at tellus sit amet, consectetur ultrices dui. Quisque gravida mollis tempor. Maecenas semper, sapien ut dignissim feugiat, massa enim viverra dolor, non varius eros nulla nec felis. Nunc massa lacus, ornare et feugiat id, bibendum quis purus. Praesent viverra elit sit amet purus consectetur venenatis. Maecenas nibh risus, elementum sit amet enim sagittis, ultrices malesuada lectus. Vivamus non felis ante. Ut vulputate ex arcu. Aliquam porta gravida porta. Aliquam eros leo, malesuada quis eros non, maximus tristique neque. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam ligula orci, mollis vel luctus nec, venenatis vitae est. Fusce rutrum convallis nisi. - Nunc laoreet eget nibh in feugiat. Pellentesque nec arcu cursus, gravida dolor a, pellentesque nisi. Praesent vel justo blandit, placerat risus eget, consectetur orci. Sed maximus metus mi, ut euismod augue ultricies in. Nunc id risus hendrerit, aliquet lorem nec, congue justo. Vestibulum vel nunc ac est euismod mattis ac vitae nulla. Donec blandit luctus mauris, sit amet bibendum dui egestas et. Aenean nec lorem nec elit ornare rutrum nec eget ligula. Fusce a ipsum vitae neque elementum pharetra. Pellentesque ullamcorper ullamcorper libero, vitae porta sem sagittis vel. Interdum et malesuada fames ac ante ipsum primis in faucibus. - Duis at massa sit amet risus pellentesque varius sit amet vitae eros. Cras tempor aliquet sapien, vehicula varius neque volutpat et. Donec purus nibh, pellentesque ut lobortis nec, ultricies ultricies nisl. Sed accumsan ut dui sit amet vulputate. Suspendisse eu facilisis massa, a hendrerit mauris. Nulla elementum molestie tincidunt. Donec mi justo, ornare vel tempor id, gravida et orci. Nam molestie erat nec nisi bibendum accumsan. Duis vitae tempor ante. Morbi congue mauris vel sagittis facilisis. Vivamus vehicula odio orci, a tempor nibh euismod in. Proin mattis, nibh at feugiat porta, purus velit posuere felis, quis volutpat sapien dui vel odio. Nam fermentum sem nec euismod aliquet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam erat volutpat. - Mauris congue lacus tortor. Pellentesque arcu eros, accumsan imperdiet porttitor vitae, mattis nec justo. Nullam ac aliquam mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse potenti. Fusce accumsan tempus felis a sagittis. Maecenas et velit odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam eros lacus, volutpat non urna sed, tincidunt ullamcorper elit. Sed sit amet gravida libero. In varius mi vel diam sollicitudin mollis. - Aenean varius, diam vitae hendrerit feugiat, libero augue ultrices odio, eget consequat sem tellus eu nisi. Nam dapibus enim et auctor sollicitudin. Nunc iaculis eros orci, ac accumsan eros malesuada ut. Ut semper augue felis, nec sodales lorem consectetur non. Cras gravida eleifend est, et sagittis eros imperdiet congue. Fusce id tellus dapibus nunc scelerisque tempus. Donec tempor placerat libero, in commodo nisi bibendum eu. Donec id eros non est sollicitudin luctus. Duis bibendum bibendum tellus nec viverra. Aliquam non faucibus ex, nec luctus dui. Curabitur efficitur varius urna non dignissim. Suspendisse elit elit, ultrices in elementum eu, tempor at magna. - Nunc in purus sit amet mi laoreet pulvinar placerat eget sapien. Donec vel felis at dui ultricies euismod quis vel neque. Donec tincidunt urna non eros pretium blandit. Nullam congue tincidunt condimentum. Curabitur et libero nibh. Proin ultricies risus id imperdiet scelerisque. Suspendisse purus mi, viverra vitae risus ut, tempus tincidunt enim. Ut luctus lobortis nisl, eget venenatis tortor cursus non. Mauris finibus nisl quis gravida ultricies. Fusce elementum lacus sit amet nunc congue, in porta nulla tincidunt. - Mauris ante risus, imperdiet blandit posuere in, blandit eu ipsum. Integer et auctor arcu. Integer quis elementum purus. Nunc in ultricies nisl, sed rutrum risus. Suspendisse venenatis eros nec lorem rhoncus, in scelerisque velit condimentum. Etiam condimentum quam felis, in elementum odio mattis et. In ut nibh porttitor, hendrerit tellus vel, luctus magna. Vestibulum et ligula et dolor pellentesque porta. Aenean efficitur porta massa et bibendum. Nulla vehicula sem in risus volutpat, a eleifend elit maximus. - Proin orci lorem, auctor a felis eu, pretium lobortis nulla. Phasellus aliquam efficitur interdum. Sed sit amet velit rutrum est dictum facilisis. Duis cursus enim at nisl tincidunt, eu molestie elit vehicula. Cras pellentesque nisl id enim feugiat fringilla. In quis tincidunt neque. Nam eu consectetur dolor. Ut id interdum mauris. Mauris nunc tortor, placerat in tempor ut, vestibulum eu nisl. Integer vel dapibus ipsum. Nunc id erat pulvinar, tincidunt magna id, condimentum massa. Pellentesque consequat est eget odio placerat vehicula. Etiam augue neque, sagittis non leo eu, tristique scelerisque dui. Ut dui urna, blandit quis urna ac, tincidunt tristique turpis. - Suspendisse venenatis rhoncus ligula ultrices condimentum. In id laoreet eros. Suspendisse suscipit fringilla leo id euismod. Sed in quam libero. Ut at ligula tellus. Sed tristique gravida dui, at egestas odio aliquam iaculis. Praesent imperdiet velit quis nibh consequat, quis pretium sem sagittis. Donec tortor ex, congue sit amet pulvinar ac, rutrum non est. Praesent ipsum magna, venenatis sit amet vulputate id, eleifend ac ipsum. - In consequat, nisi iaculis laoreet elementum, massa mauris varius nisi, et porta nisi velit at urna. Maecenas sit amet aliquet eros, a rhoncus nisl. Maecenas convallis enim nunc. Morbi purus nisl, aliquam ac tincidunt sed, mattis in augue. Quisque et elementum quam, vitae maximus orci. Suspendisse hendrerit risus nec vehicula placerat. Nulla et lectus nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - Etiam ut sodales ex. Nulla luctus, magna eu scelerisque sagittis, nibh quam consectetur neque, non rutrum dolor metus nec ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed egestas augue elit, sollicitudin accumsan massa lobortis ac. Curabitur placerat, dolor a aliquam maximus, velit ipsum laoreet ligula, id ullamcorper lacus nibh eget nisl. Donec eget lacus venenatis enim consequat auctor vel in. """ - topic_id = TopicId.from_string("0.0.7301777") - # Create a new Topic - submit_topic_message_transaction(client, topic_id, message, key) - info = TopicInfoQuery().set_topic_id(topic_id).execute(client) - print(info) +load_dotenv() + +def setup_client(): + """ + Set up and configure a Hedera client for testnet operations. + """ + network_name = os.getenv('NETWORK', 'testnet').lower() + + print(f"Connecting to Hedera {network_name} network!") + + try : + network = Network(network_name) + client = Client(network) + + operator_id = AccountId.from_string(os.getenv('OPERATOR_ID')) + operator_key = PrivateKey.from_string(os.getenv('OPERATOR_KEY')) + + client.set_operator(operator_id, operator_key) + print(f"Client initialized with operator: {operator_id}") + return client + except Exception as e: + print(f"Failed to set up client: {e}") + sys.exit(1) + +def create_topic(client): + """ + Create a new topic. + """ + print("\nCreating a Topic...") + try: + topic_receipt = ( + TopicCreateTransaction( + memo="Python SDK created topic" + ) + .freeze_with(client) + .execute(client) + ) + topic_id = topic_receipt.topic_id + + print(f"Topic created: {topic_id}: {topic_id}") + + return topic_id + except Exception as e: + print(f"Error: Creating topic: {e}") + sys.exit(1) + +def submit_topic_message_transaction(client, topic_id): + """ + Submit a chunked message to the specified topic. + """ + print("\nSubmitting large message...") + try: + message_receipt = ( + TopicMessageSubmitTransaction() + .set_topic_id(topic_id) + .set_message(BIG_CONTENT) + .freeze_with(client) + .execute(client) + ) + + if message_receipt.status != ResponseCode.SUCCESS: + print(f"Failed to submit message status: {ResponseCode(message_receipt.status).name}") + sys.exit(1) + + print(f"Message submitted (status={ResponseCode(message_receipt.status)}, txId={message_receipt.transaction_id})") + print(f"Message size:", len(BIG_CONTENT), "bytes") + print(f"Message Content: {(BIG_CONTENT[:140] + "...") if len(BIG_CONTENT) > 40 else BIG_CONTENT}") + except Exception as e: + print(f"Error: Message submission failed: {str(e)}") + sys.exit(1) + +def fetch_topic_info(client, topic_id): + """ + Fetch and print topic info. + """ + print("\nFetching topic info...") + + try: + info = TopicInfoQuery().set_topic_id(topic_id).execute(client) + print ( + f"--- Topic Info ---\n" + f"TopicId: {topic_id}\n" + f"Memo: {info.memo}\n" + f"Running Hash: {info.running_hash}\n" + f"Sequence Number: {info.sequence_number}\n" + f"Expiration Time: {info.expiration_time}\n" + f"------------------" + ) + + except Exception as e: + print(f"Failed to fetch topic info: {e}") + + +def main(): + """ + Create a topic and submit a large multi-chunk message to it. + """ + client = setup_client() + topic_id = create_topic(client) + fetch_topic_info(client, topic_id) + submit_topic_message_transaction(client, topic_id) + fetch_topic_info(client, topic_id) + if __name__ == "__main__": main() diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index b3bbc23f4..c7c097eed 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -44,8 +44,8 @@ def __init__( self._current_index = 0 self._total_chunks = self.get_required_chunks() - self._intial_transaction_id: Optional[TransactionId] = None - self._initial_transaction_id: List[TransactionId] = [] + self._initial_transaction_id: Optional[TransactionId] = None + self._transaction_ids: List[TransactionId] = [] self._signing_keys: List["PrivateKey"] = [] def get_required_chunks(self) -> int: @@ -204,7 +204,7 @@ def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessa # Multi-chunk metadata if self._total_chunks > 1: body.chunkInfo.CopyFrom(consensus_submit_message_pb2.ConsensusMessageChunkInfo( - initialTransactionID=self._intial_transaction_id._to_proto(), + initialTransactionID=self._initial_transaction_id._to_proto(), total=self._total_chunks, number=self._current_index + 1 )) @@ -256,28 +256,29 @@ def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": if self.transaction_id is None: self.transaction_id = client.generate_transaction_id() - - self._transaction_ids = [] - base_timestamp = self.transaction_id.valid_start - - for i in range(self.get_required_chunks()): - if i == 0: - if self._intial_transaction_id is None: - self._intial_transaction_id = self.transaction_id - - chunk_transaction_id = self.transaction_id - else: - chunk_valid_start = timestamp_pb2.Timestamp( - seconds=base_timestamp.seconds, - nanos=base_timestamp.nanos + i - ) - chunk_transaction_id = TransactionId( - account_id=self.transaction_id.account_id, - valid_start=chunk_valid_start - ) + if len(self._transaction_ids) == 0: + self._transaction_ids = [] + + base_timestamp = self.transaction_id.valid_start + + for i in range(self.get_required_chunks()): + if i == 0: + if self._initial_transaction_id is None: + self._initial_transaction_id = self.transaction_id + + chunk_transaction_id = self.transaction_id + else: + chunk_valid_start = timestamp_pb2.Timestamp( + seconds=base_timestamp.seconds, + nanos=base_timestamp.nanos + i + ) + chunk_transaction_id = TransactionId( + account_id=self.transaction_id.account_id, + valid_start=chunk_valid_start + ) - self._transaction_ids.append(chunk_transaction_id) + self._transaction_ids.append(chunk_transaction_id) return super().freeze_with(client) diff --git a/tests/integration/topic_message_qurey_e2e_test.py b/tests/integration/topic_message_qurey_e2e_test.py index 422916022..5baf4815a 100644 --- a/tests/integration/topic_message_qurey_e2e_test.py +++ b/tests/integration/topic_message_qurey_e2e_test.py @@ -55,8 +55,8 @@ def create_topic(client): @pytest.mark.integration -def test_integration_can_s(env): - """Test that a topic message submit transaction executes.""" +def test_topic_message_query_receives_messages(env): + """Test that topic message query receives a message.""" topic_id = create_topic(env.client) messages: List[str] = [] @@ -70,8 +70,7 @@ def on_error_handler(e): query = TopicMessageQuery( topic_id=topic_id, start_time=datetime.now(timezone.utc), - limit=0, - chunking_enabled=True + limit=0 ) handle = query.subscribe( @@ -96,7 +95,7 @@ def on_error_handler(e): start = datetime.now() while len(messages) == 0: - if datetime.now() - start > timedelta(seconds=60): + if datetime.now() - start > timedelta(seconds=120): raise TimeoutError("TopicMessage was not received in time") time.sleep(1) @@ -105,8 +104,48 @@ def on_error_handler(e): @pytest.mark.integration -def test_integration_topic_message_submit_large_transaction_can_execute(): - """Test that a topic message submit transaction executes with large message.""" +def test_topic_message_query_limit(env): + """Test topic message query stops after receiving limit messages.""" + topic_id = create_topic(env.client) + messages: List[str] = [] + + def on_message(m: TopicMessage): + messages.append(m.contents.decode("utf-8")) + + query = TopicMessageQuery( + topic_id=topic_id, + start_time=datetime.now(timezone.utc), + limit=2 + ) + + handle = query.subscribe(env.client, on_message=on_message) + + # Submit 3 messages + try: + for i in range(3): + ( + TopicMessageSubmitTransaction(topic_id=topic_id, message=f"msg{i}") + .freeze_with(env.client) + .execute(env.client) + ) + + start = datetime.now() + + while len(messages) != 2: + if datetime.now() - start > timedelta(seconds=120): + raise TimeoutError("TopicMessage was not received in time") + time.sleep(1) + + # Should stop at limit=2 + assert messages == ["msg0", "msg1"] + + finally: + handle.cancel() + + +@pytest.mark.integration +def test_topic_message_query_large_message_chunking(env): + """Test that topic message query receives chunked message.""" topic_id = create_topic(env.client) messages: List[str] = [] @@ -147,7 +186,7 @@ def on_error_handler(e): start = datetime.now() while len(messages) == 0: - if datetime.now() - start > timedelta(seconds=60): + if datetime.now() - start > timedelta(seconds=120): raise TimeoutError("TopicMessage was not received in time") time.sleep(1) diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index 6b30c556a..a1decb385 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -287,3 +287,30 @@ def test_integration_scheduled_topic_message_submit_transaction_can_execute_with env.client.set_operator(operator_id, operator_key) # Set the operator to the account delete_topic(env.client, topic_id) + + +@pytest.mark.integration +def test_integration_topic_message_submit_transaction_fails_if_required_chunk_greater_than_max_chunk(env): + """Test that a topic message fails submitting transaction when required chunk greater than max_chunks.""" + submit_key = PrivateKey.generate() + + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key, + submit_key=submit_key + ) + + info = TopicInfoQuery(topic_id=topic_id).execute(env.client) + # Check that no message is submited + assert info.sequence_number == 0 + + message_transaction = TopicMessageSubmitTransaction( + topic_id=topic_id, + message="A"*(1024*4) # requires 4 chunks + ) + message_transaction.set_max_chunks(2) + message_transaction.freeze_with(env.client) + with pytest.raises(ValueError, match="Message requires 4 chunks but max_chunks=2. Increase limit with set_max_chunks()."): + message_transaction.execute(env.client) + + delete_topic(env.client, topic_id) \ No newline at end of file diff --git a/tests/unit/test_topic_message_submit_transaction.py b/tests/unit/test_topic_message_submit_transaction.py index dad8080f3..1d8e643a2 100644 --- a/tests/unit/test_topic_message_submit_transaction.py +++ b/tests/unit/test_topic_message_submit_transaction.py @@ -35,15 +35,27 @@ def custom_fee_limit(): def test_constructor_and_setters(topic_id, message, custom_fee_limit): """Test constructor and all setter methods.""" + max_chunks = 2 + chunk_size = 128 + # Test constructor with parameters - tx = TopicMessageSubmitTransaction(topic_id=topic_id, message=message) + tx = TopicMessageSubmitTransaction( + topic_id=topic_id, + message=message, + chunk_size=chunk_size, + max_chunks=max_chunks + ) assert tx.topic_id == topic_id assert tx.message == message + assert tx.chunk_size == chunk_size + assert tx.max_chunks == max_chunks # Test constructor with default values tx_default = TopicMessageSubmitTransaction() assert tx_default.topic_id is None assert tx_default.message is None + assert tx_default.chunk_size == 1024 + assert tx_default.max_chunks == 20 # Test set_topic_id result = tx_default.set_topic_id(topic_id) @@ -55,6 +67,16 @@ def test_constructor_and_setters(topic_id, message, custom_fee_limit): assert tx_default.message == message assert result is tx_default + # Test set_chunk_size + result = tx_default.set_chunk_size(chunk_size) + assert tx_default.chunk_size == chunk_size + assert result is tx_default + + # Test set_max_chunks + result = tx_default.set_max_chunks(max_chunks) + assert tx_default.max_chunks == max_chunks + assert result is tx_default + # Test set_custom_fee_limits custom_fee_limits = [custom_fee_limit] result = tx_default.set_custom_fee_limits(custom_fee_limits) @@ -77,6 +99,9 @@ def test_set_methods_require_not_frozen( mock_client, topic_id, message, custom_fee_limit ): """Test that setter methods raise exception when transaction is frozen.""" + max_chunks = 2 + chunk_size = 128 + tx = TopicMessageSubmitTransaction(topic_id=topic_id, message=message) tx.freeze_with(mock_client) @@ -85,6 +110,8 @@ def test_set_methods_require_not_frozen( ("set_message", message), ("set_custom_fee_limits", [custom_fee_limit]), ("add_custom_fee_limit", custom_fee_limit), + ("set_chunk_size", chunk_size), + ("set_max_chunks", max_chunks) ] for method_name, value in test_cases: @@ -97,18 +124,24 @@ def test_set_methods_require_not_frozen( def test_method_chaining(topic_id, message, custom_fee_limit): """Test method chaining functionality.""" tx = TopicMessageSubmitTransaction() + max_chunks = 2 + chunk_size = 128 result = ( tx.set_topic_id(topic_id) .set_message(message) .set_custom_fee_limits([custom_fee_limit]) .add_custom_fee_limit(custom_fee_limit) + .set_chunk_size(chunk_size) + .set_max_chunks(max_chunks) ) assert result is tx assert tx.topic_id == topic_id assert tx.message == message assert len(tx.custom_fee_limits) == 2 + assert tx.chunk_size == chunk_size + assert tx.max_chunks == max_chunks def test_get_method(): @@ -209,14 +242,19 @@ def test_topic_message_submit_transaction_with_large_message(topic_id): ) response_sequences = [ - [tx_response, receipt_response], + [tx_response, receipt_response], # chunk 1 + [tx_response, receipt_response], # chunk 2 + [tx_response, receipt_response], # chunk 3 + [tx_response, receipt_response], # chunk 4 ] + with mock_hedera_servers(response_sequences) as client: tx = ( TopicMessageSubmitTransaction() .set_topic_id(topic_id) .set_message(large_message) + .freeze_with(client) ) try: From 87a8adcba0c4497fc8f0aab1f616099fa5d85c2e Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 26 Nov 2025 17:51:04 +0530 Subject: [PATCH 06/14] chore: updated changelog.md Signed-off-by: Manish Dait --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60cbf7a80..67571d707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. - Add example demonstrating usage of `CustomFeeLimit` in `examples/transaction/custom_fee_limit.py` - Added `.github/workflows/merge-conflict-bot.yml` to automatically detect and notify users of merge conflicts in Pull Requests. +- Add support for chunk message in `TopicSubmitMessageTransaction`. + ### Changed - Removed duplicate import of transaction_pb2 in transaction.py @@ -54,7 +56,6 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. - Added `docs\sdk_developers\training\receipts.md` as a training guide for users to understand hedera receipts. - Add `set_token_ids`, `_from_proto`, `_validate_checksum` to TokenAssociateTransaction [#795] - docs: added `network_and_client.md` with a table of contents, and added external example scripts (`client.py`). -- Add support for chunk message in `TopicSubmitMessageTransaction`. ### Changed From 6aaafc144c415681a3e9342370e1e55e10e0995f Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 26 Nov 2025 17:51:40 +0530 Subject: [PATCH 07/14] feat: updated client to use tls for mirror node for specific port Signed-off-by: Manish Dait --- src/hiero_sdk_python/client/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hiero_sdk_python/client/client.py b/src/hiero_sdk_python/client/client.py index 0f94fce52..062d1dd1b 100644 --- a/src/hiero_sdk_python/client/client.py +++ b/src/hiero_sdk_python/client/client.py @@ -53,7 +53,10 @@ def _init_mirror_stub(self) -> None: We now use self.network.get_mirror_address() for a configurable mirror address. """ mirror_address = self.network.get_mirror_address() - self.mirror_channel = grpc.secure_channel(mirror_address, grpc.ssl_channel_credentials()) + if mirror_address.endswith(':50212') or mirror_address.endswith(':443'): + self.mirror_channel = grpc.secure_channel(mirror_address, grpc.ssl_channel_credentials()) + else: + self.mirror_channel = grpc.insecure_channel(mirror_address) self.mirror_stub = mirror_consensus_grpc.ConsensusServiceStub(self.mirror_channel) def set_operator(self, account_id: AccountId, private_key: PrivateKey) -> None: From 5a02ced0897279ab9c4029dfd0f243e96bfe4ff5 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 26 Nov 2025 18:26:21 +0530 Subject: [PATCH 08/14] chore: fix typo issues Signed-off-by: Manish Dait --- CHANGELOG.md | 3 +-- .../consensus/topic_message_submit_chunked.py | 16 +++++++--------- .../topic_message_submit_transaction.py | 8 +++----- .../query/topic_message_query.py | 1 - ...test.py => topic_message_query_e2e_test.py} | 3 ++- ...opic_message_submit_transaction_e2e_test.py | 18 ++++++++++-------- 6 files changed, 23 insertions(+), 26 deletions(-) rename tests/integration/{topic_message_qurey_e2e_test.py => topic_message_query_e2e_test.py} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67571d707..6dd66ea29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. ### Added - Add example demonstrating usage of `CustomFeeLimit` in `examples/transaction/custom_fee_limit.py` - Added `.github/workflows/merge-conflict-bot.yml` to automatically detect and notify users of merge conflicts in Pull Requests. - -- Add support for chunk message in `TopicSubmitMessageTransaction`. +- Support for message chunking in `TopicSubmitMessageTransaction`. ### Changed diff --git a/examples/consensus/topic_message_submit_chunked.py b/examples/consensus/topic_message_submit_chunked.py index 857b7eedb..81d35465d 100644 --- a/examples/consensus/topic_message_submit_chunked.py +++ b/examples/consensus/topic_message_submit_chunked.py @@ -1,6 +1,6 @@ """ -uv run examples/topic_message_submit.py -python examples/topic_message_submit.py +uv run examples/consensus/topic_message_submit_chunked.py +python examples/consensus/topic_message_submit_chunked.py """ import os @@ -54,7 +54,7 @@ def setup_client(): print(f"Connecting to Hedera {network_name} network!") - try : + try: network = Network(network_name) client = Client(network) @@ -83,7 +83,7 @@ def create_topic(client): ) topic_id = topic_receipt.topic_id - print(f"Topic created: {topic_id}: {topic_id}") + print(f"Topic created: {topic_id}") return topic_id except Exception as e: @@ -97,7 +97,7 @@ def submit_topic_message_transaction(client, topic_id): print("\nSubmitting large message...") try: message_receipt = ( - TopicMessageSubmitTransaction() + TopicMessageSubmitTransaction() .set_topic_id(topic_id) .set_message(BIG_CONTENT) .freeze_with(client) @@ -110,7 +110,7 @@ def submit_topic_message_transaction(client, topic_id): print(f"Message submitted (status={ResponseCode(message_receipt.status)}, txId={message_receipt.transaction_id})") print(f"Message size:", len(BIG_CONTENT), "bytes") - print(f"Message Content: {(BIG_CONTENT[:140] + "...") if len(BIG_CONTENT) > 40 else BIG_CONTENT}") + print(f"Message Content: {(BIG_CONTENT[:140] + "...") if len(BIG_CONTENT) > 140 else BIG_CONTENT}") except Exception as e: print(f"Error: Message submission failed: {str(e)}") sys.exit(1) @@ -123,7 +123,7 @@ def fetch_topic_info(client, topic_id): try: info = TopicInfoQuery().set_topic_id(topic_id).execute(client) - print ( + print( f"--- Topic Info ---\n" f"TopicId: {topic_id}\n" f"Memo: {info.memo}\n" @@ -149,5 +149,3 @@ def main(): if __name__ == "__main__": main() - - \ No newline at end of file diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index c7c097eed..23be15a5a 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -115,7 +115,7 @@ def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": Set maximum allowed chunks. Args: - mac_chunks (int): The maximum number of chunks allowed. + max_chunks (int): The maximum number of chunks allowed. Returns: TopicMessageSubmitTransaction: This transaction instance (for chaining). @@ -257,9 +257,7 @@ def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": if self.transaction_id is None: self.transaction_id = client.generate_transaction_id() - if len(self._transaction_ids) == 0: - self._transaction_ids = [] - + if not self._transaction_ids: base_timestamp = self.transaction_id.valid_start for i in range(self.get_required_chunks()): @@ -310,7 +308,7 @@ def execute(self, client: "Client"): response = super().execute(client) responses.append(response) - # Return the first response JS SDK do + # Return the first response as the JS SDK does return responses[0] if responses else None def sign(self, private_key: "PrivateKey"): diff --git a/src/hiero_sdk_python/query/topic_message_query.py b/src/hiero_sdk_python/query/topic_message_query.py index fe23c0ed7..848e993ee 100644 --- a/src/hiero_sdk_python/query/topic_message_query.py +++ b/src/hiero_sdk_python/query/topic_message_query.py @@ -163,7 +163,6 @@ def run_stream(): except Exception as e: if subscription_handle.is_cancelled(): - return attempt += 1 diff --git a/tests/integration/topic_message_qurey_e2e_test.py b/tests/integration/topic_message_query_e2e_test.py similarity index 99% rename from tests/integration/topic_message_qurey_e2e_test.py rename to tests/integration/topic_message_query_e2e_test.py index 5baf4815a..335fe6810 100644 --- a/tests/integration/topic_message_qurey_e2e_test.py +++ b/tests/integration/topic_message_query_e2e_test.py @@ -75,7 +75,8 @@ def on_error_handler(e): handle = query.subscribe( env.client, - on_message=get_message,on_error=on_error_handler + on_message=get_message, + on_error=on_error_handler ) message_receipt = ( diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index a1decb385..690ccbd58 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -57,7 +57,7 @@ def test_integration_topic_message_submit_transaction_can_execute(env): ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - # Check that no message is submited + # Check that no message is submitted assert info.sequence_number == 0 message_transaction = TopicMessageSubmitTransaction( @@ -73,7 +73,7 @@ def test_integration_topic_message_submit_transaction_can_execute(env): ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - # Check that one message is submited + # Check that one message is submitted assert info.sequence_number == 1 delete_topic(env.client, topic_id) @@ -121,7 +121,7 @@ def test_integration_topic_message_submit_transaction_with_submit_key(env): ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - # Check that no message is submited + # Check that no message is submited assert info.sequence_number == 0 message_transaction = TopicMessageSubmitTransaction( @@ -139,7 +139,7 @@ def test_integration_topic_message_submit_transaction_with_submit_key(env): ), f"Message submission failed with status: {ResponseCode(message_receipt.status).name}" info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - # Check that one message is submited + # Check that one message is submited assert info.sequence_number == 1 delete_topic(env.client, topic_id) @@ -157,7 +157,7 @@ def test_integration_topic_message_submit_transaction_without_submit_key_fails(e ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - # Check that no message is submited + # Check that no message is submited assert info.sequence_number == 0 message_transaction = TopicMessageSubmitTransaction( @@ -257,7 +257,8 @@ def test_integration_scheduled_topic_message_submit_transaction_can_execute_with balance.hbars.to_tinybars() == Hbar(3).to_tinybars() ), f"Expected balance of 3 Hbar, but got {balance.hbars.to_tinybars()}" - env.client.set_operator(account.id, account.key) # Set the operator to the account + # Restore the operator to the original account + env.client.set_operator(account.id, account.key) topic_message_submit_fee_limit = ( CustomFeeLimit().set_payer_id(account.id).add_custom_fee(topic_fee) @@ -285,7 +286,8 @@ def test_integration_scheduled_topic_message_submit_transaction_can_execute_with balance.hbars.to_tinybars() < Hbar(2).to_tinybars() ), f"Expected balance of less than 2 Hbar, but got {balance.hbars.to_tinybars()}" - env.client.set_operator(operator_id, operator_key) # Set the operator to the account + # Restore the operator to the original account + env.client.set_operator(operator_id, operator_key) delete_topic(env.client, topic_id) @@ -301,7 +303,7 @@ def test_integration_topic_message_submit_transaction_fails_if_required_chunk_gr ) info = TopicInfoQuery(topic_id=topic_id).execute(env.client) - # Check that no message is submited + # Check that no message is submited assert info.sequence_number == 0 message_transaction = TopicMessageSubmitTransaction( From 90e56aa36ee762e8ca8234ac9d8c553010abdf39 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 26 Nov 2025 18:33:47 +0530 Subject: [PATCH 09/14] chore: fix mypy and pylint issues Signed-off-by: Manish Dait --- .../topic_message_submit_transaction.py | 31 ++++++++++--------- .../query/topic_message_query.py | 4 +-- ...pic_message_submit_transaction_e2e_test.py | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index 23be15a5a..c9dfb2ab1 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -47,7 +47,7 @@ def __init__( self._initial_transaction_id: Optional[TransactionId] = None self._transaction_ids: List[TransactionId] = [] self._signing_keys: List["PrivateKey"] = [] - + def get_required_chunks(self) -> int: """ Returns the number of chunks required for the current message. @@ -91,7 +91,7 @@ def set_message(self, message: str) -> "TopicMessageSubmitTransaction": self.message = message self._total_chunks = self.get_required_chunks() return self - + def set_chunk_size(self, chunk_size: int) -> "TopicMessageSubmitTransaction": """ Set maximum chunk size in bytes. @@ -105,11 +105,11 @@ def set_chunk_size(self, chunk_size: int) -> "TopicMessageSubmitTransaction": self._require_not_frozen() if chunk_size <= 0: raise ValueError("chunk_size must be positive") - + self.chunk_size = chunk_size self._total_chunks = self.get_required_chunks() return self - + def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": """ Set maximum allowed chunks. @@ -123,7 +123,7 @@ def set_max_chunks(self, max_chunks: int) -> "TopicMessageSubmitTransaction": self._require_not_frozen() if max_chunks <= 0: raise ValueError("max_chunks must be positive") - + self.max_chunks = max_chunks return self @@ -158,7 +158,7 @@ def add_custom_fee_limit( self._require_not_frozen() self.custom_fee_limits.append(custom_fee_limit) return self - + def _validate_chunking(self) -> None: """ Validates that chunk count does not exceed max_chunks. @@ -188,7 +188,7 @@ def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessa raise ValueError("Missing required fields: topic_id.") if self.message is None: raise ValueError("Missing required fields: message.") - + content = self.message.encode("utf-8") start_index = self._current_index * self.chunk_size @@ -216,7 +216,8 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody: Builds and returns the protobuf transaction body for message submission. Returns: - TransactionBody: The protobuf transaction body containing the message submission details. + TransactionBody: The protobuf transaction body containing + the message submission details. """ consensus_submit_message_body = self._build_proto_body() transaction_body = self.build_base_transaction_body() @@ -249,14 +250,14 @@ def _get_method(self, channel: _Channel) -> _Method: transaction_func=channel.topic.submitMessage, query_func=None ) - + def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": if self._transaction_body_bytes: return self if self.transaction_id is None: self.transaction_id = client.generate_transaction_id() - + if not self._transaction_ids: base_timestamp = self.transaction_id.valid_start @@ -275,12 +276,12 @@ def freeze_with(self, client: "Client") -> "TopicMessageSubmitTransaction": account_id=self.transaction_id.account_id, valid_start=chunk_valid_start ) - + self._transaction_ids.append(chunk_transaction_id) - + return super().freeze_with(client) - + def execute(self, client: "Client"): self._validate_chunking() @@ -298,7 +299,7 @@ def execute(self, client: "Client"): self._transaction_body_bytes.clear() self._signature_map.clear() - + self.freeze_with(client) for signing_key in self._signing_keys: @@ -324,4 +325,4 @@ def sign(self, private_key: "PrivateKey"): self._signing_keys.append(private_key) super().sign(private_key) - return self \ No newline at end of file + return self diff --git a/src/hiero_sdk_python/query/topic_message_query.py b/src/hiero_sdk_python/query/topic_message_query.py index 848e993ee..ea431821a 100644 --- a/src/hiero_sdk_python/query/topic_message_query.py +++ b/src/hiero_sdk_python/query/topic_message_query.py @@ -145,10 +145,10 @@ def run_stream(): continue initial_tx_id = TransactionId._from_proto(response.chunkInfo.initialTransactionID) - + if initial_tx_id not in pending_chunks: pending_chunks[initial_tx_id] = [] - + pending_chunks[initial_tx_id].append(response) if len(pending_chunks[initial_tx_id]) == response.chunkInfo.total: diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index 690ccbd58..0980b7ca2 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -80,7 +80,7 @@ def test_integration_topic_message_submit_transaction_can_execute(env): @pytest.mark.integration -def test_topic_message_submit_transation_can_submit_a_large_message(env): +def test_topic_message_submit_transaction_can_submit_a_large_message(env): """Test topic message submit transaction can submit large message.""" topic_id = create_topic( client=env.client, From b943885b450ce553889b6d7d14fc901868b96721 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 26 Nov 2025 18:38:53 +0530 Subject: [PATCH 10/14] chore: fix rebase issue Signed-off-by: Manish Dait --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd66ea29..5105cb0ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. ### Added - Add example demonstrating usage of `CustomFeeLimit` in `examples/transaction/custom_fee_limit.py` - Added `.github/workflows/merge-conflict-bot.yml` to automatically detect and notify users of merge conflicts in Pull Requests. -- Support for message chunking in `TopicSubmitMessageTransaction`. +- Support for message chunking in `TopicSubmitMessageTransaction`. ### Changed From 842115efd7d0ea5d77fb170223714845d87c8f27 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 27 Nov 2025 12:42:40 +0530 Subject: [PATCH 11/14] chore: rename example file Signed-off-by: Manish Dait --- ..._chunked.py => topic_message_submit_chunked_transaction.py} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename examples/consensus/{topic_message_submit_chunked.py => topic_message_submit_chunked_transaction.py} (99%) diff --git a/examples/consensus/topic_message_submit_chunked.py b/examples/consensus/topic_message_submit_chunked_transaction.py similarity index 99% rename from examples/consensus/topic_message_submit_chunked.py rename to examples/consensus/topic_message_submit_chunked_transaction.py index 81d35465d..c88c5313f 100644 --- a/examples/consensus/topic_message_submit_chunked.py +++ b/examples/consensus/topic_message_submit_chunked_transaction.py @@ -107,10 +107,11 @@ def submit_topic_message_transaction(client, topic_id): if message_receipt.status != ResponseCode.SUCCESS: print(f"Failed to submit message status: {ResponseCode(message_receipt.status).name}") sys.exit(1) - + print(f"Message submitted (status={ResponseCode(message_receipt.status)}, txId={message_receipt.transaction_id})") print(f"Message size:", len(BIG_CONTENT), "bytes") print(f"Message Content: {(BIG_CONTENT[:140] + "...") if len(BIG_CONTENT) > 140 else BIG_CONTENT}") + except Exception as e: print(f"Error: Message submission failed: {str(e)}") sys.exit(1) From af00d77027bb18d068200fc816956095020d115c Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 27 Nov 2025 12:43:37 +0530 Subject: [PATCH 12/14] chore: extend integration test for topic_message_submit tx Signed-off-by: Manish Dait --- ...pic_message_submit_transaction_e2e_test.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index 0980b7ca2..67005d8fa 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -109,6 +109,33 @@ def test_topic_message_submit_transaction_can_submit_a_large_message(env): delete_topic(env.client, topic_id) +@pytest.mark.integration +def test_topic_message_submit_transaction_fails_if_max_chunks_less_than_requied(env): + """Test topic message submit transaction can submit large message.""" + topic_id = create_topic( + client=env.client, + admin_key=env.operator_key + ) + + info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client) + assert info.sequence_number == 0 + + message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks + + message_tx = ( + TopicMessageSubmitTransaction() + .set_topic_id(topic_id) + .set_message(message) + .set_max_chunks(2) + .freeze_with(env.client) + ) + + with pytest.raises(ValueError): + message_receipt = message_tx.execute(env.client) + + delete_topic(env.client, topic_id) + + @pytest.mark.integration def test_integration_topic_message_submit_transaction_with_submit_key(env): """Test that a topic message submit transaction executes with submit key.""" From 2084b4130b1f7c6ef8f451175a72cc8008f72271 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 27 Nov 2025 12:44:34 +0530 Subject: [PATCH 13/14] feat: added topic_sequence_number and topic_running_hash property to tx receipt Signed-off-by: Manish Dait --- .../transaction/transaction_receipt.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/hiero_sdk_python/transaction/transaction_receipt.py b/src/hiero_sdk_python/transaction/transaction_receipt.py index fa94f3bd4..29e986d86 100644 --- a/src/hiero_sdk_python/transaction/transaction_receipt.py +++ b/src/hiero_sdk_python/transaction/transaction_receipt.py @@ -183,6 +183,29 @@ def node_id(self): int: The node ID if present; otherwise, 0. """ return self._receipt_proto.node_id + + @property + def topic_sequence_number(self) -> int: + """ + Returns the topic sequence number associated with this receipt. + + Returns: + int: The sequence number of the topic if present, otherwise 0. + """ + return self._receipt_proto.topicSequenceNumber + + @property + def topic_running_hash(self) -> Optional[bytes]: + """ + Returns the topic running hash associated with this receipt. + + Returns: + int: The running hash of the topic if present, otherwise None. + """ + if self._receipt_proto.HasField('topicRunningHash'): + return self._receipt_proto.topicRunningHash + + return None def _to_proto(self): """ From 19aeee39c0dab304db7f6b27c1bab23c98f9af6f Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 27 Nov 2025 16:15:49 +0530 Subject: [PATCH 14/14] chore: update docs string Signed-off-by: Manish Dait --- .../consensus/topic_message_submit_transaction.py | 2 ++ tests/integration/topic_message_submit_transaction_e2e_test.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py index c9dfb2ab1..4d79518ad 100644 --- a/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py +++ b/src/hiero_sdk_python/consensus/topic_message_submit_transaction.py @@ -35,6 +35,8 @@ def __init__( Args: topic_id (Optional[TopicId]): The ID of the topic. message (Optional[str]): The message to submit. + chunk_size (Optional[int]): The maximum chunk size in bytes, Default: 1024. + max_chunks (Optional[int]): The maximum number of chunks allowed, Default: 20. """ super().__init__() self.topic_id: Optional[TopicId] = topic_id diff --git a/tests/integration/topic_message_submit_transaction_e2e_test.py b/tests/integration/topic_message_submit_transaction_e2e_test.py index 67005d8fa..a2ac199e1 100644 --- a/tests/integration/topic_message_submit_transaction_e2e_test.py +++ b/tests/integration/topic_message_submit_transaction_e2e_test.py @@ -111,7 +111,7 @@ def test_topic_message_submit_transaction_can_submit_a_large_message(env): @pytest.mark.integration def test_topic_message_submit_transaction_fails_if_max_chunks_less_than_requied(env): - """Test topic message submit transaction can submit large message.""" + """Test topic message submit transaction fails if max_chunks less than requied.""" topic_id = create_topic( client=env.client, admin_key=env.operator_key