diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 35b79c711485..731e6b615d81 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -106,6 +106,7 @@ import { SeenExecutionPayloadBids, SeenPayloadAttesters, SeenPayloadEnvelopeInput, + SeenProposerPreferences, SeenSyncCommitteeMessages, } from "./seenCache/index.js"; import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js"; @@ -186,6 +187,7 @@ export class BeaconChain implements IBeaconChain { readonly seenPayloadAttesters = new SeenPayloadAttesters(); readonly seenAggregatedAttestations: SeenAggregatedAttestations; readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids(); + readonly seenProposerPreferences = new SeenProposerPreferences(); readonly seenBlockProposers = new SeenBlockProposers(); readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages(); readonly seenContributionAndProof: SeenContributionAndProof; @@ -1437,6 +1439,7 @@ export class BeaconChain implements IBeaconChain { this.payloadAttestationPool.prune(slot); this.executionPayloadBidPool.prune(slot); this.seenExecutionPayloadBids.prune(slot); + this.seenProposerPreferences.prune(slot); this.seenAttestationDatas.onSlot(slot); this.reprocessController.onSlot(slot); diff --git a/packages/beacon-node/src/chain/errors/index.ts b/packages/beacon-node/src/chain/errors/index.ts index 106d61021213..3725af0cb421 100644 --- a/packages/beacon-node/src/chain/errors/index.ts +++ b/packages/beacon-node/src/chain/errors/index.ts @@ -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"; diff --git a/packages/beacon-node/src/chain/errors/proposerPreferences.ts b/packages/beacon-node/src/chain/errors/proposerPreferences.ts new file mode 100644 index 000000000000..3e63966da850 --- /dev/null +++ b/packages/beacon-node/src/chain/errors/proposerPreferences.ts @@ -0,0 +1,39 @@ +import {Slot, ValidatorIndex} from "@lodestar/types"; +import {GossipActionError} from "./gossipValidation.js"; + +export enum ProposerPreferencesErrorCode { + INVALID_EPOCH = "PROPOSER_PREFERENCES_ERROR_INVALID_EPOCH", + PROPOSAL_SLOT_PASSED = "PROPOSER_PREFERENCES_ERROR_PROPOSAL_SLOT_PASSED", + INVALID_PROPOSER = "PROPOSER_PREFERENCES_ERROR_INVALID_PROPOSER", + ALREADY_KNOWN = "PROPOSER_PREFERENCES_ERROR_ALREADY_KNOWN", + INVALID_SIGNATURE = "PROPOSER_PREFERENCES_ERROR_INVALID_SIGNATURE", +} + +export type ProposerPreferencesErrorType = + | { + code: ProposerPreferencesErrorCode.INVALID_EPOCH; + proposalSlot: Slot; + currentEpoch: number; + } + | { + code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED; + proposalSlot: Slot; + currentSlot: Slot; + } + | { + code: ProposerPreferencesErrorCode.INVALID_PROPOSER; + proposalSlot: Slot; + validatorIndex: ValidatorIndex; + } + | { + code: ProposerPreferencesErrorCode.ALREADY_KNOWN; + proposalSlot: Slot; + validatorIndex: ValidatorIndex; + } + | { + code: ProposerPreferencesErrorCode.INVALID_SIGNATURE; + proposalSlot: Slot; + validatorIndex: ValidatorIndex; + }; + +export class ProposerPreferencesError extends GossipActionError {} diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 722461949e56..b13304a8a08f 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -61,6 +61,7 @@ import { SeenContributionAndProof, SeenExecutionPayloadBids, SeenPayloadAttesters, + SeenProposerPreferences, SeenSyncCommitteeMessages, } from "./seenCache/index.js"; import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js"; @@ -131,6 +132,7 @@ export interface IBeaconChain { readonly seenPayloadAttesters: SeenPayloadAttesters; readonly seenAggregatedAttestations: SeenAggregatedAttestations; readonly seenExecutionPayloadBids: SeenExecutionPayloadBids; + readonly seenProposerPreferences: SeenProposerPreferences; readonly seenBlockProposers: SeenBlockProposers; readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages; readonly seenContributionAndProof: SeenContributionAndProof; diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index e2f91819de62..b70844f0fb59 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -21,6 +21,7 @@ export enum RegenCaller { validateGossipAttestation = "validateGossipAttestation", validateGossipVoluntaryExit = "validateGossipVoluntaryExit", validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid", + validateGossipProposerPreferences = "validateGossipProposerPreferences", onForkChoiceFinalized = "onForkChoiceFinalized", restApi = "restApi", } diff --git a/packages/beacon-node/src/chain/seenCache/index.ts b/packages/beacon-node/src/chain/seenCache/index.ts index 23782e14385b..08d750090dba 100644 --- a/packages/beacon-node/src/chain/seenCache/index.ts +++ b/packages/beacon-node/src/chain/seenCache/index.ts @@ -5,3 +5,4 @@ export {SeenContributionAndProof} from "./seenCommitteeContribution.js"; export {SeenExecutionPayloadBids} from "./seenExecutionPayloadBids.js"; export {SeenBlockInput} from "./seenGossipBlockInput.js"; export {PayloadEnvelopeInput, SeenPayloadEnvelopeInput} from "./seenPayloadEnvelopeInput.js"; +export {SeenProposerPreferences} from "./seenProposerPreferences.js"; diff --git a/packages/beacon-node/src/chain/seenCache/seenProposerPreferences.ts b/packages/beacon-node/src/chain/seenCache/seenProposerPreferences.ts new file mode 100644 index 000000000000..3fbbdfe1d1e0 --- /dev/null +++ b/packages/beacon-node/src/chain/seenCache/seenProposerPreferences.ts @@ -0,0 +1,29 @@ +import {Slot, ValidatorIndex} from "@lodestar/types"; +import {MapDef} from "@lodestar/utils"; + +/** + * Tracks signed proposer preferences we've already seen per (proposal_slot, validator_index). + */ +export class SeenProposerPreferences { + private readonly validatorIndexesBySlot = new MapDef>(() => new Set()); + + isKnown(proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean { + return this.validatorIndexesBySlot.get(proposalSlot)?.has(validatorIndex) === true; + } + + add(proposalSlot: Slot, validatorIndex: ValidatorIndex): void { + this.validatorIndexesBySlot.getOrDefault(proposalSlot).add(validatorIndex); + } + + /** + * Entries are only load-bearing while `proposal_slot > state.slot`. Once the slot has passed the + * `[IGNORE] proposal_slot > state.slot` gossip rule takes over, so drop them on each slot tick. + */ + prune(currentSlot: Slot): void { + for (const slot of this.validatorIndexesBySlot.keys()) { + if (slot < currentSlot) { + this.validatorIndexesBySlot.delete(slot); + } + } + } +} diff --git a/packages/beacon-node/src/chain/validation/proposerPreferences.ts b/packages/beacon-node/src/chain/validation/proposerPreferences.ts new file mode 100644 index 000000000000..11ba27618cb9 --- /dev/null +++ b/packages/beacon-node/src/chain/validation/proposerPreferences.ts @@ -0,0 +1,91 @@ +import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import { + computeEpochAtSlot, + createSingleSignatureSetFromComponents, + getProposerPreferencesSigningRoot, + isStatePostGloas, +} from "@lodestar/state-transition"; +import {gloas} from "@lodestar/types"; +import {GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode} from "../errors/index.js"; +import {IBeaconChain} from "../index.js"; +import {RegenCaller} from "../regen/index.js"; + +/** + * Validates a gossiped `SignedProposerPreferences` per + * https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#proposer_preferences + */ +export async function validateGossipProposerPreferences( + chain: IBeaconChain, + signedProposerPreferences: gloas.SignedProposerPreferences +): Promise { + const preferences = signedProposerPreferences.message; + const {proposalSlot, validatorIndex} = preferences; + const proposalEpoch = computeEpochAtSlot(proposalSlot); + + // [IGNORE] `preferences.proposal_slot` is in the current or next epoch. + const currentEpoch = chain.clock.currentEpoch; + if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch + 1) { + throw new ProposerPreferencesError(GossipAction.IGNORE, { + code: ProposerPreferencesErrorCode.INVALID_EPOCH, + proposalSlot, + currentEpoch, + }); + } + + // [IGNORE] `preferences.proposal_slot` has not already passed. + const currentSlot = chain.clock.currentSlot; + if (proposalSlot <= currentSlot) { + throw new ProposerPreferencesError(GossipAction.IGNORE, { + code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED, + proposalSlot, + currentSlot, + }); + } + + const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipProposerPreferences); + if (!isStatePostGloas(state)) { + throw new Error(`Expected gloas+ state for proposer preferences validation, got fork=${state.forkName}`); + } + + // [REJECT] `preferences.validator_index` is present at the correct slot in the current or next + // epoch's portion of `state.proposer_lookahead` — i.e. `is_valid_proposal_slot(state, preferences)` + // returns True. + const epochOffset = proposalEpoch - state.epoch; + const proposers = epochOffset === 0 ? state.currentProposers : state.nextProposers; + const expectedProposer = proposers[proposalSlot % SLOTS_PER_EPOCH]; + if (epochOffset < 0 || epochOffset > 1 || expectedProposer !== validatorIndex) { + throw new ProposerPreferencesError(GossipAction.REJECT, { + code: ProposerPreferencesErrorCode.INVALID_PROPOSER, + proposalSlot, + validatorIndex, + }); + } + + // [IGNORE] The `signed_proposer_preferences` is the first valid message received from the validator + // with index `preferences.validator_index` and the given slot `preferences.proposal_slot`. + if (chain.seenProposerPreferences.isKnown(proposalSlot, validatorIndex)) { + throw new ProposerPreferencesError(GossipAction.IGNORE, { + code: ProposerPreferencesErrorCode.ALREADY_KNOWN, + proposalSlot, + validatorIndex, + }); + } + + // [REJECT] `signed_proposer_preferences.signature` is valid with respect to the validator's public key. + const signatureSet = createSingleSignatureSetFromComponents( + chain.pubkeyCache.getOrThrow(validatorIndex), + getProposerPreferencesSigningRoot(chain.config, preferences), + signedProposerPreferences.signature + ); + + if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true}))) { + throw new ProposerPreferencesError(GossipAction.REJECT, { + code: ProposerPreferencesErrorCode.INVALID_SIGNATURE, + proposalSlot, + validatorIndex, + }); + } + + // Valid + chain.seenProposerPreferences.add(proposalSlot, validatorIndex); +} diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index 76584a0ebc51..f981df5e7f6e 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -41,6 +41,7 @@ export enum GossipType { execution_payload = "execution_payload", payload_attestation_message = "payload_attestation_message", execution_payload_bid = "execution_payload_bid", + proposer_preferences = "proposer_preferences", } export type SequentialGossipType = Exclude; @@ -78,6 +79,7 @@ export type GossipTopicTypeMap = { [GossipType.execution_payload]: {type: GossipType.execution_payload}; [GossipType.payload_attestation_message]: {type: GossipType.payload_attestation_message}; [GossipType.execution_payload_bid]: {type: GossipType.execution_payload_bid}; + [GossipType.proposer_preferences]: {type: GossipType.proposer_preferences}; }; export type GossipTopicMap = { @@ -110,6 +112,7 @@ export type GossipTypeMap = { [GossipType.execution_payload]: gloas.SignedExecutionPayloadEnvelope; [GossipType.payload_attestation_message]: gloas.PayloadAttestationMessage; [GossipType.execution_payload_bid]: gloas.SignedExecutionPayloadBid; + [GossipType.proposer_preferences]: gloas.SignedProposerPreferences; }; export type GossipFnByType = { @@ -141,6 +144,9 @@ export type GossipFnByType = { payloadAttestationMessage: gloas.PayloadAttestationMessage ) => Promise | void; [GossipType.execution_payload_bid]: (executionPayloadBid: gloas.SignedExecutionPayloadBid) => Promise | void; + [GossipType.proposer_preferences]: ( + signedProposerPreferences: gloas.SignedProposerPreferences + ) => Promise | void; }; export type GossipFn = GossipFnByType[keyof GossipFnByType]; diff --git a/packages/beacon-node/src/network/gossip/scoringParameters.ts b/packages/beacon-node/src/network/gossip/scoringParameters.ts index 669d188ae8c9..3ad8ee2a673d 100644 --- a/packages/beacon-node/src/network/gossip/scoringParameters.ts +++ b/packages/beacon-node/src/network/gossip/scoringParameters.ts @@ -27,6 +27,7 @@ const BLS_TO_EXECUTION_CHANGE_WEIGHT = 0.05; const EXECUTION_PAYLOAD_WEIGHT = 0.5; const PAYLOAD_ATTESTATION_WEIGHT = 0.05; const EXECUTION_PAYLOAD_BID_WEIGHT = 0.05; +const PROPOSER_PREFERENCES_WEIGHT = 0.05; const beaconAttestationSubnetWeight = 1 / ATTESTATION_SUBNET_COUNT; const maxPositiveScore = @@ -40,7 +41,8 @@ const maxPositiveScore = BLS_TO_EXECUTION_CHANGE_WEIGHT + EXECUTION_PAYLOAD_WEIGHT + PAYLOAD_ATTESTATION_WEIGHT + - EXECUTION_PAYLOAD_BID_WEIGHT); + EXECUTION_PAYLOAD_BID_WEIGHT + + PROPOSER_PREFERENCES_WEIGHT); /** * The following params is implemented by Lighthouse at @@ -198,6 +200,17 @@ function getAllTopicsScoreParams( expectedMessageRate: 1024, // TODO GLOAS: Need an estimate for this firstMessageDecayTime: epochDurationMs * 100, }); + topicsParams[ + stringifyGossipTopic(config, { + type: GossipType.proposer_preferences, + boundary, + }) + ] = getTopicScoreParams(config, precomputedParams, { + topicWeight: PROPOSER_PREFERENCES_WEIGHT, + // Upper bound ~64 messages per epoch: one per proposer across the current + next epoch. + expectedMessageRate: 64 / SLOTS_PER_EPOCH, + firstMessageDecayTime: epochDurationMs * 100, + }); // other topics topicsParams[ diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index e01343d6bbf5..2ad9b5173a65 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -73,6 +73,7 @@ function stringifyGossipTopicType(topic: GossipTopic): string { case GossipType.execution_payload: case GossipType.payload_attestation_message: case GossipType.execution_payload_bid: + case GossipType.proposer_preferences: return topic.type; case GossipType.beacon_attestation: case GossipType.sync_committee: @@ -124,6 +125,8 @@ export function getGossipSSZType(topic: GossipTopic) { return ssz.gloas.PayloadAttestationMessage; case GossipType.execution_payload_bid: return ssz.gloas.SignedExecutionPayloadBid; + case GossipType.proposer_preferences: + return ssz.gloas.SignedProposerPreferences; } } @@ -203,6 +206,7 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr: case GossipType.execution_payload: case GossipType.payload_attestation_message: case GossipType.execution_payload_bid: + case GossipType.proposer_preferences: return {type: gossipTypeStr, boundary, encoding}; } @@ -257,6 +261,7 @@ export function getCoreTopicsAtFork( topics.push({type: GossipType.execution_payload}); topics.push({type: GossipType.payload_attestation_message}); topics.push({type: GossipType.execution_payload_bid}); + topics.push({type: GossipType.proposer_preferences}); } // After fulu also track data_column_sidecar_{index} @@ -351,4 +356,5 @@ export const gossipTopicIgnoreDuplicatePublishError: Record [GossipType.execution_payload]: true, [GossipType.payload_attestation_message]: true, [GossipType.execution_payload_bid]: true, + [GossipType.proposer_preferences]: true, }; diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 0ac83c1a0658..7a896d469457 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -79,6 +79,7 @@ import { import {validateLightClientFinalityUpdate} from "../../chain/validation/lightClientFinalityUpdate.js"; import {validateLightClientOptimisticUpdate} from "../../chain/validation/lightClientOptimisticUpdate.js"; import {validateGossipPayloadAttestationMessage} from "../../chain/validation/payloadAttestationMessage.js"; +import {validateGossipProposerPreferences} from "../../chain/validation/proposerPreferences.js"; import {OpSource} from "../../chain/validatorMonitor.js"; import {Metrics} from "../../metrics/index.js"; import {kzgCommitmentToVersionedHash} from "../../util/blobs.js"; @@ -1170,6 +1171,14 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand data: executionPayloadBid, }); }, + [GossipType.proposer_preferences]: async ({ + gossipData, + topic, + }: GossipHandlerParamGeneric) => { + const {serializedData} = gossipData; + const signedProposerPreferences = sszDeserialize(topic, serializedData); + await validateGossipProposerPreferences(chain, signedProposerPreferences); + }, }; } diff --git a/packages/beacon-node/src/network/processor/gossipQueues/index.ts b/packages/beacon-node/src/network/processor/gossipQueues/index.ts index 5f4200b9eab9..0c05c2ac9b52 100644 --- a/packages/beacon-node/src/network/processor/gossipQueues/index.ts +++ b/packages/beacon-node/src/network/processor/gossipQueues/index.ts @@ -83,6 +83,11 @@ const linearGossipQueueOpts: { type: QueueType.FIFO, dropOpts: {type: DropType.count, count: 1}, }, + [GossipType.proposer_preferences]: { + maxLength: 1024, + type: QueueType.FIFO, + dropOpts: {type: DropType.count, count: 1}, + }, }; const indexedGossipQueueOpts: { diff --git a/packages/beacon-node/src/network/processor/index.ts b/packages/beacon-node/src/network/processor/index.ts index 8e51e35d95d2..d67890cf647d 100644 --- a/packages/beacon-node/src/network/processor/index.ts +++ b/packages/beacon-node/src/network/processor/index.ts @@ -91,6 +91,7 @@ const executeGossipWorkOrderObj: Record = { [GossipType.execution_payload]: {bypassQueue: true}, [GossipType.payload_attestation_message]: {}, [GossipType.execution_payload_bid]: {}, + [GossipType.proposer_preferences]: {}, }; const executeGossipWorkOrder = Object.keys(executeGossipWorkOrderObj) as (keyof typeof executeGossipWorkOrderObj)[]; diff --git a/packages/beacon-node/test/unit/network/gossip/scoringParameters.test.ts b/packages/beacon-node/test/unit/network/gossip/scoringParameters.test.ts index aa05f51a96c4..a3934a68f09e 100644 --- a/packages/beacon-node/test/unit/network/gossip/scoringParameters.test.ts +++ b/packages/beacon-node/test/unit/network/gossip/scoringParameters.test.ts @@ -80,7 +80,7 @@ describe("computeGossipPeerScoreParams", () => { expect(params.firstMessageDeliveriesWeight).closeTo(1.8407, TOLERANCE); expect(params.firstMessageDeliveriesDecay).closeTo(0.99856, TOLERANCE); expect(params.firstMessageDeliveriesCap).closeTo(21.73035, TOLERANCE); - expect(params.invalidMessageDeliveriesWeight).closeTo(-2800, TOLERANCE); + expect(params.invalidMessageDeliveriesWeight).closeTo(-2850, TOLERANCE); expect(params.invalidMessageDeliveriesDecay).closeTo(0.99713, TOLERANCE); } @@ -106,7 +106,7 @@ describe("computeGossipPeerScoreParams", () => { expect(params.firstMessageDeliveriesWeight).closeTo(36.81486, TOLERANCE); expect(params.firstMessageDeliveriesDecay).closeTo(0.998561, TOLERANCE); expect(params.firstMessageDeliveriesCap).closeTo(1.08652, TOLERANCE); - expect(params.invalidMessageDeliveriesWeight).closeTo(-2800.0, TOLERANCE); + expect(params.invalidMessageDeliveriesWeight).closeTo(-2850.0, TOLERANCE); expect(params.invalidMessageDeliveriesDecay).closeTo(0.99713, TOLERANCE); } @@ -124,7 +124,7 @@ describe("computeGossipPeerScoreParams", () => { expect(params.firstMessageDeliveriesWeight).closeTo(0.33509, TOLERANCE); expect(params.firstMessageDeliveriesDecay).closeTo(0.86596, TOLERANCE); expect(params.firstMessageDeliveriesCap).closeTo(119.3712, TOLERANCE); - expect(params.invalidMessageDeliveriesWeight).closeTo(-280.0, TOLERANCE); + expect(params.invalidMessageDeliveriesWeight).closeTo(-285.0, TOLERANCE); expect(params.invalidMessageDeliveriesDecay).closeTo(0.99713, TOLERANCE); // Check message rate penalty params @@ -132,11 +132,11 @@ describe("computeGossipPeerScoreParams", () => { expect(params.meshMessageDeliveriesCap).closeTo(68.6255, TOLERANCE); expect(params.meshMessageDeliveriesActivation).toEqual(384 * 1000); expect(params.meshMessageDeliveriesWindow).toEqual(12 * 1000); - expect(params.meshFailurePenaltyWeight).closeTo(-0.95127, TOLERANCE); + expect(params.meshFailurePenaltyWeight).closeTo(-0.96826, TOLERANCE); expect(params.meshFailurePenaltyDecay).closeTo(0.93057, TOLERANCE); if (penaltiesActive) { - expect(params.meshMessageDeliveriesWeight).closeTo(-0.95127, TOLERANCE); + expect(params.meshMessageDeliveriesWeight).closeTo(-0.96826, TOLERANCE); expect(params.meshMessageDeliveriesThreshold).closeTo(17.15638, TOLERANCE); } else { expect(params.meshMessageDeliveriesWeight).toEqual(0.0); @@ -158,7 +158,7 @@ describe("computeGossipPeerScoreParams", () => { expect(params.firstMessageDeliveriesWeight).closeTo(1.14716, TOLERANCE); expect(params.firstMessageDeliveriesDecay).closeTo(0.99283, TOLERANCE); expect(params.firstMessageDeliveriesCap).closeTo(34.8687, TOLERANCE); - expect(params.invalidMessageDeliveriesWeight).closeTo(-280.0, TOLERANCE); + expect(params.invalidMessageDeliveriesWeight).closeTo(-285.0, TOLERANCE); expect(params.invalidMessageDeliveriesDecay).closeTo(0.99713, TOLERANCE); // Check message rate penalty params @@ -166,11 +166,11 @@ describe("computeGossipPeerScoreParams", () => { expect(params.meshMessageDeliveriesCap).closeTo(2.0547574, TOLERANCE); expect(params.meshMessageDeliveriesActivation).toEqual(384 * 1000); expect(params.meshMessageDeliveriesWindow).toEqual(12 * 1000); - expect(params.meshFailurePenaltyWeight).closeTo(-596.8696, TOLERANCE); + expect(params.meshFailurePenaltyWeight).closeTo(-607.52794, TOLERANCE); expect(params.meshFailurePenaltyDecay).closeTo(0.97163, TOLERANCE); if (penaltiesActive) { - expect(params.meshMessageDeliveriesWeight).closeTo(-596.8696, TOLERANCE); + expect(params.meshMessageDeliveriesWeight).closeTo(-607.52794, TOLERANCE); expect(params.meshMessageDeliveriesThreshold).closeTo(0.68491, TOLERANCE); } else { expect(params.meshMessageDeliveriesWeight).toEqual(0.0); @@ -200,7 +200,7 @@ describe("computeGossipPeerScoreParams", () => { expect(params.firstMessageDeliveriesWeight).closeTo(2.6807, TOLERANCE); expect(params.firstMessageDeliveriesDecay).closeTo(0.86596, TOLERANCE); expect(params.firstMessageDeliveriesCap).closeTo(14.9214, TOLERANCE); - expect(params.invalidMessageDeliveriesWeight).closeTo(-8960.0, TOLERANCE); + expect(params.invalidMessageDeliveriesWeight).closeTo(-9120.0, TOLERANCE); expect(params.invalidMessageDeliveriesDecay).closeTo(0.99713, TOLERANCE); // Check message rate penalty params @@ -208,11 +208,11 @@ describe("computeGossipPeerScoreParams", () => { expect(params.meshMessageDeliveriesCap).closeTo(69.88248, TOLERANCE); expect(params.meshMessageDeliveriesActivation).toEqual(204 * 1000); expect(params.meshMessageDeliveriesWindow).toEqual(12 * 1000); - expect(params.meshFailurePenaltyWeight).closeTo(-469.69001, TOLERANCE); + expect(params.meshFailurePenaltyWeight).closeTo(-478.07737, TOLERANCE); expect(params.meshFailurePenaltyDecay).closeTo(0.96466, TOLERANCE); if (penaltiesActive) { - expect(params.meshMessageDeliveriesWeight).closeTo(-469.69001, TOLERANCE); + expect(params.meshMessageDeliveriesWeight).closeTo(-478.07737, TOLERANCE); expect(params.meshMessageDeliveriesThreshold).closeTo(4.367655, TOLERANCE); } else { expect(params.meshMessageDeliveriesWeight).toEqual(0.0); diff --git a/packages/beacon-node/test/unit/network/gossip/topic.test.ts b/packages/beacon-node/test/unit/network/gossip/topic.test.ts index 7e9b3f6ddd46..5caa4be1802e 100644 --- a/packages/beacon-node/test/unit/network/gossip/topic.test.ts +++ b/packages/beacon-node/test/unit/network/gossip/topic.test.ts @@ -167,6 +167,16 @@ describe("network / gossip / topic", () => { topicStr: "/eth2/a41d57bd/execution_payload_bid/ssz_snappy", }, ], + [GossipType.proposer_preferences]: [ + { + topic: { + type: GossipType.proposer_preferences, + boundary: {fork: ForkName.gloas, epoch: config.GLOAS_FORK_EPOCH}, + encoding, + }, + topicStr: "/eth2/a41d57bd/proposer_preferences/ssz_snappy", + }, + ], }; for (const topics of Object.values(testCases)) { diff --git a/packages/state-transition/src/signatureSets/index.ts b/packages/state-transition/src/signatureSets/index.ts index d76ac2f10f07..67ffbc6ef3f3 100644 --- a/packages/state-transition/src/signatureSets/index.ts +++ b/packages/state-transition/src/signatureSets/index.ts @@ -20,6 +20,7 @@ export * from "./executionPayloadEnvelope.js"; export * from "./indexedAttestation.js"; export * from "./indexedPayloadAttestation.js"; export * from "./proposer.js"; +export * from "./proposerPreferences.js"; export * from "./proposerSlashings.js"; export * from "./randao.js"; export * from "./voluntaryExits.js"; diff --git a/packages/state-transition/src/signatureSets/proposerPreferences.ts b/packages/state-transition/src/signatureSets/proposerPreferences.ts new file mode 100644 index 000000000000..667abbd67dfc --- /dev/null +++ b/packages/state-transition/src/signatureSets/proposerPreferences.ts @@ -0,0 +1,12 @@ +import {BeaconConfig} from "@lodestar/config"; +import {DOMAIN_PROPOSER_PREFERENCES} from "@lodestar/params"; +import {gloas, ssz} from "@lodestar/types"; +import {computeSigningRoot} from "../util/index.js"; + +export function getProposerPreferencesSigningRoot( + config: BeaconConfig, + preferences: gloas.ProposerPreferences +): Uint8Array { + const domain = config.getDomain(preferences.proposalSlot, DOMAIN_PROPOSER_PREFERENCES); + return computeSigningRoot(ssz.gloas.ProposerPreferences, preferences, domain); +}