mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-17 00:47:03 +00:00
post-merge Bellatrix block proposals (#3570)
* post-merge Bellatrix block proposals * tolerate running without an Eth1Monitor better * remove obsolete comment * use correct empty receipts root * handle invalid CLI parameters in parseCmdArg overloads
This commit is contained in:
parent
bacc1ff8ed
commit
ab1fac7236
@ -513,6 +513,13 @@ type
|
|||||||
defaultValue: 128
|
defaultValue: 128
|
||||||
name: "safe-slots-to-import-optimistically" }: uint64
|
name: "safe-slots-to-import-optimistically" }: uint64
|
||||||
|
|
||||||
|
# Same option as appears in Lighthouse and Prysm
|
||||||
|
# https://lighthouse-book.sigmaprime.io/suggested-fee-recipient.html
|
||||||
|
# https://github.com/prysmaticlabs/prysm/pull/10312
|
||||||
|
suggestedFeeRecipient* {.
|
||||||
|
desc: "Suggested fee recipient"
|
||||||
|
name: "suggested-fee-recipient" .}: Option[Address]
|
||||||
|
|
||||||
of BNStartUpCmd.createTestnet:
|
of BNStartUpCmd.createTestnet:
|
||||||
testnetDepositsFile* {.
|
testnetDepositsFile* {.
|
||||||
desc: "A LaunchPad deposits file for the genesis state validators"
|
desc: "A LaunchPad deposits file for the genesis state validators"
|
||||||
@ -1099,6 +1106,13 @@ proc readValue*(r: var TomlReader, a: var WalletName)
|
|||||||
except CatchableError:
|
except CatchableError:
|
||||||
r.raiseUnexpectedValue("string expected")
|
r.raiseUnexpectedValue("string expected")
|
||||||
|
|
||||||
|
proc readValue*(r: var TomlReader, a: var Address)
|
||||||
|
{.raises: [Defect, IOError, SerializationError].} =
|
||||||
|
try:
|
||||||
|
a = parseCmdArg(Address, r.readValue(string))
|
||||||
|
except CatchableError:
|
||||||
|
r.raiseUnexpectedValue("string expected")
|
||||||
|
|
||||||
proc loadEth2Network*(config: BeaconNodeConf): Eth2NetworkMetadata {.raises: [Defect, IOError].} =
|
proc loadEth2Network*(config: BeaconNodeConf): Eth2NetworkMetadata {.raises: [Defect, IOError].} =
|
||||||
network_name.set(2, labelValues = [config.eth2Network.get(otherwise = "mainnet")])
|
network_name.set(2, labelValues = [config.eth2Network.get(otherwise = "mainnet")])
|
||||||
when not defined(gnosisChainBinary):
|
when not defined(gnosisChainBinary):
|
||||||
|
@ -1022,7 +1022,7 @@ proc doStop(m: Eth1Monitor) {.async.} =
|
|||||||
m.dataProvider = nil
|
m.dataProvider = nil
|
||||||
|
|
||||||
proc ensureDataProvider*(m: Eth1Monitor) {.async.} =
|
proc ensureDataProvider*(m: Eth1Monitor) {.async.} =
|
||||||
if not m.dataProvider.isNil:
|
if m.isNil or not m.dataProvider.isNil:
|
||||||
return
|
return
|
||||||
|
|
||||||
let web3Url = m.web3Urls[m.startIdx mod m.web3Urls.len]
|
let web3Url = m.web3Urls[m.startIdx mod m.web3Urls.len]
|
||||||
|
@ -376,6 +376,10 @@ proc newExecutionPayload*(
|
|||||||
UInt256.fromBytesLE(executionPayload.base_fee_per_gas.data),
|
UInt256.fromBytesLE(executionPayload.base_fee_per_gas.data),
|
||||||
numTransactions = executionPayload.transactions.len
|
numTransactions = executionPayload.transactions.len
|
||||||
|
|
||||||
|
if eth1Monitor.isNil:
|
||||||
|
info "newPayload: attempting to process execution payload without an Eth1Monitor. Ensure --web3-url setting is correct."
|
||||||
|
return PayloadExecutionStatus.syncing
|
||||||
|
|
||||||
try:
|
try:
|
||||||
let
|
let
|
||||||
payloadResponse =
|
payloadResponse =
|
||||||
@ -383,7 +387,7 @@ proc newExecutionPayload*(
|
|||||||
eth1Monitor.newPayload(
|
eth1Monitor.newPayload(
|
||||||
executionPayload.asEngineExecutionPayload),
|
executionPayload.asEngineExecutionPayload),
|
||||||
web3Timeout):
|
web3Timeout):
|
||||||
debug "newPayload: newPayload timed out"
|
info "newPayload: newPayload timed out"
|
||||||
PayloadStatusV1(status: PayloadExecutionStatus.syncing)
|
PayloadStatusV1(status: PayloadExecutionStatus.syncing)
|
||||||
payloadStatus = payloadResponse.status
|
payloadStatus = payloadResponse.status
|
||||||
|
|
||||||
@ -432,7 +436,7 @@ proc runQueueProcessingLoop*(self: ref BlockProcessor) {.async.} =
|
|||||||
self.consensusManager.eth1Monitor,
|
self.consensusManager.eth1Monitor,
|
||||||
blck.blck.bellatrixData.message.body.execution_payload)
|
blck.blck.bellatrixData.message.body.execution_payload)
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
debug "runQueueProcessingLoop: newExecutionPayload failed",
|
debug "runQueueProcessingLoop: newPayload failed",
|
||||||
err = err.msg
|
err = err.msg
|
||||||
PayloadExecutionStatus.syncing
|
PayloadExecutionStatus.syncing
|
||||||
else:
|
else:
|
||||||
@ -502,14 +506,6 @@ proc runQueueProcessingLoop*(self: ref BlockProcessor) {.async.} =
|
|||||||
|
|
||||||
if executionPayloadStatus == PayloadExecutionStatus.valid and
|
if executionPayloadStatus == PayloadExecutionStatus.valid and
|
||||||
hasExecutionPayload:
|
hasExecutionPayload:
|
||||||
let
|
await self.runForkchoiceUpdated(
|
||||||
headBlockRoot = self.consensusManager.dag.head.executionBlockRoot
|
self.consensusManager.dag.head.executionBlockRoot,
|
||||||
|
self.consensusManager.dag.finalizedHead.blck.executionBlockRoot)
|
||||||
finalizedBlockRoot =
|
|
||||||
if not isZero(
|
|
||||||
self.consensusManager.dag.finalizedHead.blck.executionBlockRoot):
|
|
||||||
self.consensusManager.dag.finalizedHead.blck.executionBlockRoot
|
|
||||||
else:
|
|
||||||
default(Eth2Digest)
|
|
||||||
|
|
||||||
await self.runForkchoiceUpdated(headBlockRoot, finalizedBlockRoot)
|
|
||||||
|
@ -169,7 +169,9 @@ proc loadEth2NetworkMetadata*(path: string, eth1Network = none(Eth1Network)): Et
|
|||||||
let data = (genesisData[0 ..< 40].toHex())
|
let data = (genesisData[0 ..< 40].toHex())
|
||||||
data in [
|
data in [
|
||||||
# Prater
|
# Prater
|
||||||
"60F4596000000000043DB0D9A83813551EE2F33450D23797757D430911A9320530AD8A0EABC43EFB"
|
"60F4596000000000043DB0D9A83813551EE2F33450D23797757D430911A9320530AD8A0EABC43EFB",
|
||||||
|
# Kiln
|
||||||
|
"0C572B620000000099B09FCD43E5905236C370F184056BEC6E6638CFC31A323B304FC4AA789CB4AD"
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
false
|
false
|
||||||
|
@ -506,3 +506,30 @@ func compute_timestamp_at_slot*(state: ForkyBeaconState, slot: Slot): uint64 =
|
|||||||
# Note: This function is unsafe with respect to overflows and underflows.
|
# Note: This function is unsafe with respect to overflows and underflows.
|
||||||
let slots_since_genesis = slot - GENESIS_SLOT
|
let slots_since_genesis = slot - GENESIS_SLOT
|
||||||
state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT
|
state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT
|
||||||
|
|
||||||
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py#L1-L31
|
||||||
|
func build_empty_execution_payload*(state: bellatrix.BeaconState): ExecutionPayload =
|
||||||
|
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
|
||||||
|
## without any transactions.
|
||||||
|
let
|
||||||
|
latest = state.latest_execution_payload_header
|
||||||
|
timestamp = compute_timestamp_at_slot(state, state.slot)
|
||||||
|
randao_mix = get_randao_mix(state, get_current_epoch(state))
|
||||||
|
|
||||||
|
var payload = ExecutionPayload(
|
||||||
|
parent_hash: latest.block_hash,
|
||||||
|
state_root: latest.state_root, # no changes to the state
|
||||||
|
receipts_root: static(Eth2Digest.fromHex(
|
||||||
|
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")),
|
||||||
|
block_number: latest.block_number + 1,
|
||||||
|
prev_randao: randao_mix,
|
||||||
|
gas_limit: latest.gas_limit, # retain same limit
|
||||||
|
gas_used: 0, # empty block, 0 gas
|
||||||
|
timestamp: timestamp,
|
||||||
|
base_fee_per_gas: latest.base_fee_per_gas) # retain same base_fee
|
||||||
|
|
||||||
|
payload.block_hash = withEth2Hash:
|
||||||
|
h.update payload.hash_tree_root().data
|
||||||
|
h.update cast[array[13, uint8]]("FAKE RLP HASH")
|
||||||
|
|
||||||
|
payload
|
||||||
|
@ -41,6 +41,7 @@ import
|
|||||||
|
|
||||||
from eth/async_utils import awaitWithTimeout
|
from eth/async_utils import awaitWithTimeout
|
||||||
from web3/engine_api import ForkchoiceUpdatedResponse
|
from web3/engine_api import ForkchoiceUpdatedResponse
|
||||||
|
from web3/engine_api_types import PayloadExecutionStatus
|
||||||
|
|
||||||
# Metrics for tracking attestation and beacon block loss
|
# Metrics for tracking attestation and beacon block loss
|
||||||
const delayBuckets = [-Inf, -4.0, -2.0, -1.0, -0.5, -0.1, -0.05,
|
const delayBuckets = [-Inf, -4.0, -2.0, -1.0, -0.5, -0.1, -0.05,
|
||||||
@ -434,7 +435,7 @@ proc forkchoice_updated(state: bellatrix.BeaconState,
|
|||||||
head_block_hash, finalized_block_hash, timestamp, random.data,
|
head_block_hash, finalized_block_hash, timestamp, random.data,
|
||||||
fee_recipient),
|
fee_recipient),
|
||||||
web3Timeout):
|
web3Timeout):
|
||||||
debug "forkchoice_updated: forkchoiceUpdated timed out"
|
info "forkchoice_updated: forkchoiceUpdated timed out"
|
||||||
default(ForkchoiceUpdatedResponse)
|
default(ForkchoiceUpdatedResponse)
|
||||||
payloadId = forkchoiceResponse.payloadId
|
payloadId = forkchoiceResponse.payloadId
|
||||||
|
|
||||||
@ -443,6 +444,65 @@ proc forkchoice_updated(state: bellatrix.BeaconState,
|
|||||||
else:
|
else:
|
||||||
none(bellatrix.PayloadID)
|
none(bellatrix.PayloadID)
|
||||||
|
|
||||||
|
proc get_execution_payload(
|
||||||
|
payload_id: Option[bellatrix.PayloadId], execution_engine: Eth1Monitor):
|
||||||
|
Future[bellatrix.ExecutionPayload] {.async.} =
|
||||||
|
return if payload_id.isNone():
|
||||||
|
# Pre-merge, empty payload
|
||||||
|
default(bellatrix.ExecutionPayload)
|
||||||
|
else:
|
||||||
|
asConsensusExecutionPayload(
|
||||||
|
await execution_engine.getPayload(payload_id.get))
|
||||||
|
|
||||||
|
proc getExecutionPayload(node: BeaconNode, proposalState: auto):
|
||||||
|
Future[ExecutionPayload] {.async.} =
|
||||||
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/bellatrix/validator.md#executionpayload
|
||||||
|
|
||||||
|
# Only current hardfork with execution payloads is Bellatrix
|
||||||
|
static: doAssert high(BeaconStateFork) == BeaconStateFork.Bellatrix
|
||||||
|
template empty_execution_payload(): auto =
|
||||||
|
build_empty_execution_payload(proposalState.bellatrixData.data)
|
||||||
|
|
||||||
|
if node.eth1Monitor.isNil:
|
||||||
|
warn "getExecutionPayload: eth1Monitor not initialized; using empty execution payload"
|
||||||
|
return empty_execution_payload
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Minimize window for Eth1 monitor to shut down connection
|
||||||
|
await node.consensusManager.eth1Monitor.ensureDataProvider()
|
||||||
|
|
||||||
|
let
|
||||||
|
feeRecipient =
|
||||||
|
if node.config.suggestedFeeRecipient.isSome:
|
||||||
|
node.config.suggestedFeeRecipient.get
|
||||||
|
else:
|
||||||
|
default(Eth1Address)
|
||||||
|
latestHead =
|
||||||
|
if not node.dag.head.executionBlockRoot.isZero:
|
||||||
|
node.dag.head.executionBlockRoot
|
||||||
|
else:
|
||||||
|
default(Eth2Digest)
|
||||||
|
latestFinalized = node.dag.finalizedHead.blck.executionBlockRoot
|
||||||
|
payload_id = (await forkchoice_updated(
|
||||||
|
proposalState.bellatrixData.data, latestHead, latestFinalized,
|
||||||
|
feeRecipient, node.consensusManager.eth1Monitor))
|
||||||
|
payload = await get_execution_payload(
|
||||||
|
payload_id, node.consensusManager.eth1Monitor)
|
||||||
|
executionPayloadStatus =
|
||||||
|
await node.consensusManager.eth1Monitor.newExecutionPayload(
|
||||||
|
payload)
|
||||||
|
|
||||||
|
if executionPayloadStatus != PayloadExecutionStatus.valid:
|
||||||
|
info "getExecutionPayload: newExecutionPayload not valid; using empty execution payload",
|
||||||
|
executionPayloadStatus
|
||||||
|
return empty_execution_payload
|
||||||
|
|
||||||
|
return payload
|
||||||
|
except CatchableError as err:
|
||||||
|
error "Error creating non-empty execution payload; using empty execution payload",
|
||||||
|
msg = err.msg
|
||||||
|
return empty_execution_payload
|
||||||
|
|
||||||
proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
||||||
randao_reveal: ValidatorSig,
|
randao_reveal: ValidatorSig,
|
||||||
validator_index: ValidatorIndex,
|
validator_index: ValidatorIndex,
|
||||||
@ -472,6 +532,9 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
|||||||
warn "Eth1 deposits not available. Skipping block proposal", slot
|
warn "Eth1 deposits not available. Skipping block proposal", slot
|
||||||
return ForkedBlockResult.err("Eth1 deposits not available")
|
return ForkedBlockResult.err("Eth1 deposits not available")
|
||||||
|
|
||||||
|
# Only current hardfork with execution payloads is Bellatrix
|
||||||
|
static: doAssert high(BeaconStateFork) == BeaconStateFork.Bellatrix
|
||||||
|
|
||||||
let exits = withState(state):
|
let exits = withState(state):
|
||||||
node.exitPool[].getBeaconBlockExits(state.data)
|
node.exitPool[].getBeaconBlockExits(state.data)
|
||||||
let res = makeBeaconBlock(
|
let res = makeBeaconBlock(
|
||||||
@ -488,7 +551,13 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
|||||||
SyncAggregate.init()
|
SyncAggregate.init()
|
||||||
else:
|
else:
|
||||||
node.syncCommitteeMsgPool[].produceSyncAggregate(head.root),
|
node.syncCommitteeMsgPool[].produceSyncAggregate(head.root),
|
||||||
default(bellatrix.ExecutionPayload),
|
if slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
|
||||||
|
# TODO when Eth1Monitor TTD following comes in, actually detect
|
||||||
|
# transition block directly
|
||||||
|
not is_merge_transition_complete(proposalState.bellatrixData.data):
|
||||||
|
default(bellatrix.ExecutionPayload)
|
||||||
|
else:
|
||||||
|
(await getExecutionPayload(node, proposalState)),
|
||||||
noRollback, # Temporary state - no need for rollback
|
noRollback, # Temporary state - no need for rollback
|
||||||
cache)
|
cache)
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
|
@ -43,33 +43,6 @@ func sign_block(state: ForkyBeaconState, blck: var ForkySignedBeaconBlock) =
|
|||||||
blck.root,
|
blck.root,
|
||||||
privkey).toValidatorSig()
|
privkey).toValidatorSig()
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py#L1-L31
|
|
||||||
func build_empty_execution_payload(state: bellatrix.BeaconState): ExecutionPayload =
|
|
||||||
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
|
|
||||||
## without any transactions.
|
|
||||||
let
|
|
||||||
latest = state.latest_execution_payload_header
|
|
||||||
timestamp = compute_timestamp_at_slot(state, state.slot)
|
|
||||||
randao_mix = get_randao_mix(state, get_current_epoch(state))
|
|
||||||
|
|
||||||
var payload = ExecutionPayload(
|
|
||||||
parent_hash: latest.block_hash,
|
|
||||||
state_root: latest.state_root, # no changes to the state
|
|
||||||
receipts_root: Eth2Digest(data: cast[array[32, uint8]](
|
|
||||||
"no receipts here\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")),
|
|
||||||
block_number: latest.block_number + 1,
|
|
||||||
prev_randao: randao_mix,
|
|
||||||
gas_limit: latest.gas_limit, # retain same limit
|
|
||||||
gas_used: 0, # empty block, 0 gas
|
|
||||||
timestamp: timestamp,
|
|
||||||
base_fee_per_gas: latest.base_fee_per_gas) # retain same base_fee
|
|
||||||
|
|
||||||
payload.block_hash = withEth2Hash:
|
|
||||||
h.update payload.hash_tree_root().data
|
|
||||||
h.update cast[array[13, uint8]]("FAKE RLP HASH")
|
|
||||||
|
|
||||||
payload
|
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/helpers/block.py#L75-L104
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/tests/core/pyspec/eth2spec/test/helpers/block.py#L75-L104
|
||||||
proc mockBlock*(
|
proc mockBlock*(
|
||||||
state: ForkedHashedBeaconState,
|
state: ForkedHashedBeaconState,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user