Skip to content

Commit

Permalink
Add CB_TX_v3 support + test
Browse files Browse the repository at this point in the history
  • Loading branch information
pshenmic committed Dec 29, 2023
1 parent 2e7848a commit 1db0f6e
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 6 deletions.
31 changes: 27 additions & 4 deletions electrum_dash/dash_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from ipaddress import ip_address, IPv6Address
from bls_py import bls

from .util import bh2u, bfh
from .util import bh2u, bfh, pack_varint
from .bitcoin import COIN
from .crypto import sha256d
from .i18n import _
Expand Down Expand Up @@ -639,7 +639,7 @@ def update_before_sign(self, tx, wallet, password):
class DashCbTx(ProTxBase):
'''Class representing DIP4 coinbase special tx'''

__slots__ = ('version height merkleRootMNList merkleRootQuorums').split()
__slots__ = ('version height merkleRootMNList merkleRootQuorums bestCLHeightDiff bestCLSignature assetLockedAmount').split()

def __str__(self):
res = ('CbTx Version: %s\n'
Expand All @@ -650,6 +650,13 @@ def __str__(self):
if self.version > 1:
res += ('merkleRootQuorums: %s\n' %
bh2u(self.merkleRootQuorums[::-1]))
if self.version > 2:
res += ('bestCLHeightDiff: %s\n' %
self.bestCLHeightDiff)
res += ('bestCLSignature: %s\n' %
bh2u(self.bestCLSignature[::-1]))
res += ('assetLockedAmount: %s\n' %
self.assetLockedAmount)
return res

def serialize(self):
Expand All @@ -663,7 +670,15 @@ def serialize(self):
if self.version > 1:
assert len(self.merkleRootQuorums) == 32, \
f'{len(self.merkleRootQuorums)} not 32'
res += self.merkleRootQuorums # merkleRootQuorums
res += self.merkleRootQuorums
if self.version > 2:
# assert len(self.bestCLHeightDiff) == 32, \
# f'{len(self.bestCLHeightDiff)} not 32'
res += pack_varint(self.bestCLHeightDiff) # bestCLHeightDiff
assert len(self.bestCLSignature) == 96, \
f'{len(self.bestCLSignature)} not 96'
res += self.bestCLSignature # bestCLSignature
res += struct.pack('<q', self.assetLockedAmount) # assetLockedAmount
return res

@classmethod
Expand All @@ -672,9 +687,17 @@ def read_vds(cls, vds):
height = vds.read_uint32()
merkleRootMNList = vds.read_bytes(32)
merkleRootQuorums = b''
bestCLHeightDiff = 0
bestCLSignature = ""
assetLockedAmount = 0
if version > 1:
merkleRootQuorums = vds.read_bytes(32)
return DashCbTx(version, height, merkleRootMNList, merkleRootQuorums)
if version > 2:
bestCLHeightDiff = vds.read_varint()
bestCLSignature = vds.read_bytes(96)
assetLockedAmount = vds.read_uint64()
return DashCbTx(version, height, merkleRootMNList, merkleRootQuorums,
bestCLHeightDiff, bestCLSignature, assetLockedAmount)


class DashSubTxRegister(ProTxBase):
Expand Down
70 changes: 68 additions & 2 deletions electrum_dash/tests/test_dash_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@
'merkleRootMNList': '76629a6e42fb519188f65889fd3ac020'
'1be87aa227462b5643e8bb2ec1d7a82a',
'merkleRootQuorums': '',
'version': 1}
'version': 1,
'bestCLHeightDiff': 0,
'bestCLSignature': '',
'assetLockedAmount': 0,
}


CB_TX_V2 = (
Expand All @@ -66,7 +70,37 @@
'27462b5643e8bb2ec1d7a82a'),
'merkleRootQuorums': ('76629a6e42fb519188f65889fd3ac0201be87aa'
'227462b5643e8bb2ec1d7a82a'),
'version': 2}
'version': 2,
'bestCLHeightDiff': 0,
'bestCLSignature': '',
'assetLockedAmount': 0
}

CB_TX_V3 = (
'0300050001000000000000000000000000000000000000000000000000000000000000'
'0000ffffffff06035cbe0d0101ffffffff0397f4e127000000001976a914c69a0bda7d'
'aaae481be8def95e5f347a1d00a4b488ac94196f1600000000016a4dd5632500000000'
'1976a914c69a0bda7daaae481be8def95e5f347a1d00a4b488ac00000000af03005cbe'
'0d003c7a25cd3258d4141c1aca784232f28b92f94221c1d6add1c7221ebecffd201297'
'52cf4e10c95caefd2972782eb6ab4bc64170c148c9f32191be3f09d546a5e500b097da'
'dbd9741dabd85bec96ed8421499ec37aeb0ec48ff25c2a994a47e030ef1c5758bf1918'
'e4fd04c9f7b149df160800a9fdbf08311b93484e545a876e81e3408a4c8358f11ce2c9'
'c01206c39122875f9dbfea67e8953da4e63a1cd8551dfc94196f1600000000')

CB_TX_V3_D = {
'height': 900700,
'merkleRootMNList': ('3c7a25cd3258d4141c1aca784232f28b92f94221'
'c1d6add1c7221ebecffd2012'),
'merkleRootQuorums': ('9752cf4e10c95caefd2972782eb6ab4bc64170c1'
'48c9f32191be3f09d546a5e5'),
'version': 3,
'bestCLHeightDiff': 0,
'bestCLSignature': (
'b097dadbd9741dabd85bec96ed8421499ec37aeb0ec48ff25c2a994a47e030ef'
'1c5758bf1918e4fd04c9f7b149df160800a9fdbf08311b93484e545a876e81e3'
'408a4c8358f11ce2c9c01206c39122875f9dbfea67e8953da4e63a1cd8551dfc'),
'assetLockedAmount': 376379796
}


PRO_REG_TX = (
Expand Down Expand Up @@ -437,6 +471,38 @@ def test_dash_tx_cb_tx_v2(self):
assert extra2.height == extra.height
assert extra2.merkleRootMNList == extra.merkleRootMNList

def test_dash_tx_cb_tx_v3(self):
tx = transaction.Transaction(CB_TX_V3)
deser = tx.to_json()
assert deser['version'] == 3
assert deser['tx_type'] == 5
extra_dict = deser['extra_payload']
assert extra_dict == CB_TX_V3_D
extra = tx.extra_payload
assert(str(extra))
assert extra.version == CB_TX_V3_D['version']
assert extra.height == CB_TX_V3_D['height']
assert len(extra.merkleRootMNList) == 32
assert extra.merkleRootMNList == bfh(CB_TX_V3_D['merkleRootMNList'])
assert len(extra.merkleRootQuorums) == 32
assert extra.merkleRootQuorums == bfh(CB_TX_V3_D['merkleRootQuorums'])
assert extra.bestCLHeightDiff == 0
assert len(extra.bestCLSignature) == 96
assert extra.bestCLSignature == bfh(CB_TX_V3_D['bestCLSignature'])
assert extra.assetLockedAmount == 376379796

ser = tx.serialize()
assert ser == CB_TX_V3

assert extra.to_hex_str() == CB_TX_V3[272:]
extra2 = ProTxBase.from_hex_str(SPEC_CB_TX, CB_TX_V3[272:])
assert extra2.version == extra.version
assert extra2.height == extra.height
assert extra2.merkleRootMNList == extra.merkleRootMNList
assert extra2.bestCLHeightDiff == extra.bestCLHeightDiff
assert extra2.bestCLSignature == extra.bestCLSignature
assert extra2.assetLockedAmount == extra.assetLockedAmount

def test_dash_tx_pro_reg_tx(self):
tx = transaction.Transaction(PRO_REG_TX)
deser = tx.to_json()
Expand Down
36 changes: 36 additions & 0 deletions electrum_dash/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ class MissingTxInputAmount(Exception):
SIGHASH_ALL = 1


struct_le_H = struct.Struct('<H')
struct_le_I = struct.Struct('<I')
struct_le_Q = struct.Struct('<Q')

unpack_le_uint16_from = struct_le_H.unpack_from
unpack_le_uint32_from = struct_le_I.unpack_from
unpack_le_uint64_from = struct_le_Q.unpack_from


class TxOutput:
scriptpubkey: bytes
value: Union[int, str]
Expand Down Expand Up @@ -307,6 +316,7 @@ def read_int32(self): return self._read_num('<i')
def read_uint32(self): return self._read_num('<I')
def read_int64(self): return self._read_num('<q')
def read_uint64(self): return self._read_num('<Q')
def read_varint(self): return self._read_varint()

def write_boolean(self, val): return self.write(b'\x01' if val else b'\x00')
def write_char(self, val): return self._write_num('<b', val)
Expand Down Expand Up @@ -357,6 +367,32 @@ def _read_num(self, format):
raise SerializationError(e) from e
return i

def _read_le_uint16(self):
result, = unpack_le_uint16_from(self.binary, self.cursor)
self.cursor += 2
return result

def _read_le_uint32(self):
result, = unpack_le_uint32_from(self.binary, self.cursor)
self.cursor += 4
return result

def _read_le_uint64(self):
result, = unpack_le_uint64_from(self.binary, self.cursor)
self.cursor += 8
return result

def _read_varint(self):
n = self.input[self.read_cursor]
self.read_cursor += 1
if n < 253:
return n
if n == 253:
return self._read_le_uint16()
if n == 254:
return self._read_le_uint32()
return self._read_le_uint64()

def _write_num(self, format, num):
s = struct.pack(format, num)
self.write(s)
Expand Down
15 changes: 15 additions & 0 deletions electrum_dash/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import secrets
import functools
from abc import abstractmethod, ABC
from struct import Struct

import attr
import aiohttp
Expand Down Expand Up @@ -98,6 +99,20 @@ def all_subclasses(cls) -> Set:
class UnknownBaseUnit(Exception): pass


def pack_varint(n):
pack_byte = Struct('B').pack
pack_le_uint16 = Struct('<H').pack
pack_le_uint32 = Struct('<I').pack
pack_le_uint64 = Struct('<Q').pack

if n < 253:
return pack_byte(n)
if n < 65536:
return pack_byte(253) + pack_le_uint16(n)
if n < 4294967296:
return pack_byte(254) + pack_le_uint32(n)
return pack_byte(255) + pack_le_uint64(n)

def decimal_point_to_base_unit_name(dp: int) -> str:
# e.g. 8 -> "DASH"
try:
Expand Down

0 comments on commit 1db0f6e

Please sign in to comment.