Add a 'we3 test' command for verifying the compatibility of a web3 provider
This commit is contained in:
parent
bb2ab7330b
commit
983b3c9fbf
|
@ -23,6 +23,7 @@ type
|
||||||
deposits
|
deposits
|
||||||
wallets
|
wallets
|
||||||
record
|
record
|
||||||
|
web3
|
||||||
|
|
||||||
WalletsCmd* {.pure.} = enum
|
WalletsCmd* {.pure.} = enum
|
||||||
create = "Creates a new EIP-2386 wallet"
|
create = "Creates a new EIP-2386 wallet"
|
||||||
|
@ -42,6 +43,14 @@ type
|
||||||
create = "Create a new ENR"
|
create = "Create a new ENR"
|
||||||
print = "Print the content of a given 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
|
BeaconNodeConf* = object
|
||||||
logLevel* {.
|
logLevel* {.
|
||||||
defaultValue: "INFO"
|
defaultValue: "INFO"
|
||||||
|
@ -79,13 +88,11 @@ type
|
||||||
desc: "URL of the Web3 server to observe Eth1"
|
desc: "URL of the Web3 server to observe Eth1"
|
||||||
name: "web3-url" }: string
|
name: "web3-url" }: string
|
||||||
|
|
||||||
depositContractAddress* {.
|
web3Mode* {.
|
||||||
desc: "Address of the deposit contract"
|
hidden
|
||||||
name: "deposit-contract" }: Option[Eth1Address]
|
defaultValue: Web3Mode.auto
|
||||||
|
desc: "URL of the Web3 server to observe Eth1"
|
||||||
depositContractDeployedAt* {.
|
name: "web3-mode" }: Web3Mode
|
||||||
desc: "The Eth1 block number or hash where the deposit contract has been deployed"
|
|
||||||
name: "deposit-contract-block" }: Option[BlockHashOrNumber]
|
|
||||||
|
|
||||||
nonInteractive* {.
|
nonInteractive* {.
|
||||||
desc: "Do not display interative prompts. Quit on missing configuration"
|
desc: "Do not display interative prompts. Quit on missing configuration"
|
||||||
|
@ -411,6 +418,14 @@ type
|
||||||
desc: "ENR URI of the record to print"
|
desc: "ENR URI of the record to print"
|
||||||
name: "enr" .}: Record
|
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
|
ValidatorClientConf* = object
|
||||||
logLevel* {.
|
logLevel* {.
|
||||||
defaultValue: "INFO"
|
defaultValue: "INFO"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import
|
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,
|
chronos, web3, web3/ethtypes as web3Types, json, chronicles/timings,
|
||||||
eth/common/eth_types, eth/async_utils,
|
eth/common/eth_types, eth/async_utils,
|
||||||
spec/[datatypes, digest, crypto, helpers],
|
spec/[datatypes, digest, crypto, helpers],
|
||||||
|
@ -937,6 +937,35 @@ proc getEth1BlockHash*(url: string, blockId: RtBlockIdentifier): Future[BlockHas
|
||||||
finally:
|
finally:
|
||||||
await web3.close()
|
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:
|
when hasGenesisDetection:
|
||||||
proc init*(T: type Eth1Monitor,
|
proc init*(T: type Eth1Monitor,
|
||||||
db: BeaconChainDB,
|
db: BeaconChainDB,
|
||||||
|
|
|
@ -36,6 +36,8 @@ import
|
||||||
sync_manager, validator_duties, filepath,
|
sync_manager, validator_duties, filepath,
|
||||||
validator_slashing_protection, ./eth2_processor
|
validator_slashing_protection, ./eth2_processor
|
||||||
|
|
||||||
|
from eth/common/eth_types import BlockHashOrNumber
|
||||||
|
|
||||||
const
|
const
|
||||||
hasPrompt = not defined(withoutPrompt)
|
hasPrompt = not defined(withoutPrompt)
|
||||||
|
|
||||||
|
@ -71,6 +73,8 @@ func enrForkIdFromState(state: BeaconState): ENRForkID =
|
||||||
proc init*(T: type BeaconNode,
|
proc init*(T: type BeaconNode,
|
||||||
rng: ref BrHmacDrbgContext,
|
rng: ref BrHmacDrbgContext,
|
||||||
conf: BeaconNodeConf,
|
conf: BeaconNodeConf,
|
||||||
|
depositContractAddress: Eth1Address,
|
||||||
|
depositContractDeployedAt: BlockHashOrNumber,
|
||||||
eth1Network: Option[Eth1Network],
|
eth1Network: Option[Eth1Network],
|
||||||
genesisStateContents: ref string,
|
genesisStateContents: ref string,
|
||||||
genesisDepositsSnapshotContents: ref string): Future[BeaconNode] {.async.} =
|
genesisDepositsSnapshotContents: ref string): Future[BeaconNode] {.async.} =
|
||||||
|
@ -133,32 +137,22 @@ proc init*(T: type BeaconNode,
|
||||||
fatal "Web3 URL not specified"
|
fatal "Web3 URL not specified"
|
||||||
quit 1
|
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
|
# TODO Could move this to a separate "GenesisMonitor" process or task
|
||||||
# that would do only this - see Paul's proposal for this.
|
# that would do only this - see Paul's proposal for this.
|
||||||
let eth1MonitorRes = await Eth1Monitor.init(
|
let eth1MonitorRes = await Eth1Monitor.init(
|
||||||
db,
|
db,
|
||||||
conf.runtimePreset,
|
conf.runtimePreset,
|
||||||
conf.web3Url,
|
conf.web3Url,
|
||||||
conf.depositContractAddress.get,
|
depositContractAddress,
|
||||||
conf.depositContractDeployedAt.get,
|
depositContractDeployedAt,
|
||||||
eth1Network)
|
eth1Network)
|
||||||
|
|
||||||
if eth1MonitorRes.isErr:
|
if eth1MonitorRes.isErr:
|
||||||
fatal "Failed to start Eth1 monitor",
|
fatal "Failed to start Eth1 monitor",
|
||||||
reason = eth1MonitorRes.error,
|
reason = eth1MonitorRes.error,
|
||||||
web3Url = conf.web3Url,
|
web3Url = conf.web3Url,
|
||||||
depositContractAddress = conf.depositContractAddress.get,
|
depositContractAddress,
|
||||||
depositContractDeployedAt = conf.depositContractDeployedAt.get
|
depositContractDeployedAt
|
||||||
quit 1
|
quit 1
|
||||||
else:
|
else:
|
||||||
eth1Monitor = eth1MonitorRes.get
|
eth1Monitor = eth1MonitorRes.get
|
||||||
|
@ -256,7 +250,6 @@ proc init*(T: type BeaconNode,
|
||||||
|
|
||||||
if eth1Monitor.isNil and
|
if eth1Monitor.isNil and
|
||||||
conf.web3Url.len > 0 and
|
conf.web3Url.len > 0 and
|
||||||
conf.depositContractAddress.isSome and
|
|
||||||
genesisDepositsSnapshotContents != nil:
|
genesisDepositsSnapshotContents != nil:
|
||||||
let genesisDepositsSnapshot = SSZ.decode(genesisDepositsSnapshotContents[],
|
let genesisDepositsSnapshot = SSZ.decode(genesisDepositsSnapshotContents[],
|
||||||
DepositContractSnapshot)
|
DepositContractSnapshot)
|
||||||
|
@ -266,7 +259,7 @@ proc init*(T: type BeaconNode,
|
||||||
db,
|
db,
|
||||||
conf.runtimePreset,
|
conf.runtimePreset,
|
||||||
conf.web3Url,
|
conf.web3Url,
|
||||||
conf.depositContractAddress.get,
|
depositContractAddress,
|
||||||
genesisDepositsSnapshot,
|
genesisDepositsSnapshot,
|
||||||
eth1Network)
|
eth1Network)
|
||||||
|
|
||||||
|
@ -274,8 +267,8 @@ proc init*(T: type BeaconNode,
|
||||||
error "Failed to start Eth1 monitor",
|
error "Failed to start Eth1 monitor",
|
||||||
reason = eth1MonitorRes.error,
|
reason = eth1MonitorRes.error,
|
||||||
web3Url = conf.web3Url,
|
web3Url = conf.web3Url,
|
||||||
depositContractAddress = conf.depositContractAddress.get,
|
depositContractAddress,
|
||||||
depositContractDeployedAt = conf.depositContractDeployedAt.get
|
depositContractDeployedAt
|
||||||
else:
|
else:
|
||||||
eth1Monitor = eth1MonitorRes.get
|
eth1Monitor = eth1MonitorRes.get
|
||||||
|
|
||||||
|
@ -862,6 +855,10 @@ proc initializeNetworking(node: BeaconNode) {.async.} =
|
||||||
|
|
||||||
await node.network.start()
|
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) =
|
proc start(node: BeaconNode) =
|
||||||
let
|
let
|
||||||
head = node.chainDag.head
|
head = node.chainDag.head
|
||||||
|
@ -887,7 +884,7 @@ proc start(node: BeaconNode) =
|
||||||
|
|
||||||
waitFor node.initializeNetworking()
|
waitFor node.initializeNetworking()
|
||||||
|
|
||||||
if node.eth1Monitor != nil and node.attachedValidators.count > 0:
|
if node.eth1Monitor != nil and node.shouldWeStartWeb3:
|
||||||
node.eth1Monitor.start()
|
node.eth1Monitor.start()
|
||||||
|
|
||||||
node.run()
|
node.run()
|
||||||
|
@ -1189,6 +1186,8 @@ programMain:
|
||||||
genesisStateContents: ref string
|
genesisStateContents: ref string
|
||||||
genesisDepositsSnapshotContents: ref string
|
genesisDepositsSnapshotContents: ref string
|
||||||
eth1Network: Option[Eth1Network]
|
eth1Network: Option[Eth1Network]
|
||||||
|
depositContractAddress: Option[Eth1Address]
|
||||||
|
depositContractDeployedAt: Option[BlockHashOrNumber]
|
||||||
|
|
||||||
setupStdoutLogging(config.logLevel)
|
setupStdoutLogging(config.logLevel)
|
||||||
|
|
||||||
|
@ -1228,34 +1227,15 @@ programMain:
|
||||||
if metadata.genesisDepositsSnapshot.len > 0:
|
if metadata.genesisDepositsSnapshot.len > 0:
|
||||||
genesisDepositsSnapshotContents = newClone metadata.genesisDepositsSnapshot
|
genesisDepositsSnapshotContents = newClone metadata.genesisDepositsSnapshot
|
||||||
|
|
||||||
template checkForIncompatibleOption(flagName, fieldName) =
|
depositContractAddress = some metadata.depositContractAddress
|
||||||
# TODO: This will have to be reworked slightly when we introduce config files.
|
depositContractDeployedAt = some metadata.depositContractDeployedAt
|
||||||
# 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
|
|
||||||
eth1Network = metadata.eth1Network
|
eth1Network = metadata.eth1Network
|
||||||
else:
|
else:
|
||||||
config.runtimePreset = defaultRuntimePreset
|
config.runtimePreset = defaultRuntimePreset
|
||||||
when const_preset == "mainnet":
|
when const_preset == "mainnet":
|
||||||
if config.cmd == noCommand:
|
if config.cmd == noCommand:
|
||||||
# TODO Remove the ability to override the depositContractAddress
|
depositContractAddress = some mainnetMetadata.depositContractAddress
|
||||||
# on the command line in favour of always requiring a custom
|
depositContractDeployedAt = some mainnetMetadata.depositContractDeployedAt
|
||||||
# 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
|
|
||||||
|
|
||||||
for node in mainnetMetadata.bootstrapNodes:
|
for node in mainnetMetadata.bootstrapNodes:
|
||||||
config.bootstrapNodes.add node
|
config.bootstrapNodes.add node
|
||||||
|
@ -1346,11 +1326,18 @@ programMain:
|
||||||
url = "http://" & $metricsAddress & ":" & $config.metricsPort & "/metrics"
|
url = "http://" & $metricsAddress & ":" & $config.metricsPort & "/metrics"
|
||||||
metrics.startHttpServer($metricsAddress, config.metricsPort)
|
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
|
# 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
|
# letting the default Ctrl+C handler exit is safe, since we only read from
|
||||||
# the db.
|
# the db.
|
||||||
var node = waitFor BeaconNode.init(
|
var node = waitFor BeaconNode.init(
|
||||||
rng, config, eth1Network,
|
rng, config,
|
||||||
|
depositContractAddress.get,
|
||||||
|
depositContractDeployedAt.get,
|
||||||
|
eth1Network,
|
||||||
genesisStateContents,
|
genesisStateContents,
|
||||||
genesisDepositsSnapshotContents)
|
genesisDepositsSnapshotContents)
|
||||||
|
|
||||||
|
@ -1539,3 +1526,10 @@ programMain:
|
||||||
of RecordCmd.print:
|
of RecordCmd.print:
|
||||||
echo $config.recordPrint
|
echo $config.recordPrint
|
||||||
|
|
||||||
|
of web3:
|
||||||
|
case config.web3Cmd:
|
||||||
|
of Web3Cmd.test:
|
||||||
|
waitFor testWeb3Provider(config.web3TestUrl,
|
||||||
|
depositContractAddress,
|
||||||
|
depositContractDeployedAt)
|
||||||
|
|
||||||
|
|
|
@ -20,20 +20,17 @@ type
|
||||||
template unimplemented() =
|
template unimplemented() =
|
||||||
raise (ref CatchableError)(msg: "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) =
|
proc installConfigApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
|
||||||
rpcServer.rpc("get_v1_config_fork_schedule") do () -> seq[Fork]:
|
rpcServer.rpc("get_v1_config_fork_schedule") do () -> seq[Fork]:
|
||||||
return @[node.chainDag.headState.data.data.fork]
|
return @[node.chainDag.headState.data.data.fork]
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_config_spec") do () -> JsonNode:
|
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 %*{
|
return %*{
|
||||||
"MAX_COMMITTEES_PER_SLOT": $MAX_COMMITTEES_PER_SLOT,
|
"MAX_COMMITTEES_PER_SLOT": $MAX_COMMITTEES_PER_SLOT,
|
||||||
"TARGET_COMMITTEE_SIZE": $TARGET_COMMITTEE_SIZE,
|
"TARGET_COMMITTEE_SIZE": $TARGET_COMMITTEE_SIZE,
|
||||||
|
@ -56,7 +53,7 @@ proc installConfigApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
|
||||||
"SECONDS_PER_ETH1_BLOCK": $SECONDS_PER_ETH1_BLOCK,
|
"SECONDS_PER_ETH1_BLOCK": $SECONDS_PER_ETH1_BLOCK,
|
||||||
"DEPOSIT_CHAIN_ID": $DEPOSIT_CHAIN_ID,
|
"DEPOSIT_CHAIN_ID": $DEPOSIT_CHAIN_ID,
|
||||||
"DEPOSIT_NETWORK_ID": $DEPOSIT_NETWORK_ID,
|
"DEPOSIT_NETWORK_ID": $DEPOSIT_NETWORK_ID,
|
||||||
"DEPOSIT_CONTRACT_ADDRESS": depositAddress,
|
"DEPOSIT_CONTRACT_ADDRESS": node.getDepositAddress,
|
||||||
"MIN_DEPOSIT_AMOUNT": $MIN_DEPOSIT_AMOUNT,
|
"MIN_DEPOSIT_AMOUNT": $MIN_DEPOSIT_AMOUNT,
|
||||||
"MAX_EFFECTIVE_BALANCE": $MAX_EFFECTIVE_BALANCE,
|
"MAX_EFFECTIVE_BALANCE": $MAX_EFFECTIVE_BALANCE,
|
||||||
"EJECTION_BALANCE": $EJECTION_BALANCE,
|
"EJECTION_BALANCE": $EJECTION_BALANCE,
|
||||||
|
@ -108,16 +105,7 @@ proc installConfigApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_config_deposit_contract") do () -> JsonNode:
|
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 %*{
|
return %*{
|
||||||
"chain_id": $DEPOSIT_CHAIN_ID,
|
"chain_id": $DEPOSIT_CHAIN_ID,
|
||||||
"address": depositAddress
|
"address": node.getDepositAddress
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue