@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
499 lines • 29.4 kB
JavaScript
import { BitArray } from "@chainsafe/ssz";
import { routes } from "@lodestar/api";
import { AncestorStatus, EpochDifference, ExecutionStatus, ForkChoiceError, ForkChoiceErrorCode, NotReorgedReason, getSafeExecutionBlockHash, } from "@lodestar/fork-choice";
import { ForkSeq, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH } from "@lodestar/params";
import { RootCache, computeEpochAtSlot, computeStartSlotAtEpoch, computeTimeAtSlot, isStartSlotOfEpoch, isStatePostAltair, isStatePostBellatrix, } from "@lodestar/state-transition";
import { isGloasBeaconBlock, ssz } from "@lodestar/types";
import { isErrorAborted, toRootHex } from "@lodestar/utils";
import { ZERO_HASH_HEX } from "../../constants/index.js";
import { callInNextEventLoop } from "../../util/eventLoop.js";
import { isOptimisticBlock } from "../../util/forkChoice.js";
import { isQueueErrorAborted } from "../../util/queue/index.js";
import { ChainEvent } from "../emitter.js";
import { ForkchoiceCaller } from "../forkChoice/index.js";
import { REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC } from "../reprocess.js";
import { toCheckpointHex } from "../stateCache/persistentCheckpointsCache.js";
import { isBlockInputBlobs, isBlockInputColumns } from "./blockInput/blockInput.js";
import { AttestationImportOpt } from "./types.js";
import { getCheckpointFromState } from "./utils/checkpoint.js";
/**
* Fork-choice allows to import attestations from current (0) or past (1) epoch.
*/
const FORK_CHOICE_ATT_EPOCH_LIMIT = 1;
/**
* Emit eventstream events for block contents events only for blocks that are recent enough to clock
*/
const EVENTSTREAM_EMIT_RECENT_BLOCK_SLOTS = 64;
/**
* Imports a fully verified block into the chain state. Produces multiple permanent side-effects.
*
* ImportBlock order of operations must guarantee that BeaconNode does not end in an unknown state:
*
* 1. Persist block to hot DB (pre-emptively)
* - Done before importing block to fork-choice to guarantee that blocks in the fork-choice *always* are persisted
* in the DB. Otherwise the beacon node may end up in an unrecoverable state. If a block is persisted in the hot
* db but is unknown by the fork-choice, then it will just use some extra disk space. On restart is will be
* pruned regardless.
* - Note that doing a disk write first introduces a small delay before setting the head. An improvement where disk
* write happens latter requires the ability to roll back a fork-choice head change if disk write fails
*
* 2. Import block to fork-choice
* 3. Import attestations to fork-choice
* 4. Import attester slashings to fork-choice
* 5. Compute head. If new head, immediately stateCache.setHeadState()
* 6. Queue notifyForkchoiceUpdate to engine api
* 7. Add post state to stateCache
*/
export async function importBlock(fullyVerifiedBlock, opts) {
const { blockInput, postState, parentBlockSlot, dataAvailabilityStatus, indexedAttestations } = fullyVerifiedBlock;
let { executionStatus } = fullyVerifiedBlock;
const block = blockInput.getBlock();
const source = blockInput.getBlockSource();
const { slot: blockSlot } = block.message;
const blockRoot = this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message);
const blockRootHex = toRootHex(blockRoot);
const currentSlot = this.forkChoice.getTime();
const currentEpoch = computeEpochAtSlot(currentSlot);
const blockEpoch = computeEpochAtSlot(blockSlot);
const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
const blockDelaySec = fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postState.genesisTime);
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
const fork = this.config.getForkSeq(blockSlot);
// this is just a type assertion since blockinput with dataPromise type will not end up here
if (!blockInput.hasAllData) {
throw Error("Unavailable block can not be imported in forkchoice");
}
// 1. Persist block to hot DB (performed asynchronously to avoid blocking head selection)
// Wait for space in the write queue to apply backpressure during sync.
// Without this, a supernode syncing from behind can accumulate many blocks worth of column
// data in memory (up to 128 columns per block) causing OOM before persistence catches up.
await this.unfinalizedBlockWrites.waitForSpace();
this.unfinalizedBlockWrites.push(blockInput).catch((e) => {
if (!isQueueErrorAborted(e)) {
this.logger.error("Error pushing block to unfinalized write queue", { slot: blockSlot }, e);
}
});
// 2. Import block to fork choice
// Should compute checkpoint balances before forkchoice.onBlock
this.checkpointBalancesCache.processState(blockRootHex, postState);
if (fork >= ForkSeq.gloas) {
const parentRootHex = toRootHex(block.message.parentRoot);
const parentBlock = this.forkChoice.getBlockHexDefaultStatus(parentRootHex);
if (parentBlock === null) {
throw Error(`Parent block not found in forkChoice, parentRoot=${parentRootHex}`);
}
if (parentBlock.executionStatus === ExecutionStatus.Invalid) {
throw Error(`Parent block has invalid execution status, parentRoot=${parentRootHex}`);
}
executionStatus = parentBlock.executionStatus;
}
const blockSummary = this.forkChoice.onBlock(block.message, postState, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus);
// This adds the state necessary to process the next block
// Some block event handlers require state being in state cache so need to do this before emitting EventType.block
this.regen.processState(blockRootHex, postState);
this.metrics?.importBlock.bySource.inc({ source: source.source });
this.logger.verbose("Added block to forkchoice and state cache", { slot: blockSlot, root: blockRootHex });
// 3. Import attestations to fork choice
//
// - For each attestation
// - Get indexed attestation
// - Register attestation with fork-choice
// - Register attestation with validator monitor (only after sync)
// Only process attestations of blocks with relevant attestations for the fork-choice:
// If current epoch is N, and block is epoch X, block may include attestations for epoch X or X - 1.
// The latest block that is useful is at epoch N - 1 which may include attestations for epoch N - 1 or N - 2.
if (opts.importAttestations === AttestationImportOpt.Force ||
(opts.importAttestations !== AttestationImportOpt.Skip && blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT)) {
const attestations = block.message.body.attestations;
const rootCache = new RootCache(postState);
const invalidAttestationErrorsByCode = new Map();
const addAttestation = fork >= ForkSeq.electra ? addAttestationPostElectra : addAttestationPreElectra;
for (let i = 0; i < attestations.length; i++) {
const attestation = attestations[i];
try {
const indexedAttestation = indexedAttestations[i];
const { target, beaconBlockRoot } = attestation.data;
const attDataRoot = toRootHex(ssz.phase0.AttestationData.hashTreeRoot(indexedAttestation.data));
addAttestation.call(this, postState, target, attDataRoot, attestation, indexedAttestation);
// Duplicated logic from fork-choice onAttestation validation logic.
// Attestations outside of this range will be dropped as Errors, so no need to import
if (opts.importAttestations === AttestationImportOpt.Force ||
(target.epoch <= currentEpoch && target.epoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT)) {
this.forkChoice.onAttestation(indexedAttestation, attDataRoot, opts.importAttestations === AttestationImportOpt.Force);
}
// Note: To avoid slowing down sync, only register attestations within FORK_CHOICE_ATT_EPOCH_LIMIT
this.seenBlockAttesters.addIndices(blockEpoch, indexedAttestation.attestingIndices);
const correctHead = ssz.Root.equals(rootCache.getBlockRootAtSlot(attestation.data.slot), beaconBlockRoot);
const missedSlotVote = ssz.Root.equals(rootCache.getBlockRootAtSlot(attestation.data.slot - 1), rootCache.getBlockRootAtSlot(attestation.data.slot));
this.validatorMonitor?.registerAttestationInBlock(indexedAttestation, parentBlockSlot, correctHead, missedSlotVote, blockRootHex, blockSlot);
}
catch (e) {
// a block has a lot of attestations and it may has same error, we don't want to log all of them
if (e instanceof ForkChoiceError && e.type.code === ForkChoiceErrorCode.INVALID_ATTESTATION) {
let errWithCount = invalidAttestationErrorsByCode.get(e.type.err.code);
if (errWithCount === undefined) {
errWithCount = { error: e, count: 1 };
invalidAttestationErrorsByCode.set(e.type.err.code, errWithCount);
}
else {
errWithCount.count++;
}
}
else {
// always log other errors
this.logger.warn("Error processing attestation from block", { slot: blockSlot }, e);
}
}
}
for (const { error, count } of invalidAttestationErrorsByCode.values()) {
this.logger.warn("Error processing attestations from block", { slot: blockSlot, erroredAttestations: count }, error);
}
}
// 4. Import attester slashings to fork choice
//
// FORK_CHOICE_ATT_EPOCH_LIMIT is for attestation to become valid
// but AttesterSlashing could be found before that time and still able to submit valid attestations
// until slashed validator become inactive, see computeActivationExitEpoch() function
if (opts.importAttestations === AttestationImportOpt.Force ||
(opts.importAttestations !== AttestationImportOpt.Skip &&
blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT - 1 - MAX_SEED_LOOKAHEAD)) {
for (const slashing of block.message.body.attesterSlashings) {
try {
// all AttesterSlashings are valid before reaching this
this.forkChoice.onAttesterSlashing(slashing);
}
catch (e) {
this.logger.warn("Error processing AttesterSlashing from block", { slot: blockSlot }, e);
}
}
}
// 4.5. Import payload attestations to fork choice (Gloas)
//
if (isGloasBeaconBlock(block.message)) {
for (const payloadAttestation of block.message.body.payloadAttestations) {
try {
// Extract PTC indices from aggregation bits
const ptcIndices = [];
for (let i = 0; i < payloadAttestation.aggregationBits.bitLen; i++) {
if (payloadAttestation.aggregationBits.get(i)) {
ptcIndices.push(i);
}
}
if (ptcIndices.length > 0) {
this.forkChoice.notifyPtcMessages(toRootHex(payloadAttestation.data.beaconBlockRoot), ptcIndices, payloadAttestation.data.payloadPresent);
}
}
catch (e) {
this.logger.warn("Error processing PayloadAttestation from block", { slot: blockSlot }, e);
}
}
}
// 5. Compute head. If new head, immediately stateCache.setHeadState()
const oldHead = this.forkChoice.getHead();
const newHead = this.recomputeForkChoiceHead(ForkchoiceCaller.importBlock);
const currFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
if (newHead.blockRoot !== oldHead.blockRoot) {
// Set head state as strong reference
this.regen.updateHeadState(newHead, postState);
try {
this.emitter.emit(routes.events.EventType.head, {
block: newHead.blockRoot,
epochTransition: computeStartSlotAtEpoch(computeEpochAtSlot(newHead.slot)) === newHead.slot,
slot: newHead.slot,
state: newHead.stateRoot,
previousDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.previous),
currentDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.current),
executionOptimistic: isOptimisticBlock(newHead),
});
}
catch (e) {
// getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
this.logger.debug("Error emitting head event", { slot: newHead.slot, root: newHead.blockRoot }, e);
}
const delaySec = this.clock.secFromSlot(newHead.slot);
this.logger.verbose("New chain head", {
slot: newHead.slot,
root: newHead.blockRoot,
delaySec,
});
if (this.metrics) {
this.metrics.headSlot.set(newHead.slot);
// Only track "recent" blocks. Otherwise sync can distort this metrics heavily.
// We want to track recent blocks coming from gossip, unknown block sync, and API.
if (delaySec < (SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000) {
this.metrics.importBlock.elapsedTimeTillBecomeHead.observe(delaySec);
const cutOffSec = this.config.getAttestationDueMs(this.config.getForkName(blockSlot)) / 1000;
if (delaySec > cutOffSec) {
this.metrics.importBlock.setHeadAfterCutoff.inc();
}
}
}
this.onNewHead(newHead);
this.metrics?.forkChoice.changedHead.inc();
const ancestorResult = this.forkChoice.getCommonAncestorDepth(oldHead, newHead);
if (ancestorResult.code === AncestorStatus.CommonAncestor) {
// CommonAncestor = chain reorg, old head and new head not direct descendants
const forkChoiceReorgEventData = {
depth: ancestorResult.depth,
epoch: computeEpochAtSlot(newHead.slot),
slot: newHead.slot,
newHeadBlock: newHead.blockRoot,
oldHeadBlock: oldHead.blockRoot,
newHeadState: newHead.stateRoot,
oldHeadState: oldHead.stateRoot,
executionOptimistic: isOptimisticBlock(newHead),
};
this.emitter.emit(routes.events.EventType.chainReorg, forkChoiceReorgEventData);
this.logger.verbose("Chain reorg", forkChoiceReorgEventData);
this.metrics?.forkChoice.reorg.inc();
this.metrics?.forkChoice.reorgDistance.observe(ancestorResult.depth);
}
// Lightclient server support (only after altair)
// - Persist state witness
// - Use block's syncAggregate
if (blockEpoch >= this.config.ALTAIR_FORK_EPOCH) {
// we want to import block asap so do this in the next event loop
callInNextEventLoop(() => {
try {
if (isStatePostAltair(postState)) {
this.lightClientServer?.onImportBlockHead(block.message, postState, parentBlockSlot);
}
}
catch (e) {
this.logger.verbose("Error lightClientServer.onImportBlock", { slot: blockSlot }, e);
}
});
}
}
// 6. Queue notifyForkchoiceUpdate to engine api
//
// NOTE: forkChoice.fsStore.finalizedCheckpoint MUST only change in response to an onBlock event
// Notifying EL of head and finalized updates as below is usually done within the 1st 4s of the slot.
// If there is an advanced payload generation in the next slot, we'll notify EL again 4s before next
// slot via PrepareNextSlotScheduler. There is no harm updating the ELs with same data, it will just ignore it.
// Suppress fcu call if shouldOverrideFcu is true. This only happens if we have proposer boost reorg enabled
// and the block is weak and can potentially be reorged out.
let shouldOverrideFcu = false;
if (blockSlot >= currentSlot && isStatePostBellatrix(postState) && postState.isExecutionStateType) {
let notOverrideFcuReason = NotReorgedReason.Unknown;
const proposalSlot = blockSlot + 1;
try {
const proposerIndex = postState.getBeaconProposer(proposalSlot);
const feeRecipient = this.beaconProposerCache.get(proposerIndex);
if (feeRecipient) {
// We would set this to true if
// 1) This is a gossip block
// 2) We are proposer of next slot
// 3) Proposer boost reorg related flag is turned on (this is checked inside the function)
// 4) Block meets the criteria of being re-orged out (this is also checked inside the function)
const result = this.forkChoice.shouldOverrideForkChoiceUpdate(blockSummary, this.clock.secFromSlot(currentSlot), currentSlot);
shouldOverrideFcu = result.shouldOverrideFcu;
if (!result.shouldOverrideFcu) {
notOverrideFcuReason = result.reason;
}
}
else {
notOverrideFcuReason = NotReorgedReason.NotProposerOfNextSlot;
}
}
catch (e) {
if (isStartSlotOfEpoch(proposalSlot)) {
notOverrideFcuReason = NotReorgedReason.NotShufflingStable;
}
else {
this.logger.warn("Unable to get beacon proposer. Do not override fcu.", { proposalSlot }, e);
}
}
if (shouldOverrideFcu) {
this.logger.verbose("Weak block detected. Skip fcu call in importBlock", {
blockRoot: blockRootHex,
slot: blockSlot,
});
}
else {
this.metrics?.importBlock.notOverrideFcuReason.inc({ reason: notOverrideFcuReason });
this.logger.verbose("Strong block detected. Not override fcu call", {
blockRoot: blockRootHex,
slot: blockSlot,
reason: notOverrideFcuReason,
});
}
}
if (!this.opts.disableImportExecutionFcU &&
(newHead.blockRoot !== oldHead.blockRoot || currFinalizedEpoch !== prevFinalizedEpoch) &&
!shouldOverrideFcu) {
/**
* On post BELLATRIX_EPOCH but pre TTD, blocks include empty execution payload with a zero block hash.
* The consensus clients must not send notifyForkchoiceUpdate before TTD since the execution client will error.
* So we must check that:
* - `headBlockHash !== null` -> Pre BELLATRIX_EPOCH
* - `headBlockHash !== ZERO_HASH` -> Pre TTD
*/
const headBlockHash = this.forkChoice.getHead().executionPayloadBlockHash ?? ZERO_HASH_HEX;
/**
* After BELLATRIX_EPOCH and TTD it's okay to send a zero hash block hash for the finalized block. This will happen if
* the current finalized block does not contain any execution payload at all (pre MERGE_EPOCH) or if it contains a
* zero block hash (pre TTD)
*/
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
if (headBlockHash !== ZERO_HASH_HEX) {
this.executionEngine
.notifyForkchoiceUpdate(this.config.getForkName(this.forkChoice.getHead().slot), headBlockHash, safeBlockHash, finalizedBlockHash)
.catch((e) => {
if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
this.logger.error("Error pushing notifyForkchoiceUpdate()", { headBlockHash, finalizedBlockHash }, e);
}
});
}
}
if (!postState.isStateValidatorsNodesPopulated()) {
this.logger.verbose("After importBlock caching postState without SSZ cache", { slot: postState.slot });
}
// Cache shufflings when crossing an epoch boundary
const parentEpoch = computeEpochAtSlot(parentBlockSlot);
if (parentEpoch < blockEpoch) {
this.shufflingCache.processState(postState);
this.logger.verbose("Processed shuffling for next epoch", { parentEpoch, blockEpoch, slot: blockSlot });
}
if (blockSlot % SLOTS_PER_EPOCH === 0) {
// Cache state to preserve epoch transition work
const checkpointState = postState;
const cp = getCheckpointFromState(checkpointState);
this.regen.addCheckpointState(cp, checkpointState);
// consumers should not mutate state ever
this.emitter.emit(ChainEvent.checkpoint, cp, checkpointState);
// Note: in-lined code from previos handler of ChainEvent.checkpoint
this.logger.verbose("Checkpoint processed", toCheckpointHex(cp));
const activeValidatorsCount = checkpointState.activeValidatorCount;
this.metrics?.currentActiveValidators.set(activeValidatorsCount);
this.metrics?.currentValidators.set({ status: "active" }, activeValidatorsCount);
const parentBlockSummary = isGloasBeaconBlock(block.message)
? this.forkChoice.getBlockHexAndBlockHash(toRootHex(checkpointState.latestBlockHeader.parentRoot), toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash))
: this.forkChoice.getBlockDefaultStatus(checkpointState.latestBlockHeader.parentRoot);
if (parentBlockSummary) {
const justifiedCheckpoint = checkpointState.currentJustifiedCheckpoint;
const justifiedEpoch = justifiedCheckpoint.epoch;
const preJustifiedEpoch = parentBlockSummary.justifiedEpoch;
if (justifiedEpoch > preJustifiedEpoch) {
this.logger.verbose("Checkpoint justified", toCheckpointHex(justifiedCheckpoint));
this.metrics?.previousJustifiedEpoch.set(checkpointState.previousJustifiedCheckpoint.epoch);
this.metrics?.currentJustifiedEpoch.set(justifiedCheckpoint.epoch);
}
const finalizedCheckpoint = checkpointState.finalizedCheckpoint;
const finalizedEpoch = finalizedCheckpoint.epoch;
const preFinalizedEpoch = parentBlockSummary.finalizedEpoch;
if (finalizedEpoch > preFinalizedEpoch) {
this.emitter.emit(routes.events.EventType.finalizedCheckpoint, {
block: toRootHex(finalizedCheckpoint.root),
epoch: finalizedCheckpoint.epoch,
state: toRootHex(checkpointState.hashTreeRoot()),
executionOptimistic: false,
});
this.logger.verbose("Checkpoint finalized", toCheckpointHex(finalizedCheckpoint));
this.metrics?.finalizedEpoch.set(finalizedCheckpoint.epoch);
}
}
}
// Send block events, only for recent enough blocks
if (currentSlot - blockSlot < EVENTSTREAM_EMIT_RECENT_BLOCK_SLOTS) {
// We want to import block asap so call all event handler in the next event loop
callInNextEventLoop(() => {
// NOTE: Skip emitting if there are no listeners from the API
if (this.emitter.listenerCount(routes.events.EventType.block)) {
this.emitter.emit(routes.events.EventType.block, {
block: blockRootHex,
slot: blockSlot,
executionOptimistic: blockSummary != null && isOptimisticBlock(blockSummary),
});
}
if (this.emitter.listenerCount(routes.events.EventType.voluntaryExit)) {
for (const voluntaryExit of block.message.body.voluntaryExits) {
this.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit);
}
}
if (this.emitter.listenerCount(routes.events.EventType.blsToExecutionChange)) {
for (const blsToExecutionChange of block.message.body.blsToExecutionChanges ?? []) {
this.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange);
}
}
if (this.emitter.listenerCount(routes.events.EventType.attestation)) {
for (const attestation of block.message.body.attestations) {
this.emitter.emit(routes.events.EventType.attestation, attestation);
}
}
if (this.emitter.listenerCount(routes.events.EventType.attesterSlashing)) {
for (const attesterSlashing of block.message.body.attesterSlashings) {
this.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
}
}
if (this.emitter.listenerCount(routes.events.EventType.proposerSlashing)) {
for (const proposerSlashing of block.message.body.proposerSlashings) {
this.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
}
}
});
}
// Register stat metrics about the block after importing it
this.metrics?.parentBlockDistance.observe(blockSlot - parentBlockSlot);
this.metrics?.proposerBalanceDeltaAny.observe(fullyVerifiedBlock.proposerBalanceDelta);
this.validatorMonitor?.registerImportedBlock(block.message, fullyVerifiedBlock);
if (isStatePostAltair(fullyVerifiedBlock.postState)) {
this.validatorMonitor?.registerSyncAggregateInBlock(blockEpoch, block.message.body.syncAggregate, fullyVerifiedBlock.postState.currentSyncCommitteeIndexed.validatorIndices);
}
if (isBlockInputColumns(blockInput)) {
for (const { source } of blockInput.getSampledColumnsWithSource()) {
this.metrics?.dataColumns.bySource.inc({ source });
}
}
else if (isBlockInputBlobs(blockInput)) {
for (const { source } of blockInput.getAllBlobsWithSource()) {
this.metrics?.importBlock.blobsBySource.inc({ blobsSource: source });
}
}
const advancedSlot = this.clock.slotWithFutureTolerance(REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC);
// Gossip blocks need to be imported as soon as possible, waiting attestations could be processed
// in the next event loop. See https://github.com/ChainSafe/lodestar/issues/4789
callInNextEventLoop(() => {
this.reprocessController.onBlockImported({ slot: blockSlot, root: blockRootHex }, advancedSlot);
});
if (opts.seenTimestampSec !== undefined) {
const recvToValidation = Date.now() / 1000 - opts.seenTimestampSec;
const validationTime = recvToValidation - recvToValLatency;
this.metrics?.gossipBlock.blockImport.recvToValidation.observe(recvToValidation);
this.metrics?.gossipBlock.blockImport.validationTime.observe(validationTime);
this.logger.debug("Imported block", { slot: blockSlot, recvToValLatency, recvToValidation, validationTime });
}
this.logger.verbose("Block processed", {
slot: blockSlot,
root: blockRootHex,
delaySec: this.clock.secFromSlot(blockSlot),
});
}
export function addAttestationPreElectra(
// added to have the same signature as addAttestationPostElectra
_, target, attDataRoot, attestation, indexedAttestation) {
this.seenAggregatedAttestations.add(target.epoch, attestation.data.index, attDataRoot, { aggregationBits: attestation.aggregationBits, trueBitCount: indexedAttestation.attestingIndices.length }, true);
}
export function addAttestationPostElectra(state, target, attDataRoot, attestation, indexedAttestation) {
const committeeIndices = attestation.committeeBits.getTrueBitIndexes();
if (committeeIndices.length === 1) {
this.seenAggregatedAttestations.add(target.epoch, committeeIndices[0], attDataRoot, { aggregationBits: attestation.aggregationBits, trueBitCount: indexedAttestation.attestingIndices.length }, true);
}
else {
const attSlot = attestation.data.slot;
const attEpoch = computeEpochAtSlot(attSlot);
const decisionRoot = state.getShufflingDecisionRoot(attEpoch);
const committees = this.shufflingCache.getBeaconCommittees(attEpoch, decisionRoot, attSlot, committeeIndices);
const aggregationBools = attestation.aggregationBits.toBoolArray();
let offset = 0;
for (let i = 0; i < committees.length; i++) {
const committee = committees[i];
const aggregationBits = BitArray.fromBoolArray(aggregationBools.slice(offset, offset + committee.length));
const trueBitCount = aggregationBits.getTrueBitIndexes().length;
offset += committee.length;
this.seenAggregatedAttestations.add(target.epoch, committeeIndices[i], attDataRoot, { aggregationBits, trueBitCount }, true);
}
}
}
//# sourceMappingURL=importBlock.js.map