1717from typing import TYPE_CHECKING , Any , Literal , overload
1818
1919from hiero_sdk_python .file .file_id import FileId
20- from hiero_sdk_python .hapi .services import file_append_pb2 , timestamp_pb2
20+ from hiero_sdk_python .hapi .services import file_append_pb2
2121from hiero_sdk_python .hapi .services .schedulable_transaction_body_pb2 import (
2222 SchedulableTransactionBody ,
2323)
2424from hiero_sdk_python .hbar import Hbar
2525from hiero_sdk_python .transaction .transaction import Transaction
26- from hiero_sdk_python .transaction .transaction_id import TransactionId
2726
2827
2928# Use TYPE_CHECKING to avoid circular import errors
3029if TYPE_CHECKING :
3130 from hiero_sdk_python .channels import _Channel
3231 from hiero_sdk_python .client .client import Client
33- from hiero_sdk_python .crypto .private_key import PrivateKey
3432 from hiero_sdk_python .executable import _Method
3533 from hiero_sdk_python .transaction .transaction import TransactionReceipt
3634 from hiero_sdk_python .transaction .transaction_response import TransactionResponse
@@ -76,10 +74,8 @@ def __init__(
7674 self ._default_transaction_fee = Hbar (5 ).to_tinybars ()
7775
7876 # Internal tracking for chunking
79- self ._current_chunk_index : int = 0
77+ self ._current_chunk_index : int | None = None
8078 self ._total_chunks : int = self ._calculate_total_chunks ()
81- self ._transaction_ids : list [TransactionId ] = []
82- self ._signing_keys : list [PrivateKey ] = [] # Use string annotation to avoid import issues
8379
8480 def _encode_contents (self , contents : str | bytes | None ) -> bytes | None :
8581 """
@@ -190,15 +186,18 @@ def _build_proto_body(self) -> file_append_pb2.FileAppendTransactionBody:
190186 if self .file_id is None :
191187 raise ValueError ("Missing required FileID" )
192188
193- if self .contents is None :
194- chunk_contents = b""
195- else :
189+ contents = self . contents if self .contents is not None else b""
190+
191+ if self . _current_chunk_index is not None :
196192 start_index = self ._current_chunk_index * self .chunk_size
197- end_index = min (start_index + self .chunk_size , len (self .contents ))
198- chunk_contents = self .contents [start_index :end_index ]
193+ end_index = min (start_index + self .chunk_size , len (contents ))
199194
195+ chunk_contents = contents [start_index :end_index ]
196+ return file_append_pb2 .FileAppendTransactionBody (
197+ fileID = self .file_id ._to_proto () if self .file_id else None , contents = chunk_contents
198+ )
200199 return file_append_pb2 .FileAppendTransactionBody (
201- fileID = self .file_id ._to_proto () if self .file_id else None , contents = chunk_contents
200+ fileID = self .file_id ._to_proto () if self .file_id else None , contents = contents
202201 )
203202
204203 def build_transaction_body (self ) -> Any :
@@ -287,28 +286,27 @@ def freeze_with(self, client: Client) -> FileAppendTransaction:
287286 return self
288287
289288 self ._resolve_transaction_id (client )
289+ self ._resolve_node_ids (client )
290+
291+ required_chunks = self .get_required_chunks ()
292+ self ._generate_transaction_ids (self ._transaction_ids [0 ], required_chunks )
290293
291- # Generate transaction IDs for all chunks
292- if not self ._transaction_ids :
293- base_timestamp = self . transaction_id . valid_start
294+ for chunk in range ( required_chunks ):
295+ self ._current_chunk_index = chunk
296+ self . _current_transaction_id_index = chunk
294297
295- for i in range (self .get_required_chunks ()):
296- if i == 0 :
297- # First chunk uses the original transaction ID
298- chunk_transaction_id = self .transaction_id
299- else :
300- # Subsequent chunks get incremented timestamps
301- # Add i nanoseconds to space out chunks
302- next_nanos = base_timestamp .nanos + i
298+ node_bytes = {}
303299
304- chunk_valid_start = timestamp_pb2 .Timestamp (
305- seconds = base_timestamp .seconds + next_nanos // 1_000_000_000 , nanos = next_nanos % 1_000_000_000
306- )
307- chunk_transaction_id = TransactionId (
308- account_id = self .transaction_id .account_id , valid_start = chunk_valid_start
309- )
300+ for node_account_id in self .node_account_ids :
301+ self ._node_account_id = node_account_id
310302
311- self ._transaction_ids .append (chunk_transaction_id )
303+ transaction_body = self .build_transaction_body ()
304+ transaction_body .transactionID .CopyFrom (self ._transaction_ids [chunk ]._to_proto ())
305+ transaction_body .nodeAccountID .CopyFrom (node_account_id ._to_proto ())
306+
307+ node_bytes [node_account_id ] = transaction_body .SerializeToString ()
308+
309+ self ._transaction_body_bytes [self ._transaction_ids [chunk ]] = node_bytes
312310
313311 return super ().freeze_with (client )
314312
@@ -399,57 +397,23 @@ def execute_all(
399397 """
400398 self ._validate_chunking ()
401399
402- if self .get_required_chunks () == 1 :
403- # Single chunk transaction
400+ if not self ._transaction_body_bytes :
401+ self .freeze_with (client )
402+
403+ # Single chunk transaction
404+ if len (self ._transaction_ids ) == 1 :
404405 return [super ().execute (client , timeout , wait_for_receipt , validate_status )]
405406
406407 # Multi-chunk transaction - execute all chunks
407408 responses = []
408-
409- for chunk_index in range (self .get_required_chunks ()):
410- self ._current_chunk_index = chunk_index
411-
412- # Set the transaction ID for this chunk
413- if self ._transaction_ids and chunk_index < len (self ._transaction_ids ):
414- self .transaction_id = self ._transaction_ids [chunk_index ]
415- # Clear the frozen state to allow rebuilding with new transaction ID
416- self ._transaction_body_bytes .clear ()
417- self ._signature_map .clear ()
418-
419- # Freeze the transaction for this chunk if not already frozen
420- self .freeze_with (client )
421-
422- # Sign with all stored signing keys for this chunk
423- for signing_key in self ._signing_keys :
424- # Call parent sign directly to avoid modifying _signing_keys
425- super ().sign (signing_key )
426-
427- # Execute the chunk
409+ self ._current_transaction_id_index = 0
410+ for index in range (len (self ._transaction_ids )):
411+ self ._current_transaction_id_index = index
428412 response = super ().execute (client , timeout , wait_for_receipt , validate_status )
429413 responses .append (response )
430414
431415 return responses
432416
433- def sign (self , private_key : PrivateKey ) -> FileAppendTransaction :
434- """
435- Signs the transaction using the provided private key.
436-
437- For multi-chunk transactions, this stores the signing key for later use.
438-
439- Args:
440- private_key (PrivateKey): The private key to sign the transaction with.
441-
442- Returns:
443- FileAppendTransaction: The current transaction instance for method chaining.
444- """
445- # Store the signing key for multi-chunk transactions (avoid duplicates)
446- if private_key not in self ._signing_keys :
447- self ._signing_keys .append (private_key )
448-
449- # Call the parent sign method for the current transaction
450- super ().sign (private_key )
451- return self
452-
453417 @property
454418 def body_size_all_chunks (self ) -> list [int ]:
455419 """Returns an array of body sizes for transactions with multiple chunks."""
@@ -470,3 +434,10 @@ def body_size_all_chunks(self) -> list[int]:
470434 self .transaction_id = original_transaction_id
471435
472436 return sizes
437+
438+ @classmethod
439+ def _from_protobuf (cls , transaction_body ):
440+ transaction = super ()._from_protobuf (transaction_body )
441+ if transaction_body .HasField ("fileAppend" ):
442+ transaction ._from_proto (transaction_body .fileAppend )
443+ return transaction
0 commit comments