Skip to content

Commit 1846a98

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 small refactor
1 parent 9b71394 commit 1846a98

Some content is hidden

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

68 files changed

+2054
-262
lines changed

packages/beacon-node/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
"@lodestar/utils": "^1.19.0",
134134
"@lodestar/validator": "^1.19.0",
135135
"@multiformats/multiaddr": "^12.1.3",
136-
"c-kzg": "^2.1.2",
136+
"c-kzg": "matthewkeil/c-kzg-4844#67bf9367817f0fa5ebd390aeb8c3ae88bdbc170e",
137137
"datastore-core": "^9.1.1",
138138
"datastore-level": "^10.1.1",
139139
"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 {routes} from "@lodestar/api";
22
import {ApplicationMethods} from "@lodestar/api/server";
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, fromHex, 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";
@@ -53,17 +56,40 @@ export function getBeaconBlockApi({
5356
opts: PublishBlockOpts = {}
5457
) => {
5558
const seenTimestampSec = Date.now() / 1000;
56-
let blockForImport: BlockInput, signedBlock: allForks.SignedBeaconBlock, blobSidecars: deneb.BlobSidecars;
59+
let blockForImport: BlockInput,
60+
signedBlock: allForks.SignedBeaconBlock,
61+
blobSidecars: deneb.BlobSidecars,
62+
dataColumnSidecars: electra.DataColumnSidecars;
5763

5864
if (isSignedBlockContents(signedBlockOrContents)) {
5965
({signedBlock} = signedBlockOrContents);
60-
blobSidecars = computeBlobSidecars(config, signedBlock, signedBlockOrContents);
61-
const blockData = {
62-
fork: config.getForkName(signedBlock.message.slot),
63-
blobs: blobSidecars,
64-
blobsSource: BlobsSource.api,
65-
blobsBytes: blobSidecars.map(() => null),
66-
} as BlockInputDataBlobs;
66+
const fork = config.getForkName(signedBlock.message.slot);
67+
let blockData: BlockInputData;
68+
if (fork === ForkName.electra) {
69+
dataColumnSidecars = computeDataColumnSidecars(config, signedBlock, signedBlockOrContents);
70+
blockData = {
71+
fork,
72+
dataColumnsLen: dataColumnSidecars.length,
73+
// custodyColumns is a 1 based index of ith column present in dataColumns[custodyColumns[i-1]]
74+
dataColumnsIndex: new Uint8Array(Array.from({length: dataColumnSidecars.length}, (_, j) => 1 + j)),
75+
dataColumns: dataColumnSidecars,
76+
dataColumnsBytes: dataColumnSidecars.map(() => null),
77+
dataColumnsSource: DataColumnsSource.api,
78+
} as BlockInputDataDataColumns;
79+
blobSidecars = [];
80+
} else if (fork === ForkName.deneb) {
81+
blobSidecars = computeBlobSidecars(config, signedBlock, signedBlockOrContents);
82+
blockData = {
83+
fork,
84+
blobs: blobSidecars,
85+
blobsSource: BlobsSource.api,
86+
blobsBytes: blobSidecars.map(() => null),
87+
} as BlockInputDataBlobs;
88+
dataColumnSidecars = [];
89+
} else {
90+
throw Error(`Invalid data fork=${fork} for publish`);
91+
}
92+
6793
blockForImport = getBlockInput.availableData(
6894
config,
6995
signedBlock,
@@ -75,6 +101,7 @@ export function getBeaconBlockApi({
75101
} else {
76102
signedBlock = signedBlockOrContents;
77103
blobSidecars = [];
104+
dataColumnSidecars = [];
78105
blockForImport = getBlockInput.preData(config, signedBlock, BlockSource.api, context?.sszBytes ?? null);
79106
}
80107

@@ -209,6 +236,7 @@ export function getBeaconBlockApi({
209236
// b) they might require more hops to reach recipients in peerDAS kind of setup where
210237
// blobs might need to hop between nodes because of partial subnet subscription
211238
...blobSidecars.map((blobSidecar) => () => network.publishBlobSidecar(blobSidecar)),
239+
...dataColumnSidecars.map((dataColumnSidecar) => () => network.publishDataColumnSidecar(dataColumnSidecar)),
212240
() => network.publishBeaconBlock(signedBlock) as Promise<unknown>,
213241
() =>
214242
// 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

+48-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

@@ -29,23 +29,45 @@ export enum BlobsSource {
2929
byRoot = "req_resp_by_root",
3030
}
3131

32+
export enum DataColumnsSource {
33+
gossip = "gossip",
34+
api = "api",
35+
byRange = "req_resp_by_range",
36+
byRoot = "req_resp_by_root",
37+
}
38+
3239
export enum GossipedInputType {
3340
block = "block",
3441
blob = "blob",
42+
dataColumn = "dataColumn",
3543
}
3644

37-
type BlobsCacheMap = Map<number, {blobSidecar: deneb.BlobSidecar; blobBytes: Uint8Array | null}>;
45+
export type BlobsCacheMap = Map<number, {blobSidecar: deneb.BlobSidecar; blobBytes: Uint8Array | null}>;
46+
export type DataColumnsCacheMap = Map<
47+
number,
48+
{dataColumnSidecar: electra.DataColumnSidecar; dataColumnBytes: Uint8Array | null}
49+
>;
3850

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

44-
export type BlockInputBlobs = {blobs: deneb.BlobSidecars; blobsBytes: (Uint8Array | null)[]; blobsSource: BlobsSource};
45-
type Availability<T> = {availabilityPromise: Promise<T>; resolveAvailability: (data: T) => void};
55+
type ForkDataColumnsInfo = {fork: ForkName.electra};
56+
type DataColumnsData = {
57+
// marker of that columns are to be custodied
58+
dataColumnsLen: number;
59+
dataColumnsIndex: Uint8Array;
60+
dataColumns: electra.DataColumnSidecars;
61+
dataColumnsBytes: (Uint8Array | null)[];
62+
dataColumnsSource: DataColumnsSource;
63+
};
64+
export type BlockInputDataDataColumns = ForkDataColumnsInfo & DataColumnsData;
65+
export type BlockInputData = BlockInputDataBlobs | BlockInputDataDataColumns;
4666

67+
type Availability<T> = {availabilityPromise: Promise<T>; resolveAvailability: (data: T) => void};
4768
type CachedBlobs = {blobsCache: BlobsCacheMap} & Availability<BlockInputDataBlobs>;
48-
export type CachedData = ForkBlobsInfo & CachedBlobs;
69+
type CachedDataColumns = {dataColumnsCache: DataColumnsCacheMap} & Availability<BlockInputDataDataColumns>;
70+
export type CachedData = (ForkBlobsInfo & CachedBlobs) | (ForkDataColumnsInfo & CachedDataColumns);
4971

5072
export type BlockInput = {block: allForks.SignedBeaconBlock; source: BlockSource; blockBytes: Uint8Array | null} & (
5173
| {type: BlockInputType.preData | BlockInputType.outOfRangeData}
@@ -161,6 +183,26 @@ export function getBlockInputBlobs(blobsCache: BlobsCacheMap): Omit<BlobsData, "
161183
return {blobs, blobsBytes};
162184
}
163185

186+
export function getBlockInputDataColumns(
187+
dataColumnsCache: DataColumnsCacheMap,
188+
columnIndexes: ColumnIndex[]
189+
): Omit<DataColumnsData, "dataColumnsLen" | "dataColumnsIndex" | "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+
164206
export enum AttestationImportOpt {
165207
Skip,
166208
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)