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

feat: validate data column sidecars #7073

Merged
merged 9 commits into from
Sep 17, 2024
72 changes: 64 additions & 8 deletions packages/beacon-node/src/chain/validation/dataColumnSidecar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {
NUMBER_OF_COLUMNS,
} from "@lodestar/params";
import {ssz, deneb, peerdas, Slot, Root} from "@lodestar/types";
import {verifyMerkleBranch} from "@lodestar/utils";
import {toHex, verifyMerkleBranch} from "@lodestar/utils";

import {DataColumnSidecarGossipError, DataColumnSidecarErrorCode} from "../errors/dataColumnSidecarError.js";
import {GossipAction} from "../errors/gossipValidation.js";
import {IBeaconChain} from "../interface.js";
import {ckzg} from "../../util/kzg.js";
import {byteArrayEquals} from "../../util/bytes.js";

export async function validateGossipDataColumnSidecar(
chain: IBeaconChain,
Expand Down Expand Up @@ -45,14 +47,68 @@ export async function validateGossipDataColumnSidecar(
}

export function validateDataColumnsSidecars(
matthewkeil marked this conversation as resolved.
Show resolved Hide resolved
_blockSlot: Slot,
_blockRoot: Root,
_expectedKzgCommitments: deneb.BlobKzgCommitments,
_dataColumnSidecars: peerdas.DataColumnSidecars,
_opts: {skipProofsCheck: boolean} = {skipProofsCheck: false}
blockSlot: Slot,
blockRoot: Root,
blockKzgCommitments: deneb.BlobKzgCommitments,
dataColumnSidecars: peerdas.DataColumnSidecars,
opts: {skipProofsCheck: boolean} = {skipProofsCheck: false}
): void {
// stubbed
return;
const commitmentBytes: Uint8Array[] = [];
const cellIndices: number[] = [];
const cells: Uint8Array[] = [];
const proofBytes: Uint8Array[] = [];

for (let sidecarsIndex = 0; sidecarsIndex < dataColumnSidecars.length; sidecarsIndex++) {
const columnSidecar = dataColumnSidecars[sidecarsIndex];
const {index: columnIndex, column, kzgCommitments, kzgProofs} = columnSidecar;
const columnBlockHeader = columnSidecar.signedBlockHeader.message;
const columnBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(columnBlockHeader);
if (
columnBlockHeader.slot !== blockSlot ||
!byteArrayEquals(columnBlockRoot, blockRoot) ||
blockKzgCommitments.length !== kzgCommitments.length ||
blockKzgCommitments
.map((commitment, i) => byteArrayEquals(commitment, kzgCommitments[i]))
.filter((result) => result === false).length
) {
throw new Error(
`Invalid data column sidecar slot=${columnBlockHeader.slot} columnBlockRoot=${toHex(columnBlockRoot)} columnIndex=${columnIndex} for the block blockRoot=${toHex(blockRoot)} slot=${blockSlot} sidecarsIndex=${sidecarsIndex}`
);
}

if (columnIndex >= NUMBER_OF_COLUMNS) {
throw new Error(
`Invalid data sidecar columnIndex=${columnIndex} in slot=${blockSlot} blockRoot=${toHex(blockRoot)} sidecarsIndex=${sidecarsIndex}`
);
}

if (column.length !== kzgCommitments.length || column.length !== kzgProofs.length) {
throw new Error(
`Invalid data sidecar array lengths for columnIndex=${columnIndex} in slot=${blockSlot} blockRoot=${toHex(blockRoot)}`
);
}

commitmentBytes.push(...kzgCommitments);
cellIndices.push(...Array.from({length: column.length}, () => columnIndex));
cells.push(...column);
proofBytes.push(...kzgProofs);
}

if (opts.skipProofsCheck) {
return;
}

let valid: boolean;
try {
valid = ckzg.verifyCellKzgProofBatch(commitmentBytes, cellIndices, cells, proofBytes);
} catch (err) {
(err as Error).message = `Error in verifyCellKzgProofBatch for slot=${blockSlot} blockRoot=${toHex(blockRoot)}`;
throw err;
}

if (!valid) {
throw new Error(`Invalid data column sidecars in slot=${blockSlot} blockRoot=${toHex(blockRoot)}`);
}
}

function validateInclusionProof(dataColumnSidecar: peerdas.DataColumnSidecar): boolean {
Expand Down
62 changes: 60 additions & 2 deletions packages/beacon-node/test/unit/util/dataColumn.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import {describe, it, expect} from "vitest";
/* eslint-disable @typescript-eslint/naming-convention */
import {describe, it, expect, beforeAll, afterEach} from "vitest";
import {fromHexString} from "@chainsafe/ssz";
import {ssz} from "@lodestar/types";
import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {NUMBER_OF_COLUMNS} from "@lodestar/params";
import {bigIntToBytes} from "@lodestar/utils";

import {getCustodyColumns} from "../../../src/util/dataColumns.js";
import {getMockedBeaconChain} from "../../mocks/mockedBeaconChain.js";
import {ckzg, initCKZG, loadEthereumTrustedSetup} from "../../../src/util/kzg.js";
import {generateRandomBlob, transactionForKzgCommitment} from "../../utils/kzg.js";
import {computeDataColumnSidecars} from "../../../src/util/blobs.js";
import {validateDataColumnsSidecars} from "../../../src/chain/validation/dataColumnSidecar.js";

describe("custody columns", () => {
describe("getCustodyColumns", () => {
const testCases = [
["cdbee32dc3c50e9711d22be5565c7e44ff6108af663b2dc5abd2df573d2fa83f", 4, [2, 80, 89, 118]],
[
Expand All @@ -26,3 +35,52 @@ describe("custody columns", () => {
});
}
});
describe("data column sidecars", () => {
const afterEachCallbacks: (() => Promise<unknown> | void)[] = [];
afterEach(async () => {
while (afterEachCallbacks.length > 0) {
const callback = afterEachCallbacks.pop();
if (callback) await callback();
}
});

beforeAll(async function () {
await initCKZG();
loadEthereumTrustedSetup();
});

it("validateDataColumnsSidecars", () => {
const chainConfig = createChainForkConfig({
...defaultChainConfig,
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: 0,
ELECTRA_FORK_EPOCH: 0,
});
const genesisValidatorsRoot = Buffer.alloc(32, 0xaa);
const config = createBeaconConfig(chainConfig, genesisValidatorsRoot);

const chain = getMockedBeaconChain({config});
afterEachCallbacks.push(() => chain.close());

const slot = 0;
const blobs = [generateRandomBlob(), generateRandomBlob()];
const kzgCommitments = blobs.map((blob) => ckzg.blobToKzgCommitment(blob));

const signedBeaconBlock = ssz.deneb.SignedBeaconBlock.defaultValue();

for (const kzgCommitment of kzgCommitments) {
signedBeaconBlock.message.body.executionPayload.transactions.push(transactionForKzgCommitment(kzgCommitment));
signedBeaconBlock.message.body.blobKzgCommitments.push(kzgCommitment);
}
const blockRoot = ssz.deneb.BeaconBlock.hashTreeRoot(signedBeaconBlock.message);
const columnSidecars = computeDataColumnSidecars(config, signedBeaconBlock, {
blobs,
});

expect(columnSidecars.length).toEqual(NUMBER_OF_COLUMNS);
expect(columnSidecars[0].column.length).toEqual(blobs.length);

expect(validateDataColumnsSidecars(slot, blockRoot, kzgCommitments, columnSidecars)).toBeUndefined();
});
});
30 changes: 4 additions & 26 deletions packages/beacon-node/test/unit/util/kzg.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {describe, it, expect, afterEach, beforeAll} from "vitest";
import {bellatrix, deneb, ssz} from "@lodestar/types";
import {BYTES_PER_FIELD_ELEMENT, BLOB_TX_TYPE} from "@lodestar/params";
import {deneb, ssz} from "@lodestar/types";
import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {signedBlockToSignedHeader} from "@lodestar/state-transition";
import {getMockedBeaconChain} from "../../mocks/mockedBeaconChain.js";
import {computeBlobSidecars, computeDataColumnSidecars, kzgCommitmentToVersionedHash} from "../../../src/util/blobs.js";
import {loadEthereumTrustedSetup, initCKZG, ckzg, FIELD_ELEMENTS_PER_BLOB_MAINNET} from "../../../src/util/kzg.js";
import {loadEthereumTrustedSetup, initCKZG, ckzg} from "../../../src/util/kzg.js";
import {validateBlobSidecars, validateGossipBlobSidecar} from "../../../src/chain/validation/blobSidecar.js";
import {generateRandomBlob, transactionForKzgCommitment} from "../../utils/kzg.js";
import {computeBlobSidecars, computeDataColumnSidecars} from "../../../src/util/blobs.js";
import {getBlobCellAndProofs} from "../../utils/getBlobCellAndProofs.js";

describe("C-KZG", () => {
Expand Down Expand Up @@ -121,25 +121,3 @@ describe("C-KZG", () => {
});
});
});

function transactionForKzgCommitment(kzgCommitment: deneb.KZGCommitment): bellatrix.Transaction {
// Just use versionedHash as the transaction encoding to mock newPayloadV3 verification
// prefixed with BLOB_TX_TYPE
const transaction = new Uint8Array(33);
const versionedHash = kzgCommitmentToVersionedHash(kzgCommitment);
transaction[0] = BLOB_TX_TYPE;
transaction.set(versionedHash, 1);
return transaction;
}

/**
* Generate random blob of sequential integers such that each element is < BLS_MODULUS
*/
function generateRandomBlob(): deneb.Blob {
const blob = new Uint8Array(FIELD_ELEMENTS_PER_BLOB_MAINNET * BYTES_PER_FIELD_ELEMENT);
const dv = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
for (let i = 0; i < FIELD_ELEMENTS_PER_BLOB_MAINNET; i++) {
dv.setUint32(i * BYTES_PER_FIELD_ELEMENT, i);
}
return blob;
}
26 changes: 26 additions & 0 deletions packages/beacon-node/test/utils/kzg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {bellatrix, deneb} from "@lodestar/types";
import {BLOB_TX_TYPE, BYTES_PER_FIELD_ELEMENT} from "@lodestar/params";
import {kzgCommitmentToVersionedHash} from "../../src/util/blobs.js";
import {FIELD_ELEMENTS_PER_BLOB_MAINNET} from "../../src/util/kzg.js";

export function transactionForKzgCommitment(kzgCommitment: deneb.KZGCommitment): bellatrix.Transaction {
// Just use versionedHash as the transaction encoding to mock newPayloadV3 verification
// prefixed with BLOB_TX_TYPE
const transaction = new Uint8Array(33);
const versionedHash = kzgCommitmentToVersionedHash(kzgCommitment);
transaction[0] = BLOB_TX_TYPE;
transaction.set(versionedHash, 1);
return transaction;
}

/**
* Generate random blob of sequential integers such that each element is < BLS_MODULUS
*/
export function generateRandomBlob(): deneb.Blob {
const blob = new Uint8Array(FIELD_ELEMENTS_PER_BLOB_MAINNET * BYTES_PER_FIELD_ELEMENT);
const dv = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
for (let i = 0; i < FIELD_ELEMENTS_PER_BLOB_MAINNET; i++) {
dv.setUint32(i * BYTES_PER_FIELD_ELEMENT, i);
}
return blob;
}
22 changes: 2 additions & 20 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12085,16 +12085,7 @@ string-argv@~0.3.1:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==

"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -13678,7 +13669,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand All @@ -13696,15 +13687,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
Expand Down
Loading