Skip to content

Commit c5cb82d

Browse files
committed
chore: refactor the to_bytes/from_bytes method
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
1 parent 3694682 commit c5cb82d

14 files changed

Lines changed: 225 additions & 79 deletions

src/hiero_sdk_python/account/account_create_transaction.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,35 @@ def _get_method(self, channel: _Channel) -> _Method:
371371
_Method: An instance of _Method containing the transaction and query functions.
372372
"""
373373
return _Method(transaction_func=channel.crypto.createAccount, query_func=None)
374+
375+
@classmethod
376+
def _from_protobuf(cls, transaction_body):
377+
"""Create account create transaction from protobuf message."""
378+
transaction = super()._from_protobuf(transaction_body)
379+
380+
if transaction_body.HasField("cryptoCreateAccount"):
381+
body = transaction_body.cryptoCreateAccount
382+
383+
if body.HasField("key"):
384+
transaction.key = Key.from_proto_key(body.key)
385+
386+
transaction.initial_balance = body.initialBalance
387+
transaction.receiver_signature_required = body.receiverSigRequired
388+
transaction.auto_renew_period = None
389+
390+
if body.HasField("autoRenewPeriod"):
391+
transaction.auto_renew_period = Duration._from_proto(body.autoRenewPeriod)
392+
393+
transaction.account_memo = body.memo if body.memo else None
394+
transaction.max_automatic_token_associations = body.max_automatic_token_associations
395+
transaction.alias = EvmAddress.from_bytes(body.alias) if body.alias else None
396+
transaction.decline_staking_reward = body.decline_reward
397+
staked_id = body.WhichOneof("staked_id")
398+
399+
if staked_id == "staked_account_id":
400+
transaction.staked_account_id = AccountId._from_proto(body.staked_account_id)
401+
402+
elif staked_id == "staked_node_id":
403+
transaction.staked_node_id = body.staked_node_id
404+
405+
return transaction

src/hiero_sdk_python/nodes/node_create_transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ def _get_method(self, channel: _Channel) -> _Method:
301301
return _Method(transaction_func=channel.address_book.createNode, query_func=None)
302302

303303
@classmethod
304-
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
305-
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
304+
def _from_protobuf(cls, transaction_body):
305+
transaction = super()._from_protobuf(transaction_body)
306306

307307
if transaction_body.HasField("nodeCreate"):
308308
pb = transaction_body.nodeCreate

src/hiero_sdk_python/nodes/node_update_transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,8 @@ def _get_method(self, channel: _Channel) -> _Method:
360360
return _Method(transaction_func=channel.address_book.updateNode, query_func=None)
361361

362362
@classmethod
363-
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
364-
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
363+
def _from_protobuf(cls, transaction_body):
364+
transaction = super()._from_protobuf(transaction_body)
365365

366366
if transaction_body.HasField("nodeUpdate"):
367367
pb = transaction_body.nodeUpdate

src/hiero_sdk_python/nodes/registered_node_create_transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def _get_method(self, channel: _Channel) -> _Method:
100100
return _Method(transaction_func=channel.address_book.createRegisteredNode, query_func=None)
101101

102102
@classmethod
103-
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
104-
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
103+
def _from_protobuf(cls, transaction_body):
104+
transaction = super()._from_protobuf(transaction_body)
105105

106106
if transaction_body.HasField("registeredNodeCreate"):
107107
pb = transaction_body.registeredNodeCreate

src/hiero_sdk_python/nodes/registered_node_delete_transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ def _get_method(self, channel: _Channel) -> _Method:
6262
return _Method(transaction_func=channel.address_book.deleteRegisteredNode, query_func=None)
6363

6464
@classmethod
65-
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
66-
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
65+
def _from_protobuf(cls, transaction_body):
66+
transaction = super()._from_protobuf(transaction_body)
6767

6868
# Extract registered node fields if the body contains a registeredNodeDelete message
6969
if transaction_body.HasField("registeredNodeDelete"):

src/hiero_sdk_python/nodes/registered_node_update_transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ def _get_method(self, channel: _Channel) -> _Method:
122122
return _Method(transaction_func=channel.address_book.updateRegisteredNode, query_func=None)
123123

124124
@classmethod
125-
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
126-
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
125+
def _from_protobuf(cls, transaction_body):
126+
transaction = super()._from_protobuf(transaction_body)
127127

128128
if transaction_body.HasField("registeredNodeUpdate"):
129129
pb = transaction_body.registeredNodeUpdate

src/hiero_sdk_python/query/fee_estimate_query.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def _post(self, url: str, payload: bytes) -> dict:
191191
raise RuntimeError("Unreachable")
192192

193193
def _execute_single(self, url: str, mode: FeeEstimateMode) -> FeeEstimateResponse:
194-
data = self._post(url, self._transaction.to_bytes())
194+
data = self._post(url, self._transaction._to_proto().SerializeToString())
195195
return self._to_response(data, mode)
196196

197197
def _execute_chunked(self, client, url: str, mode: FeeEstimateMode) -> FeeEstimateResponse:
@@ -224,7 +224,7 @@ def _execute_chunked(self, client, url: str, mode: FeeEstimateMode) -> FeeEstima
224224

225225
self._transaction.freeze_with(client)
226226

227-
tx_bytes = self._transaction.to_bytes()
227+
tx_bytes = self._transaction._to_proto().SerializeToString()
228228
data = self._post(url, tx_bytes)
229229
response = self._to_response(data, mode)
230230

src/hiero_sdk_python/query/query.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,11 @@ def _make_request_header(self) -> query_header_pb2.QueryHeader:
179179
header.responseType = query_header_pb2.ResponseType.COST_ANSWER
180180
return header
181181

182-
if (
183-
self.operator is not None
184-
and self.node_account_ids[self._node_account_ids_index] is not None
185-
and self.payment_amount is not None
186-
):
182+
if self.operator is not None and self._node_account_id is not None and self.payment_amount is not None:
187183
payment_tx = self._build_query_payment_transaction(
188184
payer_account_id=self.operator.account_id,
189185
payer_private_key=self.operator.private_key,
190-
node_account_id=self.node_account_ids[self._node_account_ids_index],
186+
node_account_id=self._node_account_id,
191187
amount=self.payment_amount,
192188
)
193189
header.payment.CopyFrom(payment_tx)

src/hiero_sdk_python/transaction/batch_transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def _verify_inner_transaction(self, transaction: Transaction) -> None:
9595
raise ValueError("Batch key needs to be set")
9696

9797
@classmethod
98-
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map) -> BatchTransaction:
98+
def _from_protobuf(cls, transaction_body) -> BatchTransaction:
9999
"""
100100
Creates a BatchTransaction instance from protobuf components.
101101
@@ -107,7 +107,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map) -> BatchTr
107107
Returns:
108108
BatchTransaction: A new transaction instance with all fields restored
109109
"""
110-
transaction = super()._from_protobuf(transaction_body, body_bytes, sig_map)
110+
transaction = super()._from_protobuf(transaction_body)
111111

112112
if transaction_body.HasField("atomic_batch"):
113113
atomic_batch = transaction_body.atomic_batch

src/hiero_sdk_python/transaction/transaction.py

Lines changed: 122 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from hiero_sdk_python.crypto.key import Key
99
from hiero_sdk_python.exceptions import PrecheckError
1010
from hiero_sdk_python.executable import _Executable, _ExecutionState
11+
from hiero_sdk_python.hapi.sdk.transaction_list_pb2 import TransactionList
1112
from hiero_sdk_python.hapi.services import basic_types_pb2, transaction_contents_pb2, transaction_pb2
1213
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody
1314
from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto
@@ -655,13 +656,41 @@ def to_bytes(self) -> bytes:
655656
Raises:
656657
Exception: If the transaction has not been frozen yet.
657658
"""
658-
self._require_frozen()
659+
transaction_list = TransactionList()
659660

660-
# Get the transaction protobuf
661-
transaction_proto = self._to_proto()
661+
if len(self.node_account_ids) == 0:
662+
transaction_body = self.build_transaction_body()
662663

663-
# Serialize to bytes
664-
return transaction_proto.SerializeToString()
664+
if self.transaction_id is not None:
665+
transaction_body.transactionID.CopyFrom(self.transaction_id._to_proto())
666+
667+
transaction = transaction_contents_pb2.SignedTransaction(bodyBytes=transaction_body.SerializeToString())
668+
669+
transaction_list.transaction_list.append(
670+
transaction_pb2.Transaction(signedTransactionBytes=transaction.SerializeToString())
671+
)
672+
673+
else:
674+
transaction_body = self.build_transaction_body()
675+
676+
if self.transaction_id is not None:
677+
transaction_body.transactionID.CopyFrom(self.transaction_id._to_proto())
678+
679+
for node_account_id in self.node_account_ids:
680+
transaction_body.nodeAccountID.CopyFrom(node_account_id._to_proto())
681+
transaction = transaction_contents_pb2.SignedTransaction(bodyBytes=transaction_body.SerializeToString())
682+
683+
if self._transaction_body_bytes:
684+
if self._signature_map and self._signature_map[transaction_body.SerializeToString()]:
685+
transaction.sigMap.CopyFrom(self._signature_map[transaction_body.SerializeToString()])
686+
else:
687+
transaction.sigMap.CopyFrom(basic_types_pb2.SignatureMap(sigPair=[]))
688+
689+
transaction_list.transaction_list.append(
690+
transaction_pb2.Transaction(signedTransactionBytes=transaction.SerializeToString())
691+
)
692+
693+
return transaction_list.SerializeToString()
665694

666695
@staticmethod
667696
def from_bytes(transaction_bytes: bytes):
@@ -726,42 +755,116 @@ def from_bytes(transaction_bytes: bytes):
726755
"""
727756
if not isinstance(transaction_bytes, bytes):
728757
raise ValueError("transaction_bytes must be bytes")
729-
730758
if len(transaction_bytes) == 0:
731759
raise ValueError("transaction_bytes cannot be empty")
732760

733761
try:
734-
transaction_proto = transaction_pb2.Transaction()
735-
transaction_proto.ParseFromString(transaction_bytes)
736-
except Exception as e:
737-
raise ValueError(f"Failed to parse transaction bytes: {e}") from e
762+
return Transaction._process_transaction_list_bytes(transaction_bytes)
763+
except Exception:
764+
try:
765+
return Transaction._process_single_transaction_bytes(transaction_bytes)
766+
except Exception as e:
767+
raise ValueError(f"Failed to parse transaction_bytes {e}") from e
738768

769+
@staticmethod
770+
def _process_base_transaction_bytes(transaction_proto: transaction_pb2.Transaction):
739771
try:
740772
signed_transaction = transaction_contents_pb2.SignedTransaction()
741773
signed_transaction.ParseFromString(transaction_proto.signedTransactionBytes)
742774
except Exception as e:
743775
raise ValueError(f"Failed to parse signed transaction: {e}") from e
744-
745776
try:
746777
transaction_body = transaction_pb2.TransactionBody()
747778
transaction_body.ParseFromString(signed_transaction.bodyBytes)
748779
except Exception as e:
749780
raise ValueError(f"Failed to parse transaction body: {e}") from e
750-
751781
transaction_type = transaction_body.WhichOneof("data")
752-
753782
if transaction_type is None:
754783
raise ValueError("Transaction body does not contain any transaction data")
755-
756784
transaction_class = Transaction._get_transaction_class(transaction_type)
757-
758785
if transaction_class is None:
759786
raise ValueError(f"Unknown transaction type: {transaction_type}")
760787

761-
return transaction_class._from_protobuf(
762-
transaction_body, signed_transaction.bodyBytes, signed_transaction.sigMap
788+
return transaction_class, transaction_body, signed_transaction
789+
790+
# Deprecated, for backward compatiblity only
791+
@staticmethod
792+
def _process_single_transaction_bytes(transaction_bytes):
793+
try:
794+
transaction_proto = transaction_pb2.Transaction()
795+
transaction_proto.ParseFromString(transaction_bytes)
796+
except Exception as e:
797+
raise ValueError(f"Failed to parse transaction bytes: {e}") from e
798+
799+
transaction_class, transaction_body, signed_transaction = Transaction._process_base_transaction_bytes(
800+
transaction_proto
763801
)
764802

803+
restore_transaction: Transaction = transaction_class._from_protobuf(transaction_body)
804+
805+
if restore_transaction._node_account_id is not None:
806+
restore_transaction.set_node_account_id(restore_transaction._node_account_id)
807+
808+
if signed_transaction.HasField("sigMap"):
809+
if signed_transaction.sigMap.sigPair:
810+
restore_transaction._signature_map[signed_transaction.bodyBytes] = signed_transaction.sigMap
811+
812+
restore_transaction._transaction_body_bytes[restore_transaction._node_account_id] = (
813+
signed_transaction.bodyBytes
814+
)
815+
816+
return restore_transaction
817+
818+
@staticmethod
819+
def _process_transaction_list_bytes(transaction_bytes: bytes):
820+
transaction_list = TransactionList()
821+
transaction_list.ParseFromString(transaction_bytes)
822+
823+
if not transaction_list.transaction_list:
824+
raise ValueError("TransactionList contains no transactions")
825+
826+
restored_transaction: Transaction = None
827+
828+
for transaction_proto in transaction_list.transaction_list:
829+
transaction_class, transaction_body, signed_transaction = Transaction._process_base_transaction_bytes(
830+
transaction_proto
831+
)
832+
833+
tmp_transaction: Transaction = transaction_class._from_protobuf(transaction_body)
834+
835+
node_id = tmp_transaction._node_account_id
836+
837+
if restored_transaction is None:
838+
restored_transaction = tmp_transaction
839+
840+
if node_id is not None:
841+
restored_transaction.node_account_ids = [node_id]
842+
843+
if tmp_transaction._signature_map:
844+
for key, value in tmp_transaction._signature_map.items():
845+
restored_transaction._signature_map[key] = value
846+
restored_transaction._transaction_body_bytes[node_id] = key
847+
848+
else:
849+
if node_id is not None and node_id not in restored_transaction.node_account_ids:
850+
restored_transaction.node_account_ids.append(node_id)
851+
852+
if tmp_transaction._signature_map:
853+
for key, value in tmp_transaction._signature_map.items():
854+
restored_transaction._signature_map[key] = value
855+
restored_transaction._transaction_body_bytes[node_id] = key
856+
857+
if signed_transaction.HasField("sigMap"):
858+
if signed_transaction.sigMap.sigPair:
859+
restored_transaction._signature_map[signed_transaction.bodyBytes] = signed_transaction.sigMap
860+
861+
restored_transaction._transaction_body_bytes[node_id] = signed_transaction.bodyBytes
862+
863+
if restored_transaction is None:
864+
raise ValueError("No valid transactions could be parsed from the byte stream")
865+
866+
return restored_transaction
867+
765868
@staticmethod
766869
def _get_transaction_class(transaction_type: str):
767870
"""
@@ -843,7 +946,7 @@ def _get_transaction_class(transaction_type: str):
843946
raise ValueError(f"Failed to import transaction class for type '{transaction_type}': {e}") from e
844947

845948
@classmethod
846-
def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
949+
def _from_protobuf(cls, transaction_body):
847950
"""
848951
Creates a transaction instance from protobuf components.
849952
@@ -864,7 +967,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
864967
transaction.transaction_id = TransactionId._from_proto(transaction_body.transactionID)
865968

866969
if transaction_body.HasField("nodeAccountID"):
867-
transaction.node_account_id = AccountId._from_proto(transaction_body.nodeAccountID)
970+
transaction._node_account_id = AccountId._from_proto(transaction_body.nodeAccountID)
868971

869972
transaction.transaction_fee = transaction_body.transactionFee
870973
transaction.transaction_valid_duration = transaction_body.transactionValidDuration.seconds
@@ -879,14 +982,6 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
879982
CustomFeeLimit._from_proto(fee) for fee in transaction_body.max_custom_fees
880983
]
881984

882-
if transaction.node_account_id:
883-
# restore for the original frozen node
884-
transaction.set_node_account_id(transaction.node_account_id)
885-
transaction._transaction_body_bytes[transaction.node_account_id] = body_bytes
886-
887-
if sig_map and sig_map.sigPair:
888-
transaction._signature_map[body_bytes] = sig_map
889-
890985
return transaction
891986

892987
def set_batch_key(self, key: Key):

0 commit comments

Comments
 (0)