Skip to content

Conversation

@manishdait
Copy link
Contributor

Description:
This PR introduce the support for chunking in TopicSubmitMessageTransaction class.

Changes Added:

  • Added chunk_size and max_chunks fields to TopicSubmitMessage.
  • Override the freeze_with() and execute() method to perform chuncked transaction.
  • Added unit/integration test for validation

Related issue(s):

Fixes #861

Notes for reviewer:

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

@manishdait manishdait force-pushed the feat/topic-message-chuncking branch from d4c8eb9 to 817dc59 Compare November 23, 2025 17:15
@github-actions
Copy link

Hi, this is WorkflowBot.
Your pull request cannot be merged as it is not passing all our workflow checks.
Please click on each check to review the logs and resolve issues so all checks pass.
To help you:

@manishdait
Copy link
Contributor Author

manishdait commented Nov 24, 2025

Hi @nadineloepfe @exploreriii,

I was debugging the issue that’s causing the above integration tests to fail. The tests run successfully on testnet but fail on solo After checking the Logs in the failing run it looks like the client is attempting to connect to the mirror node address using TLS on solo, which doesn’t support TLS by default.

To address this, I updated the client’s mirror node initialization to use a secure channel only for mainnet/testnet/previewnet and fall back to an insecure channel for solo on my fork:

def _init_mirror_stub(self) -> None:
        """
        Connect to a mirror node for topic message subscriptions.
        We now use self.network.get_mirror_address() for a configurable mirror address.
        """
        mirror_address = self.network.get_mirror_address()
        if self.network.network in ('mainnet', 'testnet', 'previewnet'):
            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)

I tested this on both testnet and solo, and also ran a solo GitHub Action on my fork.

Can you confirm if this is correct, and whether this is the right approach?

I’d appreciate your thoughts or suggestions.

@hendrikebbers
Copy link
Member

Added copilot as reviewer to show it to @exploreriii

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for message chunking in the TopicMessageSubmitTransaction class, enabling the submission of large messages that exceed the single-transaction size limit by automatically splitting them into multiple chunks.

Key Changes:

  • Added chunk_size (default: 1024 bytes) and max_chunks (default: 20) configuration parameters to TopicMessageSubmitTransaction
  • Implemented custom freeze_with() and execute() methods to handle multi-chunk transaction creation and submission
  • Updated TopicMessage and TopicMessageQuery to use TransactionId objects instead of string representations for better type safety

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 27 comments.

Show a summary per file
File Description
src/hiero_sdk_python/consensus/topic_message_submit_transaction.py Core implementation of message chunking functionality with chunk size/max chunks configuration, transaction ID generation per chunk, and multi-chunk execution logic
src/hiero_sdk_python/consensus/topic_message.py Updated to use TransactionId objects instead of string-based transaction IDs for better type consistency
src/hiero_sdk_python/query/topic_message_query.py Modified chunk tracking to use TransactionId objects as dictionary keys instead of strings for improved type safety
tests/unit/test_topic_message_submit_transaction.py Added unit tests for new chunk_size and max_chunks setters, constructor parameters, and method chaining
tests/integration/topic_message_submit_transaction_e2e_test.py Added comprehensive integration tests including large message submission, submit key validation, chunking limit validation, and refactored existing tests with helper functions
tests/integration/topic_message_qurey_e2e_test.py New integration tests for topic message query functionality with chunking support (note: filename contains typo)
examples/consensus/topic_message_submit_chunked.py New example demonstrating chunked message submission with a large Lorem Ipsum text
examples/consensus/topic_message.py Updated mock classes to align with new TransactionId usage
CHANGELOG.md Added entry documenting the chunking support feature
Comments suppressed due to low confidence (1)

src/hiero_sdk_python/consensus/topic_message_submit_transaction.py:38

  • The docstring for __init__ is incomplete - it doesn't document the new chunk_size and max_chunks parameters that were added to the constructor.
        """
        Initializes a new TopicMessageSubmitTransaction instance.
        Args:
            topic_id (Optional[TopicId]): The ID of the topic.
            message (Optional[str]): The message to submit.
        """

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

)
topic_id = topic_receipt.topic_id

print(f"Topic created: {topic_id}: {topic_id}")
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant output in print statement. The line prints {topic_id}: {topic_id} which displays the topic_id twice. It should be print(f"Topic created: {topic_id}") instead.

Suggested change
print(f"Topic created: {topic_id}: {topic_id}")
print(f"Topic created: {topic_id}")

Copilot uses AI. Check for mistakes.
)

info = TopicInfoQuery(topic_id=topic_id).execute(env.client)
# Check that no message is submited
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "submited" should be "submitted".

Copilot uses AI. Check for mistakes.
Set maximum allowed chunks.
Args:
mac_chunks (int): The maximum number of chunks allowed.
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in docstring: "mac_chunks" should be "max_chunks" in the Args documentation.

Suggested change
mac_chunks (int): The maximum number of chunks allowed.
max_chunks (int): The maximum number of chunks allowed.

Copilot uses AI. Check for mistakes.

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}")
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent condition in message content display. Line 113 displays the first 140 characters if the message length is greater than 40, but the condition len(BIG_CONTENT) > 40 doesn't align with the slice BIG_CONTENT[:140]. This should be len(BIG_CONTENT) > 140 to match the slice length.

Suggested change
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}")

Copilot uses AI. Check for mistakes.
@nadineloepfe
Copy link
Contributor

Hi @nadineloepfe @exploreriii,

I was debugging the issue that’s causing the above integration tests to fail. The tests run successfully on testnet but fail on solo After checking the Logs in the failing run it looks like the client is attempting to connect to the mirror node address using TLS on solo, which doesn’t support TLS by default.

To address this, I updated the client’s mirror node initialization to use a secure channel only for mainnet/testnet/previewnet and fall back to an insecure channel for solo on my fork:

def _init_mirror_stub(self) -> None:
        """
        Connect to a mirror node for topic message subscriptions.
        We now use self.network.get_mirror_address() for a configurable mirror address.
        """
        mirror_address = self.network.get_mirror_address()
        if self.network.network in ('mainnet', 'testnet', 'previewnet'):
            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)

I tested this on both testnet and solo, and also ran a solo GitHub Action on my fork.

Can you confirm if this is correct, and whether this is the right approach?

I’d appreciate your thoughts or suggestions.

sooo your workaround works, great idea! But I've looked into the JS SDK, and they do it slightly different - see here in NodeMirrorChannel.js:

  this._client = new grpc.Client(
      address,
      address.endsWith(":50212") || address.endsWith(":443")
          ? grpc.credentials.createSsl()
          : grpc.credentials.createInsecure(),
  );

so the TLS Decision Logic is basically port based.

  • Port :443 or :50212 -> defaults to TLS/SSL
  • any other port (5600 for local) -> defaults to insecure

The network type determines which addresses/ports are used (in ClientConstants.js). so if we want to do the same in the Python SDK, we'd have to do smth like:

 def _init_mirror_stub(self) -> None:
      mirror_address = self.network.get_mirror_address()
      if mirror_address.endswith(":443") or mirror_address.endswith(":50212"):
          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)

That said, the network name approach is also valid and will work

@manishdait
Copy link
Contributor Author

@nadineloepfe, Thanks for confirming,

I’ll update the Python SDK implementation to follow the same pattern:

  • Use TLS automatically for ports :443 and :50212
  • Default to insecure for other ports (like 5600 on solo/local)

That should match the behavior in the JS SDK

@manishdait manishdait force-pushed the feat/topic-message-chuncking branch 2 times, most recently from 845a937 to f3c664a Compare November 26, 2025 13:09
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
…tx receipt

Signed-off-by: Manish Dait <daitmanish88@gmail.com>
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
@manishdait manishdait force-pushed the feat/topic-message-chuncking branch from 87adced to 19aeee3 Compare November 27, 2025 12:11
@manishdait manishdait marked this pull request as ready for review November 27, 2025 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve TopicMessageSubmitTransaction with message-size check and chunking details

3 participants