mmgen-txsend: improve output of --status option

This commit is contained in:
The MMGen Project 2019-07-03 18:16:41 +00:00
commit 54f86e0831
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 101 additions and 40 deletions

View file

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

View file

@ -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 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
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_utxos(self):
return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
def get_status(self,status=False):
if self.is_in_mempool():
class r(object): pass
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():
return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
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)]

View file

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