diff --git a/electrumx/lib/tx_dash.py b/electrumx/lib/tx_dash.py index 774fa7d..31dee65 100644 --- a/electrumx/lib/tx_dash.py +++ b/electrumx/lib/tx_dash.py @@ -29,6 +29,8 @@ from collections import namedtuple from electrumx.lib.tx import Deserializer +from electrumx.lib.util import (pack_le_uint16, pack_le_int32, pack_le_uint32, + pack_le_int64, pack_varint, pack_varbytes) # https://github.com/dashpay/dips/blob/master/dip-0002.md @@ -36,6 +38,33 @@ class DashTx(namedtuple("DashTx", "version inputs outputs locktime " "tx_type extra_payload")): '''Class representing a Dash transaction''' + def serialize(self): + nLocktime = pack_le_uint32(self.locktime) + txins = (pack_varint(len(self.inputs)) + + b''.join(tx_in.serialize() for tx_in in self.inputs)) + txouts = (pack_varint(len(self.outputs)) + + b''.join(tx_out.serialize() for tx_out in self.outputs)) + + if self.tx_type: + uVersion = pack_le_uint16(self.version) + uTxType = pack_le_uint16(self.tx_type) + vExtra = self._serialize_extra_payload() + return uVersion + uTxType + txins + txouts + nLocktime + vExtra + else: + nVersion = pack_le_int32(self.version) + return nVersion + txins + txouts + nLocktime + + def _serialize_extra_payload(self): + extra = self.extra_payload + spec_tx_class = DeserializerDash.SPEC_TX_HANDLERS.get(self.tx_type) + if not spec_tx_class: + assert isinstance(extra, (bytes, bytearray)) + return pack_varbytes(extra) + + if not isinstance(extra, spec_tx_class): + raise ValueError('Dash tx_type does not conform with extra' + ' payload class: %s, %s' % (self.tx_type, extra)) + return pack_varbytes(extra.serialize()) # https://github.com/dashpay/dips/blob/master/dip-0002-special-transactions.md @@ -45,61 +74,280 @@ class DashProRegTx(namedtuple("DashProRegTx", "KeyIdVoting operatorReward scriptPayout " "inputsHash payloadSig")): '''Class representing DIP3 ProRegTx''' + def serialize(self): + assert (len(self.ipAddress) == 16 + and len(self.KeyIdOwner) == 20 + and len(self.PubKeyOperator) == 48 + and len(self.KeyIdVoting) == 20 + and len(self.inputsHash) == 32) + return ( + pack_le_uint16(self.version) + # version + pack_le_uint16(self.type) + # type + pack_le_uint16(self.mode) + # mode + self.collateralOutpoint.serialize() + # collateralOutpoint + self.ipAddress + # ipAddress + pack_le_uint16(self.port) + # port + self.KeyIdOwner + # KeyIdOwner + self.PubKeyOperator + # PubKeyOperator + self.KeyIdVoting + # KeyIdVoting + pack_le_uint16(self.operatorReward) + # operatorReward + pack_varbytes(self.scriptPayout) + # scriptPayout + self.inputsHash + # inputsHash + pack_varbytes(self.payloadSig) # payloadSig + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashProRegTx( + deser._read_le_uint16(), # version + deser._read_le_uint16(), # type + deser._read_le_uint16(), # mode + deser._read_outpoint(), # collateralOutpoint + deser._read_nbytes(16), # ipAddress + deser._read_le_uint16(), # port + deser._read_nbytes(20), # KeyIdOwner + deser._read_nbytes(48), # PubKeyOperator + deser._read_nbytes(20), # KeyIdVoting + deser._read_le_uint16(), # operatorReward + deser._read_varbytes(), # scriptPayout + deser._read_nbytes(32), # inputsHash + deser._read_varbytes() # payloadSig + ) class DashProUpServTx(namedtuple("DashProUpServTx", - "version proTXHash ipAddress port " + "version proTxHash ipAddress port " "scriptOperatorPayout inputsHash " "payloadSig")): '''Class representing DIP3 ProUpServTx''' + def serialize(self): + assert (len(self.proTxHash) == 32 + and len(self.ipAddress) == 16 + and len(self.inputsHash) == 32 + and len(self.payloadSig) == 96) + return ( + pack_le_uint16(self.version) + # version + self.proTxHash + # proTxHash + self.ipAddress + # ipAddress + pack_le_uint16(self.port) + # port + pack_varbytes(self.scriptOperatorPayout) + # scriptOperatorPayout + self.inputsHash + # inputsHash + self.payloadSig # payloadSig + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashProUpServTx( + deser._read_le_uint16(), # version + deser._read_nbytes(32), # proTxHash + deser._read_nbytes(16), # ipAddress + deser._read_le_uint16(), # port + deser._read_varbytes(), # scriptOperatorPayout + deser._read_nbytes(32), # inputsHash + deser._read_nbytes(96) # payloadSig + ) class DashProUpRegTx(namedtuple("DashProUpRegTx", - "version proTXHash mode PubKeyOperator " + "version proTxHash mode PubKeyOperator " "KeyIdVoting scriptPayout inputsHash " "payloadSig")): '''Class representing DIP3 ProUpRegTx''' + def serialize(self): + assert (len(self.proTxHash) == 32 + and len(self.PubKeyOperator) == 48 + and len(self.KeyIdVoting) == 20 + and len(self.inputsHash) == 32) + return ( + pack_le_uint16(self.version) + # version + self.proTxHash + # proTxHash + pack_le_uint16(self.mode) + # mode + self.PubKeyOperator + # PubKeyOperator + self.KeyIdVoting + # KeyIdVoting + pack_varbytes(self.scriptPayout) + # scriptPayout + self.inputsHash + # inputsHash + pack_varbytes(self.payloadSig) # payloadSig + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashProUpRegTx( + deser._read_le_uint16(), # version + deser._read_nbytes(32), # proTxHash + deser._read_le_uint16(), # mode + deser._read_nbytes(48), # PubKeyOperator + deser._read_nbytes(20), # KeyIdVoting + deser._read_varbytes(), # scriptPayout + deser._read_nbytes(32), # inputsHash + deser._read_varbytes() # payloadSig + ) class DashProUpRevTx(namedtuple("DashProUpRevTx", - "version proTXHash reason " + "version proTxHash reason " "inputsHash payloadSig")): '''Class representing DIP3 ProUpRevTx''' + def serialize(self): + assert (len(self.proTxHash) == 32 + and len(self.inputsHash) == 32 + and len(self.payloadSig) == 96) + return ( + pack_le_uint16(self.version) + # version + self.proTxHash + # proTxHash + pack_le_uint16(self.reason) + # reason + self.inputsHash + # inputsHash + self.payloadSig # payloadSig + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashProUpRevTx( + deser._read_le_uint16(), # version + deser._read_nbytes(32), # proTxHash + deser._read_le_uint16(), # reason + deser._read_nbytes(32), # inputsHash + deser._read_nbytes(96) # payloadSig + ) class DashCbTx(namedtuple("DashCbTx", "version height merkleRootMNList")): '''Class representing DIP4 coinbase special tx''' + def serialize(self): + assert len(self.merkleRootMNList) == 32 + return ( + pack_le_uint16(self.version) + # version + pack_le_uint32(self.height) + # height + self.merkleRootMNList # merkleRootMNList + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashCbTx( + deser._read_le_uint16(), # version + deser._read_le_uint32(), # height + deser._read_nbytes(32) # merkleRootMNList + ) class DashSubTxRegister(namedtuple("DashSubTxRegister", "version userName pubKey payloadSig")): '''Class representing DIP5 SubTxRegister''' + def serialize(self): + assert (len(self.pubKey) == 48 + and len(self.payloadSig) == 96) + return ( + pack_le_uint16(self.version) + # version + pack_varbytes(self.userName) + # userName + self.pubKey + # pubKey + self.payloadSig # payloadSig + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashSubTxRegister( + deser._read_le_uint16(), # version + deser._read_varbytes(), # userName + deser._read_nbytes(48), # pubKey + deser._read_nbytes(96) # payloadSig + ) class DashSubTxTopup(namedtuple("DashSubTxTopup", "version regTxHash")): '''Class representing DIP5 SubTxTopup''' + def serialize(self): + assert len(self.regTxHash) == 32 + return ( + pack_le_uint16(self.version) + # version + self.regTxHash # regTxHash + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashSubTxTopup( + deser._read_le_uint16(), # version + deser._read_nbytes(32) # regTxHash + ) class DashSubTxResetKey(namedtuple("DashSubTxResetKey", "version regTxHash hashPrevSubTx " "creditFee newPubKey payloadSig")): '''Class representing DIP5 SubTxResetKey''' + def serialize(self): + assert (len(self.regTxHash) == 32 + and len(self.hashPrevSubTx) == 32 + and len(self.newPubKey) == 48 + and len(self.payloadSig) == 96) + return ( + pack_le_uint16(self.version) + # version + self.regTxHash + # regTxHash + self.hashPrevSubTx + # hashPrevSubTx + pack_le_int64(self.creditFee) + # creditFee + self.newPubKey + # newPubKey + self.payloadSig # payloadSig + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashSubTxResetKey( + deser._read_le_uint16(), # version + deser._read_nbytes(32), # regTxHash + deser._read_nbytes(32), # hashPrevSubTx + deser._read_le_int64(), # creditFee + deser._read_nbytes(48), # newPubKey + deser._read_nbytes(96) # payloadSig + ) class DashSubTxCloseAccount(namedtuple("DashSubTxCloseAccount", "version regTxHash hashPrevSubTx " "creditFee payloadSig")): '''Class representing DIP5 SubTxCloseAccount''' + def serialize(self): + assert (len(self.regTxHash) == 32 + and len(self.hashPrevSubTx) == 32 + and len(self.payloadSig) == 96) + return ( + pack_le_uint16(self.version) + # version + self.regTxHash + # regTxHash + self.hashPrevSubTx + # hashPrevSubTx + pack_le_int64(self.creditFee) + # creditFee + self.payloadSig # payloadSig + ) + + @classmethod + def read_tx_extra(cls, deser): + return DashSubTxCloseAccount( + deser._read_le_uint16(), # version + deser._read_nbytes(32), # regTxHash + deser._read_nbytes(32), # hashPrevSubTx + deser._read_le_int64(), # creditFee + deser._read_nbytes(96) # payloadSig + ) # https://dash-docs.github.io/en/developer-reference#outpoint class TxOutPoint(namedtuple("TxOutPoint", "hash index")): '''Class representing tx output outpoint''' + def serialize(self): + assert len(self.hash) == 32 + return ( + self.hash + # hash + pack_le_uint32(self.index) # index + ) + + @classmethod + def read_outpoint(cls, deser): + return TxOutPoint( + deser._read_nbytes(32), # hash + deser._read_le_uint32() # index + ) class DeserializerDash(Deserializer): '''Deserializer for Dash DIP2 special tx types''' + # Supported Spec Tx types and corresponding classes mapping PRO_REG_TX = 1 PRO_UP_SERV_TX = 2 PRO_UP_REG_TX = 3 @@ -110,6 +358,21 @@ class DeserializerDash(Deserializer): SUB_TX_RESET_KEY = 10 SUB_TX_CLOSE_ACCOUNT = 11 + SPEC_TX_HANDLERS = { + PRO_REG_TX: DashProRegTx, + PRO_UP_SERV_TX: DashProUpServTx, + PRO_UP_REG_TX: DashProUpRegTx, + PRO_UP_REV_TX: DashProUpRevTx, + CB_TX: DashCbTx, + SUB_TX_REGISTER: DashSubTxRegister, + SUB_TX_TOPUP: DashSubTxTopup, + SUB_TX_RESET_KEY: DashSubTxResetKey, + SUB_TX_CLOSE_ACCOUNT: DashSubTxCloseAccount, + } + + def _read_outpoint(self): + return TxOutPoint.read_outpoint(self) + def read_tx(self): header = self._read_le_uint32() tx_type = header >> 16 # DIP2 tx type @@ -128,24 +391,11 @@ class DeserializerDash(Deserializer): if tx_type: extra_payload_size = self._read_varint() end = self.cursor + extra_payload_size - if tx_type == DeserializerDash.CB_TX: - extra_payload = self._read_cb_tx() - elif tx_type == DeserializerDash.PRO_REG_TX: - extra_payload = self._read_pro_reg_tx() - elif tx_type == DeserializerDash.PRO_UP_SERV_TX: - extra_payload = self._read_pro_up_serv_tx() - elif tx_type == DeserializerDash.PRO_UP_REG_TX: - extra_payload = self._read_pro_up_reg_tx() - elif tx_type == DeserializerDash.PRO_UP_REV_TX: - extra_payload = self._read_pro_up_rev_tx() - elif tx_type == DeserializerDash.SUB_TX_REGISTER: - extra_payload = self._read_sub_tx_register() - elif tx_type == DeserializerDash.SUB_TX_TOPUP: - extra_payload = self._read_sub_tx_topup() - elif tx_type == DeserializerDash.SUB_TX_RESET_KEY: - extra_payload = self._read_sub_tx_reset_key() - elif tx_type == DeserializerDash.SUB_TX_CLOSE_ACCOUNT: - extra_payload = self._read_sub_tx_close_account() + spec_tx_class = DeserializerDash.SPEC_TX_HANDLERS.get(tx_type) + if spec_tx_class: + read_method = getattr(spec_tx_class, 'read_tx_extra', None) + extra_payload = read_method(self) + assert isinstance(extra_payload, spec_tx_class) else: extra_payload = self._read_nbytes(extra_payload_size) assert self.cursor == end @@ -153,98 +403,3 @@ class DeserializerDash(Deserializer): extra_payload = b'' tx = DashTx(version, inputs, outputs, locktime, tx_type, extra_payload) return tx - - def _read_outpoint(self): - return TxOutPoint( - self._read_nbytes(32), # hash - self._read_le_uint32() # index - ) - - def _read_pro_reg_tx(self): - return DashProRegTx( - self._read_le_uint16(), # version - self._read_le_uint16(), # type - self._read_le_uint16(), # mode - self._read_outpoint(), # collateralOutpoint - self._read_nbytes(16), # ipAddress - self._read_le_uint16(), # port - self._read_nbytes(20), # KeyIdOwner - self._read_nbytes(48), # PubKeyOperator - self._read_nbytes(20), # KeyIdVoting - self._read_le_uint16(), # operatorReward - self._read_varbytes(), # scriptPayout - self._read_nbytes(32), # inputsHash - self._read_varbytes() # payloadSig - ) - - def _read_pro_up_serv_tx(self): - return DashProUpServTx( - self._read_le_uint16(), # version - self._read_nbytes(32), # proTXHash - self._read_nbytes(16), # ipAddress - self._read_le_uint16(), # port - self._read_varbytes(), # scriptOperatorPayout - self._read_nbytes(32), # inputsHash - self._read_nbytes(96) # payloadSig BLSSig - ) - - def _read_pro_up_reg_tx(self): - return DashProUpRegTx( - self._read_le_uint16(), # version - self._read_nbytes(32), # proTXHash - self._read_le_uint16(), # mode - self._read_nbytes(48), # PubKeyOperator - self._read_nbytes(20), # KeyIdOwner - self._read_varbytes(), # scriptPayout - self._read_nbytes(32), # inputsHash - self._read_varbytes() # payloadSig - ) - - def _read_pro_up_rev_tx(self): - return DashProUpRevTx( - self._read_le_uint16(), # version - self._read_nbytes(32), # proTXHash - self._read_le_uint16(), # reason - self._read_nbytes(32), # inputsHash - self._read_nbytes(96) # payloadSig BLSSig - ) - - def _read_cb_tx(self): - return DashCbTx( - self._read_le_uint16(), # version - self._read_le_uint32(), # height - self._read_nbytes(32) # merkleRootMNList as bytes - ) - - def _read_sub_tx_register(self): - return DashSubTxRegister( - self._read_le_uint16(), # version - self._read_varbytes(), # userName - self._read_nbytes(48), # pubKey BLSPubKey - self._read_nbytes(96) # payloadSig BLSSig - ) - - def _read_sub_tx_topup(self): - return DashSubTxTopup( - self._read_le_uint16(), # version - self._read_nbytes(32) # regTxHash - ) - - def _read_sub_tx_reset_key(self): - return DashSubTxResetKey( - self._read_le_uint16(), # version - self._read_nbytes(32), # regTxHash - self._read_nbytes(32), # hashPrevSubTx - self._read_le_int64(), # creditFee - self._read_nbytes(48), # newPubKey BLSPubKey - self._read_nbytes(96) # payloadSig BLSSig - ) - - def _read_sub_tx_close_account(self): - return DashSubTxCloseAccount( - self._read_le_uint16(), # version - self._read_nbytes(32), # regTxHash - self._read_nbytes(32), # hashPrevSubTx - self._read_le_int64(), # creditFee - self._read_nbytes(96) # payloadSig BLSSig - ) diff --git a/tests/lib/test_tx_dash.py b/tests/lib/test_tx_dash.py index 3a08885..0cf21dc 100644 --- a/tests/lib/test_tx_dash.py +++ b/tests/lib/test_tx_dash.py @@ -1,6 +1,11 @@ +import pytest + import electrumx.lib.tx_dash as lib_tx_dash +bfh = bytes.fromhex + + V2_TX = ( '020000000192809f0b234cb850d71d020e678e93f074648ed0df5affd0c46d3bcb177f' '9ccf020000008b483045022100c5403bcf86c3ae7b8fd4ca0d1e4df6729cc1af05ff95' @@ -125,11 +130,11 @@ SUB_TX_RESET_KEY = ( '3d0103f761cc69a211feffffff0189fa433e000000001976a914551ab8ca96a9142217' '4d22769c3a4f90b2dcd0de88ac00000000da0100d384e42374e8abfeffffff01570b00' '0000a40100b67ffbbd095de31ea3844675af3e98e9601210293360bf2a2e810673412b' - 'c6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360bf2a2e81067341' + 'c6e8e0e358f3fb7bdbe9a667b3d0e803000000000000601210293360bf2a2e81067341' '2bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360bf2a2e810673' '412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360bf2a2e8106' '73412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360bf2a2e81' - '0673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761cabcdefabcdef') + '0673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761cabcdefab') SUB_TX_CLOSE_ACCOUNT = ( @@ -140,10 +145,10 @@ SUB_TX_CLOSE_ACCOUNT = ( '3d0103f761cc69a211feffffff0189fa433e000000001976a914551ab8ca96a9142217' '4d22769c3a4f90b2dcd0de88ac00000000aa0100d384e42374e8abfeffffff01570b00' '0000a40100b67ffbbd095de31ea3844675af3e98e9601210293360bf2a2e810673412b' - 'c6e8e0e358f3fb7bdbe9a12bc6e8e0e358f3fb7bdbe9a62bc6e8e0e358f3fb7bdbe9a6' + 'c6e8e0e358f3fb7bdbe9a12bc6e8e803000000000000a62bc6e8e0e358f3fb7bdbe9a6' '67b3d0103f761caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9' 'a667b3d0103f761caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdb' - 'e9a667b3d0103f761cabcdefabcdef') + 'e9a667b3d0103f761cabcdefab') UNKNOWN_SPEC_TX = ( @@ -157,7 +162,7 @@ UNKNOWN_SPEC_TX = ( 'c6e8e0e358f3fb7bdbe9a12bc6e8e0e358f3fb7bdbe9a62bc6e8e0e358f3fb7bdbe9a6' '67b3d0103f761caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9' 'a667b3d0103f761caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdb' - 'e9a667b3d0103f761cabcdefabcdef') + 'e9a667b3d0103f761cabcdefab') WRONG_SPEC_TX = ( # Tx version < 3 @@ -166,77 +171,294 @@ WRONG_SPEC_TX = ( # Tx version < 3 '1171e06d7c372db92c65022061c1ec3c92f2e76bb7fb1b548d854f19a41e6421267231' '74150412caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b' '3d0103f761cc69a211feffffff0189fa433e000000001976a914551ab8ca96a9142217' - '4d22769c3a4f90b2dcd0de88ac00000000aa0100d384e42374e8abfeffffff01570b00' - '0000') + '4d22769c3a4f90b2dcd0de88ac00000000') def test_dash_v2_tx(): - test = bytes.fromhex(V2_TX) + test = bfh(V2_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 2 + assert tx.tx_type == 0 + assert tx.extra_payload == b'' + ser = tx.serialize() + assert ser == test def test_dash_tx_cb_tx(): - test = bytes.fromhex(CB_TX) + test = bfh(CB_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 5 + extra = tx.extra_payload + assert extra.version == 1 + assert extra.height == 264132 + assert len(extra.merkleRootMNList) == 32 + assert extra.merkleRootMNList == bfh( + '76629a6e42fb519188f65889fd3ac0201be87aa227462b5643e8bb2ec1d7a82a') + ser = tx.serialize() + assert ser == test def test_dash_tx_pro_reg_tx(): - test = bytes.fromhex(PRO_REG_TX) + test = bfh(PRO_REG_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 1 + extra = tx.extra_payload + assert extra.version == 1 + assert extra.type == 0 + assert extra.mode == 0 + assert len(extra.collateralOutpoint.hash) == 32 + assert extra.collateralOutpoint.hash == bfh( + '4de1afa0a321bc88c34978d4eeba739256b86f8d8cdf47651b6f60e451f0a3de') + assert extra.collateralOutpoint.index == 1 + assert len(extra.ipAddress) == 16 + assert extra.ipAddress == bfh('00000000000000000000ffff12ca34aa') + assert extra.port == 12149 + assert len(extra.KeyIdOwner) == 20 + assert extra.KeyIdOwner == bfh( + '2b3edeed6842db1f59cf35de1ab5721094f049d0') + assert len(extra.PubKeyOperator) == 48 + assert extra.PubKeyOperator == bfh( + '00ab986c589053b3f3bd720724e75e18581afdca54bce80d14750b1bcf920215' + '8fe6c596ce8391815265747bd4a2009e') + assert len(extra.KeyIdVoting) == 20 + assert extra.KeyIdVoting == bfh( + '2b3edeed6842db1f59cf35de1ab5721094f049d0') + assert extra.operatorReward == 0 + assert extra.scriptPayout == bfh( + '76a9149bf5948b901a1e3e54e42c6e10496a17cd4067e088ac') + assert len(extra.inputsHash) == 32 + assert extra.inputsHash == bfh( + '54d046585434668b4ee664c597864248b8a6aac33a7b2f4fcd1cc1b5da474a8a') + assert extra.payloadSig == bfh( + '1fc1617ae83406c92a9132f14f9fff1487f2890f401e776fdddd639bc505' + '5c456268cf7497400d3196109c8cd31b94732caf6937d63de81d9a5be4db' + '5beb83f9aa') + ser = tx.serialize() + assert ser == test def test_dash_tx_pro_up_serv_tx(): - test = bytes.fromhex(PRO_UP_SERV_TX) + test = bfh(PRO_UP_SERV_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 2 + extra = tx.extra_payload + assert extra.version == 1 + assert len(extra.proTxHash) == 32 + assert extra.proTxHash == bfh( + '3c6dca244f49f19d3f09889753ffff1fec5bb8f9f5bd5bc09dabd999da21198f') + assert len(extra.ipAddress) == 16 + assert extra.ipAddress == bfh('00000000000000000000ffff5fb73580') + assert extra.port == 4391 + assert extra.scriptOperatorPayout == bfh( + '76a91421851058431a7d722e8e8dd9509e7f2b8e7042ec88ac') + assert len(extra.inputsHash) == 32 + assert extra.inputsHash == bfh( + 'efcfe3d578914bb48c6bd71b3459d384e4237446d521c9e2c6' + 'b6fcf019b5aafc') + assert len(extra.payloadSig) == 96 + assert extra.payloadSig == bfh( + '99443fe14f644cfa47086e8897cf7b546a67723d4a8ec5353a82f962a96e' + 'c3cea328343b647aace2897d6eddd0b8c8ee0f2e56f6733aed2e9f0006ca' + 'afa6fc21c18a013c619d6e37af8d2f0985e3b769abc38ffa60e46c365a38' + 'd9fa0d44fd62') + ser = tx.serialize() + assert ser == test def test_dash_tx_pro_up_reg_tx(): - test = bytes.fromhex(PRO_UP_REG_TX) + test = bfh(PRO_UP_REG_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 3 + extra = tx.extra_payload + assert extra.version == 1 + assert len(extra.proTxHash) == 32 + assert extra.proTxHash == bfh( + 'aeb817f94b8e699b58130a53d2fbe98d5519c2abe3b15e6f36c9abeb32e4dcce') + assert extra.mode == 0 + assert len(extra.PubKeyOperator) == 48 + assert extra.PubKeyOperator == bfh( + '1061eb559a64427ad239830742ef59591cdbbdffda7d3f5e7a2d95b9607a' + 'd80e389191e44c59ea5987b85e6d0e3eb527') + assert len(extra.KeyIdVoting) == 20 + assert extra.KeyIdVoting == bfh( + 'b9e198fa7a745913c9278ec993d4472a95dac425') + assert extra.scriptPayout == bfh( + '76a914eebbacffff3a55437803e0efb68a7d591e0409d188ac') + assert len(extra.inputsHash) == 32 + assert extra.inputsHash == bfh( + '0eb0067e6ccdd2acb96e7279113702218f3f0ab6f2287e14c11c5be6f2051d5a') + assert extra.payloadSig == bfh( + '20cb00124d838b02207097048cb668244cd79df825eb2d4d211fd2c4604c1' + '8b30e1ae9bb654787144d16856676efff180889f05b5c9121a483b4ae3f0e' + 'a0ff3faf') + ser = tx.serialize() + assert ser == test def test_dash_tx_pro_up_rev_tx(): - test = bytes.fromhex(PRO_UP_REV_TX) + test = bfh(PRO_UP_REV_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 4 + extra = tx.extra_payload + assert extra.version == 1 + assert len(extra.proTxHash) == 32 + assert extra.proTxHash == bfh( + 'b67ffbbd095de31ea38446754b6bf251287936d2881d58b7c4efae0b54c75e9f') + assert extra.reason == 0 + assert len(extra.inputsHash) == 32 + assert extra.inputsHash == bfh( + 'eb073521b60306717f1d4feb3e9022f886b97bf981137684716a7d3d7e45b7fe') + assert len(extra.payloadSig) == 96 + assert extra.payloadSig == bfh( + '83f4bb5530f7c5954e8b1ad50a74a9e1d65dcdcbe4acb8cbe3671abc7911' + 'e8c3954856c4da7e5fd242f2e4f5546f08d90849245bc593d1605654e1a9' + '9cd0a79e9729799742c48d4920044666ad25a85fd093559c43e4900e634c' + '371b9b8d89ba') + ser = tx.serialize() + assert ser == test def test_dash_tx_sub_tx_register_tx(): - test = bytes.fromhex(SUB_TX_REGISTER) + test = bfh(SUB_TX_REGISTER) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 8 + extra = tx.extra_payload + assert extra.version == 1 + assert extra.userName == b'abc' + assert len(extra.pubKey) == 48 + assert extra.pubKey == bfh( + '8e7042ec88acefcfe3d578914bb48c6bd71b3459d384e42374e8abfeffff' + 'ff01570b0000000000001976a91490c5ce9d') + assert len(extra.payloadSig) == 96 + assert extra.payloadSig == bfh( + '8bc992a88ac00000000a40100b67ffbbd095de31ea38446754e8abfeffff' + 'ff01570b0000000000001976a91490c5ce9d8bc992a88ac00000000a4010' + '0b67ffbbd095de31ea38446754e8abfeffffff01570b0000000000001976' + 'a91490c5ce9d') + ser = tx.serialize() + assert ser == test def test_dash_tx_sub_tx_topup_tx(): - test = bytes.fromhex(SUB_TX_TOPUP) + test = bfh(SUB_TX_TOPUP) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 9 + extra = tx.extra_payload + assert extra.version == 1 + assert len(extra.regTxHash) == 32 + assert extra.regTxHash == bfh( + 'd384e42374e8abfeffffff01570b000000a40100b67ffbbd095de31ea3844675') + ser = tx.serialize() + assert ser == test def test_dash_tx_sub_tx_reset_key_tx(): - test = bytes.fromhex(SUB_TX_RESET_KEY) + test = bfh(SUB_TX_RESET_KEY) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 10 + extra = tx.extra_payload + assert extra.version == 1 + assert len(extra.regTxHash) == 32 + assert extra.regTxHash == bfh( + 'd384e42374e8abfeffffff01570b000000a40100b67ffbbd095de31ea3844675') + assert len(extra.hashPrevSubTx) == 32 + assert extra.hashPrevSubTx == bfh( + 'af3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0') + assert extra.creditFee == 1000 + assert len(extra.newPubKey) == 48 + assert extra.newPubKey == bfh( + '601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f7' + '61caf3e98e9601210293360bf2a2e810673') + assert len(extra.payloadSig) == 96 + assert extra.payloadSig == bfh( + '412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360b' + 'f2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e960' + '1210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761' + 'cabcdefab') + ser = tx.serialize() + assert ser == test def test_dash_tx_sub_tx_close_account_tx(): - test = bytes.fromhex(SUB_TX_CLOSE_ACCOUNT) + test = bfh(SUB_TX_CLOSE_ACCOUNT) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 11 + extra = tx.extra_payload + assert extra.version == 1 + assert len(extra.regTxHash) == 32 + assert extra.regTxHash == bfh( + 'd384e42374e8abfeffffff01570b000000a40100b67ffbbd095de31ea3844675') + assert len(extra.hashPrevSubTx) == 32 + assert extra.hashPrevSubTx == bfh( + 'af3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a12bc6e8') + assert extra.creditFee == 1000 + assert len(extra.payloadSig) == 96 + assert extra.payloadSig == bfh( + 'a62bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360b' + 'f2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e960' + '1210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761' + 'cabcdefab') + ser = tx.serialize() + assert ser == test def test_dash_tx_unknown_spec_tx(): - test = bytes.fromhex(UNKNOWN_SPEC_TX) + test = bfh(UNKNOWN_SPEC_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 187 + extra = tx.extra_payload + assert extra == bfh( + '0100d384e42374e8abfeffffff01570b000000a40100b67ffbbd095de31e' + 'a3844675af3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7b' + 'dbe9a12bc6e8e0e358f3fb7bdbe9a62bc6e8e0e358f3fb7bdbe9a667b3d0' + '103f761caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7b' + 'dbe9a667b3d0103f761caf3e98e9601210293360bf2a2e810673412bc6e8' + 'e0e358f3fb7bdbe9a667b3d0103f761cabcdefab') + ser = tx.serialize() + assert ser == test def test_dash_tx_wrong_spec_tx(): - test = bytes.fromhex(WRONG_SPEC_TX) + test = bfh(WRONG_SPEC_TX) deser = lib_tx_dash.DeserializerDash(test) tx = deser.read_tx() + assert tx.version == 12255234 + assert tx.tx_type == 0 + extra = tx.extra_payload + assert extra == b'' + ser = tx.serialize() + assert ser == test + + +def test_dash_tx_serialize_wrong_tx_type(): + test = bfh(CB_TX) + deser = lib_tx_dash.DeserializerDash(test) + tx = deser.read_tx() + assert tx.tx_type == 5 + tx = tx._replace(tx_type=4) + assert tx.tx_type == 4 + with pytest.raises(ValueError) as excinfo: + ser = tx.serialize() + assert ('Dash tx_type does not conform' + ' with extra payload class' in str(excinfo.value))