Skip to content

Commit

Permalink
fix: do not download known block by root (#6292)
Browse files Browse the repository at this point in the history
* fix: do not download known block by root

* fix: handle PreDeneb for SeenGossipBlockInput
  • Loading branch information
twoeths authored Jan 15, 2024
1 parent bf4969f commit 88744d8
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 36 deletions.
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
21 changes: 17 additions & 4 deletions packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts
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

0 comments on commit 88744d8

Please sign in to comment.