From 72a93dfcb5efa2de53ba8069d9fd49df445ea90a Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 22 Feb 2024 12:48:14 +0000 Subject: [PATCH] tx: fix nLocktime functionality, uint64 parsing; add locktime tests --- mmgen/proto/btc/tx/base.py | 5 ++-- mmgen/proto/btc/tx/info.py | 4 ++- mmgen/proto/btc/tx/new.py | 2 +- mmgen/proto/btc/tx/online.py | 6 ++-- mmgen/proto/eth/tx/completed.py | 3 -- mmgen/tx/base.py | 5 ++++ mmgen/tx/completed.py | 5 ---- test/cmdtest_py_d/ct_regtest.py | 53 ++++++++++++++++++++++++++++----- test/cmdtest_py_d/ct_shared.py | 5 ++++ 9 files changed, 65 insertions(+), 23 deletions(-) diff --git a/mmgen/proto/btc/tx/base.py b/mmgen/proto/btc/tx/base.py index 9fd17c8c..c764be7d 100755 --- a/mmgen/proto/btc/tx/base.py +++ b/mmgen/proto/btc/tx/base.py @@ -48,8 +48,6 @@ def DeserializeTX(proto,txhex): """ def bytes2int(bytes_le): - if bytes_le[-1] & 0x80: # sign bit is set - die(3,"{}: Negative values not permitted in transaction!".format(bytes_le[::-1].hex())) return int(bytes_le[::-1].hex(),16) def bytes2coin_amt(bytes_le): @@ -95,6 +93,9 @@ def DeserializeTX(proto,txhex): d = { 'version': bytes2int(bshift(4)) } + if d['version'] > 0x7fffffff: # version is signed integer + die(3,f"{d['version']}: transaction version greater than maximum allowed value (int32_t)!") + has_witness = tx[idx] == 0 if has_witness: u = bshift(2,skip=True).hex() diff --git a/mmgen/proto/btc/tx/info.py b/mmgen/proto/btc/tx/info.py index ee56c920..2cece536 100755 --- a/mmgen/proto/btc/tx/info.py +++ b/mmgen/proto/btc/tx/info.py @@ -128,7 +128,9 @@ class TxInfo(TxInfo): num = locktime or self.tx.locktime if num is None: return '(None)' - elif num >= 5 * 10**6: + elif num.bit_length() > 32: + die(2,f'{num!r}: invalid nLockTime value (integer size greater than 4 bytes)!') + elif num >= 500_000_000: import time return ' '.join(time.strftime('%c',time.gmtime(num)).split()[1:]) elif num > 0: diff --git a/mmgen/proto/btc/tx/new.py b/mmgen/proto/btc/tx/new.py index ff79ef22..16249b8f 100755 --- a/mmgen/proto/btc/tx/new.py +++ b/mmgen/proto/btc/tx/new.py @@ -130,7 +130,7 @@ class New(Base,TxBase.New): ret = await self.rpc.call( 'createrawtransaction', inputs_list, outputs_dict ) if locktime and not bump: - msg(f'Setting nLockTime to {self.strfmt_locktime(locktime)}!') + msg(f'Setting nLockTime to {self.info.strfmt_locktime(locktime)}!') assert isinstance(locktime,int), 'locktime value not an integer' self.locktime = locktime ret = ret[:-8] + bytes.fromhex(f'{locktime:08x}')[::-1].hex() diff --git a/mmgen/proto/btc/tx/online.py b/mmgen/proto/btc/tx/online.py index a004b6c7..47b58d24 100755 --- a/mmgen/proto/btc/tx/online.py +++ b/mmgen/proto/btc/tx/online.py @@ -57,9 +57,9 @@ class OnlineSigned(Signed,TxBase.OnlineSigned): m = ( 'The Aug. 1 2017 UAHF is not yet active on this chain.\n' 'Re-run the script without the --coin=bch option.') - elif errmsg.count('64: non-final'): - m = "Transaction with nLockTime {!r} can't be included in this block!".format( - self.strfmt_locktime(self.get_serialized_locktime())) + elif errmsg.count('non-final'): + m = "Transaction with nLockTime {!r} can’t be included in this block!".format( + self.info.strfmt_locktime(self.get_serialized_locktime())) else: m,nl = ('','') msg(orange('\n'+errmsg)) diff --git a/mmgen/proto/eth/tx/completed.py b/mmgen/proto/eth/tx/completed.py index 912db37f..d8645d9d 100755 --- a/mmgen/proto/eth/tx/completed.py +++ b/mmgen/proto/eth/tx/completed.py @@ -51,9 +51,6 @@ class Completed(Base,TxBase.Completed): def check_pubkey_scripts(self): pass - def strfmt_locktime(self,locktime=None,terse=False): - pass - def get_serialized_locktime(self): return None # TODO diff --git a/mmgen/tx/base.py b/mmgen/tx/base.py index d76ce644..06677104 100755 --- a/mmgen/tx/base.py +++ b/mmgen/tx/base.py @@ -123,6 +123,11 @@ class Base(MMGenObject): def dcoin(self): return self.proto.dcoin + @property + def info(self): + from .info import init_info + return init_info(self) + def check_correct_chain(self): if hasattr(self,'rpc'): if self.chain != self.rpc.chain: diff --git a/mmgen/tx/completed.py b/mmgen/tx/completed.py index 5b8fb0cf..20653671 100755 --- a/mmgen/tx/completed.py +++ b/mmgen/tx/completed.py @@ -42,11 +42,6 @@ class Completed(Base): from ..util import die die(1,'Transaction is {}signed!'.format('not ' if self.signed else '')) - @property - def info(self): - from .info import init_info - return init_info(self) - @property def file(self): from .file import MMGenTxFile diff --git a/test/cmdtest_py_d/ct_regtest.py b/test/cmdtest_py_d/ct_regtest.py index 17cc5cc5..a76db1d4 100755 --- a/test/cmdtest_py_d/ct_regtest.py +++ b/test/cmdtest_py_d/ct_regtest.py @@ -184,6 +184,7 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared): ('subgroup.msg', ['init_bob']), ('subgroup.twexport', ['fund_users']), ('subgroup.rescan', ['fund_users']), + ('subgroup.errors', ['fund_users']), ('subgroup.main', ['fund_users']), ('subgroup.twprune', ['main']), ('subgroup.txhist', ['main']), @@ -265,6 +266,12 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared): ('bob_rescan_blockchain_one', 'rescanning the blockchain (single block)'), ('bob_rescan_blockchain_ss', 'rescanning the blockchain (range of blocks)'), ), + 'errors': ( + 'various error conditions', + ('bob_bad_locktime1', 'broadcast of transaction with bad locktime (block)'), + ('bob_bad_locktime2', 'broadcast of transaction with bad locktime (integer size)'), + ('bob_bad_locktime3', 'broadcast of transaction with bad locktime (time)'), + ), 'main': ( 'creating, signing, sending and bumping transactions', ('bob_add_comment1', "adding an 80-screen-width label (lat+cyr+gr)"), @@ -911,15 +918,16 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared): t.expect(exp2,regex=True) return t - def user_txdo( - self, + def user_txdo( self, user, fee, outputs_cl, outputs_list, extra_args = [], wf = None, - bad_locktime = False, + add_comment = tx_comment_jp, + return_early = False, + return_after_send = False, menu = ['M'], skip_passphrase = False, used_chg_addr_resp = None): @@ -936,25 +944,29 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared): inputs = outputs_list, file_desc = 'Signed transaction', interactive_fee = (tx_fee,'')[bool(fee)], - add_comment = tx_comment_jp, + add_comment = add_comment, + return_early = return_early, view = 't', save = True, used_chg_addr_resp = used_chg_addr_resp) + if return_early: + return t + if not skip_passphrase: t.passphrase(dfl_wcls.desc,rt_pw) t.written_to_file('Signed transaction') self._do_confirm_send(t) - s,exit_val = (('Transaction sent',0),("can't be included",1))[bad_locktime] - t.expect(s) - t.req_exit_val = exit_val + if return_after_send: + return t + t.expect('Transaction sent') return t def bob_split1(self): sid = self._user_sid('bob') outputs_cl = [sid+':C:1,100', sid+':L:2,200',sid+':'+rtBobOp3] - return self.user_txdo('bob',rtFee[0],outputs_cl,'1') + return self.user_txdo('bob',rtFee[0],outputs_cl,'1',extra_args=['--locktime=500000001']) def get_addr_from_addrlist(self,user,sid,mmtype,idx,addr_range='1-5'): id_str = { 'L':'', 'S':'-S', 'C':'-C', 'B':'-B' }[mmtype] @@ -1427,6 +1439,31 @@ class CmdTestRegtest(CmdTestBase,CmdTestShared): t.expect('Removed label.*in tracking wallet',regex=True) return t + def bob_bad_locktime1(self): + return self._bob_bad_locktime(123456789, 'non-final', 2) # > current block height + + def bob_bad_locktime2(self): + return self._bob_bad_locktime(7_000_000_000, 'invalid', 2, return_early=True) # > 4 bytes + + def bob_bad_locktime3(self): + return self._bob_bad_locktime(0xffffffff, 'non-final', 2, return_early=False) # > cur time + + def _bob_bad_locktime(self,locktime,expect,exit_val,return_early=False): + sid = self._user_sid('bob') + t = self.user_txdo( + user = 'bob', + fee = '20s', + outputs_cl = [self.burn_addr+',0.1', sid+':C:5'], + outputs_list = '1', + extra_args = [f'--locktime={locktime}'], + return_early = return_early, + add_comment = False, + return_after_send = True) + t.req_exit_val = exit_val + if expect: + t.expect(expect) + return t + def bob_add_comment1(self): sid = self._user_sid('bob') return self.user_add_comment('bob',sid+':C:1',tw_comment_lat_cyr_gr) diff --git a/test/cmdtest_py_d/ct_shared.py b/test/cmdtest_py_d/ct_shared.py index 5a42de75..872be283 100755 --- a/test/cmdtest_py_d/ct_shared.py +++ b/test/cmdtest_py_d/ct_shared.py @@ -52,6 +52,7 @@ class CmdTestShared: add_comment = '', view = 't', save = True, + return_early = False, tweaks = [], used_chg_addr_resp = None, auto_chg_addr = None): @@ -107,6 +108,10 @@ class CmdTestShared: t.expect('Continue? (Y/n)','\n') t.do_comment(add_comment) + + if return_early: + return t + t.view_tx(view) if not txdo: t.expect('(y/N): ',('n','y')[save])