Build block in parallel with attestation packing (#4185)

* fix block proposal in first slot after checkpoint
This commit is contained in:
Jacek Sieka 2022-10-04 13:24:16 +02:00 committed by GitHub
parent 83cd104d07
commit 40bed02f60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 81 deletions

View File

@ -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] =

View File

@ -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,

View File

@ -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