Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
10ed9c2
feat: implement SingleAttestationPool
twoeths Jul 22, 2025
6267763
feat: implement getAttestationsForBlockElectraBySlot for aggregated a…
twoeths Jul 22, 2025
508461f
feat: implement getAttestationsForBlock.ts
twoeths Jul 22, 2025
b6fbfa9
feat: singleAttestationPool metrics
twoeths Jul 22, 2025
9cb6514
fix: track scanned slots of SingleAttestationPool
twoeths Jul 22, 2025
7f766a4
fix: lint
twoeths Jul 22, 2025
15e29f1
feat: capture metrics for SingleAttestationPool
twoeths Jul 22, 2025
9e088ee
feat: populate SingleAttestationPool
twoeths Jul 22, 2025
c4543a5
fix: SingleAttestationPool.add()
twoeths Jul 23, 2025
e66970c
feat: bound SingleAttestationPool
twoeths Jul 23, 2025
edb3e13
fix: break early if there are too many consolidations
twoeths Jul 23, 2025
16d2445
fix: increase max consolidations
twoeths Jul 24, 2025
9c19fa7
chore: rewrite SingleAttestationPool.prune()
twoeths Jul 24, 2025
95a5bb2
fix: produceBlockV3 unit test
twoeths Jul 24, 2025
7bc016d
fix: track packed attestations by buckets
twoeths Jul 24, 2025
682701d
fix: avoid showing stale metrics on grafana
twoeths Jul 24, 2025
7d45fe8
feat: store more SingleAttestation slots (#8084)
twoeths Jul 25, 2025
5e3b572
chore: unit tests for SinleAttestationPool
twoeths Jul 28, 2025
750c8e5
chore: unit tests for AggregatedAttestationPool
twoeths Jul 28, 2025
487c40d
feat: bound SingleAttestationPool by MAX_ATTESTATIONS_RETAINED
twoeths Jul 29, 2025
e57de03
fix: simplify getAttestationsForBlock() parameters
twoeths Jul 29, 2025
d8e959f
chore: getAttestationsForBlock() unit tests
twoeths Jul 29, 2025
af31d5e
fix: set MAX_ATTESTATIONS_RETAINED to 150k and track attestation coun…
twoeths Jul 30, 2025
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
20 changes: 19 additions & 1 deletion packages/beacon-node/src/api/impl/beacon/pool/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import {routes} from "@lodestar/api";
import {ApplicationMethods} from "@lodestar/api/server";
import {ForkPostElectra, ForkPreElectra, SYNC_COMMITTEE_SUBNET_SIZE, isForkPostElectra} from "@lodestar/params";
import {Attestation, Epoch, SingleAttestation, isElectraAttestation, ssz} from "@lodestar/types";
import {
Attestation,
Epoch,
SingleAttestation,
isElectraAttestation,
isElectraSingleAttestation,
ssz,
} from "@lodestar/types";
import {
AttestationError,
AttestationErrorCode,
Expand Down Expand Up @@ -127,6 +134,17 @@ export function getBeaconPoolApi({
metrics?.opPool.attestationPool.apiInsertOutcome.inc({insertOutcome});
}

if (isElectraSingleAttestation(attestation)) {
const insertOutcome = chain.singleAttestationPool.add(
committeeIndex,
attestation,
attDataRootHex,
committeeValidatorIndex,
committeeSize
);
metrics?.opPool.singleAttestationPool.apiInsertOutcome.inc({insertOutcome});
}

if (isForkPostElectra(fork)) {
chain.emitter.emit(
routes.events.EventType.singleAttestation,
Expand Down
24 changes: 16 additions & 8 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
import {ChainEvent, CheckpointHex, CommonBlockBody} from "../../../chain/index.js";
import {SCHEDULER_LOOKAHEAD_FACTOR} from "../../../chain/prepareNextSlot.js";
import {RegenCaller} from "../../../chain/regen/index.js";
import {AggregationInfo} from "../../../chain/seenCache/seenAggregateAndProof.js";
import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
import {ZERO_HASH} from "../../../constants/index.js";
Expand Down Expand Up @@ -1273,24 +1274,31 @@ export function getValidatorApi(
try {
// TODO: Validate in batch
const validateFn = () => validateApiAggregateAndProof(fork, chain, signedAggregateAndProof);
const {slot, beaconBlockRoot} = signedAggregateAndProof.message.aggregate.data;
const {slot, beaconBlockRoot, target} = signedAggregateAndProof.message.aggregate.data;
// when a validator is configured with multiple beacon node urls, this attestation may come from another beacon node
// and the block hasn't been in our forkchoice since we haven't seen / processing that block
// see https://github.com/ChainSafe/lodestar/issues/5098
const {indexedAttestation, committeeIndices, attDataRootHex} = await validateGossipFnRetryUnknownRoot(
validateFn,
network,
chain,
slot,
beaconBlockRoot
);
const {indexedAttestation, committeeIndices, attDataRootHex, committeeIndex} =
await validateGossipFnRetryUnknownRoot(validateFn, network, chain, slot, beaconBlockRoot);

const insertOutcome = chain.aggregatedAttestationPool.add(
signedAggregateAndProof.message.aggregate,
attDataRootHex,
indexedAttestation.attestingIndices.length,
committeeIndices
);
const aggregationInto: AggregationInfo = {
aggregationBits: signedAggregateAndProof.message.aggregate.aggregationBits,
trueBitCount: indexedAttestation.attestingIndices.length,
};
chain.addSeenAgregatedAttestation(
slot,
target.epoch,
committeeIndex,
attDataRootHex,
aggregationInto,
false
);
metrics?.opPool.aggregatedAttestationPool.apiInsertOutcome.inc({insertOutcome});

const sentPeers = await network.publishBeaconAggregateAndProof(signedAggregateAndProof);
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ export function addAttestationPostElectra(
const aggregationBits = BitArray.fromBoolArray(aggregationBools.slice(offset, offset + committee.length));
const trueBitCount = aggregationBits.getTrueBitIndexes().length;
offset += committee.length;
// no need to add to SingleAttestationPool because this block could be reorged
// it will also slow down the block import process
this.seenAggregatedAttestations.add(
target.epoch,
committeeIndices[i],
Expand Down
19 changes: 18 additions & 1 deletion packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
BeaconBlock,
BlindedBeaconBlock,
BlindedBeaconBlockBody,
CommitteeIndex,
Epoch,
ExecutionPayload,
Root,
Expand Down Expand Up @@ -76,6 +77,7 @@ import {
SyncCommitteeMessagePool,
SyncContributionAndProofPool,
} from "./opPools/index.js";
import {SingleAttestationPool} from "./opPools/singleAttestationPool.js";
import {IChainOptions} from "./options.js";
import {PrepareNextSlotScheduler} from "./prepareNextSlot.js";
import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js";
Expand All @@ -94,7 +96,7 @@ import {
SeenSyncCommitteeMessages,
} from "./seenCache/index.js";
import {SeenGossipBlockInput} from "./seenCache/index.js";
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
import {AggregationInfo, SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js";
import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
import {SeenBlockInputCache} from "./seenCache/seenBlockInput.js";
Expand Down Expand Up @@ -140,6 +142,7 @@ export class BeaconChain implements IBeaconChain {

// Ops pool
readonly attestationPool: AttestationPool;
readonly singleAttestationPool: SingleAttestationPool;
readonly aggregatedAttestationPool: AggregatedAttestationPool;
readonly syncCommitteeMessagePool: SyncCommitteeMessagePool;
readonly syncContributionAndProofPool;
Expand Down Expand Up @@ -248,6 +251,7 @@ export class BeaconChain implements IBeaconChain {
this.opts?.preaggregateSlotDistance,
metrics
);
this.singleAttestationPool = new SingleAttestationPool(metrics);
this.aggregatedAttestationPool = new AggregatedAttestationPool(this.config, metrics);
this.syncCommitteeMessagePool = new SyncCommitteeMessagePool(
clock,
Expand Down Expand Up @@ -992,6 +996,18 @@ export class BeaconChain implements IBeaconChain {
return state.epochCtx.getShufflingAtEpoch(attEpoch);
}

addSeenAgregatedAttestation(
slot: Slot,
targetEpoch: Epoch,
committeeIndex: CommitteeIndex,
attDataRoot: RootHex,
newItem: AggregationInfo,
checkIsKnown: boolean
): void {
this.seenAggregatedAttestations.add(targetEpoch, committeeIndex, attDataRoot, newItem, checkIsKnown);
this.singleAttestationPool.seenAggregatedAttestation(slot, attDataRoot, committeeIndex, newItem.aggregationBits);
}

/**
* `ForkChoice.onBlock` must never throw for a block that is valid with respect to the network
* `justifiedBalancesGetter()` must never throw and it should always return a state.
Expand Down Expand Up @@ -1138,6 +1154,7 @@ export class BeaconChain implements IBeaconChain {
this.metrics?.clockSlot.set(slot);

this.attestationPool.prune(slot);
this.singleAttestationPool.prune(slot);
this.aggregatedAttestationPool.prune(slot);
this.syncCommitteeMessagePool.prune(slot);
this.seenSyncCommitteeMessages.prune(slot);
Expand Down
14 changes: 13 additions & 1 deletion packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
BeaconBlock,
BlindedBeaconBlock,
CommitteeIndex,
Epoch,
ExecutionPayload,
Root,
Expand Down Expand Up @@ -42,6 +43,7 @@ import {ForkchoiceCaller} from "./forkChoice/index.js";
import {LightClientServer} from "./lightClient/index.js";
import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool.js";
import {AttestationPool, OpPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools/index.js";
import {SingleAttestationPool} from "./opPools/singleAttestationPool.js";
import {IChainOptions} from "./options.js";
import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/produceBlockBody.js";
import {IStateRegenerator, RegenCaller} from "./regen/index.js";
Expand All @@ -57,7 +59,7 @@ import {
SeenSyncCommitteeMessages,
} from "./seenCache/index.js";
import {SeenGossipBlockInput} from "./seenCache/index.js";
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
import {AggregationInfo, SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js";
import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
import {SeenBlockInputCache} from "./seenCache/seenBlockInput.js";
Expand Down Expand Up @@ -111,6 +113,7 @@ export interface IBeaconChain {

// Ops pool
readonly attestationPool: AttestationPool;
readonly singleAttestationPool: SingleAttestationPool;
readonly aggregatedAttestationPool: AggregatedAttestationPool;
readonly syncCommitteeMessagePool: SyncCommitteeMessagePool;
readonly syncContributionAndProofPool: SyncContributionAndProofPool;
Expand Down Expand Up @@ -260,6 +263,15 @@ export interface IBeaconChain {
blockRef: BeaconBlock | BlindedBeaconBlock,
validatorIds?: (ValidatorIndex | string)[]
): Promise<SyncCommitteeRewards>;

addSeenAgregatedAttestation(
slot: Slot,
targetEpoch: Epoch,
committeeIndex: CommitteeIndex,
attDataRoot: RootHex,
newItem: AggregationInfo,
checkIsKnown: boolean
): void;
}

export type SSZObjectType =
Expand Down
Loading
Loading