From 983b3c9fbf2e92cf039f931341f378a60d83634a Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Fri, 4 Dec 2020 18:28:42 +0200 Subject: [PATCH] Add a 'we3 test' command for verifying the compatibility of a web3 provider --- beacon_chain/conf.nim | 29 ++++++++--- beacon_chain/eth1_monitor.nim | 31 ++++++++++- beacon_chain/nimbus_beacon_node.nim | 80 +++++++++++++---------------- beacon_chain/rpc/config_api.nim | 28 +++------- 4 files changed, 97 insertions(+), 71 deletions(-) diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index ae52e449f..67c0da260 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -23,6 +23,7 @@ type deposits wallets record + web3 WalletsCmd* {.pure.} = enum create = "Creates a new EIP-2386 wallet" @@ -42,6 +43,14 @@ type create = "Create a new ENR" print = "Print the content of a given ENR" + Web3Cmd* {.pure.} = enum + test = "Test a web3 provider" + + Web3Mode* {.pure.} = enum + auto = "Enabled only when validators are attached" + enabled = "Always enabled" + disabled = "Always disabled" + BeaconNodeConf* = object logLevel* {. defaultValue: "INFO" @@ -79,13 +88,11 @@ type desc: "URL of the Web3 server to observe Eth1" name: "web3-url" }: string - depositContractAddress* {. - desc: "Address of the deposit contract" - name: "deposit-contract" }: Option[Eth1Address] - - depositContractDeployedAt* {. - desc: "The Eth1 block number or hash where the deposit contract has been deployed" - name: "deposit-contract-block" }: Option[BlockHashOrNumber] + web3Mode* {. + hidden + defaultValue: Web3Mode.auto + desc: "URL of the Web3 server to observe Eth1" + name: "web3-mode" }: Web3Mode nonInteractive* {. desc: "Do not display interative prompts. Quit on missing configuration" @@ -411,6 +418,14 @@ type desc: "ENR URI of the record to print" name: "enr" .}: Record + of web3: + case web3Cmd* {.command.}: Web3Cmd + of Web3Cmd.test: + web3TestUrl* {. + argument + desc: "The web3 provider URL to test" + name: "url" }: Uri + ValidatorClientConf* = object logLevel* {. defaultValue: "INFO" diff --git a/beacon_chain/eth1_monitor.nim b/beacon_chain/eth1_monitor.nim index 16adb2313..c0cac1727 100644 --- a/beacon_chain/eth1_monitor.nim +++ b/beacon_chain/eth1_monitor.nim @@ -1,5 +1,5 @@ import - std/[deques, tables, hashes, options, strformat, strutils, sequtils], + std/[deques, tables, hashes, options, strformat, strutils, sequtils, uri], chronos, web3, web3/ethtypes as web3Types, json, chronicles/timings, eth/common/eth_types, eth/async_utils, spec/[datatypes, digest, crypto, helpers], @@ -937,6 +937,35 @@ proc getEth1BlockHash*(url: string, blockId: RtBlockIdentifier): Future[BlockHas finally: await web3.close() +proc testWeb3Provider*(web3Url: Uri, + depositContractAddress: Option[Eth1Address], + depositContractDeployedAt: Option[BlockHashOrNumber]) {.async.} = + template mustSucceed(action: static string, expr: untyped): untyped = + try: expr + except CatchableError as err: + fatal("Failed to " & action, err = err.msg) + quit 1 + + let + web3 = mustSucceed "connect to web3 provider": + await newWeb3($web3Url) + network = mustSucceed "get network version": + awaitWithRetries web3.provider.net_version() + latestBlock = mustSucceed "get latest block": + awaitWithRetries web3.provider.eth_getBlockByNumber(blockId("latest"), false) + + echo "Network: ", network + echo "Latest block: ", latestBlock.number.uint64 + + if depositContractAddress.isSome: + let ns = web3.contractSender(DepositContract, depositContractAddress.get) + try: + let depositRoot = awaitWithRetries( + ns.get_deposit_root.call(blockNumber = latestBlock.number.uint64)) + echo "Deposit root: ", depositRoot + except CatchableError as err: + echo "Web3 provider is not archive mode" + when hasGenesisDetection: proc init*(T: type Eth1Monitor, db: BeaconChainDB, diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 964cbdcfb..d5d2534f3 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -36,6 +36,8 @@ import sync_manager, validator_duties, filepath, validator_slashing_protection, ./eth2_processor +from eth/common/eth_types import BlockHashOrNumber + const hasPrompt = not defined(withoutPrompt) @@ -71,6 +73,8 @@ func enrForkIdFromState(state: BeaconState): ENRForkID = proc init*(T: type BeaconNode, rng: ref BrHmacDrbgContext, conf: BeaconNodeConf, + depositContractAddress: Eth1Address, + depositContractDeployedAt: BlockHashOrNumber, eth1Network: Option[Eth1Network], genesisStateContents: ref string, genesisDepositsSnapshotContents: ref string): Future[BeaconNode] {.async.} = @@ -133,32 +137,22 @@ proc init*(T: type BeaconNode, fatal "Web3 URL not specified" quit 1 - if conf.depositContractAddress.isNone: - fatal "Deposit contract address not specified" - quit 1 - - if conf.depositContractDeployedAt.isNone: - # When we don't have a known genesis state, the network metadata - # must specify the deployment block of the contract. - fatal "Deposit contract deployment block not specified" - quit 1 - # TODO Could move this to a separate "GenesisMonitor" process or task # that would do only this - see Paul's proposal for this. let eth1MonitorRes = await Eth1Monitor.init( db, conf.runtimePreset, conf.web3Url, - conf.depositContractAddress.get, - conf.depositContractDeployedAt.get, + depositContractAddress, + depositContractDeployedAt, eth1Network) if eth1MonitorRes.isErr: fatal "Failed to start Eth1 monitor", reason = eth1MonitorRes.error, web3Url = conf.web3Url, - depositContractAddress = conf.depositContractAddress.get, - depositContractDeployedAt = conf.depositContractDeployedAt.get + depositContractAddress, + depositContractDeployedAt quit 1 else: eth1Monitor = eth1MonitorRes.get @@ -256,7 +250,6 @@ proc init*(T: type BeaconNode, if eth1Monitor.isNil and conf.web3Url.len > 0 and - conf.depositContractAddress.isSome and genesisDepositsSnapshotContents != nil: let genesisDepositsSnapshot = SSZ.decode(genesisDepositsSnapshotContents[], DepositContractSnapshot) @@ -266,7 +259,7 @@ proc init*(T: type BeaconNode, db, conf.runtimePreset, conf.web3Url, - conf.depositContractAddress.get, + depositContractAddress, genesisDepositsSnapshot, eth1Network) @@ -274,8 +267,8 @@ proc init*(T: type BeaconNode, error "Failed to start Eth1 monitor", reason = eth1MonitorRes.error, web3Url = conf.web3Url, - depositContractAddress = conf.depositContractAddress.get, - depositContractDeployedAt = conf.depositContractDeployedAt.get + depositContractAddress, + depositContractDeployedAt else: eth1Monitor = eth1MonitorRes.get @@ -862,6 +855,10 @@ proc initializeNetworking(node: BeaconNode) {.async.} = await node.network.start() +func shouldWeStartWeb3(node: BeaconNode): bool = + (node.config.web3Mode == Web3Mode.enabled) or + (node.config.web3Mode == Web3Mode.auto and node.attachedValidators.count > 0) + proc start(node: BeaconNode) = let head = node.chainDag.head @@ -887,7 +884,7 @@ proc start(node: BeaconNode) = waitFor node.initializeNetworking() - if node.eth1Monitor != nil and node.attachedValidators.count > 0: + if node.eth1Monitor != nil and node.shouldWeStartWeb3: node.eth1Monitor.start() node.run() @@ -1189,6 +1186,8 @@ programMain: genesisStateContents: ref string genesisDepositsSnapshotContents: ref string eth1Network: Option[Eth1Network] + depositContractAddress: Option[Eth1Address] + depositContractDeployedAt: Option[BlockHashOrNumber] setupStdoutLogging(config.logLevel) @@ -1228,34 +1227,15 @@ programMain: if metadata.genesisDepositsSnapshot.len > 0: genesisDepositsSnapshotContents = newClone metadata.genesisDepositsSnapshot - template checkForIncompatibleOption(flagName, fieldName) = - # TODO: This will have to be reworked slightly when we introduce config files. - # We'll need to keep track of the "origin" of the config value, so we can - # discriminate between values from config files that can be overridden and - # regular command-line options (that may conflict). - if config.fieldName.isSome: - fatal "Invalid CLI arguments specified. You must not specify '--network' and '" & flagName & "' at the same time", - networkParam = config.eth2Network.get, `flagName` = config.fieldName.get - quit 1 - - checkForIncompatibleOption "deposit-contract", depositContractAddress - checkForIncompatibleOption "deposit-contract-block", depositContractDeployedAt - config.depositContractAddress = some metadata.depositContractAddress - config.depositContractDeployedAt = some metadata.depositContractDeployedAt + depositContractAddress = some metadata.depositContractAddress + depositContractDeployedAt = some metadata.depositContractDeployedAt eth1Network = metadata.eth1Network else: config.runtimePreset = defaultRuntimePreset when const_preset == "mainnet": if config.cmd == noCommand: - # TODO Remove the ability to override the depositContractAddress - # on the command line in favour of always requiring a custom - # nework file. We have to do this, because any user setting - # would conflict with the default choice of 'mainnet' as a - # --network value. - config.depositContractAddress = - some mainnetMetadata.depositContractAddress - config.depositContractDeployedAt = - some mainnetMetadata.depositContractDeployedAt + depositContractAddress = some mainnetMetadata.depositContractAddress + depositContractDeployedAt = some mainnetMetadata.depositContractDeployedAt for node in mainnetMetadata.bootstrapNodes: config.bootstrapNodes.add node @@ -1346,11 +1326,18 @@ programMain: url = "http://" & $metricsAddress & ":" & $config.metricsPort & "/metrics" metrics.startHttpServer($metricsAddress, config.metricsPort) + if depositContractAddress.isNone or depositContractDeployedAt.isNone: + echo "Please specify the a network through the --network option" + quit 1 + # There are no managed event loops in here, to do a graceful shutdown, but # letting the default Ctrl+C handler exit is safe, since we only read from # the db. var node = waitFor BeaconNode.init( - rng, config, eth1Network, + rng, config, + depositContractAddress.get, + depositContractDeployedAt.get, + eth1Network, genesisStateContents, genesisDepositsSnapshotContents) @@ -1539,3 +1526,10 @@ programMain: of RecordCmd.print: echo $config.recordPrint + of web3: + case config.web3Cmd: + of Web3Cmd.test: + waitFor testWeb3Provider(config.web3TestUrl, + depositContractAddress, + depositContractDeployedAt) + diff --git a/beacon_chain/rpc/config_api.nim b/beacon_chain/rpc/config_api.nim index 71464f02f..e0939596f 100644 --- a/beacon_chain/rpc/config_api.nim +++ b/beacon_chain/rpc/config_api.nim @@ -20,20 +20,17 @@ type template unimplemented() = raise (ref CatchableError)(msg: "Unimplemented") +func getDepositAddress(node: BeaconNode): string = + if isNil(node.eth1Monitor): + "" + else: + $node.eth1Monitor.depositContractAddress() + proc installConfigApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = rpcServer.rpc("get_v1_config_fork_schedule") do () -> seq[Fork]: return @[node.chainDag.headState.data.data.fork] rpcServer.rpc("get_v1_config_spec") do () -> JsonNode: - let depositAddress = - if isNil(node.eth1Monitor): - if node.config.depositContractAddress.isSome(): - $node.config.depositContractAddress.get() - else: - "" - else: - $node.eth1Monitor.depositContractAddress() - return %*{ "MAX_COMMITTEES_PER_SLOT": $MAX_COMMITTEES_PER_SLOT, "TARGET_COMMITTEE_SIZE": $TARGET_COMMITTEE_SIZE, @@ -56,7 +53,7 @@ proc installConfigApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = "SECONDS_PER_ETH1_BLOCK": $SECONDS_PER_ETH1_BLOCK, "DEPOSIT_CHAIN_ID": $DEPOSIT_CHAIN_ID, "DEPOSIT_NETWORK_ID": $DEPOSIT_NETWORK_ID, - "DEPOSIT_CONTRACT_ADDRESS": depositAddress, + "DEPOSIT_CONTRACT_ADDRESS": node.getDepositAddress, "MIN_DEPOSIT_AMOUNT": $MIN_DEPOSIT_AMOUNT, "MAX_EFFECTIVE_BALANCE": $MAX_EFFECTIVE_BALANCE, "EJECTION_BALANCE": $EJECTION_BALANCE, @@ -108,16 +105,7 @@ proc installConfigApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = } rpcServer.rpc("get_v1_config_deposit_contract") do () -> JsonNode: - let depositAddress = - if isNil(node.eth1Monitor): - if node.config.depositContractAddress.isSome(): - $node.config.depositContractAddress.get() - else: - "" - else: - $node.eth1Monitor.depositContractAddress() - return %*{ "chain_id": $DEPOSIT_CHAIN_ID, - "address": depositAddress + "address": node.getDepositAddress }