structure for supporting capella block production (#4383)

This commit is contained in:
tersec 2022-12-02 07:39:01 +00:00 committed by GitHub
parent f1584eef2f
commit 4e71e77da7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 209 additions and 55 deletions

View File

@ -15,6 +15,8 @@ import ".."/[beacon_chain_db, beacon_node],
".."/spec/datatypes/[phase0, altair],
"."/[rest_utils, state_ttl_cache]
from ".."/spec/datatypes/bellatrix import ExecutionPayload
export rest_utils
logScope: topics = "rest_validatorapi"
@ -364,8 +366,10 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
let proposer = node.dag.getProposer(qhead, qslot)
if proposer.isNone():
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
let res = await makeBeaconBlockForHeadAndSlot(
node, qrandao, proposer.get(), qgraffiti, qhead, qslot, qskip_randao_verification)
let res =
await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
node, qrandao, proposer.get(), qgraffiti, qhead, qslot,
qskip_randao_verification)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
res.get()
@ -453,7 +457,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
bellatrixData: res.get()))
else:
# Pre-Bellatrix, this endpoint will return a BeaconBlock
let res = await makeBeaconBlockForHeadAndSlot(
let res = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
node, qrandao, proposer.get(), qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())

View File

@ -456,7 +456,7 @@ func build_empty_execution_payload*(
proc build_empty_execution_payload*(
state: capella.BeaconState,
feeRecipient: Eth1Address,
expectedWithdrawals: seq[capella.Withdrawal]): capella.ExecutionPayload =
expectedWithdrawals = newSeq[capella.Withdrawal](0)): capella.ExecutionPayload =
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
## without any transactions.
let

View File

@ -327,7 +327,9 @@ template partialBeaconBlock*(
deposits: seq[Deposit],
exits: BeaconBlockExits,
sync_aggregate: SyncAggregate,
execution_payload: bellatrix.ExecutionPayload): phase0.BeaconBlock =
execution_payload: bellatrix.ExecutionPayload,
bls_to_execution_changes: SignedBLSToExecutionChangeList):
phase0.BeaconBlock =
phase0.BeaconBlock(
slot: state.data.slot,
proposer_index: proposer_index.uint64,
@ -354,7 +356,9 @@ template partialBeaconBlock*(
deposits: seq[Deposit],
exits: BeaconBlockExits,
sync_aggregate: SyncAggregate,
execution_payload: bellatrix.ExecutionPayload): altair.BeaconBlock =
execution_payload: bellatrix.ExecutionPayload,
bls_to_execution_changes: SignedBLSToExecutionChangeList):
altair.BeaconBlock =
altair.BeaconBlock(
slot: state.data.slot,
proposer_index: proposer_index.uint64,
@ -382,7 +386,9 @@ template partialBeaconBlock*(
deposits: seq[Deposit],
exits: BeaconBlockExits,
sync_aggregate: SyncAggregate,
execution_payload: bellatrix.ExecutionPayload): bellatrix.BeaconBlock =
execution_payload: bellatrix.ExecutionPayload,
bls_to_execution_changes: SignedBLSToExecutionChangeList):
bellatrix.BeaconBlock =
bellatrix.BeaconBlock(
slot: state.data.slot,
proposer_index: proposer_index.uint64,
@ -433,7 +439,7 @@ template partialBeaconBlock*(
bls_to_execution_changes: bls_to_execution_changes
))
proc makeBeaconBlock*(
proc makeBeaconBlock*[T: bellatrix.ExecutionPayload | capella.ExecutionPayload](
cfg: RuntimeConfig,
state: var ForkedHashedBeaconState,
proposer_index: ValidatorIndex,
@ -444,15 +450,16 @@ proc makeBeaconBlock*(
deposits: seq[Deposit],
exits: BeaconBlockExits,
sync_aggregate: SyncAggregate,
executionPayload: bellatrix.ExecutionPayload,
executionPayload: T,
bls_to_execution_changes: SignedBLSToExecutionChangeList,
rollback: RollbackForkedHashedProc,
cache: var StateCache,
# TODO:
# `verificationFlags` is needed only in tests and can be
# removed if we don't use invalid signatures there
verificationFlags: UpdateFlags = {},
transactions_root: Opt[Eth2Digest] = Opt.none Eth2Digest,
execution_payload_root: Opt[Eth2Digest] = Opt.none Eth2Digest):
verificationFlags: UpdateFlags,
transactions_root: Opt[Eth2Digest],
execution_payload_root: Opt[Eth2Digest]):
Result[ForkedBeaconBlock, cstring] =
## Create a block for the given state. The latest block applied to it will
## be used for the parent_root value, and the slot will be take from
@ -468,7 +475,7 @@ proc makeBeaconBlock*(
partialBeaconBlock(
cfg, state.`kind Data`, proposer_index, randao_reveal, eth1_data,
graffiti, attestations, deposits, exits, sync_aggregate,
executionPayload))
executionPayload, bls_to_execution_changes))
let res = process_block(
cfg, state.`kind Data`.data, blck.`kind Data`.asSigVerified(),
@ -505,9 +512,53 @@ proc makeBeaconBlock*(
ok(blck)
when T is bellatrix.ExecutionPayload:
case state.kind
of BeaconStateFork.Phase0: makeBeaconBlock(phase0)
of BeaconStateFork.Altair: makeBeaconBlock(altair)
of BeaconStateFork.Bellatrix: makeBeaconBlock(bellatrix)
of BeaconStateFork.Capella:
raiseAssert $capellaImplementationMissing
of BeaconStateFork.Capella: raiseAssert "Attempt to use Bellatrix payload with Capella state"
elif T is capella.ExecutionPayload:
case state.kind
of BeaconStateFork.Phase0, BeaconStateFork.Altair, BeaconStateFork.Bellatrix:
raiseAssert "Attempt to use Capella payload with non-Capella state"
of BeaconStateFork.Capella: makeBeaconBlock(capella)
# workaround for https://github.com/nim-lang/Nim/issues/20900 rather than have
# these be default arguments
proc makeBeaconBlock*[T](
cfg: RuntimeConfig, state: var ForkedHashedBeaconState,
proposer_index: ValidatorIndex, randao_reveal: ValidatorSig,
eth1_data: Eth1Data, graffiti: GraffitiBytes,
attestations: seq[Attestation], deposits: seq[Deposit],
exits: BeaconBlockExits, sync_aggregate: SyncAggregate,
executionPayload: T,
bls_to_execution_changes: SignedBLSToExecutionChangeList,
rollback: RollbackForkedHashedProc, cache: var StateCache):
Result[ForkedBeaconBlock, cstring] =
makeBeaconBlock(
cfg, state, proposer_index, randao_reveal, eth1_data, graffiti,
attestations, deposits, exits, sync_aggregate, executionPayload,
bls_to_execution_changes, rollback, cache,
verificationFlags = {},
transactions_root = Opt.none Eth2Digest,
execution_payload_root = Opt.none Eth2Digest)
proc makeBeaconBlock*[T](
cfg: RuntimeConfig, state: var ForkedHashedBeaconState,
proposer_index: ValidatorIndex, randao_reveal: ValidatorSig,
eth1_data: Eth1Data, graffiti: GraffitiBytes,
attestations: seq[Attestation], deposits: seq[Deposit],
exits: BeaconBlockExits, sync_aggregate: SyncAggregate,
executionPayload: T,
bls_to_execution_changes: SignedBLSToExecutionChangeList,
rollback: RollbackForkedHashedProc,
cache: var StateCache, verificationFlags: UpdateFlags):
Result[ForkedBeaconBlock, cstring] =
makeBeaconBlock(
cfg, state, proposer_index, randao_reveal, eth1_data, graffiti,
attestations, deposits, exits, sync_aggregate, executionPayload,
bls_to_execution_changes, rollback, cache,
verificationFlags = verificationFlags,
transactions_root = Opt.none Eth2Digest,
execution_payload_root = Opt.none Eth2Digest)

View File

@ -462,41 +462,48 @@ proc getExecutionPayload[T](
msg = err.msg
return Opt.some empty_execution_payload
proc makeBeaconBlockForHeadAndSlot*(
proc makeBeaconBlockForHeadAndSlot*[EP](
node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
slot: Slot,
skip_randao_verification_bool: bool = false,
execution_payload: Opt[bellatrix.ExecutionPayload] = Opt.none(bellatrix.ExecutionPayload),
transactions_root: Opt[Eth2Digest] = Opt.none(Eth2Digest),
execution_payload_root: Opt[Eth2Digest] = Opt.none(Eth2Digest)):
skip_randao_verification_bool: bool,
execution_payload: Opt[EP],
transactions_root: Opt[Eth2Digest],
execution_payload_root: Opt[Eth2Digest]):
Future[ForkedBlockResult] {.async.} =
# Advance state to the slot that we're proposing for
var
cache = StateCache()
var cache = StateCache()
let
# The clearance state already typically sits at the right slot per
# `advanceClearanceState`
state = node.dag.getProposalState(head, slot, cache).valueOr:
# TODO can use `valueOr:`/`return err($error)` if/when
# https://github.com/status-im/nim-stew/issues/161 is addressed
maybeState = node.dag.getProposalState(head, slot, cache)
if maybeState.isErr:
beacon_block_production_errors.inc()
return err($error)
return err($maybeState.error)
let
state = maybeState.get
payloadFut =
if executionPayload.isSome:
let fut = newFuture[Opt[bellatrix.ExecutionPayload]]("given-payload")
let fut = newFuture[Opt[EP]]("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[bellatrix.ExecutionPayload]]("empty-payload")
let fut = newFuture[Opt[EP]]("empty-payload")
# https://github.com/nim-lang/Nim/issues/19802
fut.complete(Opt.some(default(bellatrix.ExecutionPayload)))
fut.complete(Opt.some(default(EP)))
fut
else:
# Create execution payload while packing attestations
getExecutionPayload[bellatrix.ExecutionPayload](
getExecutionPayload[EP](
node, state, slot.epoch, validator_index)
eth1Proposal = node.getBlockProposalEth1Data(state[])
@ -534,6 +541,7 @@ proc makeBeaconBlockForHeadAndSlot*(
exits,
syncAggregate,
payload,
(static(default(SignedBLSToExecutionChangeList))),
noRollback, # Temporary state - no need for rollback
cache,
# makeBeaconBlock doesn't verify BLS at all, but does have special case
@ -550,6 +558,29 @@ proc makeBeaconBlockForHeadAndSlot*(
slot, head = shortLog(head), error
$error
# workaround for https://github.com/nim-lang/Nim/issues/20900 to avoid default
# parameters
proc makeBeaconBlockForHeadAndSlot*[EP](
node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
slot: Slot):
Future[ForkedBlockResult] {.async.} =
return await makeBeaconBlockForHeadAndSlot[EP](
node, randao_reveal, validator_index, graffiti, head, slot,
false, execution_payload = Opt.none(EP),
transactions_root = Opt.none(Eth2Digest),
execution_payload_root = Opt.none(Eth2Digest))
proc makeBeaconBlockForHeadAndSlot*[EP](
node: BeaconNode, randao_reveal: ValidatorSig,
validator_index: ValidatorIndex, graffiti: GraffitiBytes, head: BlockRef,
slot: Slot, skip_randao_verification: bool):
Future[ForkedBlockResult] {.async.} =
return await makeBeaconBlockForHeadAndSlot[EP](
node, randao_reveal, validator_index, graffiti, head, slot,
skip_randao_verification, execution_payload = Opt.none(EP),
transactions_root = Opt.none(Eth2Digest),
execution_payload_root = Opt.none(Eth2Digest))
proc getBlindedExecutionPayload(
node: BeaconNode, slot: Slot, executionBlockRoot: Eth2Digest,
@ -695,8 +726,9 @@ proc getBlindedBlockParts(
shimExecutionPayload, executionPayloadHeader.get,
getFieldNames(bellatrix.ExecutionPayloadHeader))
let newBlock = await makeBeaconBlockForHeadAndSlot(
let newBlock = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
node, randao, validator_index, node.graffitiBytes, head, slot,
skip_randao_verification_bool = false,
execution_payload = Opt.some shimExecutionPayload,
transactions_root = Opt.some executionPayloadHeader.get.transactions_root,
execution_payload_root =
@ -844,7 +876,8 @@ proc proposeBlock(node: BeaconNode,
beacon_block_builder_missed_without_fallback.inc()
return newBlockMEV.get
let newBlock = await makeBeaconBlockForHeadAndSlot(
discard $capellaImplementationMissing & ": use capella.ExecutionPayload here as appropriate"
let newBlock = await makeBeaconBlockForHeadAndSlot[bellatrix.ExecutionPayload](
node, randao, validator_index, node.graffitiBytes, head, slot)
if newBlock.isErr():

View File

@ -65,6 +65,10 @@ from ../beacon_chain/spec/state_transition_block import process_block
# version, so isolate these here pending refactoring of block_sim to prefer,
# when possible, to also use the forked version. It'll be worth keeping some
# example of the non-forked version because it enables fork bootstrapping.
const defaultSignedBLSToExecutionChangeList =
default(SignedBLSToExecutionChangeList)
proc makeBeaconBlock(
cfg: RuntimeConfig,
state: var phase0.HashedBeaconState,
@ -94,7 +98,8 @@ proc makeBeaconBlock(
var blck = partialBeaconBlock(
cfg, state, proposer_index, randao_reveal, eth1_data, graffiti,
attestations, deposits, exits, sync_aggregate, execution_payload)
attestations, deposits, exits, sync_aggregate, execution_payload,
defaultSignedBLSToExecutionChangeList)
let res = process_block(
cfg, state.data, blck.asSigVerified(), verificationFlags, cache)
@ -137,7 +142,8 @@ proc makeBeaconBlock(
var blck = partialBeaconBlock(
cfg, state, proposer_index, randao_reveal, eth1_data, graffiti,
attestations, deposits, exits, sync_aggregate, execution_payload)
attestations, deposits, exits, sync_aggregate, execution_payload,
defaultSignedBLSToExecutionChangeList)
# Signatures are verified elsewhere, so don't duplicate inefficiently here
let res = process_block(
@ -181,7 +187,8 @@ proc makeBeaconBlock(
var blck = partialBeaconBlock(
cfg, state, proposer_index, randao_reveal, eth1_data, graffiti,
attestations, deposits, exits, sync_aggregate, execution_payload)
attestations, deposits, exits, sync_aggregate, execution_payload,
defaultSignedBLSToExecutionChangeList)
let res = process_block(
cfg, state.data, blck.asSigVerified(), verificationFlags, cache)
@ -462,7 +469,7 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
default(capella.ExecutionPayload)
else:
default(bellatrix.ExecutionPayload),
default(SignedBLSToExecutionChangeList),
defaultSignedBLSToExecutionChangeList,
noRollback,
cache)

View File

@ -162,6 +162,7 @@ cli do(validatorsDir: string, secretsDir: string,
BeaconBlockExits(),
syncAggregate,
default(bellatrix.ExecutionPayload),
(static(default(SignedBLSToExecutionChangeList))),
noRollback,
cache).get()

View File

@ -17,6 +17,9 @@ import
attestation_pool, blockchain_dag, block_quarantine, block_clearance],
./testutil, ./testdbutil, ./testblockutil
from ../beacon_chain/spec/datatypes/capella import
SignedBLSToExecutionChangeList
func `$`(x: BlockRef): string = shortLog(x)
const
@ -228,6 +231,7 @@ suite "Block pool processing" & preset():
getStateField(tmpState[], eth1_data),
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
default(SyncAggregate), default(ExecutionPayload),
default(SignedBLSToExecutionChangeList),
noRollback, cache)
check: message.isErr
@ -240,6 +244,7 @@ suite "Block pool processing" & preset():
getStateField(tmpState[], eth1_data),
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
default(SyncAggregate), default(ExecutionPayload),
default(SignedBLSToExecutionChangeList),
noRollback, cache, {skipRandaoVerification})
check: message.isErr
@ -252,6 +257,7 @@ suite "Block pool processing" & preset():
getStateField(tmpState[], eth1_data),
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
default(SyncAggregate), default(ExecutionPayload),
default(SignedBLSToExecutionChangeList),
noRollback, cache, {})
check: message.isErr
@ -264,6 +270,7 @@ suite "Block pool processing" & preset():
getStateField(tmpState[], eth1_data),
default(GraffitiBytes), @[], @[], BeaconBlockExits(),
default(SyncAggregate), default(ExecutionPayload),
default(SignedBLSToExecutionChangeList),
noRollback, cache, {skipRandaoVerification})
check: message.isOk

View File

@ -78,8 +78,11 @@ func signBlock(
ValidatorSig()
ForkedSignedBeaconBlock.init(forked, root, signature)
from ../beacon_chain/spec/datatypes/capella import
BeaconState, ExecutionPayload
func build_empty_merge_execution_payload(state: bellatrix.BeaconState):
ExecutionPayload =
bellatrix.ExecutionPayload =
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
## without any transactions from a non-merged block.
@ -90,7 +93,7 @@ func build_empty_merge_execution_payload(state: bellatrix.BeaconState):
timestamp = compute_timestamp_at_slot(state, state.slot)
randao_mix = get_randao_mix(state, get_current_epoch(state))
var payload = ExecutionPayload(
var payload = bellatrix.ExecutionPayload(
parent_hash: latest.block_hash,
state_root: latest.state_root, # no changes to the state
receipts_root: EMPTY_ROOT_HASH,
@ -105,17 +108,47 @@ func build_empty_merge_execution_payload(state: bellatrix.BeaconState):
payload
proc addTestBlock*(
proc build_empty_merge_execution_payload(state: capella.BeaconState):
capella.ExecutionPayload =
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
## without any transactions from a non-merged block.
doAssert not is_merge_transition_complete(state)
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 = capella.ExecutionPayload(
parent_hash: latest.block_hash,
state_root: latest.state_root, # no changes to the state
receipts_root: EMPTY_ROOT_HASH,
block_number: latest.block_number + 1,
prev_randao: randao_mix,
gas_limit: 30000000, # retain same limit
gas_used: 0, # empty block, 0 gas
timestamp: timestamp,
base_fee_per_gas: EIP1559_INITIAL_BASE_FEE)
payload.block_hash = rlpHash emptyPayloadToBlockHeader(payload)
payload
from ../beacon_chain/spec/datatypes/capella import
SignedBLSToExecutionChangeList
proc addTestBlockAux[EP: bellatrix.ExecutionPayload | capella.ExecutionPayload](
state: var ForkedHashedBeaconState,
cache: var StateCache,
eth1_data = Eth1Data(),
attestations = newSeq[Attestation](),
deposits = newSeq[Deposit](),
sync_aggregate = SyncAggregate.init(),
graffiti = default(GraffitiBytes),
flags: set[UpdateFlag] = {},
nextSlot = true,
cfg = defaultRuntimeConfig): ForkedSignedBeaconBlock =
eth1_data: Eth1Data,
attestations: seq[Attestation],
deposits: seq[Deposit],
sync_aggregate: SyncAggregate,
graffiti: GraffitiBytes,
flags: set[UpdateFlag],
nextSlot: bool,
cfg: RuntimeConfig): ForkedSignedBeaconBlock =
# Create and add a block to state - state will advance by one slot!
if nextSlot:
var info = ForkedEpochInfo()
@ -138,9 +171,10 @@ proc addTestBlock*(
let execution_payload =
withState(state):
when stateFork >= BeaconStateFork.Capella:
raiseAssert $capellaImplementationMissing
elif stateFork >= BeaconStateFork.Bellatrix:
when (stateFork == BeaconStateFork.Bellatrix and
EP is bellatrix.ExecutionPayload) or
(stateFork == BeaconStateFork.Capella and
EP is capella.ExecutionPayload):
# Merge shortly after Bellatrix
if forkyState.data.slot >
cfg.BELLATRIX_FORK_EPOCH * SLOTS_PER_EPOCH + 10:
@ -150,9 +184,9 @@ proc addTestBlock*(
else:
build_empty_merge_execution_payload(forkyState.data)
else:
default(ExecutionPayload)
default(EP)
else:
default(ExecutionPayload)
default(EP)
let
message = makeBeaconBlock(
@ -171,6 +205,7 @@ proc addTestBlock*(
BeaconBlockExits(),
sync_aggregate,
execution_payload,
default(SignedBLSToExecutionChangeList),
noRollback,
cache,
verificationFlags = {skipBlsValidation})
@ -186,6 +221,22 @@ proc addTestBlock*(
new_block
proc addTestBlock*(
state: var ForkedHashedBeaconState, cache: var StateCache,
eth1_data = Eth1Data(), attestations = newSeq[Attestation](),
deposits = newSeq[Deposit](), sync_aggregate = SyncAggregate.init(),
graffiti = default(GraffitiBytes), flags: set[UpdateFlag] = {},
nextSlot = true, cfg = defaultRuntimeConfig): ForkedSignedBeaconBlock =
case state.kind
of BeaconStateFork.Phase0, BeaconStateFork.Altair, BeaconStateFork.Bellatrix:
addTestBlockAux[bellatrix.ExecutionPayload](
state, cache, eth1_data, attestations, deposits, sync_aggregate,
graffiti, flags, nextSlot, cfg)
of BeaconStateFork.Capella:
addTestBlockAux[capella.ExecutionPayload](
state, cache, eth1_data, attestations, deposits, sync_aggregate,
graffiti, flags, nextSlot, cfg)
proc makeTestBlock*(
state: ForkedHashedBeaconState,
cache: var StateCache,