diff --git a/packages/api/src/beacon/routes/validator.ts b/packages/api/src/beacon/routes/validator.ts index 38212929d1db..8d0045a581ea 100644 --- a/packages/api/src/beacon/routes/validator.ts +++ b/packages/api/src/beacon/routes/validator.ts @@ -16,6 +16,7 @@ import { ValidatorIndex, RootHex, StringType, + Wei, } from "@lodestar/types"; import { RoutesData, @@ -24,6 +25,7 @@ import { ContainerData, Schema, WithVersion, + WithBlockValue, reqOnlyBody, ReqSerializers, jsonType, @@ -147,7 +149,11 @@ export type Api = { * @returns any Success response * @throws ApiError */ - produceBlock(slot: Slot, randaoReveal: BLSSignature, graffiti: string): Promise<{data: allForks.BeaconBlock}>; + produceBlock( + slot: Slot, + randaoReveal: BLSSignature, + graffiti: string + ): Promise<{data: allForks.BeaconBlock; blockValue: Wei}>; /** * Requests a beacon node to produce a valid block, which can then be signed by a validator. @@ -163,13 +169,13 @@ export type Api = { slot: Slot, randaoReveal: BLSSignature, graffiti: string - ): Promise<{data: allForks.BeaconBlock; version: ForkName}>; + ): Promise<{data: allForks.BeaconBlock; version: ForkName; blockValue: Wei}>; produceBlindedBlock( slot: Slot, randaoReveal: BLSSignature, graffiti: string - ): Promise<{data: allForks.BlindedBeaconBlock; version: ForkName}>; + ): Promise<{data: allForks.BlindedBeaconBlock; version: ForkName; blockValue: Wei}>; /** * Produce an attestation data @@ -441,14 +447,16 @@ export function getReturnTypes(): ReturnTypes { getAttesterDuties: WithDependentRootExecutionOptimistic(ArrayOf(AttesterDuty)), getProposerDuties: WithDependentRootExecutionOptimistic(ArrayOf(ProposerDuty)), getSyncCommitteeDuties: ContainerDataExecutionOptimistic(ArrayOf(SyncDuty)), - produceBlock: ContainerData(ssz.phase0.BeaconBlock), - produceBlockV2: WithVersion((fork: ForkName) => ssz[fork].BeaconBlock), - produceBlindedBlock: WithVersion((fork: ForkName) => { - if (fork === ForkName.phase0 || fork === ForkName.altair) { - throw Error(`No BlindedBlock for fork ${fork} previous to bellatrix`); - } - return ssz[fork].BlindedBeaconBlock; - }), + produceBlock: WithBlockValue(ContainerData(ssz.phase0.BeaconBlock)), + produceBlockV2: WithBlockValue(WithVersion((fork: ForkName) => ssz[fork].BeaconBlock)), + produceBlindedBlock: WithBlockValue( + WithVersion((fork: ForkName) => { + if (fork === ForkName.phase0 || fork === ForkName.altair) { + throw Error(`No BlindedBlock for fork ${fork} previous to bellatrix`); + } + return ssz[fork].BlindedBeaconBlock; + }) + ), produceAttestationData: ContainerData(ssz.phase0.AttestationData), produceSyncCommitteeContribution: ContainerData(ssz.altair.SyncCommitteeContribution), getAggregatedAttestation: ContainerData(ssz.phase0.Attestation), diff --git a/packages/api/src/utils/types.ts b/packages/api/src/utils/types.ts index 0056f2a1aafa..da719ff48b8e 100644 --- a/packages/api/src/utils/types.ts +++ b/packages/api/src/utils/types.ts @@ -195,6 +195,22 @@ export function WithExecutionOptimistic( }; } +/** + * SSZ factory helper to wrap an existing type with `{blockValue: Wei}` + */ +export function WithBlockValue(type: TypeJson): TypeJson { + return { + toJson: ({blockValue, ...data}) => ({ + ...(type.toJson((data as unknown) as T) as Record), + block_value: blockValue.toString(), + }), + fromJson: ({block_value, ...data}: T & {block_value: string}) => ({ + ...type.fromJson(data), + blockValue: BigInt(block_value), + }), + }; +} + type JsonCase = "snake" | "constant" | "camel" | "param" | "header" | "pascal" | "dot" | "notransform"; /** Helper to only translate casing */ diff --git a/packages/api/test/unit/beacon/testData/validator.ts b/packages/api/test/unit/beacon/testData/validator.ts index 49f7c2f349db..92a061eeabc0 100644 --- a/packages/api/test/unit/beacon/testData/validator.ts +++ b/packages/api/test/unit/beacon/testData/validator.ts @@ -44,15 +44,19 @@ export const testData: GenericServerTestCases = { }, produceBlock: { args: [32000, randaoReveal, graffiti], - res: {data: ssz.phase0.BeaconBlock.defaultValue()}, + res: {data: ssz.phase0.BeaconBlock.defaultValue(), blockValue: ssz.Wei.defaultValue()}, }, produceBlockV2: { args: [32000, randaoReveal, graffiti], - res: {data: ssz.altair.BeaconBlock.defaultValue(), version: ForkName.altair}, + res: {data: ssz.altair.BeaconBlock.defaultValue(), version: ForkName.altair, blockValue: ssz.Wei.defaultValue()}, }, produceBlindedBlock: { args: [32000, randaoReveal, graffiti], - res: {data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(), version: ForkName.bellatrix}, + res: { + data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(), + version: ForkName.bellatrix, + blockValue: ssz.Wei.defaultValue(), + }, }, produceAttestationData: { args: [2, 32000], diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 4584e2573335..0626d532865a 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -203,13 +203,13 @@ export function getValidatorApi({chain, config, logger, metrics, network, sync}: chain.forkChoice.updateHead(); timer = metrics?.blockProductionTime.startTimer(); - const block = await chain.produceBlindedBlock({ + const {block, blockValue} = await chain.produceBlindedBlock({ slot, randaoReveal, graffiti: toGraffitiBuffer(graffiti || ""), }); metrics?.blockProductionSuccess.inc(); - return {data: block, version: config.getForkName(block.slot)}; + return {data: block, version: config.getForkName(block.slot), blockValue}; } finally { if (timer) timer(); } @@ -233,14 +233,14 @@ export function getValidatorApi({chain, config, logger, metrics, network, sync}: chain.recomputeForkChoiceHead(); timer = metrics?.blockProductionTime.startTimer(); - const block = await chain.produceBlock({ + const {block, blockValue} = await chain.produceBlock({ slot, randaoReveal, graffiti: toGraffitiBuffer(graffiti || ""), }); metrics?.blockProductionSuccess.inc(); metrics?.blockProductionNumAggregated.observe(block.body.attestations.length); - return {data: block, version: config.getForkName(block.slot)}; + return {data: block, version: config.getForkName(block.slot), blockValue}; } finally { if (timer) timer(); } diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 47d813bf0129..ab86b36b71c7 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -12,7 +12,7 @@ import { PubkeyIndexMap, } from "@lodestar/state-transition"; import {IBeaconConfig} from "@lodestar/config"; -import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, eip4844} from "@lodestar/types"; +import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, eip4844, Wei} from "@lodestar/types"; import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; import {ILogger, pruneSetToMax, toHex} from "@lodestar/utils"; @@ -353,25 +353,27 @@ export class BeaconChain implements IBeaconChain { return await this.db.block.get(fromHexString(block.blockRoot)); } - produceBlock(blockAttributes: BlockAttributes): Promise { + produceBlock(blockAttributes: BlockAttributes): Promise<{block: allForks.BeaconBlock; blockValue: Wei}> { return this.produceBlockWrapper(BlockType.Full, blockAttributes); } - produceBlindedBlock(blockAttributes: BlockAttributes): Promise { + produceBlindedBlock( + blockAttributes: BlockAttributes + ): Promise<{block: allForks.BlindedBeaconBlock; blockValue: Wei}> { return this.produceBlockWrapper(BlockType.Blinded, blockAttributes); } async produceBlockWrapper( blockType: T, {randaoReveal, graffiti, slot}: BlockAttributes - ): Promise> { + ): Promise<{block: AssembledBlockType; blockValue: Wei}> { const head = this.forkChoice.getHead(); const state = await this.regen.getBlockSlotState(head.blockRoot, slot, RegenCaller.produceBlock); const parentBlockRoot = fromHexString(head.blockRoot); const proposerIndex = state.epochCtx.getBeaconProposer(slot); const proposerPubKey = state.epochCtx.index2pubkey[proposerIndex].toBytes(); - const {body, blobs} = await produceBlockBody.call(this, blockType, state, { + const {body, blobs, blockValue} = await produceBlockBody.call(this, blockType, state, { randaoReveal, graffiti, slot, @@ -410,7 +412,7 @@ export class BeaconChain implements IBeaconChain { ); } - return block; + return {block, blockValue}; } /** diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 945b884735ff..8242bf677ee3 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -1,4 +1,4 @@ -import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, eip4844} from "@lodestar/types"; +import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, eip4844, Wei} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {IBeaconConfig} from "@lodestar/config"; import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz"; @@ -113,8 +113,8 @@ export interface IBeaconChain { getBlobsSidecar(beaconBlock: eip4844.BeaconBlock): eip4844.BlobsSidecar; - produceBlock(blockAttributes: BlockAttributes): Promise; - produceBlindedBlock(blockAttributes: BlockAttributes): Promise; + produceBlock(blockAttributes: BlockAttributes): Promise<{block: allForks.BeaconBlock; blockValue: Wei}>; + produceBlindedBlock(blockAttributes: BlockAttributes): Promise<{block: allForks.BlindedBeaconBlock; blockValue: Wei}>; /** Process a block until complete */ processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise; diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index d828e41b924c..260a41ac4a8a 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -12,6 +12,7 @@ import { BLSSignature, capella, eip4844, + Wei, } from "@lodestar/types"; import { CachedBeaconStateAllForks, @@ -89,11 +90,12 @@ export async function produceBlockBody( proposerIndex: ValidatorIndex; proposerPubKey: BLSPubkey; } -): Promise<{body: AssembledBodyType; blobs: BlobsResult}> { +): Promise<{body: AssembledBodyType; blobs: BlobsResult; blockValue: Wei}> { // Type-safe for blobs variable. Translate 'null' value into 'preEIP4844' enum // TODO: Not ideal, but better than just using null. // TODO: Does not guarantee that preEIP4844 enum goes with a preEIP4844 block let blobsResult: BlobsResult; + let blockValue: Wei; // TODO: // Iterate through the naive aggregation pool and ensure all the attestations from there @@ -170,8 +172,9 @@ export async function produceBlockBody( proposerPubKey ); (blockBody as allForks.BlindedBeaconBlockBody).executionPayloadHeader = builderRes.header; + blockValue = builderRes.blockValue; if (ForkSeq[fork] >= ForkSeq.eip4844) { - const {blobKzgCommitments} = builderRes as {blobKzgCommitments: eip4844.BlobKzgCommitments}; + const {blobKzgCommitments} = builderRes; if (blobKzgCommitments === undefined) { throw Error(`Invalid builder getHeader response for fork=${fork}, missing blobKzgCommitments`); } @@ -203,6 +206,7 @@ export async function produceBlockBody( fork ].ExecutionPayload.defaultValue(); blobsResult = {type: BlobsResultType.preEIP4844}; + blockValue = BigInt(0); } else { const {prepType, payloadId} = prepareRes; if (prepType !== PayloadPreparationType.Cached) { @@ -214,12 +218,14 @@ export async function produceBlockBody( await sleep(PAYLOAD_GENERATION_TIME_MS); } - const payload = await this.executionEngine.getPayload(fork, payloadId); - (blockBody as allForks.ExecutionBlockBody).executionPayload = payload; + const engineRes = await this.executionEngine.getPayload(fork, payloadId); + const {executionPayload} = engineRes; + (blockBody as allForks.ExecutionBlockBody).executionPayload = executionPayload; + blockValue = engineRes.blockValue; const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime); this.metrics?.blockPayload.payloadFetchedTime.observe({prepType}, fetchedTime); - if (payload.transactions.length === 0) { + if (executionPayload.transactions.length === 0) { this.metrics?.blockPayload.emptyPayloads.inc({prepType}); } @@ -231,14 +237,14 @@ export async function produceBlockBody( const blobsBundle = await this.executionEngine.getBlobsBundle(payloadId); // Sanity check consistency between getPayload() and getBlobsBundle() - const blockHash = toHex(payload.blockHash); + const blockHash = toHex(executionPayload.blockHash); if (blobsBundle.blockHash !== blockHash) { throw Error(`blobsBundle incorrect blockHash ${blobsBundle.blockHash} != ${blockHash}`); } // Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions if (this.opts.sanityCheckExecutionEngineBlobs) { - validateBlobsAndKzgCommitments(payload, blobsBundle); + validateBlobsAndKzgCommitments(executionPayload, blobsBundle); } (blockBody as eip4844.BeaconBlockBody).blobKzgCommitments = blobsBundle.kzgs; @@ -262,6 +268,7 @@ export async function produceBlockBody( fork ].ExecutionPayload.defaultValue(); blobsResult = {type: BlobsResultType.preEIP4844}; + blockValue = BigInt(0); } else { // since merge transition is complete, we need a valid payload even if with an // empty (transactions) one. defaultValue isn't gonna cut it! @@ -271,6 +278,7 @@ export async function produceBlockBody( } } else { blobsResult = {type: BlobsResultType.preEIP4844}; + blockValue = BigInt(0); } if (ForkSeq[fork] >= ForkSeq.capella) { @@ -278,7 +286,7 @@ export async function produceBlockBody( (blockBody as capella.BeaconBlockBody).blsToExecutionChanges = blsToExecutionChanges; } - return {body: blockBody as AssembledBodyType, blobs: blobsResult}; + return {body: blockBody as AssembledBodyType, blobs: blobsResult, blockValue}; } /** @@ -376,7 +384,11 @@ async function prepareExecutionPayloadHeader( fork: ForkExecution, state: CachedBeaconStateBellatrix, proposerPubKey: BLSPubkey -): Promise<{header: allForks.ExecutionPayloadHeader; blobKzgCommitments?: eip4844.BlobKzgCommitments}> { +): Promise<{ + header: allForks.ExecutionPayloadHeader; + blockValue: Wei; + blobKzgCommitments?: eip4844.BlobKzgCommitments; +}> { if (!chain.executionBuilder) { throw Error("executionBuilder required"); } diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 5ce8411c08f8..d4055e971e86 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -1,4 +1,4 @@ -import {allForks, bellatrix, Slot, Root, BLSPubkey, ssz, eip4844} from "@lodestar/types"; +import {allForks, bellatrix, Slot, Root, BLSPubkey, ssz, eip4844, Wei} from "@lodestar/types"; import {IChainForkConfig} from "@lodestar/config"; import {getClient, Api as BuilderApi} from "@lodestar/api/builder"; import {byteArrayEquals, toHexString} from "@chainsafe/ssz"; @@ -57,9 +57,15 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey - ): Promise<{header: allForks.ExecutionPayloadHeader; blobKzgCommitments?: eip4844.BlobKzgCommitments}> { + ): Promise<{ + header: allForks.ExecutionPayloadHeader; + blockValue: Wei; + blobKzgCommitments?: eip4844.BlobKzgCommitments; + }> { const {data: signedBid} = await this.api.getHeader(slot, parentHash, proposerPubKey); - return signedBid.message; + const {header, value: blockValue} = signedBid.message; + const {blobKzgCommitments} = signedBid.message as {blobKzgCommitments?: eip4844.BlobKzgCommitments}; + return {header, blockValue, blobKzgCommitments}; } async submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise { diff --git a/packages/beacon-node/src/execution/builder/interface.ts b/packages/beacon-node/src/execution/builder/interface.ts index 8be1a0853d10..19f03b7f6367 100644 --- a/packages/beacon-node/src/execution/builder/interface.ts +++ b/packages/beacon-node/src/execution/builder/interface.ts @@ -1,4 +1,4 @@ -import {allForks, bellatrix, Root, Slot, BLSPubkey, eip4844} from "@lodestar/types"; +import {allForks, bellatrix, Root, Slot, BLSPubkey, eip4844, Wei} from "@lodestar/types"; export interface IExecutionBuilder { /** @@ -15,7 +15,11 @@ export interface IExecutionBuilder { slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey - ): Promise<{header: allForks.ExecutionPayloadHeader; blobKzgCommitments?: eip4844.BlobKzgCommitments}>; + ): Promise<{ + header: allForks.ExecutionPayloadHeader; + blockValue: Wei; + blobKzgCommitments?: eip4844.BlobKzgCommitments; + }>; submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise; submitBlindedBlockV2( signedBlock: allForks.SignedBlindedBeaconBlock diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index 7c6f14248b36..3171c1cd69ec 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -1,4 +1,4 @@ -import {RootHex, allForks} from "@lodestar/types"; +import {RootHex, allForks, Wei} from "@lodestar/types"; import {SLOTS_PER_EPOCH, ForkName, ForkSeq} from "@lodestar/params"; import {ErrorJsonRpcResponse, HttpRpcError} from "../../eth1/provider/jsonRpcHttpClient.js"; @@ -284,7 +284,10 @@ export class ExecutionEngineHttp implements IExecutionEngine { * 2. The call MUST be responded with 5: Unavailable payload error if the building process identified by the payloadId doesn't exist. * 3. Client software MAY stop the corresponding building process after serving this call. */ - async getPayload(fork: ForkName, payloadId: PayloadId): Promise { + async getPayload( + fork: ForkName, + payloadId: PayloadId + ): Promise<{executionPayload: allForks.ExecutionPayload; blockValue: Wei}> { const method = ForkSeq[fork] >= ForkSeq.eip4844 ? "engine_getPayloadV3" diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index 205fd8b2a648..6a996d49bb3f 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -1,6 +1,6 @@ import {ForkName} from "@lodestar/params"; import {KZGCommitment, Blob} from "@lodestar/types/eip4844"; -import {RootHex, allForks, capella} from "@lodestar/types"; +import {RootHex, allForks, capella, Wei} from "@lodestar/types"; import {DATA, QUANTITY} from "../../eth1/provider/utils.js"; import {PayloadIdCache, PayloadId, WithdrawalV1} from "./payloadIdCache.js"; @@ -115,7 +115,10 @@ export interface IExecutionEngine { * Required for block producing * https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/validator.md#get_payload */ - getPayload(fork: ForkName, payloadId: PayloadId): Promise; + getPayload( + fork: ForkName, + payloadId: PayloadId + ): Promise<{executionPayload: allForks.ExecutionPayload; blockValue: Wei}>; /** * "After retrieving the execution payload from the execution engine as specified in Bellatrix, diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index 14b9bbf696a0..c10c3ccb92fa 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -1,4 +1,4 @@ -import {allForks, capella, eip4844} from "@lodestar/types"; +import {allForks, capella, eip4844, Wei} from "@lodestar/types"; import { BYTES_PER_LOGS_BLOOM, FIELD_ELEMENTS_PER_BLOB, @@ -176,10 +176,22 @@ export function hasBlockValue(response: ExecutionPayloadResponseV2): response is return (response as ExecutionPayloadRpcWithBlockValue).blockValue !== undefined; } -export function parseExecutionPayload(fork: ForkName, response: ExecutionPayloadResponseV2): allForks.ExecutionPayload { - const data: ExecutionPayloadRpc = hasBlockValue(response) ? response.executionPayload : response; +export function parseExecutionPayload( + fork: ForkName, + response: ExecutionPayloadResponseV2 +): {executionPayload: allForks.ExecutionPayload; blockValue: Wei} { + let data: ExecutionPayloadRpc; + let blockValue: Wei; + if (hasBlockValue(response)) { + blockValue = quantityToBigint(response.blockValue); + data = response.executionPayload; + } else { + data = response; + // Just set it to zero as default + blockValue = BigInt(0); + } - const payload = { + const executionPayload = { parentHash: dataToBytes(data.parentHash, 32), feeRecipient: dataToBytes(data.feeRecipient, 20), stateRoot: dataToBytes(data.stateRoot, 32), @@ -201,23 +213,23 @@ export function parseExecutionPayload(fork: ForkName, response: ExecutionPayload // Geth can also reply with null if (withdrawals == null) { throw Error( - `withdrawals missing for ${fork} >= capella executionPayload number=${payload.blockNumber} hash=${data.blockHash}` + `withdrawals missing for ${fork} >= capella executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}` ); } - (payload as capella.ExecutionPayload).withdrawals = withdrawals.map((w) => deserializeWithdrawal(w)); + (executionPayload as capella.ExecutionPayload).withdrawals = withdrawals.map((w) => deserializeWithdrawal(w)); } // EIP-4844 adds excessDataGas to the ExecutionPayload if (ForkSeq[fork] >= ForkSeq.eip4844) { if (data.excessDataGas == null) { throw Error( - `excessDataGas missing for ${fork} >= eip4844 executionPayload number=${payload.blockNumber} hash=${data.blockHash}` + `excessDataGas missing for ${fork} >= eip4844 executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}` ); } - (payload as eip4844.ExecutionPayload).excessDataGas = quantityToBigint(data.excessDataGas); + (executionPayload as eip4844.ExecutionPayload).excessDataGas = quantityToBigint(data.excessDataGas); } - return payload; + return {executionPayload, blockValue}; } export function serializePayloadAttributes(data: PayloadAttributes): PayloadAttributesRpc { diff --git a/packages/beacon-node/test/sim/merge-interop.test.ts b/packages/beacon-node/test/sim/merge-interop.test.ts index b74e5380aab8..fab89c6c87a6 100644 --- a/packages/beacon-node/test/sim/merge-interop.test.ts +++ b/packages/beacon-node/test/sim/merge-interop.test.ts @@ -144,7 +144,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayloadV1","params":["0xa247243752eb10b4"],"id":67}' http://localhost:8550 **/ - const payload = await executionEngine.getPayload(ForkName.bellatrix, payloadId); + const {executionPayload: payload} = await executionEngine.getPayload(ForkName.bellatrix, payloadId); if (TX_SCENARIOS.includes("simple")) { if (payload.transactions.length !== 1) throw new Error("Expected a simple transaction to be in the fetched payload"); diff --git a/packages/beacon-node/test/sim/withdrawal-interop.test.ts b/packages/beacon-node/test/sim/withdrawal-interop.test.ts index 7c76a8cf5149..284a8082910c 100644 --- a/packages/beacon-node/test/sim/withdrawal-interop.test.ts +++ b/packages/beacon-node/test/sim/withdrawal-interop.test.ts @@ -160,7 +160,8 @@ describe("executionEngine / ExecutionEngineHttp", function () { if (!payloadId) throw Error("InvalidPayloadId"); // 2. Get the payload - const payload = await executionEngine.getPayload(ForkName.capella, payloadId); + const payloadAndBlockValue = await executionEngine.getPayload(ForkName.capella, payloadId); + const payload = payloadAndBlockValue.executionPayload; const blockHash = toHexString(payload.blockHash); const expectedBlockHash = "0x64707e5574d14103a7f583e702f09e68ca1eb334e8eb0632a4272efe54f2fc7c"; if (blockHash !== expectedBlockHash) { diff --git a/packages/beacon-node/test/unit/executionEngine/http.test.ts b/packages/beacon-node/test/unit/executionEngine/http.test.ts index 1af2093e54ab..26cdbb023c2e 100644 --- a/packages/beacon-node/test/unit/executionEngine/http.test.ts +++ b/packages/beacon-node/test/unit/executionEngine/http.test.ts @@ -75,7 +75,8 @@ describe("ExecutionEngine / http", () => { }; returnValue = response; - const payload = await executionEngine.getPayload(ForkName.bellatrix, "0x0"); + const payloadAndBlockValue = await executionEngine.getPayload(ForkName.bellatrix, "0x0"); + const payload = payloadAndBlockValue.executionPayload; expect(serializeExecutionPayload(ForkName.bellatrix, payload)).to.deep.equal( response.result, @@ -120,7 +121,7 @@ describe("ExecutionEngine / http", () => { const {status} = await executionEngine.notifyNewPayload( ForkName.bellatrix, - parseExecutionPayload(ForkName.bellatrix, request.params[0]) + parseExecutionPayload(ForkName.bellatrix, request.params[0]).executionPayload ); expect(status).to.equal("VALID", "Wrong returned execute payload result"); diff --git a/packages/beacon-node/test/utils/mocks/chain/chain.ts b/packages/beacon-node/test/utils/mocks/chain/chain.ts index 5aae4e53c7d1..fb7843195dcb 100644 --- a/packages/beacon-node/test/utils/mocks/chain/chain.ts +++ b/packages/beacon-node/test/utils/mocks/chain/chain.ts @@ -1,7 +1,7 @@ import sinon from "sinon"; import {CompositeTypeAny, toHexString, TreeView} from "@chainsafe/ssz"; -import {phase0, allForks, UintNum64, Root, Slot, ssz, Uint16, UintBn64, RootHex, eip4844} from "@lodestar/types"; +import {phase0, allForks, UintNum64, Root, Slot, ssz, Uint16, UintBn64, RootHex, eip4844, Wei} from "@lodestar/types"; import {IBeaconConfig} from "@lodestar/config"; import {BeaconStateAllForks, CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {CheckpointWithHex, IForkChoice, ProtoBlock, ExecutionStatus, AncestorStatus} from "@lodestar/fork-choice"; @@ -173,10 +173,12 @@ export class MockBeaconChain implements IBeaconChain { throw Error("Not implemented"); } - async produceBlock(_blockAttributes: BlockAttributes): Promise { + async produceBlock(_blockAttributes: BlockAttributes): Promise<{block: allForks.BeaconBlock; blockValue: Wei}> { throw Error("Not implemented"); } - async produceBlindedBlock(_blockAttributes: BlockAttributes): Promise { + async produceBlindedBlock( + _blockAttributes: BlockAttributes + ): Promise<{block: allForks.BlindedBeaconBlock; blockValue: Wei}> { throw Error("Not implemented"); } diff --git a/packages/types/src/primitive/sszTypes.ts b/packages/types/src/primitive/sszTypes.ts index 868fb0bfa92c..39a62c2c9b33 100644 --- a/packages/types/src/primitive/sszTypes.ts +++ b/packages/types/src/primitive/sszTypes.ts @@ -50,6 +50,7 @@ export const SubcommitteeIndex = UintNum64; export const ValidatorIndex = UintNum64; export const WithdrawalIndex = UintNum64; export const Gwei = UintBn64; +export const Wei = UintBn256; export const Root = new ByteVectorType(32); export const Version = Bytes4; diff --git a/packages/types/src/primitive/types.ts b/packages/types/src/primitive/types.ts index 5bde0bbe45af..7d4349d75e49 100644 --- a/packages/types/src/primitive/types.ts +++ b/packages/types/src/primitive/types.ts @@ -30,6 +30,7 @@ export type SubcommitteeIndex = UintNum64; export type ValidatorIndex = UintNum64; export type WithdrawalIndex = UintNum64; export type Gwei = UintBn64; +export type Wei = UintBn256; export type Root = Bytes32; export type Version = Bytes4; export type DomainType = Bytes4; diff --git a/packages/validator/src/services/block.ts b/packages/validator/src/services/block.ts index aa96338ded7c..40057405a19b 100644 --- a/packages/validator/src/services/block.ts +++ b/packages/validator/src/services/block.ts @@ -1,4 +1,4 @@ -import {BLSPubkey, Slot, BLSSignature, allForks, bellatrix, capella, isBlindedBeaconBlock} from "@lodestar/types"; +import {BLSPubkey, Slot, BLSSignature, allForks, bellatrix, capella, isBlindedBeaconBlock, Wei} from "@lodestar/types"; import {IChainForkConfig} from "@lodestar/config"; import {ForkName} from "@lodestar/params"; import {extendError, prettyBytes} from "@lodestar/utils"; @@ -137,14 +137,15 @@ export class BlockProposingService { const fullBlock = await fullBlockPromise; // A metric on the choice between blindedBlock and normal block can be applied + // TODO: compare the blockValue that has been obtained in each of full or blinded block if (blindedBlock) { - const debugLogCtx = {source: "builder"}; + const debugLogCtx = {source: "builder", blockValue: blindedBlock.blockValue.toString()}; return {...blindedBlock, debugLogCtx}; } else { - const debugLogCtx = {source: "engine"}; if (!fullBlock) { throw Error("Failed to produce engine or builder block"); } + const debugLogCtx = {source: "engine", blockValue: fullBlock.blockValue.toString()}; const blockFeeRecipient = (fullBlock.data as bellatrix.BeaconBlock).body.executionPayload?.feeRecipient; const feeRecipient = blockFeeRecipient !== undefined ? toHexString(blockFeeRecipient) : undefined; if (feeRecipient !== undefined) { @@ -177,7 +178,7 @@ export class BlockProposingService { slot, randaoReveal, graffiti - ): Promise<{data: allForks.BeaconBlock}> => { + ): Promise<{data: allForks.BeaconBlock; blockValue: Wei}> => { switch (this.config.getForkName(slot)) { case ForkName.phase0: return this.api.validator.produceBlock(slot, randaoReveal, graffiti); diff --git a/packages/validator/test/unit/services/block.test.ts b/packages/validator/test/unit/services/block.test.ts index 821a3f19e1d7..ce6e55299efb 100644 --- a/packages/validator/test/unit/services/block.test.ts +++ b/packages/validator/test/unit/services/block.test.ts @@ -48,7 +48,7 @@ describe("BlockDutiesService", function () { const signedBlock = ssz.phase0.SignedBeaconBlock.defaultValue(); validatorStore.signRandao.resolves(signedBlock.message.body.randaoReveal); validatorStore.signBlock.callsFake(async (_, block) => ({message: block, signature: signedBlock.signature})); - api.validator.produceBlock.resolves({data: signedBlock.message}); + api.validator.produceBlock.resolves({data: signedBlock.message, blockValue: ssz.Wei.defaultValue()}); api.beacon.publishBlock.resolves(); // Trigger block production for slot 1