Skip to content
Open
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
27 changes: 27 additions & 0 deletions packages/api/src/beacon/routes/beacon/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const AttestationListTypeElectra = ArrayOf(ssz.electra.Attestation);
const AttesterSlashingListTypePhase0 = ArrayOf(ssz.phase0.AttesterSlashing);
const AttesterSlashingListTypeElectra = ArrayOf(ssz.electra.AttesterSlashing);
const ProposerSlashingListType = ArrayOf(ssz.phase0.ProposerSlashing);
const SignedProposerPreferencesListType = ArrayOf(ssz.gloas.SignedProposerPreferences);
const SignedVoluntaryExitListType = ArrayOf(ssz.phase0.SignedVoluntaryExit);
const SignedBLSToExecutionChangeListType = ArrayOf(ssz.capella.SignedBLSToExecutionChange);
const SyncCommitteeMessageListType = ArrayOf(ssz.altair.SyncCommitteeMessage);
Expand All @@ -47,6 +48,7 @@ type AttesterSlashingListElectra = ValueOf<typeof AttesterSlashingListTypeElectr
type AttesterSlashingList = AttesterSlashingListPhase0 | AttesterSlashingListElectra;

type ProposerSlashingList = ValueOf<typeof ProposerSlashingListType>;
type SignedProposerPreferencesList = ValueOf<typeof SignedProposerPreferencesListType>;
type SignedVoluntaryExitList = ValueOf<typeof SignedVoluntaryExitListType>;
type SignedBLSToExecutionChangeList = ValueOf<typeof SignedBLSToExecutionChangeListType>;
type SyncCommitteeMessageList = ValueOf<typeof SyncCommitteeMessageListType>;
Expand Down Expand Up @@ -115,6 +117,18 @@ export type Endpoints = {
EmptyMeta
>;

/**
* Get ProposerPreferences from operations pool
* Retrieves proposer preferences known by the node but not necessarily incorporated into gossip handling logic yet.
*/
getPoolProposerPreferences: Endpoint<
"GET",
{slot?: Slot},
{query: {slot?: number}},
SignedProposerPreferencesList,
EmptyMeta
>;

/**
* Get SignedVoluntaryExit from operations pool
* Retrieves voluntary exits known by the node but not necessarily incorporated into any block
Expand Down Expand Up @@ -303,6 +317,19 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
meta: EmptyMetaCodec,
},
},
getPoolProposerPreferences: {
url: "/eth/v1/beacon/pool/proposer_preferences",
method: "GET",
req: {
writeReq: ({slot}) => ({query: {slot}}),
parseReq: ({query}) => ({slot: query.slot}),
schema: {query: {slot: Schema.Uint}},
},
resp: {
data: SignedProposerPreferencesListType,
meta: EmptyMetaCodec,
},
},
getPoolVoluntaryExits: {
url: "/eth/v1/beacon/pool/voluntary_exits",
method: "GET",
Expand Down
6 changes: 6 additions & 0 deletions packages/api/src/beacon/routes/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
altair,
capella,
electra,
gloas,
phase0,
ssz,
sszTypesFor,
Expand Down Expand Up @@ -88,6 +89,8 @@ export enum EventType {
blobSidecar = "blob_sidecar",
/** The node has received a valid DataColumnSidecar (from P2P or API) */
dataColumnSidecar = "data_column_sidecar",
/** The node has received valid SignedProposerPreferences (from P2P or API) */
proposerPreferences = "proposer_preferences",
}

export const eventTypes: {[K in EventType]: K} = {
Expand All @@ -108,6 +111,7 @@ export const eventTypes: {[K in EventType]: K} = {
[EventType.payloadAttributes]: EventType.payloadAttributes,
[EventType.blobSidecar]: EventType.blobSidecar,
[EventType.dataColumnSidecar]: EventType.dataColumnSidecar,
[EventType.proposerPreferences]: EventType.proposerPreferences,
};

export type EventData = {
Expand Down Expand Up @@ -157,6 +161,7 @@ export type EventData = {
[EventType.payloadAttributes]: {version: ForkName; data: SSEPayloadAttributes};
[EventType.blobSidecar]: BlobSidecarSSE;
[EventType.dataColumnSidecar]: DataColumnSidecarSSE;
[EventType.proposerPreferences]: gloas.SignedProposerPreferences;
};

export type BeaconEvent = {[K in EventType]: {type: K; message: EventData[K]}}[EventType];
Expand Down Expand Up @@ -311,6 +316,7 @@ export function getTypeByEvent(config: ChainForkConfig): {[K in EventType]: Type
[EventType.payloadAttributes]: WithVersion((fork) => getPostBellatrixForkTypes(fork).SSEPayloadAttributes),
[EventType.blobSidecar]: blobSidecarSSE,
[EventType.dataColumnSidecar]: dataColumnSidecarSSE,
[EventType.proposerPreferences]: ssz.gloas.SignedProposerPreferences,

[EventType.lightClientOptimisticUpdate]: WithVersion(
(fork) => getPostAltairForkTypes(fork).LightClientOptimisticUpdate
Expand Down
22 changes: 22 additions & 0 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export const SignedValidatorRegistrationV1ListType = ArrayOf(
ssz.bellatrix.SignedValidatorRegistrationV1,
VALIDATOR_REGISTRY_LIMIT
);
export const SignedProposerPreferencesListType = ArrayOf(ssz.gloas.SignedProposerPreferences);

export type ValidatorIndices = ValueOf<typeof ValidatorIndicesType>;
export type AttesterDuty = ValueOf<typeof AttesterDutyType>;
Expand All @@ -245,6 +246,7 @@ export type SyncCommitteeSelectionList = ValueOf<typeof SyncCommitteeSelectionLi
export type LivenessResponseData = ValueOf<typeof LivenessResponseDataType>;
export type LivenessResponseDataList = ValueOf<typeof LivenessResponseDataListType>;
export type SignedValidatorRegistrationV1List = ValueOf<typeof SignedValidatorRegistrationV1ListType>;
export type SignedProposerPreferencesList = ValueOf<typeof SignedProposerPreferencesListType>;

export type Endpoints = {
/**
Expand Down Expand Up @@ -490,6 +492,14 @@ export type Endpoints = {
EmptyMeta
>;

submitProposerPreferences: Endpoint<
"POST",
{proposerPreferences: SignedProposerPreferencesList},
{body: unknown},
EmptyResponseData,
EmptyMeta
>;

/**
* Determine if a distributed validator has been selected to aggregate attestations
*
Expand Down Expand Up @@ -978,6 +988,18 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
}),
resp: EmptyResponseCodec,
},
submitProposerPreferences: {
url: "/eth/v1/validator/proposer_preferences",
method: "POST",
req: JsonOnlyReq({
writeReqJson: ({proposerPreferences}) => ({
body: SignedProposerPreferencesListType.toJson(proposerPreferences),
}),
parseReqJson: ({body}) => ({proposerPreferences: SignedProposerPreferencesListType.fromJson(body)}),
schema: {body: Schema.ObjectArray},
}),
resp: EmptyResponseCodec,
},
submitBeaconCommitteeSelections: {
url: "/eth/v1/validator/beacon_committee_selections",
method: "POST",
Expand Down
4 changes: 4 additions & 0 deletions packages/api/test/unit/beacon/testData/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export const testData: GenericServerTestCases<Endpoints> = {
args: undefined,
res: {data: [ssz.phase0.ProposerSlashing.defaultValue()]},
},
getPoolProposerPreferences: {
args: {slot: 1},
res: {data: [ssz.gloas.SignedProposerPreferences.defaultValue()]},
},
getPoolVoluntaryExits: {
args: undefined,
res: {data: [ssz.phase0.SignedVoluntaryExit.defaultValue()]},
Expand Down
1 change: 1 addition & 0 deletions packages/api/test/unit/beacon/testData/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,5 @@ export const eventTestData: EventData = {
"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
],
}),
[EventType.proposerPreferences]: ssz.gloas.SignedProposerPreferences.defaultValue(),
};
4 changes: 4 additions & 0 deletions packages/api/test/unit/beacon/testData/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export const testData: GenericServerTestCases<Endpoints> = {
args: {proposers: [{validatorIndex: 1, feeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}]},
res: undefined,
},
submitProposerPreferences: {
args: {proposerPreferences: [ssz.gloas.SignedProposerPreferences.defaultValue()]},
res: undefined,
},
submitBeaconCommitteeSelections: {
args: {selections: []},
res: {data: [{validatorIndex: 1, slot: 2, selectionProof}]},
Expand Down
4 changes: 4 additions & 0 deletions packages/beacon-node/src/api/impl/beacon/pool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export function getBeaconPoolApi({
return {data: chain.opPool.getAllProposerSlashings()};
},

async getPoolProposerPreferences({slot}) {
return {data: chain.proposerPreferencesPool.getAll({slot})};
},

async getPoolVoluntaryExits() {
return {data: chain.opPool.getAllVoluntaryExits()};
},
Expand Down
29 changes: 29 additions & 0 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {BlockType, ProduceFullDeneb} from "../../../chain/produceBlock/index.js"
import {RegenCaller} from "../../../chain/regen/index.js";
import {CheckpointHex} from "../../../chain/stateCache/types.js";
import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
import {validateApiProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
import {ZERO_HASH} from "../../../constants/index.js";
import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
Expand Down Expand Up @@ -1469,6 +1470,34 @@ export function getValidatorApi(
await chain.updateBeaconProposerData(chain.clock.currentEpoch, proposers);
},

async submitProposerPreferences({proposerPreferences}) {
const failures: FailureList = [];

await Promise.all(
proposerPreferences.map(async (signedProposerPreferences, i) => {
try {
await validateApiProposerPreferences(chain, signedProposerPreferences);

const insertOutcome = chain.proposerPreferencesPool.add(signedProposerPreferences);
metrics?.opPool.proposerPreferencesPool.apiInsertOutcome.inc({insertOutcome});

const {proposalSlot, validatorIndex} = signedProposerPreferences.message;
chain.seenProposerPreferences.add(proposalSlot, validatorIndex);
chain.emitter.emit(routes.events.EventType.proposerPreferences, signedProposerPreferences);

await network.publishProposerPreferences(signedProposerPreferences);
} catch (e) {
failures.push({index: i, message: (e as Error).message});
logger.verbose(`Error on submitProposerPreferences [${i}]`, {}, e as Error);
}
})
);

if (failures.length > 0) {
throw new IndexedError("Error processing proposer preferences", failures);
}
},

async submitBeaconCommitteeSelections() {
throw new OnlySupportedByDVT();
},
Expand Down
8 changes: 8 additions & 0 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import {
ExecutionPayloadBidPool,
OpPool,
PayloadAttestationPool,
ProposerPreferencesPool,
SyncCommitteeMessagePool,
SyncContributionAndProofPool,
} from "./opPools/index.js";
Expand All @@ -100,6 +101,7 @@ import {
SeenExecutionPayloadBids,
SeenExecutionPayloadEnvelopes,
SeenPayloadAttesters,
SeenProposerPreferences,
SeenSyncCommitteeMessages,
} from "./seenCache/index.js";
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
Expand Down Expand Up @@ -164,6 +166,7 @@ export class BeaconChain implements IBeaconChain {
readonly syncContributionAndProofPool;
readonly executionPayloadBidPool: ExecutionPayloadBidPool;
readonly payloadAttestationPool: PayloadAttestationPool;
readonly proposerPreferencesPool: ProposerPreferencesPool;
readonly opPool: OpPool;

// Gossip seen cache
Expand All @@ -173,6 +176,7 @@ export class BeaconChain implements IBeaconChain {
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
readonly seenExecutionPayloadEnvelopes = new SeenExecutionPayloadEnvelopes();
readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
readonly seenProposerPreferences = new SeenProposerPreferences();
readonly seenBlockProposers = new SeenBlockProposers();
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
readonly seenContributionAndProof: SeenContributionAndProof;
Expand Down Expand Up @@ -291,6 +295,7 @@ export class BeaconChain implements IBeaconChain {
this.syncContributionAndProofPool = new SyncContributionAndProofPool(config, clock, metrics, logger);
this.executionPayloadBidPool = new ExecutionPayloadBidPool();
this.payloadAttestationPool = new PayloadAttestationPool(config, clock, metrics);
this.proposerPreferencesPool = new ProposerPreferencesPool();
this.opPool = new OpPool(config);

this.seenAggregatedAttestations = new SeenAggregatedAttestations(metrics);
Expand Down Expand Up @@ -1270,6 +1275,7 @@ export class BeaconChain implements IBeaconChain {
metrics.opPool.syncCommitteeMessagePoolSize.set(this.syncCommitteeMessagePool.size);
metrics.opPool.payloadAttestationPool.size.set(this.payloadAttestationPool.size);
metrics.opPool.executionPayloadBidPool.size.set(this.executionPayloadBidPool.size);
metrics.opPool.proposerPreferencesPool.size.set(this.proposerPreferencesPool.size);
// syncContributionAndProofPool tracks metrics on its own
metrics.opPool.blsToExecutionChangePoolSize.set(this.opPool.blsToExecutionChangeSize);
metrics.chain.blacklistedBlocks.set(this.blacklistedBlocks.size);
Expand Down Expand Up @@ -1302,7 +1308,9 @@ export class BeaconChain implements IBeaconChain {
this.seenSyncCommitteeMessages.prune(slot);
this.payloadAttestationPool.prune(slot);
this.executionPayloadBidPool.prune(slot);
this.proposerPreferencesPool.prune(slot);
this.seenExecutionPayloadBids.prune(slot);
this.seenProposerPreferences.prune(slot);
this.seenAttestationDatas.onSlot(slot);
this.reprocessController.onSlot(slot);

Expand Down
13 changes: 12 additions & 1 deletion packages/beacon-node/src/chain/errors/executionPayloadBid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export enum ExecutionPayloadBidErrorCode {
UNKNOWN_BLOCK_ROOT = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_BLOCK_ROOT",
INVALID_SLOT = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SLOT",
INVALID_SIGNATURE = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SIGNATURE",
PREFERENCES_NOT_SEEN = "EXECUTION_PAYLOAD_BID_ERROR_PREFERENCES_NOT_SEEN",
PREFERENCES_MISMATCH = "EXECUTION_PAYLOAD_BID_ERROR_PREFERENCES_MISMATCH",
}

export type ExecutionPayloadBidErrorType =
Expand All @@ -30,6 +32,15 @@ export type ExecutionPayloadBidErrorType =
| {code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH; bidValue: number; builderBalance: number}
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT; parentBlockRoot: RootHex}
| {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
| {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: BuilderIndex; slot: Slot};
| {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: BuilderIndex; slot: Slot}
| {code: ExecutionPayloadBidErrorCode.PREFERENCES_NOT_SEEN; slot: Slot}
| {
code: ExecutionPayloadBidErrorCode.PREFERENCES_MISMATCH;
slot: Slot;
bidFeeRecipient: string;
expectedFeeRecipient: string;
bidGasLimit: bigint;
expectedGasLimit: number;
};

export class ExecutionPayloadBidError extends GossipActionError<ExecutionPayloadBidErrorType> {}
1 change: 1 addition & 0 deletions packages/beacon-node/src/chain/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from "./executionPayloadBid.js";
export * from "./executionPayloadEnvelope.js";
export * from "./gossipValidation.js";
export * from "./payloadAttestation.js";
export * from "./proposerPreferences.js";
export * from "./proposerSlashingError.js";
export * from "./syncCommitteeError.js";
export * from "./voluntaryExitError.js";
34 changes: 34 additions & 0 deletions packages/beacon-node/src/chain/errors/proposerPreferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {Slot, ValidatorIndex} from "@lodestar/types";
import {GossipActionError} from "./gossipValidation.js";

export enum ProposerPreferencesErrorCode {
INVALID_EPOCH = "PROPOSER_PREFERENCES_ERROR_INVALID_EPOCH",
INVALID_PROPOSAL_SLOT = "PROPOSER_PREFERENCES_ERROR_INVALID_PROPOSAL_SLOT",
PREFERENCES_ALREADY_KNOWN = "PROPOSER_PREFERENCES_ERROR_PREFERENCES_ALREADY_KNOWN",
INVALID_SIGNATURE = "PROPOSER_PREFERENCES_ERROR_INVALID_SIGNATURE",
}

export type ProposerPreferencesErrorType =
| {
code: ProposerPreferencesErrorCode.INVALID_EPOCH;
currentEpoch: number;
proposalSlot: Slot;
}
| {
code: ProposerPreferencesErrorCode.INVALID_PROPOSAL_SLOT;
proposalSlot: Slot;
validatorIndex: ValidatorIndex;
expectedValidatorIndex: ValidatorIndex | null;
}
| {
code: ProposerPreferencesErrorCode.PREFERENCES_ALREADY_KNOWN;
proposalSlot: Slot;
validatorIndex: ValidatorIndex;
}
| {
code: ProposerPreferencesErrorCode.INVALID_SIGNATURE;
proposalSlot: Slot;
validatorIndex: ValidatorIndex;
};

export class ProposerPreferencesError extends GossipActionError<ProposerPreferencesErrorType> {}
4 changes: 4 additions & 0 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
ExecutionPayloadBidPool,
OpPool,
PayloadAttestationPool,
ProposerPreferencesPool,
SyncCommitteeMessagePool,
SyncContributionAndProofPool,
} from "./opPools/index.js";
Expand All @@ -66,6 +67,7 @@ import {
SeenExecutionPayloadBids,
SeenExecutionPayloadEnvelopes,
SeenPayloadAttesters,
SeenProposerPreferences,
SeenSyncCommitteeMessages,
} from "./seenCache/index.js";
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
Expand Down Expand Up @@ -128,6 +130,7 @@ export interface IBeaconChain {
readonly syncContributionAndProofPool: SyncContributionAndProofPool;
readonly executionPayloadBidPool: ExecutionPayloadBidPool;
readonly payloadAttestationPool: PayloadAttestationPool;
readonly proposerPreferencesPool: ProposerPreferencesPool;
readonly opPool: OpPool;

// Gossip seen cache
Expand All @@ -137,6 +140,7 @@ export interface IBeaconChain {
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
readonly seenExecutionPayloadEnvelopes: SeenExecutionPayloadEnvelopes;
readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
readonly seenProposerPreferences: SeenProposerPreferences;
readonly seenBlockProposers: SeenBlockProposers;
readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
readonly seenContributionAndProof: SeenContributionAndProof;
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/chain/opPools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export {AttestationPool} from "./attestationPool.js";
export {ExecutionPayloadBidPool} from "./executionPayloadBidPool.js";
export {OpPool} from "./opPool.js";
export {PayloadAttestationPool} from "./payloadAttestationPool.js";
export {ProposerPreferencesPool} from "./proposerPreferencesPool.js";
export {SyncCommitteeMessagePool} from "./syncCommitteeMessagePool.js";
export {SyncContributionAndProofPool} from "./syncContributionAndProofPool.js";
Loading
Loading