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:
tersec 2022-04-14 20:15:34 +00:00 committed by GitHub
parent bacc1ff8ed
commit ab1fac7236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 44 deletions

View File

@ -513,6 +513,13 @@ type
defaultValue: 128
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:
testnetDepositsFile* {.
desc: "A LaunchPad deposits file for the genesis state validators"
@ -1099,6 +1106,13 @@ proc readValue*(r: var TomlReader, a: var WalletName)
except CatchableError:
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].} =
network_name.set(2, labelValues = [config.eth2Network.get(otherwise = "mainnet")])
when not defined(gnosisChainBinary):

View File

@ -1022,7 +1022,7 @@ proc doStop(m: Eth1Monitor) {.async.} =
m.dataProvider = nil
proc ensureDataProvider*(m: Eth1Monitor) {.async.} =
if not m.dataProvider.isNil:
if m.isNil or not m.dataProvider.isNil:
return
let web3Url = m.web3Urls[m.startIdx mod m.web3Urls.len]

View File

@ -376,6 +376,10 @@ proc newExecutionPayload*(
UInt256.fromBytesLE(executionPayload.base_fee_per_gas.data),
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:
let
payloadResponse =
@ -383,7 +387,7 @@ proc newExecutionPayload*(
eth1Monitor.newPayload(
executionPayload.asEngineExecutionPayload),
web3Timeout):
debug "newPayload: newPayload timed out"
info "newPayload: newPayload timed out"
PayloadStatusV1(status: PayloadExecutionStatus.syncing)
payloadStatus = payloadResponse.status
@ -432,7 +436,7 @@ proc runQueueProcessingLoop*(self: ref BlockProcessor) {.async.} =
self.consensusManager.eth1Monitor,
blck.blck.bellatrixData.message.body.execution_payload)
except CatchableError as err:
debug "runQueueProcessingLoop: newExecutionPayload failed",
debug "runQueueProcessingLoop: newPayload failed",
err = err.msg
PayloadExecutionStatus.syncing
else:
@ -502,14 +506,6 @@ proc runQueueProcessingLoop*(self: ref BlockProcessor) {.async.} =
if executionPayloadStatus == PayloadExecutionStatus.valid and
hasExecutionPayload:
let
headBlockRoot = self.consensusManager.dag.head.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)
await self.runForkchoiceUpdated(
self.consensusManager.dag.head.executionBlockRoot,
self.consensusManager.dag.finalizedHead.blck.executionBlockRoot)

View File

@ -169,7 +169,9 @@ proc loadEth2NetworkMetadata*(path: string, eth1Network = none(Eth1Network)): Et
let data = (genesisData[0 ..< 40].toHex())
data in [
# Prater
"60F4596000000000043DB0D9A83813551EE2F33450D23797757D430911A9320530AD8A0EABC43EFB"
"60F4596000000000043DB0D9A83813551EE2F33450D23797757D430911A9320530AD8A0EABC43EFB",
# Kiln
"0C572B620000000099B09FCD43E5905236C370F184056BEC6E6638CFC31A323B304FC4AA789CB4AD"
]
else:
false

View File

@ -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.
let slots_since_genesis = slot - GENESIS_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

View File

@ -41,6 +41,7 @@ import
from eth/async_utils import awaitWithTimeout
from web3/engine_api import ForkchoiceUpdatedResponse
from web3/engine_api_types import PayloadExecutionStatus
# Metrics for tracking attestation and beacon block loss
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,
fee_recipient),
web3Timeout):
debug "forkchoice_updated: forkchoiceUpdated timed out"
info "forkchoice_updated: forkchoiceUpdated timed out"
default(ForkchoiceUpdatedResponse)
payloadId = forkchoiceResponse.payloadId
@ -443,6 +444,65 @@ proc forkchoice_updated(state: bellatrix.BeaconState,
else:
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,
randao_reveal: ValidatorSig,
validator_index: ValidatorIndex,
@ -472,6 +532,9 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
warn "Eth1 deposits not available. Skipping block proposal", slot
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):
node.exitPool[].getBeaconBlockExits(state.data)
let res = makeBeaconBlock(
@ -488,7 +551,13 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
SyncAggregate.init()
else:
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
cache)
if res.isErr():

View File

@ -43,33 +43,6 @@ func sign_block(state: ForkyBeaconState, blck: var ForkySignedBeaconBlock) =
blck.root,
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
proc mockBlock*(
state: ForkedHashedBeaconState,