Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ 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`.

### Changed

- Removed duplicate import of transaction_pb2 in transaction.py
Expand Down Expand Up @@ -55,6 +56,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- 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`).


### Changed

- Upgraded step-security/harden-runner v2.13.2
Expand Down
21 changes: 11 additions & 10 deletions examples/consensus/topic_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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)
Expand Down
152 changes: 152 additions & 0 deletions examples/consensus/topic_message_submit_chunked_transaction.py

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/hiero_sdk_python/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
28 changes: 12 additions & 16 deletions src/hiero_sdk_python/consensus/topic_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -102,25 +99,24 @@ 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)
chunks.append(c)

total_size += len(r.message)


if (
transaction_id is None
and r.HasField("chunkInfo")
and r.chunkInfo.HasField("initialTransactionID")
):
tx_id = r.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(r.chunkInfo.initialTransactionID)

contents = bytearray(total_size)

offset: int = 0
for r in sorted_responses:
end = offset + len(r.message)
Expand Down
Loading