Skip to content

Commit 54354a5

Browse files
committed
chore: added new test for the topic submit message tx
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
1 parent f120663 commit 54354a5

6 files changed

Lines changed: 319 additions & 44 deletions

src/hiero_sdk_python/consensus/topic_message_submit_transaction.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def _message_as_bytes(self) -> bytes:
6969

7070
return self.message.encode("utf-8") if isinstance(self.message, str) else self.message
7171
self._initial_transaction_id: TransactionId | None = None
72-
self._chunk_info: consensus_submit_message_pb2.ConsensusMessageChunkInfo | None = None
72+
self._current_chunk_index: int | None = None
7373

7474
def get_required_chunks(self) -> int:
7575
"""
@@ -183,16 +183,22 @@ def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessa
183183

184184
content = self._message_as_bytes()
185185

186-
if self._chunk_info is not None:
187-
start_index = (self._chunk_info.number - 1) * self.chunk_size
186+
if self._current_chunk_index is not None:
187+
chunk_info = consensus_submit_message_pb2.ConsensusMessageChunkInfo(
188+
initialTransactionID=self._initial_transaction_id._to_proto(),
189+
total=self._total_chunks,
190+
number=self._current_chunk_index + 1,
191+
)
192+
193+
start_index = (self._current_chunk_index) * self.chunk_size
188194
end_index = min(start_index + self.chunk_size, len(content))
189195

190196
chunk_content = content[start_index:end_index]
191197

192198
return consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody(
193199
topicID=self.topic_id._to_proto() if self.topic_id else None,
194200
message=chunk_content,
195-
chunkInfo=self._chunk_info,
201+
chunkInfo=chunk_info,
196202
)
197203

198204
return consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody(
@@ -262,11 +268,7 @@ def freeze_with(self, client: Client) -> TopicMessageSubmitTransaction:
262268

263269
for chunk in range(self.get_required_chunks()):
264270
self._current_transaction_id_index = chunk
265-
self._chunk_info = consensus_submit_message_pb2.ConsensusMessageChunkInfo(
266-
initialTransactionID=self._initial_transaction_id._to_proto(),
267-
total=required_chunks,
268-
number=chunk + 1,
269-
)
271+
self._current_chunk_index = chunk
270272

271273
node_bytes = {}
272274

@@ -396,7 +398,9 @@ def body_size_all_chunks(self) -> list[int]:
396398

397399
try:
398400
for i, _ in enumerate(self._transaction_ids):
401+
self._current_chunk_index = i
399402
self._current_transaction_id_index = i
403+
400404
self._chunk_info = consensus_submit_message_pb2.ConsensusMessageChunkInfo(
401405
initialTransactionID=self._initial_transaction_id._to_proto(),
402406
total=self._total_chunks,

tests/integration/file_append_transaction_e2e_test.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def test_file_append_chunk_transaction_can_execute_with_manual_freeze(env):
331331

332332

333333
@pytest.mark.integration
334-
def test(env):
334+
def test_serialize_chunked_file_append_transaction_can_be_executed(env):
335335
create_receipt = (
336336
FileCreateTransaction()
337337
.set_keys(env.client.operator_private_key.public_key())
@@ -345,25 +345,28 @@ def test(env):
345345
file_contents = FileContentsQuery().set_file_id(file_id).execute(env.client)
346346
assert file_contents == b""
347347

348-
content = "A" * (8192) # content with (4000/1024) bytes ie approx 4 chunks
348+
content = "A" * (8192) # content with (8192/4096) bytes ie approx 2 chunks
349349

350-
tx = (
350+
tx1 = (
351351
FileAppendTransaction()
352352
.set_file_id(file_id)
353353
.set_contents(content)
354-
.set_transaction_id(TransactionId.generate(AccountId(0, 0, 101)))
355-
.set_node_account_ids([AccountId(0, 0, 3), AccountId(0, 0, 4)])
354+
.freeze_with(env.client)
356355
)
357356

358-
tx._default_transaction_fee = Hbar.from_hbars(10).to_tinybars()
357+
tx1._default_transaction_fee = Hbar.from_hbars(10).to_tinybars()
358+
tx_bytes = tx1.to_bytes()
359+
360+
tx2 = Transaction.from_bytes(tx_bytes)
359361

360-
print(tx.get_required_chunks())
361-
print(tx.transaction_id)
362-
print(tx.node_account_ids)
362+
assert isinstance(tx2, FileAppendTransaction)
363+
assert tx2.get_required_chunks() == 2
364+
assert len(tx2._transaction_ids) == 2
365+
assert len(tx2.node_account_ids) == len(env.client.network.nodes)
363366

364-
bytestx = tx.to_bytes()
365-
ntx = Transaction.from_bytes(bytestx)
367+
receipt = tx2.execute(env.client)
366368

367-
print(ntx.get_required_chunks())
368-
print(ntx.transaction_id)
369-
print(ntx.node_account_ids)
369+
assert receipt.status == ResponseCode.SUCCESS
370+
371+
file_contents = FileContentsQuery().set_file_id(file_id).execute(env.client)
372+
assert file_contents == bytes(content, "utf-8")

tests/integration/topic_message_submit_transaction_e2e_test.py

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -325,30 +325,71 @@ def test_topic_message_submit_transaction_can_submit_a_large_message_manual_free
325325

326326

327327
@pytest.mark.integration
328-
def test(env):
328+
def test_non_freeze_serialize_chunk_topic_message_submit_transaction_can_be_executed(env):
329+
"""Test topic message submit transaction can execute serialize chunk transaction."""
329330
topic_id = create_topic(client=env.client, admin_key=env.operator_key)
330331

331332
info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client)
332333
assert info.sequence_number == 0
333334

334-
message = "ABCD" # message with (1024 * 14) bytes ie 14 chunks
335+
message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks
336+
337+
tx1 = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message)
338+
339+
tx_bytes = tx1.to_bytes()
340+
341+
tx2 = Transaction.from_bytes(tx_bytes)
342+
343+
assert isinstance(tx2, TopicMessageSubmitTransaction)
344+
assert tx2.topic_id == tx1.topic_id
345+
assert tx2.message == tx1.message
346+
347+
receipt = tx2.execute(env.client)
348+
349+
assert receipt.status == ResponseCode.SUCCESS
350+
351+
info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client)
352+
assert info.sequence_number == 14
353+
354+
delete_topic(env.client, topic_id)
335355

336-
message_tx = (
337-
TopicMessageSubmitTransaction()
338-
.set_topic_id(topic_id)
339-
.set_chunk_size(1)
340-
.set_message(message)
341-
.freeze_with(env.client)
342-
)
343-
print(len(message_tx._transaction_ids))
344-
print(len(message_tx._transaction_body_bytes.keys()))
345356

346-
bytestx = message_tx.to_bytes()
347-
tx = Transaction.from_bytes(bytestx)
357+
@pytest.mark.integration
358+
def test_freeze_serialize_chunk_topic_message_submit_transaction_can_be_executed(env):
359+
"""Test topic message submit transaction can execute frozen serialize chunk transaction."""
360+
topic_id = create_topic(client=env.client, admin_key=env.operator_key)
361+
362+
info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client)
363+
assert info.sequence_number == 0
364+
365+
message = "A" * (1024 * 14) # message with (1024 * 14) bytes ie 14 chunks
366+
367+
tx1 = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_message(message).freeze_with(env.client)
368+
369+
tx_bytes = tx1.to_bytes()
348370

349-
print(isinstance(tx, TopicMessageSubmitTransaction))
350-
print(len(tx._transaction_ids))
351-
print(len(tx._transaction_body_bytes.keys()))
352-
tx.execute(env.client)
371+
tx2 = Transaction.from_bytes(tx_bytes)
353372

354-
print(tx.message)
373+
assert isinstance(tx2, TopicMessageSubmitTransaction)
374+
assert tx2.topic_id == tx1.topic_id
375+
# if transaction is frozen the serialization uses the transaction_body_bytes
376+
# so the message will only contain the text for the first chunk
377+
assert tx2.message != tx1.message
378+
assert tx2._transaction_ids == tx1._transaction_ids
379+
assert len(tx2._transaction_body_bytes) == len(tx1._transaction_body_bytes)
380+
381+
for transaction_id, node_bytes in tx2._transaction_body_bytes.items():
382+
for node_id, _ in node_bytes.items():
383+
assert (
384+
tx1._transaction_body_bytes[transaction_id][node_id]
385+
== tx2._transaction_body_bytes[transaction_id][node_id]
386+
)
387+
388+
receipt = tx2.execute(env.client)
389+
390+
assert receipt.status == ResponseCode.SUCCESS
391+
392+
info = TopicInfoQuery().set_topic_id(topic_id).execute(env.client)
393+
assert info.sequence_number == 14
394+
395+
delete_topic(env.client, topic_id)

tests/unit/topic_message_submit_transaction_test.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from hiero_sdk_python.account.account_id import AccountId
1010
from hiero_sdk_python.consensus.topic_message_submit_transaction import TopicMessageSubmitTransaction
11+
from hiero_sdk_python.crypto.private_key import PrivateKey
1112
from hiero_sdk_python.exceptions import PrecheckError, ReceiptStatusError
1213
from hiero_sdk_python.hapi.services import (
1314
response_header_pb2,
@@ -22,6 +23,7 @@
2223
)
2324
from hiero_sdk_python.response_code import ResponseCode
2425
from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit
26+
from hiero_sdk_python.transaction.transaction import Transaction
2527
from hiero_sdk_python.transaction.transaction_id import TransactionId
2628
from hiero_sdk_python.transaction.transaction_receipt import TransactionReceipt
2729
from hiero_sdk_python.transaction.transaction_response import TransactionResponse
@@ -566,3 +568,165 @@ def test_execute_raises_when_message_is_empty(topic_id, mock_client):
566568

567569
with pytest.raises(ValueError, match="Missing required fields: message"):
568570
transaction.freeze_with(mock_client)
571+
572+
573+
def test_frezee_with_client_generate_all_transaction_body_bytes(topic_id, mock_client):
574+
"""Test freeze_with() generate all required transaction body bytes."""
575+
message = "A" * 40 # will create 4 chunks
576+
577+
transaction = TopicMessageSubmitTransaction().set_topic_id(topic_id).set_chunk_size(10).set_message(message)
578+
579+
assert len(transaction._transaction_ids) == 0
580+
assert not transaction._transaction_body_bytes
581+
582+
transaction.freeze_with(mock_client)
583+
584+
# generated transaction ids == number of chunks i.e 4 (40/10)
585+
assert len(transaction._transaction_ids) == 4
586+
587+
transaction_ids = transaction._transaction_ids
588+
initial_transaction = transaction_ids[0]
589+
590+
for i, _ in enumerate(transaction_ids):
591+
assert transaction_ids[i].account_id == initial_transaction.account_id
592+
assert transaction_ids[i].valid_start.nanos == initial_transaction.valid_start.nanos + i
593+
594+
# create body bytes for each transaction_id and every node_account_ids
595+
assert len(transaction._transaction_body_bytes) == 4
596+
for transaction_id in transaction._transaction_ids:
597+
assert len(transaction._transaction_body_bytes[transaction_id]) == len(mock_client.network.nodes)
598+
599+
600+
def test_manual_frezee_generate_all_transaction_body_bytes(topic_id):
601+
"""Test freeze() generate all required transaction body bytes."""
602+
transaction_id = TransactionId.generate(AccountId(0, 0, 1))
603+
node_account_ids = [AccountId(0, 0, 4), AccountId(0, 0, 5)]
604+
605+
message = "A" * 20 # will create 2 chunks
606+
607+
transaction = (
608+
TopicMessageSubmitTransaction()
609+
.set_topic_id(topic_id)
610+
.set_chunk_size(10)
611+
.set_message(message)
612+
.set_transaction_id(transaction_id)
613+
.set_node_account_ids(node_account_ids)
614+
)
615+
616+
# single transaction_id which is set
617+
assert len(transaction._transaction_ids) == 1
618+
assert not transaction._transaction_body_bytes
619+
620+
transaction.freeze()
621+
622+
# generated transaction ids == number of chunks i.e 2 (20/10)
623+
assert len(transaction._transaction_ids) == 2
624+
625+
transaction_ids = transaction._transaction_ids
626+
627+
assert transaction_ids[0] == transaction_id
628+
629+
for i, _ in enumerate(transaction_ids):
630+
assert transaction_ids[i].account_id == transaction_id.account_id
631+
assert transaction_ids[i].valid_start.nanos == transaction_id.valid_start.nanos + i
632+
633+
# create body bytes for each transaction_id and every node_account_ids
634+
assert len(transaction._transaction_body_bytes) == 2
635+
for transaction_id in transaction._transaction_ids:
636+
assert len(transaction._transaction_body_bytes[transaction_id]) == len(node_account_ids)
637+
638+
639+
def test_serialize_chunk_transaction_preserve_signature_map(topic_id):
640+
"""Test serialize chunk transaction preserve signature maps."""
641+
transaction_id = TransactionId.generate(AccountId(0, 0, 1))
642+
node_account_ids = [AccountId(0, 0, 4), AccountId(0, 0, 5)]
643+
key = PrivateKey.generate_ecdsa()
644+
645+
message = "A" * 20 # will create 2 chunks
646+
647+
tx1 = (
648+
TopicMessageSubmitTransaction()
649+
.set_topic_id(topic_id)
650+
.set_chunk_size(10)
651+
.set_message(message)
652+
.set_transaction_id(transaction_id)
653+
.set_node_account_ids(node_account_ids)
654+
.freeze()
655+
.sign(key)
656+
)
657+
658+
assert tx1._signature_map
659+
660+
for transaction_id in tx1._transaction_ids:
661+
for node_id in node_account_ids:
662+
body_bytes = tx1._transaction_body_bytes[transaction_id][node_id]
663+
sig_pairs = tx1._signature_map[body_bytes].sigPair
664+
665+
assert len(sig_pairs) == 1
666+
667+
pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs}
668+
assert pubkey_prefixes == {key.public_key().to_bytes_raw()}
669+
670+
# will create transaction_bytes like
671+
# {tx_id1: {node_id1: bytes, node_id2: bytes}, tx_id2: {node_id1: bytes, node_id2: bytes}}
672+
673+
tx_bytes = tx1.to_bytes()
674+
tx2 = Transaction.from_bytes(tx_bytes)
675+
676+
assert isinstance(tx2, TopicMessageSubmitTransaction)
677+
678+
assert tx2._signature_map
679+
680+
for transaction_id in tx2._transaction_ids:
681+
for node_id in node_account_ids:
682+
body_bytes = tx2._transaction_body_bytes[transaction_id][node_id]
683+
sig_pairs = tx2._signature_map[body_bytes].sigPair
684+
685+
assert len(sig_pairs) == 1
686+
687+
pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs}
688+
assert pubkey_prefixes == {key.public_key().to_bytes_raw()}
689+
690+
691+
def test_signing_serialize_chunk_transaction_sign_all_available_bytes(topic_id):
692+
"""Test that signing the serialize chunk transaction sign all available bytes."""
693+
transaction_id = TransactionId.generate(AccountId(0, 0, 1))
694+
node_account_ids = [AccountId(0, 0, 4), AccountId(0, 0, 5)]
695+
696+
message = "A" * 20 # will create 2 chunks
697+
698+
tx1 = (
699+
TopicMessageSubmitTransaction()
700+
.set_topic_id(topic_id)
701+
.set_chunk_size(10)
702+
.set_message(message)
703+
.set_transaction_id(transaction_id)
704+
.set_node_account_ids(node_account_ids)
705+
.freeze()
706+
)
707+
708+
assert not tx1._signature_map
709+
710+
# will create transaction_bytes like
711+
# {tx_id1: {node_id1: bytes, node_id2: bytes}, tx_id2: {node_id1: bytes, node_id2: bytes}}
712+
713+
tx_bytes = tx1.to_bytes()
714+
tx2 = Transaction.from_bytes(tx_bytes)
715+
716+
assert isinstance(tx2, TopicMessageSubmitTransaction)
717+
718+
key = PrivateKey.generate_ecdsa()
719+
720+
tx2.sign(key)
721+
722+
assert tx2._signature_map
723+
724+
for transaction_id in tx2._transaction_ids:
725+
for node_id in node_account_ids:
726+
body_bytes = tx2._transaction_body_bytes[transaction_id][node_id]
727+
sig_pairs = tx2._signature_map[body_bytes].sigPair
728+
729+
assert len(sig_pairs) == 1
730+
731+
pubkey_prefixes = {sp.pubKeyPrefix for sp in sig_pairs}
732+
assert pubkey_prefixes == {key.public_key().to_bytes_raw()}

0 commit comments

Comments
 (0)