Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do not download known block by root #6292

Merged
merged 2 commits into from
Jan 15, 2024
Merged
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
4 changes: 4 additions & 0 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ export class BeaconChain implements IBeaconChain {
await this.bls.close();
}

seenBlock(blockRoot: RootHex): boolean {
return this.seenGossipBlockInput.hasBlock(blockRoot) || this.forkChoice.hasBlockHex(blockRoot);
}

regenCanAcceptWork(): boolean {
return this.regen.canAcceptWork();
}
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export interface IBeaconChain {

/** Stop beacon chain processing */
close(): Promise<void>;
/** Chain has seen the specified block root or not. The block may not be processed yet, use forkchoice.hasBlock to check it */
seenBlock(blockRoot: RootHex): boolean;
/** Populate in-memory caches with persisted data. Call at least once on startup */
loadFromDisk(): Promise<void>;
/** Persist in-memory data to the DB. Call at least once before stopping the process */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {toHexString} from "@chainsafe/ssz";
import {deneb, RootHex, ssz, allForks} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import {pruneSetToMax} from "@lodestar/utils";
import {BLOBSIDECAR_FIXED_SIZE} from "@lodestar/params";
import {BLOBSIDECAR_FIXED_SIZE, ForkSeq} from "@lodestar/params";

import {
BlockInput,
Expand All @@ -29,9 +29,11 @@ type BlockInputCacheType = {
const MAX_GOSSIPINPUT_CACHE = 5;

/**
* SeenGossipBlockInput tracks and caches the live blobs and blocks on the network to solve data availability
* for the blockInput. If no block has been seen yet for some already seen blobs, it responds will null, but
* on the first block or the consequent blobs it responds with blobs promise till all blobs become available.
* For predeneb, SeenGossipBlockInput only tracks and caches block so that we don't need to download known block
* roots. From deneb, it serves same purpose plus tracks and caches the live blobs and blocks on the network to
* solve data availability for the blockInput. If no block has been seen yet for some already seen blobs, it
* responds will null, but on the first block or the consequent blobs it responds with blobs promise till all blobs
* become available.
*
* One can start processing block on blobs promise blockInput response and can await on the promise before
* fully importing the block. The blobs promise is gets resolved as soon as all blobs corresponding to that
Expand All @@ -44,6 +46,10 @@ export class SeenGossipBlockInput {
pruneSetToMax(this.blockInputCache, MAX_GOSSIPINPUT_CACHE);
}

hasBlock(blockRoot: RootHex): boolean {
return this.blockInputCache.has(blockRoot);
}

getGossipBlockInput(
config: ChainForkConfig,
gossipedInput: GossipedBlockInput
Expand Down Expand Up @@ -83,9 +89,16 @@ export class SeenGossipBlockInput {
if (!this.blockInputCache.has(blockHex)) {
this.blockInputCache.set(blockHex, blockCache);
}

const {block: signedBlock, blockBytes, blobsCache, availabilityPromise, resolveAvailability} = blockCache;

if (signedBlock !== undefined) {
if (config.getForkSeq(signedBlock.message.slot) < ForkSeq.deneb) {
return {
blockInput: getBlockInput.preDeneb(config, signedBlock, BlockSource.gossip, blockBytes ?? null),
blockInputMeta: {pending: null, haveBlobs: 0, expectedBlobs: 0},
};
}
// block is available, check if all blobs have shown up
const {slot, body} = signedBlock.message;
const {blobKzgCommitments} = body as deneb.BeaconBlockBody;
Expand Down
43 changes: 15 additions & 28 deletions packages/beacon-node/src/network/processor/gossipHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ import {PeerAction} from "../peers/index.js";
import {validateLightClientFinalityUpdate} from "../../chain/validation/lightClientFinalityUpdate.js";
import {validateLightClientOptimisticUpdate} from "../../chain/validation/lightClientOptimisticUpdate.js";
import {validateGossipBlobSidecar} from "../../chain/validation/blobSidecar.js";
import {
BlockInput,
BlockSource,
getBlockInput,
GossipedInputType,
BlobSidecarValidation,
} from "../../chain/blocks/types.js";
import {BlockInput, GossipedInputType, BlobSidecarValidation} from "../../chain/blocks/types.js";
import {sszDeserialize} from "../gossip/topic.js";
import {INetworkCore} from "../core/index.js";
import {INetwork} from "../interface.js";
Expand Down Expand Up @@ -123,28 +117,21 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler
const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
const recvToVal = Date.now() / 1000 - seenTimestampSec;

let blockInput;
let blockInputMeta;
if (config.getForkSeq(signedBlock.message.slot) >= ForkSeq.deneb) {
const blockInputRes = chain.seenGossipBlockInput.getGossipBlockInput(config, {
type: GossipedInputType.block,
signedBlock,
blockBytes,
});

blockInput = blockInputRes.blockInput;
blockInputMeta = blockInputRes.blockInputMeta;

// blockInput can't be returned null, improve by enforcing via return types
if (blockInput === null) {
throw Error(
`Invalid null blockInput returned by getGossipBlockInput for type=${GossipedInputType.block} blockHex=${blockHex} slot=${slot}`
);
}
} else {
blockInput = getBlockInput.preDeneb(config, signedBlock, BlockSource.gossip, blockBytes);
blockInputMeta = {};
// always set block to seen cache for all forks so that we don't need to download it
const blockInputRes = chain.seenGossipBlockInput.getGossipBlockInput(config, {
type: GossipedInputType.block,
signedBlock,
blockBytes,
});
const blockInput = blockInputRes.blockInput;
// blockInput can't be returned null, improve by enforcing via return types
if (blockInput === null) {
throw Error(
`Invalid null blockInput returned by getGossipBlockInput for type=${GossipedInputType.block} blockHex=${blockHex} slot=${slot}`
);
}
const blockInputMeta =
config.getForkSeq(signedBlock.message.slot) >= ForkSeq.deneb ? blockInputRes.blockInputMeta : {};

metrics?.gossipBlock.receivedToGossipValidate.observe(recvToVal);
logger.verbose("Received gossip block", {
Expand Down
9 changes: 5 additions & 4 deletions packages/beacon-node/src/network/processor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,12 @@ export class NetworkProcessor {
}

searchUnknownSlotRoot({slot, root}: SlotRootHex, peer?: PeerIdStr): void {
// Search for the unknown block
if (!this.unknownRootsBySlot.getOrDefault(slot).has(root)) {
this.unknownRootsBySlot.getOrDefault(slot).add(root);
this.events.emit(NetworkEvent.unknownBlock, {rootHex: root, peer});
if (this.chain.seenBlock(root) || this.unknownRootsBySlot.getOrDefault(slot).has(root)) {
return;
}
// Search for the unknown block
this.unknownRootsBySlot.getOrDefault(slot).add(root);
this.events.emit(NetworkEvent.unknownBlock, {rootHex: root, peer});
}

private onPendingGossipsubMessage(message: PendingGossipsubMessage): void {
Expand Down
Loading