Browse Source

psbt: cleaner API for serialize* methods

master
SomberNight 6 years ago
parent
commit
26a5f212cb
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 36
      electrum/tests/test_transaction.py
  2. 25
      electrum/transaction.py

36
electrum/tests/test_transaction.py

@ -1,5 +1,5 @@
from electrum import transaction from electrum import transaction
from electrum.transaction import convert_tx_str_to_hex, tx_from_any from electrum.transaction import convert_tx_str_to_hex, tx_from_any, Transaction, PartialTransaction
from electrum.bitcoin import TYPE_ADDRESS from electrum.bitcoin import TYPE_ADDRESS
from electrum.util import bh2u, bfh from electrum.util import bh2u, bfh
from electrum import keystore from electrum import keystore
@ -161,6 +161,40 @@ class TestTransaction(ElectrumTestCase):
self.assertEqual(None, addr_from_script('200289e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751cac')) self.assertEqual(None, addr_from_script('200289e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751cac'))
self.assertEqual(None, addr_from_script('210589e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751c8bac')) self.assertEqual(None, addr_from_script('210589e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751c8bac'))
def test_tx_serialize_methods_for_psbt(self):
raw_hex = "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"
raw_base64 = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA"
partial_tx = tx_from_any(raw_hex)
self.assertEqual(PartialTransaction, type(partial_tx))
self.assertEqual(raw_base64,
partial_tx.serialize())
self.assertEqual(raw_hex,
partial_tx.serialize_as_bytes().hex())
self.assertEqual(raw_base64,
partial_tx._serialize_as_base64())
def test_tx_serialize_methods_for_network_tx(self):
raw_hex = "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000"
tx = tx_from_any(raw_hex)
self.assertEqual(Transaction, type(tx))
self.assertEqual(raw_hex,
tx.serialize())
self.assertEqual(raw_hex,
tx.serialize_as_bytes().hex())
def test_tx_serialize_methods_for_psbt_that_is_ready_to_be_finalized(self):
raw_hex_psbt = "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"
raw_hex_network_tx = "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000"
partial_tx = tx_from_any(raw_hex_psbt)
self.assertEqual(PartialTransaction, type(partial_tx))
self.assertEqual(raw_hex_network_tx,
partial_tx.serialize())
self.assertEqual(raw_hex_network_tx,
partial_tx.serialize_as_bytes().hex())
# note: the diff between the following, and raw_hex_psbt, is that we added
# an extra FINAL_SCRIPTWITNESS field in finalize_psbt()
self.assertEqual("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae010801000001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",
partial_tx.serialize_as_bytes(force_psbt=True).hex())
##### #####

25
electrum/transaction.py

@ -54,6 +54,7 @@ if TYPE_CHECKING:
_logger = get_logger(__name__) _logger = get_logger(__name__)
DEBUG_PSBT_PARSING = False
class SerializationError(Exception): class SerializationError(Exception):
@ -1075,7 +1076,7 @@ class PartialTxInput(TxInput, PSBTSection):
kt = PSBTInputType(kt) kt = PSBTInputType(kt)
except ValueError: except ValueError:
pass # unknown type pass # unknown type
#print(f"{repr(kt)} {key.hex()} {val.hex()}") if DEBUG_PSBT_PARSING: print(f"{repr(kt)} {key.hex()} {val.hex()}")
if kt == PSBTInputType.NON_WITNESS_UTXO: if kt == PSBTInputType.NON_WITNESS_UTXO:
if self.utxo is not None: if self.utxo is not None:
raise SerializationError(f"duplicate key: {repr(kt)}") raise SerializationError(f"duplicate key: {repr(kt)}")
@ -1150,7 +1151,7 @@ class PartialTxInput(TxInput, PSBTSection):
wr(PSBTInputType.REDEEM_SCRIPT, self.redeem_script) wr(PSBTInputType.REDEEM_SCRIPT, self.redeem_script)
if self.witness_script is not None: if self.witness_script is not None:
wr(PSBTInputType.WITNESS_SCRIPT, self.witness_script) wr(PSBTInputType.WITNESS_SCRIPT, self.witness_script)
for k in self.bip32_paths: for k in sorted(self.bip32_paths):
packed_path = pack_bip32_root_fingerprint_and_int_path(*self.bip32_paths[k]) packed_path = pack_bip32_root_fingerprint_and_int_path(*self.bip32_paths[k])
wr(PSBTInputType.BIP32_DERIVATION, packed_path, k) wr(PSBTInputType.BIP32_DERIVATION, packed_path, k)
if self.script_sig is not None: if self.script_sig is not None:
@ -1330,7 +1331,7 @@ class PartialTxOutput(TxOutput, PSBTSection):
kt = PSBTOutputType(kt) kt = PSBTOutputType(kt)
except ValueError: except ValueError:
pass # unknown type pass # unknown type
#print(f"{repr(kt)} {key.hex()} {val.hex()}") if DEBUG_PSBT_PARSING: print(f"{repr(kt)} {key.hex()} {val.hex()}")
if kt == PSBTOutputType.REDEEM_SCRIPT: if kt == PSBTOutputType.REDEEM_SCRIPT:
if self.redeem_script is not None: if self.redeem_script is not None:
raise SerializationError(f"duplicate key: {repr(kt)}") raise SerializationError(f"duplicate key: {repr(kt)}")
@ -1359,7 +1360,7 @@ class PartialTxOutput(TxOutput, PSBTSection):
wr(PSBTOutputType.REDEEM_SCRIPT, self.redeem_script) wr(PSBTOutputType.REDEEM_SCRIPT, self.redeem_script)
if self.witness_script is not None: if self.witness_script is not None:
wr(PSBTOutputType.WITNESS_SCRIPT, self.witness_script) wr(PSBTOutputType.WITNESS_SCRIPT, self.witness_script)
for k in self.bip32_paths: for k in sorted(self.bip32_paths):
packed_path = pack_bip32_root_fingerprint_and_int_path(*self.bip32_paths[k]) packed_path = pack_bip32_root_fingerprint_and_int_path(*self.bip32_paths[k])
wr(PSBTOutputType.BIP32_DERIVATION, packed_path, k) wr(PSBTOutputType.BIP32_DERIVATION, packed_path, k)
for full_key, val in sorted(self._unknown.items()): for full_key, val in sorted(self._unknown.items()):
@ -1452,7 +1453,7 @@ class PartialTransaction(Transaction):
kt = PSBTGlobalType(kt) kt = PSBTGlobalType(kt)
except ValueError: except ValueError:
pass # unknown type pass # unknown type
#print(f"{repr(kt)} {key.hex()} {val.hex()}") if DEBUG_PSBT_PARSING: print(f"{repr(kt)} {key.hex()} {val.hex()}")
if kt == PSBTGlobalType.UNSIGNED_TX: if kt == PSBTGlobalType.UNSIGNED_TX:
pass # already handled during "first" parsing pass pass # already handled during "first" parsing pass
elif kt == PSBTGlobalType.XPUB: elif kt == PSBTGlobalType.XPUB:
@ -1477,9 +1478,11 @@ class PartialTransaction(Transaction):
try: try:
# inputs sections # inputs sections
for txin in tx.inputs(): for txin in tx.inputs():
if DEBUG_PSBT_PARSING: print("-> new input starts")
txin._populate_psbt_fields_from_fd(fd) txin._populate_psbt_fields_from_fd(fd)
# outputs sections # outputs sections
for txout in tx.outputs(): for txout in tx.outputs():
if DEBUG_PSBT_PARSING: print("-> new output starts")
txout._populate_psbt_fields_from_fd(fd) txout._populate_psbt_fields_from_fd(fd)
except UnexpectedEndOfStream: except UnexpectedEndOfStream:
raise UnexpectedEndOfStream('Unexpected end of stream. Num input and output maps provided does not match unsigned tx.') from None raise UnexpectedEndOfStream('Unexpected end of stream. Num input and output maps provided does not match unsigned tx.') from None
@ -1680,17 +1683,23 @@ class PartialTransaction(Transaction):
return s, r return s, r
def serialize(self) -> str: def serialize(self) -> str:
"""Returns PSBT as base64 text, or raw hex of network tx (if complete)."""
self.finalize_psbt() self.finalize_psbt()
if self.is_complete(): if self.is_complete():
return Transaction.serialize(self) return Transaction.serialize(self)
return self.serialize_as_base64() return self._serialize_as_base64()
def serialize_as_bytes(self) -> bytes: def serialize_as_bytes(self, *, force_psbt: bool = False) -> bytes:
"""Returns PSBT as raw bytes, or raw bytes of network tx (if complete)."""
self.finalize_psbt()
if force_psbt or not self.is_complete():
with io.BytesIO() as fd: with io.BytesIO() as fd:
self._serialize_psbt(fd) self._serialize_psbt(fd)
return fd.getvalue() return fd.getvalue()
else:
return Transaction.serialize_as_bytes(self)
def serialize_as_base64(self) -> str: def _serialize_as_base64(self) -> str:
raw_bytes = self.serialize_as_bytes() raw_bytes = self.serialize_as_bytes()
return base64.b64encode(raw_bytes).decode('ascii') return base64.b64encode(raw_bytes).decode('ascii')

Loading…
Cancel
Save