Browse Source

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
The MMGen Project 3 years ago
parent
commit
ac7bced
6 changed files with 114 additions and 11 deletions
  1. 1 1
      mmgen/altcoins/eth/contract.py
  2. 1 1
      mmgen/altcoins/eth/tx.py
  3. 28 4
      mmgen/daemon.py
  4. 1 1
      mmgen/protocol.py
  5. 7 2
      mmgen/rpc.py
  6. 76 2
      test/test_py_d/ts_ethdev.py

+ 1 - 1
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:

+ 1 - 1
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'])

+ 28 - 4
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

+ 1 - 1
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

+ 7 - 2
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!!!'

+ 76 - 2
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))