@lodestar/beacon-node 1.51.0-dev.a736f94f66 → 1.51.0-dev.ab919f3073
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/impl/api.js +1 -1
- package/lib/api/impl/api.js.map +1 -1
- package/lib/api/impl/beacon/blocks/index.d.ts +2 -1
- package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +12 -7
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/index.d.ts +2 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js +2 -2
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/options.d.ts +2 -0
- package/lib/api/options.d.ts.map +1 -1
- package/lib/api/options.js +1 -0
- package/lib/api/options.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +54 -96
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +6 -5
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +2 -1
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/chain.js +4 -4
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/opPools/inclusionListStore.d.ts.map +1 -1
- package/lib/chain/opPools/inclusionListStore.js +2 -2
- package/lib/chain/opPools/inclusionListStore.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +0 -1
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +0 -1
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +0 -1
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts +0 -15
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +0 -153
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/inclusionList.d.ts.map +1 -1
- package/lib/chain/validation/inclusionList.js +11 -2
- package/lib/chain/validation/inclusionList.js.map +1 -1
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +2 -0
- package/lib/db/beacon.js.map +1 -1
- package/lib/execution/engine/blobVersionedHashes.d.ts +1 -1
- package/lib/execution/engine/blobVersionedHashes.d.ts.map +1 -1
- package/lib/execution/engine/blobVersionedHashes.js.map +1 -1
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +0 -1
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +6 -4
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/payloadIdCache.d.ts +1 -2
- package/lib/execution/engine/payloadIdCache.d.ts.map +1 -1
- package/lib/execution/engine/payloadIdCache.js +3 -3
- package/lib/execution/engine/payloadIdCache.js.map +1 -1
- package/lib/execution/engine/sszTypes.d.ts +38 -38
- package/lib/execution/engine/types.d.ts +1 -1
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.js +7 -6
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +3 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +6 -6
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +27 -18
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +42 -18
- package/lib/sync/unknownBlock.js.map +1 -1
- package/package.json +30 -30
- package/src/api/impl/api.ts +1 -1
- package/src/api/impl/beacon/blocks/index.ts +22 -17
- package/src/api/impl/beacon/index.ts +3 -1
- package/src/api/options.ts +3 -0
- package/src/chain/archiveStore/utils/archiveBlocks.ts +64 -126
- package/src/chain/blocks/importBlock.ts +8 -7
- package/src/chain/blocks/importExecutionPayload.ts +2 -1
- package/src/chain/chain.ts +4 -4
- package/src/chain/opPools/inclusionListStore.ts +3 -3
- package/src/chain/produceBlock/produceBlockBody.ts +0 -1
- package/src/chain/regen/interface.ts +0 -1
- package/src/chain/validation/executionPayloadBid.ts +0 -180
- package/src/chain/validation/inclusionList.ts +11 -2
- package/src/db/beacon.ts +5 -0
- package/src/execution/engine/blobVersionedHashes.ts +1 -1
- package/src/execution/engine/http.ts +0 -1
- package/src/execution/engine/mock.ts +6 -4
- package/src/execution/engine/payloadIdCache.ts +6 -7
- package/src/execution/engine/types.ts +8 -8
- package/src/index.ts +2 -0
- package/src/metrics/metrics/lodestar.ts +5 -0
- package/src/network/processor/gossipHandlers.ts +35 -20
- package/src/sync/unknownBlock.ts +47 -28
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {KeyValue} from "@lodestar/db";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CheckpointWithHex,
|
|
6
|
+
ExecutionStatus,
|
|
7
|
+
IForkChoice,
|
|
8
|
+
PayloadStatus,
|
|
9
|
+
ProtoBlock,
|
|
10
|
+
isGloasBlock,
|
|
11
|
+
} from "@lodestar/fork-choice";
|
|
5
12
|
import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
6
13
|
import {computeEpochAtSlot, computeStartSlotAtEpoch, getBlobDataRetentionStartEpoch} from "@lodestar/state-transition";
|
|
7
14
|
import {Epoch, Slot} from "@lodestar/types";
|
|
8
|
-
import {Logger,
|
|
15
|
+
import {Logger, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
9
16
|
import {IBeaconDb} from "../../../db/index.js";
|
|
10
17
|
import {BlockArchiveBatchPutBinaryItem, encodeArchivedHeaderEnvelope} from "../../../db/repositories/index.js";
|
|
11
18
|
import {Metrics} from "../../../metrics/metrics.js";
|
|
@@ -136,23 +143,6 @@ export async function archiveBlocks(
|
|
|
136
143
|
logger.verbose("Migrated blobSidecars from hot DB to cold DB", {...logCtx, migratedEntries});
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
// Keep the normal LevelDB hot-to-cold flow for legacy columns. New columns are written
|
|
140
|
-
// directly to flat files, so this only moves fallback data left by older versions.
|
|
141
|
-
if (finalizedPostFulu) {
|
|
142
|
-
const migratedSlots = await migrateDataColumnSidecarsFromHotToColdDb(
|
|
143
|
-
config,
|
|
144
|
-
db,
|
|
145
|
-
logger,
|
|
146
|
-
finalizedCanonicalBlocks,
|
|
147
|
-
currentEpoch
|
|
148
|
-
);
|
|
149
|
-
logger.verbose("Migrated legacy dataColumnSidecars from hot DB to cold DB", {
|
|
150
|
-
...logCtx,
|
|
151
|
-
migratedEntries: migratedSlots.length,
|
|
152
|
-
slotRange: prettyPrintIndices(migratedSlots),
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
146
|
if (finalizedPostGloas) {
|
|
157
147
|
const migratedSlots = await migrateExecutionPayloadEnvelopesFromHotToColdDb(
|
|
158
148
|
config,
|
|
@@ -169,44 +159,34 @@ export async function archiveBlocks(
|
|
|
169
159
|
}
|
|
170
160
|
}
|
|
171
161
|
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
const canonicalRoots = new Set(finalizedCanonicalBlocks.map((block) => block.blockRoot));
|
|
163
|
+
const orphanedBlocks: ProtoBlock[] = [];
|
|
164
|
+
const orphanedPayloads: ProtoBlock[] = [];
|
|
174
165
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// Persist orphaned blocks to disk before deleting them from hot db
|
|
179
|
-
await Promise.all(
|
|
180
|
-
nonCanonicalBlockRoots.map(async (root, index) => {
|
|
181
|
-
const block = finalizedNonCanonicalBlocks[index];
|
|
182
|
-
const blockBytes = await db.block.getBinary(root);
|
|
183
|
-
const blockLogCtx = {slot: block.slot, root: block.blockRoot};
|
|
184
|
-
if (blockBytes) {
|
|
185
|
-
await persistOrphanedBlock(block.slot, block.blockRoot, blockBytes, {
|
|
186
|
-
persistOrphanedBlocksDir: persistOrphanedBlocksDir ?? "orphaned_blocks",
|
|
187
|
-
});
|
|
188
|
-
logger.verbose("Persisted orphaned block", {...logCtx, ...blockLogCtx});
|
|
189
|
-
} else {
|
|
190
|
-
logger.warn("Tried to persist orphaned block but no block found", {...logCtx, ...blockLogCtx});
|
|
191
|
-
}
|
|
192
|
-
})
|
|
193
|
-
);
|
|
166
|
+
for (const block of finalizedNonCanonicalBlocks) {
|
|
167
|
+
if (block.payloadStatus === PayloadStatus.FULL) {
|
|
168
|
+
orphanedPayloads.push(block);
|
|
194
169
|
}
|
|
170
|
+
// Every Gloas block gets EMPTY before FULL. If only FULL survives pruning, an earlier archive run
|
|
171
|
+
// already handled the root's block-level data.
|
|
172
|
+
const isBlockNode = !isGloasBlock(block) || block.payloadStatus === PayloadStatus.EMPTY;
|
|
173
|
+
if (isBlockNode && !canonicalRoots.has(block.blockRoot)) {
|
|
174
|
+
orphanedBlocks.push(block);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const orphanedBlockRoots = orphanedBlocks.map((block) => fromHex(block.blockRoot));
|
|
195
178
|
|
|
196
|
-
|
|
197
|
-
const
|
|
179
|
+
if (orphanedPayloads.length > 0) {
|
|
180
|
+
const orphanedPayloadRoots = orphanedPayloads.map((block) => fromHex(block.blockRoot));
|
|
181
|
+
const orphanedPayloadLogCtx = {
|
|
198
182
|
...logCtx,
|
|
199
|
-
count:
|
|
200
|
-
slotRange: prettyPrintIndices(
|
|
183
|
+
count: orphanedPayloads.length,
|
|
184
|
+
slotRange: prettyPrintIndices(orphanedPayloads.map((block) => block.slot).sort((a, b) => a - b)),
|
|
201
185
|
};
|
|
202
186
|
|
|
203
|
-
const columnItems =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
.filter(
|
|
207
|
-
(summary) => config.getForkSeq(summary.slot) >= ForkSeq.fulu && summary.payloadStatus === PayloadStatus.FULL
|
|
208
|
-
)
|
|
209
|
-
.map((summary) => ({slot: summary.slot, blockRoot: summary.blockRoot}));
|
|
187
|
+
const columnItems = orphanedPayloads
|
|
188
|
+
.filter((block) => config.getForkSeq(block.slot) >= ForkSeq.fulu)
|
|
189
|
+
.map((block) => ({slot: block.slot, blockRoot: block.blockRoot}));
|
|
210
190
|
if (columnItems.length > 0) {
|
|
211
191
|
// Delete sidecars first so their block roots remain available to retry cleanup after a failure or crash.
|
|
212
192
|
await db.dataColumns.deleteMany(columnItems);
|
|
@@ -217,18 +197,42 @@ export async function archiveBlocks(
|
|
|
217
197
|
});
|
|
218
198
|
}
|
|
219
199
|
|
|
220
|
-
await db.block.batchDelete(nonCanonicalBlockRoots);
|
|
221
|
-
logger.verbose("Deleted non canonical blocks from hot DB", nonCanonicalLogCtx);
|
|
222
|
-
|
|
223
200
|
if (finalizedPostDeneb) {
|
|
224
|
-
await db.blobSidecars.batchDelete(
|
|
225
|
-
logger.verbose("Deleted non canonical blobSidecars from hot DB",
|
|
201
|
+
await db.blobSidecars.batchDelete(orphanedPayloadRoots);
|
|
202
|
+
logger.verbose("Deleted non canonical blobSidecars from hot DB", orphanedPayloadLogCtx);
|
|
226
203
|
}
|
|
227
204
|
|
|
228
205
|
if (finalizedPostGloas) {
|
|
229
|
-
await db.executionPayloadEnvelope.batchDelete(
|
|
230
|
-
logger.verbose("Deleted non canonical executionPayloadEnvelopes from hot DB",
|
|
206
|
+
await db.executionPayloadEnvelope.batchDelete(orphanedPayloadRoots);
|
|
207
|
+
logger.verbose("Deleted non canonical executionPayloadEnvelopes from hot DB", orphanedPayloadLogCtx);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (orphanedBlocks.length > 0) {
|
|
212
|
+
if (persistOrphanedBlocks) {
|
|
213
|
+
// Persist orphaned blocks to disk before deleting them from hot db
|
|
214
|
+
await Promise.all(
|
|
215
|
+
orphanedBlocks.map(async (block) => {
|
|
216
|
+
const blockBytes = await db.block.getBinary(fromHex(block.blockRoot));
|
|
217
|
+
const blockLogCtx = {slot: block.slot, root: block.blockRoot};
|
|
218
|
+
if (blockBytes) {
|
|
219
|
+
await persistOrphanedBlock(block.slot, block.blockRoot, blockBytes, {
|
|
220
|
+
persistOrphanedBlocksDir: persistOrphanedBlocksDir ?? "orphaned_blocks",
|
|
221
|
+
});
|
|
222
|
+
logger.verbose("Persisted orphaned block", {...logCtx, ...blockLogCtx});
|
|
223
|
+
} else {
|
|
224
|
+
logger.debug("Tried to persist orphaned block but no block found", {...logCtx, ...blockLogCtx});
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
);
|
|
231
228
|
}
|
|
229
|
+
|
|
230
|
+
await db.block.batchDelete(orphanedBlockRoots);
|
|
231
|
+
logger.verbose("Deleted non canonical blocks from hot DB", {
|
|
232
|
+
...logCtx,
|
|
233
|
+
count: orphanedBlocks.length,
|
|
234
|
+
slotRange: prettyPrintIndices(orphanedBlocks.map((block) => block.slot).sort((a, b) => a - b)),
|
|
235
|
+
});
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
// Delete expired blobs
|
|
@@ -283,9 +287,9 @@ export async function archiveBlocks(
|
|
|
283
287
|
}
|
|
284
288
|
}
|
|
285
289
|
|
|
286
|
-
//
|
|
290
|
+
// Pruning potential checkpoint data
|
|
287
291
|
const finalizedCanonicalNonCheckpointBlocks = getNonCheckpointBlocks(finalizedCanonicalBlockRoots);
|
|
288
|
-
const nonCheckpointBlockRoots: Uint8Array[] = [...
|
|
292
|
+
const nonCheckpointBlockRoots: Uint8Array[] = [...orphanedBlockRoots];
|
|
289
293
|
for (const block of finalizedCanonicalNonCheckpointBlocks) {
|
|
290
294
|
nonCheckpointBlockRoots.push(block.root);
|
|
291
295
|
}
|
|
@@ -408,72 +412,6 @@ async function migrateBlobSidecarsFromHotToColdDb(
|
|
|
408
412
|
return migratedWrappedBlobSidecars;
|
|
409
413
|
}
|
|
410
414
|
|
|
411
|
-
// TODO: This function can be simplified further by reducing layers of promises in a loop
|
|
412
|
-
/**
|
|
413
|
-
* Post-gloas the data columns of a Gloas block are tied to its execution payload envelope.
|
|
414
|
-
* Columns only exist once the FULL variant of the block is in the proto-array. Pre-Gloas
|
|
415
|
-
* blocks only have a FULL variant, so the payload-status filter passes them all.
|
|
416
|
-
*/
|
|
417
|
-
async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
418
|
-
config: ChainForkConfig,
|
|
419
|
-
db: IBeaconDb,
|
|
420
|
-
logger: Logger,
|
|
421
|
-
canonicalBlocks: ProtoBlock[],
|
|
422
|
-
currentEpoch: Epoch
|
|
423
|
-
): Promise<Slot[]> {
|
|
424
|
-
const columnBlocks = canonicalBlocks.filter(
|
|
425
|
-
(block) => config.getForkSeq(block.slot) < ForkSeq.gloas || block.payloadStatus === PayloadStatus.FULL
|
|
426
|
-
);
|
|
427
|
-
if (columnBlocks.length === 0) return [];
|
|
428
|
-
const blocks: BlockRootSlot[] = columnBlocks.map((block) => ({slot: block.slot, root: fromHex(block.blockRoot)}));
|
|
429
|
-
|
|
430
|
-
const migratedSlots: Slot[] = [];
|
|
431
|
-
for (let i = 0; i < blocks.length; i += BLOB_SIDECAR_BATCH_SIZE) {
|
|
432
|
-
const toIdx = Math.min(i + BLOB_SIDECAR_BATCH_SIZE, blocks.length);
|
|
433
|
-
const batch = blocks.slice(i, toIdx);
|
|
434
|
-
if (batch.length === 0) break;
|
|
435
|
-
|
|
436
|
-
const promises: Promise<void>[] = [];
|
|
437
|
-
for (const block of batch) {
|
|
438
|
-
const blockEpoch = computeEpochAtSlot(block.slot);
|
|
439
|
-
if (
|
|
440
|
-
config.getForkSeq(block.slot) < ForkSeq.fulu ||
|
|
441
|
-
blockEpoch < getBlobDataRetentionStartEpoch(config, currentEpoch)
|
|
442
|
-
) {
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const dataColumnSidecarBytes = await fromAsync(db.dataColumnSidecar.valuesStreamBinary(block.root));
|
|
447
|
-
if (dataColumnSidecarBytes.length === 0) {
|
|
448
|
-
logger.debug("DataColumnSidecars in forkchoice but missing in hot db, could be already archived", {
|
|
449
|
-
slot: block.slot,
|
|
450
|
-
root: toRootHex(block.root),
|
|
451
|
-
});
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
logger.verbose("Migrated legacy dataColumnSidecars for block", {
|
|
455
|
-
currentEpoch,
|
|
456
|
-
slot: block.slot,
|
|
457
|
-
root: toRootHex(block.root),
|
|
458
|
-
numSidecars: dataColumnSidecarBytes.length,
|
|
459
|
-
});
|
|
460
|
-
promises.push(
|
|
461
|
-
db.dataColumnSidecarArchive.putManyBinary(
|
|
462
|
-
block.slot,
|
|
463
|
-
dataColumnSidecarBytes.map((entry) => ({key: entry.id, value: entry.value}))
|
|
464
|
-
)
|
|
465
|
-
);
|
|
466
|
-
migratedSlots.push(block.slot);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
await Promise.all(promises);
|
|
470
|
-
await db.dataColumnSidecar.deleteMany(batch.map((block) => block.root));
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Ancestor walk is newest to oldest; sort ascending so `prettyPrintIndices` renders cleanly.
|
|
474
|
-
return migratedSlots.sort((a, b) => a - b);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
415
|
/**
|
|
478
416
|
* Post-gloas given a finalized checkpoint at a block root, payload of that block root
|
|
479
417
|
* is not considered finalized, hence they are archived in the next run.
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
IBeaconStateView,
|
|
22
22
|
RootCache,
|
|
23
23
|
computeEpochAtSlot,
|
|
24
|
-
|
|
24
|
+
computeTimeAtSlotMs,
|
|
25
25
|
getBeaconCommittees,
|
|
26
26
|
isStatePostAltair,
|
|
27
27
|
isStatePostBellatrix,
|
|
@@ -105,8 +105,9 @@ export async function importBlock(
|
|
|
105
105
|
const currentEpoch = computeEpochAtSlot(currentSlot);
|
|
106
106
|
const blockEpoch = computeEpochAtSlot(blockSlot);
|
|
107
107
|
const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
|
|
108
|
-
const
|
|
109
|
-
fullyVerifiedBlock.seenTimestampSec
|
|
108
|
+
const receiveDelayMs =
|
|
109
|
+
Math.round(fullyVerifiedBlock.seenTimestampSec * 1000) -
|
|
110
|
+
computeTimeAtSlotMs(this.config, blockSlot, postState.genesisTime * 1000);
|
|
110
111
|
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
|
|
111
112
|
const fork = this.config.getForkSeq(blockSlot);
|
|
112
113
|
|
|
@@ -148,12 +149,12 @@ export async function importBlock(
|
|
|
148
149
|
executionStatus = parentBlock.executionStatus;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
const
|
|
152
|
+
const importDelayMs = this.clock.msFromSlot(blockSlot);
|
|
152
153
|
const blockSummary = this.forkChoice.onBlock(
|
|
153
154
|
block.message,
|
|
154
155
|
postState,
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
receiveDelayMs,
|
|
157
|
+
importDelayMs,
|
|
157
158
|
currentSlot,
|
|
158
159
|
executionStatus,
|
|
159
160
|
dataAvailabilityStatus
|
|
@@ -417,7 +418,7 @@ export async function importBlock(
|
|
|
417
418
|
// 4) Block meets the criteria of being re-orged out (this is also checked inside the function)
|
|
418
419
|
const result = this.forkChoice.shouldOverrideForkChoiceUpdate(
|
|
419
420
|
blockSummary,
|
|
420
|
-
this.clock.
|
|
421
|
+
this.clock.msFromSlot(currentSlot),
|
|
421
422
|
currentSlot
|
|
422
423
|
);
|
|
423
424
|
shouldOverrideFcu = result.shouldOverrideFcu;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {SerializedList} from "@chainsafe/ssz";
|
|
1
2
|
import {routes} from "@lodestar/api";
|
|
2
3
|
import {
|
|
3
4
|
ExecutionStatus,
|
|
@@ -206,7 +207,7 @@ export async function importExecutionPayload(
|
|
|
206
207
|
}
|
|
207
208
|
inclusionListTransactions =
|
|
208
209
|
dependentRoot === null
|
|
209
|
-
?
|
|
210
|
+
? SerializedList.empty()
|
|
210
211
|
: this.inclusionListStore.getInclusionListTransactions(inclusionListSlot, dependentRoot, true);
|
|
211
212
|
}
|
|
212
213
|
|
package/src/chain/chain.ts
CHANGED
|
@@ -1486,10 +1486,10 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1486
1486
|
this.metrics?.forkChoice.requests.inc();
|
|
1487
1487
|
const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.predictProposerHead});
|
|
1488
1488
|
const slot = this.clock.currentSlot;
|
|
1489
|
-
const
|
|
1489
|
+
const msFromSlot = this.clock.msFromSlot(slot);
|
|
1490
1490
|
|
|
1491
1491
|
try {
|
|
1492
|
-
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead,
|
|
1492
|
+
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, msFromSlot, slot}).head;
|
|
1493
1493
|
} catch (e) {
|
|
1494
1494
|
this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetPredictedProposerHead});
|
|
1495
1495
|
throw e;
|
|
@@ -1501,13 +1501,13 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1501
1501
|
getProposerHead(slot: Slot): ProtoBlock {
|
|
1502
1502
|
this.metrics?.forkChoice.requests.inc();
|
|
1503
1503
|
const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.getProposerHead});
|
|
1504
|
-
const
|
|
1504
|
+
const msFromSlot = this.clock.msFromSlot(slot);
|
|
1505
1505
|
|
|
1506
1506
|
try {
|
|
1507
1507
|
// Do not emit head event here, when proposing we rely on the one emitted when importing our own block
|
|
1508
1508
|
const {head, isHeadTimely, notReorgedReason} = this.forkChoice.updateAndGetHead({
|
|
1509
1509
|
mode: UpdateHeadOpt.GetProposerHead,
|
|
1510
|
-
|
|
1510
|
+
msFromSlot,
|
|
1511
1511
|
slot,
|
|
1512
1512
|
});
|
|
1513
1513
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {BitArray} from "@chainsafe/ssz";
|
|
1
|
+
import {BitArray, SerializedList} from "@chainsafe/ssz";
|
|
2
2
|
import {BeaconConfig} from "@lodestar/config";
|
|
3
3
|
import {INCLUSION_LIST_COMMITTEE_SIZE} from "@lodestar/params";
|
|
4
4
|
import {RootHex, Slot, ValidatorIndex, bellatrix, heze, ssz} from "@lodestar/types";
|
|
@@ -108,7 +108,7 @@ export class InclusionListStore {
|
|
|
108
108
|
|
|
109
109
|
/** Deduplicated transactions from valid, non-equivocating inclusion lists at `(slot, dependentRoot)`. */
|
|
110
110
|
getInclusionListTransactions(slot: Slot, dependentRoot: RootHex, onlyTimely = true): bellatrix.Transactions {
|
|
111
|
-
const transactions:
|
|
111
|
+
const transactions: Uint8Array[] = [];
|
|
112
112
|
const seen = new Set<string>();
|
|
113
113
|
|
|
114
114
|
for (const {signedInclusionList} of this.getEligible(slot, dependentRoot, onlyTimely)) {
|
|
@@ -121,7 +121,7 @@ export class InclusionListStore {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
return transactions;
|
|
124
|
+
return SerializedList.fromArray(transactions);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/** Bit `i` is set when committee member `i` submitted a valid, non-equivocating inclusion list */
|
|
@@ -781,7 +781,6 @@ export async function prepareExecutionPayload(
|
|
|
781
781
|
|
|
782
782
|
const payloadIdCached = chain.executionEngine.payloadIdCache.get({
|
|
783
783
|
headBlockHash: toRootHex(parentBlockHash),
|
|
784
|
-
finalizedBlockHash,
|
|
785
784
|
timestamp: numToQuantity(timestamp),
|
|
786
785
|
prevRandao: toHex(prevRandao),
|
|
787
786
|
suggestedFeeRecipient,
|
|
@@ -25,7 +25,6 @@ export enum RegenCaller {
|
|
|
25
25
|
publishDeferredVoluntaryExits = "publishDeferredVoluntaryExits",
|
|
26
26
|
validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
|
|
27
27
|
validateGossipInclusionList = "validateGossipInclusionList",
|
|
28
|
-
validateApiExecutionPayloadBid = "validateApiExecutionPayloadBid",
|
|
29
28
|
validateGossipPayloadAttestationMessage = "validateGossipPayloadAttestationMessage",
|
|
30
29
|
validateGossipProposerPreferences = "validateGossipProposerPreferences",
|
|
31
30
|
onForkChoiceFinalized = "onForkChoiceFinalized",
|
|
@@ -83,186 +83,6 @@ function isBidCompatibleWithHead(
|
|
|
83
83
|
return buildsOnParentPayload;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/**
|
|
87
|
-
* Validation for bids submitted via the API by the operator's own builder.
|
|
88
|
-
*
|
|
89
|
-
* Only non-transient checks are applied: slot later than parent, zero execution payment, blob
|
|
90
|
-
* commitment count, builder eligibility, version and balance coverage, prev_randao, and signature.
|
|
91
|
-
* Transient gossip rules (head compatibility, first bid per tuple, value increment, proposer
|
|
92
|
-
* preferences) are not applied, since those only limit forwarding of peers' messages and the
|
|
93
|
-
* builder may legitimately bid on a branch this node does not consider head.
|
|
94
|
-
* The parent-payload builder-exit rule is omitted because this endpoint only accepts bids from
|
|
95
|
-
* the operator's own builder.
|
|
96
|
-
*
|
|
97
|
-
* Throws on any failed check. Also throws if the bid's parent block is unknown or its state is
|
|
98
|
-
* unavailable, since the checks cannot be evaluated against the parent branch.
|
|
99
|
-
*/
|
|
100
|
-
export async function validateApiExecutionPayloadBid(
|
|
101
|
-
chain: IBeaconChain,
|
|
102
|
-
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
|
|
103
|
-
): Promise<void> {
|
|
104
|
-
const bid = signedExecutionPayloadBid.message;
|
|
105
|
-
const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
|
|
106
|
-
|
|
107
|
-
// [REJECT] `bid.execution_payment` is zero.
|
|
108
|
-
if (bid.executionPayment !== 0n) {
|
|
109
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
110
|
-
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
111
|
-
builderIndex: bid.builderIndex,
|
|
112
|
-
executionPayment: bid.executionPayment,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// [REJECT] The bid's block hash is not equal to its parent block hash -- i.e.
|
|
117
|
-
// `bid.block_hash != bid.parent_block_hash`.
|
|
118
|
-
if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
|
|
119
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
120
|
-
code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH,
|
|
121
|
-
blockHash: toRootHex(bid.blockHash),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
126
|
-
// consensus layer -- i.e. validate that
|
|
127
|
-
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
128
|
-
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
129
|
-
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
130
|
-
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
131
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
132
|
-
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
133
|
-
blobKzgCommitmentsLen,
|
|
134
|
-
commitmentLimit: maxBlobsPerBlock,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
139
|
-
if (
|
|
140
|
-
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
|
|
141
|
-
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot - 1)
|
|
142
|
-
) {
|
|
143
|
-
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
144
|
-
code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
|
|
145
|
-
builderIndex: bid.builderIndex,
|
|
146
|
-
slot: bid.slot,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// [IGNORE] The bid is received after the inclusion list deadline of `bid.slot - 1`
|
|
151
|
-
if (
|
|
152
|
-
isForkPostHeze(chain.config.getForkName(bid.slot)) &&
|
|
153
|
-
chain.clock.msFromSlot(bid.slot - 1) < chain.config.getInclusionListDueMs(chain.config.getForkName(bid.slot - 1))
|
|
154
|
-
) {
|
|
155
|
-
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
156
|
-
code: ExecutionPayloadBidErrorCode.BEFORE_INCLUSION_LIST_DEADLINE,
|
|
157
|
-
builderIndex: bid.builderIndex,
|
|
158
|
-
slot: bid.slot,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// [IGNORE] `bid.parent_block_root` is the hash tree root of a known beacon block in fork choice.
|
|
163
|
-
// Moved earlier than the spec ordering so we can derive the proposer dependent root for the
|
|
164
|
-
// proposer-preferences lookup below from a known fork-choice block.
|
|
165
|
-
const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(bidParentBlockRoot);
|
|
166
|
-
if (parentBlock === null) {
|
|
167
|
-
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
168
|
-
code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
169
|
-
parentBlockRoot: bidParentBlockRoot,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// [REJECT] The bid is for a higher slot than its parent block -- i.e.
|
|
174
|
-
// validate that `bid.slot` is greater than the slot of the block with root
|
|
175
|
-
// `bid.parent_block_root`.
|
|
176
|
-
if (bid.slot <= parentBlock.slot) {
|
|
177
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
178
|
-
code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT,
|
|
179
|
-
parentSlot: parentBlock.slot,
|
|
180
|
-
slot: bid.slot,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const state = await chain.regen
|
|
185
|
-
.getBlockSlotState(parentBlock, bid.slot, {dontTransferCache: true}, RegenCaller.validateApiExecutionPayloadBid)
|
|
186
|
-
.catch(() => {
|
|
187
|
-
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
188
|
-
code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
189
|
-
parentBlockRoot: bidParentBlockRoot,
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
if (!isStatePostGloas(state)) {
|
|
194
|
-
throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// [REJECT] `bid.builder_index` is within bounds -- i.e. `bid.builder_index < len(state.builders)`.
|
|
198
|
-
// `state.getBuilder` returns a lazy SSZ `getReadonly` view that is not bounds-checked eagerly; an
|
|
199
|
-
// out-of-range index only throws (`LeafNode has no right node`) on deferred field access (e.g. inside
|
|
200
|
-
// `isActiveBuilder`), escaping a try/catch around `getBuilder`. Check the length explicitly instead.
|
|
201
|
-
if (bid.builderIndex >= state.getBuildersLength()) {
|
|
202
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
203
|
-
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
204
|
-
builderIndex: bid.builderIndex,
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
|
|
209
|
-
// `is_active_builder(state, bid.builder_index)` returns `True`.
|
|
210
|
-
const builder = state.getBuilder(bid.builderIndex);
|
|
211
|
-
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
212
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
213
|
-
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
214
|
-
builderIndex: bid.builderIndex,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// [REJECT] The builder version is `PAYLOAD_BUILDER_VERSION` -- i.e.
|
|
219
|
-
// `state.builders[bid.builder_index].version == PAYLOAD_BUILDER_VERSION`.
|
|
220
|
-
if (builder.version !== PAYLOAD_BUILDER_VERSION) {
|
|
221
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
222
|
-
code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION,
|
|
223
|
-
builderIndex: bid.builderIndex,
|
|
224
|
-
version: builder.version,
|
|
225
|
-
expectedVersion: PAYLOAD_BUILDER_VERSION,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// [IGNORE] `bid.value` is less or equal than the builder's excess balance --
|
|
230
|
-
// i.e. `can_builder_cover_bid(state, builder_index, amount)` returns `True`.
|
|
231
|
-
if (!state.canBuilderCoverBid(bid.builderIndex, bid.value)) {
|
|
232
|
-
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
233
|
-
code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH,
|
|
234
|
-
bidValue: bid.value,
|
|
235
|
-
builderBalance: builder.balance,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that
|
|
240
|
-
// `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))`.
|
|
241
|
-
const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
|
|
242
|
-
if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
|
|
243
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
244
|
-
code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO,
|
|
245
|
-
builderIndex: bid.builderIndex,
|
|
246
|
-
bidPrevRandao: toHex(bid.prevRandao),
|
|
247
|
-
expectedPrevRandao: toHex(randaoMix),
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
|
|
252
|
-
const signatureSet = createSingleSignatureSetFromComponents(
|
|
253
|
-
builder.pubkey,
|
|
254
|
-
getExecutionPayloadBidSigningRoot(chain.config, bid),
|
|
255
|
-
signedExecutionPayloadBid.signature
|
|
256
|
-
);
|
|
257
|
-
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
|
|
258
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
259
|
-
code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE,
|
|
260
|
-
builderIndex: bid.builderIndex,
|
|
261
|
-
slot: bid.slot,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
86
|
export async function validateGossipExecutionPayloadBid(
|
|
267
87
|
chain: IBeaconChain,
|
|
268
88
|
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
|
|
@@ -31,7 +31,10 @@ export async function validateGossipInclusionList(
|
|
|
31
31
|
signedInclusionList: heze.SignedInclusionList
|
|
32
32
|
): Promise<InclusionListValidationResult> {
|
|
33
33
|
const {slot, validatorIndex, transactions, dependentRoot} = signedInclusionList.message;
|
|
34
|
-
|
|
34
|
+
let totalBytes = 0;
|
|
35
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
36
|
+
totalBytes += transactions.getSize(i);
|
|
37
|
+
}
|
|
35
38
|
|
|
36
39
|
// A pre-heze slot has no inclusion list committee to validate against
|
|
37
40
|
if (!isForkPostHeze(chain.config.getForkName(slot))) {
|
|
@@ -75,7 +78,13 @@ export async function validateGossipInclusionList(
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
// [REJECT] Every transaction in message.transactions is non-empty
|
|
78
|
-
|
|
81
|
+
let emptyTransactionIndex = -1;
|
|
82
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
83
|
+
if (transactions.getSize(i) === 0) {
|
|
84
|
+
emptyTransactionIndex = i;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
79
88
|
if (emptyTransactionIndex !== -1) {
|
|
80
89
|
throw new InclusionListError(GossipAction.REJECT, {
|
|
81
90
|
code: InclusionListErrorCode.EMPTY_TRANSACTION,
|
package/src/db/beacon.ts
CHANGED
|
@@ -130,6 +130,11 @@ export class BeaconDb implements IBeaconDb {
|
|
|
130
130
|
await this.blobSidecars.batchDelete(await this.blobSidecars.keys());
|
|
131
131
|
// New columns are persisted in flat files, so this bucket only contains legacy hot data.
|
|
132
132
|
await this.deleteBucketData(Bucket.allForks_dataColumnSidecars);
|
|
133
|
+
// Compact even if empty to reclaim tombstones left by previous startups.
|
|
134
|
+
await this.db.compactRange(
|
|
135
|
+
encodeKey(Bucket.allForks_dataColumnSidecars, Buffer.alloc(0)),
|
|
136
|
+
encodeKey(Bucket.allForks_dataColumnSidecars + 1, Buffer.alloc(0))
|
|
137
|
+
);
|
|
133
138
|
// Prune all hot blocks
|
|
134
139
|
// TODO: Enable once it's deemed safe
|
|
135
140
|
// await this.block.batchDelete(await this.block.keys());
|
|
@@ -7,7 +7,7 @@ import {VersionedHashes} from "./interface.js";
|
|
|
7
7
|
* Skip unrelated fields without decoding or copying them, including untrusted nested access lists.
|
|
8
8
|
* The EL remains responsible for the other transaction validity checks.
|
|
9
9
|
*/
|
|
10
|
-
export function isValidBlobVersionedHashes(transactions: Uint8Array
|
|
10
|
+
export function isValidBlobVersionedHashes(transactions: Iterable<Uint8Array>, expected: VersionedHashes): boolean {
|
|
11
11
|
let index = 0;
|
|
12
12
|
for (const transaction of transactions) {
|
|
13
13
|
if (transaction.length === 0) return false;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import {SerializedList} from "@chainsafe/ssz";
|
|
2
3
|
import {ChainConfig} from "@lodestar/config";
|
|
3
4
|
import {
|
|
4
5
|
BLOB_TX_TYPE,
|
|
@@ -352,14 +353,14 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
|
|
|
352
353
|
executionPayload.gasUsed = Math.floor(0.5 * INTEROP_GAS_LIMIT);
|
|
353
354
|
executionPayload.timestamp = payloadAttributes.timestamp;
|
|
354
355
|
executionPayload.blockHash = crypto.randomBytes(32);
|
|
355
|
-
|
|
356
|
+
const transactions: Uint8Array[] = [];
|
|
356
357
|
|
|
357
358
|
// Between 0 and 4 transactions for all forks
|
|
358
359
|
const eip1559TxCount = Math.round(4 * Math.random());
|
|
359
360
|
for (let i = 0; i < eip1559TxCount; i++) {
|
|
360
361
|
const tx = crypto.randomBytes(512);
|
|
361
362
|
tx[0] = TX_TYPE_EIP1559;
|
|
362
|
-
|
|
363
|
+
transactions.push(tx);
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
const commitments: deneb.KZGCommitment[] = [];
|
|
@@ -373,7 +374,7 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
|
|
|
373
374
|
const blob = generateRandomBlob();
|
|
374
375
|
const commitment = kzg.blobToKzgCommitment(blob);
|
|
375
376
|
const {proofs: cellProofs} = kzg.computeCellsAndKzgProofs(blob);
|
|
376
|
-
|
|
377
|
+
transactions.push(transactionForKzgCommitment(commitment));
|
|
377
378
|
commitments.push(commitment);
|
|
378
379
|
blobs.push(blob);
|
|
379
380
|
proofs.push(...cellProofs);
|
|
@@ -385,12 +386,13 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
|
|
|
385
386
|
const blob = generateRandomBlob();
|
|
386
387
|
const commitment = kzg.blobToKzgCommitment(blob);
|
|
387
388
|
const proof = kzg.computeBlobKzgProof(blob, commitment);
|
|
388
|
-
|
|
389
|
+
transactions.push(transactionForKzgCommitment(commitment));
|
|
389
390
|
commitments.push(commitment);
|
|
390
391
|
blobs.push(blob);
|
|
391
392
|
proofs.push(proof);
|
|
392
393
|
}
|
|
393
394
|
}
|
|
395
|
+
executionPayload.transactions = SerializedList.fromArray(transactions);
|
|
394
396
|
|
|
395
397
|
if (ForkSeq[fork] >= ForkSeq.capella) {
|
|
396
398
|
(executionPayload as ExecutionPayload<ForkPostCapella>).withdrawals = ssz.capella.Withdrawals.defaultValue();
|