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

SSE emit payload attributes before each slot #5281

Merged
merged 2 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/api/src/beacon/routes/events.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {Epoch, phase0, capella, Slot, ssz, StringType, RootHex, altair, UintNum64, allForks} from "@lodestar/types";
import {ContainerType} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {isForkExecution, ForkName} from "@lodestar/params";

import {RouteDef, TypeJson} from "../../utils/index.js";
import {RouteDef, TypeJson, WithVersion} from "../../utils/index.js";
import {HttpStatusCode} from "../../utils/client/httpStatusCode.js";
import {ApiClientResponse} from "../../interfaces.js";

Expand Down Expand Up @@ -36,6 +37,8 @@ export enum EventType {
lightClientFinalityUpdate = "light_client_finality_update",
/** New or better light client update available */
lightClientUpdate = "light_client_update",
/** Payload attributes for block proposal */
payloadAttributes = "payload_attributes",
}

export const eventTypes: {[K in EventType]: K} = {
Expand All @@ -50,6 +53,7 @@ export const eventTypes: {[K in EventType]: K} = {
[EventType.lightClientOptimisticUpdate]: EventType.lightClientOptimisticUpdate,
[EventType.lightClientFinalityUpdate]: EventType.lightClientFinalityUpdate,
[EventType.lightClientUpdate]: EventType.lightClientUpdate,
[EventType.payloadAttributes]: EventType.payloadAttributes,
};

export type EventData = {
Expand Down Expand Up @@ -90,6 +94,7 @@ export type EventData = {
[EventType.lightClientOptimisticUpdate]: allForks.LightClientOptimisticUpdate;
[EventType.lightClientFinalityUpdate]: allForks.LightClientFinalityUpdate;
[EventType.lightClientUpdate]: allForks.LightClientUpdate;
[EventType.payloadAttributes]: {version: ForkName; data: allForks.SSEPayloadAttributes};
};

export type BeaconEvent = {[K in EventType]: {type: K; message: EventData[K]}}[EventType];
Expand Down Expand Up @@ -182,6 +187,9 @@ export function getTypeByEvent(config: ChainForkConfig): {[K in EventType]: Type
),

[EventType.contributionAndProof]: ssz.altair.SignedContributionAndProof,
[EventType.payloadAttributes]: WithVersion((fork) =>
isForkExecution(fork) ? ssz.allForksExecution[fork].SSEPayloadAttributes : ssz.bellatrix.SSEPayloadAttributes
),

[EventType.lightClientOptimisticUpdate]: {
toJson: (data) =>
Expand Down
5 changes: 5 additions & 0 deletions packages/api/test/unit/beacon/testData/events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {ssz} from "@lodestar/types";
import {ForkName} from "@lodestar/params";
import {Api, EventData, EventType} from "../../../../src/beacon/routes/events.js";
import {GenericServerTestCases} from "../../../utils/genericServerTest.js";

Expand Down Expand Up @@ -104,4 +105,8 @@ export const eventTestData: EventData = {
signatureSlot: ssz.Slot.defaultValue(),
},
[EventType.lightClientUpdate]: ssz.altair.LightClientUpdate.defaultValue(),
[EventType.payloadAttributes]: {
version: ForkName.bellatrix,
data: ssz.bellatrix.SSEPayloadAttributes.defaultValue(),
},
};
2 changes: 2 additions & 0 deletions packages/beacon-node/src/chain/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type BlockProcessOpts = {
* will still issue fcU for block proposal
*/
disableImportExecutionFcU?: boolean;
emitPayloadAttributes?: boolean;
};

export const defaultChainOptions: IChainOptions = {
Expand All @@ -57,4 +58,5 @@ export const defaultChainOptions: IChainOptions = {
suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient,
assertCorrectProgressiveBalances: false,
archiveStateEpochFrequency: 1024,
emitPayloadAttributes: false,
};
18 changes: 16 additions & 2 deletions packages/beacon-node/src/chain/prepareNextSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import {computeEpochAtSlot, isExecutionStateType, computeTimeAtSlot} from "@lode
import {ChainForkConfig} from "@lodestar/config";
import {ForkSeq, SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params";
import {Slot} from "@lodestar/types";
import {Logger, sleep} from "@lodestar/utils";
import {Logger, sleep, fromHex} from "@lodestar/utils";
import {routes} from "@lodestar/api";
import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
import {Metrics} from "../metrics/index.js";
import {TransitionConfigurationV1} from "../execution/engine/interface.js";
import {ChainEvent} from "./emitter.js";
import {prepareExecutionPayload} from "./produceBlock/produceBlockBody.js";
import {prepareExecutionPayload, getPayloadAttributesForSSE} from "./produceBlock/produceBlockBody.js";
import {IBeaconChain} from "./interface.js";
import {RegenCaller} from "./regen/index.js";

Expand Down Expand Up @@ -156,6 +157,19 @@ export class PrepareNextSlotScheduler {
feeRecipient,
});
}

// If emitPayloadAttributes is true emit a SSE payloadAttributes event
if (this.chain.opts.emitPayloadAttributes === true) {
const data = await getPayloadAttributesForSSE(fork as ForkExecution, this.chain, {
prepareState,
prepareSlot,
parentBlockRoot: fromHex(headRoot),
// The likely consumers of this API are builders and will anyway ignore the
// feeRecipient, so just pass zero hash for now till a real use case arises
feeRecipient: "0x0000000000000000000000000000000000000000000000000000000000000000",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this always ZERO_HASH?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm it could be set to the proposer's fee recipient (or the beacon's default in case we don't have one), it any way seems to be irrelevant/ignorable

image

https://github.com/ethereum/beacon-APIs/pull/305/files#diff-3cc6eaa800c1a4bd6adcd78f1722b466f7a24048a66e320c8f92872fbcd9eefbR120

});
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
}
}
} catch (e) {
this.metrics?.precomputeNextEpochTransition.count.inc({result: "error"}, 1);
Expand Down
49 changes: 47 additions & 2 deletions packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from "@lodestar/state-transition";
import {ChainForkConfig} from "@lodestar/config";
import {ForkSeq, ForkExecution, isForkExecution} from "@lodestar/params";
import {toHex, sleep, Logger} from "@lodestar/utils";
import {toHex, sleep, Logger, fromHex} from "@lodestar/utils";

import type {BeaconChain} from "../chain.js";
import {PayloadId, IExecutionEngine, IExecutionBuilder, PayloadAttributes} from "../../execution/index.js";
Expand Down Expand Up @@ -418,7 +418,7 @@ async function prepareExecutionPayloadHeader(
return chain.executionBuilder.getHeader(state.slot, parentHash, proposerPubKey);
}

async function getExecutionPayloadParentHash(
export async function getExecutionPayloadParentHash(
chain: {
eth1: IEth1ForBlockProduction;
config: ChainForkConfig;
Expand Down Expand Up @@ -452,4 +452,49 @@ async function getExecutionPayloadParentHash(
}
}

export async function getPayloadAttributesForSSE(
fork: ForkExecution,
chain: {
eth1: IEth1ForBlockProduction;
config: ChainForkConfig;
},
{
prepareState,
prepareSlot,
parentBlockRoot,
feeRecipient,
}: {prepareState: CachedBeaconStateExecutions; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string}
): Promise<allForks.SSEPayloadAttributes> {
const parentHashRes = await getExecutionPayloadParentHash(chain, prepareState);

if (!parentHashRes.isPremerge) {
const {parentHash} = parentHashRes;
const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
const prevRandao = getRandaoMix(prepareState, prepareState.epochCtx.epoch);
const payloadAttributes = {
timestamp,
prevRandao,
suggestedFeeRecipient: fromHex(feeRecipient),
};

if (ForkSeq[fork] >= ForkSeq.capella) {
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals(
prepareState as CachedBeaconStateCapella
).withdrawals;
}

const ssePayloadAttributes: allForks.SSEPayloadAttributes = {
proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot),
proposalSlot: prepareSlot,
proposalBlockNumber: prepareState.latestExecutionPayloadHeader.blockNumber + 1,
parentBlockRoot,
parentBlockHash: parentHash,
payloadAttributes,
};
return ssePayloadAttributes;
} else {
throw Error("The execution is still pre-merge");
}
}

/** process_sync_committee_contributions is implemented in syncCommitteeContribution.getSyncAggregate */
21 changes: 15 additions & 6 deletions packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {ForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {WinstonLogger} from "@lodestar/utils";
import {ForkName, SLOTS_PER_EPOCH} from "@lodestar/params";
import {ChainForkConfig} from "@lodestar/config";
import {routes} from "@lodestar/api";
import {BeaconChain, ChainEventEmitter} from "../../../src/chain/index.js";
import {IBeaconChain} from "../../../src/chain/interface.js";
import {IChainOptions} from "../../../src/chain/options.js";
import {LocalClock} from "../../../src/chain/clock/index.js";
import {PrepareNextSlotScheduler} from "../../../src/chain/prepareNextSlot.js";
import {StateRegenerator} from "../../../src/chain/regen/index.js";
Expand All @@ -17,8 +19,9 @@ import {PayloadIdCache} from "../../../src/execution/engine/payloadIdCache.js";
import {ExecutionEngineHttp} from "../../../src/execution/engine/http.js";
import {IExecutionEngine} from "../../../src/execution/engine/interface.js";
import {StubbedChainMutable} from "../../utils/stub/index.js";
import {zeroProtoBlock} from "../../utils/mocks/chain/chain.js";

type StubbedChain = StubbedChainMutable<"clock" | "forkChoice" | "emitter" | "regen">;
type StubbedChain = StubbedChainMutable<"clock" | "forkChoice" | "emitter" | "regen" | "opts">;

describe("PrepareNextSlot scheduler", () => {
const sandbox = sinon.createSandbox();
Expand All @@ -33,7 +36,8 @@ describe("PrepareNextSlot scheduler", () => {
let getForkStub: SinonStubFn<typeof config["getForkName"]>;
let updateBuilderStatus: SinonStubFn<IBeaconChain["updateBuilderStatus"]>;
let executionEngineStub: SinonStubbedInstance<ExecutionEngineHttp> & ExecutionEngineHttp;

const emitPayloadAttributes = true;
const proposerIndex = 0;
beforeEach(() => {
sandbox.useFakeTimers();
chainStub = sandbox.createStubInstance(BeaconChain) as StubbedChain;
Expand All @@ -42,9 +46,8 @@ describe("PrepareNextSlot scheduler", () => {
chainStub.clock = clockStub;
forkChoiceStub = sandbox.createStubInstance(ForkChoice) as SinonStubbedInstance<ForkChoice> & ForkChoice;
chainStub.forkChoice = forkChoiceStub;
const emitterStub = sandbox.createStubInstance(ChainEventEmitter) as SinonStubbedInstance<ChainEventEmitter> &
ChainEventEmitter;
chainStub.emitter = emitterStub;
const emitter = new ChainEventEmitter();
chainStub.emitter = emitter;
regenStub = sandbox.createStubInstance(StateRegenerator) as SinonStubbedInstance<StateRegenerator> &
StateRegenerator;
chainStub.regen = regenStub;
Expand All @@ -60,6 +63,8 @@ describe("PrepareNextSlot scheduler", () => {
ExecutionEngineHttp;
((chainStub as unknown) as {executionEngine: IExecutionEngine}).executionEngine = executionEngineStub;
((chainStub as unknown) as {config: ChainForkConfig}).config = (config as unknown) as ChainForkConfig;
chainStub.opts = {emitPayloadAttributes} as IChainOptions;

scheduler = new PrepareNextSlotScheduler(chainStub, config, null, loggerStub, abortController.signal);
});

Expand Down Expand Up @@ -135,12 +140,15 @@ describe("PrepareNextSlot scheduler", () => {
});

it("bellatrix - should prepare payload", async () => {
const spy = sinon.spy();
chainStub.emitter.on(routes.events.EventType.payloadAttributes, spy);
getForkStub.returns(ForkName.bellatrix);
chainStub.recomputeForkChoiceHead.returns({slot: SLOTS_PER_EPOCH - 3} as ProtoBlock);
chainStub.recomputeForkChoiceHead.returns({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock);
forkChoiceStub.getJustifiedBlock.returns({} as ProtoBlock);
forkChoiceStub.getFinalizedBlock.returns({} as ProtoBlock);
updateBuilderStatus.returns(void 0);
const state = generateCachedBellatrixState();
sinon.stub(state.epochCtx, "getBeaconProposer").returns(proposerIndex);
regenStub.getBlockSlotState.resolves(state);
beaconProposerCacheStub.get.returns("0x fee recipient address");
((executionEngineStub as unknown) as {payloadIdCache: PayloadIdCache}).payloadIdCache = new PayloadIdCache();
Expand All @@ -157,5 +165,6 @@ describe("PrepareNextSlot scheduler", () => {
expect(forkChoiceStub.getFinalizedBlock, "expect forkChoice.getFinalizedBlock to be called").to.be.called;
expect(executionEngineStub.notifyForkchoiceUpdate, "expect executionEngine.notifyForkchoiceUpdate to be called").to
.be.calledOnce;
expect(spy).to.be.calledOnce;
});
});
9 changes: 9 additions & 0 deletions packages/cli/src/options/beaconNodeOptions/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type ChainArgs = {
"chain.maxSkipSlots": number;
"safe-slots-to-import-optimistically": number;
"chain.archiveStateEpochFrequency": number;
emitPayloadAttributes: boolean;
};

export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] {
Expand All @@ -37,6 +38,7 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] {
maxSkipSlots: args["chain.maxSkipSlots"],
safeSlotsToImportOptimistically: args["safe-slots-to-import-optimistically"],
archiveStateEpochFrequency: args["chain.archiveStateEpochFrequency"],
emitPayloadAttributes: args["emitPayloadAttributes"],
};
}

Expand All @@ -49,6 +51,13 @@ export const options: CliCommandOptions<ChainArgs> = {
group: "chain",
},

emitPayloadAttributes: {
type: "boolean",
defaultDescription: String(defaultOptions.chain.emitPayloadAttributes),
description: "Flag to SSE emit execution payloadAttributes before every slot",
group: "chain",
},

"chain.blsVerifyAllMultiThread": {
hidden: true,
type: "boolean",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/test/unit/options/beaconNodeOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe("options / beaconNodeOptions", () => {
"chain.maxSkipSlots": 100,
"safe-slots-to-import-optimistically": 256,
"chain.archiveStateEpochFrequency": 1024,
emitPayloadAttributes: false,

eth1: true,
"eth1.providerUrl": "http://my.node:8545",
Expand Down Expand Up @@ -117,6 +118,7 @@ describe("options / beaconNodeOptions", () => {
assertCorrectProgressiveBalances: true,
maxSkipSlots: 100,
archiveStateEpochFrequency: 1024,
emitPayloadAttributes: false,
},
eth1: {
enabled: true,
Expand Down
3 changes: 3 additions & 0 deletions packages/types/src/allForks/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const allForksExecution = {
ExecutionPayloadHeader: bellatrix.ExecutionPayloadHeader,
BuilderBid: bellatrix.BuilderBid,
SignedBuilderBid: bellatrix.SignedBuilderBid,
SSEPayloadAttributes: bellatrix.SSEPayloadAttributes,
},
capella: {
BeaconBlockBody: capella.BeaconBlockBody,
Expand All @@ -71,6 +72,7 @@ export const allForksExecution = {
ExecutionPayloadHeader: capella.ExecutionPayloadHeader,
BuilderBid: capella.BuilderBid,
SignedBuilderBid: capella.SignedBuilderBid,
SSEPayloadAttributes: capella.SSEPayloadAttributes,
},
deneb: {
BeaconBlockBody: deneb.BeaconBlockBody,
Expand All @@ -81,6 +83,7 @@ export const allForksExecution = {
ExecutionPayloadHeader: deneb.ExecutionPayloadHeader,
BuilderBid: deneb.BuilderBid,
SignedBuilderBid: deneb.SignedBuilderBid,
SSEPayloadAttributes: capella.SSEPayloadAttributes,
},
};

Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/allForks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export type LightClientOptimisticUpdate =
export type LightClientStore = altair.LightClientStore | capella.LightClientStore | deneb.LightClientStore;

export type SignedBeaconBlockAndBlobsSidecar = deneb.SignedBeaconBlockAndBlobsSidecar;

export type SSEPayloadAttributes = bellatrix.SSEPayloadAttributes | capella.SSEPayloadAttributes;
/**
* Types known to change between forks
*/
Expand Down Expand Up @@ -214,6 +216,9 @@ export type AllForksExecutionSSZTypes = {
SignedBuilderBid: AllForksTypeOf<
typeof bellatrixSsz.SignedBuilderBid | typeof capellaSsz.SignedBuilderBid | typeof denebSsz.SignedBuilderBid
>;
SSEPayloadAttributes: AllForksTypeOf<
typeof bellatrixSsz.SSEPayloadAttributes | typeof capellaSsz.SSEPayloadAttributes
>;
};

export type AllForksBlindedSSZTypes = {
Expand Down
25 changes: 25 additions & 0 deletions packages/types/src/bellatrix/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,28 @@ export const SignedBuilderBid = new ContainerType(
},
{typeName: "SignedBuilderBid", jsonCase: "eth2"}
);

// PayloadAttributes primarily for SSE event
export const PayloadAttributes = new ContainerType(
{timestamp: UintNum64, prevRandao: Bytes32, suggestedFeeRecipient: ExecutionAddress},
{typeName: "PayloadAttributes", jsonCase: "eth2"}
);

export const SSEPayloadAttributesCommon = new ContainerType(
{
proposerIndex: UintNum64,
proposalSlot: Slot,
proposalBlockNumber: UintNum64,
parentBlockRoot: Root,
parentBlockHash: Root,
},
{typeName: "SSEPayloadAttributesCommon", jsonCase: "eth2"}
);

export const SSEPayloadAttributes = new ContainerType(
{
...SSEPayloadAttributesCommon.fields,
payloadAttributes: PayloadAttributes,
},
{typeName: "SSEPayloadAttributes", jsonCase: "eth2"}
);
1 change: 1 addition & 0 deletions packages/types/src/bellatrix/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export type ValidatorRegistrationV1 = ValueOf<typeof ssz.ValidatorRegistrationV1
export type SignedValidatorRegistrationV1 = ValueOf<typeof ssz.SignedValidatorRegistrationV1>;
export type BuilderBid = ValueOf<typeof ssz.BuilderBid>;
export type SignedBuilderBid = ValueOf<typeof ssz.SignedBuilderBid>;
export type SSEPayloadAttributes = ValueOf<typeof ssz.SSEPayloadAttributes>;

export type FullOrBlindedExecutionPayload = ExecutionPayload | ExecutionPayloadHeader;
Loading