tx: fix nLocktime functionality, uint64 parsing; add locktime tests

This commit is contained in:
The MMGen Project 2024-02-22 12:48:14 +00:00
commit 72a93dfcb5
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
9 changed files with 65 additions and 23 deletions

View file

@ -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()

View file

@ -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:

View file

@ -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()

View file

@ -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} cant be included in this block!".format(
self.info.strfmt_locktime(self.get_serialized_locktime()))
else:
m,nl = ('','')
msg(orange('\n'+errmsg))

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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])