mmgen-txsend: add --test option

Test whether a transaction is sendable without sending it

- for BTC and friends, uses the ‘testmempoolaccept’ RPC call
- not implemented for ETH
This commit is contained in:
The MMGen Project 2025-03-15 18:24:53 +00:00
commit 1f166ce458
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
5 changed files with 46 additions and 5 deletions

View file

@ -52,6 +52,7 @@ opts_data = {
-q, --quiet Suppress warnings; overwrite files without prompting
-s, --status Get status of a sent transaction (or current transaction,
whether sent or unsent, when used with --autosign)
-t, --test Test whether the transaction can be sent without sending it
-v, --verbose Be more verbose
-y, --yes Answer 'yes' to prompts, suppress non-essential output
"""
@ -66,6 +67,9 @@ if cfg.autosign and cfg.outdir:
if cfg.mark_sent and not cfg.autosign:
die(1, '--mark-sent is used only in combination with --autosign')
if cfg.test and cfg.dump_hex:
die(1, '--test cannot be used in combination with --dump-hex')
if cfg.dump_hex and cfg.dump_hex != '-':
from .fileutil import check_outfile_dir
check_outfile_dir(cfg.dump_hex)
@ -158,6 +162,8 @@ async def main():
await post_send(tx)
else:
await post_send(tx)
elif cfg.test:
await tx.test_sendable()
elif await tx.send():
await post_send(tx)

View file

@ -13,13 +13,13 @@ proto.btc.tx.online: Bitcoin online signed transaction class
"""
from ....tx import online as TxBase
from ....util import msg, die
from ....util import msg, ymsg, die
from ....color import orange
from .signed import Signed
class OnlineSigned(Signed, TxBase.OnlineSigned):
async def send(self, *, prompt_user=True):
async def send_checks(self):
self.check_correct_chain()
@ -37,6 +37,27 @@ class OnlineSigned(Signed, TxBase.OnlineSigned):
await self.status.display()
async def test_sendable(self):
await self.send_checks()
res = await self.rpc.call('testmempoolaccept', (self.serialized,))
ret = res[0]
if ret['allowed']:
from ....obj import CoinTxID
msg('TxID: {}'.format(CoinTxID(ret['txid']).hl()))
msg('Transaction can be sent')
return True
else:
ymsg('Transaction cannot be sent')
msg(ret['reject-reason'])
return False
async def send(self, *, prompt_user=True):
await self.send_checks()
if prompt_user:
self.confirm_send()

View file

@ -20,6 +20,9 @@ from .signed import Signed, TokenSigned
class OnlineSigned(Signed, TxBase.OnlineSigned):
async def test_sendable(self):
raise NotImplementedError('transaction testing not implemented for Ethereum')
async def send(self, *, prompt_user=True):
self.check_correct_chain()

View file

@ -465,6 +465,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
('bob_dump_hex_sign', 'dump_hex transaction - signing'),
('bob_dump_hex_dump_stdout', 'dump_hex transaction - dumping tx hex to stdout'),
('bob_dump_hex_dump', 'dump_hex transaction - dumping tx hex to file'),
('bob_dump_hex_test', 'dump_hex transaction - test whether TX can be sent'),
('bob_dump_hex_send_cli', 'dump_hex transaction - sending via cli'),
('generate', 'mining a block'),
('bob_bal7', 'Bob’s balance'),
@ -2241,6 +2242,12 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
assert is_hex_str(txid) and len(txid) == 64
return t
def bob_dump_hex_test(self):
txfile = get_file_with_ext(self.dump_hex_subdir, 'sigtx')
t = self.spawn('mmgen-txsend', ['--bob', '--test', txfile])
self.txsend_ui_common(t, bogus_send=False, test=True)
return t
def bob_dump_hex_send_cli(self):
return self._user_dump_hex_send_cli('bob', subdir='nochg_tx')

View file

@ -162,6 +162,7 @@ class CmdTestShared:
file_desc = 'Sent transaction',
confirm_send = True,
bogus_send = True,
test = False,
quiet = False,
has_label = False):
@ -172,16 +173,19 @@ class CmdTestShared:
t.view_tx(view)
t.do_comment(add_comment, has_label=has_label)
self._do_confirm_send(t, quiet=quiet, confirm_send=confirm_send)
if not test:
self._do_confirm_send(t, quiet=quiet, confirm_send=confirm_send)
if bogus_send:
txid = ''
t.expect('BOGUS transaction NOT sent')
else:
txid = strip_ansi_escapes(t.expect_getend('Transaction sent: '))
m = 'TxID: ' if test else 'Transaction sent: '
txid = strip_ansi_escapes(t.expect_getend(m))
assert len(txid) == 64, f'{txid!r}: Incorrect txid length!'
t.written_to_file(file_desc)
if not test:
t.written_to_file(file_desc)
return txid