From 7c8c24e1c642b4982dd7da18fa0a22bd3ac6030a Mon Sep 17 00:00:00 2001 From: Jerry Date: Fri, 29 Aug 2025 19:23:48 -0700 Subject: [PATCH] Preserve container type of redeemers and plutus_data Currently, fields in a witness will be forced to be wrapped in NonEmptyOrderedSet when deserialized from a cbor due to post_init. This behavior will alter transaction id and also result in script_data_hash. This change is to ensure the original data format from a transaction will be preserved when deserialized. --- pycardano/txbuilder.py | 4 +++- pycardano/witness.py | 4 ++-- test/pycardano/test_serialization.py | 3 +++ test/pycardano/test_witness.py | 17 ++++++++++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index d99fb20d..dd83fbb4 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -1204,7 +1204,7 @@ def build_witness_set( f"Unsupported script type: {type(script)}" ) - return TransactionWitnessSet( + witness_set = TransactionWitnessSet( native_scripts=native_scripts if native_scripts else None, plutus_v1_script=plutus_v1_scripts if plutus_v1_scripts else None, plutus_v2_script=plutus_v2_scripts if plutus_v2_scripts else None, @@ -1212,6 +1212,8 @@ def build_witness_set( redeemer=self.redeemers() if self._redeemer_list else None, plutus_data=plutus_data if plutus_data else None, ) + witness_set.convert_to_latest_spec() + return witness_set def _ensure_no_input_exclusion_conflict(self): intersection = set(self.inputs).intersection(set(self.excluded_inputs)) diff --git a/pycardano/witness.py b/pycardano/witness.py index 54a94ce7..52690a3f 100644 --- a/pycardano/witness.py +++ b/pycardano/witness.py @@ -108,7 +108,7 @@ class TransactionWitnessSet(MapCBORSerializable): }, ) - plutus_data: Optional[Union[IndefiniteList, List[Any], NonEmptyOrderedSet[Any]]] = ( + plutus_data: Optional[Union[List[Any], IndefiniteList, NonEmptyOrderedSet[Any]]] = ( field( default=None, metadata={"optional": True, "key": 4}, @@ -140,7 +140,7 @@ class TransactionWitnessSet(MapCBORSerializable): }, ) - def __post_init__(self): + def convert_to_latest_spec(self): # Convert lists to NonEmptyOrderedSet for fields that should use NonEmptyOrderedSet if isinstance(self.vkey_witnesses, list): self.vkey_witnesses = NonEmptyOrderedSet(self.vkey_witnesses) diff --git a/test/pycardano/test_serialization.py b/test/pycardano/test_serialization.py index f4edfe65..65b9d1ef 100644 --- a/test/pycardano/test_serialization.py +++ b/test/pycardano/test_serialization.py @@ -761,6 +761,7 @@ def test_transaction_witness_set_with_ordered_sets(): # Test conversion from list to NonEmptyOrderedSet witness_set = TransactionWitnessSet(vkey_witnesses=[witness]) + witness_set.convert_to_latest_spec() assert isinstance(witness_set.vkey_witnesses, NonEmptyOrderedSet) assert witness in witness_set.vkey_witnesses @@ -772,11 +773,13 @@ def test_transaction_witness_set_with_ordered_sets(): # Test empty list conversion witness_set = TransactionWitnessSet(vkey_witnesses=[]) + witness_set.convert_to_latest_spec() with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): witness_set.to_validated_primitive() # Test None value witness_set = TransactionWitnessSet(vkey_witnesses=None) + witness_set.convert_to_latest_spec() primitive = witness_set.to_primitive() restored = TransactionWitnessSet.from_primitive(primitive) assert restored.vkey_witnesses is None diff --git a/test/pycardano/test_witness.py b/test/pycardano/test_witness.py index 65752c72..b4291034 100644 --- a/test/pycardano/test_witness.py +++ b/test/pycardano/test_witness.py @@ -1,6 +1,14 @@ +import json import tempfile -from pycardano import PaymentSigningKey, PaymentVerificationKey, VerificationKeyWitness +from pycardano import ( + PaymentSigningKey, + PaymentVerificationKey, + Transaction, + TransactionWitnessSet, + Unit, + VerificationKeyWitness, +) def test_witness_save_load(): @@ -17,3 +25,10 @@ def test_witness_save_load(): assert witness == loaded_witness assert witness != vk + + +def test_redeemer_decode(): + witness = TransactionWitnessSet(plutus_data=[Unit()]) + encoded = witness.to_cbor() + decoded = TransactionWitnessSet.from_cbor(encoded) + assert isinstance(decoded.plutus_data, list)