Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add consensus_block_value to produceBlockV3 #6136

Merged
merged 19 commits into from
Dec 8, 2023
11 changes: 6 additions & 5 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
StringType,
SubcommitteeIndex,
Wei,
Gwei,
} from "@lodestar/types";
import {ApiClientResponse} from "../../interfaces.js";
import {HttpStatusCode} from "../../utils/client/httpStatusCode.js";
Expand All @@ -27,7 +28,7 @@ import {
ArrayOf,
Schema,
WithVersion,
WithExecutionPayloadValue,
WithBlockValues,
reqOnlyBody,
ReqSerializers,
jsonType,
Expand All @@ -54,11 +55,11 @@ export type ExtraProduceBlockOps = {
strictFeeRecipientCheck?: boolean;
};

export type ProduceBlockOrContentsRes = {executionPayloadValue: Wei} & (
export type ProduceBlockOrContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Gwei} & (
| {data: allForks.BeaconBlock; version: ForkPreBlobs}
| {data: allForks.BlockContents; version: ForkBlobs}
);
export type ProduceBlindedBlockOrContentsRes = {executionPayloadValue: Wei} & (
export type ProduceBlindedBlockOrContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Gwei} & (
| {data: allForks.BlindedBeaconBlock; version: ForkPreBlobs}
| {data: allForks.BlindedBlockContents; version: ForkBlobs}
);
Expand Down Expand Up @@ -715,12 +716,12 @@ export function getReturnTypes(): ReturnTypes<Api> {
{jsonCase: "eth2"}
);

const produceBlockOrContents = WithExecutionPayloadValue(
const produceBlockOrContents = WithBlockValues(
WithVersion<allForks.BeaconBlockOrContents>((fork: ForkName) =>
isForkBlobs(fork) ? allForksBlockContentsResSerializer(fork) : ssz[fork].BeaconBlock
)
) as TypeJson<ProduceBlockOrContentsRes>;
const produceBlindedBlockOrContents = WithExecutionPayloadValue(
const produceBlindedBlockOrContents = WithBlockValues(
WithVersion<allForks.BlindedBeaconBlockOrContents>((fork: ForkName) =>
isForkBlobs(fork)
? allForksBlindedBlockContentsResSerializer(fork)
Expand Down
16 changes: 11 additions & 5 deletions packages/api/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,20 +179,26 @@ export function WithExecutionOptimistic<T extends {data: unknown}>(
}

/**
* SSZ factory helper to wrap an existing type with `{executionPayloadValue: Wei}`
* SSZ factory helper to wrap an existing type with `{executionPayloadValue: Wei, consensusBlockValue: GWei}`
*/
export function WithExecutionPayloadValue<T extends {data: unknown}>(
export function WithBlockValues<T extends {data: unknown}>(
type: TypeJson<T>
): TypeJson<T & {executionPayloadValue: bigint}> {
): TypeJson<T & {executionPayloadValue: bigint; consensusBlockValue: bigint}> {
return {
toJson: ({executionPayloadValue, ...data}) => ({
toJson: ({executionPayloadValue, consensusBlockValue, ...data}) => ({
...(type.toJson(data as unknown as T) as Record<string, unknown>),
execution_payload_value: executionPayloadValue.toString(),
consensus_block_value: consensusBlockValue.toString(),
}),
fromJson: ({execution_payload_value, ...data}: T & {execution_payload_value: string}) => ({
fromJson: ({
execution_payload_value,
consensus_block_value,
...data
}: T & {execution_payload_value: string; consensus_block_value: string}) => ({
...type.fromJson(data),
// For cross client usage where beacon or validator are of separate clients, executionPayloadValue could be missing
executionPayloadValue: BigInt(execution_payload_value ?? "0"),
consensusBlockValue: BigInt(consensus_block_value ?? "0"),
}),
};
}
Expand Down
3 changes: 3 additions & 0 deletions packages/api/test/unit/beacon/testData/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const testData: GenericServerTestCases<Api> = {
data: ssz.altair.BeaconBlock.defaultValue(),
version: ForkName.altair,
executionPayloadValue: ssz.Wei.defaultValue(),
consensusBlockValue: ssz.Gwei.defaultValue(),
},
},
produceBlockV3: {
Expand All @@ -68,6 +69,7 @@ export const testData: GenericServerTestCases<Api> = {
data: ssz.altair.BeaconBlock.defaultValue(),
version: ForkName.altair,
executionPayloadValue: ssz.Wei.defaultValue(),
consensusBlockValue: ssz.Gwei.defaultValue(),
executionPayloadBlinded: false,
},
},
Expand All @@ -77,6 +79,7 @@ export const testData: GenericServerTestCases<Api> = {
data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(),
version: ForkName.bellatrix,
executionPayloadValue: ssz.Wei.defaultValue(),
consensusBlockValue: ssz.Gwei.defaultValue(),
},
},
produceAttestationData: {
Expand Down
36 changes: 27 additions & 9 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ export function getValidatorApi({
let timer;
try {
timer = metrics?.blockProductionTime.startTimer();
const {block, executionPayloadValue} = await chain.produceBlindedBlock({
const {block, executionPayloadValue, consensusBlockValue} = await chain.produceBlindedBlock({
slot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
Expand All @@ -325,6 +325,7 @@ export function getValidatorApi({
logger.verbose("Produced blinded block", {
slot,
executionPayloadValue,
consensusBlockValue,
root: toHexString(config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block)),
});

Expand All @@ -342,9 +343,10 @@ export function getValidatorApi({
data: {blindedBlock: block, blindedBlobSidecars} as allForks.BlindedBlockContents,
version,
executionPayloadValue,
consensusBlockValue,
};
} else {
return {data: block, version, executionPayloadValue};
return {data: block, version, executionPayloadValue, consensusBlockValue};
}
} finally {
if (timer) timer({source});
Expand Down Expand Up @@ -378,13 +380,12 @@ export function getValidatorApi({
let timer;
try {
timer = metrics?.blockProductionTime.startTimer();
const {block, executionPayloadValue} = await chain.produceBlock({
const {block, executionPayloadValue, consensusBlockValue} = await chain.produceBlock({
slot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
feeRecipient,
});

const version = config.getForkName(block.slot);
if (strictFeeRecipientCheck && feeRecipient && isForkExecution(version)) {
const blockFeeRecipient = toHexString((block as bellatrix.BeaconBlock).body.executionPayload.feeRecipient);
Expand All @@ -398,6 +399,7 @@ export function getValidatorApi({
logger.verbose("Produced execution block", {
slot,
executionPayloadValue,
consensusBlockValue,
root: toHexString(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block)),
});
if (chain.opts.persistProducedBlocks) {
Expand All @@ -409,9 +411,14 @@ export function getValidatorApi({
if (blobSidecars === undefined) {
throw Error("blobSidecars missing in cache");
}
return {data: {block, blobSidecars} as allForks.BlockContents, version, executionPayloadValue};
return {
data: {block, blobSidecars} as allForks.BlockContents,
version,
executionPayloadValue,
consensusBlockValue,
};
} else {
return {data: block, version, executionPayloadValue};
return {data: block, version, executionPayloadValue, consensusBlockValue};
}
} finally {
if (timer) timer({source});
Expand Down Expand Up @@ -531,15 +538,18 @@ export function getValidatorApi({

const builderPayloadValue = blindedBlock?.executionPayloadValue ?? BigInt(0);
const enginePayloadValue = fullBlock?.executionPayloadValue ?? BigInt(0);
const consensusBlockValueBuilder = blindedBlock?.consensusBlockValue ?? BigInt(0);
const consensusBlockValueEngine = fullBlock?.consensusBlockValue ?? BigInt(0);

const blockValueBuilder = builderPayloadValue + consensusBlockValueBuilder;
const blockValueEngine = enginePayloadValue + consensusBlockValueEngine;

let selectedSource: ProducedBlockSource | null = null;

if (fullBlock && blindedBlock) {
switch (builderSelection) {
case routes.validator.BuilderSelection.MaxProfit: {
// If executionPayloadValues are zero, than choose builder as most likely beacon didn't provide executionPayloadValue
// and builder blocks are most likely thresholded by a min bid
if (enginePayloadValue >= builderPayloadValue && enginePayloadValue !== BigInt(0)) {
if (blockValueEngine >= blockValueBuilder) {
selectedSource = ProducedBlockSource.engine;
} else {
selectedSource = ProducedBlockSource.builder;
Expand All @@ -562,20 +572,28 @@ export function getValidatorApi({
// winston logger doesn't like bigint
enginePayloadValue: `${enginePayloadValue}`,
builderPayloadValue: `${builderPayloadValue}`,
consensusBlockValueEngine: `${consensusBlockValueEngine}`,
consensusBlockValueBuilder: `${consensusBlockValueBuilder}`,
g11tech marked this conversation as resolved.
Show resolved Hide resolved
blockValueEngine: `${blockValueEngine}`,
blockValueBuilder: `${blockValueBuilder}`,
slot,
});
} else if (fullBlock && !blindedBlock) {
selectedSource = ProducedBlockSource.engine;
logger.verbose("Selected engine block: no builder block produced", {
// winston logger doesn't like bigint
enginePayloadValue: `${enginePayloadValue}`,
consensusBlockValueEngine: `${consensusBlockValueEngine}`,
blockValueEngine: `${blockValueEngine}`,
slot,
});
} else if (blindedBlock && !fullBlock) {
selectedSource = ProducedBlockSource.builder;
logger.verbose("Selected builder block: no engine block produced", {
// winston logger doesn't like bigint
builderPayloadValue: `${builderPayloadValue}`,
consensusBlockValueBuilder: `${consensusBlockValueBuilder}`,
blockValueBuilder: `${blockValueBuilder}`,
slot,
});
}
Expand Down
17 changes: 11 additions & 6 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "node:path";
import {CompositeTypeAny, fromHexString, toHexString, TreeView, Type} from "@chainsafe/ssz";
import {CompositeTypeAny, fromHexString, TreeView, Type, toHexString} from "@chainsafe/ssz";
import {
BeaconStateAllForks,
CachedBeaconStateAllForks,
Expand Down Expand Up @@ -27,6 +27,7 @@ import {
Wei,
bellatrix,
isBlindedBeaconBlock,
Gwei,
} from "@lodestar/types";
import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {ProcessShutdownCallback} from "@lodestar/validator";
Expand Down Expand Up @@ -469,20 +470,22 @@ export class BeaconChain implements IBeaconChain {
return data && {block: data, executionOptimistic: false};
}

produceBlock(blockAttributes: BlockAttributes): Promise<{block: allForks.BeaconBlock; executionPayloadValue: Wei}> {
produceBlock(
blockAttributes: BlockAttributes
): Promise<{block: allForks.BeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}> {
return this.produceBlockWrapper<BlockType.Full>(BlockType.Full, blockAttributes);
}

produceBlindedBlock(
blockAttributes: BlockAttributes
): Promise<{block: allForks.BlindedBeaconBlock; executionPayloadValue: Wei}> {
): Promise<{block: allForks.BlindedBeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}> {
return this.produceBlockWrapper<BlockType.Blinded>(BlockType.Blinded, blockAttributes);
}

async produceBlockWrapper<T extends BlockType>(
blockType: T,
{randaoReveal, graffiti, slot, feeRecipient}: BlockAttributes
): Promise<{block: AssembledBlockType<T>; executionPayloadValue: Wei}> {
): Promise<{block: AssembledBlockType<T>; executionPayloadValue: Wei; consensusBlockValue: Gwei}> {
const head = this.forkChoice.getHead();
const state = await this.regen.getBlockSlotState(
head.blockRoot,
Expand Down Expand Up @@ -523,7 +526,9 @@ export class BeaconChain implements IBeaconChain {
stateRoot: ZERO_HASH,
body,
} as AssembledBlockType<T>;
block.stateRoot = computeNewStateRoot(this.metrics, state, block);

const {newStateRoot, proposerReward} = computeNewStateRoot(this.metrics, state, block);
block.stateRoot = newStateRoot;
const blockRoot =
blockType === BlockType.Full
? this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block)
Expand Down Expand Up @@ -575,7 +580,7 @@ export class BeaconChain implements IBeaconChain {
);
}

return {block, executionPayloadValue};
return {block, executionPayloadValue, consensusBlockValue: proposerReward};
}

/**
Expand Down
20 changes: 17 additions & 3 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz";
import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, deneb, Wei} from "@lodestar/types";
import {
allForks,
UintNum64,
Root,
phase0,
Slot,
RootHex,
Epoch,
ValidatorIndex,
deneb,
Wei,
Gwei,
} from "@lodestar/types";
import {
BeaconStateAllForks,
CachedBeaconStateAllForks,
Expand Down Expand Up @@ -141,10 +153,12 @@ export interface IBeaconChain {

getBlobSidecars(beaconBlock: deneb.BeaconBlock): deneb.BlobSidecars;

produceBlock(blockAttributes: BlockAttributes): Promise<{block: allForks.BeaconBlock; executionPayloadValue: Wei}>;
produceBlock(
blockAttributes: BlockAttributes
): Promise<{block: allForks.BeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}>;
produceBlindedBlock(
blockAttributes: BlockAttributes
): Promise<{block: allForks.BlindedBeaconBlock; executionPayloadValue: Wei}>;
): Promise<{block: allForks.BlindedBeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}>;

/** Process a block until complete */
processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
ExecutionPayloadStatus,
stateTransition,
} from "@lodestar/state-transition";
import {allForks, Root} from "@lodestar/types";
import {allForks, Gwei, Root} from "@lodestar/types";
import {ZERO_HASH} from "../../constants/index.js";
import {Metrics} from "../../metrics/index.js";

Expand All @@ -17,7 +17,7 @@ export function computeNewStateRoot(
metrics: Metrics | null,
state: CachedBeaconStateAllForks,
block: allForks.FullOrBlindedBeaconBlock
): Root {
): {newStateRoot: Root; proposerReward: Gwei} {
// Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type
const blockEmptySig = {message: block, signature: ZERO_HASH} as allForks.FullOrBlindedSignedBeaconBlock;

Expand All @@ -41,5 +41,8 @@ export function computeNewStateRoot(
metrics
);

return postState.hashTreeRoot();
const {attestations, syncAggregate, slashing} = postState.proposerRewards;
const proposerReward = BigInt(attestations + syncAggregate + slashing);
g11tech marked this conversation as resolved.
Show resolved Hide resolved

return {newStateRoot: postState.hashTreeRoot(), proposerReward};
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe("api/validator - produceBlockV2", function () {

const fullBlock = ssz.bellatrix.BeaconBlock.defaultValue();
const executionPayloadValue = ssz.Wei.defaultValue();
const consensusBlockValue = ssz.Gwei.defaultValue();

const currentSlot = 100000;
vi.spyOn(server.chainStub.clock, "currentSlot", "get").mockReturnValue(currentSlot);
Expand All @@ -84,7 +85,7 @@ describe("api/validator - produceBlockV2", function () {
const feeRecipient = "0xcccccccccccccccccccccccccccccccccccccccc";

const api = getValidatorApi(modules);
server.chainStub.produceBlock.mockResolvedValue({block: fullBlock, executionPayloadValue});
server.chainStub.produceBlock.mockResolvedValue({block: fullBlock, executionPayloadValue, consensusBlockValue});

// check if expectedFeeRecipient is passed to produceBlock
await api.produceBlockV2(slot, randaoReveal, graffiti, {feeRecipient});
Expand Down
Loading
Loading