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
23 changes: 22 additions & 1 deletion packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ import {LightClientServer} from "./lightClient/index.js";
import {
AggregatedAttestationPool,
AttestationPool,
ExecutionPayloadBidPool,
OpPool,
PayloadAttestationPool,
SyncCommitteeMessagePool,
SyncContributionAndProofPool,
} from "./opPools/index.js";
Expand All @@ -85,6 +87,9 @@ import {
SeenAttesters,
SeenBlockProposers,
SeenContributionAndProof,
SeenExecutionPayloadBids,
SeenExecutionPayloadEnvelopes,
SeenPayloadAttesters,
SeenSyncCommitteeMessages,
} from "./seenCache/index.js";
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
Expand Down Expand Up @@ -140,12 +145,17 @@ export class BeaconChain implements IBeaconChain {
readonly aggregatedAttestationPool: AggregatedAttestationPool;
readonly syncCommitteeMessagePool: SyncCommitteeMessagePool;
readonly syncContributionAndProofPool;
readonly executionPayloadBidPool: ExecutionPayloadBidPool;
readonly payloadAttestationPool: PayloadAttestationPool;
readonly opPool: OpPool;

// Gossip seen cache
readonly seenAttesters = new SeenAttesters();
readonly seenAggregators = new SeenAggregators();
readonly seenPayloadAttesters = new SeenPayloadAttesters();
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
readonly seenExecutionPayloadEnvelopes = new SeenExecutionPayloadEnvelopes();
readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
readonly seenBlockProposers = new SeenBlockProposers();
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
readonly seenContributionAndProof: SeenContributionAndProof;
Expand Down Expand Up @@ -262,6 +272,8 @@ export class BeaconChain implements IBeaconChain {
this.aggregatedAttestationPool = new AggregatedAttestationPool(this.config, metrics);
this.syncCommitteeMessagePool = new SyncCommitteeMessagePool(config, clock, this.opts?.preaggregateSlotDistance);
this.syncContributionAndProofPool = new SyncContributionAndProofPool(config, clock, metrics, logger);
this.executionPayloadBidPool = new ExecutionPayloadBidPool();
this.payloadAttestationPool = new PayloadAttestationPool(config, clock, metrics);
this.opPool = new OpPool(config);

this.seenAggregatedAttestations = new SeenAggregatedAttestations(metrics);
Expand Down Expand Up @@ -455,6 +467,8 @@ export class BeaconChain implements IBeaconChain {
this.seenAttesters.isKnown(epoch, index) ||
// seenAggregators = single aggregator index, not participants of the aggregate
this.seenAggregators.isKnown(epoch, index) ||
// seenPayloadAttesters = single signer of payload attestation message
this.seenPayloadAttesters.isKnown(epoch, index) ||
// seenBlockProposers = single block proposer
this.seenBlockProposers.seenAtEpoch(epoch, index)
);
Expand Down Expand Up @@ -1097,6 +1111,7 @@ export class BeaconChain implements IBeaconChain {
metrics.opPool.proposerSlashingPoolSize.set(this.opPool.proposerSlashingsSize);
metrics.opPool.voluntaryExitPoolSize.set(this.opPool.voluntaryExitsSize);
metrics.opPool.syncCommitteeMessagePoolSize.set(this.syncCommitteeMessagePool.size);
metrics.opPool.payloadAttestationPool.size.set(this.payloadAttestationPool.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 @@ -1127,6 +1142,9 @@ export class BeaconChain implements IBeaconChain {
this.aggregatedAttestationPool.prune(slot);
this.syncCommitteeMessagePool.prune(slot);
this.seenSyncCommitteeMessages.prune(slot);
this.payloadAttestationPool.prune(slot);
this.executionPayloadBidPool.prune(slot);
this.seenExecutionPayloadBids.prune(slot);
this.seenAttestationDatas.onSlot(slot);
this.reprocessController.onSlot(slot);

Expand All @@ -1150,6 +1168,7 @@ export class BeaconChain implements IBeaconChain {

this.seenAttesters.prune(epoch);
this.seenAggregators.prune(epoch);
this.seenPayloadAttesters.prune(epoch);
this.seenAggregatedAttestations.prune(epoch);
this.seenBlockAttesters.prune(epoch);
this.beaconProposerCache.prune(epoch);
Expand All @@ -1166,7 +1185,9 @@ export class BeaconChain implements IBeaconChain {

private async onForkChoiceFinalized(this: BeaconChain, cp: CheckpointWithHex): Promise<void> {
this.logger.verbose("Fork choice finalized", {epoch: cp.epoch, root: cp.rootHex});
this.seenBlockProposers.prune(computeStartSlotAtEpoch(cp.epoch));
const finalizedSlot = computeStartSlotAtEpoch(cp.epoch);
this.seenBlockProposers.prune(finalizedSlot);
this.seenExecutionPayloadEnvelopes.prune(finalizedSlot);

// Update validator custody to account for effective balance changes
await this.updateValidatorsCustodyRequirement(cp);
Expand Down
12 changes: 11 additions & 1 deletion packages/beacon-node/src/chain/errors/attestationError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ export enum AttestationErrorCode {
* Electra: Attester not in committee
*/
ATTESTER_NOT_IN_COMMITTEE = "ATTESTATION_ERROR_ATTESTER_NOT_IN_COMMITTEE",
/**
* Gloas: Invalid attestationData index: is non-zero and non-one
*/
INVALID_PAYLOAD_STATUS_VALUE = "ATTESTATION_ERROR_INVALID_PAYLOAD_STATUS_VALUE",
/**
* Gloas: Current slot attestation is marking payload as present
*/
PREMATURELY_INDICATED_PAYLOAD_PRESENT = "ATTESTATION_ERROR_PREMATURELY_INDICATED_PAYLOAD_PRESENT",
}

export type AttestationErrorType =
Expand Down Expand Up @@ -175,7 +183,9 @@ export type AttestationErrorType =
| {code: AttestationErrorCode.TOO_MANY_SKIPPED_SLOTS; headBlockSlot: Slot; attestationSlot: Slot}
| {code: AttestationErrorCode.NOT_EXACTLY_ONE_COMMITTEE_BIT_SET}
| {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX}
| {code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE};
| {code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE}
| {code: AttestationErrorCode.INVALID_PAYLOAD_STATUS_VALUE}
| {code: AttestationErrorCode.PREMATURELY_INDICATED_PAYLOAD_PRESENT};

export class AttestationError extends GossipActionError<AttestationErrorType> {
getMetadata(): Record<string, string | number | null> {
Expand Down
39 changes: 39 additions & 0 deletions packages/beacon-node/src/chain/errors/executionPayloadBid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
import {GossipActionError} from "./gossipValidation.ts";

export enum ExecutionPayloadBidErrorCode {
BUILDER_NOT_ELIGIBLE = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_NOT_ELIGIBLE",
BUILDER_BAD_CREDENTIALS = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_BAD_CREDENTIALS",
NON_ZERO_EXECUTION_PAYMENT = "EXECUTION_PAYLOAD_BID_ERROR_NON_ZERO_EXECUTION_PAYMENT",
BID_ALREADY_KNOWN = "EXECUTION_PAYLOAD_BID_ERROR_BID_ALREADY_KNOWN",
BID_TOO_LOW = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_LOW",
BID_TOO_HIGH = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_HIGH",
UNKNOWN_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_PARENT_BLOCK_HASH",
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",
}

export type ExecutionPayloadBidErrorType =
| {code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE; builderIndex: ValidatorIndex}
Copy link
Member

Choose a reason for hiding this comment

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

a lot of these builderIndex: ValidatorIndex we will need to change once builders are no longer validators

| {code: ExecutionPayloadBidErrorCode.BUILDER_BAD_CREDENTIALS; builderIndex: ValidatorIndex}
| {
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT;
builderIndex: ValidatorIndex;
executionPayment: number;
}
| {
code: ExecutionPayloadBidErrorCode.BID_ALREADY_KNOWN;
builderIndex: ValidatorIndex;
slot: Slot;
parentBlockRoot: RootHex;
parentBlockHash: RootHex;
}
| {code: ExecutionPayloadBidErrorCode.BID_TOO_LOW; bidValue: number; currentHighestBid: number}
| {code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH; bidValue: number; builderBalance: number}
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT; parentBlockRoot: RootHex}
| {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: ValidatorIndex; slot: Slot}
| {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: ValidatorIndex; slot: Slot};

export class ExecutionPayloadBidError extends GossipActionError<ExecutionPayloadBidErrorType> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
import {GossipActionError} from "./gossipValidation.js";

export enum ExecutionPayloadEnvelopeErrorCode {
BELONG_TO_FINALIZED_BLOCK = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BELONG_TO_FINALIZED_BLOCK",
BLOCK_ROOT_UNKNOWN = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BLOCK_ROOT_UNKNOWN",
ENVELOPE_ALREADY_KNOWN = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_ALREADY_KNOWN",
INVALID_BLOCK = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_INVALID_BLOCK",
SLOT_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_SLOT_MISMATCH",
BUILDER_INDEX_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BUILDER_INDEX_MISMATCH",
BLOCK_HASH_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BLOCK_HASH_MISMATCH",
INVALID_SIGNATURE = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_INVALID_SIGNATURE",
CACHE_FAIL = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_CACHE_FAIL",
}
export type ExecutionPayloadEnvelopeErrorType =
| {code: ExecutionPayloadEnvelopeErrorCode.BELONG_TO_FINALIZED_BLOCK; envelopeSlot: Slot; finalizedSlot: Slot}
| {code: ExecutionPayloadEnvelopeErrorCode.BLOCK_ROOT_UNKNOWN; blockRoot: RootHex}
| {
code: ExecutionPayloadEnvelopeErrorCode.ENVELOPE_ALREADY_KNOWN;
blockRoot: RootHex;
slot: Slot;
builderIndex: ValidatorIndex;
}
| {code: ExecutionPayloadEnvelopeErrorCode.INVALID_BLOCK; blockRoot: RootHex}
| {code: ExecutionPayloadEnvelopeErrorCode.SLOT_MISMATCH; envelopeSlot: Slot; blockSlot: Slot}
| {
code: ExecutionPayloadEnvelopeErrorCode.BUILDER_INDEX_MISMATCH;
envelopeBuilderIndex: ValidatorIndex;
bidBuilderIndex: ValidatorIndex;
}
| {code: ExecutionPayloadEnvelopeErrorCode.BLOCK_HASH_MISMATCH; envelopeBlockHash: RootHex; bidBlockHash: RootHex}
| {code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE}
| {code: ExecutionPayloadEnvelopeErrorCode.CACHE_FAIL; blockRoot: RootHex};

export class ExecutionPayloadEnvelopeError extends GossipActionError<ExecutionPayloadEnvelopeErrorType> {}
3 changes: 3 additions & 0 deletions packages/beacon-node/src/chain/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ export * from "./blobSidecarError.js";
export * from "./blockError.js";
export * from "./blsToExecutionChangeError.js";
export * from "./dataColumnSidecarError.js";
export * from "./executionPayloadBid.js";
export * from "./executionPayloadEnvelope.js";
export * from "./gossipValidation.js";
export * from "./payloadAttestation.js";
export * from "./proposerSlashingError.js";
export * from "./syncCommitteeError.js";
export * from "./voluntaryExitError.js";
25 changes: 25 additions & 0 deletions packages/beacon-node/src/chain/errors/payloadAttestation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
import {GossipActionError} from "./gossipValidation.ts";

export enum PayloadAttestationErrorCode {
NOT_CURRENT_SLOT = "PAYLOAD_ATTESTATION_ERROR_NOT_CURRENT_SLOT",
PAYLOAD_ATTESTATION_ALREADY_KNOWN = "PAYLOAD_ATTESTATION_ERROR_PAYLOAD_ATTESTATION_ALREADY_KNOWN",
UNKNOWN_BLOCK_ROOT = "PAYLOAD_ATTESTATION_ERROR_UNKNOWN_BLOCK_ROOT",
INVALID_BLOCK = "PAYLOAD_ATTESTATION_ERROR_INVALID_BLOCK",
INVALID_ATTESTER = "PAYLOAD_ATTESTATION_ERROR_INVALID_ATTESTER",
INVALID_SIGNATURE = "PAYLOAD_ATTESTATION_ERROR_INVALID_SIGNATURE",
}
export type PayloadAttestationErrorType =
| {code: PayloadAttestationErrorCode.NOT_CURRENT_SLOT; currentSlot: Slot; slot: Slot}
| {
code: PayloadAttestationErrorCode.PAYLOAD_ATTESTATION_ALREADY_KNOWN;
validatorIndex: ValidatorIndex;
slot: Slot;
blockRoot: RootHex;
}
| {code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT; blockRoot: RootHex}
| {code: PayloadAttestationErrorCode.INVALID_BLOCK; blockRoot: RootHex}
| {code: PayloadAttestationErrorCode.INVALID_ATTESTER; attesterIndex: ValidatorIndex}
| {code: PayloadAttestationErrorCode.INVALID_SIGNATURE};

export class PayloadAttestationError extends GossipActionError<PayloadAttestationErrorType> {}
19 changes: 19 additions & 0 deletions packages/beacon-node/src/chain/forkChoice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {ZERO_HASH_HEX} from "@lodestar/params";
import {
CachedBeaconStateAllForks,
CachedBeaconStateGloas,
DataAvailabilityStatus,
computeAnchorCheckpoint,
computeEpochAtSlot,
Expand Down Expand Up @@ -144,6 +145,15 @@ export function initializeForkChoiceFromFinalizedState(
: {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}),

dataAvailabilityStatus: DataAvailabilityStatus.PreData,
...(computeEpochAtSlot(blockHeader.slot) < state.config.GLOAS_FORK_EPOCH
? {
builderIndex: undefined,
blockHashHex: undefined,
}
: {
builderIndex: (state as CachedBeaconStateGloas).latestExecutionPayloadBid.builderIndex,
blockHashHex: toRootHex((state as CachedBeaconStateGloas).latestExecutionPayloadBid.blockHash),
}),
},
currentSlot
),
Expand Down Expand Up @@ -225,6 +235,15 @@ export function initializeForkChoiceFromUnfinalizedState(
: {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}),

dataAvailabilityStatus: DataAvailabilityStatus.PreData,
...(computeEpochAtSlot(blockHeader.slot) < unfinalizedState.config.GLOAS_FORK_EPOCH
? {
builderIndex: undefined,
blockHashHex: undefined,
}
: {
builderIndex: (unfinalizedState as CachedBeaconStateGloas).latestExecutionPayloadBid.builderIndex,
blockHashHex: toRootHex((unfinalizedState as CachedBeaconStateGloas).latestExecutionPayloadBid.blockHash),
}),
};

const parentSlot = blockHeader.slot - 1;
Expand Down
17 changes: 16 additions & 1 deletion packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ import {ForkchoiceCaller} from "./forkChoice/index.js";
import {GetBlobsTracker} from "./GetBlobsTracker.js";
import {LightClientServer} from "./lightClient/index.js";
import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool.js";
import {AttestationPool, OpPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools/index.js";
import {
AttestationPool,
ExecutionPayloadBidPool,
OpPool,
PayloadAttestationPool,
SyncCommitteeMessagePool,
SyncContributionAndProofPool,
} from "./opPools/index.js";
import {IChainOptions} from "./options.js";
import {AssembledBlockType, BlockAttributes, BlockType, ProduceResult} from "./produceBlock/produceBlockBody.js";
import {IStateRegenerator, RegenCaller} from "./regen/index.js";
Expand All @@ -56,6 +63,9 @@ import {
SeenAttesters,
SeenBlockProposers,
SeenContributionAndProof,
SeenExecutionPayloadBids,
SeenExecutionPayloadEnvelopes,
SeenPayloadAttesters,
SeenSyncCommitteeMessages,
} from "./seenCache/index.js";
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
Expand Down Expand Up @@ -116,12 +126,17 @@ export interface IBeaconChain {
readonly aggregatedAttestationPool: AggregatedAttestationPool;
readonly syncCommitteeMessagePool: SyncCommitteeMessagePool;
readonly syncContributionAndProofPool: SyncContributionAndProofPool;
readonly executionPayloadBidPool: ExecutionPayloadBidPool;
readonly payloadAttestationPool: PayloadAttestationPool;
readonly opPool: OpPool;

// Gossip seen cache
readonly seenAttesters: SeenAttesters;
readonly seenAggregators: SeenAggregators;
readonly seenPayloadAttesters: SeenPayloadAttesters;
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
readonly seenExecutionPayloadEnvelopes: SeenExecutionPayloadEnvelopes;
readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
readonly seenBlockProposers: SeenBlockProposers;
readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
readonly seenContributionAndProof: SeenContributionAndProof;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {Slot, gloas} from "@lodestar/types";
import {MapDef, toRootHex} from "@lodestar/utils";
import {InsertOutcome} from "./types.js";
import {pruneBySlot} from "./utils.js";

const SLOTS_RETAINED = 2;

type BlockRootHex = string;
type BlockHashHex = string;

/**
* Store the best execution payload bid per slot / (parent block root, parent block hash).
*/
export class ExecutionPayloadBidPool {
private readonly bidByParentHashByParentRootBySlot = new MapDef<
Slot,
MapDef<BlockRootHex, Map<BlockHashHex, gloas.ExecutionPayloadBid>>
>(() => new MapDef<BlockRootHex, Map<BlockHashHex, gloas.ExecutionPayloadBid>>(() => new Map()));
private lowestPermissibleSlot = 0;

add(bid: gloas.ExecutionPayloadBid): InsertOutcome {
const {slot, parentBlockRoot, parentBlockHash, value} = bid;
const lowestPermissibleSlot = this.lowestPermissibleSlot;

if (slot < lowestPermissibleSlot) {
return InsertOutcome.Old;
}

const parentRootHex = toRootHex(parentBlockRoot);
const parentHashHex = toRootHex(parentBlockHash);
const bidByParentHash = this.bidByParentHashByParentRootBySlot.getOrDefault(slot).getOrDefault(parentRootHex);
const existing = bidByParentHash.get(parentHashHex);

if (existing) {
const existingValue = existing.value;
const newValue = value;
if (newValue > existingValue) {
bidByParentHash.set(parentHashHex, bid);
return InsertOutcome.NewData;
}
return newValue === existingValue ? InsertOutcome.AlreadyKnown : InsertOutcome.NotBetterThan;
}

bidByParentHash.set(parentHashHex, bid);
return InsertOutcome.NewData;
}

/**
* Return the highest-value bid matching slot, parent block root, and parent block hash.
* Used for gossip validation and block production.
*/
getBestBid(
parentBlockRoot: BlockRootHex,
parentBlockHash: BlockHashHex,
slot: Slot
): gloas.ExecutionPayloadBid | null {
const bidByParentHash = this.bidByParentHashByParentRootBySlot.get(slot)?.get(parentBlockRoot);
return bidByParentHash?.get(parentBlockHash) ?? null;
}

prune(clockSlot: Slot): void {
this.lowestPermissibleSlot = pruneBySlot(this.bidByParentHashByParentRootBySlot, clockSlot, SLOTS_RETAINED);
}
}
2 changes: 2 additions & 0 deletions packages/beacon-node/src/chain/opPools/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export {AggregatedAttestationPool} from "./aggregatedAttestationPool.js";
export {AttestationPool} from "./attestationPool.js";
export {ExecutionPayloadBidPool} from "./executionPayloadBidPool.js";
export {OpPool} from "./opPool.js";
export {PayloadAttestationPool} from "./payloadAttestationPool.js";
export {SyncCommitteeMessagePool} from "./syncCommitteeMessagePool.js";
export {SyncContributionAndProofPool} from "./syncContributionAndProofPool.js";
Loading
Loading