From ac7bced5795429877fb5842da52c6ff2f49a467f Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 5 Aug 2021 14:11:46 +0000 Subject: [PATCH] ETH: support Geth - Select with --daemon-id=geth - Geth is not tested on mainnet yet Testing: $ test/test.py -e --coin=eth --daemon-id=geth ethdev --- mmgen/altcoins/eth/contract.py | 2 +- mmgen/altcoins/eth/tx.py | 2 +- mmgen/daemon.py | 32 ++++++++++++-- mmgen/protocol.py | 2 +- mmgen/rpc.py | 9 +++- test/test_py_d/ts_ethdev.py | 78 +++++++++++++++++++++++++++++++++- 6 files changed, 114 insertions(+), 11 deletions(-) diff --git a/mmgen/altcoins/eth/contract.py b/mmgen/altcoins/eth/contract.py index 3c484996..b4c684ed 100755 --- a/mmgen/altcoins/eth/contract.py +++ b/mmgen/altcoins/eth/contract.py @@ -52,7 +52,7 @@ class TokenBase(MMGenObject): # ERC20 data = create_method_id(method_sig) + method_args if g.debug: msg('ETH_CALL {}: {}'.format(method_sig,'\n '.join(parse_abi(data)))) - ret = await self.rpc.call('eth_call',{ 'to': '0x'+self.addr, 'data': '0x'+data }) + ret = await self.rpc.call('eth_call',{ 'to': '0x'+self.addr, 'data': '0x'+data },'pending') if toUnit: return int(ret,16) * self.base_unit else: diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index 10401835..9785532c 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -396,7 +396,7 @@ class EthereumMMGenTX: return False if self.rpc.daemon.id in ('parity','openethereum'): pool = [x['hash'] for x in await self.rpc.call('parity_pendingTransactions')] - elif self.rpc.daemon.id == 'erigon': + elif self.rpc.daemon.id in ('geth','erigon'): res = await self.rpc.call('txpool_content') pmsg('txpool_content:',res) # DEBUG pool = list(res['pending']) + list(res['queued']) diff --git a/mmgen/daemon.py b/mmgen/daemon.py index ced36317..0330091e 100755 --- a/mmgen/daemon.py +++ b/mmgen/daemon.py @@ -344,7 +344,7 @@ class CoinDaemon(Daemon): 'BCH': _cd('Bitcoin Cash Node', ['bitcoin_cash_node']), 'LTC': _cd('Litecoin', ['litecoin_core']), 'XMR': _cd('Monero', ['monero']), - 'ETH': _cd('Ethereum', ['openethereum'] + (['erigon'] if g.enable_erigon else []) ), + 'ETH': _cd('Ethereum', ['openethereum','geth'] + (['erigon'] if g.enable_erigon else []) ), 'ETC': _cd('Ethereum Classic', ['parity']), } @@ -658,8 +658,34 @@ class parity_daemon(openethereum_daemon): ports_shift = _nw(100,110,120) rpc_ports = _nw(*[8545 + n for n in ports_shift]) # non-standard +class geth_daemon(CoinDaemon): + daemon_data = _dd('Geth', 1010007, '1.10.7') + version_pat = r'Geth/v(\d+)\.(\d+)\.(\d+)' + exec_fn = 'geth' + ports_shift = _nw(300,310,320) + rpc_ports = _nw(*[8545 + n for n in ports_shift]) # non-standard + use_pidfile = False + use_threads = True + datadirs = { + 'linux': [g.home_dir,'.ethereum','geth'], + 'win': [os.getenv('LOCALAPPDATA'),'Geth'] # FIXME + } + + def subclass_init(self): + self.datadir = os.path.join(self.datadir,self.id,getattr(self.proto.network_names,self.network)) + self.coind_args = list_gen( + ['--verbosity=0'], + ['--http'], + ['--http.api=eth,web3,txpool'], # ,clique,personal,net'], + [f'--http.port={self.rpc_port}'], + ['--maxpeers=0', not 'online' in self.opts], + [f'--datadir={self.datadir}'], + ['--chain=goerli', self.network=='testnet'], + ['--dev', self.network=='regtest'], + ) + # https://github.com/ledgerwatch/erigon -class erigon_daemon(CoinDaemon): +class erigon_daemon(geth_daemon): avail_opts = ('online',) daemon_data = _dd('Erigon', 2021007005, '2021.07.5') version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)' @@ -667,8 +693,6 @@ class erigon_daemon(CoinDaemon): private_ports = _nw(9090,9091,9092) # testnet and regtest are non-standard ports_shift = _nw(200,210,220) rpc_ports = _nw(*[8545 + n for n in ports_shift]) # non-standard - use_pidfile = False - use_threads = True datadirs = { 'linux': [g.home_dir,'.local','share','erigon'], 'win': [os.getenv('LOCALAPPDATA'),'Erigon'] # FIXME diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 7b3ab972..9e294d08 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -407,7 +407,7 @@ class CoinProtocol(MMGenObject): 61: 'classic', # ethereum classic mainnet 62: 'morden', # ethereum classic testnet 17: 'developmentchain', # parity dev chain - 1337: 'developmentchain', # erigon dev chain + 1337: 'developmentchain', # geth dev chain } @property diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 2a9bb238..19b3c263 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -624,8 +624,9 @@ class EthereumRPCClient(RPCClient,metaclass=aInitMeta): self.caps += ('full_node',) self.chainID = None if ci == 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 == 'erigon': - daemon_warning(self.daemon.id) + elif self.daemon.id in ('geth','erigon'): + if self.daemon.network == 'mainnet': + daemon_warning(self.daemon.id) self.caps += ('full_node',) self.chainID = Int(ci,16) self.chain = self.proto.chain_ids[self.chainID] @@ -718,6 +719,10 @@ class MoneroWalletRPCClient(MoneroRPCClient): class daemon_warning(oneshot_warning): + class geth: + color = 'yellow' + message = 'Geth has not been tested on mainnet. You may experience problems.' + class erigon: color = 'red' message = 'Erigon support is EXPERIMENTAL. Use at your own risk!!!' diff --git a/test/test_py_d/ts_ethdev.py b/test/test_py_d/ts_ethdev.py index 67519645..e2111d46 100755 --- a/test/test_py_d/ts_ethdev.py +++ b/test/test_py_d/ts_ethdev.py @@ -152,7 +152,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): ('wallet_upgrade2', 'upgrading the tracking wallet (v2 -> v3)'), ('addrgen', 'generating addresses'), ('addrimport', 'importing addresses'), - ('addrimport_dev_addr', "importing OpenEthereum dev address 'Ox00a329c..'"), + ('addrimport_dev_addr', "importing dev faucet address 'Ox00a329c..'"), ('txcreate1', 'creating a transaction (spend from dev address to address :1)'), ('txview1_raw', 'viewing the raw transaction'), @@ -333,13 +333,87 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): for d in ('mm1','mm2'): copytree(os.path.join(srcdir,d),os.path.join(self.tmpdir,d)) if not opt.no_daemon_autostart: - if not start_test_daemons(self.proto.coin+'_rt',remove_datadir=True): + if g.daemon_id == 'geth': + self.geth_setup() + if not start_test_daemons( + self.proto.coin+'_rt', + remove_datadir = not g.daemon_id=='geth' ): return False from mmgen.rpc import rpc_init rpc = await rpc_init(self.proto) imsg('Daemon: {} v{}'.format(rpc.daemon.coind_name,rpc.daemon_version_str)) return 'ok' + def geth_setup(self): + + def make_key(keystore): + pwfile = joinpath(self.tmpdir,'account_passwd') + write_to_file(pwfile,'') + run(['rm','-rf',keystore]) + cmd = f'geth account new --password={pwfile} --lightkdf --keystore {keystore}' + cp = run(cmd.split(),stdout=PIPE,stderr=PIPE) + if cp.returncode: + die(1,cp.stderr.decode()) + keyfile = os.path.join(keystore,os.listdir(keystore)[0]) + return json.loads(open(keyfile).read())['address'] + + def make_genesis(signer_addr,prealloc_addr,prealloc_amt): + return { + 'config': { + 'chainId': 1337, # TODO: replace constant with var + 'homesteadBlock': 0, + 'eip150Block': 0, + 'eip155Block': 0, + 'eip158Block': 0, + 'byzantiumBlock': 0, + 'constantinopleBlock': 0, + 'petersburgBlock': 0, + 'clique': { + 'period': 0, + 'epoch': 30000 + } + }, + 'difficulty': '1', + 'gasLimit': '8000000', + 'extradata': '0x' + 64*'0' + signer_addr + 130*'0', + 'alloc': { + prealloc_addr: { 'balance': str(prealloc_amt.toWei()) } + } + } + + def init_genesis(fn): + cmd = f'geth init --datadir {d.datadir} {fn}' + cp = run(cmd.split(),stdout=PIPE,stderr=PIPE) + if cp.returncode: + die(1,cp.stderr.decode()) + + from mmgen.daemon import CoinDaemon + import json + + d = CoinDaemon(proto=self.proto,test_suite=True) + d.stop(quiet=True) + d.remove_datadir() + + imsg(cyan('Initializing Geth:')) + + keystore = os.path.relpath(os.path.join(d.datadir,'keystore')) + imsg(f' Keystore: {keystore}') + + signer_addr = make_key(keystore) + imsg(f' Signer address: {signer_addr}') + + prealloc_amt = ETHAmt('1_000_000_000') + imsg(f' Faucet: {dfl_addr} ({prealloc_amt} ETH)') + + genesis_data = make_genesis(signer_addr,dfl_addr,prealloc_amt) + + genesis_fn = joinpath(self.tmpdir,'genesis.json') + imsg(f' Genesis block data: {genesis_fn}') + + write_to_file( genesis_fn, json.dumps(genesis_data,indent=' ')+'\n' ) + + init_genesis(genesis_fn) + def wallet_upgrade(self,src_file): if self.proto.coin == 'ETC': msg('skipping test {!r} for ETC'.format(self.test_name))