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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
## [Unreleased]

### Src
-
- Added Transaction size calculation properties `size`, `body_size` and `body_size_all_chunks`.

### Tests
- Refactor `mock_server` setup for network level TLS handling and added thread safety
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,7 @@ 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._resolve_transaction_id(client)

if not self._transaction_ids:
base_timestamp = self.transaction_id.valid_start
Expand Down Expand Up @@ -417,3 +416,22 @@ def sign(self, private_key: "PrivateKey"):

super().sign(private_key)
return self

@property
def body_size_all_chunks(self) -> List[int]:
"""
Returns an array of body sizes for transactions with multiple chunks.
"""
self._require_frozen()
sizes = []

original_index = self._current_index
original_transaction_id = self.transaction_id
for transaction_id in self._transaction_ids:
self.transaction_id = transaction_id
sizes.append(self.body_size)

self._current_index = original_index
self.transaction_id = original_transaction_id
return sizes

79 changes: 44 additions & 35 deletions src/hiero_sdk_python/file/file_append_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
if TYPE_CHECKING:
from hiero_sdk_python.client import Client
from hiero_sdk_python.crypto.private_key import PrivateKey
from hiero_sdk_python.key import PrivateKey
from hiero_sdk_python.channels import _Channel
from hiero_sdk_python.executable import _Method
from hiero_sdk_python.transaction.transaction import TransactionReceipt
Expand Down Expand Up @@ -284,42 +285,31 @@ def freeze_with(self, client: "Client") -> FileAppendTransaction:
return self


if self.transaction_id is None:
self.transaction_id = client.generate_transaction_id()
self._resolve_transaction_id(client)

# Generate transaction IDs for all chunks
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:
# Subsequent chunks get incremented timestamps
# Add i nanoseconds to space out chunks
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)

# We iterate through every node in the client's network
# For each node, set the node_account_id and build the transaction body
# This allows the transaction to be submitted to any node in the network
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()

# Set the node account id to the current node in the network
self.node_account_id = client.network.current_node._account_id

return self
if not 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:
# Subsequent chunks get incremented timestamps
# Add i nanoseconds to space out chunks
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
)
Comment on lines +291 to +308
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file_append_transaction.py code around lines 307-310
head -n 320 src/hiero_sdk_python/file/file_append_transaction.py | tail -n 40

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1610


🏁 Script executed:

# Check the full freeze_with method and chunk ID generation
sed -n '270,320p' src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 2038


🏁 Script executed:

# Look at the body_size_all_chunks implementation mentioned
sed -n '460,480p' src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 593


🏁 Script executed:

# Check protobuf Timestamp definition - find where it's imported/defined
find src -name "timestamp_pb2.py" -o -name "*.proto" | head -20

Repository: hiero-ledger/hiero-sdk-python

Length of output: 55


🏁 Script executed:

# Search for similar timestamp overflow handling in topic transactions
rg "chunk.*timestamp|nanos.*i|second.*total_nanos" src/hiero_sdk_python --type py -B 2 -A 2

Repository: hiero-ledger/hiero-sdk-python

Length of output: 8576


🏁 Script executed:

# Check body_size property implementation
rg "def body_size|@property" src/hiero_sdk_python/file/file_append_transaction.py -A 5

Repository: hiero-ledger/hiero-sdk-python

Length of output: 287


🏁 Script executed:

# Check how body_size is calculated - likely in parent Transaction class
rg "def body_size" src/hiero_sdk_python/transaction/transaction.py -A 10

Repository: hiero-ledger/hiero-sdk-python

Length of output: 268


🏁 Script executed:

# Look for _current_chunk_index usage in FileAppendTransaction
rg "_current_chunk_index" src/hiero_sdk_python/file/file_append_transaction.py -B 2 -A 2

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1017


🏁 Script executed:

# Check the protobuf Timestamp field constraints
rg "class.*Timestamp|nanos.*999" src/hiero_sdk_python/hapi -r --type py | head -20

Repository: hiero-ledger/hiero-sdk-python

Length of output: 181


🏁 Script executed:

# Check if there are any existing tests for chunk timestamp generation
fd "test.*file.*append" src/hiero_sdk_python --type f

Repository: hiero-ledger/hiero-sdk-python

Length of output: 55


🏁 Script executed:

# Look at tests to see how chunk timestamps are validated
find . -path "*/test*" -name "*file*append*.py" -o -path "*/test*" -name "*append*.py" | head -5

Repository: hiero-ledger/hiero-sdk-python

Length of output: 173


🏁 Script executed:

# Look at the chunk_index usage in build_transaction_body
rg "build_transaction_body|_current_chunk_index" src/hiero_sdk_python/file/file_append_transaction.py -B 3 -A 3

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1529


🏁 Script executed:

# Check what body_size calls and how it uses _current_chunk_index
sed -n '1,100p' src/hiero_sdk_python/file/file_append_transaction.py | grep -A 20 "def build_transaction_body"

Repository: hiero-ledger/hiero-sdk-python

Length of output: 55


🏁 Script executed:

# Look at the full build_transaction_body implementation
sed -n '150,250p' src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 3615


🏁 Script executed:

# Check if Timestamp class wraps protobuf Timestamp
cat src/hiero_sdk_python/timestamp.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 5051


🏁 Script executed:

# Look at what type transaction_id.valid_start is
rg "valid_start" src/hiero_sdk_python/transaction/transaction_id.py -B 2 -A 2 | head -30

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1155


🏁 Script executed:

# Check the unit tests for FileAppendTransaction
head -n 100 tests/unit/file_append_transaction_test.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 3737


🏁 Script executed:

# Check the exact protobuf Timestamp definition for nanos bounds
rg "nanos" src/hiero_sdk_python/hapi/services -l | head -5

Repository: hiero-ledger/hiero-sdk-python

Length of output: 147


🏁 Script executed:

# Verify the protobuf constraints - check imported timestamp_pb2
python3 << 'EOF'
# Check what constraints protobuf enforces on Timestamp.nanos
from google.protobuf import descriptor
try:
    from hiero_sdk_python.hapi.services import timestamp_pb2
    print("Timestamp message fields:")
    for field in timestamp_pb2.Timestamp.DESCRIPTOR.fields:
        print(f"  {field.name}: {field.type} (label: {field.label})")
except Exception as e:
    print(f"Error: {e}")
EOF

Repository: hiero-ledger/hiero-sdk-python

Length of output: 114


🏁 Script executed:

# Search for how body_size_all_chunks should work - check similar implementation in topic_message_submit_transaction
rg "body_size_all_chunks" src/hiero_sdk_python/consensus/topic_message_submit_transaction.py -A 15

Repository: hiero-ledger/hiero-sdk-python

Length of output: 545


🏁 Script executed:

# Verify that _current_chunk_index is supposed to be used when calculating body_size
rg "_current_chunk_index|start_index.*chunk_size" src/hiero_sdk_python/file/file_append_transaction.py -B 2 -A 2

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1018


🏁 Script executed:

# Check if body_size_all_chunks in topic_message_submit_transaction also has the same issue
sed -n '450,480p' src/hiero_sdk_python/consensus/topic_message_submit_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 55


🏁 Script executed:

# Look at how execute() properly updates _current_chunk_index
sed -n '320,350p' src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 979


🏁 Script executed:

# Verify protobuf Timestamp nanos maximum value by checking Google protobuf docs or imports
rg "timestamp_pb2" src/hiero_sdk_python -l | head -3 | xargs grep -l "Timestamp"

Repository: hiero-ledger/hiero-sdk-python

Length of output: 203


🏁 Script executed:

# Check imports in file_append_transaction to see if using protobuf Timestamp or SDK Timestamp
head -n 30 src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1265


🏁 Script executed:

# Look at how the SDK's Timestamp.plus_nanos handles overflow properly
sed -n '95,110p' src/hiero_sdk_python/timestamp.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 558


🏁 Script executed:

# Check if there are any validation tests for timestamp bounds
rg "nanos.*99" tests/unit -l | head -3

Repository: hiero-ledger/hiero-sdk-python

Length of output: 55


🏁 Script executed:

# Verify the actual behavior by checking what chunk_index should do
sed -n '195,220p' src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1016


🏁 Script executed:

# Check if there's loop update logic we might have missed
sed -n '468,476p' src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 367


BLOCKER: Add nanosecond carry logic to prevent timestamp overflow.

Lines 307-310 add i directly to base_timestamp.nanos without handling second boundary rollover. When base_timestamp.nanos + i exceeds 999,999,999, the protobuf Timestamp.nanos field becomes invalid. Later chunks receive malformed transaction IDs.

BLOCKER: Advance _current_chunk_index in body_size_all_chunks loop.

Lines 468-476: The loop iterates over transaction IDs but never updates _current_chunk_index. Since _build_proto_body() uses _current_chunk_index to slice the correct chunk (line 182: start_index = self._current_chunk_index * self.chunk_size), all body size calculations operate on chunk 0. The method returns sizes for the same chunk repeatedly instead of one size per chunk.

Suggested fixes

For timestamp overflow:

-                    chunk_valid_start = timestamp_pb2.Timestamp(
-                        seconds=base_timestamp.seconds,
-                        nanos=base_timestamp.nanos + i
-                    )
+                    total_nanos = base_timestamp.nanos + i
+                    chunk_valid_start = timestamp_pb2.Timestamp(
+                        seconds=base_timestamp.seconds + total_nanos // 1_000_000_000,
+                        nanos=total_nanos % 1_000_000_000,
+                    )

For body_size_all_chunks:

         original_index = self._current_chunk_index
         for transaction_id in self._transaction_ids:
             self.transaction_id = transaction_id
+            self._current_chunk_index = self._transaction_ids.index(transaction_id)
             sizes.append(self.body_size)
         
         self._current_chunk_index = original_index
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not 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:
# Subsequent chunks get incremented timestamps
# Add i nanoseconds to space out chunks
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 not 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:
# Subsequent chunks get incremented timestamps
# Add i nanoseconds to space out chunks
total_nanos = base_timestamp.nanos + i
chunk_valid_start = timestamp_pb2.Timestamp(
seconds=base_timestamp.seconds + total_nanos // 1_000_000_000,
nanos=total_nanos % 1_000_000_000,
)
chunk_transaction_id = TransactionId(
account_id=self.transaction_id.account_id,
valid_start=chunk_valid_start
)


self._transaction_ids.append(chunk_transaction_id)

return super().freeze_with(client)

@overload
def execute(
Expand Down Expand Up @@ -461,4 +451,23 @@ def sign(self, private_key: "PrivateKey") -> FileAppendTransaction:

# Call the parent sign method for the current transaction
super().sign(private_key)
return self
return self

@property
def body_size_all_chunks(self) -> List[int]:
"""
Returns an array of body sizes for transactions with multiple chunks.
"""
self._require_frozen()
sizes = []

original_index = self._current_chunk_index
original_transaction_id = self.transaction_id
for transaction_id in self._transaction_ids:
self.transaction_id = transaction_id
sizes.append(self.body_size)

self._current_chunk_index = original_index
self.transaction_id = original_transaction_id
return sizes

2 changes: 2 additions & 0 deletions src/hiero_sdk_python/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ def _get_channel(self):
if self._root_certificates:
# Use the certificate that is provided
self._node_pem_cert = self._root_certificates
print("node cert ", self._node_pem_cert)

Comment on lines 124 to +128
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove the certificate debug dump.

This branch runs in normal SDK usage whenever custom root certificates are configured, and it writes the full PEM bundle to stdout. That leaks certificate material into application/test logs and bypasses the SDK logger configuration.

🛡️ Proposed fix
         if self._address._is_transport_security():
             if self._root_certificates:
                 # Use the certificate that is provided
                 self._node_pem_cert = self._root_certificates
-                print("node cert ", self._node_pem_cert)
             
             else:
As per coding guidelines: `REVIEW FOCUS 3 — CERTIFICATE HANDLING (SECURITY CRITICAL)`.

else:
# Fetch pem_cert for the node
self._node_pem_cert = self._fetch_server_certificate_pem()
Expand Down
68 changes: 43 additions & 25 deletions src/hiero_sdk_python/transaction/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,30 @@ def _to_proto(self):
return transaction_pb2.Transaction(
signedTransactionBytes=signed_transaction.SerializeToString()
)

def _resolve_transaction_id(self, client: "Client"):
if self.transaction_id is not None:
return

if client is not None:
operator_account_id = client.operator_account_id
if operator_account_id is not None:
self.transaction_id = client.generate_transaction_id()
else:
raise ValueError(
"Client must have an operator_account or transactionId must be set."
)
else:
raise ValueError(
"Transaction ID must be set before freezing. Use freeze_with(client) or set_transaction_id()."
)

def _resolve_node_ids(self, client: "Client"):
if self.node_account_id is None and len(self.node_account_ids) == 0:
if client is None:
raise ValueError(
"Node account ID must be set before freezing. Use freeze_with(client) or manually set node_account_ids."
)

def freeze(self):
"""
Expand All @@ -258,29 +282,9 @@ def freeze(self):
Raises:
ValueError: If transaction_id or node_account_id are not set.
"""
if self._transaction_body_bytes:
return self

if self.transaction_id is None:
raise ValueError("Transaction ID must be set before freezing. Use freeze_with(client) or set_transaction_id().")

if self.node_account_id is None and len(self.node_account_ids) == 0:
raise ValueError("Node account ID must be set before freezing. Use freeze_with(client) or manually set node_account_ids.")

# Populate node_account_ids for backward compatibility
if self.node_account_id:
self.set_node_account_id(self.node_account_id)
self._transaction_body_bytes[self.node_account_id] = self.build_transaction_body().SerializeToString()
return self

# Build the transaction body for the single node
for node_account_id in self.node_account_ids:
self.node_account_id = node_account_id
self._transaction_body_bytes[node_account_id] = self.build_transaction_body().SerializeToString()

return self
return self.freeze_with(None)

def freeze_with(self, client):
def freeze_with(self, client: "Client"):
"""
Freezes the transaction by building the transaction body and setting necessary IDs.

Expand All @@ -296,8 +300,10 @@ def freeze_with(self, client):
if self._transaction_body_bytes:
return self

if self.transaction_id is None:
self.transaction_id = client.generate_transaction_id()
# Check transaction_id and node id to be set when using freeze()
self._resolve_transaction_id(client)

self._resolve_node_ids(client)

# We iterate through every node in the client's network
# For each node, set the node_account_id and build the transaction body
Expand Down Expand Up @@ -424,7 +430,7 @@ def is_signed_by(self, public_key):
return True
return False

def build_transaction_body(self):
def build_transaction_body(self) -> transaction_pb2.TransactionBody:
"""
Abstract method to build the transaction body.

Expand Down Expand Up @@ -913,3 +919,15 @@ def batchify(self, client: Client, batch_key: Key):
self.freeze_with(client)
self.sign(client.operator_private_key)
return self

@property
def size(self) -> int:
"""Returns the total transaction size in bytes after protobuf encoding"""
self._require_frozen()
return self._make_request().ByteSize()

@property
def body_size(self) -> int:
"""Returns just the transaction body size in bytes after encoding"""
self._require_frozen()
return self.build_transaction_body().ByteSize()
39 changes: 39 additions & 0 deletions tests/integration/file_append_transaction_e2e_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import pytest
from pytest import mark

from hiero_sdk_python.account.account_id import AccountId
from hiero_sdk_python.file.file_create_transaction import FileCreateTransaction
from hiero_sdk_python.file.file_append_transaction import FileAppendTransaction
from hiero_sdk_python.file.file_contents_query import FileContentsQuery
from hiero_sdk_python.file.file_id import FileId
from hiero_sdk_python.response_code import ResponseCode
from hiero_sdk_python.hbar import Hbar
from hiero_sdk_python.exceptions import PrecheckError
from hiero_sdk_python.transaction.transaction_id import TransactionId
from tests.integration.utils import env, IntegrationTestEnv

# Generate big contents for chunking tests - similar to JavaScript bigContents
Expand Down Expand Up @@ -304,3 +306,40 @@ def test_integration_file_append_transaction_method_chaining(env):

append_receipt = append_tx.execute(env.client)
assert append_receipt.status == ResponseCode.SUCCESS

@pytest.mark.integration
def test_file_append_chunk_transaction_can_execute_with_manual_freeze(env):
"""Test file append transaction can execute with manual freeze."""
create_receipt = (
FileCreateTransaction()
.set_keys(env.client.operator_private_key.public_key())
.set_contents(b"")
.execute(env.client)
)

assert create_receipt.status == ResponseCode.SUCCESS
file_id = create_receipt.file_id

file_contents = FileContentsQuery().set_file_id(file_id).execute(env.client)
assert file_contents == b""

content = "A" * (4000) # content with (4000/1024) bytes ie approx 4 chunks

tx = (
FileAppendTransaction()
.set_file_id(file_id)
.set_chunk_size(1024)
.set_contents(content)
.set_transaction_id(TransactionId.generate(env.client.operator_account_id))
.set_node_account_id(AccountId(0,0,3))
.freeze()
Comment on lines +328 to +335
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use a configured network node instead of hardcoding 0.0.3.

This only covers the manual-freeze path on environments that expose that exact node account. Pulling the node ID from env.client.network keeps the test deterministic across CI, localnet, and any future network config.

🧪 Proposed fix
+    node_account_id = next(node._account_id for node in env.client.network.nodes)
+
     tx = (
         FileAppendTransaction()
         .set_file_id(file_id)
         .set_chunk_size(1024)
         .set_contents(content)
         .set_transaction_id(TransactionId.generate(env.client.operator_account_id))
-        .set_node_account_id(AccountId(0,0,3))
+        .set_node_account_id(node_account_id)
         .freeze()
     )

)

tx.sign(env.client.operator_private_key)

receipt = tx.execute(env.client)

assert receipt.status == ResponseCode.SUCCESS

file_contents = FileContentsQuery().set_file_id(file_id).execute(env.client)
assert file_contents == bytes(content, "utf-8")
37 changes: 36 additions & 1 deletion tests/integration/topic_message_submit_transaction_e2e_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest

from hiero_sdk_python.account.account_id import AccountId
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_submit_transaction import (
Expand All @@ -16,6 +17,7 @@
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 hiero_sdk_python.transaction.transaction_id import TransactionId
from tests.integration.utils import env

def create_topic(client, admin_key=None, submit_key=None, custom_fees=None):
Expand Down Expand Up @@ -342,4 +344,37 @@ def test_integration_topic_message_submit_transaction_fails_if_required_chunk_gr
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)
delete_topic(env.client, topic_id)


@pytest.mark.integration
def test_topic_message_submit_transaction_can_submit_a_large_message_manual_freeze(env):
"""Test topic message submit transaction can submit large message with manual freeze."""
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_transaction_id(TransactionId.generate(env.client.operator_account_id))
.set_node_account_id(AccountId(0,0,3))
.freeze()
Comment on lines +363 to +369
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use a node from env.client.network instead of hard-coding 0.0.3.

Hard-coding a specific node account ID makes this manual-freeze test environment-dependent. If the configured integration network exposes a different node set, the test fails for the wrong reason instead of validating the manual-freeze path.

🛠️ Proposed fix
+    node_account_id = env.client.network.nodes[0]._account_id
     message_tx = (
         TopicMessageSubmitTransaction()
         .set_topic_id(topic_id)
         .set_message(message)
         .set_transaction_id(TransactionId.generate(env.client.operator_account_id))
-        .set_node_account_id(AccountId(0,0,3))
+        .set_node_account_id(node_account_id)
         .freeze()
     )
As per coding guidelines: `Integration tests validate real network behavior - they must be reliable and safe.`

)

message_tx.sign(env.client.operator_private_key)
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)
8 changes: 8 additions & 0 deletions tests/unit/mock_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import threading
from concurrent import futures
from contextlib import contextmanager
from hiero_sdk_python.channels import _Channel
from hiero_sdk_python.client.network import Network
from hiero_sdk_python.client.client import Client
from hiero_sdk_python.account.account_id import AccountId
Expand Down Expand Up @@ -177,6 +178,13 @@ def mock_hedera_servers(response_sequences):
node._close()

client = Client(network)

# Force non-tls channel
for node in client.network.nodes:
node._address._is_transport_security = lambda: False
node._set_verify_certificates(False)
node._close()

client.logger.set_level(LogLevel.DISABLED)
# Set the operator
key = PrivateKey.generate()
Expand Down
Loading
Loading