6 Commits 2f6e52be73 ... 1e422b2c2b

Author SHA1 Message Date
  The MMGen Project 1e422b2c2b support Rust Ethereum client (Reth) 9 months ago
  The MMGen Project 400054975b nix: upgrade nixpkgs to v24.11 9 months ago
  The MMGen Project 94bee46cb8 new `mmgen-cli` utility 9 months ago
  The MMGen Project ef8e994266 eth, cmdtest.py ethdev: minor changes, cleanups 9 months ago
  The MMGen Project 6ceff42ff8 make `autosign` a config file option 9 months ago
  The MMGen Project 2f65a75f7e swap: Midgard -> Thornode 9 months ago
48 changed files with 311 additions and 88 deletions
  1. 1 0
      MANIFEST.in
  2. 16 0
      cmds/mmgen-cli
  3. 1 0
      doc/wiki/MMGen-Wallet-command-help.md
  4. 35 0
      doc/wiki/commands/command-help-cli.md
  5. 3 0
      mmgen/cfg.py
  6. 1 1
      mmgen/daemon.py
  7. 4 0
      mmgen/data/mmgen.cfg
  8. 1 1
      mmgen/data/version
  9. 74 0
      mmgen/main_cli.py
  10. 1 10
      mmgen/proto/btc/regtest.py
  11. 14 11
      mmgen/proto/eth/daemon.py
  12. 2 0
      mmgen/proto/eth/params.py
  13. 5 1
      mmgen/proto/eth/rpc.py
  14. 4 4
      mmgen/proto/eth/tx/base.py
  15. 1 1
      mmgen/proto/eth/tx/status.py
  16. 2 2
      mmgen/swap/proto/thorchain/__init__.py
  17. 4 4
      mmgen/swap/proto/thorchain/thornode.py
  18. 16 0
      mmgen/util2.py
  19. 1 1
      nix/merged-packages.nix
  20. 8 0
      nix/nixpkgs-24.11.nix
  21. 1 0
      nix/shell.nix
  22. 1 1
      nix/user-packages.nix
  23. 1 0
      setup.cfg
  24. 63 21
      test/cmdtest_d/ct_ethdev.py
  25. 2 6
      test/cmdtest_d/ct_input.py
  26. 20 3
      test/cmdtest_d/ct_regtest.py
  27. 4 4
      test/cmdtest_d/ct_swap.py
  28. 4 4
      test/cmdtest_d/thornode.py
  29. 1 1
      test/daemontest_d/ut_rpc.py
  30. 0 11
      test/overlay/fakemods/mmgen/swap/proto/thorchain/midgard.py
  31. 11 0
      test/overlay/fakemods/mmgen/swap/proto/thorchain/thornode.py
  32. 0 0
      test/ref/ethereum/bin/geth/mm1/ERC20Interface.bin
  33. 0 0
      test/ref/ethereum/bin/geth/mm1/Owned.bin
  34. 0 0
      test/ref/ethereum/bin/geth/mm1/SafeMath.bin
  35. 0 0
      test/ref/ethereum/bin/geth/mm1/Token.bin
  36. 0 0
      test/ref/ethereum/bin/geth/mm2/ERC20Interface.bin
  37. 0 0
      test/ref/ethereum/bin/geth/mm2/Owned.bin
  38. 0 0
      test/ref/ethereum/bin/geth/mm2/SafeMath.bin
  39. 0 0
      test/ref/ethereum/bin/geth/mm2/Token.bin
  40. 0 0
      test/ref/ethereum/bin/reth/mm1/ERC20Interface.bin
  41. 1 0
      test/ref/ethereum/bin/reth/mm1/Owned.bin
  42. 1 0
      test/ref/ethereum/bin/reth/mm1/SafeMath.bin
  43. 0 0
      test/ref/ethereum/bin/reth/mm1/Token.bin
  44. 0 0
      test/ref/ethereum/bin/reth/mm2/ERC20Interface.bin
  45. 1 0
      test/ref/ethereum/bin/reth/mm2/Owned.bin
  46. 1 0
      test/ref/ethereum/bin/reth/mm2/SafeMath.bin
  47. 0 0
      test/ref/ethereum/bin/reth/mm2/Token.bin
  48. 5 1
      test/test-release.d/cfg.sh

+ 1 - 0
MANIFEST.in

@@ -16,6 +16,7 @@ include test/*/*.py
 include test/ref/*
 include test/ref/*/*
 include test/ref/*/*/*/*
+include test/ref/*/*/*/*/*
 include test/overlay/fakemods/mmgen/*.py
 include test/overlay/fakemods/mmgen/*/*.py
 include test/overlay/fakemods/mmgen/*/*/*/*.py

+ 16 - 0
cmds/mmgen-cli

@@ -0,0 +1,16 @@
+#!/usr/bin/env python3
+#
+# MMGen Wallet, a terminal-based cryptocurrency wallet
+# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
+# Licensed under the GNU General Public License, Version 3:
+#   https://www.gnu.org/licenses
+# Public project repositories:
+#   https://github.com/mmgen/mmgen-wallet
+#   https://gitlab.com/mmgen/mmgen-wallet
+
+"""
+mmgen-cli: Communicate with a coin daemon via its JSON-RPC interface
+"""
+
+from mmgen.main import launch
+launch(mod='cli')

+ 1 - 0
doc/wiki/MMGen-Wallet-command-help.md

@@ -1,6 +1,7 @@
 * [mmgen-addrgen](commands/command-help-addrgen.md)
 * [mmgen-addrimport](commands/command-help-addrimport.md)
 * [mmgen-autosign](commands/command-help-autosign.md)
+* [mmgen-cli](commands/command-help-cli.md)
 * [mmgen-keygen](commands/command-help-keygen.md)
 * [mmgen-msg](commands/command-help-msg.md)
 * [mmgen-passchg](commands/command-help-passchg.md)

+ 35 - 0
doc/wiki/commands/command-help-cli.md

@@ -0,0 +1,35 @@
+```text
+  MMGEN-CLI: Communicate with a coin daemon via its JSON-RPC interface
+  USAGE:     mmgen-cli [opts] <command> <command args>
+  OPTIONS:
+  -h, --help             Print this help message
+      --longhelp         Print help message for long (global) options
+  -a, --ascii-output     Ensure that output is ASCII encoded
+  -w, --wallet NAME      Use tracking wallet with name NAME
+
+
+  The utility accepts all MMGen global configuration options and sources the user
+  config file, allowing users to preconfigure hosts, ports, passwords, datadirs,
+  tracking wallets and so forth, thus saving a great deal of typing at the
+  command line. This behavior may be overridden with the --skip-cfg-file option.
+
+  Arguments are given in JSON format, with lowercase ‘true’, ‘false’ and ‘null’
+  for booleans and None, and double-quoted strings in dicts and lists.
+
+
+                                     EXAMPLES
+
+    $ mmgen-cli --wallet=wallet2 listreceivedbyaddress 0 true
+
+    $ mmgen-cli --coin=ltc --rpc-host=orion getblockcount
+
+    $ mmgen-cli --regtest=1 --wallet=bob getbalance
+
+    $ mmgen-cli --coin=eth eth_getBalance 0x00000000219ab540356cBB839Cbe05303d7705Fa latest
+
+    $ mmgen-cli createrawtransaction \
+      '[{"txid":"832f5aa9af55dc453314e26869c8f96db1f2a9acac9f23ae18d396903971e0c6","vout":0}]' \
+      '[{"1111111111111111111114oLvT2":0.001}]'
+
+  MMGEN v15.1.dev19              March 2025                       MMGEN-CLI(1)
+```

+ 3 - 0
mmgen/cfg.py

@@ -62,6 +62,7 @@ class GlobalConstants(Lockable):
 		'addrgen':      _cc(True,  False, True,  None,    [],      'lmw'),
 		'addrimport':   _cc(True,  True,  True,  None,    ['tw'],  'lmw'),
 		'autosign':     _cc(True,  True,  False, '-rRb',  ['rpc'], 'lm'),
+		'cli':          _cc(True,  True,  True,  None,    ['tw'],  'lmw'),
 		'keygen':       _cc(True,  False, True,  None,    [],      'lmw'),
 		'msg':          _cc(True,  True,  True,  None,    ['msg'], 'lmw'),
 		'passchg':      _cc(False, False, False, None,    [],      'lmw'),
@@ -221,6 +222,7 @@ class Config(Lockable):
 	force_standalone_scrypt_module = False
 	enable_erigon                  = False
 	autochg_ignore_labels          = False
+	autosign                       = False
 
 	# regtest:
 	bob          = False
@@ -287,6 +289,7 @@ class Config(Lockable):
 	# coin-specific only:  bch_cashaddr (alias of cashaddr)
 	_cfg_file_opts = (
 		'autochg_ignore_labels',
+		'autosign',
 		'color',
 		'daemon_data_dir',
 		'debug',

+ 1 - 1
mmgen/daemon.py

@@ -287,7 +287,7 @@ class CoinDaemon(Daemon):
 		'BCH': _cd(['bitcoin_cash_node']),
 		'LTC': _cd(['litecoin_core']),
 		'XMR': _cd(['monero']),
-		'ETH': _cd(['geth', 'erigon', 'openethereum']),
+		'ETH': _cd(['geth', 'reth', 'erigon', 'openethereum']),
 		'ETC': _cd(['parity']),
 	}
 

+ 4 - 0
mmgen/data/mmgen.cfg

@@ -85,6 +85,10 @@
 # variants (see below):
 # tw_name my-other-tracking-wallet
 
+# Uncomment to make autosign with automount the default.  Can be overridden
+# on the command line with --no-autosign
+# autosign true
+
 #####################################################################
 ## RPC options. These also have coin-specific variants (see below) ##
 #####################################################################

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-15.1.dev18
+15.1.dev19

+ 74 - 0
mmgen/main_cli.py

@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# MMGen Wallet, a terminal-based cryptocurrency wallet
+# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
+# Licensed under the GNU General Public License, Version 3:
+#   https://www.gnu.org/licenses
+# Public project repositories:
+#   https://github.com/mmgen/mmgen-wallet
+#   https://gitlab.com/mmgen/mmgen-wallet
+
+"""
+mmgen-cli: Communicate with a coin daemon via its JSON-RPC interface
+"""
+
+import asyncio, json
+
+from .util2 import cliargs_convert
+from .cfg import gc, Config
+from .rpc import rpc_init, json_encoder
+
+opts_data = {
+	'text': {
+		'desc':    'Communicate with a coin daemon via its JSON-RPC interface',
+		'usage':   '[opts] <command> <command args>',
+		'options': """
+-h, --help             Print this help message
+--, --longhelp         Print help message for long (global) options
+-a, --ascii-output     Ensure that output is ASCII encoded
+-w, --wallet=NAME      Use tracking wallet with name NAME
+""",
+	'notes': """
+
+The utility accepts all {pn} global configuration options and sources the user
+config file, allowing users to preconfigure hosts, ports, passwords, datadirs,
+tracking wallets and so forth, thus saving a great deal of typing at the
+command line. This behavior may be overridden with the --skip-cfg-file option.
+
+Arguments are given in JSON format, with lowercase ‘true’, ‘false’ and ‘null’
+for booleans and None, and double-quoted strings in dicts and lists.
+
+
+                                   EXAMPLES
+
+  $ mmgen-cli --wallet=wallet2 listreceivedbyaddress 0 true
+
+  $ mmgen-cli --coin=ltc --rpc-host=orion getblockcount
+
+  $ mmgen-cli --regtest=1 --wallet=bob getbalance
+
+  $ mmgen-cli --coin=eth eth_getBalance 0x00000000219ab540356cBB839Cbe05303d7705Fa latest
+
+  $ mmgen-cli createrawtransaction \\
+    '[{{"txid":"832f5aa9af55dc453314e26869c8f96db1f2a9acac9f23ae18d396903971e0c6","vout":0}}]' \\
+    '[{{"1111111111111111111114oLvT2":0.001}}]'
+"""
+	},
+	'code': {
+		'notes': lambda cfg, s, help_notes: s.format(
+			pn = gc.proj_name)
+	}
+}
+
+cfg = Config(opts_data=opts_data)
+cmd, *args = cfg._args
+
+async def main():
+
+	c = await rpc_init(cfg)
+	ret = await c.call(cmd, *cliargs_convert(args), wallet=cfg.wallet)
+	print(
+		(ascii(ret) if cfg.ascii_output else ret) if isinstance(ret, str) else
+		json.dumps(ret, cls=json_encoder, indent=4, ensure_ascii=cfg.ascii_output))
+
+asyncio.run(main())

+ 1 - 10
mmgen/proto/btc/regtest.py

@@ -22,6 +22,7 @@ proto.btc.regtest: Coin daemon regression test mode setup and operations
 
 import os, shutil, json
 from ...util import msg, gmsg, die, capfirst, suf
+from ...util2 import cliargs_convert
 from ...protocol import init_proto
 from ...rpc import rpc_init, json_encoder
 from ...objmethods import MMGenObject
@@ -46,16 +47,6 @@ def create_data_dir(cfg, data_dir):
 	except:
 		pass
 
-def cliargs_convert(args):
-	def gen():
-		for arg in args:
-			try:
-				yield json.loads(arg) # list, dict, bool, int, null
-			except:
-				yield arg # arbitrary string
-
-	return tuple(gen())
-
 class MMGenRegtest(MMGenObject):
 
 	rpc_user     = 'bobandalice'

+ 14 - 11
mmgen/proto/eth/daemon.py

@@ -112,27 +112,30 @@ class geth_daemon(ethereum_daemon):
 
 	def init_subclass(self):
 
-		def have_authrpc():
-			from subprocess import run, PIPE
-			try:
-				return b'authrpc' in run(['geth', 'help'], check=True, stdout=PIPE).stdout
-			except:
-				return False
-
 		self.coind_args = list_gen(
-			['--verbosity=0'],
+			['node', self.id == 'reth'],
+			['--quiet', self.id == 'reth'],
+			['--verbosity=0', self.id == 'geth'],
 			['--ipcdisable'], # IPC-RPC: if path to socket is longer than 108 chars, geth fails to start
 			['--http'],
 			['--http.api=eth,web3,txpool'],
 			[f'--http.port={self.rpc_port}'],
-			[f'--authrpc.port={self.authrpc_port}', have_authrpc()],
+			[f'--authrpc.port={self.authrpc_port}'],
 			[f'--port={self.p2p_port}', self.p2p_port], # geth binds p2p port even with --maxpeers=0
-			['--maxpeers=0', not self.opt.online],
+			['--maxpeers=0', self.id == 'geth' and not self.opt.online],
 			[f'--datadir={self.datadir}', self.non_dfl_datadir],
-			['--goerli', self.network=='testnet'],
+			['--holesky', self.network=='testnet'],
 			['--dev', self.network=='regtest'],
 		)
 
+class reth_daemon(geth_daemon):
+	daemon_data = _dd('Reth', 1002002, '1.2.2')
+	version_pat = r'reth/v(\d+)\.(\d+)\.(\d+)'
+	exec_fn = 'reth'
+	datadirs = {
+		'linux': [gc.home_dir, '.local', 'share', 'reth'],
+	}
+
 # https://github.com/ledgerwatch/erigon
 class erigon_daemon(geth_daemon):
 	daemon_data = _dd('Erigon', 2022099099, '2022.99.99')

+ 2 - 0
mmgen/proto/eth/params.py

@@ -38,6 +38,7 @@ class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Secp256k1):
 	avg_bdi       = 15
 	decimal_prec  = 36
 
+	# https://www.chainid.dev
 	chain_ids = {
 		1:    'ethereum',         # ethereum mainnet
 		2:    'morden',           # morden testnet (deprecated)
@@ -50,6 +51,7 @@ class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Secp256k1):
 		17:   'developmentchain', # parity dev chain
 		1337: 'developmentchain', # geth dev chain
 		711:  'ethereum',         # geth mainnet (empty chain)
+		17000: 'holesky',         # proof-of-stake testnet
 	}
 
 	coin_cfg_opts = (

+ 5 - 1
mmgen/proto/eth/rpc.py

@@ -25,6 +25,10 @@ class daemon_warning(oneshot_warning_group):
 		color = 'yellow'
 		message = 'Geth has not been tested on mainnet. You may experience problems.'
 
+	class reth:
+		color = 'yellow'
+		message = 'Reth has not been tested on mainnet. You may experience problems.'
+
 	class erigon:
 		color = 'red'
 		message = 'Erigon support is EXPERIMENTAL. Use at your own risk!!!'
@@ -80,7 +84,7 @@ class EthereumRPCClient(RPCClient, metaclass=AsyncInit):
 				self.caps += ('full_node',)
 			self.chainID = None if ci is None else Int(ci, 16) # parity/oe return chainID only for dev chain
 			self.chain = (await self.call('parity_chain')).replace(' ', '_').replace('_testnet', '')
-		elif self.daemon.id in ('geth', 'erigon'):
+		elif self.daemon.id in ('geth', 'reth', 'erigon'):
 			if self.daemon.network == 'mainnet':
 				daemon_warning(self.daemon.id)
 			self.caps += ('full_node',)

+ 4 - 4
mmgen/proto/eth/tx/base.py

@@ -67,10 +67,10 @@ class Base(TxBase.Base):
 			gas_sent      = Int(tx['gas'], 16),
 			gas_used      = Int(rx['gasUsed'], 16),
 			gas_price     = self.proto.coin_amt(int(tx['gasPrice'], 16), from_unit='wei'),
-			contract_addr = self.proto.coin_addr(rx['contractAddress'][2:]) if rx['contractAddress'] else None,
-			tx            = tx,
-			rx            = rx,
-		)
+			contract_addr = self.proto.coin_addr(rx['contractAddress'][2:])
+				if rx['contractAddress'] else None,
+			tx = tx,
+			rx = rx)
 
 	def check_serialized_integrity(self): # TODO
 		return True

+ 1 - 1
mmgen/proto/eth/tx/status.py

@@ -33,7 +33,7 @@ class Status(TxBase.Status):
 				return False
 			if tx.rpc.daemon.id in ('parity', 'openethereum'):
 				pool = [x['hash'] for x in await tx.rpc.call('parity_pendingTransactions')]
-			elif tx.rpc.daemon.id in ('geth', 'erigon'):
+			elif tx.rpc.daemon.id in ('geth', 'reth', 'erigon'):
 				res = await tx.rpc.call('txpool_content')
 				pool = list(res['pending']) + list(res['queued'])
 			return '0x'+tx.coin_txid in pool

+ 2 - 2
mmgen/swap/proto/thorchain/__init__.py

@@ -37,7 +37,7 @@ class ExpInt4(ExpInt):
 		return ExpInt.__new__(cls, spec, prec=params.exp_prec)
 
 def rpc_client(tx, amt):
-	from .midgard import Midgard
-	return Midgard(tx, amt)
+	from .thornode import Thornode
+	return Thornode(tx, amt)
 
 from .memo import Memo as data

+ 4 - 4
mmgen/swap/proto/thorchain/midgard.py → mmgen/swap/proto/thorchain/thornode.py

@@ -9,12 +9,12 @@
 #   https://gitlab.com/mmgen/mmgen-wallet
 
 """
-swap.proto.thorchain.midgard: THORChain swap protocol network query ops
+swap.proto.thorchain.thornode: THORChain swap protocol network query ops
 """
 
 import json
 
-class MidgardRPCClient:
+class ThornodeRPCClient:
 
 	http_hdrs = {'Content-Type': 'application/json'}
 	proto = 'https'
@@ -44,12 +44,12 @@ class MidgardRPCClient:
 			timeout = timeout or self.timeout,
 			verify  = self.verify)
 
-class Midgard:
+class Thornode:
 
 	def __init__(self, tx, amt):
 		self.tx = tx
 		self.in_amt = amt
-		self.rpc = MidgardRPCClient(tx)
+		self.rpc = ThornodeRPCClient(tx)
 
 	def get_quote(self):
 		self.get_str = '/thorchain/quote/swap?from_asset={a}.{a}&to_asset={b}.{b}&amount={c}'.format(

+ 16 - 0
mmgen/util2.py

@@ -197,6 +197,22 @@ def decode_pretty_hexdump(data):
 		msg('Data not in hexdump format')
 		return False
 
+def cliargs_convert(args):
+
+	# return str instead of float for input into JSON-RPC
+	def float_parser(n):
+		return n
+
+	import json
+	def gen():
+		for arg in args:
+			try:
+				yield json.loads(arg, parse_float=float_parser) # list, dict, bool, int, null, float
+			except json.decoder.JSONDecodeError:
+				yield arg # arbitrary string
+
+	return tuple(gen())
+
 class ExpInt(int):
 	'encode or parse an integer in exponential notation with specified precision'
 

+ 1 - 1
nix/merged-packages.nix

@@ -1,7 +1,7 @@
 { add_pkgs_path }:
 
 let
-    dfl_nixpkgs = import ./nixpkgs-24.05.nix {};
+    dfl_nixpkgs = import ./nixpkgs-24.11.nix {};
     dfl_python = pkgs.python312;
     null_pkgs = {
         system-packages = {};

+ 8 - 0
nix/nixpkgs-24.11.nix

@@ -0,0 +1,8 @@
+import (
+    fetchGit {
+        url = "https://github.com/NixOS/nixpkgs.git";
+        ref = "release-24.11";
+        rev = "8b27c1239e5c421a2bbc2c65d52e4a6fbf2ff296"; # refs/tags/24.11
+        shallow = true;
+    }
+)

+ 1 - 0
nix/shell.nix

@@ -39,6 +39,7 @@ pkgs.mkShellNoCC {
         pwd=$(pwd)
         export PYTHONPATH=$pwd
         export PATH=$pwd/cmds:$pwd/.bin-override:$HOME/.local/bin:$PATH
+        export LANG="en_US.UTF-8"
 
         [ "$UID" == 0 ] || do_bin_override
     '';

+ 1 - 1
nix/user-packages.nix

@@ -8,7 +8,7 @@
 rec {
     ### Set nixpkgs globally for the MMGen environment.
     ### If you set it, make sure to uncomment the python variable assignment below.
-    # pkgs = import (bdir + /nixpkgs-24.05.nix) {};
+    # pkgs = import (bdir + /nixpkgs-24.11.nix) {};
 
     ### Set python version globally for the MMGen environment.
     ### Must be set if pkgs is set.

+ 1 - 0
setup.cfg

@@ -102,6 +102,7 @@ scripts =
 	cmds/mmgen-addrgen
 	cmds/mmgen-addrimport
 	cmds/mmgen-autosign
+	cmds/mmgen-cli
 	cmds/mmgen-keygen
 	cmds/mmgen-msg
 	cmds/mmgen-passchg

+ 63 - 21
test/cmdtest_d/ct_ethdev.py

@@ -20,7 +20,7 @@
 test.cmdtest_d.ct_ethdev: Ethdev tests for the cmdtest.py test suite
 """
 
-import sys, os, re, shutil, asyncio, json
+import sys, time, os, re, shutil, asyncio, json
 from decimal import Decimal
 from collections import namedtuple
 from subprocess import run, PIPE, DEVNULL
@@ -66,10 +66,19 @@ dfl_sid = '98831F3A'
 dfl_devaddr = '00a329c0648769a73afac7f9381e08fb43dbea72'
 dfl_devkey = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
 
+def get_reth_dev_keypair():
+	from mmgen.bip39 import bip39
+	from mmgen.bip_hd import MasterNode
+	mn = 'test test test test test test test test test test test junk' # See ‘reth node --help’
+	seed = bip39().generate_seed(mn.split())
+	m = MasterNode(cfg, seed)
+	node = m.to_chain(idx=0, coin='eth').derive_private(0)
+	return (node.key.hex(), node.address)
+
 burn_addr  = 'deadbeef'*5
 burn_addr2 = 'beadcafe'*5
 
-amt1 = '999999.12345689012345678'
+amt1 = '999777.12345689012345678'
 amt2 = '888.111122223333444455'
 
 parity_devkey_fn = 'parity.devkey'
@@ -81,9 +90,19 @@ def set_vbals(daemon_id):
 		vbal2 = '99.996560752'
 		vbal3 = '1.2314176'
 		vbal4 = '127.0287834'
-		vbal5 = '1000126.14775104212345678'
-		vbal6 = '1000126.14880104212345678'
-		vbal7 = '1000124.91891764212345678'
+		vbal5 = '999904.14775104212345678'
+		vbal6 = '999904.14880104212345678'
+		vbal7 = '999902.91891764212345678'
+		vbal9 = '1.2262504'
+	elif daemon_id == 'reth':
+		vbal1 = '1.2288334'
+		vbal2 = '99.996560752'
+		vbal3 = '1.23142525'
+		vbal3 = '1.2314176'
+		vbal4 = '127.0287834'
+		vbal5 = '999904.14775104212345678'
+		vbal6 = '999904.14880104212345678'
+		vbal7 = '999902.91891764212345678'
 		vbal9 = '1.2262504'
 	else:
 		vbal1 = '1.2288396'
@@ -153,7 +172,7 @@ token_bals = lambda k: {
 }[k]
 
 token_bals_getbalance = lambda k: {
-	'1': (vbal4, '999999.12345689012345678'),
+	'1': (vbal4, '999777.12345689012345678'),
 	'2': ('111.888877776666555545', '888.111122223333444455')
 }[k]
 
@@ -193,6 +212,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		('addrimport',          'importing addresses'),
 		('addrimport_dev_addr', "importing dev faucet address 'Ox00a329c..'"),
 		('fund_dev_address',    'funding the default (Parity dev) address'),
+		('cli_dev_balance',      'mmgen-cli eth_getBalance'),
 	),
 	'msg': (
 		'message signing',
@@ -267,8 +287,6 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		('token_deploy2a',  'deploying ERC20 token #2 (SafeMath)'),
 		('token_deploy2b',  'deploying ERC20 token #2 (Owned)'),
 		('token_deploy2c',  'deploying ERC20 token #2 (Token)'),
-
-		('contract_deploy', 'deploying contract (create, sign, send)'),
 	),
 	'token': (
 		'creating, signing, sending and bumping ERC20 token transactions',
@@ -410,6 +428,10 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		from mmgen.daemon import CoinDaemon
 		self.daemon = CoinDaemon( cfg, self.proto.coin+'_rt', test_suite=True)
 
+		if self.daemon.id == 'reth':
+			global dfl_devkey, dfl_devaddr
+			dfl_devkey, dfl_devaddr = get_reth_dev_keypair()
+
 		set_vbals(self.daemon.id)
 
 		self.using_solc = check_solc_ver()
@@ -433,16 +455,21 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		from mmgen.rpc import rpc_init
 		return await rpc_init(cfg, self.proto)
 
+	def mining_delay(self): # workaround for mining race condition in dev mode
+		if self.daemon.id == 'reth':
+			time.sleep(0.5)
+
 	async def setup(self):
 		self.spawn('', msg_only=True)
 
+		d = self.daemon
+
 		if not self.using_solc:
-			srcdir = os.path.join(self.tr.repo_root, 'test', 'ref', 'ethereum', 'bin')
+			subdir = 'reth' if d.id == 'reth' else 'geth'
+			srcdir = os.path.join(self.tr.repo_root, 'test', 'ref', 'ethereum', 'bin', subdir)
 			from shutil import copytree
-			for d in ('mm1', 'mm2'):
-				copytree(os.path.join(srcdir, d), os.path.join(self.tmpdir, d))
-
-		d = self.daemon
+			for _ in ('mm1', 'mm2'):
+				copytree(os.path.join(srcdir, _), os.path.join(self.tmpdir, _))
 
 		if d.id in ('geth', 'erigon'):
 			self.genesis_setup(d)
@@ -570,6 +597,16 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		t.expect('version')
 		return t
 
+	def cli_dev_balance(self):
+		t = self.spawn(
+			'mmgen-cli',
+			[f'--coin={self.proto.coin}', '--regtest=1', 'eth_getBalance', '0x'+dfl_devaddr, 'latest'])
+		if self.daemon.id == 'geth':
+			t.expect('0x33b2e3c91ec0e9113986000')
+		elif self.daemon.id == 'reth':
+			t.expect('0xd3c21bcecceda1000000')
+		return t
+
 	async def _wallet_upgrade(self, src_fn, expect1, expect2=None):
 		if self.proto.coin == 'ETC':
 			msg(f'skipping test {self.test_name!r} for ETC')
@@ -596,7 +633,10 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		return await self._wallet_upgrade('tracking-wallet-v2.json', 'token params field', 'network field')
 
 	def addrgen(self, addrs='1-3,11-13,21-23'):
-		t = self.spawn('mmgen-addrgen', self.eth_args + [dfl_words_file, addrs])
+		t = self.spawn(
+			'mmgen-addrgen',
+			[f'--coin={self.proto.coin}'] + self.eth_args + [dfl_words_file, addrs],
+			no_passthru_opts = True)
 		t.written_to_file('Addresses')
 		return t
 
@@ -753,6 +793,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		return self.bal(n='3')
 
 	def tx_status(self, ext, expect_str, expect_str2='', add_args=[], exit_val=0):
+		self.mining_delay()
 		ext = ext.format('-α' if cfg.debug_utf8 else '')
 		txfile = self.get_file_with_ext(ext, no_dot=True)
 		t = self.spawn(
@@ -883,6 +924,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		return self.bal(n='5')
 
 	def bal(self, n):
+		self.mining_delay()
 		t = self.spawn('mmgen-tool', self.eth_args + ['twview', 'wide=1'])
 		text = t.read(strip_color=True)
 		for addr, amt in bals(n):
@@ -893,6 +935,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		return t
 
 	def token_bal(self, n=None):
+		self.mining_delay()
 		t = self.spawn('mmgen-tool', self.eth_args + ['--token=mm1', 'twview', 'wide=1'])
 		text = t.read(strip_color=True)
 		for addr, _amt1, _amt2 in token_bals(n):
@@ -972,8 +1015,8 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 		return self.token_compile(token_data)
 
 	async def get_tx_receipt(self, txid):
-		if self.daemon.id == 'geth': # yet another Geth bug
-			await asyncio.sleep(0.5)
+		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=cfg, proto=self.proto, target='tx')
 		tx.rpc = await self.rpc
@@ -1035,16 +1078,13 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 	def bal6(self):
 		return self.bal5()
 
-	async def token_deploy2a(self):
-		return await self.token_deploy(num=2, key='SafeMath', gas=500_000)
+	async def token_deploy2a(self): # test create, sign, send:
+		return await self.token_deploy(num=2, key='SafeMath', gas=500_000, mmgen_cmd='txcreate')
 	async def token_deploy2b(self):
 		return await self.token_deploy(num=2, key='Owned',   gas=1_000_000)
 	async def token_deploy2c(self):
 		return await self.token_deploy(num=2, key='Token',   gas=4_000_000)
 
-	async def contract_deploy(self): # test create, sign, send
-		return await self.token_deploy(num=2, key='SafeMath', gas=500_000, mmgen_cmd='txcreate')
-
 	async def token_transfer_ops(self, op, amt=1000, num_tokens=2):
 		self.spawn('', msg_only=True)
 		sid = dfl_sid
@@ -1306,6 +1346,8 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
 
 	def _txcreate_refresh_balances(self, bals, args, total, adj_total, total_coin):
 
+		self.mining_delay()
+
 		if total_coin is None:
 			total_coin = self.proto.coin
 

+ 2 - 6
test/cmdtest_d/ct_input.py

@@ -110,16 +110,12 @@ class CmdTestInput(CmdTestBase):
 	def get_seed_from_stdin(self):
 		self.spawn('', msg_only=True)
 		from subprocess import run, PIPE
-		cmd = ['python3', 'cmds/mmgen-walletconv', '--in-fmt=words', '--out-fmt=words', '--outdir=test/trash']
+		cmd = ['python3', 'cmds/mmgen-walletconv', '--skip-cfg-file', '--in-fmt=words', '--out-fmt=words', '--outdir=test/trash']
 		mn = sample_mn['mmgen']['mn']
 		run_env = dict(os.environ)
 		run_env['MMGEN_TEST_SUITE'] = ''
 
-		# the test can fail the first time if cfg file has changed, so run it twice if necessary:
-		for _ in range(2):
-			cp = run(cmd, input=mn.encode(), stdout=PIPE, stderr=PIPE, env=run_env)
-			if b'written to file' in cp.stderr:
-				break
+		cp = run(cmd, input=mn.encode(), stdout=PIPE, stderr=PIPE, env=run_env)
 
 		from mmgen.color import set_vt100
 		set_vt100()

+ 20 - 3
test/cmdtest_d/ct_regtest.py

@@ -196,6 +196,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
 		'miscellaneous commands',
 		('daemon_version',         'mmgen-tool daemon_version'),
 		('halving_calculator_bob', 'halving calculator (Bob)'),
+		('cli_txcreate',           '‘mmgen-cli createrawtransaction’'),
 	),
 	'init_bob': (
 		'creating Bob’s MMGen wallet and tracking wallet',
@@ -223,7 +224,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
 		('fund_bob',                     'funding Bob’s wallet'),
 		('fund_alice',                   'funding Alice’s wallet'),
 		('generate',                     'mining a block'),
-		('bob_bal1',                     'Bob’s balance'),
+		('bob_bal1_cli',                 'Bob’s balance (via ‘mmgen-cli’)'),
 		('generate_extra_deterministic', 'generate extra blocks for deterministic run'),
 	),
 	'msg': (
@@ -547,6 +548,18 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
 		t.expect('time until halving')
 		return t
 
+	def cli_txcreate(self):
+		txid = 'beadcafe' * 8
+		return self.spawn(
+			'mmgen-cli',
+			[
+				'--regtest=1',
+				f'--coin={self.proto.coin}',
+				'createrawtransaction',
+				f'[{{"txid":"{txid}","vout":7}}]',
+				f'[{{"{self.burn_addr}":0.001}}]'
+			])
+
 	def walletgen(self, user):
 		t = self.spawn('mmgen-walletgen', ['-q', '-r0', '-p1', f'--{user}'], no_passthru_opts=True)
 		t.passphrase_new(f'new {dfl_wcls.desc}', rt_pw)
@@ -777,8 +790,12 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
 	def alice_bal2(self):
 		return self.user_bal('alice', rtBals[8])
 
-	def bob_bal1(self):
-		return self.user_bal('bob', rtFundAmt, self._cashaddr_opt(0))
+	def bob_bal1_cli(self):
+		t = self.spawn(
+			'mmgen-cli',
+			['--regtest=1', '--wallet=bob', f'--coin={self.proto.coin}', 'getbalance', '*', '0', 'true'])
+		t.expect(rtFundAmt + '.00')
+		return t
 
 	def bob_bal2(self):
 		return self.user_bal('bob', rtBals[0], self._cashaddr_opt(1))

+ 4 - 4
test/cmdtest_d/ct_swap.py

@@ -17,7 +17,7 @@ from pathlib import Path
 from mmgen.protocol import init_proto
 from ..include.common import make_burn_addr, gr_uc
 from .common import dfl_bip39_file
-from .midgard import run_midgard_server
+from .thornode import run_thornode_server
 
 from .ct_autosign import CmdTestAutosign, CmdTestAutosignThreaded
 from .ct_regtest import CmdTestRegtest, rt_data, dfl_wcls, rt_pw, cfg, strip_ansi_escapes
@@ -25,9 +25,9 @@ from .ct_regtest import CmdTestRegtest, rt_data, dfl_wcls, rt_pw, cfg, strip_ans
 sample1 = gr_uc[:24]
 sample2 = '00010203040506'
 
-def midgard_server_start():
+def thornode_server_start():
 	import threading
-	t = threading.Thread(target=run_midgard_server, name='Midgard server thread')
+	t = threading.Thread(target=run_thornode_server, name='Thornode server thread')
 	t.daemon = True
 	t.start()
 
@@ -175,7 +175,7 @@ class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
 
 		self.protos = [init_proto(cfg, k, network='regtest', need_amt=True) for k in ('btc', 'ltc', 'bch')]
 
-		midgard_server_start() # TODO: stop server when test group finishes executing
+		thornode_server_start() # TODO: stop server when test group finishes executing
 
 		self.opts.append('--bob')
 

+ 4 - 4
test/cmdtest_d/midgard.py → test/cmdtest_d/thornode.py

@@ -78,12 +78,12 @@ class handler(BaseHTTPRequestHandler):
 	header = b'HTTP/1.1 200 OK\nContent-type: application/json\n\n'
 
 	def do_GET(self):
-		# print(f'Midgard server received:\n  {self.requestline}')
+		# print(f'Thornode server received:\n  {self.requestline}')
 		self.wfile.write(self.header + json.dumps(create_data(self.requestline)).encode())
 
-def run_midgard_server(server_class=HTTPServer, handler_class=handler):
-	print('Midgard server listening on port 18800')
+def run_thornode_server(server_class=HTTPServer, handler_class=handler):
+	print('Thornode server listening on port 18800')
 	server_address = ('localhost', 18800)
 	httpd = server_class(server_address, handler_class)
 	httpd.serve_forever()
-	print('Midgard server exiting')
+	print('Thornode server exiting')

+ 1 - 1
test/daemontest_d/ut_rpc.py

@@ -201,7 +201,7 @@ class unit_tests:
 				'rpc_port':     32323, # ignored
 				'btc_tw_name': 'ignored',
 				'tw_name':     'also-ignored',
-				'eth_testnet_chain_names': ['goerli', 'foo', 'bar', 'baz'],
+				'eth_testnet_chain_names': ['goerli', 'holesky', 'foo', 'bar', 'baz'],
 		})
 
 	async def erigon(self, name, ut):

+ 0 - 11
test/overlay/fakemods/mmgen/swap/proto/thorchain/midgard.py

@@ -1,11 +0,0 @@
-from .midgard_orig import *
-
-class overlay_fake_MidgardRPCClient:
-
-	proto  = 'http'
-	host   = 'localhost:18800'
-	verify = False
-
-MidgardRPCClient.proto = overlay_fake_MidgardRPCClient.proto
-MidgardRPCClient.host = overlay_fake_MidgardRPCClient.host
-MidgardRPCClient.verify = overlay_fake_MidgardRPCClient.verify

+ 11 - 0
test/overlay/fakemods/mmgen/swap/proto/thorchain/thornode.py

@@ -0,0 +1,11 @@
+from .thornode_orig import *
+
+class overlay_fake_ThornodeRPCClient:
+
+	proto  = 'http'
+	host   = 'localhost:18800'
+	verify = False
+
+ThornodeRPCClient.proto = overlay_fake_ThornodeRPCClient.proto
+ThornodeRPCClient.host = overlay_fake_ThornodeRPCClient.host
+ThornodeRPCClient.verify = overlay_fake_ThornodeRPCClient.verify

+ 0 - 0
test/ref/ethereum/bin/mm1/ERC20Interface.bin → test/ref/ethereum/bin/geth/mm1/ERC20Interface.bin


+ 0 - 0
test/ref/ethereum/bin/mm1/Owned.bin → test/ref/ethereum/bin/geth/mm1/Owned.bin


+ 0 - 0
test/ref/ethereum/bin/mm1/SafeMath.bin → test/ref/ethereum/bin/geth/mm1/SafeMath.bin


+ 0 - 0
test/ref/ethereum/bin/mm1/Token.bin → test/ref/ethereum/bin/geth/mm1/Token.bin


+ 0 - 0
test/ref/ethereum/bin/mm2/ERC20Interface.bin → test/ref/ethereum/bin/geth/mm2/ERC20Interface.bin


+ 0 - 0
test/ref/ethereum/bin/mm2/Owned.bin → test/ref/ethereum/bin/geth/mm2/Owned.bin


+ 0 - 0
test/ref/ethereum/bin/mm2/SafeMath.bin → test/ref/ethereum/bin/geth/mm2/SafeMath.bin


+ 0 - 0
test/ref/ethereum/bin/mm2/Token.bin → test/ref/ethereum/bin/geth/mm2/Token.bin


+ 0 - 0
test/ref/ethereum/bin/reth/mm1/ERC20Interface.bin


+ 1 - 0
test/ref/ethereum/bin/reth/mm1/Owned.bin

@@ -0,0 +1 @@
+6080604052348015600f57600080fd5b50600080546001600160a01b031916331790556101ca806100316000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806379ba5097146100515780638da5cb5b1461005b578063d4ee1d901461008a578063f2fde38b1461009d575b600080fd5b6100596100b0565b005b60005461006e906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b60015461006e906001600160a01b031681565b6100596100ab366004610164565b61012b565b6001546001600160a01b031633146100c757600080fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000546001600160a01b0316331461014257600080fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60006020828403121561017657600080fd5b81356001600160a01b038116811461018d57600080fd5b939250505056fea2646970667358221220bcd3e32a06d3bc97ce1339e72449373355373643c31c80385ca5f89938aca39c64736f6c634300081a0033

+ 1 - 0
test/ref/ethereum/bin/reth/mm1/SafeMath.bin

@@ -0,0 +1 @@
+6080604052348015600f57600080fd5b506102018061001f6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063a293d1e814610051578063b5931f7c14610076578063d05c78da14610089578063e6cb90131461009c575b600080fd5b61006461005f366004610134565b6100af565b60405190815260200160405180910390f35b610064610084366004610134565b6100cf565b610064610097366004610134565b6100e7565b6100646100aa366004610134565b610119565b6000828211156100be57600080fd5b6100c8828461016c565b9392505050565b60008082116100dd57600080fd5b6100c8828461017f565b60006100f382846101a1565b905082158061010a575081610108848361017f565b145b61011357600080fd5b92915050565b600061012582846101b8565b90508281101561011357600080fd5b6000806040838503121561014757600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011357610113610156565b60008261019c57634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761011357610113610156565b808201808211156101135761011361015656fea264697066735822122081e009254874ff32aa4fa09f9d8a315bf6a4e9e870bfb5d8b4e6611f659ed84964736f6c634300081a0033

File diff suppressed because it is too large
+ 0 - 0
test/ref/ethereum/bin/reth/mm1/Token.bin


+ 0 - 0
test/ref/ethereum/bin/reth/mm2/ERC20Interface.bin


+ 1 - 0
test/ref/ethereum/bin/reth/mm2/Owned.bin

@@ -0,0 +1 @@
+6080604052348015600f57600080fd5b50600080546001600160a01b031916331790556101ca806100316000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806379ba5097146100515780638da5cb5b1461005b578063d4ee1d901461008a578063f2fde38b1461009d575b600080fd5b6100596100b0565b005b60005461006e906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b60015461006e906001600160a01b031681565b6100596100ab366004610164565b61012b565b6001546001600160a01b031633146100c757600080fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000546001600160a01b0316331461014257600080fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60006020828403121561017657600080fd5b81356001600160a01b038116811461018d57600080fd5b939250505056fea2646970667358221220099e0ef07629964c2926acd072d60ab552ff4e436a34052b97f44c2afb36e84664736f6c634300081a0033

+ 1 - 0
test/ref/ethereum/bin/reth/mm2/SafeMath.bin

@@ -0,0 +1 @@
+6080604052348015600f57600080fd5b506102018061001f6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063a293d1e814610051578063b5931f7c14610076578063d05c78da14610089578063e6cb90131461009c575b600080fd5b61006461005f366004610134565b6100af565b60405190815260200160405180910390f35b610064610084366004610134565b6100cf565b610064610097366004610134565b6100e7565b6100646100aa366004610134565b610119565b6000828211156100be57600080fd5b6100c8828461016c565b9392505050565b60008082116100dd57600080fd5b6100c8828461017f565b60006100f382846101a1565b905082158061010a575081610108848361017f565b145b61011357600080fd5b92915050565b600061012582846101b8565b90508281101561011357600080fd5b6000806040838503121561014757600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011357610113610156565b60008261019c57634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761011357610113610156565b808201808211156101135761011361015656fea264697066735822122045f0003b01e5d932310b9838629204723acd115b8e8d8a25145a9d08e21cce6164736f6c634300081a0033

File diff suppressed because it is too large
+ 0 - 0
test/ref/ethereum/bin/reth/mm2/Token.bin


+ 5 - 1
test/test-release.d/cfg.sh

@@ -230,7 +230,11 @@ init_tests() {
 	t_ltc_rt="- $cmdtest_py --coin=ltc regtest"
 
 	d_eth="operations for Ethereum using devnet"
-	t_eth="geth $cmdtest_py --coin=eth ethdev"
+	t_eth="
+		geth $cmdtest_py --coin=eth ethdev
+		reth $cmdtest_py --coin=eth --daemon-id=reth ethdev
+	"
+	[ "$FAST" ]  && t_eth_skip='reth'
 
 	d_etc="operations for Ethereum Classic using devnet"
 	t_etc="parity $cmdtest_py --coin=etc ethdev"

Some files were not shown because too many files changed in this diff