Skip to content

Commit 7cd296b

Browse files
authored
feat: add kzg commitment length check when validating gossip blocks (#7302)
1 parent c290469 commit 7cd296b

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-6
lines changed

packages/beacon-node/src/chain/errors/blockError.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export enum BlockErrorCode {
6464
TOO_MANY_SKIPPED_SLOTS = "TOO_MANY_SKIPPED_SLOTS",
6565
/** The blobs are unavailable */
6666
DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
67+
/** Block contains too many kzg commitments */
68+
TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
6769
}
6870

6971
type ExecutionErrorStatus = Exclude<
@@ -105,7 +107,8 @@ export type BlockErrorType =
105107
| {code: BlockErrorCode.SAME_PARENT_HASH; blockHash: RootHex}
106108
| {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number}
107109
| {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionErrorStatus; errorMessage: string}
108-
| {code: BlockErrorCode.DATA_UNAVAILABLE};
110+
| {code: BlockErrorCode.DATA_UNAVAILABLE}
111+
| {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number};
109112

110113
export class BlockGossipError extends GossipActionError<BlockErrorType> {}
111114

packages/beacon-node/src/chain/validation/block.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {ChainForkConfig} from "@lodestar/config";
2-
import {ForkName} from "@lodestar/params";
2+
import {ForkName, isForkBlobs} from "@lodestar/params";
33
import {
44
computeStartSlotAtEpoch,
55
computeTimeAtSlot,
@@ -8,7 +8,7 @@ import {
88
isExecutionEnabled,
99
isExecutionStateType,
1010
} from "@lodestar/state-transition";
11-
import {SignedBeaconBlock} from "@lodestar/types";
11+
import {SignedBeaconBlock, deneb} from "@lodestar/types";
1212
import {sleep, toRootHex} from "@lodestar/utils";
1313
import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js";
1414
import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js";
@@ -110,6 +110,18 @@ export async function validateGossipBlock(
110110
});
111111
}
112112

113+
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
114+
if (isForkBlobs(fork)) {
115+
const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
116+
if (blobKzgCommitmentsLen > chain.config.MAX_BLOBS_PER_BLOCK) {
117+
throw new BlockGossipError(GossipAction.REJECT, {
118+
code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS,
119+
blobKzgCommitmentsLen,
120+
commitmentLimit: chain.config.MAX_BLOBS_PER_BLOCK,
121+
});
122+
}
123+
}
124+
113125
// use getPreState to reload state if needed. It also checks for whether the current finalized checkpoint is an ancestor of the block.
114126
// As a result, we throw an IGNORE (whereas the spec says we should REJECT for this scenario).
115127
// this is something we should change this in the future to make the code airtight to the spec.

packages/beacon-node/test/unit/chain/validation/block.test.ts

+44-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {config} from "@lodestar/config/default";
22
import {ProtoBlock} from "@lodestar/fork-choice";
3-
import {ForkName} from "@lodestar/params";
3+
import {ForkBlobs, ForkName} from "@lodestar/params";
44
import {SignedBeaconBlock, ssz} from "@lodestar/types";
55
import {Mock, Mocked, beforeEach, describe, it, vi} from "vitest";
66
import {BlockErrorCode} from "../../../../src/chain/errors/index.js";
@@ -20,12 +20,15 @@ describe("gossip block validation", () => {
2020
let job: SignedBeaconBlock;
2121
const proposerIndex = 0;
2222
const clockSlot = 32;
23-
const block = ssz.phase0.BeaconBlock.defaultValue();
23+
const block = ssz.deneb.BeaconBlock.defaultValue();
2424
block.slot = clockSlot;
2525
const signature = EMPTY_SIGNATURE;
2626
const maxSkipSlots = 10;
2727

2828
beforeEach(() => {
29+
// Fill up with kzg commitments
30+
block.body.blobKzgCommitments = Array.from({length: config.MAX_BLOBS_PER_BLOCK}, () => new Uint8Array([0]));
31+
2932
chain = getMockedBeaconChain();
3033
vi.spyOn(chain.clock, "currentSlotWithGossipDisparity", "get").mockReturnValue(clockSlot);
3134
forkChoice = chain.forkChoice;
@@ -184,9 +187,47 @@ describe("gossip block validation", () => {
184187
regen.getPreState.mockResolvedValue(state);
185188
// BLS signature verifier returns valid
186189
verifySignature.mockResolvedValue(true);
187-
// Force proposer shuffling cache to return wrong value
190+
// Force proposer shuffling cache to return correct value
188191
vi.spyOn(state.epochCtx, "getBeaconProposer").mockReturnValue(proposerIndex);
189192

190193
await validateGossipBlock(config, chain, job, ForkName.phase0);
191194
});
195+
196+
it("deneb - TOO_MANY_KZG_COMMITMENTS", async () => {
197+
// Return not known for proposed block
198+
forkChoice.getBlockHex.mockReturnValueOnce(null);
199+
// Returned parent block is latter than proposed block
200+
forkChoice.getBlockHex.mockReturnValueOnce({slot: clockSlot - 1} as ProtoBlock);
201+
// Regen returns some state
202+
const state = generateCachedState();
203+
regen.getPreState.mockResolvedValue(state);
204+
// BLS signature verifier returns valid
205+
verifySignature.mockResolvedValue(true);
206+
// Force proposer shuffling cache to return correct value
207+
vi.spyOn(state.epochCtx, "getBeaconProposer").mockReturnValue(proposerIndex + 1);
208+
// Add one extra kzg commitment in the block so it goes over the limit
209+
(job as SignedBeaconBlock<ForkBlobs>).message.body.blobKzgCommitments.push(new Uint8Array([0]));
210+
211+
await expectRejectedWithLodestarError(
212+
validateGossipBlock(config, chain, job, ForkName.deneb),
213+
BlockErrorCode.TOO_MANY_KZG_COMMITMENTS
214+
);
215+
});
216+
217+
it("deneb - valid", async () => {
218+
// Return not known for proposed block
219+
forkChoice.getBlockHex.mockReturnValueOnce(null);
220+
// Returned parent block is latter than proposed block
221+
forkChoice.getBlockHex.mockReturnValueOnce({slot: clockSlot - 1} as ProtoBlock);
222+
// Regen returns some state
223+
const state = generateCachedState();
224+
regen.getPreState.mockResolvedValue(state);
225+
// BLS signature verifier returns valid
226+
verifySignature.mockResolvedValue(true);
227+
// Force proposer shuffling cache to return correct value
228+
vi.spyOn(state.epochCtx, "getBeaconProposer").mockReturnValue(proposerIndex);
229+
// Keep number of kzg commitments as is so it stays within the limit
230+
231+
await validateGossipBlock(config, chain, job, ForkName.deneb);
232+
});
192233
});

0 commit comments

Comments
 (0)