From 99264d7507b14d412d415ead3fc8826fd9dd1a90 Mon Sep 17 00:00:00 2001 From: henridf Date: Fri, 30 Jun 2023 09:39:41 +0200 Subject: [PATCH] Local validator proposals (#5137) * Implement local validator block proposals * Address review feedback * Fix rebase issue --- beacon_chain/el/el_manager.nim | 1 + beacon_chain/spec/datatypes/deneb.nim | 1 + beacon_chain/spec/eth2_apis/rest_types.nim | 6 +- beacon_chain/spec/forks.nim | 7 +- beacon_chain/spec/mev/deneb_mev.nim | 87 +++++++++++++ .../spec/mev/rest_deneb_mev_calls.nim | 27 ++++ beacon_chain/spec/signatures.nim | 5 +- .../validators/message_router_mev.nim | 7 ++ beacon_chain/validators/validator_duties.nim | 116 +++++++++++++----- beacon_chain/validators/validator_pool.nim | 21 +++- 10 files changed, 234 insertions(+), 44 deletions(-) create mode 100644 beacon_chain/spec/mev/deneb_mev.nim create mode 100644 beacon_chain/spec/mev/rest_deneb_mev_calls.nim diff --git a/beacon_chain/el/el_manager.nim b/beacon_chain/el/el_manager.nim index 4f8e77908..b60b13938 100644 --- a/beacon_chain/el/el_manager.nim +++ b/beacon_chain/el/el_manager.nim @@ -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) ) diff --git a/beacon_chain/spec/datatypes/deneb.nim b/beacon_chain/spec/datatypes/deneb.nim index a16ed371d..2edfead0d 100644 --- a/beacon_chain/spec/datatypes/deneb.nim +++ b/beacon_chain/spec/datatypes/deneb.nim @@ -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 diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index 58e7e473f..49ab4111c 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -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]] diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim index b6fe225ae..b4fec7743 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -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, diff --git a/beacon_chain/spec/mev/deneb_mev.nim b/beacon_chain/spec/mev/deneb_mev.nim new file mode 100644 index 000000000..22e6d2f3b --- /dev/null +++ b/beacon_chain/spec/mev/deneb_mev.nim @@ -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) + ) diff --git a/beacon_chain/spec/mev/rest_deneb_mev_calls.nim b/beacon_chain/spec/mev/rest_deneb_mev_calls.nim new file mode 100644 index 000000000..0414f8616 --- /dev/null +++ b/beacon_chain/spec/mev/rest_deneb_mev_calls.nim @@ -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 diff --git a/beacon_chain/spec/signatures.nim b/beacon_chain/spec/signatures.nim index b028b621d..d0b3772c7 100644 --- a/beacon_chain/spec/signatures.nim +++ b/beacon_chain/spec/signatures.nim @@ -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) diff --git a/beacon_chain/validators/message_router_mev.nim b/beacon_chain/validators/message_router_mev.nim index 7715bd73c..be2edcff6 100644 --- a/beacon_chain/validators/message_router_mev.nim +++ b/beacon_chain/validators/message_router_mev.nim @@ -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" diff --git a/beacon_chain/validators/validator_duties.nim b/beacon_chain/validators/validator_duties.nim index 1a30a49aa..ddd671998 100644 --- a/beacon_chain/validators/validator_duties.nim +++ b/beacon_chain/validators/validator_duties.nim @@ -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..= 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) diff --git a/beacon_chain/validators/validator_pool.nim b/beacon_chain/validators/validator_pool.nim index 5e4bedffe..fb5e78e37 100644 --- a/beacon_chain/validators/validator_pool.nim +++ b/beacon_chain/validators/validator_pool.nim @@ -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")