mmgen-txsend: improve output of --status option
This commit is contained in:
parent
85d3336a9e
commit
54f86e0831
3 changed files with 101 additions and 40 deletions
|
|
@ -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):
|
||||
|
|
|
|||
86
mmgen/tx.py
86
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 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)]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue