2021-03-26 06:52:01 +00:00
|
|
|
# beacon_chain
|
2023-01-06 21:01:10 +00:00
|
|
|
# Copyright (c) 2018-2023 Status Research & Development GmbH
|
2021-03-26 06:52:01 +00:00
|
|
|
# Licensed and distributed under either of
|
|
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
|
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
2023-01-20 14:14:37 +00:00
|
|
|
{.push raises: [].}
|
2021-03-26 06:52:01 +00:00
|
|
|
|
2018-11-26 13:33:06 +00:00
|
|
|
import
|
2021-12-03 15:04:58 +00:00
|
|
|
std/[deques, options, strformat, strutils, sequtils, tables,
|
2021-09-29 19:26:27 +00:00
|
|
|
typetraits, uri, json],
|
2020-12-09 22:44:59 +00:00
|
|
|
# Nimble packages:
|
2022-06-09 14:30:13 +00:00
|
|
|
chronos, metrics, chronicles/timings, stint/endians2,
|
2023-03-05 01:40:21 +00:00
|
|
|
json_rpc/[client, errors],
|
|
|
|
web3, web3/ethhexstrings, web3/engine_api,
|
|
|
|
eth/common/[eth_types, transaction],
|
2022-12-07 10:24:51 +00:00
|
|
|
eth/async_utils, stew/[byteutils, objects, results, shims/hashes],
|
2020-12-09 22:44:59 +00:00
|
|
|
# Local modules:
|
2022-12-07 10:24:51 +00:00
|
|
|
../spec/[deposit_snapshots, eth2_merkleization, forks, helpers],
|
2023-02-25 01:03:34 +00:00
|
|
|
../spec/datatypes/[base, phase0, bellatrix, deneb],
|
2021-03-05 13:12:00 +00:00
|
|
|
../networking/network_metadata,
|
2021-05-21 09:23:28 +00:00
|
|
|
../consensus_object_pools/block_pools_types,
|
2023-03-05 01:40:21 +00:00
|
|
|
".."/[beacon_chain_db, beacon_node_status, beacon_clock, future_combinators],
|
|
|
|
"."/[merkle_minimal, el_conf]
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2022-03-31 14:43:05 +00:00
|
|
|
from std/times import getTime, inSeconds, initTime, `-`
|
|
|
|
from ../spec/engine_authentication import getSignedIatToken
|
|
|
|
|
2020-06-19 17:42:28 +00:00
|
|
|
export
|
2023-03-05 01:40:21 +00:00
|
|
|
el_conf, engine_api, deques, base, DepositTreeSnapshot
|
2020-06-19 17:42:28 +00:00
|
|
|
|
2020-11-19 17:19:03 +00:00
|
|
|
logScope:
|
2023-03-05 01:40:21 +00:00
|
|
|
topics = "elmon"
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2021-10-14 12:33:55 +00:00
|
|
|
type
|
|
|
|
PubKeyBytes = DynamicBytes[48, 48]
|
|
|
|
WithdrawalCredentialsBytes = DynamicBytes[32, 32]
|
|
|
|
SignatureBytes = DynamicBytes[96, 96]
|
|
|
|
Int64LeBytes = DynamicBytes[8, 8]
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
contract(DepositContract):
|
2021-10-14 12:33:55 +00:00
|
|
|
proc deposit(pubkey: PubKeyBytes,
|
|
|
|
withdrawalCredentials: WithdrawalCredentialsBytes,
|
|
|
|
signature: SignatureBytes,
|
2020-03-24 11:13:07 +00:00
|
|
|
deposit_data_root: FixedBytes[32])
|
|
|
|
|
|
|
|
proc get_deposit_root(): FixedBytes[32]
|
2021-10-14 12:33:55 +00:00
|
|
|
proc get_deposit_count(): Int64LeBytes
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2021-10-14 12:33:55 +00:00
|
|
|
proc DepositEvent(pubkey: PubKeyBytes,
|
|
|
|
withdrawalCredentials: WithdrawalCredentialsBytes,
|
|
|
|
amount: Int64LeBytes,
|
|
|
|
signature: SignatureBytes,
|
|
|
|
index: Int64LeBytes) {.event.}
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
const
|
2020-12-15 23:09:19 +00:00
|
|
|
hasDepositRootChecks = defined(has_deposit_root_checks)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2023-03-14 11:45:01 +00:00
|
|
|
targetBlocksPerLogsRequest = 1000'u64
|
|
|
|
# TODO
|
|
|
|
#
|
|
|
|
# This is currently set to 1000, because this was the default maximum
|
|
|
|
# value in Besu circa our 22.3.0 release. Previously, we've used 5000,
|
|
|
|
# but this was effectively forcing the fallback logic in `syncBlockRange`
|
|
|
|
# to always execute multiple requests before getting a successful response.
|
|
|
|
#
|
|
|
|
# Besu have raised this default to 5000 in https://github.com/hyperledger/besu/pull/5209
|
|
|
|
# which is expected to ship in their next release.
|
|
|
|
#
|
|
|
|
# Full deposits sync time with various values for this parameter:
|
|
|
|
#
|
|
|
|
# Blocks per request | Geth running on the same host | Geth running on a more distant host
|
|
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
# 1000 | 11m 20s | 22m
|
|
|
|
# 5000 | 5m 20s | 15m 40s
|
|
|
|
# 100000 | 4m 10s | not tested
|
|
|
|
#
|
|
|
|
# The number of requests scales linearly with the parameter value as you would expect.
|
|
|
|
#
|
|
|
|
# These results suggest that it would be reasonable for us to get back to 5000 once the
|
|
|
|
# Besu release is well-spread within their userbase.
|
2022-08-12 13:51:33 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
# Engine API timeouts
|
|
|
|
engineApiConnectionTimeout = 5.seconds # How much we wait before giving up connecting to the Engine API
|
|
|
|
web3RequestsTimeout* = 8.seconds # How much we wait for eth_* requests (e.g. eth_getBlockByHash)
|
|
|
|
|
|
|
|
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.2/src/engine/specification.md#request-2
|
|
|
|
GETPAYLOAD_TIMEOUT = 1.seconds
|
|
|
|
|
|
|
|
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.2/src/engine/experimental/blob-extension.md#engine_getblobsbundlev1
|
|
|
|
GETBLOBS_TIMEOUT = 1.seconds
|
|
|
|
|
2018-11-26 13:33:06 +00:00
|
|
|
type
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth1BlockNumber* = uint64
|
|
|
|
Eth1BlockTimestamp* = uint64
|
2023-03-05 01:40:21 +00:00
|
|
|
Eth1BlockHeader = engine_api.BlockHeader
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2021-12-23 14:58:54 +00:00
|
|
|
GenesisStateRef = ref phase0.BeaconState
|
2020-10-12 01:07:20 +00:00
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth1Block* = ref object
|
2022-08-10 12:31:10 +00:00
|
|
|
hash*: Eth2Digest
|
2020-03-24 11:13:07 +00:00
|
|
|
number*: Eth1BlockNumber
|
|
|
|
timestamp*: Eth1BlockTimestamp
|
2022-08-10 12:31:10 +00:00
|
|
|
## Basic properties of the block
|
|
|
|
## These must be initialized in the constructor
|
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
deposits*: seq[DepositData]
|
2022-08-10 12:31:10 +00:00
|
|
|
## Deposits inside this particular block
|
|
|
|
|
|
|
|
depositRoot*: Eth2Digest
|
|
|
|
depositCount*: uint64
|
|
|
|
## Global deposits count and hash tree root of the entire sequence
|
|
|
|
## These are computed when the block is added to the chain (see `addBlock`)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth1Chain* = object
|
2020-12-03 04:30:35 +00:00
|
|
|
db: BeaconChainDB
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
cfg: RuntimeConfig
|
2020-12-03 04:30:35 +00:00
|
|
|
finalizedBlockHash: Eth2Digest
|
|
|
|
finalizedDepositsMerkleizer: DepositsMerkleizer
|
2022-01-03 12:22:56 +00:00
|
|
|
## The latest block that reached a 50% majority vote from
|
|
|
|
## the Eth2 validators according to the follow distance and
|
|
|
|
## the ETH1_VOTING_PERIOD
|
|
|
|
|
2022-08-12 13:52:06 +00:00
|
|
|
blocks*: Deque[Eth1Block]
|
2022-01-03 12:22:56 +00:00
|
|
|
## A non-forkable chain of blocks ending at the block with
|
|
|
|
## ETH1_FOLLOW_DISTANCE offset from the head.
|
|
|
|
|
|
|
|
blocksByHash: Table[BlockHash, Eth1Block]
|
|
|
|
|
2022-08-09 21:32:34 +00:00
|
|
|
headMerkleizer: DepositsMerkleizer
|
|
|
|
## Merkleizer state after applying all `blocks`
|
|
|
|
|
2021-01-29 21:21:44 +00:00
|
|
|
hasConsensusViolation: bool
|
|
|
|
## The local chain contradicts the observed consensus on the network
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
NoPayloadAttributesType = object
|
|
|
|
## A type with exactly one value, and which is not constructed via a `nil`
|
|
|
|
## value for a ref object, which which Nim 1.6 crashes with an ICE.
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
NextExpectedPayloadParams* = object
|
|
|
|
headBlockHash*: Eth2Digest
|
|
|
|
safeBlockHash*: Eth2Digest
|
|
|
|
finalizedBlockHash*: Eth2Digest
|
2023-03-06 16:19:15 +00:00
|
|
|
payloadAttributes*: PayloadAttributesV2
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
ELManager* = ref object
|
2020-12-15 21:59:29 +00:00
|
|
|
eth1Network: Option[Eth1Network]
|
2023-03-05 01:40:21 +00:00
|
|
|
## If this value is supplied the EL monitor will check whether
|
|
|
|
## all configured EL nodes are connected to the same network.
|
|
|
|
|
2020-12-15 21:59:29 +00:00
|
|
|
depositContractAddress*: Eth1Address
|
2023-03-05 01:40:21 +00:00
|
|
|
depositContractBlockNumber: uint64
|
|
|
|
depositContractBlockHash: BlockHash
|
|
|
|
|
2022-08-12 13:51:33 +00:00
|
|
|
blocksPerLogsRequest: uint64
|
2023-03-05 01:40:21 +00:00
|
|
|
## This value is used to dynamically adjust the number of
|
|
|
|
## blocks we are trying to download at once during deposit
|
|
|
|
## syncing. By default, the value is set to the constant
|
|
|
|
## `targetBlocksPerLogsRequest`, but if the EL is failing
|
|
|
|
## to serve this number of blocks per single `eth_getLogs`
|
|
|
|
## request, we temporarily lower the value until the request
|
|
|
|
## succeeds. The failures are generally expected only in
|
|
|
|
## periods in the history for very high deposit density.
|
|
|
|
|
|
|
|
elConnections: seq[ELConnection]
|
|
|
|
## All active EL connections
|
|
|
|
|
|
|
|
eth1Chain: Eth1Chain
|
|
|
|
## At larger distances, this chain consists of all blocks
|
|
|
|
## with deposits. Within the relevant voting period, it
|
|
|
|
## also includes blocks without deposits because we must
|
|
|
|
## vote for a block only if it's part of our known history.
|
|
|
|
|
|
|
|
syncTargetBlock: Option[Eth1BlockNumber]
|
|
|
|
|
|
|
|
chainSyncingLoopFut: Future[void]
|
|
|
|
exchangeTransitionConfigurationLoopFut: Future[void]
|
|
|
|
stopFut: Future[void]
|
2020-10-14 14:04:08 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
nextExpectedPayloadParams*: Option[NextExpectedPayloadParams]
|
2020-11-03 01:21:07 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
EtcStatus {.pure.} = enum
|
|
|
|
notExchangedYet
|
|
|
|
exchangeError
|
|
|
|
mismatch
|
|
|
|
match
|
2020-11-03 01:21:07 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
DepositContractSyncStatus {.pure.} = enum
|
|
|
|
unknown
|
|
|
|
notSynced
|
|
|
|
synced
|
2022-06-15 02:38:27 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
ConnectionState = enum
|
|
|
|
NeverTested
|
|
|
|
Working
|
|
|
|
Degraded
|
|
|
|
|
|
|
|
ELConnection* = ref object
|
|
|
|
engineUrl: EngineApiUrl
|
|
|
|
|
|
|
|
web3: Option[Web3]
|
|
|
|
## This will be `none` before connecting and while we are
|
|
|
|
## reconnecting after a lost connetion. You can wait on
|
|
|
|
## the future below for the moment the connection is active.
|
|
|
|
|
|
|
|
connectingFut: Future[Result[Web3, string]]
|
|
|
|
## This future will be replaced when the connection is lost.
|
|
|
|
|
|
|
|
etcStatus: EtcStatus
|
|
|
|
## The latest status of the `exchangeTransitionConfiguration`
|
|
|
|
## exchange.
|
|
|
|
|
|
|
|
state: ConnectionState
|
2019-11-22 13:16:07 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
depositContractSyncStatus: DepositContractSyncStatus
|
|
|
|
## Are we sure that this EL has synced the deposit contract?
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
lastPayloadId: Option[engine_api.PayloadID]
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2021-11-25 16:51:51 +00:00
|
|
|
FullBlockId* = object
|
|
|
|
number: Eth1BlockNumber
|
|
|
|
hash: BlockHash
|
|
|
|
|
2021-05-28 12:42:39 +00:00
|
|
|
DataProviderFailure* = object of CatchableError
|
|
|
|
CorruptDataProvider* = object of DataProviderFailure
|
|
|
|
DataProviderTimeout* = object of DataProviderFailure
|
2020-03-24 11:13:07 +00:00
|
|
|
|
|
|
|
DisconnectHandler* = proc () {.gcsafe, raises: [Defect].}
|
|
|
|
|
|
|
|
DepositEventHandler* = proc (
|
2021-10-14 12:33:55 +00:00
|
|
|
pubkey: PubKeyBytes,
|
|
|
|
withdrawalCredentials: WithdrawalCredentialsBytes,
|
|
|
|
amount: Int64LeBytes,
|
|
|
|
signature: SignatureBytes,
|
|
|
|
merkleTreeIndex: Int64LeBytes,
|
|
|
|
j: JsonNode) {.gcsafe, raises: [Defect].}
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-11-19 17:19:03 +00:00
|
|
|
BlockProposalEth1Data* = object
|
|
|
|
vote*: Eth1Data
|
|
|
|
deposits*: seq[Deposit]
|
2020-11-24 21:21:47 +00:00
|
|
|
hasMissingDeposits*: bool
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
BellatrixExecutionPayloadWithValue* = object
|
|
|
|
executionPayload*: ExecutionPayloadV1
|
|
|
|
blockValue*: UInt256
|
|
|
|
|
|
|
|
CancunExecutionPayloadAndBlobs* = object
|
|
|
|
executionPayload*: ExecutionPayloadV3
|
|
|
|
blockValue*: UInt256
|
|
|
|
kzgs*: seq[engine_api.KZGCommitment]
|
|
|
|
blobs*: seq[engine_api.Blob]
|
|
|
|
|
|
|
|
SomeEnginePayloadWithValue =
|
|
|
|
BellatrixExecutionPayloadWithValue |
|
|
|
|
GetPayloadV2Response |
|
|
|
|
CancunExecutionPayloadAndBlobs
|
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
const
|
|
|
|
NoPayloadAttributes* = default(NoPayloadAttributesType)
|
|
|
|
|
2020-12-09 22:44:59 +00:00
|
|
|
declareCounter failed_web3_requests,
|
|
|
|
"Failed web3 requests"
|
|
|
|
|
|
|
|
declareGauge eth1_latest_head,
|
|
|
|
"The highest Eth1 block number observed on the network"
|
|
|
|
|
|
|
|
declareGauge eth1_synced_head,
|
|
|
|
"Block number of the highest synchronized block according to follow distance"
|
|
|
|
|
|
|
|
declareGauge eth1_finalized_head,
|
|
|
|
"Block number of the highest Eth1 block finalized by Eth2 consensus"
|
|
|
|
|
|
|
|
declareGauge eth1_finalized_deposits,
|
|
|
|
"Number of deposits that were finalized by the Eth2 consensus"
|
|
|
|
|
|
|
|
declareGauge eth1_chain_len,
|
|
|
|
"The length of the in-memory chain of Eth1 blocks"
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
declareCounter engine_api_responses,
|
|
|
|
"Number of successful requests to the newPayload Engine API end-point",
|
|
|
|
labels = ["url", "request", "status"]
|
|
|
|
|
2023-03-09 17:24:03 +00:00
|
|
|
declareHistogram engine_api_request_duration_seconds,
|
2023-03-09 17:29:04 +00:00
|
|
|
"Time(s) used to generate signature usign remote signer",
|
2023-03-09 17:24:03 +00:00
|
|
|
buckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0],
|
2023-03-09 17:29:04 +00:00
|
|
|
labels = ["url", "request"]
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
declareCounter engine_api_timeouts,
|
|
|
|
"Number of timed-out requests to Engine API end-point",
|
|
|
|
labels = ["url", "request"]
|
|
|
|
|
|
|
|
declareCounter engine_api_last_minute_forkchoice_updates_sent,
|
|
|
|
"Number of last minute requests to the forkchoiceUpdated Engine API end-point just before block proposals",
|
|
|
|
labels = ["url"]
|
|
|
|
|
2023-03-09 17:29:04 +00:00
|
|
|
proc setDegradedState(connection: ELConnection,
|
|
|
|
requestName: string,
|
|
|
|
statusCode: int, errMsg: string) =
|
|
|
|
case connection.state
|
|
|
|
of NeverTested, Working:
|
|
|
|
warn "Connection to EL node degraded",
|
|
|
|
url = url(connection.engineUrl),
|
|
|
|
failedRequest = requestName,
|
|
|
|
statusCode, err = errMsg
|
|
|
|
of Degraded:
|
|
|
|
discard
|
2023-03-09 23:41:28 +00:00
|
|
|
|
|
|
|
reset connection.web3
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.state = Degraded
|
|
|
|
|
|
|
|
proc setWorkingState(connection: ELConnection) =
|
|
|
|
case connection.state
|
|
|
|
of Degraded:
|
|
|
|
info "Connection to EL node restored",
|
|
|
|
url = url(connection.engineUrl)
|
|
|
|
of NeverTested, Working:
|
|
|
|
discard
|
|
|
|
connection.state = Working
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc trackEngineApiRequest(connection: ELConnection,
|
|
|
|
request: FutureBase, requestName: string,
|
2023-03-21 22:19:36 +00:00
|
|
|
startTime: Moment, deadline: Future[void],
|
|
|
|
failureAllowed = false) =
|
2023-03-09 17:29:04 +00:00
|
|
|
request.addCallback do (udata: pointer) {.gcsafe, raises: [Defect].}:
|
|
|
|
# TODO `udata` is nil here. How come?
|
|
|
|
# This forces us to create a GC cycle between the Future and the closure
|
|
|
|
if request.completed:
|
2023-03-09 20:28:46 +00:00
|
|
|
engine_api_request_duration_seconds.observe(
|
2023-03-09 17:29:04 +00:00
|
|
|
float(milliseconds(Moment.now - startTime)) / 1000.0,
|
|
|
|
[connection.engineUrl.url, requestName])
|
|
|
|
|
|
|
|
connection.setWorkingState()
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
deadline.addCallback do (udata: pointer) {.gcsafe, raises: [Defect].}:
|
|
|
|
if not request.finished:
|
|
|
|
request.cancel()
|
|
|
|
engine_api_timeouts.inc(1, [connection.engineUrl.url, requestName])
|
|
|
|
else:
|
|
|
|
let statusCode = if not request.failed:
|
|
|
|
200
|
|
|
|
elif request.error of ErrorResponse:
|
|
|
|
((ref ErrorResponse) request.error).status
|
|
|
|
else:
|
|
|
|
0
|
|
|
|
|
2023-03-21 22:19:36 +00:00
|
|
|
if request.failed and not failureAllowed:
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.setDegradedState(requestName, statusCode, request.error.msg)
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
engine_api_responses.inc(1, [connection.engineUrl.url, requestName, $statusCode])
|
|
|
|
|
|
|
|
template awaitOrRaiseOnTimeout[T](fut: Future[T],
|
|
|
|
timeout: Duration): T =
|
|
|
|
awaitWithTimeout(fut, timeout):
|
|
|
|
raise newException(DataProviderTimeout, "Timeout")
|
|
|
|
|
2023-03-09 17:29:04 +00:00
|
|
|
template trackedRequestWithTimeout[T](connection: ELConnection,
|
|
|
|
requestName: static string,
|
|
|
|
lazyRequestExpression: Future[T],
|
2023-03-21 22:19:36 +00:00
|
|
|
timeout: Duration,
|
|
|
|
failureAllowed = false): T =
|
2023-03-09 17:29:04 +00:00
|
|
|
let
|
|
|
|
connectionParam = connection
|
|
|
|
startTime = Moment.now
|
|
|
|
deadline = sleepAsync(timeout)
|
|
|
|
request = lazyRequestExpression
|
|
|
|
|
2023-03-21 22:19:36 +00:00
|
|
|
connectionParam.trackEngineApiRequest(
|
|
|
|
request, requestName, startTime, deadline, failureAllowed)
|
2023-03-09 17:29:04 +00:00
|
|
|
|
|
|
|
awaitWithTimeout(request, deadline):
|
|
|
|
raise newException(DataProviderTimeout, "Timeout")
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
template cfg(m: ELManager): auto =
|
|
|
|
m.eth1Chain.cfg
|
|
|
|
|
|
|
|
template db(m: ELManager): BeaconChainDB =
|
|
|
|
m.eth1Chain.db
|
2021-12-23 14:58:54 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func hasJwtSecret*(m: ELManager): bool =
|
|
|
|
for c in m.elConnections:
|
|
|
|
if c.engineUrl.jwtSecret.isSome:
|
|
|
|
return true
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func isSynced*(m: ELManager): bool =
|
|
|
|
m.syncTargetBlock.isSome and
|
|
|
|
m.eth1Chain.blocks.len > 0 and
|
|
|
|
m.syncTargetBlock.get <= m.eth1Chain.blocks[^1].number
|
2020-12-03 04:30:35 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
template eth1ChainBlocks*(m: ELManager): Deque[Eth1Block] =
|
|
|
|
m.eth1Chain.blocks
|
2022-08-09 21:32:34 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
template finalizedDepositsMerkleizer(m: ELManager): auto =
|
|
|
|
m.eth1Chain.finalizedDepositsMerkleizer
|
|
|
|
|
|
|
|
template headMerkleizer(m: ELManager): auto =
|
|
|
|
m.eth1Chain.headMerkleizer
|
2020-11-05 23:11:06 +00:00
|
|
|
|
2020-12-09 22:44:59 +00:00
|
|
|
template toGaugeValue(x: Quantity): int64 =
|
|
|
|
toGaugeValue(distinctBase x)
|
|
|
|
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
# TODO: Add cfg validation
|
2020-07-07 23:02:14 +00:00
|
|
|
# MIN_GENESIS_ACTIVE_VALIDATOR_COUNT should be larger than SLOTS_PER_EPOCH
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
# doAssert SECONDS_PER_ETH1_BLOCK * cfg.ETH1_FOLLOW_DISTANCE < GENESIS_DELAY,
|
2020-07-07 23:02:14 +00:00
|
|
|
# "Invalid configuration: GENESIS_DELAY is set too low"
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-01-30 23:26:57 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#get_eth1_data
|
2021-06-11 17:51:46 +00:00
|
|
|
func compute_time_at_slot(genesis_time: uint64, slot: Slot): uint64 =
|
|
|
|
genesis_time + slot * SECONDS_PER_SLOT
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-03-27 14:41:22 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.5/specs/phase0/validator.md#get_eth1_data
|
2021-12-05 17:32:41 +00:00
|
|
|
func voting_period_start_time(state: ForkedHashedBeaconState): uint64 =
|
2020-06-15 09:38:05 +00:00
|
|
|
let eth1_voting_period_start_slot =
|
2021-05-21 09:23:28 +00:00
|
|
|
getStateField(state, slot) - getStateField(state, slot) mod
|
|
|
|
SLOTS_PER_ETH1_VOTING_PERIOD.uint64
|
2021-06-11 17:51:46 +00:00
|
|
|
compute_time_at_slot(
|
|
|
|
getStateField(state, genesis_time), eth1_voting_period_start_slot)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-01-30 23:26:57 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#get_eth1_data
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
func is_candidate_block(cfg: RuntimeConfig,
|
2020-11-20 14:05:37 +00:00
|
|
|
blk: Eth1Block,
|
|
|
|
period_start: uint64): bool =
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
(blk.timestamp + cfg.SECONDS_PER_ETH1_BLOCK * cfg.ETH1_FOLLOW_DISTANCE <= period_start) and
|
|
|
|
(blk.timestamp + cfg.SECONDS_PER_ETH1_BLOCK * cfg.ETH1_FOLLOW_DISTANCE * 2 >= period_start)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-19 17:42:28 +00:00
|
|
|
func asEth2Digest*(x: BlockHash): Eth2Digest =
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth2Digest(data: array[32, byte](x))
|
|
|
|
|
2022-03-25 11:40:10 +00:00
|
|
|
template asBlockHash*(x: Eth2Digest): BlockHash =
|
2020-03-24 11:13:07 +00:00
|
|
|
BlockHash(x.data)
|
|
|
|
|
2023-01-06 21:01:10 +00:00
|
|
|
func asConsensusWithdrawal(w: WithdrawalV1): capella.Withdrawal =
|
|
|
|
capella.Withdrawal(
|
|
|
|
index: w.index.uint64,
|
|
|
|
validator_index: w.validatorIndex.uint64,
|
|
|
|
address: ExecutionAddress(data: w.address.distinctBase),
|
2023-03-05 01:40:21 +00:00
|
|
|
amount: GWei w.amount)
|
2023-01-06 21:01:10 +00:00
|
|
|
|
|
|
|
func asEngineWithdrawal(w: capella.Withdrawal): WithdrawalV1 =
|
|
|
|
WithdrawalV1(
|
|
|
|
index: Quantity(w.index),
|
|
|
|
validatorIndex: Quantity(w.validator_index),
|
|
|
|
address: Address(w.address.data),
|
2023-01-14 17:26:57 +00:00
|
|
|
amount: Quantity(w.amount))
|
2023-01-06 21:01:10 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func asConsensusType*(rpcExecutionPayload: ExecutionPayloadV1):
|
2022-01-12 14:50:30 +00:00
|
|
|
bellatrix.ExecutionPayload =
|
2022-04-15 12:46:56 +00:00
|
|
|
template getTransaction(tt: TypedTransaction): bellatrix.Transaction =
|
|
|
|
bellatrix.Transaction.init(tt.distinctBase)
|
2022-01-03 12:22:56 +00:00
|
|
|
|
2022-01-12 14:50:30 +00:00
|
|
|
bellatrix.ExecutionPayload(
|
2022-01-03 12:22:56 +00:00
|
|
|
parent_hash: rpcExecutionPayload.parentHash.asEth2Digest,
|
|
|
|
feeRecipient:
|
|
|
|
ExecutionAddress(data: rpcExecutionPayload.feeRecipient.distinctBase),
|
|
|
|
state_root: rpcExecutionPayload.stateRoot.asEth2Digest,
|
|
|
|
receipts_root: rpcExecutionPayload.receiptsRoot.asEth2Digest,
|
|
|
|
logs_bloom: BloomLogs(data: rpcExecutionPayload.logsBloom.distinctBase),
|
2022-03-03 16:08:14 +00:00
|
|
|
prev_randao: rpcExecutionPayload.prevRandao.asEth2Digest,
|
2022-01-03 12:22:56 +00:00
|
|
|
block_number: rpcExecutionPayload.blockNumber.uint64,
|
|
|
|
gas_limit: rpcExecutionPayload.gasLimit.uint64,
|
|
|
|
gas_used: rpcExecutionPayload.gasUsed.uint64,
|
|
|
|
timestamp: rpcExecutionPayload.timestamp.uint64,
|
2023-02-23 02:10:07 +00:00
|
|
|
extra_data: List[byte, MAX_EXTRA_DATA_BYTES].init(rpcExecutionPayload.extraData.bytes),
|
2022-06-01 12:57:28 +00:00
|
|
|
base_fee_per_gas: rpcExecutionPayload.baseFeePerGas,
|
2022-01-03 12:22:56 +00:00
|
|
|
block_hash: rpcExecutionPayload.blockHash.asEth2Digest,
|
2022-01-12 14:50:30 +00:00
|
|
|
transactions: List[bellatrix.Transaction, MAX_TRANSACTIONS_PER_PAYLOAD].init(
|
2022-01-03 12:22:56 +00:00
|
|
|
mapIt(rpcExecutionPayload.transactions, it.getTransaction)))
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func asConsensusType*(payloadWithValue: BellatrixExecutionPayloadWithValue):
|
|
|
|
bellatrix.ExecutionPayloadForSigning =
|
|
|
|
bellatrix.ExecutionPayloadForSigning(
|
|
|
|
executionPayload: payloadWithValue.executionPayload.asConsensusType,
|
|
|
|
blockValue: payloadWithValue.blockValue)
|
|
|
|
|
|
|
|
template maybeDeref[T](o: Option[T]): T = o.get
|
|
|
|
template maybeDeref[V](v: V): V = v
|
|
|
|
|
|
|
|
func asConsensusType*(rpcExecutionPayload: ExecutionPayloadV1OrV2|ExecutionPayloadV2):
|
2022-11-29 05:02:16 +00:00
|
|
|
capella.ExecutionPayload =
|
|
|
|
template getTransaction(tt: TypedTransaction): bellatrix.Transaction =
|
|
|
|
bellatrix.Transaction.init(tt.distinctBase)
|
|
|
|
|
|
|
|
capella.ExecutionPayload(
|
|
|
|
parent_hash: rpcExecutionPayload.parentHash.asEth2Digest,
|
|
|
|
feeRecipient:
|
|
|
|
ExecutionAddress(data: rpcExecutionPayload.feeRecipient.distinctBase),
|
|
|
|
state_root: rpcExecutionPayload.stateRoot.asEth2Digest,
|
|
|
|
receipts_root: rpcExecutionPayload.receiptsRoot.asEth2Digest,
|
|
|
|
logs_bloom: BloomLogs(data: rpcExecutionPayload.logsBloom.distinctBase),
|
|
|
|
prev_randao: rpcExecutionPayload.prevRandao.asEth2Digest,
|
|
|
|
block_number: rpcExecutionPayload.blockNumber.uint64,
|
|
|
|
gas_limit: rpcExecutionPayload.gasLimit.uint64,
|
|
|
|
gas_used: rpcExecutionPayload.gasUsed.uint64,
|
|
|
|
timestamp: rpcExecutionPayload.timestamp.uint64,
|
2023-02-23 02:10:07 +00:00
|
|
|
extra_data: List[byte, MAX_EXTRA_DATA_BYTES].init(rpcExecutionPayload.extraData.bytes),
|
2022-11-29 05:02:16 +00:00
|
|
|
base_fee_per_gas: rpcExecutionPayload.baseFeePerGas,
|
|
|
|
block_hash: rpcExecutionPayload.blockHash.asEth2Digest,
|
|
|
|
transactions: List[bellatrix.Transaction, MAX_TRANSACTIONS_PER_PAYLOAD].init(
|
|
|
|
mapIt(rpcExecutionPayload.transactions, it.getTransaction)),
|
2022-11-29 11:04:36 +00:00
|
|
|
withdrawals: List[capella.Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD].init(
|
2023-03-05 01:40:21 +00:00
|
|
|
mapIt(maybeDeref rpcExecutionPayload.withdrawals, it.asConsensusWithdrawal)))
|
|
|
|
|
|
|
|
func asConsensusType*(payloadWithValue: engine_api.GetPayloadV2Response):
|
|
|
|
capella.ExecutionPayloadForSigning =
|
|
|
|
capella.ExecutionPayloadForSigning(
|
|
|
|
executionPayload: payloadWithValue.executionPayload.asConsensusType,
|
|
|
|
blockValue: payloadWithValue.blockValue)
|
2022-11-29 05:02:16 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func asConsensusType*(rpcExecutionPayload: ExecutionPayloadV3):
|
2023-02-25 01:03:34 +00:00
|
|
|
deneb.ExecutionPayload =
|
2023-01-11 18:21:19 +00:00
|
|
|
template getTransaction(tt: TypedTransaction): bellatrix.Transaction =
|
|
|
|
bellatrix.Transaction.init(tt.distinctBase)
|
|
|
|
|
2023-02-25 01:03:34 +00:00
|
|
|
deneb.ExecutionPayload(
|
2023-01-11 18:21:19 +00:00
|
|
|
parent_hash: rpcExecutionPayload.parentHash.asEth2Digest,
|
|
|
|
feeRecipient:
|
|
|
|
ExecutionAddress(data: rpcExecutionPayload.feeRecipient.distinctBase),
|
|
|
|
state_root: rpcExecutionPayload.stateRoot.asEth2Digest,
|
|
|
|
receipts_root: rpcExecutionPayload.receiptsRoot.asEth2Digest,
|
|
|
|
logs_bloom: BloomLogs(data: rpcExecutionPayload.logsBloom.distinctBase),
|
|
|
|
prev_randao: rpcExecutionPayload.prevRandao.asEth2Digest,
|
|
|
|
block_number: rpcExecutionPayload.blockNumber.uint64,
|
|
|
|
gas_limit: rpcExecutionPayload.gasLimit.uint64,
|
|
|
|
gas_used: rpcExecutionPayload.gasUsed.uint64,
|
|
|
|
timestamp: rpcExecutionPayload.timestamp.uint64,
|
2023-02-23 02:10:07 +00:00
|
|
|
extra_data: List[byte, MAX_EXTRA_DATA_BYTES].init(rpcExecutionPayload.extraData.bytes),
|
2023-01-11 18:21:19 +00:00
|
|
|
base_fee_per_gas: rpcExecutionPayload.baseFeePerGas,
|
|
|
|
excess_data_gas: rpcExecutionPayload.excessDataGas,
|
|
|
|
block_hash: rpcExecutionPayload.blockHash.asEth2Digest,
|
|
|
|
transactions: List[bellatrix.Transaction, MAX_TRANSACTIONS_PER_PAYLOAD].init(
|
|
|
|
mapIt(rpcExecutionPayload.transactions, it.getTransaction)),
|
|
|
|
withdrawals: List[capella.Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD].init(
|
|
|
|
mapIt(rpcExecutionPayload.withdrawals, it.asConsensusWithdrawal)))
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func asConsensusType*(cancunPayload: CancunExecutionPayloadAndBlobs):
|
|
|
|
deneb.ExecutionPayloadForSigning =
|
|
|
|
deneb.ExecutionPayloadForSigning(
|
|
|
|
executionPayload: cancunPayload.executionPayload.asConsensusType,
|
|
|
|
blockValue: cancunPayload.blockValue,
|
|
|
|
# TODO
|
|
|
|
# The `mapIt` calls below are necessary only because we use different distinct
|
|
|
|
# types for KZG commitments and Blobs in the `web3` and the `deneb` spec types.
|
|
|
|
# Both are defined as `array[N, byte]` under the hood.
|
|
|
|
kzgs: KZGCommitments cancunPayload.kzgs.mapIt(it.bytes),
|
|
|
|
blobs: Blobs cancunPayload.blobs.mapIt(it.bytes)
|
|
|
|
)
|
|
|
|
|
2022-01-12 14:50:30 +00:00
|
|
|
func asEngineExecutionPayload*(executionPayload: bellatrix.ExecutionPayload):
|
2022-01-03 12:22:56 +00:00
|
|
|
ExecutionPayloadV1 =
|
2022-04-15 12:46:56 +00:00
|
|
|
template getTypedTransaction(tt: bellatrix.Transaction): TypedTransaction =
|
|
|
|
TypedTransaction(tt.distinctBase)
|
2022-01-03 12:22:56 +00:00
|
|
|
|
|
|
|
engine_api.ExecutionPayloadV1(
|
|
|
|
parentHash: executionPayload.parent_hash.asBlockHash,
|
2022-04-08 16:22:49 +00:00
|
|
|
feeRecipient: Address(executionPayload.fee_recipient.data),
|
2022-01-03 12:22:56 +00:00
|
|
|
stateRoot: executionPayload.state_root.asBlockHash,
|
|
|
|
receiptsRoot: executionPayload.receipts_root.asBlockHash,
|
|
|
|
logsBloom:
|
|
|
|
FixedBytes[BYTES_PER_LOGS_BLOOM](executionPayload.logs_bloom.data),
|
2022-03-03 16:08:14 +00:00
|
|
|
prevRandao: executionPayload.prev_randao.asBlockHash,
|
2022-01-03 12:22:56 +00:00
|
|
|
blockNumber: Quantity(executionPayload.block_number),
|
2022-11-24 07:53:04 +00:00
|
|
|
gasLimit: Quantity(executionPayload.gas_limit),
|
|
|
|
gasUsed: Quantity(executionPayload.gas_used),
|
|
|
|
timestamp: Quantity(executionPayload.timestamp),
|
2023-02-23 02:10:07 +00:00
|
|
|
extraData: DynamicBytes[0, MAX_EXTRA_DATA_BYTES](executionPayload.extra_data),
|
2022-11-24 07:53:04 +00:00
|
|
|
baseFeePerGas: executionPayload.base_fee_per_gas,
|
|
|
|
blockHash: executionPayload.block_hash.asBlockHash,
|
|
|
|
transactions: mapIt(executionPayload.transactions, it.getTypedTransaction))
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
template toEngineWithdrawal(w: capella.Withdrawal): WithdrawalV1 =
|
|
|
|
WithdrawalV1(
|
|
|
|
index: Quantity(w.index),
|
|
|
|
validatorIndex: Quantity(w.validator_index),
|
|
|
|
address: Address(w.address.data),
|
|
|
|
amount: Quantity(w.amount))
|
|
|
|
|
2022-11-24 07:53:04 +00:00
|
|
|
func asEngineExecutionPayload*(executionPayload: capella.ExecutionPayload):
|
2022-11-29 05:02:16 +00:00
|
|
|
ExecutionPayloadV2 =
|
2022-11-24 07:53:04 +00:00
|
|
|
template getTypedTransaction(tt: bellatrix.Transaction): TypedTransaction =
|
|
|
|
TypedTransaction(tt.distinctBase)
|
2022-11-29 05:02:16 +00:00
|
|
|
engine_api.ExecutionPayloadV2(
|
2022-11-24 07:53:04 +00:00
|
|
|
parentHash: executionPayload.parent_hash.asBlockHash,
|
|
|
|
feeRecipient: Address(executionPayload.fee_recipient.data),
|
|
|
|
stateRoot: executionPayload.state_root.asBlockHash,
|
|
|
|
receiptsRoot: executionPayload.receipts_root.asBlockHash,
|
|
|
|
logsBloom:
|
|
|
|
FixedBytes[BYTES_PER_LOGS_BLOOM](executionPayload.logs_bloom.data),
|
|
|
|
prevRandao: executionPayload.prev_randao.asBlockHash,
|
|
|
|
blockNumber: Quantity(executionPayload.block_number),
|
2022-01-03 12:22:56 +00:00
|
|
|
gasLimit: Quantity(executionPayload.gas_limit),
|
|
|
|
gasUsed: Quantity(executionPayload.gas_used),
|
|
|
|
timestamp: Quantity(executionPayload.timestamp),
|
2023-02-23 02:10:07 +00:00
|
|
|
extraData: DynamicBytes[0, MAX_EXTRA_DATA_BYTES](executionPayload.extra_data),
|
2022-06-01 12:57:28 +00:00
|
|
|
baseFeePerGas: executionPayload.base_fee_per_gas,
|
2022-01-03 12:22:56 +00:00
|
|
|
blockHash: executionPayload.block_hash.asBlockHash,
|
2022-11-29 05:02:16 +00:00
|
|
|
transactions: mapIt(executionPayload.transactions, it.getTypedTransaction),
|
2023-03-05 01:40:21 +00:00
|
|
|
withdrawals: mapIt(executionPayload.withdrawals, it.toEngineWithdrawal))
|
2022-01-03 12:22:56 +00:00
|
|
|
|
2023-02-25 01:03:34 +00:00
|
|
|
func asEngineExecutionPayload*(executionPayload: deneb.ExecutionPayload):
|
2023-01-11 18:21:19 +00:00
|
|
|
ExecutionPayloadV3 =
|
|
|
|
template getTypedTransaction(tt: bellatrix.Transaction): TypedTransaction =
|
|
|
|
TypedTransaction(tt.distinctBase)
|
|
|
|
|
|
|
|
engine_api.ExecutionPayloadV3(
|
|
|
|
parentHash: executionPayload.parent_hash.asBlockHash,
|
|
|
|
feeRecipient: Address(executionPayload.fee_recipient.data),
|
|
|
|
stateRoot: executionPayload.state_root.asBlockHash,
|
|
|
|
receiptsRoot: executionPayload.receipts_root.asBlockHash,
|
|
|
|
logsBloom:
|
|
|
|
FixedBytes[BYTES_PER_LOGS_BLOOM](executionPayload.logs_bloom.data),
|
|
|
|
prevRandao: executionPayload.prev_randao.asBlockHash,
|
|
|
|
blockNumber: Quantity(executionPayload.block_number),
|
|
|
|
gasLimit: Quantity(executionPayload.gas_limit),
|
|
|
|
gasUsed: Quantity(executionPayload.gas_used),
|
|
|
|
timestamp: Quantity(executionPayload.timestamp),
|
2023-02-23 02:10:07 +00:00
|
|
|
extraData: DynamicBytes[0, MAX_EXTRA_DATA_BYTES](executionPayload.extra_data),
|
2023-01-11 18:21:19 +00:00
|
|
|
baseFeePerGas: executionPayload.base_fee_per_gas,
|
|
|
|
excessDataGas: executionPayload.excess_data_gas,
|
|
|
|
blockHash: executionPayload.block_hash.asBlockHash,
|
|
|
|
transactions: mapIt(executionPayload.transactions, it.getTypedTransaction),
|
|
|
|
withdrawals: mapIt(executionPayload.withdrawals, it.asEngineWithdrawal))
|
|
|
|
|
2020-09-06 08:39:25 +00:00
|
|
|
func shortLog*(b: Eth1Block): string =
|
2021-03-26 06:52:01 +00:00
|
|
|
try:
|
2022-08-10 12:31:10 +00:00
|
|
|
&"{b.number}:{shortLog b.hash}(deposits = {b.depositCount})"
|
2021-03-26 06:52:01 +00:00
|
|
|
except ValueError as exc: raiseAssert exc.msg
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2021-12-05 17:32:41 +00:00
|
|
|
template findBlock(chain: Eth1Chain, eth1Data: Eth1Data): Eth1Block =
|
2020-12-03 04:30:35 +00:00
|
|
|
getOrDefault(chain.blocksByHash, asBlockHash(eth1Data.block_hash), nil)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-11-19 17:19:03 +00:00
|
|
|
func makeSuccessorWithoutDeposits(existingBlock: Eth1Block,
|
2022-04-08 16:22:49 +00:00
|
|
|
successor: BlockObject): Eth1Block =
|
2020-11-24 21:21:47 +00:00
|
|
|
result = Eth1Block(
|
2022-08-10 12:31:10 +00:00
|
|
|
hash: successor.hash.asEth2Digest,
|
2020-11-19 17:19:03 +00:00
|
|
|
number: Eth1BlockNumber successor.number,
|
2022-08-10 12:31:10 +00:00
|
|
|
timestamp: Eth1BlockTimestamp successor.timestamp)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
func latestCandidateBlock(chain: Eth1Chain, periodStart: uint64): Eth1Block =
|
|
|
|
for i in countdown(chain.blocks.len - 1, 0):
|
|
|
|
let blk = chain.blocks[i]
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
if is_candidate_block(chain.cfg, blk, periodStart):
|
2020-03-24 11:13:07 +00:00
|
|
|
return blk
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
proc popFirst(chain: var Eth1Chain) =
|
|
|
|
let removed = chain.blocks.popFirst
|
2022-08-10 12:31:10 +00:00
|
|
|
chain.blocksByHash.del removed.hash.asBlockHash
|
2020-12-03 04:30:35 +00:00
|
|
|
eth1_chain_len.set chain.blocks.len.int64
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2022-08-10 12:31:10 +00:00
|
|
|
func getDepositsRoot*(m: DepositsMerkleizer): Eth2Digest =
|
|
|
|
mixInLength(m.getFinalHash, int m.totalChunks)
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
proc addBlock*(chain: var Eth1Chain, newBlock: Eth1Block) =
|
2022-08-10 12:31:10 +00:00
|
|
|
for deposit in newBlock.deposits:
|
|
|
|
chain.headMerkleizer.addChunk hash_tree_root(deposit).data
|
|
|
|
|
|
|
|
newBlock.depositCount = chain.headMerkleizer.getChunkCount
|
|
|
|
newBlock.depositRoot = chain.headMerkleizer.getDepositsRoot
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
chain.blocks.addLast newBlock
|
2022-08-10 12:31:10 +00:00
|
|
|
chain.blocksByHash[newBlock.hash.asBlockHash] = newBlock
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
eth1_chain_len.set chain.blocks.len.int64
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2022-08-10 12:31:10 +00:00
|
|
|
func toVoteData(blk: Eth1Block): Eth1Data =
|
|
|
|
Eth1Data(
|
|
|
|
deposit_root: blk.depositRoot,
|
|
|
|
deposit_count: blk.depositCount,
|
|
|
|
block_hash: blk.hash)
|
|
|
|
|
2021-12-08 17:29:22 +00:00
|
|
|
func hash*(x: Eth1Data): Hash =
|
2021-12-03 15:04:58 +00:00
|
|
|
hash(x.block_hash)
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc close(connection: ELConnection): Future[void] {.async.} =
|
|
|
|
if connection.web3.isSome:
|
|
|
|
awaitWithTimeout(connection.web3.get.close(), 30.seconds):
|
|
|
|
debug "Failed to close data provider in time"
|
2020-12-01 21:20:28 +00:00
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
func isConnected(connection: ELConnection): bool =
|
2023-03-05 01:40:21 +00:00
|
|
|
connection.web3.isSome
|
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
func getJsonRpcRequestHeaders(jwtSecret: Option[seq[byte]]):
|
2023-03-05 01:40:21 +00:00
|
|
|
auto =
|
|
|
|
if jwtSecret.isSome:
|
|
|
|
let secret = jwtSecret.get
|
|
|
|
(proc(): seq[(string, string)] =
|
|
|
|
# https://www.rfc-editor.org/rfc/rfc6750#section-6.1.1
|
|
|
|
@[("Authorization", "Bearer " & getSignedIatToken(
|
|
|
|
secret, (getTime() - initTime(0, 0)).inSeconds))])
|
|
|
|
else:
|
|
|
|
(proc(): seq[(string, string)] = @[])
|
|
|
|
|
|
|
|
proc newWeb3*(engineUrl: EngineApiUrl): Future[Web3] =
|
|
|
|
newWeb3(engineUrl.url, getJsonRpcRequestHeaders(engineUrl.jwtSecret))
|
|
|
|
|
|
|
|
proc establishEngineApiConnection*(url: EngineApiUrl):
|
|
|
|
Future[Result[Web3, string]] {.async.} =
|
|
|
|
let web3Fut = newWeb3(url)
|
|
|
|
yield web3Fut or sleepAsync(engineApiConnectionTimeout)
|
|
|
|
|
2023-03-09 23:41:28 +00:00
|
|
|
if not web3Fut.completed:
|
2023-03-05 01:40:21 +00:00
|
|
|
await cancelAndWait(web3Fut)
|
|
|
|
if web3Fut.failed:
|
|
|
|
return err "Failed to setup Engine API connection: " & web3Fut.readError.msg
|
2020-12-01 21:20:28 +00:00
|
|
|
else:
|
2023-03-05 01:40:21 +00:00
|
|
|
return err "Failed to setup Engine API connection"
|
|
|
|
else:
|
|
|
|
return ok web3Fut.read
|
2020-12-01 21:20:28 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc tryConnecting(connection: ELConnection): Future[bool] {.async.} =
|
|
|
|
if connection.isConnected:
|
|
|
|
return true
|
2020-12-01 21:20:28 +00:00
|
|
|
|
2023-03-09 23:41:28 +00:00
|
|
|
if connection.connectingFut == nil or
|
|
|
|
connection.connectingFut.finished: # The previous attempt was not successful
|
2023-03-05 01:40:21 +00:00
|
|
|
connection.connectingFut = establishEngineApiConnection(connection.engineUrl)
|
2020-12-01 21:20:28 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
let web3Res = await connection.connectingFut
|
|
|
|
if web3Res.isErr:
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
connection.web3 = some web3Res.get
|
|
|
|
return true
|
2020-12-01 21:20:28 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc connectedRpcClient(connection: ELConnection): Future[RpcClient] {.async.} =
|
|
|
|
while not connection.isConnected:
|
|
|
|
if not await connection.tryConnecting():
|
|
|
|
await sleepAsync(chronos.seconds(10))
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
return connection.web3.get.provider
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc getBlockByHash(rpcClient: RpcClient, hash: BlockHash): Future[BlockObject] =
|
|
|
|
rpcClient.eth_getBlockByHash(hash, false)
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc getBlockByNumber*(rpcClient: RpcClient,
|
2022-02-04 12:12:19 +00:00
|
|
|
number: Eth1BlockNumber): Future[BlockObject] =
|
2023-03-05 01:40:21 +00:00
|
|
|
let hexNumber = try:
|
|
|
|
&"0x{number:X}" # No leading 0's!
|
|
|
|
except ValueError as exc:
|
|
|
|
# Since the format above is valid, failing here should not be possible
|
|
|
|
raiseAssert exc.msg
|
2023-01-21 00:47:38 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient.eth_getBlockByNumber(hexNumber, false)
|
|
|
|
|
|
|
|
proc getBlock(rpcClient: RpcClient, id: BlockHashOrNumber): Future[BlockObject] =
|
|
|
|
if id.isHash:
|
|
|
|
let hash = id.hash.asBlockHash()
|
|
|
|
return rpcClient.getBlockByHash(hash)
|
|
|
|
else:
|
|
|
|
return rpcClient.getBlockByNumber(id.number)
|
|
|
|
|
|
|
|
func areSameAs(expectedParams: Option[NextExpectedPayloadParams],
|
|
|
|
latestHead, latestSafe, latestFinalized: Eth2Digest,
|
|
|
|
timestamp: uint64,
|
|
|
|
randomData: Eth2Digest,
|
|
|
|
feeRecipient: Eth1Address,
|
|
|
|
withdrawals: seq[WithdrawalV1]): bool =
|
2023-03-06 16:19:15 +00:00
|
|
|
expectedParams.isSome and
|
|
|
|
expectedParams.get.headBlockHash == latestHead and
|
|
|
|
expectedParams.get.safeBlockHash == latestSafe and
|
|
|
|
expectedParams.get.finalizedBlockHash == latestFinalized and
|
|
|
|
expectedParams.get.payloadAttributes.timestamp.uint64 == timestamp and
|
|
|
|
expectedParams.get.payloadAttributes.prevRandao.bytes == randomData.data and
|
|
|
|
expectedParams.get.payloadAttributes.suggestedFeeRecipient == feeRecipient and
|
|
|
|
expectedParams.get.payloadAttributes.withdrawals == withdrawals
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
proc forkchoiceUpdated(rpcClient: RpcClient,
|
|
|
|
state: ForkchoiceStateV1,
|
2023-03-06 16:19:15 +00:00
|
|
|
payloadAttributes: PayloadAttributesV1 |
|
|
|
|
PayloadAttributesV2 |
|
|
|
|
NoPayloadAttributesType):
|
|
|
|
Future[ForkchoiceUpdatedResponse] =
|
|
|
|
when payloadAttributes is NoPayloadAttributesType:
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient.engine_forkchoiceUpdatedV1(state, none PayloadAttributesV1)
|
2023-03-06 16:19:15 +00:00
|
|
|
elif payloadAttributes is PayloadAttributesV1:
|
|
|
|
rpcClient.engine_forkchoiceUpdatedV1(state, some payloadAttributes)
|
|
|
|
elif payloadAttributes is PayloadAttributesV2:
|
|
|
|
rpcClient.engine_forkchoiceUpdatedV2(state, some payloadAttributes)
|
2023-01-06 21:01:10 +00:00
|
|
|
else:
|
2023-03-06 16:19:15 +00:00
|
|
|
static: doAssert false
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
func computeBlockValue(blk: ExecutionPayloadV1): UInt256 {.raises: [RlpError, Defect].} =
|
|
|
|
for transactionBytes in blk.transactions:
|
|
|
|
var rlp = rlpFromBytes distinctBase(transactionBytes)
|
|
|
|
let transaction = rlp.read(eth_types.Transaction)
|
|
|
|
result += distinctBase(effectiveGasTip(transaction, blk.baseFeePerGas)).u256
|
|
|
|
|
|
|
|
proc getPayloadFromSingleEL(
|
|
|
|
connection: ELConnection,
|
|
|
|
GetPayloadResponseType: type,
|
|
|
|
isForkChoiceUpToDate: bool,
|
|
|
|
headBlock, safeBlock, finalizedBlock: Eth2Digest,
|
|
|
|
timestamp: uint64,
|
|
|
|
randomData: Eth2Digest,
|
|
|
|
suggestedFeeRecipient: Eth1Address,
|
|
|
|
withdrawals: seq[WithdrawalV1]): Future[GetPayloadResponseType] {.async.} =
|
|
|
|
|
|
|
|
let
|
|
|
|
rpcClient = await connection.connectedRpcClient()
|
|
|
|
payloadId = if isForkChoiceUpToDate and connection.lastPayloadId.isSome:
|
|
|
|
connection.lastPayloadId.get
|
|
|
|
elif not headBlock.isZero:
|
|
|
|
engine_api_last_minute_forkchoice_updates_sent.inc(1, [connection.engineUrl.url])
|
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
when GetPayloadResponseType is BellatrixExecutionPayloadWithValue:
|
|
|
|
let response = await rpcClient.forkchoiceUpdated(
|
|
|
|
ForkchoiceStateV1(
|
|
|
|
headBlockHash: headBlock.asBlockHash,
|
|
|
|
safeBlockHash: safeBlock.asBlockHash,
|
|
|
|
finalizedBlockHash: finalizedBlock.asBlockHash),
|
|
|
|
PayloadAttributesV1(
|
|
|
|
timestamp: Quantity timestamp,
|
|
|
|
prevRandao: FixedBytes[32] randomData.data,
|
|
|
|
suggestedFeeRecipient: suggestedFeeRecipient))
|
|
|
|
elif GetPayloadResponseType is engine_api.GetPayloadV2Response or GetPayloadResponseType is CancunExecutionPayloadAndBlobs:
|
|
|
|
let response = await rpcClient.forkchoiceUpdated(
|
|
|
|
ForkchoiceStateV1(
|
|
|
|
headBlockHash: headBlock.asBlockHash,
|
|
|
|
safeBlockHash: safeBlock.asBlockHash,
|
|
|
|
finalizedBlockHash: finalizedBlock.asBlockHash),
|
|
|
|
PayloadAttributesV2(
|
|
|
|
timestamp: Quantity timestamp,
|
|
|
|
prevRandao: FixedBytes[32] randomData.data,
|
|
|
|
suggestedFeeRecipient: suggestedFeeRecipient,
|
|
|
|
withdrawals: withdrawals))
|
|
|
|
else:
|
|
|
|
static: doAssert false
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
if response.payloadStatus.status != PayloadExecutionStatus.valid or
|
|
|
|
response.payloadId.isNone:
|
|
|
|
raise newException(CatchableError, "Head block is not a valid payload")
|
|
|
|
|
|
|
|
# Give the EL some time to assemble the block
|
|
|
|
await sleepAsync(chronos.milliseconds 500)
|
|
|
|
|
|
|
|
response.payloadId.get
|
|
|
|
else:
|
|
|
|
raise newException(CatchableError, "No confirmed execution head yet")
|
|
|
|
|
|
|
|
when GetPayloadResponseType is CancunExecutionPayloadAndBlobs:
|
|
|
|
let
|
|
|
|
response = await engine_api.getPayload(rpcClient,
|
|
|
|
GetPayloadV3Response,
|
|
|
|
payloadId)
|
|
|
|
blobsBundle = await engine_getBlobsBundleV1(rpcClient, payloadId)
|
|
|
|
# TODO validate the blobs bundle
|
|
|
|
return CancunExecutionPayloadAndBlobs(
|
|
|
|
executionPayload: response.executionPayload,
|
|
|
|
blockValue: response.blockValue,
|
|
|
|
kzgs: blobsBundle.kzgs, # TODO Avoid the copies here with `move`
|
|
|
|
blobs: blobsBundle.blobs)
|
|
|
|
elif GetPayloadResponseType is BellatrixExecutionPayloadWithValue:
|
|
|
|
let payload= await engine_api.getPayload(rpcClient, ExecutionPayloadV1, payloadId)
|
|
|
|
return BellatrixExecutionPayloadWithValue(
|
|
|
|
executionPayload: payload,
|
|
|
|
blockValue: computeBlockValue payload)
|
|
|
|
else:
|
|
|
|
return await engine_api.getPayload(rpcClient, GetPayloadResponseType, payloadId)
|
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
func cmpGetPayloadResponses(lhs, rhs: SomeEnginePayloadWithValue): int =
|
2023-03-05 01:40:21 +00:00
|
|
|
cmp(distinctBase lhs.blockValue, distinctBase rhs.blockValue)
|
2021-05-20 10:44:13 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
template EngineApiResponseType*(T: type bellatrix.ExecutionPayloadForSigning): type =
|
|
|
|
BellatrixExecutionPayloadWithValue
|
|
|
|
|
|
|
|
template EngineApiResponseType*(T: type capella.ExecutionPayloadForSigning): type =
|
|
|
|
engine_api.GetPayloadV2Response
|
|
|
|
|
|
|
|
template EngineApiResponseType*(T: type deneb.ExecutionPayloadForSigning): type =
|
|
|
|
CancunExecutionPayloadAndBlobs
|
|
|
|
|
|
|
|
template payload(response: engine_api.ExecutionPayloadV1): engine_api.ExecutionPayloadV1 =
|
|
|
|
response
|
|
|
|
|
|
|
|
template payload(response: engine_api.GetPayloadV2Response): engine_api.ExecutionPayloadV1OrV2 =
|
|
|
|
response.executionPayload
|
|
|
|
|
|
|
|
template payload(response: engine_api.GetPayloadV3Response): engine_api.ExecutionPayloadV3 =
|
|
|
|
response.executionPayload
|
|
|
|
|
|
|
|
template toEngineWithdrawals*(withdrawals: seq[capella.Withdrawal]): seq[WithdrawalV1] =
|
|
|
|
mapIt(withdrawals, toEngineWithdrawal(it))
|
|
|
|
|
|
|
|
template toFork(T: type ExecutionPayloadV1): ConsensusFork =
|
|
|
|
ConsensusFork.Bellatrix
|
|
|
|
|
|
|
|
template toFork(T: typedesc[ExecutionPayloadV1OrV2|ExecutionPayloadV2]): ConsensusFork =
|
|
|
|
ConsensusFork.Capella
|
|
|
|
|
|
|
|
template toFork(T: type ExecutionPayloadV3): ConsensusFork =
|
|
|
|
ConsensusFork.Deneb
|
|
|
|
|
|
|
|
proc getPayload*(m: ELManager,
|
|
|
|
PayloadType: type ForkyExecutionPayloadForSigning,
|
|
|
|
headBlock, safeBlock, finalizedBlock: Eth2Digest,
|
|
|
|
timestamp: uint64,
|
|
|
|
randomData: Eth2Digest,
|
|
|
|
suggestedFeeRecipient: Eth1Address,
|
|
|
|
withdrawals: seq[capella.Withdrawal]):
|
|
|
|
Future[Opt[PayloadType]] {.async.} =
|
|
|
|
if m.elConnections.len == 0:
|
|
|
|
return err()
|
|
|
|
|
|
|
|
let
|
|
|
|
engineApiWithdrawals = toEngineWithdrawals withdrawals
|
|
|
|
let isFcUpToDate = m.nextExpectedPayloadParams.areSameAs(
|
|
|
|
headBlock, safeBlock, finalizedBlock, timestamp,
|
|
|
|
randomData, suggestedFeeRecipient, engineApiWithdrawals)
|
|
|
|
|
|
|
|
let
|
|
|
|
timeout = when PayloadType is deneb.ExecutionPayloadForSigning:
|
|
|
|
# TODO We should follow the spec and track the timeouts of
|
|
|
|
# the individual engine API calls inside `getPayloadFromSingleEL`.
|
|
|
|
GETPAYLOAD_TIMEOUT + GETBLOBS_TIMEOUT
|
|
|
|
else:
|
|
|
|
GETPAYLOAD_TIMEOUT
|
|
|
|
deadline = sleepAsync(timeout)
|
|
|
|
requests = m.elConnections.mapIt(it.getPayloadFromSingleEL(
|
|
|
|
EngineApiResponseType(PayloadType),
|
|
|
|
isFcUpToDate, headBlock, safeBlock, finalizedBlock,
|
|
|
|
timestamp, randomData, suggestedFeeRecipient, engineApiWithdrawals
|
|
|
|
))
|
|
|
|
requestsCompleted = allFutures(requests)
|
|
|
|
|
|
|
|
await requestsCompleted or deadline
|
|
|
|
|
|
|
|
var bestPayloadIdx = none int
|
|
|
|
for idx, req in requests:
|
|
|
|
if not req.finished:
|
|
|
|
req.cancel()
|
|
|
|
elif req.failed:
|
|
|
|
error "Failed to get execution payload from EL",
|
2023-03-10 09:12:29 +00:00
|
|
|
url = m.elConnections[idx].engineUrl.url,
|
|
|
|
err = req.error.msg
|
2023-03-05 01:40:21 +00:00
|
|
|
else:
|
|
|
|
const payloadFork = PayloadType.toFork
|
|
|
|
when payloadFork >= ConsensusFork.Capella:
|
|
|
|
when payloadFork == ConsensusFork.Capella:
|
|
|
|
# TODO: The engine_api module may offer an alternative API where it is guaranteed
|
|
|
|
# to return the correct response type (i.e. the rule below will be enforced
|
|
|
|
# during deserialization).
|
|
|
|
if req.read.executionPayload.withdrawals.isNone:
|
2023-03-10 09:12:29 +00:00
|
|
|
warn "Execution client returned a block without a 'withdrawals' field for a post-Shanghai block",
|
2023-03-05 01:40:21 +00:00
|
|
|
url = m.elConnections[idx].engineUrl.url
|
|
|
|
continue
|
|
|
|
|
|
|
|
if engineApiWithdrawals != req.read.executionPayload.withdrawals.maybeDeref:
|
|
|
|
warn "Execution client did not return correct withdrawals",
|
|
|
|
withdrawals_from_cl = engineApiWithdrawals,
|
|
|
|
withdrawals_from_el = req.read.executionPayload.withdrawals
|
|
|
|
|
2023-03-07 04:43:27 +00:00
|
|
|
if req.read.executionPayload.extraData.len > MAX_EXTRA_DATA_BYTES:
|
|
|
|
warn "Execution client provided a block with invalid extraData (size exceeds limit)",
|
2023-03-09 17:29:04 +00:00
|
|
|
size = req.read.executionPayload.extraData.len,
|
|
|
|
limit = MAX_EXTRA_DATA_BYTES
|
2023-03-07 04:43:27 +00:00
|
|
|
continue
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
if bestPayloadIdx.isNone:
|
|
|
|
bestPayloadIdx = some idx
|
|
|
|
else:
|
|
|
|
if cmpGetPayloadResponses(req.read, requests[bestPayloadIdx.get].read) > 0:
|
|
|
|
bestPayloadIdx = some idx
|
|
|
|
|
|
|
|
if bestPayloadIdx.isSome:
|
|
|
|
return ok requests[bestPayloadIdx.get].read.asConsensusType
|
|
|
|
else:
|
|
|
|
return err()
|
|
|
|
|
|
|
|
proc waitELToSyncDeposits(connection: ELConnection,
|
|
|
|
minimalRequiredBlock: BlockHash) {.async.} =
|
|
|
|
var rpcClient = await connection.connectedRpcClient()
|
|
|
|
|
|
|
|
if connection.depositContractSyncStatus == DepositContractSyncStatus.synced:
|
|
|
|
return
|
|
|
|
|
|
|
|
var attempt = 0
|
|
|
|
|
|
|
|
while true:
|
|
|
|
try:
|
2023-03-09 17:29:04 +00:00
|
|
|
discard connection.trackedRequestWithTimeout(
|
|
|
|
"getBlockByHash",
|
|
|
|
rpcClient.getBlockByHash(minimalRequiredBlock),
|
2023-03-21 22:19:36 +00:00
|
|
|
web3RequestsTimeout,
|
|
|
|
failureAllowed = true)
|
2023-03-05 01:40:21 +00:00
|
|
|
connection.depositContractSyncStatus = DepositContractSyncStatus.synced
|
|
|
|
return
|
|
|
|
except CancelledError as err:
|
|
|
|
trace "waitELToSyncDepositContract cancelled",
|
|
|
|
url = connection.engineUrl.url
|
|
|
|
raise err
|
|
|
|
except CatchableError as err:
|
|
|
|
connection.depositContractSyncStatus = DepositContractSyncStatus.notSynced
|
|
|
|
if attempt == 0:
|
|
|
|
warn "Failed to obtain the most recent known block from the execution " &
|
|
|
|
"layer node (the node is probably not synced)",
|
|
|
|
url = connection.engineUrl.url,
|
|
|
|
blk = minimalRequiredBlock,
|
|
|
|
err = err.msg
|
|
|
|
elif attempt mod 60 == 0:
|
|
|
|
# This warning will be produced every 30 minutes
|
|
|
|
warn "Still failing to obtain the most recent known block from the " &
|
|
|
|
"execution layer node (the node is probably still not synced)",
|
|
|
|
url = connection.engineUrl.url,
|
|
|
|
blk = minimalRequiredBlock,
|
|
|
|
err = err.msg
|
2023-03-10 10:55:55 +00:00
|
|
|
inc attempt
|
2023-03-05 01:40:21 +00:00
|
|
|
await sleepAsync(seconds(30))
|
|
|
|
rpcClient = await connection.connectedRpcClient()
|
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
func networkHasDepositContract(m: ELManager): bool =
|
2023-03-05 01:40:21 +00:00
|
|
|
not m.cfg.DEPOSIT_CONTRACT_ADDRESS.isDefaultValue
|
|
|
|
|
|
|
|
func mostRecentKnownBlock(m: ELManager): BlockHash =
|
|
|
|
if m.eth1Chain.finalizedDepositsMerkleizer.getChunkCount() > 0:
|
|
|
|
m.eth1Chain.finalizedBlockHash.asBlockHash
|
|
|
|
else:
|
|
|
|
m.depositContractBlockHash
|
|
|
|
|
|
|
|
proc selectConnectionForChainSyncing(m: ELManager): Future[ELConnection] {.async.} =
|
|
|
|
doAssert m.elConnections.len > 0
|
|
|
|
|
|
|
|
let connectionsFuts = mapIt(
|
|
|
|
m.elConnections,
|
|
|
|
if m.networkHasDepositContract:
|
|
|
|
FutureBase waitELToSyncDeposits(it, m.mostRecentKnownBlock)
|
|
|
|
else:
|
|
|
|
FutureBase connectedRpcClient(it))
|
|
|
|
|
|
|
|
# TODO: Ideally, the cancellation will be handled automatically
|
|
|
|
# by a helper like `firstCompletedFuture`
|
2023-03-10 10:55:55 +00:00
|
|
|
let firstConnected = try:
|
|
|
|
await firstCompletedFuture(connectionsFuts)
|
|
|
|
except CancelledError as err:
|
|
|
|
for future in connectionsFuts:
|
|
|
|
future.cancel()
|
|
|
|
raise err
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
for future in connectionsFuts:
|
|
|
|
if future != firstConnected:
|
|
|
|
future.cancel()
|
|
|
|
|
|
|
|
return m.elConnections[find(connectionsFuts, firstConnected)]
|
|
|
|
|
|
|
|
proc sendNewPayloadToSingleEL(connection: ELConnection,
|
|
|
|
payload: engine_api.ExecutionPayloadV1):
|
|
|
|
Future[PayloadStatusV1] {.async.} =
|
|
|
|
let rpcClient = await connection.connectedRpcClient()
|
|
|
|
return await rpcClient.engine_newPayloadV1(payload)
|
|
|
|
|
|
|
|
proc sendNewPayloadToSingleEL(connection: ELConnection,
|
|
|
|
payload: engine_api.ExecutionPayloadV2):
|
|
|
|
Future[PayloadStatusV1] {.async.} =
|
|
|
|
let rpcClient = await connection.connectedRpcClient()
|
|
|
|
return await rpcClient.engine_newPayloadV2(payload)
|
|
|
|
|
|
|
|
proc sendNewPayloadToSingleEL(connection: ELConnection,
|
|
|
|
payload: engine_api.ExecutionPayloadV3):
|
|
|
|
Future[PayloadStatusV1] {.async.} =
|
|
|
|
let rpcClient = await connection.connectedRpcClient()
|
|
|
|
return await rpcClient.engine_newPayloadV3(payload)
|
2022-06-15 02:38:27 +00:00
|
|
|
|
2022-08-22 19:44:40 +00:00
|
|
|
type
|
2023-03-05 01:40:21 +00:00
|
|
|
StatusRelation = enum
|
|
|
|
newStatusIsPreferable
|
|
|
|
oldStatusIsOk
|
|
|
|
disagreement
|
|
|
|
|
2023-03-14 16:18:53 +00:00
|
|
|
func compareStatuses(newStatus, prevStatus: PayloadExecutionStatus): StatusRelation =
|
2023-03-05 01:40:21 +00:00
|
|
|
case prevStatus
|
|
|
|
of PayloadExecutionStatus.syncing:
|
|
|
|
if newStatus == PayloadExecutionStatus.syncing:
|
|
|
|
oldStatusIsOk
|
|
|
|
else:
|
|
|
|
newStatusIsPreferable
|
|
|
|
|
|
|
|
of PayloadExecutionStatus.valid:
|
|
|
|
case newStatus
|
|
|
|
of PayloadExecutionStatus.syncing,
|
|
|
|
PayloadExecutionStatus.accepted,
|
|
|
|
PayloadExecutionStatus.valid:
|
|
|
|
oldStatusIsOk
|
|
|
|
of PayloadExecutionStatus.invalid_block_hash,
|
|
|
|
PayloadExecutionStatus.invalid:
|
|
|
|
disagreement
|
|
|
|
|
|
|
|
of PayloadExecutionStatus.invalid:
|
|
|
|
case newStatus
|
|
|
|
of PayloadExecutionStatus.syncing,
|
|
|
|
PayloadExecutionStatus.invalid:
|
|
|
|
oldStatusIsOk
|
|
|
|
of PayloadExecutionStatus.valid,
|
|
|
|
PayloadExecutionStatus.accepted,
|
|
|
|
PayloadExecutionStatus.invalid_block_hash:
|
|
|
|
disagreement
|
|
|
|
|
|
|
|
of PayloadExecutionStatus.accepted:
|
|
|
|
case newStatus
|
|
|
|
of PayloadExecutionStatus.accepted,
|
|
|
|
PayloadExecutionStatus.syncing:
|
|
|
|
oldStatusIsOk
|
|
|
|
of PayloadExecutionStatus.valid:
|
|
|
|
newStatusIsPreferable
|
|
|
|
of PayloadExecutionStatus.invalid_block_hash,
|
|
|
|
PayloadExecutionStatus.invalid:
|
|
|
|
disagreement
|
|
|
|
|
|
|
|
of PayloadExecutionStatus.invalid_block_hash:
|
|
|
|
if newStatus == PayloadExecutionStatus.invalid_block_hash:
|
|
|
|
oldStatusIsOk
|
|
|
|
else:
|
|
|
|
disagreement
|
|
|
|
|
|
|
|
type
|
|
|
|
ELConsensusViolationDetector = object
|
|
|
|
selectedResponse: Option[int]
|
|
|
|
disagreementAlreadyDetected: bool
|
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
func init(T: type ELConsensusViolationDetector): T =
|
2023-03-05 01:40:21 +00:00
|
|
|
ELConsensusViolationDetector(selectedResponse: none int,
|
|
|
|
disagreementAlreadyDetected: false)
|
|
|
|
|
|
|
|
proc processResponse[ELResponseType](
|
|
|
|
d: var ELConsensusViolationDetector,
|
|
|
|
connections: openArray[ELConnection],
|
|
|
|
requests: openArray[Future[ELResponseType]],
|
|
|
|
idx: int) =
|
|
|
|
|
2023-03-21 21:35:22 +00:00
|
|
|
if not requests[idx].completed:
|
|
|
|
return
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
let status = try: requests[idx].read.status
|
|
|
|
except CatchableError: raiseAssert "checked above"
|
|
|
|
if d.selectedResponse.isNone:
|
|
|
|
d.selectedResponse = some idx
|
|
|
|
elif not d.disagreementAlreadyDetected:
|
|
|
|
let prevStatus = try: requests[d.selectedResponse.get].read.status
|
|
|
|
except CatchableError: raiseAssert "previously checked"
|
|
|
|
case compareStatuses(status, prevStatus)
|
|
|
|
of newStatusIsPreferable:
|
|
|
|
d.selectedResponse = some idx
|
|
|
|
of oldStatusIsOk:
|
|
|
|
discard
|
|
|
|
of disagreement:
|
|
|
|
d.disagreementAlreadyDetected = true
|
|
|
|
error "Execution layer consensus violation detected",
|
|
|
|
responseType = name(ELResponseType),
|
|
|
|
url1 = connections[d.selectedResponse.get].engineUrl.url,
|
|
|
|
status1 = prevStatus,
|
|
|
|
url2 = connections[idx].engineUrl.url,
|
|
|
|
status2 = status
|
|
|
|
|
|
|
|
proc sendNewPayload*(m: ELManager,
|
|
|
|
payload: engine_api.ExecutionPayloadV1 | engine_api.ExecutionPayloadV2 | engine_api.ExecutionPayloadV3):
|
|
|
|
Future[PayloadExecutionStatus] {.async.} =
|
|
|
|
let
|
|
|
|
earlyDeadline = sleepAsync(chronos.seconds 1)
|
2023-03-09 17:29:04 +00:00
|
|
|
startTime = Moment.now
|
2023-03-05 01:40:21 +00:00
|
|
|
deadline = sleepAsync(NEWPAYLOAD_TIMEOUT)
|
|
|
|
requests = m.elConnections.mapIt:
|
|
|
|
let req = sendNewPayloadToSingleEL(it, payload)
|
2023-03-09 17:29:04 +00:00
|
|
|
trackEngineApiRequest(it, req, "newPayload", startTime, deadline)
|
2023-03-05 01:40:21 +00:00
|
|
|
req
|
|
|
|
|
|
|
|
requestsCompleted = allFutures(requests)
|
|
|
|
|
|
|
|
await requestsCompleted or earlyDeadline
|
2022-08-22 19:44:40 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
var
|
|
|
|
stillPending = newSeq[Future[PayloadStatusV1]]()
|
|
|
|
responseProcessor = init ELConsensusViolationDetector
|
|
|
|
|
|
|
|
for idx, req in requests:
|
|
|
|
if not req.finished:
|
|
|
|
stillPending.add req
|
2023-03-21 21:35:22 +00:00
|
|
|
elif req.completed:
|
2023-03-05 01:40:21 +00:00
|
|
|
responseProcessor.processResponse(m.elConnections, requests, idx)
|
|
|
|
|
|
|
|
if responseProcessor.disagreementAlreadyDetected:
|
|
|
|
return PayloadExecutionStatus.invalid
|
|
|
|
elif responseProcessor.selectedResponse.isSome:
|
|
|
|
return requests[responseProcessor.selectedResponse.get].read.status
|
|
|
|
|
|
|
|
await requestsCompleted or deadline
|
|
|
|
|
|
|
|
for idx, req in requests:
|
|
|
|
if req.completed and req in stillPending:
|
|
|
|
responseProcessor.processResponse(m.elConnections, requests, idx)
|
|
|
|
|
|
|
|
return if responseProcessor.disagreementAlreadyDetected:
|
|
|
|
PayloadExecutionStatus.invalid
|
|
|
|
elif responseProcessor.selectedResponse.isSome:
|
|
|
|
requests[responseProcessor.selectedResponse.get].read.status
|
|
|
|
else:
|
|
|
|
PayloadExecutionStatus.syncing
|
|
|
|
|
|
|
|
proc forkchoiceUpdatedForSingleEL(
|
|
|
|
connection: ELConnection,
|
|
|
|
state: ref ForkchoiceStateV1,
|
2023-03-06 16:19:15 +00:00
|
|
|
payloadAttributes: PayloadAttributesV1 | PayloadAttributesV2 |
|
|
|
|
NoPayloadAttributesType):
|
2023-03-05 01:40:21 +00:00
|
|
|
Future[PayloadStatusV1] {.async.} =
|
|
|
|
let
|
|
|
|
rpcClient = await connection.connectedRpcClient()
|
|
|
|
response = await rpcClient.forkchoiceUpdated(state[], payloadAttributes)
|
|
|
|
|
|
|
|
if response.payloadStatus.status notin {syncing, valid, invalid}:
|
|
|
|
debug "Invalid fork-choice updated response from the EL",
|
|
|
|
payloadStatus = response.payloadStatus
|
|
|
|
return
|
|
|
|
|
|
|
|
if response.payloadStatus.status == PayloadExecutionStatus.valid and
|
|
|
|
response.payloadId.isSome:
|
|
|
|
connection.lastPayloadId = response.payloadId
|
|
|
|
|
|
|
|
return response.payloadStatus
|
|
|
|
|
|
|
|
proc forkchoiceUpdated*(m: ELManager,
|
|
|
|
headBlockHash, safeBlockHash, finalizedBlockHash: Eth2Digest,
|
2023-03-06 16:19:15 +00:00
|
|
|
payloadAttributes: PayloadAttributesV1 | PayloadAttributesV2 |
|
|
|
|
NoPayloadAttributesType):
|
2023-03-05 01:40:21 +00:00
|
|
|
Future[(PayloadExecutionStatus, Option[BlockHash])] {.async.} =
|
|
|
|
doAssert not headBlockHash.isZero
|
|
|
|
|
|
|
|
# Allow finalizedBlockHash to be 0 to avoid sync deadlocks.
|
|
|
|
#
|
|
|
|
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3675.md#pos-events
|
|
|
|
# has "Before the first finalized block occurs in the system the finalized
|
|
|
|
# block hash provided by this event is stubbed with
|
|
|
|
# `0x0000000000000000000000000000000000000000000000000000000000000000`."
|
|
|
|
# and
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/bellatrix/validator.md#executionpayload
|
|
|
|
# notes "`finalized_block_hash` is the hash of the latest finalized execution
|
|
|
|
# payload (`Hash32()` if none yet finalized)"
|
|
|
|
|
|
|
|
if m.elConnections.len == 0:
|
|
|
|
return (PayloadExecutionStatus.syncing, none BlockHash)
|
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
when payloadAttributes is PayloadAttributesV2:
|
|
|
|
template payloadAttributesV2(): auto = payloadAttributes
|
|
|
|
elif payloadAttributes is PayloadAttributesV1:
|
|
|
|
template payloadAttributesV2(): auto = PayloadAttributesV2(
|
|
|
|
timestamp: payloadAttributes.timestamp,
|
|
|
|
prevRandao: payloadAttributes.prevRandao,
|
|
|
|
suggestedFeeRecipient: payloadAttributes.suggestedFeeRecipient,
|
|
|
|
withdrawals: @[])
|
|
|
|
elif payloadAttributes is NoPayloadAttributesType:
|
|
|
|
template payloadAttributesV2(): auto =
|
|
|
|
# Because timestamp and prevRandao are both 0, won't false-positive match
|
|
|
|
(static(default(PayloadAttributesV2)))
|
|
|
|
else:
|
|
|
|
static: doAssert false
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
m.nextExpectedPayloadParams = some NextExpectedPayloadParams(
|
|
|
|
headBlockHash: headBlockHash,
|
|
|
|
safeBlockHash: safeBlockHash,
|
|
|
|
finalizedBlockHash: finalizedBlockHash,
|
2023-03-06 16:19:15 +00:00
|
|
|
payloadAttributes: payloadAttributesV2)
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
let
|
|
|
|
state = newClone ForkchoiceStateV1(
|
|
|
|
headBlockHash: headBlockHash.asBlockHash,
|
|
|
|
safeBlockHash: safeBlockHash.asBlockHash,
|
|
|
|
finalizedBlockHash: finalizedBlockHash.asBlockHash)
|
|
|
|
earlyDeadline = sleepAsync(chronos.seconds 1)
|
2023-03-09 17:29:04 +00:00
|
|
|
startTime = Moment.now
|
2023-03-05 01:40:21 +00:00
|
|
|
deadline = sleepAsync(FORKCHOICEUPDATED_TIMEOUT)
|
|
|
|
requests = m.elConnections.mapIt:
|
|
|
|
let req = it.forkchoiceUpdatedForSingleEL(state, payloadAttributes)
|
2023-03-09 17:29:04 +00:00
|
|
|
trackEngineApiRequest(it, req, "forkchoiceUpdated", startTime, deadline)
|
2023-03-05 01:40:21 +00:00
|
|
|
req
|
|
|
|
requestsCompleted = allFutures(requests)
|
|
|
|
|
|
|
|
await requestsCompleted or earlyDeadline
|
|
|
|
|
|
|
|
var
|
|
|
|
stillPending = newSeq[Future[PayloadStatusV1]]()
|
|
|
|
responseProcessor = init ELConsensusViolationDetector
|
|
|
|
|
|
|
|
for idx, req in requests:
|
|
|
|
if not req.finished:
|
|
|
|
stillPending.add req
|
2023-03-21 21:35:22 +00:00
|
|
|
elif req.completed:
|
2023-03-05 01:40:21 +00:00
|
|
|
responseProcessor.processResponse(m.elConnections, requests, idx)
|
|
|
|
|
|
|
|
if responseProcessor.disagreementAlreadyDetected:
|
|
|
|
return (PayloadExecutionStatus.invalid, none BlockHash)
|
|
|
|
elif responseProcessor.selectedResponse.isSome:
|
|
|
|
return (requests[responseProcessor.selectedResponse.get].read.status,
|
|
|
|
requests[responseProcessor.selectedResponse.get].read.latestValidHash)
|
|
|
|
|
|
|
|
await requestsCompleted or deadline
|
|
|
|
|
|
|
|
for idx, req in requests:
|
|
|
|
if req.completed and req in stillPending:
|
|
|
|
responseProcessor.processResponse(m.elConnections, requests, idx)
|
|
|
|
|
|
|
|
return if responseProcessor.disagreementAlreadyDetected:
|
|
|
|
(PayloadExecutionStatus.invalid, none BlockHash)
|
|
|
|
elif responseProcessor.selectedResponse.isSome:
|
|
|
|
(requests[responseProcessor.selectedResponse.get].read.status,
|
|
|
|
requests[responseProcessor.selectedResponse.get].read.latestValidHash)
|
|
|
|
else:
|
|
|
|
(PayloadExecutionStatus.syncing, none BlockHash)
|
|
|
|
|
|
|
|
proc forkchoiceUpdatedNoResult*(m: ELManager,
|
|
|
|
headBlockHash, safeBlockHash, finalizedBlockHash: Eth2Digest,
|
2023-03-06 16:19:15 +00:00
|
|
|
payloadAttributes: PayloadAttributesV1 | PayloadAttributesV2) {.async.} =
|
2023-03-05 01:40:21 +00:00
|
|
|
discard await m.forkchoiceUpdated(
|
|
|
|
headBlockHash, safeBlockHash, finalizedBlockHash, payloadAttributes)
|
|
|
|
|
|
|
|
# TODO can't be defined within exchangeConfigWithSingleEL
|
2023-03-06 16:19:15 +00:00
|
|
|
func `==`(x, y: Quantity): bool {.borrow.}
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
proc exchangeConfigWithSingleEL(m: ELManager, connection: ELConnection) {.async.} =
|
|
|
|
let rpcClient = await connection.connectedRpcClient()
|
|
|
|
|
|
|
|
if m.eth1Network.isSome and
|
|
|
|
connection.etcStatus == EtcStatus.notExchangedYet:
|
2022-07-25 19:23:02 +00:00
|
|
|
try:
|
2023-03-05 01:40:21 +00:00
|
|
|
let
|
|
|
|
providerChain =
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.trackedRequestWithTimeout(
|
|
|
|
"chainId",
|
|
|
|
rpcClient.eth_chainId(),
|
|
|
|
web3RequestsTimeout)
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
# https://eips.ethereum.org/EIPS/eip-155#list-of-chain-ids
|
|
|
|
expectedChain = case m.eth1Network.get
|
|
|
|
of mainnet: 1.Quantity
|
|
|
|
of ropsten: 3.Quantity
|
|
|
|
of rinkeby: 4.Quantity
|
|
|
|
of goerli: 5.Quantity
|
|
|
|
of sepolia: 11155111.Quantity # https://chainid.network/
|
|
|
|
if expectedChain != providerChain:
|
|
|
|
warn "The specified EL client is connected to a different chain",
|
|
|
|
url = connection.engineUrl,
|
|
|
|
expectedChain = distinctBase(expectedChain),
|
|
|
|
actualChain = distinctBase(providerChain)
|
|
|
|
connection.etcStatus = EtcStatus.mismatch
|
|
|
|
return
|
|
|
|
except CatchableError as exc:
|
|
|
|
# Typically because it's not synced through EIP-155, assuming this Web3
|
|
|
|
# endpoint has been otherwise working.
|
|
|
|
debug "Failed to obtain eth_chainId",
|
|
|
|
error = exc.msg
|
|
|
|
|
|
|
|
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.2/src/engine/specification.md#engine_exchangetransitionconfigurationv1
|
|
|
|
let
|
|
|
|
ourConf = TransitionConfigurationV1(
|
|
|
|
terminalTotalDifficulty: m.eth1Chain.cfg.TERMINAL_TOTAL_DIFFICULTY,
|
|
|
|
terminalBlockHash: m.eth1Chain.cfg.TERMINAL_BLOCK_HASH,
|
|
|
|
terminalBlockNumber: Quantity 0)
|
|
|
|
elConf = try:
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.trackedRequestWithTimeout(
|
|
|
|
"exchangeTransitionConfiguration",
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient.engine_exchangeTransitionConfigurationV1(ourConf),
|
2022-07-25 19:23:02 +00:00
|
|
|
timeout = 1.seconds)
|
|
|
|
except CatchableError as err:
|
2023-03-09 23:41:28 +00:00
|
|
|
warn "Failed to exchange transition configuration",
|
2023-03-05 01:40:21 +00:00
|
|
|
url = connection.engineUrl, err = err.msg
|
|
|
|
connection.etcStatus = EtcStatus.exchangeError
|
|
|
|
return
|
2022-08-22 19:44:40 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
connection.etcStatus =
|
|
|
|
if ourConf.terminalTotalDifficulty != elConf.terminalTotalDifficulty:
|
2023-03-09 23:41:28 +00:00
|
|
|
warn "Engine API configured with different terminal total difficulty",
|
|
|
|
engineAPI_value = elConf.terminalTotalDifficulty,
|
|
|
|
localValue = ourConf.terminalTotalDifficulty
|
2022-09-16 13:33:22 +00:00
|
|
|
EtcStatus.mismatch
|
2023-03-05 01:40:21 +00:00
|
|
|
elif ourConf.terminalBlockNumber != elConf.terminalBlockNumber:
|
2022-08-22 19:44:40 +00:00
|
|
|
warn "Engine API reporting different terminal block number",
|
2023-03-05 01:40:21 +00:00
|
|
|
engineAPI_value = elConf.terminalBlockNumber.uint64,
|
|
|
|
localValue = ourConf.terminalBlockNumber.uint64
|
2022-09-16 13:33:22 +00:00
|
|
|
EtcStatus.mismatch
|
2023-03-05 01:40:21 +00:00
|
|
|
elif ourConf.terminalBlockHash != elConf.terminalBlockHash:
|
2022-08-22 19:44:40 +00:00
|
|
|
warn "Engine API reporting different terminal block hash",
|
2023-03-05 01:40:21 +00:00
|
|
|
engineAPI_value = elConf.terminalBlockHash,
|
|
|
|
localValue = ourConf.terminalBlockHash
|
2022-09-16 13:33:22 +00:00
|
|
|
EtcStatus.mismatch
|
|
|
|
else:
|
2023-03-05 01:40:21 +00:00
|
|
|
if connection.etcStatus == EtcStatus.notExchangedYet:
|
2022-09-16 13:33:22 +00:00
|
|
|
# Log successful engine configuration exchange once at startup
|
2023-03-05 01:40:21 +00:00
|
|
|
info "Successfully exchanged engine configuration",
|
|
|
|
url = connection.engineUrl
|
2022-09-16 13:33:22 +00:00
|
|
|
EtcStatus.match
|
2022-06-15 02:38:27 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc exchangeTransitionConfiguration*(m: ELManager) {.async.} =
|
|
|
|
if m.elConnections.len == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
let
|
|
|
|
deadline = sleepAsync(3.seconds)
|
|
|
|
requests = m.elConnections.mapIt(m.exchangeConfigWithSingleEL(it))
|
|
|
|
requestsCompleted = allFutures(requests)
|
|
|
|
|
|
|
|
await requestsCompleted or deadline
|
|
|
|
|
2023-03-09 23:41:28 +00:00
|
|
|
var cancelled = 0
|
2023-03-05 01:40:21 +00:00
|
|
|
for idx, req in requests:
|
|
|
|
if not req.finished:
|
|
|
|
m.elConnections[idx].etcStatus = EtcStatus.exchangeError
|
|
|
|
req.cancel()
|
2023-03-09 23:41:28 +00:00
|
|
|
inc cancelled
|
|
|
|
|
|
|
|
if cancelled == requests.len:
|
|
|
|
warn "Failed to exchange configuration with the configured EL end-points"
|
2023-03-05 01:40:21 +00:00
|
|
|
|
2020-11-03 01:21:07 +00:00
|
|
|
template readJsonField(j: JsonNode, fieldName: string, ValueType: type): untyped =
|
2020-06-27 12:01:19 +00:00
|
|
|
var res: ValueType
|
|
|
|
fromJson(j[fieldName], fieldName, res)
|
|
|
|
res
|
|
|
|
|
2021-10-14 12:33:55 +00:00
|
|
|
template init[N: static int](T: type DynamicBytes[N, N]): T =
|
|
|
|
T newSeq[byte](N)
|
|
|
|
|
2023-03-09 17:29:04 +00:00
|
|
|
proc fetchTimestamp(connection: ELConnection,
|
|
|
|
rpcClient: RpcClient,
|
|
|
|
blk: Eth1Block) {.async.} =
|
|
|
|
debug "Fetching block timestamp", blockNum = blk.number
|
|
|
|
|
|
|
|
let web3block = connection.trackedRequestWithTimeout(
|
|
|
|
"getBlockByHash",
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient.getBlockByHash(blk.hash.asBlockHash),
|
|
|
|
web3RequestsTimeout)
|
2023-03-09 17:29:04 +00:00
|
|
|
|
2022-08-10 12:31:10 +00:00
|
|
|
blk.timestamp = Eth1BlockTimestamp web3block.timestamp
|
|
|
|
|
2021-11-25 10:53:31 +00:00
|
|
|
func depositEventsToBlocks(depositsList: JsonNode): seq[Eth1Block] {.
|
2021-03-26 06:52:01 +00:00
|
|
|
raises: [Defect, CatchableError].} =
|
2020-06-27 12:01:19 +00:00
|
|
|
if depositsList.kind != JArray:
|
|
|
|
raise newException(CatchableError,
|
|
|
|
"Web3 provider didn't return a list of deposit events")
|
|
|
|
|
|
|
|
var lastEth1Block: Eth1Block
|
|
|
|
|
|
|
|
for logEvent in depositsList:
|
|
|
|
let
|
|
|
|
blockNumber = Eth1BlockNumber readJsonField(logEvent, "blockNumber", Quantity)
|
|
|
|
blockHash = readJsonField(logEvent, "blockHash", BlockHash)
|
|
|
|
logData = strip0xPrefix(logEvent["data"].getStr)
|
|
|
|
|
|
|
|
if lastEth1Block == nil or lastEth1Block.number != blockNumber:
|
|
|
|
lastEth1Block = Eth1Block(
|
2022-08-10 12:31:10 +00:00
|
|
|
hash: blockHash.asEth2Digest,
|
|
|
|
number: blockNumber
|
|
|
|
# The `timestamp` is set in `syncBlockRange` immediately
|
|
|
|
# after calling this function, because we don't want to
|
|
|
|
# make this function `async`
|
|
|
|
)
|
2020-06-27 12:01:19 +00:00
|
|
|
|
|
|
|
result.add lastEth1Block
|
|
|
|
|
|
|
|
var
|
2021-10-14 12:33:55 +00:00
|
|
|
pubkey = init PubKeyBytes
|
|
|
|
withdrawalCredentials = init WithdrawalCredentialsBytes
|
|
|
|
amount = init Int64LeBytes
|
|
|
|
signature = init SignatureBytes
|
|
|
|
index = init Int64LeBytes
|
2020-06-27 12:01:19 +00:00
|
|
|
|
|
|
|
var offset = 0
|
|
|
|
offset += decode(logData, offset, pubkey)
|
|
|
|
offset += decode(logData, offset, withdrawalCredentials)
|
|
|
|
offset += decode(logData, offset, amount)
|
|
|
|
offset += decode(logData, offset, signature)
|
|
|
|
offset += decode(logData, offset, index)
|
|
|
|
|
2021-10-14 12:33:55 +00:00
|
|
|
if pubkey.len != 48 or
|
|
|
|
withdrawalCredentials.len != 32 or
|
|
|
|
amount.len != 8 or
|
|
|
|
signature.len != 96 or
|
|
|
|
index.len != 8:
|
|
|
|
raise newException(CorruptDataProvider, "Web3 provider supplied invalid deposit logs")
|
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
lastEth1Block.deposits.add DepositData(
|
2021-10-14 12:33:55 +00:00
|
|
|
pubkey: ValidatorPubKey.init(pubkey.toArray),
|
|
|
|
withdrawal_credentials: Eth2Digest(data: withdrawalCredentials.toArray),
|
|
|
|
amount: bytes_to_uint64(amount.toArray),
|
|
|
|
signature: ValidatorSig.init(signature.toArray))
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2020-12-01 11:14:32 +00:00
|
|
|
type
|
|
|
|
DepositContractDataStatus = enum
|
|
|
|
Fetched
|
|
|
|
VerifiedCorrect
|
|
|
|
DepositRootIncorrect
|
|
|
|
DepositRootUnavailable
|
|
|
|
DepositCountIncorrect
|
|
|
|
DepositCountUnavailable
|
2020-11-03 01:21:07 +00:00
|
|
|
|
2020-12-01 11:14:32 +00:00
|
|
|
when hasDepositRootChecks:
|
2020-11-24 21:21:47 +00:00
|
|
|
const
|
2022-07-25 19:23:02 +00:00
|
|
|
contractCallTimeout = 60.seconds
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2023-03-09 17:29:04 +00:00
|
|
|
proc fetchDepositContractData(connection: ELConnection,
|
|
|
|
rpcClient: RpcClient,
|
2023-03-05 01:40:21 +00:00
|
|
|
depositContact: Sender[DepositContract],
|
|
|
|
blk: Eth1Block): Future[DepositContractDataStatus] {.async.} =
|
2020-11-03 01:21:07 +00:00
|
|
|
let
|
2023-03-09 17:29:04 +00:00
|
|
|
startTime = Moment.now
|
|
|
|
deadline = sleepAsync(contractCallTimeout)
|
2023-03-05 01:40:21 +00:00
|
|
|
depositRoot = depositContract.get_deposit_root.call(blockNumber = blk.number)
|
|
|
|
rawCount = depositContract.get_deposit_count.call(blockNumber = blk.number)
|
2020-10-21 13:25:53 +00:00
|
|
|
|
2023-03-21 22:19:36 +00:00
|
|
|
# We allow failures on these requests becaues the clients
|
|
|
|
# are expected to prune the state data for historical blocks
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.trackEngineApiRequest(
|
2023-03-21 22:19:36 +00:00
|
|
|
depositRoot, "get_deposit_root", startTime, deadline,
|
|
|
|
failureAllowed = true)
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.trackEngineApiRequest(
|
2023-03-21 22:19:36 +00:00
|
|
|
rawCount, "get_deposit_count", startTime, deadline,
|
|
|
|
failureAllowed = true)
|
2023-03-09 17:29:04 +00:00
|
|
|
|
2020-11-03 01:21:07 +00:00
|
|
|
try:
|
2020-11-24 21:21:47 +00:00
|
|
|
let fetchedRoot = asEth2Digest(
|
2023-03-09 17:29:04 +00:00
|
|
|
awaitWithTimeout(depositRoot, deadline))
|
2022-08-10 12:31:10 +00:00
|
|
|
if blk.depositRoot.isZero:
|
|
|
|
blk.depositRoot = fetchedRoot
|
2020-11-03 01:21:07 +00:00
|
|
|
result = Fetched
|
2022-08-10 12:31:10 +00:00
|
|
|
elif blk.depositRoot == fetchedRoot:
|
2020-11-03 01:21:07 +00:00
|
|
|
result = VerifiedCorrect
|
|
|
|
else:
|
|
|
|
result = DepositRootIncorrect
|
|
|
|
except CatchableError as err:
|
|
|
|
debug "Failed to fetch deposits root",
|
|
|
|
blockNumber = blk.number,
|
|
|
|
err = err.msg
|
|
|
|
result = DepositRootUnavailable
|
2020-10-21 13:25:53 +00:00
|
|
|
|
2020-11-03 01:21:07 +00:00
|
|
|
try:
|
2022-08-09 21:32:34 +00:00
|
|
|
let fetchedCount = bytes_to_uint64(
|
2023-03-09 17:29:04 +00:00
|
|
|
awaitWithTimeout(rawCount, deadline).toArray)
|
2022-08-10 12:31:10 +00:00
|
|
|
if blk.depositCount == 0:
|
|
|
|
blk.depositCount = fetchedCount
|
|
|
|
elif blk.depositCount != fetchedCount:
|
2020-11-03 01:21:07 +00:00
|
|
|
result = DepositCountIncorrect
|
|
|
|
except CatchableError as err:
|
|
|
|
debug "Failed to fetch deposits count",
|
|
|
|
blockNumber = blk.number,
|
|
|
|
err = err.msg
|
|
|
|
result = DepositCountUnavailable
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
proc pruneOldBlocks(chain: var Eth1Chain, depositIndex: uint64) =
|
2022-12-07 10:24:51 +00:00
|
|
|
## Called on block finalization to delete old and now redundant data.
|
2020-12-03 04:30:35 +00:00
|
|
|
let initialChunks = chain.finalizedDepositsMerkleizer.getChunkCount
|
2020-11-24 21:21:47 +00:00
|
|
|
var lastBlock: Eth1Block
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
while chain.blocks.len > 0:
|
|
|
|
let blk = chain.blocks.peekFirst
|
2022-08-10 12:31:10 +00:00
|
|
|
if blk.depositCount >= depositIndex:
|
2020-11-24 21:21:47 +00:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
for deposit in blk.deposits:
|
2020-12-03 04:30:35 +00:00
|
|
|
chain.finalizedDepositsMerkleizer.addChunk hash_tree_root(deposit).data
|
|
|
|
chain.popFirst()
|
2020-11-24 21:21:47 +00:00
|
|
|
lastBlock = blk
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
if chain.finalizedDepositsMerkleizer.getChunkCount > initialChunks:
|
2022-08-10 12:31:10 +00:00
|
|
|
chain.finalizedBlockHash = lastBlock.hash
|
2022-12-07 10:24:51 +00:00
|
|
|
chain.db.putDepositTreeSnapshot DepositTreeSnapshot(
|
2022-08-10 12:31:10 +00:00
|
|
|
eth1Block: lastBlock.hash,
|
2022-12-07 10:24:51 +00:00
|
|
|
depositContractState: chain.finalizedDepositsMerkleizer.toDepositContractState,
|
2023-02-23 02:10:07 +00:00
|
|
|
blockHeight: lastBlock.number)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2020-12-09 22:44:59 +00:00
|
|
|
eth1_finalized_head.set lastBlock.number.toGaugeValue
|
2022-08-10 12:31:10 +00:00
|
|
|
eth1_finalized_deposits.set lastBlock.depositCount.toGaugeValue
|
2020-12-09 22:44:59 +00:00
|
|
|
|
2020-11-30 23:59:35 +00:00
|
|
|
debug "Eth1 blocks pruned",
|
2022-08-10 12:31:10 +00:00
|
|
|
newTailBlock = lastBlock.hash,
|
|
|
|
depositsCount = lastBlock.depositCount
|
2020-11-30 23:59:35 +00:00
|
|
|
|
2021-11-25 10:53:31 +00:00
|
|
|
func advanceMerkleizer(chain: Eth1Chain,
|
2020-11-24 21:21:47 +00:00
|
|
|
merkleizer: var DepositsMerkleizer,
|
|
|
|
depositIndex: uint64): bool =
|
2020-12-03 04:30:35 +00:00
|
|
|
if chain.blocks.len == 0:
|
2020-11-24 21:21:47 +00:00
|
|
|
return depositIndex == merkleizer.getChunkCount
|
|
|
|
|
2022-08-10 12:31:10 +00:00
|
|
|
if chain.blocks.peekLast.depositCount < depositIndex:
|
2020-11-24 21:21:47 +00:00
|
|
|
return false
|
|
|
|
|
|
|
|
let
|
2020-12-03 04:30:35 +00:00
|
|
|
firstBlock = chain.blocks[0]
|
2022-08-10 12:31:10 +00:00
|
|
|
depositsInLastPrunedBlock = firstBlock.depositCount -
|
2020-11-24 21:21:47 +00:00
|
|
|
firstBlock.deposits.lenu64
|
|
|
|
|
|
|
|
# advanceMerkleizer should always be called shortly after prunning the chain
|
|
|
|
doAssert depositsInLastPrunedBlock == merkleizer.getChunkCount
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
for blk in chain.blocks:
|
2020-11-24 21:21:47 +00:00
|
|
|
for deposit in blk.deposits:
|
|
|
|
if merkleizer.getChunkCount < depositIndex:
|
|
|
|
merkleizer.addChunk hash_tree_root(deposit).data
|
|
|
|
else:
|
|
|
|
return true
|
|
|
|
|
|
|
|
return merkleizer.getChunkCount == depositIndex
|
|
|
|
|
2022-08-12 13:52:06 +00:00
|
|
|
iterator getDepositsRange*(chain: Eth1Chain, first, last: uint64): DepositData =
|
2020-11-24 21:21:47 +00:00
|
|
|
# TODO It's possible to make this faster by performing binary search that
|
|
|
|
# will locate the blocks holding the `first` and `last` indices.
|
|
|
|
# TODO There is an assumption here that the requested range will be present
|
2022-08-12 13:52:06 +00:00
|
|
|
# in the Eth1Chain. This should hold true at the call sites right now,
|
|
|
|
# but we need to guard the pre-conditions better.
|
2020-12-03 04:30:35 +00:00
|
|
|
for blk in chain.blocks:
|
2022-08-10 12:31:10 +00:00
|
|
|
if blk.depositCount <= first:
|
2020-11-24 21:21:47 +00:00
|
|
|
continue
|
|
|
|
|
2022-08-10 12:31:10 +00:00
|
|
|
let firstDepositIdxInBlk = blk.depositCount - blk.deposits.lenu64
|
2020-11-24 21:21:47 +00:00
|
|
|
if firstDepositIdxInBlk >= last:
|
2022-08-12 13:52:06 +00:00
|
|
|
break
|
2020-11-24 21:21:47 +00:00
|
|
|
|
|
|
|
for i in 0 ..< blk.deposits.lenu64:
|
|
|
|
let globalIdx = firstDepositIdxInBlk + i
|
|
|
|
if globalIdx >= first and globalIdx < last:
|
2022-08-12 13:52:06 +00:00
|
|
|
yield blk.deposits[i]
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2021-11-25 10:53:31 +00:00
|
|
|
func lowerBound(chain: Eth1Chain, depositCount: uint64): Eth1Block =
|
2020-11-30 23:59:35 +00:00
|
|
|
# TODO: This can be replaced with a proper binary search in the
|
|
|
|
# future, but the `algorithm` module currently requires an
|
|
|
|
# `openArray`, which the `deques` module can't provide yet.
|
|
|
|
for eth1Block in chain.blocks:
|
2022-08-10 12:31:10 +00:00
|
|
|
if eth1Block.depositCount > depositCount:
|
2020-11-30 23:59:35 +00:00
|
|
|
return
|
|
|
|
result = eth1Block
|
|
|
|
|
2021-12-05 17:32:41 +00:00
|
|
|
proc trackFinalizedState(chain: var Eth1Chain,
|
|
|
|
finalizedEth1Data: Eth1Data,
|
2022-01-31 17:28:26 +00:00
|
|
|
finalizedStateDepositIndex: uint64,
|
|
|
|
blockProposalExpected = false): bool =
|
2023-03-05 01:40:21 +00:00
|
|
|
## This function will return true if the ELManager is synced
|
2022-08-10 12:31:10 +00:00
|
|
|
## to the finalization point.
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
if chain.blocks.len == 0:
|
2020-11-30 23:59:35 +00:00
|
|
|
debug "Eth1 chain not initialized"
|
|
|
|
return false
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
let latest = chain.blocks.peekLast
|
2022-08-10 12:31:10 +00:00
|
|
|
if latest.depositCount < finalizedEth1Data.deposit_count:
|
2022-01-31 17:28:26 +00:00
|
|
|
if blockProposalExpected:
|
|
|
|
error "The Eth1 chain is not synced",
|
2022-08-10 12:31:10 +00:00
|
|
|
ourDepositsCount = latest.depositCount,
|
2022-01-31 17:28:26 +00:00
|
|
|
targetDepositsCount = finalizedEth1Data.deposit_count
|
2020-11-30 23:59:35 +00:00
|
|
|
return false
|
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
let matchingBlock = chain.lowerBound(finalizedEth1Data.deposit_count)
|
2020-11-30 23:59:35 +00:00
|
|
|
result = if matchingBlock != nil:
|
2022-08-10 12:31:10 +00:00
|
|
|
if matchingBlock.depositRoot == finalizedEth1Data.deposit_root:
|
2020-11-24 21:21:47 +00:00
|
|
|
true
|
|
|
|
else:
|
|
|
|
error "Corrupted deposits history detected",
|
2022-08-10 12:31:10 +00:00
|
|
|
ourDepositsCount = matchingBlock.depositCount,
|
2020-12-03 04:30:35 +00:00
|
|
|
taretDepositsCount = finalizedEth1Data.deposit_count,
|
2022-08-10 12:31:10 +00:00
|
|
|
ourDepositsRoot = matchingBlock.depositRoot,
|
2020-12-03 04:30:35 +00:00
|
|
|
targetDepositsRoot = finalizedEth1Data.deposit_root
|
2021-01-29 21:21:44 +00:00
|
|
|
chain.hasConsensusViolation = true
|
2020-11-04 09:06:58 +00:00
|
|
|
false
|
|
|
|
else:
|
2020-11-30 23:59:35 +00:00
|
|
|
error "The Eth1 chain is in inconsistent state",
|
2020-11-24 21:21:47 +00:00
|
|
|
checkpointHash = finalizedEth1Data.block_hash,
|
|
|
|
checkpointDeposits = finalizedEth1Data.deposit_count,
|
2020-12-03 04:30:35 +00:00
|
|
|
localChainStart = shortLog(chain.blocks.peekFirst),
|
|
|
|
localChainEnd = shortLog(chain.blocks.peekLast)
|
2021-01-29 21:21:44 +00:00
|
|
|
chain.hasConsensusViolation = true
|
2020-11-04 09:06:58 +00:00
|
|
|
false
|
2020-10-26 08:55:10 +00:00
|
|
|
|
2020-11-30 23:59:35 +00:00
|
|
|
if result:
|
2020-12-03 04:30:35 +00:00
|
|
|
chain.pruneOldBlocks(finalizedStateDepositIndex)
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
template trackFinalizedState*(m: ELManager,
|
2020-12-03 04:30:35 +00:00
|
|
|
finalizedEth1Data: Eth1Data,
|
|
|
|
finalizedStateDepositIndex: uint64): bool =
|
2023-03-05 01:40:21 +00:00
|
|
|
trackFinalizedState(m.eth1Chain, finalizedEth1Data, finalizedStateDepositIndex)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2023-01-30 23:26:57 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#get_eth1_data
|
2020-12-03 04:30:35 +00:00
|
|
|
proc getBlockProposalData*(chain: var Eth1Chain,
|
2021-06-11 17:51:46 +00:00
|
|
|
state: ForkedHashedBeaconState,
|
2020-11-30 23:59:35 +00:00
|
|
|
finalizedEth1Data: Eth1Data,
|
|
|
|
finalizedStateDepositIndex: uint64): BlockProposalEth1Data =
|
|
|
|
let
|
|
|
|
periodStart = voting_period_start_time(state)
|
2020-12-03 04:30:35 +00:00
|
|
|
hasLatestDeposits = chain.trackFinalizedState(finalizedEth1Data,
|
2022-01-31 17:28:26 +00:00
|
|
|
finalizedStateDepositIndex,
|
|
|
|
blockProposalExpected = true)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-11-19 17:19:03 +00:00
|
|
|
var otherVotesCountTable = initCountTable[Eth1Data]()
|
2021-05-21 09:23:28 +00:00
|
|
|
for vote in getStateField(state, eth1_data_votes):
|
2020-12-03 04:30:35 +00:00
|
|
|
let eth1Block = chain.findBlock(vote)
|
2021-04-30 23:00:03 +00:00
|
|
|
if eth1Block != nil and
|
2022-08-10 12:31:10 +00:00
|
|
|
eth1Block.depositRoot == vote.deposit_root and
|
2021-05-21 09:23:28 +00:00
|
|
|
vote.deposit_count >= getStateField(state, eth1_data).deposit_count and
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
is_candidate_block(chain.cfg, eth1Block, periodStart):
|
2020-11-19 17:19:03 +00:00
|
|
|
otherVotesCountTable.inc vote
|
2020-11-20 14:05:37 +00:00
|
|
|
else:
|
2021-04-30 23:00:03 +00:00
|
|
|
debug "Ignoring eth1 vote",
|
|
|
|
root = vote.block_hash,
|
|
|
|
deposits = vote.deposit_count,
|
|
|
|
depositsRoot = vote.deposit_root,
|
2021-05-21 09:23:28 +00:00
|
|
|
localDeposits = getStateField(state, eth1_data).deposit_count
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2022-04-27 09:39:41 +00:00
|
|
|
let
|
|
|
|
stateDepositIdx = getStateField(state, eth1_deposit_index)
|
|
|
|
stateDepositsCount = getStateField(state, eth1_data).deposit_count
|
|
|
|
|
|
|
|
# A valid state should never have this condition, but it doesn't hurt
|
|
|
|
# to be extra defensive here because we are working with uint types
|
|
|
|
var pendingDepositsCount = if stateDepositsCount > stateDepositIdx:
|
|
|
|
stateDepositsCount - stateDepositIdx
|
|
|
|
else:
|
|
|
|
0
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
if otherVotesCountTable.len > 0:
|
2020-11-19 17:19:03 +00:00
|
|
|
let (winningVote, votes) = otherVotesCountTable.largest
|
2020-11-20 14:05:37 +00:00
|
|
|
debug "Voting on eth1 head with majority", votes
|
2020-11-19 17:19:03 +00:00
|
|
|
result.vote = winningVote
|
2020-10-12 01:07:20 +00:00
|
|
|
if uint64((votes + 1) * 2) > SLOTS_PER_ETH1_VOTING_PERIOD:
|
2022-04-27 09:39:41 +00:00
|
|
|
pendingDepositsCount = winningVote.deposit_count - stateDepositIdx
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
else:
|
2020-12-03 04:30:35 +00:00
|
|
|
let latestBlock = chain.latestCandidateBlock(periodStart)
|
2020-10-12 01:07:20 +00:00
|
|
|
if latestBlock == nil:
|
2020-11-20 14:05:37 +00:00
|
|
|
debug "No acceptable eth1 votes and no recent candidates. Voting no change"
|
2021-05-21 09:23:28 +00:00
|
|
|
result.vote = getStateField(state, eth1_data)
|
2020-10-12 01:07:20 +00:00
|
|
|
else:
|
2020-11-20 14:05:37 +00:00
|
|
|
debug "No acceptable eth1 votes. Voting for latest candidate"
|
2022-08-10 12:31:10 +00:00
|
|
|
result.vote = latestBlock.toVoteData
|
2020-10-15 17:30:33 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
if pendingDepositsCount > 0:
|
|
|
|
if hasLatestDeposits:
|
2020-12-02 18:15:36 +00:00
|
|
|
let
|
|
|
|
totalDepositsInNewBlock = min(MAX_DEPOSITS, pendingDepositsCount)
|
2022-04-27 09:39:41 +00:00
|
|
|
postStateDepositIdx = stateDepositIdx + pendingDepositsCount
|
2022-08-12 13:52:06 +00:00
|
|
|
var
|
|
|
|
deposits = newSeqOfCap[DepositData](totalDepositsInNewBlock)
|
|
|
|
depositRoots = newSeqOfCap[Eth2Digest](pendingDepositsCount)
|
|
|
|
for data in chain.getDepositsRange(stateDepositIdx, postStateDepositIdx):
|
|
|
|
if deposits.lenu64 < totalDepositsInNewBlock:
|
|
|
|
deposits.add data
|
|
|
|
depositRoots.add hash_tree_root(data)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
var scratchMerkleizer = copy chain.finalizedDepositsMerkleizer
|
2022-04-27 09:39:41 +00:00
|
|
|
if chain.advanceMerkleizer(scratchMerkleizer, stateDepositIdx):
|
2020-11-24 21:21:47 +00:00
|
|
|
let proofs = scratchMerkleizer.addChunksAndGenMerkleProofs(depositRoots)
|
2020-12-02 18:15:36 +00:00
|
|
|
for i in 0 ..< totalDepositsInNewBlock:
|
|
|
|
var proof: array[33, Eth2Digest]
|
|
|
|
proof[0..31] = proofs.getProof(i.int)
|
|
|
|
proof[32] = default(Eth2Digest)
|
2022-04-27 09:39:41 +00:00
|
|
|
proof[32].data[0..7] = toBytesLE uint64(postStateDepositIdx)
|
2020-12-02 18:15:36 +00:00
|
|
|
result.deposits.add Deposit(data: deposits[i], proof: proof)
|
2020-11-24 21:21:47 +00:00
|
|
|
else:
|
|
|
|
error "The Eth1 chain is in inconsistent state" # This should not really happen
|
|
|
|
result.hasMissingDeposits = true
|
|
|
|
else:
|
|
|
|
result.hasMissingDeposits = true
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
template getBlockProposalData*(m: ELManager,
|
2021-06-11 17:51:46 +00:00
|
|
|
state: ForkedHashedBeaconState,
|
2020-12-03 04:30:35 +00:00
|
|
|
finalizedEth1Data: Eth1Data,
|
2022-03-31 14:43:05 +00:00
|
|
|
finalizedStateDepositIndex: uint64):
|
|
|
|
BlockProposalEth1Data =
|
|
|
|
getBlockProposalData(
|
2023-03-05 01:40:21 +00:00
|
|
|
m.eth1Chain, state, finalizedEth1Data, finalizedStateDepositIndex)
|
2022-03-31 14:43:05 +00:00
|
|
|
|
2023-03-06 16:19:15 +00:00
|
|
|
func new*(T: type ELConnection,
|
2023-03-05 01:40:21 +00:00
|
|
|
engineUrl: EngineApiUrl): T =
|
|
|
|
ELConnection(
|
|
|
|
engineUrl: engineUrl,
|
|
|
|
depositContractSyncStatus: DepositContractSyncStatus.unknown)
|
2020-11-17 19:50:07 +00:00
|
|
|
|
2020-12-03 04:30:35 +00:00
|
|
|
template getOrDefault[T, E](r: Result[T, E]): T =
|
|
|
|
type TT = T
|
|
|
|
get(r, default(TT))
|
|
|
|
|
2022-12-19 17:19:48 +00:00
|
|
|
proc init*(T: type Eth1Chain,
|
|
|
|
cfg: RuntimeConfig,
|
|
|
|
db: BeaconChainDB,
|
|
|
|
depositContractBlockNumber: uint64,
|
|
|
|
depositContractBlockHash: Eth2Digest): T =
|
2022-06-29 16:53:59 +00:00
|
|
|
let
|
2022-12-19 17:19:48 +00:00
|
|
|
(finalizedBlockHash, depositContractState) =
|
2022-07-14 04:07:40 +00:00
|
|
|
if db != nil:
|
2022-12-19 17:19:48 +00:00
|
|
|
let treeSnapshot = db.getDepositTreeSnapshot()
|
|
|
|
if treeSnapshot.isSome:
|
|
|
|
(treeSnapshot.get.eth1Block, treeSnapshot.get.depositContractState)
|
|
|
|
else:
|
|
|
|
let oldSnapshot = db.getUpgradableDepositSnapshot()
|
|
|
|
if oldSnapshot.isSome:
|
|
|
|
(oldSnapshot.get.eth1Block, oldSnapshot.get.depositContractState)
|
|
|
|
else:
|
|
|
|
db.putDepositTreeSnapshot DepositTreeSnapshot(
|
|
|
|
eth1Block: depositContractBlockHash,
|
|
|
|
blockHeight: depositContractBlockNumber)
|
|
|
|
(depositContractBlockHash, default(DepositContractState))
|
2022-07-14 04:07:40 +00:00
|
|
|
else:
|
2022-12-19 17:19:48 +00:00
|
|
|
(depositContractBlockHash, default(DepositContractState))
|
|
|
|
m = DepositsMerkleizer.init(depositContractState)
|
2020-12-03 04:30:35 +00:00
|
|
|
|
|
|
|
T(db: db,
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
cfg: cfg,
|
2022-12-19 17:19:48 +00:00
|
|
|
finalizedBlockHash: finalizedBlockHash,
|
2022-08-10 12:31:10 +00:00
|
|
|
finalizedDepositsMerkleizer: m,
|
|
|
|
headMerkleizer: copy m)
|
2020-12-03 04:30:35 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc new*(T: type ELManager,
|
|
|
|
cfg: RuntimeConfig,
|
|
|
|
depositContractBlockNumber: uint64,
|
|
|
|
depositContractBlockHash: Eth2Digest,
|
|
|
|
db: BeaconChainDB,
|
|
|
|
engineApiUrls: seq[EngineApiUrl],
|
|
|
|
eth1Network: Option[Eth1Network]): T =
|
|
|
|
let
|
|
|
|
eth1Chain = Eth1Chain.init(
|
|
|
|
cfg, db, depositContractBlockNumber, depositContractBlockHash)
|
2022-06-15 02:38:27 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
debug "Initializing ELManager",
|
2023-02-23 02:10:07 +00:00
|
|
|
depositContractBlockNumber,
|
|
|
|
depositContractBlockHash
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
T(eth1Chain: eth1Chain,
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
depositContractAddress: cfg.DEPOSIT_CONTRACT_ADDRESS,
|
2023-03-05 01:40:21 +00:00
|
|
|
depositContractBlockNumber: depositContractBlockNumber,
|
|
|
|
depositContractBlockHash: depositContractBlockHash.asBlockHash,
|
|
|
|
elConnections: mapIt(engineApiUrls, ELConnection.new(it)),
|
2020-12-15 21:59:29 +00:00
|
|
|
eth1Network: eth1Network,
|
2023-01-18 14:01:49 +00:00
|
|
|
blocksPerLogsRequest: targetBlocksPerLogsRequest)
|
2019-09-09 15:59:02 +00:00
|
|
|
|
2020-09-28 15:19:57 +00:00
|
|
|
proc safeCancel(fut: var Future[void]) =
|
|
|
|
if not fut.isNil and not fut.finished:
|
|
|
|
fut.cancel()
|
2020-11-03 01:21:07 +00:00
|
|
|
fut = nil
|
2020-09-28 15:19:57 +00:00
|
|
|
|
2021-11-25 10:53:31 +00:00
|
|
|
func clear(chain: var Eth1Chain) =
|
2020-11-24 21:21:47 +00:00
|
|
|
chain.blocks.clear()
|
|
|
|
chain.blocksByHash.clear()
|
2022-08-10 12:31:10 +00:00
|
|
|
chain.headMerkleizer = copy chain.finalizedDepositsMerkleizer
|
2021-01-29 21:21:44 +00:00
|
|
|
chain.hasConsensusViolation = false
|
2020-11-24 21:21:47 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc doStop(m: ELManager) {.async.} =
|
|
|
|
safeCancel m.chainSyncingLoopFut
|
|
|
|
safeCancel m.exchangeTransitionConfigurationLoopFut
|
2022-06-17 06:32:52 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
if m.elConnections.len > 0:
|
|
|
|
let closeConnectionFutures = mapIt(m.elConnections, close(it))
|
|
|
|
await allFutures(closeConnectionFutures)
|
2022-01-31 17:28:26 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc stop(m: ELManager) {.async.} =
|
|
|
|
if not m.stopFut.isNil:
|
2020-12-15 21:59:29 +00:00
|
|
|
await m.stopFut
|
2023-03-05 01:40:21 +00:00
|
|
|
else:
|
|
|
|
m.stopFut = m.doStop()
|
2020-12-15 21:59:29 +00:00
|
|
|
await m.stopFut
|
2023-03-05 01:40:21 +00:00
|
|
|
m.stopFut = nil
|
2020-12-15 21:59:29 +00:00
|
|
|
|
2020-11-19 17:19:03 +00:00
|
|
|
const
|
2020-11-24 21:21:47 +00:00
|
|
|
votedBlocksSafetyMargin = 50
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2023-03-10 17:40:27 +00:00
|
|
|
func earliestBlockOfInterest(
|
|
|
|
m: ELManager,
|
|
|
|
latestEth1BlockNumber: Eth1BlockNumber): Eth1BlockNumber =
|
|
|
|
let blocksOfInterestRange =
|
|
|
|
SLOTS_PER_ETH1_VOTING_PERIOD +
|
|
|
|
(2 * m.cfg.ETH1_FOLLOW_DISTANCE) +
|
|
|
|
votedBlocksSafetyMargin
|
|
|
|
|
|
|
|
if latestEth1BlockNumber > blocksOfInterestRange:
|
|
|
|
latestEth1BlockNumber - blocksOfInterestRange
|
|
|
|
else:
|
|
|
|
0
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc syncBlockRange(m: ELManager,
|
2023-03-09 17:29:04 +00:00
|
|
|
connection: ELConnection,
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient: RpcClient,
|
|
|
|
depositContract: Sender[DepositContract],
|
2020-11-19 17:19:03 +00:00
|
|
|
fromBlock, toBlock,
|
2020-12-15 21:59:29 +00:00
|
|
|
fullSyncFromBlock: Eth1BlockNumber) {.gcsafe, async.} =
|
2023-03-05 01:40:21 +00:00
|
|
|
doAssert m.eth1Chain.blocks.len > 0
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2020-11-03 01:21:07 +00:00
|
|
|
var currentBlock = fromBlock
|
|
|
|
while currentBlock <= toBlock:
|
2020-11-14 20:51:50 +00:00
|
|
|
var
|
|
|
|
depositLogs: JsonNode = nil
|
|
|
|
maxBlockNumberRequested: Eth1BlockNumber
|
2020-12-01 23:35:07 +00:00
|
|
|
backoff = 100
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-11-03 01:21:07 +00:00
|
|
|
while true:
|
2022-08-12 13:51:33 +00:00
|
|
|
maxBlockNumberRequested =
|
|
|
|
min(toBlock, currentBlock + m.blocksPerLogsRequest - 1)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
debug "Obtaining deposit log events",
|
|
|
|
fromBlock = currentBlock,
|
2020-12-01 23:35:07 +00:00
|
|
|
toBlock = maxBlockNumberRequested,
|
|
|
|
backoff
|
2020-02-07 07:13:38 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
debug.logTime "Deposit logs obtained":
|
2020-12-01 11:14:32 +00:00
|
|
|
# Reduce all request rate until we have a more general solution
|
|
|
|
# for dealing with Infura's rate limits
|
2020-12-01 23:35:07 +00:00
|
|
|
await sleepAsync(milliseconds(backoff))
|
2023-03-09 17:29:04 +00:00
|
|
|
let
|
|
|
|
startTime = Moment.now
|
|
|
|
deadline = sleepAsync 30.seconds
|
|
|
|
jsonLogsFut = depositContract.getJsonLogs(
|
|
|
|
DepositEvent,
|
|
|
|
fromBlock = some blockId(currentBlock),
|
|
|
|
toBlock = some blockId(maxBlockNumberRequested))
|
|
|
|
|
|
|
|
connection.trackEngineApiRequest(
|
|
|
|
jsonLogsFut, "getLogs", startTime, deadline)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
|
|
|
depositLogs = try:
|
2022-08-12 13:51:33 +00:00
|
|
|
# Downloading large amounts of deposits may take several minutes
|
2023-03-09 17:29:04 +00:00
|
|
|
awaitWithTimeout(jsonLogsFut, deadline):
|
2022-08-12 13:51:33 +00:00
|
|
|
raise newException(DataProviderTimeout,
|
2020-11-24 21:21:47 +00:00
|
|
|
"Request time out while obtaining json logs")
|
|
|
|
except CatchableError as err:
|
2020-12-01 23:35:07 +00:00
|
|
|
debug "Request for deposit logs failed", err = err.msg
|
2020-12-09 22:44:59 +00:00
|
|
|
inc failed_web3_requests
|
2020-12-01 23:35:07 +00:00
|
|
|
backoff = (backoff * 3) div 2
|
2022-08-12 13:51:33 +00:00
|
|
|
m.blocksPerLogsRequest = m.blocksPerLogsRequest div 2
|
|
|
|
if m.blocksPerLogsRequest == 0:
|
|
|
|
m.blocksPerLogsRequest = 1
|
|
|
|
raise err
|
|
|
|
continue
|
|
|
|
m.blocksPerLogsRequest = min(
|
|
|
|
(m.blocksPerLogsRequest * 3 + 1) div 2,
|
|
|
|
targetBlocksPerLogsRequest)
|
2020-11-03 01:21:07 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
currentBlock = maxBlockNumberRequested + 1
|
|
|
|
break
|
|
|
|
|
2020-12-01 11:14:32 +00:00
|
|
|
let blocksWithDeposits = depositEventsToBlocks(depositLogs)
|
2020-11-24 21:21:47 +00:00
|
|
|
|
|
|
|
for i in 0 ..< blocksWithDeposits.len:
|
|
|
|
let blk = blocksWithDeposits[i]
|
2020-11-19 17:19:03 +00:00
|
|
|
if blk.number > fullSyncFromBlock:
|
2023-03-14 11:46:22 +00:00
|
|
|
await fetchTimestamp(connection, rpcClient, blk)
|
2023-03-05 01:40:21 +00:00
|
|
|
let lastBlock = m.eth1Chain.blocks.peekLast
|
2020-11-19 17:19:03 +00:00
|
|
|
for n in max(lastBlock.number + 1, fullSyncFromBlock) ..< blk.number:
|
2020-12-01 23:35:07 +00:00
|
|
|
debug "Obtaining block without deposits", blockNum = n
|
2023-03-09 17:29:04 +00:00
|
|
|
let blockWithoutDeposits = connection.trackedRequestWithTimeout(
|
|
|
|
"getBlockByNumber",
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient.getBlockByNumber(n),
|
|
|
|
web3RequestsTimeout)
|
2020-12-01 21:20:28 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
m.eth1Chain.addBlock(
|
2020-11-19 17:19:03 +00:00
|
|
|
lastBlock.makeSuccessorWithoutDeposits(blockWithoutDeposits))
|
2020-12-09 22:44:59 +00:00
|
|
|
eth1_synced_head.set blockWithoutDeposits.number.toGaugeValue
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
m.eth1Chain.addBlock blk
|
2020-12-09 22:44:59 +00:00
|
|
|
eth1_synced_head.set blk.number.toGaugeValue
|
2020-11-24 21:21:47 +00:00
|
|
|
|
|
|
|
if blocksWithDeposits.len > 0:
|
|
|
|
let lastIdx = blocksWithDeposits.len - 1
|
|
|
|
template lastBlock: auto = blocksWithDeposits[lastIdx]
|
2020-11-03 01:21:07 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
let status = when hasDepositRootChecks:
|
2023-03-09 17:29:04 +00:00
|
|
|
rpcClient.fetchDepositContractData(depositContract, lastBlock)
|
2020-11-24 21:21:47 +00:00
|
|
|
else:
|
|
|
|
DepositRootUnavailable
|
2020-11-03 01:21:07 +00:00
|
|
|
|
|
|
|
when hasDepositRootChecks:
|
2020-11-24 21:21:47 +00:00
|
|
|
debug "Deposit contract state verified",
|
|
|
|
status = $status,
|
2022-08-10 12:31:10 +00:00
|
|
|
ourCount = lastBlock.depositCount,
|
|
|
|
ourRoot = lastBlock.depositRoot
|
2020-11-03 01:21:07 +00:00
|
|
|
|
2020-11-24 21:21:47 +00:00
|
|
|
case status
|
|
|
|
of DepositRootIncorrect, DepositCountIncorrect:
|
|
|
|
raise newException(CorruptDataProvider,
|
|
|
|
"The deposit log events disagree with the deposit contract state")
|
|
|
|
else:
|
|
|
|
discard
|
2020-11-03 01:21:07 +00:00
|
|
|
|
2022-01-03 21:18:49 +00:00
|
|
|
info "Eth1 sync progress",
|
2020-11-24 21:21:47 +00:00
|
|
|
blockNumber = lastBlock.number,
|
2022-08-10 12:31:10 +00:00
|
|
|
depositsProcessed = lastBlock.depositCount
|
2020-11-17 19:50:07 +00:00
|
|
|
|
2021-11-25 16:51:51 +00:00
|
|
|
func init(T: type FullBlockId, blk: Eth1BlockHeader|BlockObject): T =
|
|
|
|
FullBlockId(number: Eth1BlockNumber blk.number, hash: blk.hash)
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func isNewLastBlock(m: ELManager, blk: Eth1BlockHeader|BlockObject): bool =
|
2022-09-18 05:45:51 +00:00
|
|
|
m.latestEth1Block.isNone or blk.number.uint64 > m.latestEth1BlockNumber
|
2022-08-12 19:44:55 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
func hasProperlyConfiguredConnection*(m: ELManager): bool =
|
|
|
|
for connection in m.elConnections:
|
|
|
|
if connection.etcStatus == EtcStatus.match:
|
|
|
|
return true
|
2022-01-31 17:28:26 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
return false
|
2022-02-15 19:10:04 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc startExchangeTransitionConfigurationLoop(m: ELManager) {.async.} =
|
|
|
|
debug "Starting exchange transition configuration loop"
|
2020-12-15 21:59:29 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
while true:
|
|
|
|
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.2/src/engine/specification.md#engine_exchangetransitionconfigurationv1
|
|
|
|
debug "Exchange transition configuration tick"
|
|
|
|
traceAsyncErrors m.exchangeTransitionConfiguration()
|
2023-03-09 17:29:04 +00:00
|
|
|
await sleepAsync(60.seconds)
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
proc syncEth1Chain(m: ELManager, connection: ELConnection) {.async.} =
|
2023-03-09 23:41:28 +00:00
|
|
|
let rpcClient = awaitOrRaiseOnTimeout(connection.connectedRpcClient(),
|
|
|
|
1.seconds)
|
2023-03-05 01:40:21 +00:00
|
|
|
let
|
2023-03-14 16:05:21 +00:00
|
|
|
# BEWARE
|
|
|
|
# `connectedRpcClient` guarantees that connection.web3 will not be
|
|
|
|
# `none` here, but it's not safe to initialize this later (e.g closer
|
|
|
|
# to where it's used) because `connection.web3` may be set to `none`
|
|
|
|
# at any time after a failed request. Luckily, the `contractSender`
|
|
|
|
# object is very cheap to create.
|
|
|
|
depositContract = connection.web3.get.contractSender(
|
|
|
|
DepositContract, m.depositContractAddress)
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
shouldProcessDeposits = not (
|
|
|
|
m.depositContractAddress.isZeroMemory or
|
|
|
|
m.eth1Chain.finalizedBlockHash.data.isZeroMemory)
|
|
|
|
|
|
|
|
trace "Starting syncEth1Chain", shouldProcessDeposits
|
2022-01-31 17:28:26 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
logScope:
|
|
|
|
url = connection.engineUrl.url
|
2022-01-31 17:28:26 +00:00
|
|
|
|
|
|
|
# We might need to reset the chain if the new provider disagrees
|
|
|
|
# with the previous one regarding the history of the chain or if
|
|
|
|
# we have detected a conensus violation - our view disagreeing with
|
|
|
|
# the majority of the validators in the network.
|
|
|
|
#
|
|
|
|
# Consensus violations happen in practice because the web3 providers
|
|
|
|
# sometimes return incomplete or incorrect deposit log events even
|
|
|
|
# when they don't indicate any errors in the response. When this
|
|
|
|
# happens, we are usually able to download the data successfully
|
|
|
|
# on the second attempt.
|
2023-03-05 01:40:21 +00:00
|
|
|
#
|
|
|
|
# TODO
|
|
|
|
# Perhaps the above problem was manifesting only with the obsolete
|
|
|
|
# JSON-RPC data providers, which can no longer be used with Nimbus.
|
|
|
|
if m.eth1Chain.blocks.len > 0:
|
|
|
|
let needsReset = m.eth1Chain.hasConsensusViolation or (block:
|
2022-01-31 17:28:26 +00:00
|
|
|
let
|
2023-03-05 01:40:21 +00:00
|
|
|
lastKnownBlock = m.eth1Chain.blocks.peekLast
|
2023-03-09 17:29:04 +00:00
|
|
|
matchingBlockAtNewProvider = connection.trackedRequestWithTimeout(
|
|
|
|
"getBlockByNumber",
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient.getBlockByNumber(lastKnownBlock.number),
|
|
|
|
web3RequestsTimeout)
|
2022-01-31 17:28:26 +00:00
|
|
|
|
2022-08-10 12:31:10 +00:00
|
|
|
lastKnownBlock.hash.asBlockHash != matchingBlockAtNewProvider.hash)
|
2022-01-31 17:28:26 +00:00
|
|
|
|
|
|
|
if needsReset:
|
2023-03-05 01:40:21 +00:00
|
|
|
trace "Resetting the Eth1 chain",
|
|
|
|
hasConsensusViolation = m.eth1Chain.hasConsensusViolation
|
|
|
|
m.eth1Chain.clear()
|
2022-12-07 10:24:51 +00:00
|
|
|
|
2022-06-15 02:38:27 +00:00
|
|
|
var eth1SyncedTo: Eth1BlockNumber
|
2022-08-12 13:48:33 +00:00
|
|
|
if shouldProcessDeposits:
|
2023-03-05 01:40:21 +00:00
|
|
|
if m.eth1Chain.blocks.len == 0:
|
|
|
|
let finalizedBlockHash = m.eth1Chain.finalizedBlockHash.asBlockHash
|
|
|
|
let startBlock =
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.trackedRequestWithTimeout(
|
|
|
|
"getBlockByHash",
|
|
|
|
rpcClient.getBlockByHash(finalizedBlockHash),
|
|
|
|
web3RequestsTimeout)
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
m.eth1Chain.addBlock Eth1Block(
|
|
|
|
hash: m.eth1Chain.finalizedBlockHash,
|
2022-08-12 13:48:33 +00:00
|
|
|
number: Eth1BlockNumber startBlock.number,
|
|
|
|
timestamp: Eth1BlockTimestamp startBlock.timestamp)
|
2020-11-19 17:19:03 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
eth1SyncedTo = m.eth1Chain.blocks[^1].number
|
2022-06-15 02:38:27 +00:00
|
|
|
|
|
|
|
eth1_synced_head.set eth1SyncedTo.toGaugeValue
|
|
|
|
eth1_finalized_head.set eth1SyncedTo.toGaugeValue
|
|
|
|
eth1_finalized_deposits.set(
|
2023-03-05 01:40:21 +00:00
|
|
|
m.eth1Chain.finalizedDepositsMerkleizer.getChunkCount.toGaugeValue)
|
2020-12-09 22:44:59 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
debug "Starting Eth1 syncing", `from` = shortLog(m.eth1Chain.blocks[^1])
|
2020-11-30 23:59:35 +00:00
|
|
|
|
2022-08-12 19:44:55 +00:00
|
|
|
var didPollOnce = false
|
2020-11-03 01:21:07 +00:00
|
|
|
while true:
|
2023-03-05 01:40:21 +00:00
|
|
|
debug "syncEth1Chain tick"
|
|
|
|
|
2020-11-03 01:21:07 +00:00
|
|
|
if bnStatus == BeaconNodeStatus.Stopping:
|
2020-12-15 21:59:29 +00:00
|
|
|
await m.stop()
|
2020-11-03 01:21:07 +00:00
|
|
|
return
|
2020-10-14 14:04:08 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
if m.eth1Chain.hasConsensusViolation:
|
2021-01-29 21:21:44 +00:00
|
|
|
raise newException(CorruptDataProvider, "Eth1 chain contradicts Eth2 consensus")
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
let latestBlock = try:
|
2023-03-09 17:29:04 +00:00
|
|
|
connection.trackedRequestWithTimeout(
|
|
|
|
"getBlockByNumber",
|
2023-03-05 01:40:21 +00:00
|
|
|
rpcClient.eth_getBlockByNumber(blockId("latest"), false),
|
|
|
|
web3RequestsTimeout)
|
|
|
|
except CatchableError as err:
|
2023-03-10 09:12:29 +00:00
|
|
|
warn "Failed to obtain the latest block from the EL", err = err.msg
|
2023-03-05 01:40:21 +00:00
|
|
|
raise err
|
2021-11-25 16:51:51 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
m.syncTargetBlock = some(
|
|
|
|
if Eth1BlockNumber(latestBlock.number) > m.cfg.ETH1_FOLLOW_DISTANCE:
|
|
|
|
Eth1BlockNumber(latestBlock.number) - m.cfg.ETH1_FOLLOW_DISTANCE
|
2022-08-12 19:44:55 +00:00
|
|
|
else:
|
2023-03-05 01:40:21 +00:00
|
|
|
Eth1BlockNumber(0))
|
|
|
|
if m.syncTargetBlock.get <= eth1SyncedTo:
|
|
|
|
# The chain reorged to a lower height.
|
|
|
|
# It's relatively safe to ignore that.
|
|
|
|
await sleepAsync(m.cfg.SECONDS_PER_ETH1_BLOCK.int.seconds)
|
|
|
|
continue
|
2021-11-25 16:51:51 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
eth1_latest_head.set latestBlock.number.toGaugeValue
|
2021-02-04 15:01:47 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
if shouldProcessDeposits and
|
|
|
|
latestBlock.number.uint64 > m.cfg.ETH1_FOLLOW_DISTANCE:
|
2023-03-09 17:29:04 +00:00
|
|
|
await m.syncBlockRange(connection,
|
|
|
|
rpcClient,
|
2023-03-05 01:40:21 +00:00
|
|
|
depositContract,
|
|
|
|
eth1SyncedTo + 1,
|
|
|
|
m.syncTargetBlock.get,
|
|
|
|
m.earliestBlockOfInterest(Eth1BlockNumber latestBlock.number))
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
eth1SyncedTo = m.syncTargetBlock.get
|
|
|
|
eth1_synced_head.set eth1SyncedTo.toGaugeValue
|
2022-06-15 02:38:27 +00:00
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
proc startChainSyncingLoop(m: ELManager) {.async.} =
|
|
|
|
info "Starting execution layer deposits syncing",
|
|
|
|
contract = $m.depositContractAddress
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2023-03-10 10:55:55 +00:00
|
|
|
var syncedConnectionFut = m.selectConnectionForChainSyncing()
|
|
|
|
info "Connection attempt started"
|
|
|
|
|
2023-03-05 01:40:21 +00:00
|
|
|
while true:
|
2023-03-10 10:55:55 +00:00
|
|
|
try:
|
|
|
|
await syncedConnectionFut or sleepAsync(60.seconds)
|
|
|
|
if not syncedConnectionFut.finished:
|
2023-03-10 09:12:29 +00:00
|
|
|
warn "No suitable EL connection for deposit syncing"
|
2023-03-05 01:40:21 +00:00
|
|
|
await sleepAsync(chronos.seconds(30))
|
2022-06-15 02:38:27 +00:00
|
|
|
continue
|
|
|
|
|
2023-03-10 10:55:55 +00:00
|
|
|
await syncEth1Chain(m, syncedConnectionFut.read)
|
2023-03-05 01:40:21 +00:00
|
|
|
except CatchableError as err:
|
2023-03-10 10:55:55 +00:00
|
|
|
await sleepAsync(10.seconds)
|
|
|
|
|
2023-03-09 23:41:28 +00:00
|
|
|
# A more detailed error is already logged by trackEngineApiRequest
|
|
|
|
debug "Restarting the deposit syncing loop"
|
2023-03-10 10:55:55 +00:00
|
|
|
|
|
|
|
# To be extra safe, we will make a fresh connection attempt
|
|
|
|
await syncedConnectionFut.cancelAndWait()
|
|
|
|
syncedConnectionFut = m.selectConnectionForChainSyncing()
|
2023-03-05 01:40:21 +00:00
|
|
|
|
|
|
|
proc start*(m: ELManager) {.gcsafe.} =
|
|
|
|
if m.elConnections.len == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
## Calling `ELManager.start()` on an already started ELManager is a noop
|
|
|
|
if m.chainSyncingLoopFut.isNil:
|
|
|
|
m.chainSyncingLoopFut =
|
|
|
|
m.startChainSyncingLoop()
|
|
|
|
|
|
|
|
if m.hasJwtSecret and m.exchangeTransitionConfigurationLoopFut.isNil:
|
|
|
|
m.exchangeTransitionConfigurationLoopFut =
|
|
|
|
m.startExchangeTransitionConfigurationLoop()
|
2019-11-22 13:16:07 +00:00
|
|
|
|
2022-07-25 19:23:02 +00:00
|
|
|
func `$`(x: Quantity): string =
|
|
|
|
$(x.uint64)
|
|
|
|
|
|
|
|
func `$`(x: BlockObject): string =
|
|
|
|
$(x.number) & " [" & $(x.hash) & "]"
|
|
|
|
|
2020-12-04 16:28:42 +00:00
|
|
|
proc testWeb3Provider*(web3Url: Uri,
|
2022-03-31 14:43:05 +00:00
|
|
|
depositContractAddress: Eth1Address,
|
|
|
|
jwtSecret: Option[seq[byte]]) {.async.} =
|
2022-07-25 19:23:02 +00:00
|
|
|
stdout.write "Establishing web3 connection..."
|
|
|
|
var web3: Web3
|
|
|
|
try:
|
|
|
|
web3 = awaitOrRaiseOnTimeout(
|
2023-03-09 23:41:28 +00:00
|
|
|
newWeb3($web3Url, getJsonRpcRequestHeaders(jwtSecret)),
|
|
|
|
5.seconds)
|
2022-07-25 19:23:02 +00:00
|
|
|
stdout.write "\rEstablishing web3 connection: Connected\n"
|
|
|
|
except CatchableError as err:
|
|
|
|
stdout.write "\rEstablishing web3 connection: Failure(" & err.msg & ")\n"
|
|
|
|
quit 1
|
|
|
|
|
|
|
|
template request(actionDesc: static string,
|
|
|
|
action: untyped): untyped =
|
|
|
|
stdout.write actionDesc & "..."
|
|
|
|
stdout.flushFile()
|
|
|
|
var res: typeof(read action)
|
|
|
|
try:
|
2023-03-05 01:40:21 +00:00
|
|
|
res = awaitOrRaiseOnTimeout(action, web3RequestsTimeout)
|
2022-07-25 19:23:02 +00:00
|
|
|
stdout.write "\r" & actionDesc & ": " & $res
|
2020-12-04 16:28:42 +00:00
|
|
|
except CatchableError as err:
|
2022-07-25 19:23:02 +00:00
|
|
|
stdout.write "\r" & actionDesc & ": Error(" & err.msg & ")"
|
|
|
|
stdout.write "\n"
|
|
|
|
res
|
2020-12-04 16:28:42 +00:00
|
|
|
|
|
|
|
let
|
2022-07-25 19:23:02 +00:00
|
|
|
chainId = request "Chain ID":
|
|
|
|
web3.provider.eth_chainId()
|
2022-06-27 15:02:12 +00:00
|
|
|
|
2022-07-25 19:23:02 +00:00
|
|
|
latestBlock = request "Latest block":
|
|
|
|
web3.provider.eth_getBlockByNumber(blockId("latest"), false)
|
|
|
|
|
|
|
|
syncStatus = request "Sync status":
|
|
|
|
web3.provider.eth_syncing()
|
|
|
|
|
|
|
|
ns = web3.contractSender(DepositContract, depositContractAddress)
|
|
|
|
|
|
|
|
depositRoot = request "Deposit root":
|
|
|
|
ns.get_deposit_root.call(blockNumber = latestBlock.number.uint64)
|