Skip to content

Commit 99671a2

Browse files
committed
feat: implement peerDAS on electra
add some presets add further params and types add data column to types repo and network move to max request data columns to preset add the datacolumns data in blockinput and fix breaking errors in seen gossip blockinput handle data columns in gossip and the seengossip further propagate forkaware blockdata and resolve build/type issues further handle datacolumns sync by range by root and forkaware data handling fix issues chore: update c-kzg to peerDas version feat: add peerDas ckzg functions to interface fix the lookups handle the publishing flow various sync try fixes fixes compute blob side car various misl debuggings and fixes debug and apply fixes and get range and by root sync to work will full custody enable syncing with lower custody requirement use node peerid rather than a dummy string get and use the nodeid from enr and correctly compute subnets and column indexes filterout and connect to peers only matching out custody requiremnt try adding custody requirement add protection for subnet calc get the sync working with devnet 0 correctly set the enr with custody subnet info rebase fixes
1 parent 8a26fac commit 99671a2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1967
-236
lines changed

packages/beacon-node/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
"@lodestar/utils": "^1.18.1",
133133
"@lodestar/validator": "^1.18.1",
134134
"@multiformats/multiaddr": "^12.1.3",
135-
"c-kzg": "^2.1.2",
135+
"c-kzg": "matthewkeil/c-kzg-4844#67bf9367817f0fa5ebd390aeb8c3ae88bdbc170e",
136136
"datastore-core": "^9.1.1",
137137
"datastore-level": "^10.1.1",
138138
"deepmerge": "^4.3.1",

packages/beacon-node/src/api/impl/beacon/blocks/index.ts

+39-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
import {fromHexString, toHexString} from "@chainsafe/ssz";
22
import {routes, ServerApi, ResponseFormat} from "@lodestar/api";
33
import {computeEpochAtSlot, computeTimeAtSlot, reconstructFullBlockOrContents} from "@lodestar/state-transition";
4-
import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
4+
import {SLOTS_PER_HISTORICAL_ROOT, ForkName} from "@lodestar/params";
55
import {sleep, toHex} from "@lodestar/utils";
6-
import {allForks, deneb, isSignedBlockContents, ProducedBlockSource} from "@lodestar/types";
6+
import {allForks, deneb, electra, isSignedBlockContents, ProducedBlockSource} from "@lodestar/types";
77
import {
88
BlockSource,
99
getBlockInput,
1010
ImportBlockOpts,
1111
BlockInput,
1212
BlobsSource,
1313
BlockInputDataBlobs,
14+
BlockInputDataDataColumns,
15+
DataColumnsSource,
16+
BlockInputData,
1417
} from "../../../../chain/blocks/types.js";
1518
import {promiseAllMaybeAsync} from "../../../../util/promises.js";
1619
import {isOptimisticBlock} from "../../../../util/forkChoice.js";
17-
import {computeBlobSidecars} from "../../../../util/blobs.js";
20+
import {computeBlobSidecars, computeDataColumnSidecars} from "../../../../util/blobs.js";
1821
import {BlockError, BlockErrorCode, BlockGossipError} from "../../../../chain/errors/index.js";
1922
import {OpSource} from "../../../../metrics/validatorMonitor.js";
2023
import {NetworkEvent} from "../../../../network/index.js";
@@ -49,17 +52,40 @@ export function getBeaconBlockApi({
4952
opts: PublishBlockOpts = {}
5053
) => {
5154
const seenTimestampSec = Date.now() / 1000;
52-
let blockForImport: BlockInput, signedBlock: allForks.SignedBeaconBlock, blobSidecars: deneb.BlobSidecars;
55+
let blockForImport: BlockInput,
56+
signedBlock: allForks.SignedBeaconBlock,
57+
blobSidecars: deneb.BlobSidecars,
58+
dataColumnSidecars: electra.DataColumnSidecars;
5359

5460
if (isSignedBlockContents(signedBlockOrContents)) {
5561
({signedBlock} = signedBlockOrContents);
56-
blobSidecars = computeBlobSidecars(config, signedBlock, signedBlockOrContents);
57-
const blockData = {
58-
fork: config.getForkName(signedBlock.message.slot),
59-
blobs: blobSidecars,
60-
blobsSource: BlobsSource.api,
61-
blobsBytes: blobSidecars.map(() => null),
62-
} as BlockInputDataBlobs;
62+
const fork = config.getForkName(signedBlock.message.slot);
63+
let blockData: BlockInputData;
64+
if (fork === ForkName.electra) {
65+
dataColumnSidecars = computeDataColumnSidecars(config, signedBlock, signedBlockOrContents);
66+
blockData = {
67+
fork,
68+
numColumns: dataColumnSidecars.length,
69+
// custodyColumns is a 1 based index of ith column present in dataColumns[custodyColumns[i-1]]
70+
custodyColumns: new Uint8Array(Array.from({length: dataColumnSidecars.length}, (_, j) => 1 + j)),
71+
dataColumns: dataColumnSidecars,
72+
dataColumnsBytes: dataColumnSidecars.map(() => null),
73+
dataColumnsSource: DataColumnsSource.api,
74+
} as BlockInputDataDataColumns;
75+
blobSidecars = [];
76+
} else if (fork === ForkName.deneb) {
77+
blobSidecars = computeBlobSidecars(config, signedBlock, signedBlockOrContents);
78+
blockData = {
79+
fork,
80+
blobs: blobSidecars,
81+
blobsSource: BlobsSource.api,
82+
blobsBytes: blobSidecars.map(() => null),
83+
} as BlockInputDataBlobs;
84+
dataColumnSidecars = [];
85+
} else {
86+
throw Error(`Invalid data fork=${fork} for publish`);
87+
}
88+
6389
blockForImport = getBlockInput.availableData(
6490
config,
6591
signedBlock,
@@ -71,6 +97,7 @@ export function getBeaconBlockApi({
7197
} else {
7298
signedBlock = signedBlockOrContents;
7399
blobSidecars = [];
100+
dataColumnSidecars = [];
74101
// TODO: Once API supports submitting data as SSZ, replace null with blockBytes
75102
blockForImport = getBlockInput.preData(config, signedBlock, BlockSource.api, null);
76103
}
@@ -206,6 +233,7 @@ export function getBeaconBlockApi({
206233
// b) they might require more hops to reach recipients in peerDAS kind of setup where
207234
// blobs might need to hop between nodes because of partial subnet subscription
208235
...blobSidecars.map((blobSidecar) => () => network.publishBlobSidecar(blobSidecar)),
236+
...dataColumnSidecars.map((dataColumnSidecar) => () => network.publishDataColumnSidecar(dataColumnSidecar)),
209237
() => network.publishBeaconBlock(signedBlock) as Promise<unknown>,
210238
() =>
211239
// there is no rush to persist block since we published it to gossip anyway

packages/beacon-node/src/chain/blocks/importBlock.ts

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {toHexString} from "@chainsafe/ssz";
22
import {capella, ssz, allForks, altair} from "@lodestar/types";
3-
import {ForkSeq, INTERVALS_PER_SLOT, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
3+
import {ForkName, ForkSeq, INTERVALS_PER_SLOT, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
44
import {
55
CachedBeaconStateAltair,
66
computeEpochAtSlot,
@@ -113,18 +113,23 @@ export async function importBlock(
113113
// out of data range blocks and import then in forkchoice although one would not be able to
114114
// attest and propose with such head similar to optimistic sync
115115
if (blockInput.type === BlockInputType.availableData) {
116-
const {blobsSource, blobs} = blockInput.blockData;
117-
118-
this.metrics?.importBlock.blobsBySource.inc({blobsSource});
119-
for (const blobSidecar of blobs) {
120-
const {index, kzgCommitment} = blobSidecar;
121-
this.emitter.emit(routes.events.EventType.blobSidecar, {
122-
blockRoot: blockRootHex,
123-
slot: blockSlot,
124-
index,
125-
kzgCommitment: toHexString(kzgCommitment),
126-
versionedHash: toHexString(kzgCommitmentToVersionedHash(kzgCommitment)),
127-
});
116+
const {blockData} = blockInput;
117+
if (blockData.fork === ForkName.deneb) {
118+
const {blobsSource, blobs} = blockData;
119+
120+
this.metrics?.importBlock.blobsBySource.inc({blobsSource});
121+
for (const blobSidecar of blobs) {
122+
const {index, kzgCommitment} = blobSidecar;
123+
this.emitter.emit(routes.events.EventType.blobSidecar, {
124+
blockRoot: blockRootHex,
125+
slot: blockSlot,
126+
index,
127+
kzgCommitment: toHexString(kzgCommitment),
128+
versionedHash: toHexString(kzgCommitmentToVersionedHash(kzgCommitment)),
129+
});
130+
}
131+
} else if (blockData.fork === ForkName.electra) {
132+
// TODO peerDAS build and emit the event for the datacolumns
128133
}
129134
}
130135
});

packages/beacon-node/src/chain/blocks/types.ts

+52-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition";
22
import {MaybeValidExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice";
3-
import {allForks, deneb, Slot, RootHex} from "@lodestar/types";
3+
import {allForks, deneb, Slot, RootHex, electra, ColumnIndex} from "@lodestar/types";
44
import {ForkSeq, ForkName} from "@lodestar/params";
55
import {ChainForkConfig} from "@lodestar/config";
66

@@ -28,26 +28,52 @@ export enum BlobsSource {
2828
byRoot = "req_resp_by_root",
2929
}
3030

31+
export enum DataColumnsSource {
32+
gossip = "gossip",
33+
api = "api",
34+
byRange = "req_resp_by_range",
35+
byRoot = "req_resp_by_root",
36+
}
37+
3138
export enum GossipedInputType {
3239
block = "block",
3340
blob = "blob",
41+
dataColumn = "dataColumn",
3442
}
3543

3644
export type BlobsCache = Map<number, {blobSidecar: deneb.BlobSidecar; blobBytes: Uint8Array | null}>;
45+
export type DataColumnsCache = Map<
46+
number,
47+
{dataColumnSidecar: electra.DataColumnSidecar; dataColumnBytes: Uint8Array | null}
48+
>;
3749

3850
type ForkBlobsInfo = {fork: ForkName.deneb};
3951
type BlobsData = {blobs: deneb.BlobSidecars; blobsBytes: (Uint8Array | null)[]; blobsSource: BlobsSource};
4052
export type BlockInputDataBlobs = ForkBlobsInfo & BlobsData;
41-
export type BlockInputData = BlockInputDataBlobs;
4253

43-
type BlobsInputCache = {blobsCache: BlobsCache};
44-
export type BlockInputCacheBlobs = ForkBlobsInfo & BlobsInputCache;
54+
type ForkDataColumnsInfo = {fork: ForkName.electra};
55+
type DataColumnsData = {
56+
// marker of that columns are to be custodied
57+
numColumns: number;
58+
custodyColumns: Uint8Array;
59+
dataColumns: electra.DataColumnSidecars;
60+
dataColumnsBytes: (Uint8Array | null)[];
61+
dataColumnsSource: DataColumnsSource;
62+
};
63+
export type BlockInputDataDataColumns = ForkDataColumnsInfo & DataColumnsData;
64+
65+
export type BlockInputData = BlockInputDataBlobs | BlockInputDataDataColumns;
4566

46-
export type BlockInputBlobs = {blobs: deneb.BlobSidecars; blobsBytes: (Uint8Array | null)[]; blobsSource: BlobsSource};
4767
type Availability<T> = {availabilityPromise: Promise<T>; resolveAvailability: (data: T) => void};
4868

69+
type BlobsInputCache = {blobsCache: BlobsCache};
70+
export type BlockInputCacheBlobs = ForkBlobsInfo & BlobsInputCache;
4971
type CachedBlobs = BlobsInputCache & Availability<BlockInputDataBlobs>;
50-
export type CachedData = ForkBlobsInfo & CachedBlobs;
72+
73+
type DataColumnsInputCache = {dataColumnsCache: DataColumnsCache};
74+
type CachedDataColumns = DataColumnsInputCache & Availability<BlockInputDataDataColumns>;
75+
76+
export type CachedData = (ForkBlobsInfo & CachedBlobs) | (ForkDataColumnsInfo & CachedDataColumns);
5177

5278
export type BlockInput = {block: allForks.SignedBeaconBlock; source: BlockSource; blockBytes: Uint8Array | null} & (
5379
| {type: BlockInputType.preData | BlockInputType.outOfRangeData}
@@ -157,6 +183,26 @@ export function getBlockInputBlobs(blobsCache: BlobsCache): Omit<BlobsData, "blo
157183
return {blobs, blobsBytes};
158184
}
159185

186+
export function getBlockInputDataColumns(
187+
dataColumnsCache: DataColumnsCache,
188+
columnIndexes: ColumnIndex[]
189+
): Omit<DataColumnsData, "numColumns" | "custodyColumns" | "dataColumnsSource"> {
190+
const dataColumns = [];
191+
const dataColumnsBytes = [];
192+
193+
for (const index of columnIndexes) {
194+
const dataColumnCache = dataColumnsCache.get(index);
195+
if (dataColumnCache === undefined) {
196+
// check if the index is correct as per the custody columns
197+
throw Error(`Missing dataColumnCache at index=${index}`);
198+
}
199+
const {dataColumnSidecar, dataColumnBytes} = dataColumnCache;
200+
dataColumns.push(dataColumnSidecar);
201+
dataColumnsBytes.push(dataColumnBytes);
202+
}
203+
return {dataColumns, dataColumnsBytes};
204+
}
205+
160206
export enum AttestationImportOpt {
161207
Skip,
162208
Force,

packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts

+33-14
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@ import {DataAvailabilityStatus} from "@lodestar/fork-choice";
33
import {ChainForkConfig} from "@lodestar/config";
44
import {deneb, UintNum64} from "@lodestar/types";
55
import {Logger} from "@lodestar/utils";
6+
import {ForkName} from "@lodestar/params";
67
import {BlockError, BlockErrorCode} from "../errors/index.js";
78
import {validateBlobSidecars} from "../validation/blobSidecar.js";
9+
import {validateDataColumnsSidecars} from "../validation/dataColumnSidecar.js";
810
import {Metrics} from "../../metrics/metrics.js";
9-
import {BlockInput, BlockInputType, ImportBlockOpts, BlobSidecarValidation, getBlockInput} from "./types.js";
11+
import {
12+
BlockInput,
13+
BlockInputType,
14+
ImportBlockOpts,
15+
BlobSidecarValidation,
16+
getBlockInput,
17+
BlockInputData,
18+
} from "./types.js";
1019

1120
// we can now wait for full 12 seconds because unavailable block sync will try pulling
1221
// the blobs from the network anyway after 500ms of seeing the block
@@ -88,27 +97,37 @@ async function maybeValidateBlobs(
8897
// run full validation
8998
const {block} = blockInput;
9099
const blockSlot = block.message.slot;
91-
92-
const blobsData =
93-
blockInput.type === BlockInputType.availableData
94-
? blockInput.blockData
95-
: await raceWithCutoff(chain, blockInput, blockInput.cachedData.availabilityPromise);
96-
const {blobs} = blobsData;
97-
98100
const {blobKzgCommitments} = (block as deneb.SignedBeaconBlock).message.body;
99101
const beaconBlockRoot = chain.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message);
100-
101-
// if the blob siddecars have been individually verified then we can skip kzg proof check
102-
// but other checks to match blobs with block data still need to be performed
103-
const skipProofsCheck = opts.validBlobSidecars === BlobSidecarValidation.Individual;
104-
validateBlobSidecars(blockSlot, beaconBlockRoot, blobKzgCommitments, blobs, {skipProofsCheck});
102+
const blockData =
103+
blockInput.type === BlockInputType.availableData
104+
? blockInput.blockData
105+
: await raceWithCutoff(
106+
chain,
107+
blockInput,
108+
blockInput.cachedData.availabilityPromise as Promise<BlockInputData>
109+
);
110+
111+
if (blockData.fork === ForkName.deneb) {
112+
const {blobs} = blockData;
113+
114+
// if the blob siddecars have been individually verified then we can skip kzg proof check
115+
// but other checks to match blobs with block data still need to be performed
116+
const skipProofsCheck = opts.validBlobSidecars === BlobSidecarValidation.Individual;
117+
validateBlobSidecars(blockSlot, beaconBlockRoot, blobKzgCommitments, blobs, {skipProofsCheck});
118+
} else if (blockData.fork === ForkName.electra) {
119+
const {dataColumns} = blockData;
120+
const skipProofsCheck = opts.validBlobSidecars === BlobSidecarValidation.Individual;
121+
// might require numColumns, custodyColumns from blockData as input to below
122+
validateDataColumnsSidecars(blockSlot, beaconBlockRoot, blobKzgCommitments, dataColumns, {skipProofsCheck});
123+
}
105124

106125
const availableBlockInput = getBlockInput.availableData(
107126
chain.config,
108127
blockInput.block,
109128
blockInput.source,
110129
blockInput.blockBytes,
111-
blobsData
130+
blockData
112131
);
113132
return {dataAvailabilityStatus: DataAvailabilityStatus.Available, availableBlockInput: availableBlockInput};
114133
}

0 commit comments

Comments
 (0)