Build block in parallel with attestation packing (#4185)
* fix block proposal in first slot after checkpoint
This commit is contained in:
parent
83cd104d07
commit
40bed02f60
|
@ -2025,6 +2025,39 @@ proc getProposer*(
|
|||
|
||||
proposer
|
||||
|
||||
proc getProposalState*(
|
||||
dag: ChainDagRef, head: BlockRef, slot: Slot, cache: var StateCache):
|
||||
Result[ref ForkedHashedBeaconState, cstring] =
|
||||
## Return a state suitable for making proposals for the given head and slot -
|
||||
## in particular, the state can be discarded after use and does not have a
|
||||
## state root set
|
||||
|
||||
# Start with the clearance state, since this one typically has been advanced
|
||||
# and thus has a hot hash tree cache
|
||||
let state = newClone(dag.clearanceState)
|
||||
|
||||
var
|
||||
info = ForkedEpochInfo()
|
||||
if not state[].can_advance_slots(head.root, slot):
|
||||
# The last state root will be computed as part of block production, so skip
|
||||
# it now
|
||||
if not dag.updateState(
|
||||
state[], head.atSlot(slot - 1).toBlockSlotId().expect("not nil"),
|
||||
false, cache):
|
||||
error "Cannot get proposal state - skipping block production, database corrupt?",
|
||||
head = shortLog(head),
|
||||
slot
|
||||
return err("Cannot create proposal state")
|
||||
else:
|
||||
loadStateCache(dag, cache, head.bid, slot.epoch)
|
||||
|
||||
if getStateField(state[], slot) < slot:
|
||||
process_slots(
|
||||
dag.cfg, state[], slot, cache, info,
|
||||
{skipLastStateRootCalculation}).expect("advancing 1 slot should not fail")
|
||||
|
||||
ok state
|
||||
|
||||
proc aggregateAll*(
|
||||
dag: ChainDAGRef,
|
||||
validator_indices: openArray[ValidatorIndex]): Result[CookedPubKey, cstring] =
|
||||
|
|
|
@ -359,8 +359,7 @@ from web3/engine_api_types import PayloadExecutionStatus
|
|||
|
||||
proc getExecutionPayload[T](
|
||||
node: BeaconNode, proposalState: ref ForkedHashedBeaconState,
|
||||
epoch: Epoch, validator_index: ValidatorIndex,
|
||||
pubkey: ValidatorPubKey): Future[Opt[T]] {.async.} =
|
||||
epoch: Epoch, validator_index: ValidatorIndex): Future[Opt[T]] {.async.} =
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/bellatrix/validator.md#executionpayload
|
||||
|
||||
template empty_execution_payload(): auto =
|
||||
|
@ -394,7 +393,13 @@ proc getExecutionPayload[T](
|
|||
default(Eth2Digest)
|
||||
latestSafe = beaconHead.safeExecutionPayloadHash
|
||||
latestFinalized = beaconHead.finalizedExecutionPayloadHash
|
||||
feeRecipient = node.getFeeRecipient(pubkey, validator_index, epoch)
|
||||
feeRecipient = block:
|
||||
let pubkey = node.dag.validatorKey(validator_index)
|
||||
if pubkey.isNone():
|
||||
error "Cannot get proposer pubkey, bug?", validator_index
|
||||
default(Eth1Address)
|
||||
else:
|
||||
node.getFeeRecipient(pubkey.get().toPubKey(), validator_index, epoch)
|
||||
lastFcU = node.consensusManager.forkchoiceUpdatedInfo
|
||||
timestamp = withState(proposalState[]):
|
||||
compute_timestamp_at_slot(forkyState.data, forkyState.data.slot)
|
||||
|
@ -462,104 +467,87 @@ proc makeBeaconBlockForHeadAndSlot*(
|
|||
execution_payload_root: Opt[Eth2Digest] = Opt.none(Eth2Digest)):
|
||||
Future[ForkedBlockResult] {.async.} =
|
||||
# Advance state to the slot that we're proposing for
|
||||
let proposalState = assignClone(node.dag.headState)
|
||||
var
|
||||
cache = StateCache()
|
||||
|
||||
# TODO fails at checkpoint synced head
|
||||
node.dag.withUpdatedState(
|
||||
proposalState[],
|
||||
head.atSlot(slot - 1).toBlockSlotId().expect("not nil")):
|
||||
# Advance to the given slot without calculating state root - we'll only
|
||||
# need a state root _with_ the block applied
|
||||
var info: ForkedEpochInfo
|
||||
# Execution payload handling will need a review post-Bellatrix
|
||||
static: doAssert high(BeaconStateFork) == BeaconStateFork.Bellatrix
|
||||
|
||||
process_slots(
|
||||
node.dag.cfg, state, slot, cache, info,
|
||||
{skipLastStateRootCalculation}).expect("advancing 1 slot should not fail")
|
||||
|
||||
let
|
||||
eth1Proposal = node.getBlockProposalEth1Data(state)
|
||||
|
||||
if eth1Proposal.hasMissingDeposits:
|
||||
let
|
||||
# The clearance state already typically sits at the right slot per
|
||||
# `advanceClearanceState`
|
||||
state = node.dag.getProposalState(head, slot, cache).valueOr:
|
||||
beacon_block_production_errors.inc()
|
||||
warn "Eth1 deposits not available. Skipping block proposal", slot
|
||||
return ForkedBlockResult.err("Eth1 deposits not available")
|
||||
return err($error)
|
||||
payloadFut =
|
||||
if executionPayload.isSome:
|
||||
let fut = newFuture[Opt[ExecutionPayload]]("given-payload")
|
||||
fut.complete(executionPayload)
|
||||
fut
|
||||
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
|
||||
not (
|
||||
is_merge_transition_complete(state[]) or
|
||||
((not node.eth1Monitor.isNil) and node.eth1Monitor.ttdReached)):
|
||||
let fut = newFuture[Opt[ExecutionPayload]]("empty-payload")
|
||||
# https://github.com/nim-lang/Nim/issues/19802
|
||||
fut.complete(Opt.some(default(bellatrix.ExecutionPayload)))
|
||||
fut
|
||||
else:
|
||||
# Create execution payload while packing attestations
|
||||
getExecutionPayload[bellatrix.ExecutionPayload](
|
||||
node, state, slot.epoch, validator_index)
|
||||
|
||||
# Only current hardfork with execution payloads is Bellatrix
|
||||
static: doAssert high(BeaconStateFork) == BeaconStateFork.Bellatrix
|
||||
eth1Proposal = node.getBlockProposalEth1Data(state[])
|
||||
|
||||
let
|
||||
exits = withState(state):
|
||||
node.exitPool[].getBeaconBlockExits(node.dag.cfg, forkyState.data)
|
||||
effectiveExecutionPayload =
|
||||
if executionPayload.isSome:
|
||||
executionPayload.get
|
||||
elif slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
|
||||
not (
|
||||
is_merge_transition_complete(proposalState[]) or
|
||||
((not node.eth1Monitor.isNil) and node.eth1Monitor.ttdReached)):
|
||||
# https://github.com/nim-lang/Nim/issues/19802
|
||||
(static(default(bellatrix.ExecutionPayload)))
|
||||
else:
|
||||
let
|
||||
pubkey = node.dag.validatorKey(validator_index)
|
||||
maybeExecutionPayload =
|
||||
(await getExecutionPayload[bellatrix.ExecutionPayload](
|
||||
node, proposalState, slot.epoch, validator_index,
|
||||
# TODO https://github.com/nim-lang/Nim/issues/19802
|
||||
if pubkey.isSome: pubkey.get.toPubKey else: default(ValidatorPubKey)))
|
||||
if maybeExecutionPayload.isNone:
|
||||
beacon_block_production_errors.inc()
|
||||
warn "Unable to get execution payload. Skipping block proposal",
|
||||
slot, validator_index
|
||||
return ForkedBlockResult.err("Unable to get execution payload")
|
||||
maybeExecutionPayload.get
|
||||
if eth1Proposal.hasMissingDeposits:
|
||||
beacon_block_production_errors.inc()
|
||||
warn "Eth1 deposits not available. Skipping block proposal", slot
|
||||
return err("Eth1 deposits not available")
|
||||
|
||||
let res = makeBeaconBlock(
|
||||
let
|
||||
attestations =
|
||||
node.attestationPool[].getAttestationsForBlock(state[], cache)
|
||||
exits = withState(state[]):
|
||||
node.exitPool[].getBeaconBlockExits(node.dag.cfg, forkyState.data)
|
||||
syncAggregate =
|
||||
if slot.epoch < node.dag.cfg.ALTAIR_FORK_EPOCH:
|
||||
SyncAggregate.init()
|
||||
else:
|
||||
node.syncCommitteeMsgPool[].produceSyncAggregate(head.root)
|
||||
payload = (await payloadFut).valueOr:
|
||||
beacon_block_production_errors.inc()
|
||||
warn "Unable to get execution payload. Skipping block proposal",
|
||||
slot, validator_index
|
||||
return err("Unable to get execution payload")
|
||||
|
||||
return makeBeaconBlock(
|
||||
node.dag.cfg,
|
||||
state,
|
||||
state[],
|
||||
validator_index,
|
||||
randao_reveal,
|
||||
eth1Proposal.vote,
|
||||
graffiti,
|
||||
node.attestationPool[].getAttestationsForBlock(state, cache),
|
||||
attestations,
|
||||
eth1Proposal.deposits,
|
||||
exits,
|
||||
if slot.epoch < node.dag.cfg.ALTAIR_FORK_EPOCH:
|
||||
SyncAggregate.init()
|
||||
else:
|
||||
node.syncCommitteeMsgPool[].produceSyncAggregate(head.root),
|
||||
effectiveExecutionPayload,
|
||||
syncAggregate,
|
||||
payload,
|
||||
noRollback, # Temporary state - no need for rollback
|
||||
cache,
|
||||
# makeBeaconBlock doesn't verify BLS at all, but does have special case
|
||||
# for skipRandaoVerification separately
|
||||
verificationFlags =
|
||||
if skip_randao_verification_bool: {skipRandaoVerification} else: {},
|
||||
transactions_root =
|
||||
if transactions_root.isSome:
|
||||
Opt.some transactions_root.get
|
||||
else:
|
||||
Opt.none(Eth2Digest),
|
||||
execution_payload_root =
|
||||
if execution_payload_root.isSome:
|
||||
Opt.some execution_payload_root.get
|
||||
else:
|
||||
Opt.none Eth2Digest)
|
||||
if res.isErr():
|
||||
# This is almost certainly a bug, but it's complex enough that there's a
|
||||
# small risk it might happen even when most proposals succeed - thus we
|
||||
# log instead of asserting
|
||||
beacon_block_production_errors.inc()
|
||||
error "Cannot create block for proposal",
|
||||
slot, head = shortLog(head), error = res.error()
|
||||
return err($res.error)
|
||||
return ok(res.get())
|
||||
do:
|
||||
transactions_root = transactions_root,
|
||||
execution_payload_root = execution_payload_root).mapErr do (error: cstring) -> string:
|
||||
# This is almost certainly a bug, but it's complex enough that there's a
|
||||
# small risk it might happen even when most proposals succeed - thus we
|
||||
# log instead of asserting
|
||||
beacon_block_production_errors.inc()
|
||||
error "Cannot get proposal state - skipping block production, database corrupt?",
|
||||
head = shortLog(head),
|
||||
slot
|
||||
return err("Cannot create proposal state")
|
||||
error "Cannot create block for proposal",
|
||||
slot, head = shortLog(head)
|
||||
$error
|
||||
|
||||
|
||||
proc getBlindedExecutionPayload(
|
||||
node: BeaconNode, slot: Slot, executionBlockRoot: Eth2Digest,
|
||||
|
|
|
@ -826,6 +826,8 @@ suite "Backfill":
|
|||
validatorMonitor = newClone(ValidatorMonitor.init())
|
||||
dag = init(ChainDAGRef, defaultRuntimeConfig, db, validatorMonitor, {})
|
||||
|
||||
var cache = StateCache()
|
||||
|
||||
check:
|
||||
dag.getBlockRef(tailBlock.root).get().bid == dag.tail
|
||||
dag.getBlockRef(blocks[^2].root).isNone()
|
||||
|
@ -857,6 +859,9 @@ suite "Backfill":
|
|||
|
||||
dag.backfill == tailBlock.phase0Data.message.toBeaconBlockSummary()
|
||||
|
||||
# Check that we can propose right from the checkpoint state
|
||||
dag.getProposalState(dag.head, dag.head.slot + 1, cache).isOk()
|
||||
|
||||
var
|
||||
badBlock = blocks[^2].phase0Data
|
||||
badBlock.signature = blocks[^3].phase0Data.signature
|
||||
|
|
Loading…
Reference in New Issue