txsend: add --wait and --txhex-idx options; related cleanups
This commit is contained in:
parent
2f6ef808fb
commit
69a39fad5e
14 changed files with 200 additions and 154 deletions
|
|
@ -78,7 +78,10 @@ opts_data = {
|
|||
-- -q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-- -s, --send Sign and send the transaction (the default if seed
|
||||
+ data is provided)
|
||||
-- -T, --txhex-idx=N Send only part ‘N’ of a multi-part transaction.
|
||||
+ Indexing begins with one.
|
||||
-- -v, --verbose Produce more verbose output
|
||||
e- -w, --wait Wait for transaction confirmation
|
||||
-- -W, --allow-non-wallet-swap Allow signing of swap transactions that send funds
|
||||
+ to non-wallet addresses
|
||||
-- -x, --proxy=P Fetch the swap quote via SOCKS5 proxy ‘P’ (host:port)
|
||||
|
|
|
|||
|
|
@ -90,11 +90,14 @@ opts_data = {
|
|||
+ according to BIP 125)
|
||||
-s -s, --swap-proto Swap protocol to use (Default: {x_dfl},
|
||||
+ Choices: {x_all})
|
||||
-- -T, --txhex-idx=N Send only part ‘N’ of a multi-part transaction.
|
||||
+ Indexing begins with one.
|
||||
-- -u, --subseeds= n The number of subseed pairs to scan for (default: {ss},
|
||||
+ maximum: {ss_max}). Only the default or first supplied
|
||||
+ wallet is scanned for subseeds.
|
||||
-- -v, --verbose Produce more verbose output
|
||||
b- -V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
|
||||
e- -w, --wait Wait for transaction confirmation
|
||||
-s -x, --proxy=P Fetch the swap quote via SOCKS5 proxy ‘P’ (host:port)
|
||||
e- -X, --cached-balances Use cached balances
|
||||
-- -y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
|
|
|
|||
|
|
@ -51,13 +51,16 @@ opts_data = {
|
|||
action has been successfully sent out-of-band.
|
||||
-n, --tx-proxy=P Send transaction via public TX proxy ‘P’ (supported proxies:
|
||||
{tx_proxies}). This is done via a publicly accessible web
|
||||
page, so no API key or registration is required
|
||||
page, so no API key or registration is required.
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-r, --receipt Print the receipt of the sent transaction (Ethereum only)
|
||||
-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
|
||||
-T, --txhex-idx=N Send only part ‘N’ of a multi-part transaction. Indexing
|
||||
begins with one.
|
||||
-v, --verbose Be more verbose
|
||||
-w, --wait Wait for transaction confirmation (Ethereum only)
|
||||
-x, --proxy=P Connect to TX proxy via SOCKS5 proxy ‘P’ (host:port)
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
"""
|
||||
|
|
@ -141,22 +144,15 @@ async def main():
|
|||
await tx.post_send(asi)
|
||||
sys.exit(0)
|
||||
|
||||
if cfg.status:
|
||||
if tx.coin_txid:
|
||||
cfg._util.qmsg(f'{tx.proto.coin} txid: {tx.coin_txid.hl()}')
|
||||
retval = await tx.status.display(usr_req=True, return_exit_val=True)
|
||||
if cfg.verbose:
|
||||
tx.info.view_with_prompt('View transaction details?', pause=False)
|
||||
sys.exit(retval)
|
||||
if not cfg.status or cfg.receipt:
|
||||
if tx.is_swap and not tx.check_swap_expiry():
|
||||
die(1, 'Swap quote has expired. Please re-create the transaction')
|
||||
|
||||
if tx.is_swap and not tx.check_swap_expiry():
|
||||
die(1, 'Swap quote has expired. Please re-create the transaction')
|
||||
|
||||
if not (cfg.yes or cfg.receipt):
|
||||
tx.info.view_with_prompt('View transaction details?')
|
||||
if tx.add_comment(): # edits an existing comment, returns true if changed
|
||||
if not cfg.autosign:
|
||||
tx.file.write(ask_write_default_yes=True)
|
||||
if not cfg.yes:
|
||||
tx.info.view_with_prompt('View transaction details?')
|
||||
if tx.add_comment(): # edits an existing comment, returns true if changed
|
||||
if not cfg.autosign:
|
||||
tx.file.write(ask_write_default_yes=True)
|
||||
|
||||
await tx.send(cfg, asi)
|
||||
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ class OnlineSigned(Signed, TxBase.OnlineSigned):
|
|||
msg(orange('\n'+errmsg))
|
||||
die(2, f'{m}{nl}Send of MMGen transaction {self.txid} failed')
|
||||
|
||||
def post_write(self):
|
||||
pass
|
||||
async def post_network_send(self, coin_txid):
|
||||
return True
|
||||
|
||||
class Sent(TxBase.Sent, OnlineSigned):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -15,20 +15,19 @@ proto.btc.tx.status: Bitcoin transaction status class
|
|||
import time
|
||||
|
||||
from ....tx import status as TxBase
|
||||
from ....util import msg, suf, die
|
||||
from ....util import msg, suf
|
||||
from ....util2 import format_elapsed_hr
|
||||
|
||||
class Status(TxBase.Status):
|
||||
|
||||
async def display(self, *, usr_req=False, return_exit_val=False):
|
||||
async def display(self, *, idx=''):
|
||||
|
||||
def do_exit(retval, message):
|
||||
if return_exit_val:
|
||||
def do_return(exitval, message):
|
||||
if message:
|
||||
msg(message)
|
||||
return retval
|
||||
else:
|
||||
die(retval, message)
|
||||
return exitval
|
||||
|
||||
assert idx == '', f'multiple txhex not supported for {self.tx.proto}'
|
||||
tx = self.tx
|
||||
|
||||
class r:
|
||||
|
|
@ -82,25 +81,23 @@ class Status(TxBase.Status):
|
|||
return False
|
||||
|
||||
if await is_in_mempool():
|
||||
if usr_req:
|
||||
d = await tx.rpc.icall(
|
||||
'gettransaction',
|
||||
txid = tx.coin_txid,
|
||||
include_watchonly = True,
|
||||
verbose = False)
|
||||
rep = ('' if d.get('bip125-replaceable') == 'yes' else 'NOT ') + 'replaceable'
|
||||
t = d['timereceived']
|
||||
if tx.cfg.quiet:
|
||||
msg('Transaction is in mempool')
|
||||
else:
|
||||
msg(f'TX status: in mempool, {rep}')
|
||||
msg('Sent {} ({})'.format(time.strftime('%c', time.gmtime(t)), format_elapsed_hr(t)))
|
||||
d = await tx.rpc.icall(
|
||||
'gettransaction',
|
||||
txid = tx.coin_txid,
|
||||
include_watchonly = True,
|
||||
verbose = False)
|
||||
rep = ('' if d.get('bip125-replaceable') == 'yes' else 'NOT ') + 'replaceable'
|
||||
t = d['timereceived']
|
||||
if tx.cfg.quiet:
|
||||
msg('Transaction is in mempool')
|
||||
else:
|
||||
msg('Warning: transaction is in mempool!')
|
||||
msg(f'TX status: in mempool, {rep}')
|
||||
msg('Sent {} ({})'.format(time.strftime('%c', time.gmtime(t)), format_elapsed_hr(t)))
|
||||
return do_return(0, '')
|
||||
elif await is_in_wallet():
|
||||
return do_exit(0, f'Transaction has {r.confs} confirmation{suf(r.confs)}')
|
||||
return do_return(0, f'Transaction has {r.confs} confirmation{suf(r.confs)}')
|
||||
elif await is_in_utxos():
|
||||
return do_exit(4, 'ERROR: transaction is in the blockchain (but not in the tracking wallet)!')
|
||||
return do_return(4, 'ERROR: transaction is in the blockchain (but not in the tracking wallet)!')
|
||||
elif await is_replaced():
|
||||
msg('Transaction has been replaced')
|
||||
msg('Replacement transaction ' + (
|
||||
|
|
@ -117,4 +114,4 @@ class Status(TxBase.Status):
|
|||
d.append({})
|
||||
for txid, mp_entry in zip(r.replacing_txs, d):
|
||||
msg(f' {txid}' + (' in mempool' if 'height' in mp_entry else ''))
|
||||
return do_exit(0, '')
|
||||
return do_return(0, '')
|
||||
|
|
|
|||
|
|
@ -54,11 +54,29 @@ class Base(TxBase):
|
|||
def is_replaceable(self):
|
||||
return True
|
||||
|
||||
# used for testing only:
|
||||
async def get_receipt(self, txid):
|
||||
rx = await self.rpc.call('eth_getTransactionReceipt', '0x'+txid) # -> null if pending
|
||||
if not rx:
|
||||
async def get_receipt(self, txid, *, receipt_only=False):
|
||||
import asyncio
|
||||
from ....util import msg, msg_r
|
||||
|
||||
for n in range(60):
|
||||
rx = await self.rpc.call('eth_getTransactionReceipt', '0x'+txid) # -> null if pending
|
||||
if rx or not self.cfg.wait:
|
||||
break
|
||||
if n == 0:
|
||||
msg_r('Waiting for first confirmation..')
|
||||
await asyncio.sleep(1)
|
||||
msg_r('.')
|
||||
|
||||
if rx:
|
||||
if n:
|
||||
msg('OK')
|
||||
if receipt_only:
|
||||
return rx
|
||||
else:
|
||||
if self.cfg.wait:
|
||||
msg('timeout exceeded!')
|
||||
return None
|
||||
|
||||
tx = await self.rpc.call('eth_getTransactionByHash', '0x'+txid)
|
||||
return namedtuple('exec_status',
|
||||
['status', 'gas_sent', 'gas_used', 'gas_price', 'contract_addr', 'tx', 'rx'])(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ proto.eth.tx.online: Ethereum online signed transaction class
|
|||
"""
|
||||
|
||||
from ....util import msg, die
|
||||
from ....color import orange
|
||||
from ....color import yellow, green, orange
|
||||
from ....tx import online as TxBase
|
||||
from .. import erigon_sleep
|
||||
from .signed import Signed, TokenSigned
|
||||
|
|
@ -42,9 +42,25 @@ class OnlineSigned(Signed, TxBase.OnlineSigned):
|
|||
await erigon_sleep(self)
|
||||
return ret.removeprefix('0x')
|
||||
|
||||
def post_write(self):
|
||||
if 'token_addr' in self.txobj and not self.txobj['to']:
|
||||
msg('Contract address: {}'.format(self.txobj['token_addr'].hl(0)))
|
||||
async def post_network_send(self, coin_txid):
|
||||
res = await self.get_receipt(coin_txid)
|
||||
if not res:
|
||||
if self.cfg.wait:
|
||||
msg('{} {} {}'.format(
|
||||
yellow('Send of'),
|
||||
coin_txid.hl(),
|
||||
yellow('failed? (failed to get receipt)')))
|
||||
return False
|
||||
msg(f'Gas sent: {res.gas_sent.hl()}\n'
|
||||
f'Gas used: {res.gas_used.hl()}')
|
||||
if res.status == 0:
|
||||
if res.gas_used == res.gas_sent:
|
||||
msg(yellow('All gas was used!'))
|
||||
die(1, f'Send of {coin_txid.hl()} failed (status=0)')
|
||||
msg(f'Status: {green(str(res.status))}')
|
||||
if res.contract_addr:
|
||||
msg('Contract address: {}'.format(res.contract_addr.hl(0)))
|
||||
return True
|
||||
|
||||
class TokenOnlineSigned(TokenSigned, OnlineSigned):
|
||||
|
||||
|
|
|
|||
|
|
@ -13,31 +13,33 @@ proto.eth.tx.status: Ethereum transaction status class
|
|||
"""
|
||||
|
||||
from ....tx import status as TxBase
|
||||
from ....util import msg, Msg, die, suf, capfirst
|
||||
from ....util import msg, suf, capfirst
|
||||
|
||||
class Status(TxBase.Status):
|
||||
|
||||
async def display(self, *, usr_req=False, return_exit_val=False, print_receipt=False, idx=''):
|
||||
async def display(self, *, idx=''):
|
||||
|
||||
def do_exit(retval, message):
|
||||
if return_exit_val:
|
||||
def do_return(exitval, message):
|
||||
if message:
|
||||
msg(message)
|
||||
return retval
|
||||
else:
|
||||
die(retval, message)
|
||||
return exitval
|
||||
|
||||
tx = self.tx
|
||||
coin_txid = '0x' + getattr(tx, f'coin_txid{idx}')
|
||||
tx_desc = 'transaction' + (f' {idx}' if idx else '')
|
||||
|
||||
async def is_in_mempool():
|
||||
if not 'full_node' in tx.rpc.caps:
|
||||
return False
|
||||
if tx.rpc.daemon.id in ('parity', 'openethereum'):
|
||||
pool = [x['hash'] for x in await tx.rpc.call('parity_pendingTransactions')]
|
||||
return coin_txid in [x['hash'] for x in await tx.rpc.call('parity_pendingTransactions')]
|
||||
elif tx.rpc.daemon.id in ('geth', 'reth', 'erigon'):
|
||||
def gen(key):
|
||||
for e in res[key].values():
|
||||
for v in e.values():
|
||||
yield v['hash']
|
||||
res = await tx.rpc.call('txpool_content')
|
||||
pool = list(res['pending']) + list(res['queued'])
|
||||
return coin_txid in pool
|
||||
return coin_txid in list(gen('queued')) + list(gen('pending'))
|
||||
|
||||
async def is_in_wallet():
|
||||
d = await tx.rpc.call('eth_getTransactionReceipt', coin_txid)
|
||||
|
|
@ -50,26 +52,18 @@ class Status(TxBase.Status):
|
|||
rx = d)
|
||||
|
||||
if await is_in_mempool():
|
||||
msg(
|
||||
'Transaction is in mempool' if usr_req else
|
||||
'Warning: transaction is in mempool!')
|
||||
return
|
||||
return do_return(0, f'{capfirst(tx_desc)} is in mempool')
|
||||
|
||||
if usr_req or print_receipt:
|
||||
ret = await is_in_wallet()
|
||||
if print_receipt:
|
||||
import json
|
||||
Msg(json.dumps(ret.rx, indent=4))
|
||||
return not ret.exec_status
|
||||
if ret:
|
||||
if tx.txobj['data'] and not tx.is_swap:
|
||||
cd = capfirst(tx.contract_desc)
|
||||
if ret.exec_status == 0:
|
||||
msg(f'{cd} failed to execute!')
|
||||
else:
|
||||
msg(f'{cd} successfully executed with status {ret.exec_status}')
|
||||
return do_exit(0, f'Transaction has {ret.confs} confirmation{suf(ret.confs)}')
|
||||
return do_exit(1, 'Transaction is neither in mempool nor blockchain!')
|
||||
if res := await is_in_wallet():
|
||||
if tx.txobj['data'] and not tx.is_swap:
|
||||
cd = capfirst(tx.contract_desc)
|
||||
msg(f'{cd} failed to execute!' if res.exec_status == 0 else
|
||||
f'{cd} successfully executed with status {res.exec_status}')
|
||||
return do_return(
|
||||
int(not res.exec_status),
|
||||
f'{capfirst(tx_desc)} has {res.confs} confirmation{suf(res.confs)}')
|
||||
|
||||
return do_return(1, f'{capfirst(tx_desc)} is neither in mempool nor blockchain!')
|
||||
|
||||
class TokenStatus(Status):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@
|
|||
tx.online: online signed transaction class
|
||||
"""
|
||||
|
||||
import sys, time, asyncio
|
||||
|
||||
from ..util import msg, Msg, ymsg, make_timestr, die
|
||||
from ..color import pink, yellow
|
||||
|
||||
from .signed import Signed, AutomountSigned
|
||||
|
||||
class OnlineSigned(Signed):
|
||||
|
|
@ -22,10 +27,7 @@ class OnlineSigned(Signed):
|
|||
return _base_proto_subclass('Status', 'status', self.proto)(self)
|
||||
|
||||
def check_swap_expiry(self):
|
||||
import time
|
||||
from ..util import msg, make_timestr
|
||||
from ..util2 import format_elapsed_hr
|
||||
from ..color import pink, yellow
|
||||
expiry = self.swap_quote_expiry
|
||||
now = int(time.time())
|
||||
t_rem = expiry - now
|
||||
|
|
@ -36,8 +38,7 @@ class OnlineSigned(Signed):
|
|||
c = make_timestr(expiry)))
|
||||
return t_rem >= 0
|
||||
|
||||
def confirm_send(self):
|
||||
from ..util import msg
|
||||
def confirm_send(self, idxs):
|
||||
from ..ui import confirm_or_raise
|
||||
confirm_or_raise(
|
||||
cfg = self.cfg,
|
||||
|
|
@ -45,6 +46,8 @@ class OnlineSigned(Signed):
|
|||
action = f'broadcast this transaction to the {self.proto.coin} {self.proto.network.upper()} network',
|
||||
expect = 'YES' if self.cfg.quiet or self.cfg.yes else 'YES, I REALLY WANT TO DO THIS')
|
||||
msg('Sending transaction')
|
||||
if len(idxs) > 1 and getattr(self, 'coin_txid2', None) and self.is_swap:
|
||||
ymsg('Warning: two transactions (approval and router) will be broadcast to the network')
|
||||
|
||||
async def post_send(self, asi):
|
||||
from . import SentTX
|
||||
|
|
@ -55,21 +58,40 @@ class OnlineSigned(Signed):
|
|||
outdir = asi.txauto_dir if asi else None,
|
||||
ask_overwrite = False,
|
||||
ask_write = False)
|
||||
tx2.post_write()
|
||||
|
||||
async def send(self, cfg, asi):
|
||||
|
||||
if not (cfg.receipt or cfg.dump_hex or cfg.test):
|
||||
self.confirm_send()
|
||||
|
||||
status_exitval = None
|
||||
sent_status = None
|
||||
all_ok = True
|
||||
idxs = ['', '2']
|
||||
|
||||
for idx in ('', '2'):
|
||||
if cfg.txhex_idx:
|
||||
if getattr(self, 'coin_txid2', None):
|
||||
if cfg.txhex_idx in ('1', '2'):
|
||||
idxs = ['' if cfg.txhex_idx == '1' else cfg.txhex_idx]
|
||||
else:
|
||||
die(1, f'{cfg.txhex_idx}: invalid parameter for --txhex-idx (must be 1 or 2)')
|
||||
else:
|
||||
die(1, 'Transaction has only one part, so --txhex-idx makes no sense')
|
||||
|
||||
if not (cfg.status or cfg.receipt or cfg.dump_hex or cfg.test):
|
||||
self.confirm_send(idxs)
|
||||
|
||||
for idx in idxs:
|
||||
if coin_txid := getattr(self, f'coin_txid{idx}', None):
|
||||
txhex = getattr(self, f'serialized{idx}')
|
||||
if cfg.receipt:
|
||||
import sys
|
||||
sys.exit(await self.status.display(print_receipt=True, idx=idx))
|
||||
if cfg.status:
|
||||
cfg._util.qmsg(f'{self.proto.coin} txid: {coin_txid.hl()}')
|
||||
if cfg.verbose:
|
||||
await self.post_network_send(coin_txid)
|
||||
status_exitval = await self.status.display(idx=idx)
|
||||
elif cfg.receipt:
|
||||
if res := await self.get_receipt(coin_txid, receipt_only=True):
|
||||
import json
|
||||
Msg(json.dumps(res, indent=4))
|
||||
else:
|
||||
msg(f'Unable to get receipt for TX {coin_txid.hl()}')
|
||||
elif cfg.dump_hex:
|
||||
from ..fileutil import write_data_to_file
|
||||
write_data_to_file(
|
||||
|
|
@ -80,29 +102,43 @@ class OnlineSigned(Signed):
|
|||
ask_overwrite = False,
|
||||
ask_tty = False)
|
||||
elif cfg.tx_proxy:
|
||||
if idx != '' and not cfg.test_suite:
|
||||
await asyncio.sleep(2)
|
||||
from .tx_proxy import send_tx
|
||||
msg(f'Sending TX: {coin_txid.hl()}')
|
||||
if ret := send_tx(cfg, txhex):
|
||||
if ret != coin_txid:
|
||||
from ..util import ymsg
|
||||
ymsg(f'Warning: txid mismatch (after sending) ({ret} != {coin_txid})')
|
||||
sent_status = 'confirm_post_send'
|
||||
elif cfg.test:
|
||||
await self.test_sendable(txhex)
|
||||
else: # node send
|
||||
msg(f'Sending TX: {coin_txid.hl()}')
|
||||
if not cfg.bogus_send:
|
||||
if idx != '':
|
||||
await asyncio.sleep(1)
|
||||
ret = await self.send_with_node(txhex)
|
||||
assert ret == coin_txid, f'txid mismatch (after sending) ({ret} != {coin_txid})'
|
||||
desc = 'BOGUS transaction NOT' if cfg.bogus_send else 'Transaction'
|
||||
from ..util import msg
|
||||
msg(desc + ' sent: ' + coin_txid.hl())
|
||||
sent_status = 'no_confirm_post_send'
|
||||
|
||||
if sent_status:
|
||||
if cfg.wait and sent_status:
|
||||
res = await self.post_network_send(coin_txid)
|
||||
if all_ok:
|
||||
all_ok = res
|
||||
|
||||
if not cfg.txhex_idx and sent_status and all_ok:
|
||||
from ..ui import keypress_confirm
|
||||
if sent_status == 'no_confirm_post_send' or not asi or keypress_confirm(
|
||||
cfg, 'Mark transaction as sent on removable device?'):
|
||||
await self.post_send(asi)
|
||||
|
||||
if status_exitval is not None:
|
||||
if cfg.verbose:
|
||||
self.info.view_with_prompt('View transaction details?', pause=False)
|
||||
sys.exit(status_exitval)
|
||||
|
||||
class AutomountOnlineSigned(AutomountSigned, OnlineSigned):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -71,14 +71,14 @@ class TxProxyClient:
|
|||
assert len(res) == 1, 'more than one matching form!'
|
||||
return res[0]
|
||||
|
||||
def cache_fn(self, desc):
|
||||
return f'{self.name}-{desc}.html'
|
||||
def cache_fn(self, desc, *, extra_desc=None):
|
||||
return '{}-{}{}.html'.format(self.name, desc, f'-{extra_desc}' if extra_desc else '')
|
||||
|
||||
def save_response(self, data, desc):
|
||||
def save_response(self, data, desc, *, extra_desc=None):
|
||||
from ..fileutil import write_data_to_file
|
||||
write_data_to_file(
|
||||
self.cfg,
|
||||
self.cache_fn(desc),
|
||||
self.cache_fn(desc, extra_desc=extra_desc),
|
||||
data,
|
||||
desc = f'{desc} page from {orange(self.host)}')
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ def send_tx(cfg, txhex):
|
|||
msg('done')
|
||||
|
||||
msg('Transaction ' + (f'sent: {txid.hl()}' if txid else 'send failed'))
|
||||
c.save_response(result_text, 'result')
|
||||
c.save_response(result_text, 'result', extra_desc=txid)
|
||||
|
||||
return txid
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ ignore = [
|
|||
"test/*.py" = [ "F401" ] # imported but unused
|
||||
"test/colortest.py" = [ "F403", "F405" ] # `import *` used
|
||||
"test/tooltest2.py" = [ "F403", "F405" ] # `import *` used
|
||||
"test/overlay/tree/*" = [ "ALL" ]
|
||||
|
||||
[tool.pylint.format]
|
||||
indent-string = "\t"
|
||||
|
|
|
|||
|
|
@ -208,9 +208,7 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe
|
|||
('token_deploy_a', 'deploying ERC20 token MM1 (SafeMath)'),
|
||||
('token_deploy_b', 'deploying ERC20 token MM1 (Owned)'),
|
||||
('token_deploy_c', 'deploying ERC20 token MM1 (Token)'),
|
||||
('wait_reth2', 'waiting for block'),
|
||||
('token_fund_user', 'transferring token funds from dev to user'),
|
||||
('wait6', 'waiting for block'),
|
||||
('token_addrgen', 'generating token addresses'),
|
||||
('token_addrimport', 'importing token addresses using token address (MM1)'),
|
||||
('token_bal1', 'the token balance'),
|
||||
|
|
@ -219,7 +217,7 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe
|
|||
'creating, signing, sending, bumping and resending a token transaction (fee-bump only)',
|
||||
('token_txdo1', 'creating, signing and sending a token transaction'),
|
||||
('token_txbump1', 'bumping the token transaction (fee-bump)'),
|
||||
('wait7', 'waiting for block'),
|
||||
('wait6', 'waiting for block'),
|
||||
('token_bal2', 'the token balance'),
|
||||
),
|
||||
'token_new_outputs': (
|
||||
|
|
@ -228,7 +226,7 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe
|
|||
('token_txbump2', 'creating a replacement token transaction (new outputs)'),
|
||||
('token_txbump2sign', 'signing the replacement transaction'),
|
||||
('token_txbump2send', 'sending the replacement transaction'),
|
||||
('wait8', 'waiting for block'),
|
||||
('wait7', 'waiting for block'),
|
||||
('token_bal3', 'the token balance'),
|
||||
)
|
||||
}
|
||||
|
|
@ -338,8 +336,7 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe
|
|||
def wait_reth1(self):
|
||||
return self._wait_for_block() if self.daemon.id == 'reth' else 'silent'
|
||||
|
||||
wait_reth2 = wait_reth1
|
||||
wait1 = wait2 = wait3 = wait4 = wait5 = wait6 = wait7 = wait8 = CmdTestEthBumpMethods._wait_for_block
|
||||
wait1 = wait2 = wait3 = wait4 = wait5 = wait6 = wait7 = CmdTestEthBumpMethods._wait_for_block
|
||||
|
||||
txsign1 = txsign2 = txbump1sign = txbump2sign = CmdTestEthBumpMethods._txsign
|
||||
txsend1 = txsend2 = txbump1send = txbump2send = CmdTestEthBumpMethods._txsend
|
||||
|
|
|
|||
|
|
@ -240,8 +240,8 @@ class CmdTestEthdevMethods:
|
|||
gas,
|
||||
mmgen_cmd = 'txdo',
|
||||
gas_price = '8G',
|
||||
num = None,
|
||||
get_receipt = True):
|
||||
num = None):
|
||||
|
||||
keyfile = joinpath(self.tmpdir, dfl_devkey_fn)
|
||||
fn = joinpath(self.tmpdir, 'mm'+str(num), key+'.bin')
|
||||
args = [
|
||||
|
|
@ -250,8 +250,8 @@ class CmdTestEthdevMethods:
|
|||
f'--gas={gas}',
|
||||
f'--contract-data={fn}',
|
||||
f'--inputs={dfl_devaddr}',
|
||||
'--yes',
|
||||
]
|
||||
'--yes'
|
||||
] + (['--wait'] if mmgen_cmd == 'txdo' else [])
|
||||
|
||||
contract_addr = self._get_contract_address(dfl_devaddr)
|
||||
if key == 'Token':
|
||||
|
|
@ -271,36 +271,30 @@ class CmdTestEthdevMethods:
|
|||
|
||||
txfile = txfile.replace('.rawtx', '.sigtx')
|
||||
t = self.spawn('mmgen-txsend',
|
||||
self.eth_opts + [txfile], no_msg=True, no_passthru_opts=['coin'])
|
||||
self.eth_opts + ['--wait', txfile], no_msg=True, no_passthru_opts=['coin'])
|
||||
|
||||
txid = self.txsend_ui_common(t,
|
||||
self.txsend_ui_common(t,
|
||||
caller = mmgen_cmd,
|
||||
quiet = mmgen_cmd == 'txdo' or not self.cfg.debug,
|
||||
contract_addr = contract_addr,
|
||||
bogus_send = False)
|
||||
|
||||
_ = strip_ansi_escapes(t.expect_getend('Contract address: '))
|
||||
assert _ == contract_addr, f'Contract address mismatch: {_} != {contract_addr}'
|
||||
|
||||
if get_receipt:
|
||||
if (await self.get_tx_receipt(txid)).status == 0:
|
||||
die(2, f'Contract {num}:{key} failed to deploy. Aborting')
|
||||
|
||||
if key == 'Token':
|
||||
imsg(f'\nToken MM{num} deployed!')
|
||||
|
||||
return t
|
||||
|
||||
async def _token_deploy_math(self, *, num, get_receipt=True, mmgen_cmd='txdo'):
|
||||
async def _token_deploy_math(self, *, num, mmgen_cmd='txdo'):
|
||||
return await self._token_deploy(
|
||||
num=num, key='SafeMath', gas=500_000, get_receipt=get_receipt, mmgen_cmd=mmgen_cmd)
|
||||
num=num, key='SafeMath', gas=500_000, mmgen_cmd=mmgen_cmd)
|
||||
|
||||
async def _token_deploy_owned(self, *, num, get_receipt=True):
|
||||
async def _token_deploy_owned(self, *, num):
|
||||
return await self._token_deploy(
|
||||
num=num, key='Owned', gas=1_000_000, get_receipt=get_receipt)
|
||||
num=num, key='Owned', gas=1_000_000)
|
||||
|
||||
async def _token_deploy_token(self, *, num, get_receipt=True):
|
||||
async def _token_deploy_token(self, *, num):
|
||||
return await self._token_deploy(
|
||||
num=num, key='Token', gas=4_000_000, gas_price='7G', get_receipt=get_receipt)
|
||||
num=num, key='Token', gas=4_000_000, gas_price='7G')
|
||||
|
||||
def _token_bal_check(self, *, pat):
|
||||
return self._bal_check(pat=pat, add_opts=['--token=MM1'])
|
||||
|
|
@ -315,7 +309,7 @@ class CmdTestEthdevMethods:
|
|||
caller = cmd,
|
||||
file_desc = 'Unsigned automount transaction')
|
||||
|
||||
async def _token_transfer_ops(self, *, op, mm_idxs, amt=1000, get_receipt=True, sid=dfl_sid):
|
||||
async def _token_transfer_ops(self, *, op, mm_idxs, amt=1000, sid=dfl_sid):
|
||||
self.spawn(msg_only=True)
|
||||
from mmgen.tool.wallet import tool_cmd
|
||||
usr_mmaddrs = [f'{sid}:E:{i}' for i in mm_idxs]
|
||||
|
|
@ -338,9 +332,14 @@ class CmdTestEthdevMethods:
|
|||
key = dfl_devkey,
|
||||
gas = self.proto.coin_amt(120000, from_unit='wei'),
|
||||
gasPrice = self.proto.coin_amt(8, from_unit='Gwei'))
|
||||
|
||||
if get_receipt and (await self.get_tx_receipt(txid)).status == 0:
|
||||
die(2, 'Transfer of token funds failed. Aborting')
|
||||
rpc = await self.rpc
|
||||
for n in range(50): # long delay for txbump
|
||||
rx = await rpc.call('eth_getTransactionReceipt', '0x' + txid) # -> null if pending
|
||||
if rx:
|
||||
break
|
||||
await asyncio.sleep(0.5)
|
||||
if not rx:
|
||||
die(1, 'tx receipt timeout exceeded')
|
||||
|
||||
async def show_bals(rpc):
|
||||
for i in range(len(usr_mmaddrs)):
|
||||
|
|
@ -1114,13 +1113,13 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
|||
def bal3(self):
|
||||
return self.bal(n='3')
|
||||
|
||||
def tx_status(self, ext, expect_str, expect_str2='', exit_val=0):
|
||||
def tx_status(self, ext, *, expect_str, expect_str2='', add_opts=[], exit_val=0):
|
||||
self.mining_delay()
|
||||
ext = ext.format('-α' if self.cfg.debug_utf8 else '')
|
||||
txfile = self.get_file_with_ext(ext, no_dot=True)
|
||||
t = self.spawn(
|
||||
'mmgen-txsend',
|
||||
self.eth_opts + ['--status', txfile],
|
||||
self.eth_opts + add_opts + ['--status', txfile],
|
||||
no_passthru_opts = ['coin'],
|
||||
exit_val = exit_val)
|
||||
t.expect(expect_str)
|
||||
|
|
@ -1129,7 +1128,10 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
|||
return t
|
||||
|
||||
def tx_status1(self):
|
||||
return self.tx_status(ext='2.345,50000]{}.regtest.sigtx', expect_str='has 1 confirmation')
|
||||
return self.tx_status(
|
||||
ext = '2.345,50000]{}.regtest.sigtx',
|
||||
add_opts = ['--verbose'],
|
||||
expect_str = 'has 1 confirmation')
|
||||
|
||||
def tx_status1a(self):
|
||||
return self.tx_status(ext='2.345,50000]{}.regtest.sigtx', expect_str='has 2 confirmations')
|
||||
|
|
@ -1333,22 +1335,6 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
|||
token_data = {'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10}
|
||||
return self.token_compile(token_data)
|
||||
|
||||
async def get_tx_receipt(self, txid):
|
||||
if self.daemon.id in ('geth', 'reth'): # workaround for mining race condition in dev mode
|
||||
await asyncio.sleep(1 if self.daemon.id == 'reth' else 0.5)
|
||||
from mmgen.tx import NewTX
|
||||
tx = await NewTX(cfg=self.cfg, proto=self.proto, target='tx')
|
||||
tx.rpc = await self.rpc
|
||||
res = await tx.get_receipt(txid)
|
||||
if not res:
|
||||
die(1, f'Error getting receipt for transaction {txid}')
|
||||
imsg(f'Gas sent: {res.gas_sent.hl():<9} {(res.gas_sent*res.gas_price).hl2(encl="()")}')
|
||||
imsg(f'Gas used: {res.gas_used.hl():<9} {(res.gas_used*res.gas_price).hl2(encl="()")}')
|
||||
imsg(f'Gas price: {res.gas_price.hl()}')
|
||||
if res.gas_used == res.gas_sent:
|
||||
omsg(yellow('Warning: all gas was used!'))
|
||||
return res
|
||||
|
||||
async def token_deploy1a(self):
|
||||
return await self._token_deploy_math(num=1)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,20 +34,19 @@ def {name}(self):
|
|||
class CmdTestEthSwapMethods:
|
||||
|
||||
async def token_deploy_a(self):
|
||||
return await self._token_deploy_math(num=1, get_receipt=False)
|
||||
return await self._token_deploy_math(num=1)
|
||||
|
||||
async def token_deploy_b(self):
|
||||
return await self._token_deploy_owned(num=1, get_receipt=False)
|
||||
return await self._token_deploy_owned(num=1)
|
||||
|
||||
async def token_deploy_c(self):
|
||||
return await self._token_deploy_token(num=1, get_receipt=False)
|
||||
return await self._token_deploy_token(num=1)
|
||||
|
||||
def token_fund_user(self):
|
||||
return self._token_transfer_ops(
|
||||
op = 'fund_user',
|
||||
mm_idxs = [1],
|
||||
amt = self.token_fund_amt,
|
||||
get_receipt = False)
|
||||
amt = self.token_fund_amt)
|
||||
|
||||
def token_addrgen(self):
|
||||
return self._token_addrgen(mm_idxs=[1], naddrs=5)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue