@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
128 lines • 5.47 kB
JavaScript
import { computeStartSlotAtEpoch } from "@lodestar/state-transition";
import { isDaOutOfRange } from "../blocks/blockInput/index.js";
import { PayloadEnvelopeInput } from "../blocks/payloadEnvelopeInput/index.js";
import { ChainEvent } from "../emitter.js";
export { PayloadEnvelopeInput } from "../blocks/payloadEnvelopeInput/index.js";
/**
* Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
*
* Created whenever we have a block because it needs block bid.
* Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
* (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
* transiently hold more during forks, range-sync bursts, or when `prepareNextSlot` skips
* ticks; subsequent ticks settle it back.
*/
export class SeenPayloadEnvelopeInput {
config;
clock;
forkChoice;
chainEvents;
signal;
serializedCache;
metrics;
logger;
payloadInputs = new Map();
constructor({ config, clock, forkChoice, chainEvents, signal, serializedCache, metrics, logger, }) {
this.config = config;
this.clock = clock;
this.forkChoice = forkChoice;
this.chainEvents = chainEvents;
this.signal = signal;
this.serializedCache = serializedCache;
this.metrics = metrics;
this.logger = logger;
if (metrics) {
metrics.seenCache.payloadEnvelopeInput.count.addCollect(() => {
metrics.seenCache.payloadEnvelopeInput.count.set(this.payloadInputs.size);
metrics.seenCache.payloadEnvelopeInput.serializedObjectRefs.set(Array.from(this.payloadInputs.values()).reduce((count, payloadInput) => count + payloadInput.getSerializedCacheKeys().length, 0));
});
}
this.chainEvents.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
this.signal.addEventListener("abort", () => {
this.chainEvents.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
});
}
pruneFinalized = (checkpoint) => {
const finalizedSlot = computeStartSlotAtEpoch(checkpoint.epoch);
let deletedCount = 0;
for (const [, input] of this.payloadInputs) {
if (input.slot < finalizedSlot) {
this.evictPayloadInput(input);
deletedCount++;
}
}
this.logger?.debug("SeenPayloadEnvelopeInput.pruneFinalized deleted entries", {
finalizedSlot,
finalizedRoot: checkpoint.rootHex,
deletedCount,
});
};
add(props) {
const existing = this.payloadInputs.get(props.blockRootHex);
if (existing !== undefined) {
this.logger?.verbose("SeenPayloadEnvelopeInput.add reused existing entry", {
slot: existing.slot,
root: props.blockRootHex,
});
return existing;
}
const daOutOfRange = isDaOutOfRange(this.config, props.forkName, props.block.message.slot, this.clock.currentEpoch);
const input = PayloadEnvelopeInput.createFromBlock({ ...props, daOutOfRange });
this.payloadInputs.set(props.blockRootHex, input);
this.metrics?.seenCache.payloadEnvelopeInput.created.inc();
this.logger?.verbose("SeenPayloadEnvelopeInput.add created new entry", {
slot: input.slot,
root: props.blockRootHex,
daOutOfRange,
});
return input;
}
/**
* Used at chain initialization to seed the anchor block's PayloadEnvelopeInput from
* `state.latestExecutionPayloadBid`.
*/
addFromBid(props) {
const existing = this.payloadInputs.get(props.blockRootHex);
if (existing !== undefined) {
return existing;
}
const daOutOfRange = isDaOutOfRange(this.config, props.forkName, props.slot, this.clock.currentEpoch);
const input = PayloadEnvelopeInput.createFromBid({ ...props, daOutOfRange });
this.payloadInputs.set(props.blockRootHex, input);
this.metrics?.seenCache.payloadEnvelopeInput.created.inc();
this.logger?.verbose("SeenPayloadEnvelopeInput.addFromBid created new entry", {
slot: input.slot,
root: props.blockRootHex,
daOutOfRange,
});
return input;
}
get(blockRootHex) {
return this.payloadInputs.get(blockRootHex);
}
hasPayload(blockRootHex) {
return this.payloadInputs.get(blockRootHex)?.hasPayloadEnvelope() ?? false;
}
size() {
return this.payloadInputs.size;
}
pruneBelowParent(parentBlock) {
for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
if (block.slot < parentBlock.slot) {
const input = this.payloadInputs.get(block.blockRoot);
if (input) {
this.evictPayloadInput(input);
this.logger?.verbose("SeenPayloadEnvelopeInput.pruneBelowParent deleted", {
slot: block.slot,
root: block.blockRoot,
});
}
}
}
}
evictPayloadInput(payloadInput) {
this.serializedCache.delete(payloadInput.getSerializedCacheKeys());
this.payloadInputs.delete(payloadInput.blockRootHex);
}
}
//# sourceMappingURL=seenPayloadEnvelopeInput.js.map