fix: untrusted slot in network processor (#8466)

**Motivation**

- we see different slots for the same root in `BlockInputSync` logs,
this causes confusion
- this was added last week in #8416

**Description**

- the [root, slot] is not trusted in network processor because messages
could be originated from an out-of-sync node
- we actually don't need the slot in BlockInputSync, it's just a nice to
have but caused confusion. So I reverted that part: do not emit slot to
`BlockInputSync` from network processor
- log the full root hex

Closes #8465

---------

Co-authored-by: Tuyen Nguyen <twoeths@users.noreply.github.com>
This commit is contained in:
twoeths
2025-09-25 18:29:19 +07:00
committed by GitHub
parent 8689cc3545
commit 98d359db41
6 changed files with 18 additions and 24 deletions

View File

@@ -3,7 +3,7 @@ import {StrictEventEmitter} from "strict-event-emitter-types";
import {routes} from "@lodestar/api";
import {CheckpointWithHex} from "@lodestar/fork-choice";
import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {RootOptionalSlot, deneb, fulu, phase0} from "@lodestar/types";
import {RootHex, deneb, fulu, phase0} from "@lodestar/types";
import {PeerIdStr} from "../util/peerId.js";
import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
@@ -76,7 +76,7 @@ type ApiEvents = {[K in routes.events.EventType]: (data: routes.events.EventData
export type ChainEventData = {
[ChainEvent.unknownParent]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
[ChainEvent.unknownBlockRoot]: {rootSlot: RootOptionalSlot; peer?: PeerIdStr; source: BlockInputSource};
[ChainEvent.unknownBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
[ChainEvent.incompleteBlockInput]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
};

View File

@@ -230,14 +230,13 @@ export class NetworkProcessor {
return queue.getAll();
}
searchUnknownSlotRoot(slotRoot: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
const {slot, root} = slotRoot;
searchUnknownSlotRoot({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
if (this.chain.seenBlock(root) || this.unknownRootsBySlot.getOrDefault(slot).has(root)) {
return;
}
// Search for the unknown block
this.unknownRootsBySlot.getOrDefault(slot).add(root);
this.chain.emitter.emit(ChainEvent.unknownBlockRoot, {rootSlot: slotRoot, peer, source});
this.chain.emitter.emit(ChainEvent.unknownBlockRoot, {rootHex: root, peer, source});
}
private onPendingGossipsubMessage(message: PendingGossipsubMessage): void {

View File

@@ -37,8 +37,6 @@ export type PendingBlockInput = {
export type PendingRootHex = {
status: PendingBlockInputStatus.pending | PendingBlockInputStatus.fetching;
rootHex: RootHex;
// optional because we may not know the slot of parent_unknown event
slot?: Slot;
timeAddedSec: number;
timeSyncedSec?: number;
peerIdStrings: Set<string>;

View File

@@ -1,8 +1,8 @@
import {ChainForkConfig} from "@lodestar/config";
import {ForkSeq, INTERVALS_PER_SLOT} from "@lodestar/params";
import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
import {RootHex, Slot} from "@lodestar/types";
import {Logger, prettyBytes, prettyPrintIndices, pruneSetToMax, sleep} from "@lodestar/utils";
import {RootHex} from "@lodestar/types";
import {Logger, prettyPrintIndices, pruneSetToMax, sleep} from "@lodestar/utils";
import {isBlockInputBlobs, isBlockInputColumns} from "../chain/blocks/blockInput/blockInput.js";
import {BlockInputSource, IBlockInput} from "../chain/blocks/blockInput/types.js";
import {BlockError, BlockErrorCode} from "../chain/errors/index.js";
@@ -146,8 +146,7 @@ export class BlockInputSync {
*/
private onUnknownBlockRoot = (data: ChainEventData[ChainEvent.unknownBlockRoot]): void => {
try {
const {root, slot} = data.rootSlot;
this.addByRootHex(root, slot, data.peer);
this.addByRootHex(data.rootHex, data.peer);
this.triggerUnknownBlockSearch();
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_BLOCK_ROOT});
this.metrics?.blockInputSync.source.inc({source: data.source});
@@ -175,8 +174,7 @@ export class BlockInputSync {
*/
private onUnknownParent = (data: ChainEventData[ChainEvent.unknownParent]): void => {
try {
// we don't know the slot of parent, hence make it undefined
this.addByRootHex(data.blockInput.parentRootHex, undefined, data.peer);
this.addByRootHex(data.blockInput.parentRootHex, data.peer);
this.addByBlockInput(data.blockInput, data.peer);
this.triggerUnknownBlockSearch();
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PARENT});
@@ -186,12 +184,11 @@ export class BlockInputSync {
}
};
private addByRootHex = (rootHex: RootHex, slot?: Slot, peerIdStr?: PeerIdStr): void => {
private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): void => {
let pendingBlock = this.pendingBlocks.get(rootHex);
if (!pendingBlock) {
pendingBlock = {
status: PendingBlockInputStatus.pending,
slot,
rootHex: rootHex,
peerIdStrings: new Set(),
timeAddedSec: Date.now() / 1000,
@@ -199,7 +196,7 @@ export class BlockInputSync {
this.pendingBlocks.set(rootHex, pendingBlock);
this.logger.verbose("Added new rootHex to BlockInputSync.pendingBlocks", {
rootHex: prettyBytes(pendingBlock.rootHex),
root: pendingBlock.rootHex,
peerIdStr: peerIdStr ?? "unknown peer",
});
}
@@ -318,7 +315,7 @@ export class BlockInputSync {
const rootHex = getBlockInputSyncCacheItemRootHex(block);
const logCtx = {
slot: getBlockInputSyncCacheItemSlot(block),
blockRoot: prettyBytes(rootHex),
root: rootHex,
pendingBlocks: this.pendingBlocks.size,
};
@@ -362,7 +359,7 @@ export class BlockInputSync {
});
this.removeAndDownScoreAllDescendants(block);
} else {
this.onUnknownBlockRoot({rootSlot: {root: pending.blockInput.parentRootHex}, source: BlockInputSource.byRoot});
this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
}
} else {
this.metrics?.blockInputSync.downloadedBlocksError.inc();
@@ -407,7 +404,7 @@ export class BlockInputSync {
// eligible for proposer boost to prevent unbundling attack
this.logger.verbose("Avoid proposer boost for this block of known proposer", {
slot: blockSlot,
blockRoot: prettyBytes(pendingBlock.blockInput.blockRootHex),
root: pendingBlock.blockInput.blockRootHex,
proposerIndex,
});
await sleep(this.proposerBoostSecWindow * 1000);
@@ -500,7 +497,7 @@ export class BlockInputSync {
: null;
const fetchStartSec = Date.now() / 1000;
let slot = isPendingBlockInput(cacheItem) ? cacheItem.blockInput.slot : cacheItem.slot;
let slot = isPendingBlockInput(cacheItem) ? cacheItem.blockInput.slot : undefined;
if (slot !== undefined) {
this.metrics?.blockInputSync.fetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
}
@@ -515,7 +512,7 @@ export class BlockInputSync {
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
if (peerMeta === null) {
// no more peer with needed columns to try, throw error
let message = `Error fetching UnknownBlockRoot slot=${slot} blockRoot=${prettyBytes(rootHex)} after ${i}: cannot find peer`;
let message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer`;
if (pendingColumns) {
message += ` with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
}
@@ -605,7 +602,7 @@ export class BlockInputSync {
}
} // end while loop over peers
const message = `Error fetching BlockInput with slot=${slot} blockRoot=${prettyBytes(rootHex)} after ${i - 1} attempts.`;
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
if (!isPendingBlockInput(cacheItem)) {
throw Error(`${message} No block and no data was found.`);

View File

@@ -176,7 +176,7 @@ describe("sync / unknown block sync for fulu", () => {
break;
case ChainEvent.unknownBlockRoot:
bn2.chain.emitter.emit(ChainEvent.unknownBlockRoot, {
rootSlot: {root: headSummary.blockRoot},
rootHex: headSummary.blockRoot,
peer: bn2.network.peerId.toString(),
source: BlockInputSource.gossip,
});

View File

@@ -215,7 +215,7 @@ describe.skip(
});
} else {
chain.emitter?.emit(ChainEvent.unknownBlockRoot, {
rootSlot: {root: blockRootHexC},
rootHex: blockRootHexC,
peer,
source: BlockInputSource.gossip,
});