Browse Source

mmgen-txsend: improve output of --status option

MMGen 5 years ago
parent
commit
54f86e0831
3 changed files with 101 additions and 40 deletions
  1. 0 2
      mmgen/tw.py
  2. 44 36
      mmgen/tx.py
  3. 57 2
      test/test_py_d/ts_regtest.py

+ 0 - 2
mmgen/tw.py

@@ -121,8 +121,6 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 		# 4. include_unsafe (boolean, optional, default=true) Include outputs that are not safe to spend
 		# 5. query_options  (json object, optional) JSON with query options
 
-		# for now, self.addrs is just an empty list for Bitcoin and friends
-		add_args = (9999999,self.addrs) if self.addrs else ()
 		return g.rpch.listunspent(self.minconf)
 
 	def get_unspent_data(self):

+ 44 - 36
mmgen/tx.py

@@ -876,58 +876,66 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 	def has_segwit_outputs(self):
 		return any(o.mmid and o.mmid.mmtype in ('S','B') for o in self.outputs)
 
-	def is_in_mempool(self):
-		return 'size' in g.rpch.getmempoolentry(self.coin_txid,on_fail='silent')
+	def get_status(self,status=False):
 
-	def is_in_wallet(self):
-		ret = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
-		if 'confirmations' in ret and ret['confirmations'] > 0:
-			return ret['confirmations']
-		else:
-			return False
+		class r(object): pass
 
-	def is_replaced(self):
-		if self.is_in_mempool(): return False
-		ret = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
-		if not 'bip125-replaceable' in ret or not 'confirmations' in ret or ret['confirmations'] > 0:
-			return False
-		return -ret['confirmations'] + 1,ret # 1: replacement in mempool, 2: replacement confirmed
+		def is_in_wallet():
+			ret = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
+			if 'confirmations' in ret and ret['confirmations'] > 0:
+				r.confs = ret['confirmations']
+				return True
+			else:
+				return False
 
-	def is_in_utxos(self):
-		return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
+		def is_in_utxos():
+			return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
 
-	def get_status(self,status=False):
-		if self.is_in_mempool():
+		def is_in_mempool():
+			return 'size' in g.rpch.getmempoolentry(self.coin_txid,on_fail='silent')
+
+		def is_replaced():
+			if is_in_mempool(): return False
+			ret = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
+
+			if not 'bip125-replaceable' in ret or not 'confirmations' in ret or ret['confirmations'] > 0:
+				return False
+
+			r.replacing_confs = -ret['confirmations']
+			r.replacing_txs = ret['walletconflicts']
+			return True
+
+		if is_in_mempool():
 			if status:
 				d = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
 				brs = 'bip125-replaceable'
-				r = '{}replaceable'.format(('NOT ','')[brs in d and d[brs]=='yes'])
+				rep = '{}replaceable'.format(('NOT ','')[brs in d and d[brs]=='yes'])
 				t = d['timereceived']
 				m = 'Sent {} ({} h/m/s ago)'
 				b = m.format(time.strftime('%c',time.gmtime(t)),secs_to_dhms(int(time.time()-t)))
 				if opt.quiet:
 					msg('Transaction is in mempool')
 				else:
-					msg('TX status: in mempool, {}\n{}'.format(r,b))
+					msg('TX status: in mempool, {}\n{}'.format(rep,b))
 			else:
 				msg('Warning: transaction is in mempool!')
-		elif self.is_in_wallet():
-			confs = self.is_in_wallet()
-			die(0,'Transaction has {} confirmation{}'.format(confs,suf(confs,'s')))
-		elif self.is_in_utxos():
+		elif is_in_wallet():
+			die(0,'Transaction has {} confirmation{}'.format(r.confs,suf(r.confs)))
+		elif is_in_utxos():
 			die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
-		else:
-			ret = self.is_replaced() # ret[0]==1: replacement in mempool, ret[0]==2: replacement confirmed
-			if ret and ret[0]:
-				m1 = 'Transaction has been replaced'
-				m2 = ('',', and the replacement TX is confirmed')[ret[0]==2]
-				msg('{}{}!'.format(m1,m2))
-				if not opt.quiet:
-					msg('Replacing transactions:')
-					rt = ret[1]['walletconflicts']
-					for t,s in [(tx,'size' in g.rpch.getmempoolentry(tx,on_fail='silent')) for tx in rt]:
-						msg('  {}{}'.format(t,('',' in mempool')[s]))
-				die(0,'')
+		elif is_replaced():
+			m1 = 'Transaction has been replaced'
+			m2 = 'Replacement transaction is in mempool'
+			rc = r.replacing_confs
+			if rc:
+				m2 = 'Replacement transaction has {} confirmation{}'.format(rc,suf(rc))
+			msg('{}\n{}'.format(m1,m2))
+			if not opt.quiet:
+				msg('Replacing transactions:')
+				d = ((t,g.rpch.getmempoolentry(t,on_fail='silent')) for t in r.replacing_txs)
+				for txid,mp_entry in d:
+					msg('  {}{}'.format(txid,' in mempool' if ('size' in mp_entry) else ''))
+			die(0,'')
 
 	def confirm_send(self):
 		m1 = ("Once this transaction is sent, there's no taking it back!",'')[bool(opt.quiet)]

+ 57 - 2
test/test_py_d/ts_regtest.py

@@ -22,6 +22,7 @@ ts_regtest.py: Regtest tests for the test.py test suite
 
 import os,subprocess
 from decimal import Decimal
+from ast import literal_eval
 from mmgen.globalvars import g
 from mmgen.opts import opt
 from mmgen.util import die,gmsg,write_data_to_file
@@ -158,9 +159,16 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		('bob_bal2f',                "Bob's balance (showempty=1 sort=age,reverse)"),
 		('bob_rbf_send',             'sending funds to Alice (RBF)'),
 		('get_mempool1',             'mempool (before RBF bump)'),
+		('bob_rbf_status1',          'getting status of transaction'),
 		('bob_rbf_bump',             'bumping RBF transaction'),
 		('get_mempool2',             'mempool (after RBF bump)'),
+		('bob_rbf_status2',          'getting status of transaction after replacement'),
+		('bob_rbf_status3',          'getting status of replacement transaction (mempool)'),
 		('generate',                 'mining a block'),
+		('bob_rbf_status4',          'getting status of transaction after confirmed (1) replacement'),
+		('bob_rbf_status5',          'getting status of replacement transaction (confirmed)'),
+		('generate',                 'mining a block'),
+		('bob_rbf_status6',          'getting status of transaction after confirmed (2) replacement'),
 		('bob_bal3',                 "Bob's balance"),
 		('bob_pre_import',           'sending to non-imported address'),
 		('generate',                 'mining a block'),
@@ -483,6 +491,14 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		cmp_or_die(rtBals[6],ret)
 		return t
 
+	def user_txsend_status(self,user,tx_file,exp1='',exp2='',extra_args=[],bogus_send=False):
+		os.environ['MMGEN_BOGUS_SEND'] = ('','1')[bool(bogus_send)]
+		t = self.spawn('mmgen-txsend',['-d',self.tmpdir,'--'+user,'--status'] + extra_args + [tx_file])
+		os.environ['MMGEN_BOGUS_SEND'] = '1'
+		if exp1: t.expect(exp1)
+		if exp2: t.expect(exp2)
+		return t
+
 	def user_txdo(  self, user, fee, outputs_cl, outputs_list,
 					extra_args   = [],
 					wf           = None,
@@ -590,8 +606,8 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		disable_debug()
 		ret = self.spawn('mmgen-regtest',['show_mempool']).read()
 		restore_debug()
-		from ast import literal_eval
-		return literal_eval(ret.split('\n')[0]) # allow for extra output by handler at end
+		self.mempool = literal_eval(ret.split('\n')[0]) # allow for extra output by handler at end
+		return self.mempool
 
 	def get_mempool1(self):
 		mp = self._get_mempool()
@@ -600,6 +616,17 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		self.write_to_tmpfile('rbf_txid',mp[0]+'\n')
 		return 'ok'
 
+	def bob_rbf_status(self,fee,exp1,exp2='',skip_bch=False):
+		if skip_bch and not g.proto.cap('rbf'):
+			msg('skipping test {} for BCH'.format(self.test_name))
+			return 'skip'
+		ext = ',{}]{x}.testnet.sigtx'.format(fee[:-1],x='-α' if g.debug_utf8 else '')
+		txfile = self.get_file_with_ext(ext,delete=False,no_dot=True)
+		return self.user_txsend_status('bob',txfile,exp1,exp2)
+
+	def bob_rbf_status1(self):
+		return self.bob_rbf_status(rtFee[1],'in mempool, replaceable',skip_bch=True)
+
 	def get_mempool2(self):
 		if not g.proto.cap('rbf'):
 			msg('Skipping post-RBF mempool check'); return 'skip'
@@ -611,6 +638,34 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 			rdie(2,'TX in mempool has not changed!  RBF bump failed')
 		return 'ok'
 
+	def bob_rbf_status2(self):
+		if not g.proto.cap('rbf'): return 'skip'
+		return self.bob_rbf_status(rtFee[1],
+			'Transaction has been replaced','{} in mempool'.format(self.mempool[0]),
+			skip_bch=True)
+
+	def bob_rbf_status3(self):
+		if not g.proto.cap('rbf'): return 'skip'
+		return self.bob_rbf_status(rtFee[2],'status: in mempool, replaceable',skip_bch=True)
+
+	def bob_rbf_status4(self):
+		if not g.proto.cap('rbf'): return 'skip'
+		return self.bob_rbf_status(rtFee[1],
+			'Replacement transaction has 1 confirmation',
+			'Replacing transactions:\n  {}'.format(self.mempool[0]),
+			skip_bch=True)
+
+	def bob_rbf_status5(self):
+		if not g.proto.cap('rbf'): return 'skip'
+		return self.bob_rbf_status(rtFee[2],'Transaction has 1 confirmation',skip_bch=True)
+
+	def bob_rbf_status6(self):
+		if not g.proto.cap('rbf'): return 'skip'
+		return self.bob_rbf_status(rtFee[1],
+			'Replacement transaction has 2 confirmations',
+			'Replacing transactions:\n  {}'.format(self.mempool[0]),
+			skip_bch=True)
+
 	@staticmethod
 	def _gen_pairs(n):
 		disable_debug()