Local validator proposals (#5137)

* Implement local validator block proposals

* Address review feedback

* Fix rebase issue
This commit is contained in:
henridf 2023-06-30 09:39:41 +02:00 committed by GitHub
parent ba94dc849f
commit 99264d7507
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 234 additions and 44 deletions

View File

@ -564,6 +564,7 @@ func asConsensusType*(payload: engine_api.GetPayloadV3Response):
# types for KZG commitments and Blobs in the `web3` and the `deneb` spec types.
# Both are defined as `array[N, byte]` under the hood.
kzgs: KzgCommitments payload.blobsBundle.commitments.mapIt(it.bytes),
proofs: payload.blobsBundle.proofs.mapIt(it.bytes),
blobs: Blobs payload.blobsBundle.blobs.mapIt(it.bytes)
)

View File

@ -109,6 +109,7 @@ type
executionPayload*: ExecutionPayload
blockValue*: Wei
kzgs*: KzgCommitments
proofs*:seq[KZGProof]
blobs*: Blobs
# https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/deneb/beacon-chain.md#executionpayloadheader

View File

@ -18,12 +18,12 @@ import
stew/base10, web3/ethtypes,
".."/forks,
".."/datatypes/[phase0, altair, bellatrix, deneb],
".."/mev/[bellatrix_mev, capella_mev]
".."/mev/[bellatrix_mev, capella_mev, deneb_mev]
from ".."/datatypes/capella import BeaconBlockBody
export forks, phase0, altair, bellatrix, capella, bellatrix_mev, capella_mev,
tables
deneb_mev, tables
const
# https://github.com/ethereum/eth2.0-APIs/blob/master/apis/beacon/states/validator_balances.yaml#L17
@ -652,6 +652,7 @@ type
GetGenesisResponse* = DataEnclosedObject[RestGenesis]
GetHeaderResponseBellatrix* = DataVersionEnclosedObject[bellatrix_mev.SignedBuilderBid]
GetHeaderResponseCapella* = DataVersionEnclosedObject[capella_mev.SignedBuilderBid]
GetHeaderResponseDeneb* = DataVersionEnclosedObject[deneb_mev.SignedBuilderBid]
GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity]
GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount]
GetPeerResponse* = DataMetaEnclosedObject[RestNodePeer]
@ -680,6 +681,7 @@ type
ProduceSyncCommitteeContributionResponse* = DataEnclosedObject[SyncCommitteeContribution]
SubmitBlindedBlockResponseBellatrix* = DataEnclosedObject[bellatrix.ExecutionPayload]
SubmitBlindedBlockResponseCapella* = DataEnclosedObject[capella.ExecutionPayload]
SubmitBlindedBlockResponseDeneb* = DataEnclosedObject[deneb.ExecutionPayload]
GetValidatorsActivityResponse* = DataEnclosedObject[seq[RestActivityItem]]
GetValidatorsLivenessResponse* = DataEnclosedObject[seq[RestLivenessItem]]

View File

@ -17,12 +17,12 @@ import
block_id, eth2_merkleization, eth2_ssz_serialization,
forks_light_client, presets],
./datatypes/[phase0, altair, bellatrix, capella, deneb],
./mev/bellatrix_mev, ./mev/capella_mev
./mev/bellatrix_mev, ./mev/capella_mev, ./mev/deneb_mev
export
extras, block_id, phase0, altair, bellatrix, capella, deneb,
eth2_merkleization, eth2_ssz_serialization, forks_light_client,
presets, bellatrix_mev, capella_mev
presets, bellatrix_mev, capella_mev, deneb_mev
# This file contains helpers for dealing with forks - we have two ways we can
# deal with forks:
@ -851,7 +851,8 @@ template withStateAndBlck*(
func toBeaconBlockHeader*(
blck: SomeForkyBeaconBlock | bellatrix_mev.BlindedBeaconBlock |
capella_mev.BlindedBeaconBlock): BeaconBlockHeader =
capella_mev.BlindedBeaconBlock | deneb_mev.BlindedBeaconBlock):
BeaconBlockHeader =
## Reduce a given `BeaconBlock` to its `BeaconBlockHeader`.
BeaconBlockHeader(
slot: blck.slot,

View File

@ -0,0 +1,87 @@
# beacon_chain
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ".."/datatypes/[altair, capella, deneb]
from stew/byteutils import to0xHex
{.push raises: [].}
type
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#builderbid
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/capella/builder.md#executionpayloadheader
BuilderBid* = object
header*: deneb.ExecutionPayloadHeader
value*: UInt256
pubkey*: ValidatorPubKey
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#signedbuilderbid
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/capella/builder.md#executionpayloadheader
SignedBuilderBid* = object
message*: BuilderBid
signature*: ValidatorSig
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/capella/builder.md#blindedbeaconblockbody
# https://github.com/ethereum/builder-specs/blob/0b913daaa491cd889083827375977a6285e684bd/specs/deneb/builder.md#blindedbeaconblockbody
BlindedBeaconBlockBody* = object
randao_reveal*: ValidatorSig
eth1_data*: Eth1Data
graffiti*: GraffitiBytes
proposer_slashings*: List[ProposerSlashing, Limit MAX_PROPOSER_SLASHINGS]
attester_slashings*: List[AttesterSlashing, Limit MAX_ATTESTER_SLASHINGS]
attestations*: List[Attestation, Limit MAX_ATTESTATIONS]
deposits*: List[Deposit, Limit MAX_DEPOSITS]
voluntary_exits*: List[SignedVoluntaryExit, Limit MAX_VOLUNTARY_EXITS]
sync_aggregate*: SyncAggregate
execution_payload_header*:
capella.ExecutionPayloadHeader
bls_to_execution_changes*:
List[SignedBLSToExecutionChange,
Limit MAX_BLS_TO_EXECUTION_CHANGES]
blob_kzg_commitments*: KzgCommitments # [New in Deneb]
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#blindedbeaconblock
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/capella/builder.md#blindedbeaconblockbody
# https://github.com/ethereum/builder-specs/blob/0b913daaa491cd889083827375977a6285e684bd/specs/deneb/builder.md#blindedbeaconblockbody
BlindedBeaconBlock* = object
slot*: Slot
proposer_index*: uint64
parent_root*: Eth2Digest
state_root*: Eth2Digest
body*: BlindedBeaconBlockBody # [Modified in Deneb]
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#signedblindedbeaconblock
# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/capella/builder.md#blindedbeaconblockbody
SignedBlindedBeaconBlock* = object
message*: BlindedBeaconBlock
signature*: ValidatorSig
func shortLog*(v: BlindedBeaconBlock): auto =
(
slot: shortLog(v.slot),
proposer_index: v.proposer_index,
parent_root: shortLog(v.parent_root),
state_root: shortLog(v.state_root),
eth1data: v.body.eth1_data,
graffiti: $v.body.graffiti,
proposer_slashings_len: v.body.proposer_slashings.len(),
attester_slashings_len: v.body.attester_slashings.len(),
attestations_len: v.body.attestations.len(),
deposits_len: v.body.deposits.len(),
voluntary_exits_len: v.body.voluntary_exits.len(),
sync_committee_participants: v.body.sync_aggregate.num_active_participants,
block_number: v.body.execution_payload_header.block_number,
# TODO checksum hex? shortlog?
fee_recipient: to0xHex(v.body.execution_payload_header.fee_recipient.data),
bls_to_execution_changes_len: v.body.bls_to_execution_changes.len(),
blob_kzg_commitments_len: 0, # Deneb compat
)
func shortLog*(v: SignedBlindedBeaconBlock): auto =
(
blck: shortLog(v.message),
signature: shortLog(v.signature)
)

View File

@ -0,0 +1,27 @@
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [].}
import
chronos, presto/client,
".."/eth2_apis/[rest_types, eth2_rest_serialization]
export chronos, client, rest_types, eth2_rest_serialization
proc getHeaderDeneb*(slot: Slot,
parent_hash: Eth2Digest,
pubkey: ValidatorPubKey
): RestResponse[GetHeaderResponseDeneb] {.
rest, endpoint: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}",
meth: MethodGet, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/header.yaml
proc submitBlindedBlock*(body: deneb_mev.SignedBlindedBeaconBlock
): RestResponse[SubmitBlindedBlockResponseDeneb] {.
rest, endpoint: "/eth/v1/builder/blinded_blocks",
meth: MethodPost, connection: {Dedicated, Close}.}
## https://github.com/ethereum/builder-specs/blob/v0.3.0/apis/builder/blinded_blocks.yaml

View File

@ -369,7 +369,7 @@ proc verify_contribution_and_proof_signature*(
func compute_builder_signing_root(
fork: Fork,
msg: bellatrix_mev.BuilderBid | capella_mev.BuilderBid |
ValidatorRegistrationV1): Eth2Digest =
deneb_mev.BuilderBid | ValidatorRegistrationV1): Eth2Digest =
# Uses genesis fork version regardless
doAssert fork.current_version == fork.previous_version
@ -384,7 +384,8 @@ proc get_builder_signature*(
blsSign(privkey, signing_root.data)
proc verify_builder_signature*(
fork: Fork, msg: bellatrix_mev.BuilderBid | capella_mev.BuilderBid,
fork: Fork, msg: bellatrix_mev.BuilderBid | capella_mev.BuilderBid |
deneb_mev.BuilderBid,
pubkey: ValidatorPubKey | CookedPubKey, signature: SomeSig): bool =
let signing_root = compute_builder_signing_root(fork, msg)
blsVerify(pubkey, signing_root.data, signature)

View File

@ -192,3 +192,10 @@ proc unblindAndRouteBlockMEV*(
# local build process as a fallback, even in the event of some failure
# with the external buildernetwork.
return err("unblindAndRouteBlockMEV error")
proc unblindAndRouteBlockMEV*(
node: BeaconNode, payloadBuilderRestClient: RestClientRef,
blindedBlock: deneb_mev.SignedBlindedBeaconBlock):
Future[Result[Opt[BlockRef], string]] {.async.} =
debugRaiseAssert $denebImplementationMissing & ": makeBlindedBeaconBlockForHeadAndSlot"

View File

@ -23,6 +23,7 @@ import
eth/db/kvstore,
eth/p2p/discoveryv5/[protocol, enr],
web3/ethtypes,
kzg4844,
# Local modules
../spec/datatypes/[phase0, altair, bellatrix],
@ -85,8 +86,13 @@ declarePublicGauge(attached_validator_balance_total,
logScope: topics = "beacval"
type
BlobsBundle = tuple[blobs: deneb.Blobs,
kzgs: KzgCommitments,
proofs: seq[kzg_abi.KZGProof]]
ForkedBlockResult =
Result[tuple[blck: ForkedBeaconBlock, blockValue: Wei], string]
Result[tuple[blck: ForkedBeaconBlock,
blockValue: Wei,
blobsBundleOpt: Opt[BlobsBundle]], string]
BlindedBlockResult[SBBB] =
Result[tuple[blindedBlckPart: SBBB, blockValue: UInt256], string]
@ -432,8 +438,14 @@ proc makeBeaconBlockForHeadAndSlot*(
slot, head = shortLog(head), error
$error
var blobsBundleOpt = Opt.none(BlobsBundle)
when payload is deneb.ExecutionPayloadForSigning:
let bb: BlobsBundle = (blobs: payload.blobs,
kzgs: payload.kzgs,
proofs: payload.proofs)
blobsBundleOpt = Opt.some(bb)
return if blck.isOk:
ok((blck.get, payload.blockValue))
ok((blck.get, payload.blockValue, blobsBundleOpt))
else:
err(blck.error)
@ -452,11 +464,16 @@ proc makeBeaconBlockForHeadAndSlot*(
withdrawals_root = Opt.none(Eth2Digest))
proc getBlindedExecutionPayload[
EPH: bellatrix.ExecutionPayloadHeader | capella.ExecutionPayloadHeader](
EPH: bellatrix.ExecutionPayloadHeader | capella.ExecutionPayloadHeader |
deneb.ExecutionPayloadHeader](
node: BeaconNode, payloadBuilderClient: RestClientRef, slot: Slot,
executionBlockRoot: Eth2Digest, pubkey: ValidatorPubKey):
Future[BlindedBlockResult[EPH]] {.async.} =
when EPH is capella.ExecutionPayloadHeader:
when EPH is deneb.ExecutionPayloadHeader:
let blindedHeader = default(RestResponse[GetHeaderResponseDeneb])
debugRaiseAssert $denebImplementationMissing &
": makeBlindedBeaconBlockForHeadAndSlot"
elif EPH is capella.ExecutionPayloadHeader:
let blindedHeader = awaitWithTimeout(
payloadBuilderClient.getHeaderCapella(slot, executionBlockRoot, pubkey),
BUILDER_PROPOSAL_DELAY_TOLERANCE):
@ -558,11 +575,13 @@ proc blindedBlockCheckSlashingAndSign[T](
proc getUnsignedBlindedBeaconBlock[
T: bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock](
capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock](
node: BeaconNode, slot: Slot, validator: AttachedValidator,
validator_index: ValidatorIndex, forkedBlock: ForkedBeaconBlock,
executionPayloadHeader: bellatrix.ExecutionPayloadHeader |
capella.ExecutionPayloadHeader): Result[T, string] =
capella.ExecutionPayloadHeader |
deneb.ExecutionPayloadHeader): Result[T, string] =
withBlck(forkedBlock):
when consensusFork >= ConsensusFork.Deneb:
debugRaiseAssert $denebImplementationMissing & ": getUnsignedBlindedBeaconBlock"
@ -622,7 +641,8 @@ proc getBlindedBlockParts[EPH: ForkyExecutionPayloadHeader](
Opt.some executionPayloadHeader.get.blindedBlckPart.withdrawals_root
elif EPH is deneb.ExecutionPayloadHeader:
type PayloadType = deneb.ExecutionPayloadForSigning
let withdrawals_root = Opt.some executionPayloadHeader.get.withdrawals_root
let withdrawals_root =
Opt.some executionPayloadHeader.get.blindedBlckPart.withdrawals_root
else:
static: doAssert false
@ -656,7 +676,8 @@ proc getBlindedBlockParts[EPH: ForkyExecutionPayloadHeader](
proc getBuilderBid[
SBBB: bellatrix_mev.SignedBlindedBeaconBlock |
capella_mev.SignedBlindedBeaconBlock](
capella_mev.SignedBlindedBeaconBlock |
deneb_mev.SignedBlindedBeaconBlock](
node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef,
validator: AttachedValidator, slot: Slot, randao: ValidatorSig,
validator_index: ValidatorIndex):
@ -667,6 +688,8 @@ proc getBuilderBid[
type EPH = bellatrix.ExecutionPayloadHeader
elif SBBB is capella_mev.SignedBlindedBeaconBlock:
type EPH = capella.ExecutionPayloadHeader
elif SBBB is deneb_mev.SignedBlindedBeaconBlock:
type EPH = deneb.ExecutionPayloadHeader
else:
static: doAssert false
@ -784,13 +807,6 @@ proc makeBlindedBeaconBlockForHeadAndSlot*[
else:
return err("Attempt to create pre-Bellatrix blinded block")
# TODO remove BlobsSidecar; it's not in consensus specs anymore
type BlobsSidecar = object
beacon_block_root*: Eth2Digest
beacon_block_slot*: Slot
blobs*: Blobs
kzg_aggregated_proof*: kzg_abi.KzgProof
proc proposeBlockAux(
SBBB: typedesc, EPS: typedesc, node: BeaconNode,
validator: AttachedValidator, validator_index: ValidatorIndex,
@ -920,17 +936,6 @@ proc proposeBlockAux(
var forkedBlck = engineBlockFut.read.get().blck
withBlck(forkedBlck):
var blobs_sidecar = BlobsSidecar(
beacon_block_slot: slot,
)
when blck is deneb.BeaconBlock:
# TODO: The blobs_sidecar variable is not currently used.
# It could be initialized in makeBeaconBlockForHeadAndSlot
# where the required information is available.
# blobs_sidecar.blobs = forkedBlck.blobs
# blobs_sidecar.kzg_aggregated_proof = kzg_aggregated_proof
discard
let
blockRoot = hash_tree_root(blck)
signingRoot = compute_block_signing_root(
@ -940,7 +945,30 @@ proc proposeBlockAux(
.slashingProtection
.registerBlock(validator_index, validator.pubkey, slot, signingRoot)
blobs_sidecar.beacon_block_root = blockRoot
let blobSidecarsOpt =
when blck is deneb.BeaconBlock:
var sidecars: seq[BlobSidecar]
let bundle = engineBlockFut.read.get().blobsBundleOpt.get()
let (blobs, kzgs, proofs) = (bundle.blobs, bundle.kzgs, bundle.proofs)
for i in 0..<blobs.len:
var sidecar = BlobSidecar(
block_root: blockRoot,
index: BlobIndex(i),
slot: slot,
block_parent_root: blck.parent_root,
proposer_index: blck.proposer_index,
blob: blobs[i],
kzg_commitment: kzgs[i],
kzg_proof: proofs[i]
)
sidecars.add(sidecar)
Opt.some(sidecars)
elif blck is phase0.BeaconBlock or blck is altair.BeaconBlock or
blck is bellatrix.BeaconBlock or blck is capella.BeaconBlock:
Opt.none(seq[BlobSidecar])
else:
static: doAssert "Unknown BeaconBlock type"
if notSlashable.isErr:
warn "Slashing protection activated for block proposal",
blockRoot = shortLog(blockRoot), blck = shortLog(blck),
@ -974,13 +1002,35 @@ proc proposeBlockAux(
capella.SignedBeaconBlock(
message: blck, signature: signature, root: blockRoot)
elif blck is deneb.BeaconBlock:
# TODO: also route blobs
deneb.SignedBeaconBlock(message: blck, signature: signature, root: blockRoot)
deneb.SignedBeaconBlock(
message: blck, signature: signature, root: blockRoot)
else:
static: doAssert "Unknown SignedBeaconBlock type"
newBlockRef = (await node.router.routeSignedBeaconBlock(
signedBlock, Opt.none(SignedBlobSidecars))).valueOr:
return head # Errors logged in router
signedBlobs =
when blck is phase0.BeaconBlock or blck is altair.BeaconBlock or
blck is bellatrix.BeaconBlock or blck is capella.BeaconBlock:
Opt.none(SignedBlobSidecars)
elif blck is deneb.BeaconBlock:
var signed: seq[SignedBlobSidecar]
let blobSidecars = blobSidecarsOpt.get()
for i in 0..<blobs.len:
let res = validator.getBlobSignature(fork, genesis_validators_root,
slot, blobSidecars[i])
if res.isErr():
warn "Unable to sign blob",
reason = res.error()
return
let signature = res.get()
signed.add(deneb.SignedBlobSidecar(
message: blobSidecars[i],
signature: signature))
Opt.some(signed)
else:
static: doAssert "Unknown SignedBeaconBlock type"
newBlockRef =
(await node.router.routeSignedBeaconBlock(signedBlock, signedBlobs)).valueOr:
return head # Errors logged in router
if newBlockRef.isNone():
return head # Validation errors logged in router
@ -1028,7 +1078,7 @@ proc proposeBlock(node: BeaconNode,
if slot.epoch >= node.dag.cfg.DENEB_FORK_EPOCH:
debugRaiseAssert $denebImplementationMissing & ": proposeBlock"
proposeBlockContinuation(
capella_mev.SignedBlindedBeaconBlock, deneb.ExecutionPayloadForSigning)
deneb_mev.SignedBlindedBeaconBlock, deneb.ExecutionPayloadForSigning)
elif slot.epoch >= node.dag.cfg.CAPELLA_FORK_EPOCH:
proposeBlockContinuation(
capella_mev.SignedBlindedBeaconBlock, capella.ExecutionPayloadForSigning)

View File

@ -435,14 +435,16 @@ proc getBlockSignature*(v: AttachedValidator, fork: Fork,
block_root: Eth2Digest,
blck: ForkedBeaconBlock | ForkedBlindedBeaconBlock |
bellatrix_mev.BlindedBeaconBlock |
capella_mev.BlindedBeaconBlock
capella_mev.BlindedBeaconBlock |
deneb_mev.BlindedBeaconBlock
): Future[SignatureResult] {.async.} =
type SomeBlockBody =
bellatrix.BeaconBlockBody |
capella.BeaconBlockBody |
deneb.BeaconBlockBody |
bellatrix_mev.BlindedBeaconBlockBody |
capella_mev.BlindedBeaconBlockBody
capella_mev.BlindedBeaconBlockBody |
deneb_mev.BlindedBeaconBlockBody
template blockPropertiesProofs(blockBody: SomeBlockBody,
forkIndexField: untyped): seq[Web3SignerMerkleProof] =
@ -538,9 +540,20 @@ proc getBlockSignature*(v: AttachedValidator, fork: Fork,
Web3SignerForkedBeaconBlock(kind: ConsensusFork.Capella,
data: blck.toBeaconBlockHeader),
proofs)
elif blck is deneb_mev.BlindedBeaconBlock:
case v.data.remoteType
of RemoteSignerType.Web3Signer:
Web3SignerRequest.init(fork, genesis_validators_root,
Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb,
data: blck.toBeaconBlockHeader))
of RemoteSignerType.VerifyingWeb3Signer:
let proofs = blockPropertiesProofs(
blck.body, denebIndex)
Web3SignerRequest.init(fork, genesis_validators_root,
Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb,
data: blck.toBeaconBlockHeader),
proofs)
else:
# There should be a deneb_mev module just like the ones above
discard denebImplementationMissing
case blck.kind
of ConsensusFork.Phase0, ConsensusFork.Altair:
return SignatureResult.err("Invalid beacon block fork version")