Browse Source

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

The MMGen Project 1 year ago
parent
commit
72a93dfc

+ 3 - 2
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()

+ 3 - 1
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:

+ 1 - 1
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()

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

+ 0 - 3
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
 

+ 5 - 0
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:

+ 0 - 5
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

+ 45 - 8
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)

+ 5 - 0
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])