@lodestar/beacon-node 1.37.0-dev.d048e9aee4 → 1.37.0-dev.dcab62468a
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/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +2 -0
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.js +14 -1
- package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts +11 -1
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js +30 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
- package/lib/chain/validation/blobSidecar.d.ts.map +1 -1
- package/lib/chain/validation/blobSidecar.js +29 -22
- package/lib/chain/validation/blobSidecar.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +10 -7
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
- package/lib/chain/validation/dataColumnSidecar.js +33 -26
- package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +2 -1
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/interface.d.ts +3 -3
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +3 -3
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +3 -3
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +2 -0
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/utils/collect.d.ts +3 -3
- package/lib/network/reqresp/utils/collect.d.ts.map +1 -1
- package/lib/network/reqresp/utils/collect.js +7 -3
- package/lib/network/reqresp/utils/collect.js.map +1 -1
- package/lib/network/reqresp/utils/collectSequentialBlocksInRange.d.ts +3 -2
- package/lib/network/reqresp/utils/collectSequentialBlocksInRange.d.ts.map +1 -1
- package/lib/network/reqresp/utils/collectSequentialBlocksInRange.js +5 -3
- package/lib/network/reqresp/utils/collectSequentialBlocksInRange.js.map +1 -1
- package/lib/sync/backfill/backfill.d.ts.map +1 -1
- package/lib/sync/backfill/backfill.js +37 -14
- package/lib/sync/backfill/backfill.js.map +1 -1
- package/lib/sync/backfill/verify.d.ts +4 -4
- package/lib/sync/backfill/verify.d.ts.map +1 -1
- package/lib/sync/backfill/verify.js +6 -6
- package/lib/sync/backfill/verify.js.map +1 -1
- package/lib/sync/utils/downloadByRange.js +1 -1
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/downloadByRoot.js +1 -1
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/package.json +14 -19
- package/src/chain/blocks/importBlock.ts +3 -0
- package/src/chain/blocks/writeBlockInputToDb.ts +13 -1
- package/src/chain/seenCache/seenGossipBlockInput.ts +33 -2
- package/src/chain/validation/blobSidecar.ts +33 -24
- package/src/chain/validation/block.ts +11 -7
- package/src/chain/validation/dataColumnSidecar.ts +43 -33
- package/src/network/gossip/encoding.ts +2 -1
- package/src/network/interface.ts +2 -9
- package/src/network/network.ts +8 -9
- package/src/network/processor/gossipHandlers.ts +2 -0
- package/src/network/reqresp/utils/collect.ts +12 -6
- package/src/network/reqresp/utils/collectSequentialBlocksInRange.ts +10 -6
- package/src/sync/backfill/backfill.ts +40 -16
- package/src/sync/backfill/verify.ts +10 -10
- package/src/sync/utils/downloadByRange.ts +1 -1
- package/src/sync/utils/downloadByRoot.ts +1 -1
- package/lib/network/gossip/snappy_bun.d.ts +0 -3
- package/lib/network/gossip/snappy_bun.d.ts.map +0 -1
- package/lib/network/gossip/snappy_bun.js +0 -3
- package/lib/network/gossip/snappy_bun.js.map +0 -1
- package/src/network/gossip/snappy_bun.ts +0 -2
|
@@ -2,8 +2,8 @@ import {ChainForkConfig} from "@lodestar/config";
|
|
|
2
2
|
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
3
3
|
import {ForkName, ForkPostFulu, ForkPreGloas, isForkPostDeneb, isForkPostFulu, isForkPostGloas} from "@lodestar/params";
|
|
4
4
|
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
5
|
-
import {RootHex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
|
|
6
|
-
import {LodestarError, Logger} from "@lodestar/utils";
|
|
5
|
+
import {BLSSignature, RootHex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
|
|
6
|
+
import {LodestarError, Logger, pruneSetToMax} from "@lodestar/utils";
|
|
7
7
|
import {Metrics} from "../../metrics/metrics.js";
|
|
8
8
|
import {IClock} from "../../util/clock.js";
|
|
9
9
|
import {CustodyConfig} from "../../util/dataColumns.js";
|
|
@@ -83,6 +83,10 @@ export class SeenBlockInput {
|
|
|
83
83
|
private readonly metrics: Metrics | null;
|
|
84
84
|
private readonly logger?: Logger;
|
|
85
85
|
private blockInputs = new Map<RootHex, IBlockInput>();
|
|
86
|
+
// using a Map of slot helps it more convenient to prune
|
|
87
|
+
// there should only 1 block root per slot but we need to always compare against rootHex
|
|
88
|
+
// and the signature to ensure we only skip verification if both match
|
|
89
|
+
private verifiedProposerSignatures = new Map<Slot, Map<RootHex, BLSSignature>>();
|
|
86
90
|
|
|
87
91
|
constructor({config, custodyConfig, clock, chainEvents, signal, metrics, logger}: SeenBlockInputCacheModules) {
|
|
88
92
|
this.config = config;
|
|
@@ -330,6 +334,32 @@ export class SeenBlockInput {
|
|
|
330
334
|
return blockInput;
|
|
331
335
|
}
|
|
332
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Check if a proposer signature has already been verified for this slot and block root.
|
|
339
|
+
*/
|
|
340
|
+
isVerifiedProposerSignature(slot: Slot, blockRootHex: RootHex, signature: BLSSignature): boolean {
|
|
341
|
+
const seenMap = this.verifiedProposerSignatures.get(slot);
|
|
342
|
+
const cachedSignature = seenMap?.get(blockRootHex);
|
|
343
|
+
if (!cachedSignature) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
// Only consider verified if the signature matches
|
|
347
|
+
return Buffer.compare(cachedSignature, signature) === 0;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Mark that the proposer signature for this slot and block root has been verified
|
|
352
|
+
* so that we only verify it once per slot
|
|
353
|
+
*/
|
|
354
|
+
markVerifiedProposerSignature(slot: Slot, blockRootHex: RootHex, signature: BLSSignature): void {
|
|
355
|
+
let seenMap = this.verifiedProposerSignatures.get(slot);
|
|
356
|
+
if (!seenMap) {
|
|
357
|
+
seenMap = new Map<RootHex, BLSSignature>();
|
|
358
|
+
this.verifiedProposerSignatures.set(slot, seenMap);
|
|
359
|
+
}
|
|
360
|
+
seenMap.set(blockRootHex, signature);
|
|
361
|
+
}
|
|
362
|
+
|
|
333
363
|
private buildCommonProps(slot: Slot): {
|
|
334
364
|
daOutOfRange: boolean;
|
|
335
365
|
forkName: ForkName;
|
|
@@ -356,6 +386,7 @@ export class SeenBlockInput {
|
|
|
356
386
|
if (itemsToDelete <= 0) return;
|
|
357
387
|
}
|
|
358
388
|
}
|
|
389
|
+
pruneSetToMax(this.verifiedProposerSignatures, MAX_BLOCK_INPUT_CACHE_SIZE);
|
|
359
390
|
}
|
|
360
391
|
}
|
|
361
392
|
|
|
@@ -135,15 +135,20 @@ export async function validateGossipBlobSidecar(
|
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
// [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey.
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
const signature = blobSidecar.signedBlockHeader.signature;
|
|
139
|
+
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blobSlot, blockHex, signature)) {
|
|
140
|
+
const signatureSet = getBlockHeaderProposerSignatureSetByParentStateSlot(blockState, blobSidecar.signedBlockHeader);
|
|
141
|
+
// Don't batch so verification is not delayed
|
|
142
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet], {verifyOnMainThread: true}))) {
|
|
143
|
+
throw new BlobSidecarGossipError(GossipAction.REJECT, {
|
|
144
|
+
code: BlobSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
|
|
145
|
+
blockRoot: blockHex,
|
|
146
|
+
index: blobSidecar.index,
|
|
147
|
+
slot: blobSlot,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
chain.seenBlockInputCache.markVerifiedProposerSignature(blobSlot, blockHex, signature);
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
// verify if the blob inclusion proof is correct
|
|
@@ -231,22 +236,26 @@ export async function validateBlockBlobSidecars(
|
|
|
231
236
|
}
|
|
232
237
|
|
|
233
238
|
if (chain !== null) {
|
|
234
|
-
const
|
|
235
|
-
const
|
|
239
|
+
const blockRootHex = toRootHex(blockRoot);
|
|
240
|
+
const signature = firstSidecarSignedBlockHeader.signature;
|
|
241
|
+
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockSlot, blockRootHex, signature)) {
|
|
242
|
+
const headState = await chain.getHeadState();
|
|
243
|
+
const signatureSet = getBlockHeaderProposerSignatureSetByHeaderSlot(headState, firstSidecarSignedBlockHeader);
|
|
236
244
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
245
|
+
if (
|
|
246
|
+
!(await chain.bls.verifySignatureSets([signatureSet], {
|
|
247
|
+
verifyOnMainThread: true,
|
|
248
|
+
}))
|
|
249
|
+
) {
|
|
250
|
+
throw new BlobSidecarValidationError({
|
|
251
|
+
code: BlobSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
|
|
252
|
+
blockRoot: blockRootHex,
|
|
253
|
+
slot: blockSlot,
|
|
254
|
+
index: blobSidecars[0].index,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
chain.seenBlockInputCache.markVerifiedProposerSignature(blockSlot, blockRootHex, signature);
|
|
250
259
|
}
|
|
251
260
|
}
|
|
252
261
|
|
|
@@ -153,13 +153,17 @@ export async function validateGossipBlock(
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
156
|
+
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockSlot, blockRoot, signedBlock.signature)) {
|
|
157
|
+
const signatureSet = getBlockProposerSignatureSet(blockState, signedBlock);
|
|
158
|
+
// Don't batch so verification is not delayed
|
|
159
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet], {verifyOnMainThread: true}))) {
|
|
160
|
+
throw new BlockGossipError(GossipAction.REJECT, {
|
|
161
|
+
code: BlockErrorCode.PROPOSAL_SIGNATURE_INVALID,
|
|
162
|
+
blockSlot,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
chain.seenBlockInputCache.markVerifiedProposerSignature(blockSlot, blockRoot, signedBlock.signature);
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
// [REJECT] The block is proposed by the expected proposer_index for the block's slot in the context of the current
|
|
@@ -32,6 +32,7 @@ export async function validateGossipDataColumnSidecar(
|
|
|
32
32
|
metrics: Metrics | null
|
|
33
33
|
): Promise<void> {
|
|
34
34
|
const blockHeader = dataColumnSidecar.signedBlockHeader.message;
|
|
35
|
+
const blockRootHex = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader));
|
|
35
36
|
|
|
36
37
|
// 1) [REJECT] The sidecar is valid as verified by verify_data_column_sidecar
|
|
37
38
|
verifyDataColumnSidecar(chain.config, dataColumnSidecar);
|
|
@@ -131,24 +132,28 @@ export async function validateGossipDataColumnSidecar(
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
// 5) [REJECT] The proposer signature of sidecar.signed_block_header, is valid with respect to the block_header.proposer_index pubkey.
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
135
|
+
const signature = dataColumnSidecar.signedBlockHeader.signature;
|
|
136
|
+
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockHeader.slot, blockRootHex, signature)) {
|
|
137
|
+
const signatureSet = getBlockHeaderProposerSignatureSetByParentStateSlot(
|
|
138
|
+
blockState,
|
|
139
|
+
dataColumnSidecar.signedBlockHeader
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
!(await chain.bls.verifySignatureSets([signatureSet], {
|
|
144
|
+
// verify on main thread so that we only need to verify block proposer signature once per block
|
|
145
|
+
verifyOnMainThread: true,
|
|
146
|
+
}))
|
|
147
|
+
) {
|
|
148
|
+
throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
|
|
149
|
+
code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
|
|
150
|
+
blockRoot: blockRootHex,
|
|
151
|
+
index: dataColumnSidecar.index,
|
|
152
|
+
slot: blockHeader.slot,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
chain.seenBlockInputCache.markVerifiedProposerSignature(blockHeader.slot, blockRootHex, signature);
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
// 9) [REJECT] The current finalized_checkpoint is an ancestor of the sidecar's block
|
|
@@ -326,22 +331,27 @@ export async function validateBlockDataColumnSidecars(
|
|
|
326
331
|
}
|
|
327
332
|
|
|
328
333
|
if (chain !== null) {
|
|
329
|
-
const
|
|
330
|
-
const
|
|
334
|
+
const rootHex = toRootHex(blockRoot);
|
|
335
|
+
const slot = firstSidecarSignedBlockHeader.message.slot;
|
|
336
|
+
const signature = firstSidecarSignedBlockHeader.signature;
|
|
337
|
+
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(slot, rootHex, signature)) {
|
|
338
|
+
const headState = await chain.getHeadState();
|
|
339
|
+
const signatureSet = getBlockHeaderProposerSignatureSetByHeaderSlot(headState, firstSidecarSignedBlockHeader);
|
|
340
|
+
|
|
341
|
+
if (
|
|
342
|
+
!(await chain.bls.verifySignatureSets([signatureSet], {
|
|
343
|
+
verifyOnMainThread: true,
|
|
344
|
+
}))
|
|
345
|
+
) {
|
|
346
|
+
throw new DataColumnSidecarValidationError({
|
|
347
|
+
code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
|
|
348
|
+
blockRoot: rootHex,
|
|
349
|
+
slot: blockSlot,
|
|
350
|
+
index: dataColumnSidecars[0].index,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
331
353
|
|
|
332
|
-
|
|
333
|
-
!(await chain.bls.verifySignatureSets([signatureSet], {
|
|
334
|
-
batchable: true,
|
|
335
|
-
priority: true,
|
|
336
|
-
verifyOnMainThread: false,
|
|
337
|
-
}))
|
|
338
|
-
) {
|
|
339
|
-
throw new DataColumnSidecarValidationError({
|
|
340
|
-
code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
|
|
341
|
-
blockRoot: toRootHex(blockRoot),
|
|
342
|
-
slot: blockSlot,
|
|
343
|
-
index: dataColumnSidecars[0].index,
|
|
344
|
-
});
|
|
354
|
+
chain.seenBlockInputCache.markVerifiedProposerSignature(slot, rootHex, signature);
|
|
345
355
|
}
|
|
346
356
|
}
|
|
347
357
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {Message} from "@libp2p/interface";
|
|
2
|
+
// snappyjs is better for compression for smaller payloads
|
|
3
|
+
import {compress, uncompress} from "snappyjs";
|
|
2
4
|
import xxhashFactory from "xxhash-wasm";
|
|
3
|
-
import {compress, uncompress} from "#snappy";
|
|
4
5
|
import {digest} from "@chainsafe/as-sha256";
|
|
5
6
|
import {RPC} from "@chainsafe/libp2p-gossipsub/message";
|
|
6
7
|
import {DataTransform} from "@chainsafe/libp2p-gossipsub/types";
|
package/src/network/interface.ts
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
Slot,
|
|
28
28
|
SlotRootHex,
|
|
29
29
|
SubnetID,
|
|
30
|
-
WithBytes,
|
|
31
30
|
altair,
|
|
32
31
|
capella,
|
|
33
32
|
deneb,
|
|
@@ -69,14 +68,8 @@ export interface INetwork extends INetworkCorePublic {
|
|
|
69
68
|
reStatusPeers(peers: PeerIdStr[]): Promise<void>;
|
|
70
69
|
searchUnknownSlotRoot(slotRoot: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void;
|
|
71
70
|
// ReqResp
|
|
72
|
-
sendBeaconBlocksByRange(
|
|
73
|
-
|
|
74
|
-
request: phase0.BeaconBlocksByRangeRequest
|
|
75
|
-
): Promise<WithBytes<SignedBeaconBlock>[]>;
|
|
76
|
-
sendBeaconBlocksByRoot(
|
|
77
|
-
peerId: PeerIdStr,
|
|
78
|
-
request: BeaconBlocksByRootRequest
|
|
79
|
-
): Promise<WithBytes<SignedBeaconBlock>[]>;
|
|
71
|
+
sendBeaconBlocksByRange(peerId: PeerIdStr, request: phase0.BeaconBlocksByRangeRequest): Promise<SignedBeaconBlock[]>;
|
|
72
|
+
sendBeaconBlocksByRoot(peerId: PeerIdStr, request: BeaconBlocksByRootRequest): Promise<SignedBeaconBlock[]>;
|
|
80
73
|
sendBlobSidecarsByRange(peerId: PeerIdStr, request: deneb.BlobSidecarsByRangeRequest): Promise<deneb.BlobSidecar[]>;
|
|
81
74
|
sendBlobSidecarsByRoot(peerId: PeerIdStr, request: BlobSidecarsByRootRequest): Promise<deneb.BlobSidecar[]>;
|
|
82
75
|
sendDataColumnSidecarsByRange(
|
package/src/network/network.ts
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
SingleAttestation,
|
|
21
21
|
SlotRootHex,
|
|
22
22
|
SubnetID,
|
|
23
|
-
WithBytes,
|
|
24
23
|
altair,
|
|
25
24
|
capella,
|
|
26
25
|
deneb,
|
|
@@ -513,7 +512,7 @@ export class Network implements INetwork {
|
|
|
513
512
|
async sendBeaconBlocksByRange(
|
|
514
513
|
peerId: PeerIdStr,
|
|
515
514
|
request: phase0.BeaconBlocksByRangeRequest
|
|
516
|
-
): Promise<
|
|
515
|
+
): Promise<SignedBeaconBlock[]> {
|
|
517
516
|
return collectSequentialBlocksInRange(
|
|
518
517
|
this.sendReqRespRequest(
|
|
519
518
|
peerId,
|
|
@@ -526,10 +525,7 @@ export class Network implements INetwork {
|
|
|
526
525
|
);
|
|
527
526
|
}
|
|
528
527
|
|
|
529
|
-
async sendBeaconBlocksByRoot(
|
|
530
|
-
peerId: PeerIdStr,
|
|
531
|
-
request: BeaconBlocksByRootRequest
|
|
532
|
-
): Promise<WithBytes<SignedBeaconBlock>[]> {
|
|
528
|
+
async sendBeaconBlocksByRoot(peerId: PeerIdStr, request: BeaconBlocksByRootRequest): Promise<SignedBeaconBlock[]> {
|
|
533
529
|
return collectMaxResponseTypedWithBytes(
|
|
534
530
|
this.sendReqRespRequest(
|
|
535
531
|
peerId,
|
|
@@ -539,7 +535,8 @@ export class Network implements INetwork {
|
|
|
539
535
|
request
|
|
540
536
|
),
|
|
541
537
|
request.length,
|
|
542
|
-
responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByRoot]
|
|
538
|
+
responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByRoot],
|
|
539
|
+
this.chain.serializedCache
|
|
543
540
|
);
|
|
544
541
|
}
|
|
545
542
|
|
|
@@ -592,7 +589,8 @@ export class Network implements INetwork {
|
|
|
592
589
|
return collectMaxResponseTyped(
|
|
593
590
|
this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRoot, [Version.V1], request),
|
|
594
591
|
request.length,
|
|
595
|
-
responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRoot]
|
|
592
|
+
responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRoot],
|
|
593
|
+
this.chain.serializedCache
|
|
596
594
|
);
|
|
597
595
|
}
|
|
598
596
|
|
|
@@ -614,7 +612,8 @@ export class Network implements INetwork {
|
|
|
614
612
|
return collectMaxResponseTyped(
|
|
615
613
|
this.sendReqRespRequest(peerId, ReqRespMethod.DataColumnSidecarsByRoot, [Version.V1], request),
|
|
616
614
|
request.reduce((total, {columns}) => total + columns.length, 0),
|
|
617
|
-
responseSszTypeByMethod[ReqRespMethod.DataColumnSidecarsByRoot]
|
|
615
|
+
responseSszTypeByMethod[ReqRespMethod.DataColumnSidecarsByRoot],
|
|
616
|
+
this.chain.serializedCache
|
|
618
617
|
);
|
|
619
618
|
}
|
|
620
619
|
|
|
@@ -516,6 +516,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
516
516
|
throw new GossipActionError(GossipAction.REJECT, {code: "PRE_DENEB_BLOCK"});
|
|
517
517
|
}
|
|
518
518
|
const blockInput = await validateBeaconBlob(blobSidecar, topic.subnet, peerIdStr, seenTimestampSec);
|
|
519
|
+
chain.serializedCache.set(blobSidecar, serializedData);
|
|
519
520
|
if (!blockInput.hasBlockAndAllData()) {
|
|
520
521
|
const cutoffTimeMs = getCutoffTimeMs(chain, blobSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
|
|
521
522
|
chain.logger.debug("Received gossip blob, waiting for full data availability", {
|
|
@@ -562,6 +563,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
562
563
|
peerIdStr,
|
|
563
564
|
seenTimestampSec
|
|
564
565
|
);
|
|
566
|
+
chain.serializedCache.set(dataColumnSidecar, serializedData);
|
|
565
567
|
const blockInputMeta = blockInput.getLogMeta();
|
|
566
568
|
const {receivedColumns} = blockInputMeta;
|
|
567
569
|
// it's not helpful to track every single column received
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Type} from "@chainsafe/ssz";
|
|
2
2
|
import {RequestError, RequestErrorCode, ResponseIncoming} from "@lodestar/reqresp";
|
|
3
|
-
import {
|
|
3
|
+
import {SerializedCache} from "../../../util/serializedCache.ts";
|
|
4
4
|
import {ResponseTypeGetter} from "../types.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -32,13 +32,16 @@ export async function collectExactOneTyped<T>(
|
|
|
32
32
|
export async function collectMaxResponseTyped<T>(
|
|
33
33
|
source: AsyncIterable<ResponseIncoming>,
|
|
34
34
|
maxResponses: number,
|
|
35
|
-
typeFn: ResponseTypeGetter<T
|
|
35
|
+
typeFn: ResponseTypeGetter<T>,
|
|
36
|
+
serializedCache?: SerializedCache
|
|
36
37
|
): Promise<T[]> {
|
|
37
38
|
// else: zero or more responses
|
|
38
39
|
const responses: T[] = [];
|
|
39
40
|
for await (const chunk of source) {
|
|
40
41
|
const type = typeFn(chunk.fork, chunk.protocolVersion);
|
|
41
42
|
const response = sszDeserializeResponse(type, chunk.data);
|
|
43
|
+
// optionally cache the serialized response if the cache is available
|
|
44
|
+
serializedCache?.set(response as object, chunk.data);
|
|
42
45
|
responses.push(response);
|
|
43
46
|
|
|
44
47
|
if (maxResponses !== undefined && responses.length >= maxResponses) {
|
|
@@ -58,14 +61,17 @@ export async function collectMaxResponseTyped<T>(
|
|
|
58
61
|
export async function collectMaxResponseTypedWithBytes<T>(
|
|
59
62
|
source: AsyncIterable<ResponseIncoming>,
|
|
60
63
|
maxResponses: number,
|
|
61
|
-
typeFn: ResponseTypeGetter<T
|
|
62
|
-
|
|
64
|
+
typeFn: ResponseTypeGetter<T>,
|
|
65
|
+
serializedCache?: SerializedCache
|
|
66
|
+
): Promise<T[]> {
|
|
63
67
|
// else: zero or more responses
|
|
64
|
-
const responses:
|
|
68
|
+
const responses: T[] = [];
|
|
65
69
|
for await (const chunk of source) {
|
|
66
70
|
const type = typeFn(chunk.fork, chunk.protocolVersion);
|
|
67
71
|
const data = sszDeserializeResponse(type, chunk.data);
|
|
68
|
-
responses.push(
|
|
72
|
+
responses.push(data);
|
|
73
|
+
// optionally cache the serialized response if the cache is available
|
|
74
|
+
serializedCache?.set(data as object, chunk.data);
|
|
69
75
|
|
|
70
76
|
if (maxResponses !== undefined && responses.length >= maxResponses) {
|
|
71
77
|
break;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {ResponseIncoming} from "@lodestar/reqresp";
|
|
2
|
-
import {SignedBeaconBlock,
|
|
2
|
+
import {SignedBeaconBlock, phase0} from "@lodestar/types";
|
|
3
3
|
import {LodestarError} from "@lodestar/utils";
|
|
4
|
+
import {SerializedCache} from "../../../util/serializedCache.ts";
|
|
4
5
|
import {ReqRespMethod, responseSszTypeByMethod} from "../types.js";
|
|
5
6
|
import {sszDeserializeResponse} from "./collect.js";
|
|
6
7
|
|
|
@@ -10,9 +11,10 @@ import {sszDeserializeResponse} from "./collect.js";
|
|
|
10
11
|
*/
|
|
11
12
|
export async function collectSequentialBlocksInRange(
|
|
12
13
|
blockStream: AsyncIterable<ResponseIncoming>,
|
|
13
|
-
{count, startSlot}: Pick<phase0.BeaconBlocksByRangeRequest, "count" | "startSlot"
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
{count, startSlot}: Pick<phase0.BeaconBlocksByRangeRequest, "count" | "startSlot">,
|
|
15
|
+
serializedCache?: SerializedCache
|
|
16
|
+
): Promise<SignedBeaconBlock[]> {
|
|
17
|
+
const blocks: SignedBeaconBlock[] = [];
|
|
16
18
|
|
|
17
19
|
for await (const chunk of blockStream) {
|
|
18
20
|
const blockType = responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByRange](chunk.fork, chunk.protocolVersion);
|
|
@@ -30,11 +32,13 @@ export async function collectSequentialBlocksInRange(
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const prevBlock = blocks.at(-1);
|
|
33
|
-
if (prevBlock && prevBlock.
|
|
35
|
+
if (prevBlock && prevBlock.message.slot >= blockSlot) {
|
|
34
36
|
throw new BlocksByRangeError({code: BlocksByRangeErrorCode.BAD_SEQUENCE});
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
blocks.push(
|
|
39
|
+
blocks.push(block);
|
|
40
|
+
// optionally cache the serialized response if the cache is available
|
|
41
|
+
serializedCache?.set(block, chunk.data);
|
|
38
42
|
if (blocks.length >= count) {
|
|
39
43
|
break; // Done, collected all blocks
|
|
40
44
|
}
|
|
@@ -749,27 +749,33 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter}
|
|
|
749
749
|
const anchorBlock = res[0];
|
|
750
750
|
|
|
751
751
|
// GENESIS_SLOT doesn't has valid signature
|
|
752
|
-
if (anchorBlock.
|
|
752
|
+
if (anchorBlock.message.slot === GENESIS_SLOT) return;
|
|
753
753
|
await verifyBlockProposerSignature(this.chain.bls, this.chain.getHeadState(), [anchorBlock]);
|
|
754
754
|
|
|
755
755
|
// We can write to the disk if this is ahead of prevFinalizedCheckpointBlock otherwise
|
|
756
756
|
// we will need to go make checks on the top of sync loop before writing as it might
|
|
757
757
|
// override prevFinalizedCheckpointBlock
|
|
758
|
-
if (this.prevFinalizedCheckpointBlock.slot < anchorBlock.
|
|
759
|
-
|
|
758
|
+
if (this.prevFinalizedCheckpointBlock.slot < anchorBlock.message.slot) {
|
|
759
|
+
const serialized = this.chain.serializedCache.get(anchorBlock);
|
|
760
|
+
if (serialized) {
|
|
761
|
+
await this.db.blockArchive.putBinary(anchorBlock.message.slot, serialized);
|
|
762
|
+
} else {
|
|
763
|
+
await this.db.blockArchive.put(anchorBlock.message.slot, anchorBlock);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
760
766
|
|
|
761
767
|
this.syncAnchor = {
|
|
762
|
-
anchorBlock: anchorBlock
|
|
768
|
+
anchorBlock: anchorBlock,
|
|
763
769
|
anchorBlockRoot,
|
|
764
|
-
anchorSlot: anchorBlock.
|
|
765
|
-
lastBackSyncedBlock: {root: anchorBlockRoot, slot: anchorBlock.
|
|
770
|
+
anchorSlot: anchorBlock.message.slot,
|
|
771
|
+
lastBackSyncedBlock: {root: anchorBlockRoot, slot: anchorBlock.message.slot, block: anchorBlock},
|
|
766
772
|
};
|
|
767
773
|
|
|
768
774
|
this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.blockbyroot});
|
|
769
775
|
|
|
770
776
|
this.logger.verbose("Fetched new anchorBlock", {
|
|
771
777
|
root: toRootHex(anchorBlockRoot),
|
|
772
|
-
slot: anchorBlock.
|
|
778
|
+
slot: anchorBlock.message.slot,
|
|
773
779
|
});
|
|
774
780
|
|
|
775
781
|
return;
|
|
@@ -825,15 +831,33 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter}
|
|
|
825
831
|
nextAnchor.slot > this.prevFinalizedCheckpointBlock.slot
|
|
826
832
|
? verifiedBlocks
|
|
827
833
|
: verifiedBlocks.slice(0, verifiedBlocks.length - 1);
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
834
|
+
|
|
835
|
+
const binaryPuts = [];
|
|
836
|
+
const nonBinaryPuts = [];
|
|
837
|
+
|
|
838
|
+
for (const block of blocksToPut) {
|
|
839
|
+
const serialized = this.chain.serializedCache.get(block);
|
|
840
|
+
const item = {
|
|
841
|
+
key: block.message.slot,
|
|
842
|
+
slot: block.message.slot,
|
|
843
|
+
blockRoot: this.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message),
|
|
844
|
+
parentRoot: block.message.parentRoot,
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
if (serialized) {
|
|
848
|
+
binaryPuts.push({...item, value: serialized});
|
|
849
|
+
} else {
|
|
850
|
+
nonBinaryPuts.push({...item, value: block});
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (binaryPuts.length > 0) {
|
|
855
|
+
await this.db.blockArchive.batchPutBinary(binaryPuts);
|
|
856
|
+
}
|
|
857
|
+
if (nonBinaryPuts.length > 0) {
|
|
858
|
+
await this.db.blockArchive.batchPut(nonBinaryPuts);
|
|
859
|
+
}
|
|
860
|
+
|
|
837
861
|
this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.rangesync}, verifiedBlocks.length);
|
|
838
862
|
}
|
|
839
863
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {BeaconConfig} from "@lodestar/config";
|
|
2
2
|
import {GENESIS_SLOT} from "@lodestar/params";
|
|
3
3
|
import {CachedBeaconStateAllForks, ISignatureSet, getBlockProposerSignatureSet} from "@lodestar/state-transition";
|
|
4
|
-
import {Root, SignedBeaconBlock, Slot,
|
|
4
|
+
import {Root, SignedBeaconBlock, Slot, ssz} from "@lodestar/types";
|
|
5
5
|
import {IBlsVerifier} from "../../chain/bls/index.js";
|
|
6
6
|
import {BackfillSyncError, BackfillSyncErrorCode} from "./errors.js";
|
|
7
7
|
|
|
@@ -14,19 +14,19 @@ export type BackfillBlock = BackfillBlockHeader & {block: SignedBeaconBlock};
|
|
|
14
14
|
|
|
15
15
|
export function verifyBlockSequence(
|
|
16
16
|
config: BeaconConfig,
|
|
17
|
-
blocks:
|
|
17
|
+
blocks: SignedBeaconBlock[],
|
|
18
18
|
anchorRoot: Root
|
|
19
19
|
): {
|
|
20
20
|
nextAnchor: BackfillBlock | null;
|
|
21
|
-
verifiedBlocks:
|
|
21
|
+
verifiedBlocks: SignedBeaconBlock[];
|
|
22
22
|
error?: BackfillSyncErrorCode.NOT_LINEAR;
|
|
23
23
|
} {
|
|
24
24
|
let nextRoot: Root = anchorRoot;
|
|
25
25
|
let nextAnchor: BackfillBlock | null = null;
|
|
26
26
|
|
|
27
|
-
const verifiedBlocks:
|
|
27
|
+
const verifiedBlocks: SignedBeaconBlock[] = [];
|
|
28
28
|
for (const block of blocks.reverse()) {
|
|
29
|
-
const blockRoot = config.getForkTypes(block.
|
|
29
|
+
const blockRoot = config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message);
|
|
30
30
|
if (!ssz.Root.equals(blockRoot, nextRoot)) {
|
|
31
31
|
if (ssz.Root.equals(nextRoot, anchorRoot)) {
|
|
32
32
|
throw new BackfillSyncError({code: BackfillSyncErrorCode.NOT_ANCHORED});
|
|
@@ -34,8 +34,8 @@ export function verifyBlockSequence(
|
|
|
34
34
|
return {nextAnchor, verifiedBlocks, error: BackfillSyncErrorCode.NOT_LINEAR};
|
|
35
35
|
}
|
|
36
36
|
verifiedBlocks.push(block);
|
|
37
|
-
nextAnchor = {block: block
|
|
38
|
-
nextRoot = block.
|
|
37
|
+
nextAnchor = {block: block, slot: block.message.slot, root: nextRoot};
|
|
38
|
+
nextRoot = block.message.parentRoot;
|
|
39
39
|
}
|
|
40
40
|
return {nextAnchor, verifiedBlocks};
|
|
41
41
|
}
|
|
@@ -43,12 +43,12 @@ export function verifyBlockSequence(
|
|
|
43
43
|
export async function verifyBlockProposerSignature(
|
|
44
44
|
bls: IBlsVerifier,
|
|
45
45
|
state: CachedBeaconStateAllForks,
|
|
46
|
-
blocks:
|
|
46
|
+
blocks: SignedBeaconBlock[]
|
|
47
47
|
): Promise<void> {
|
|
48
|
-
if (blocks.length === 1 && blocks[0].
|
|
48
|
+
if (blocks.length === 1 && blocks[0].message.slot === GENESIS_SLOT) return;
|
|
49
49
|
const signatures = blocks.reduce((sigs: ISignatureSet[], block) => {
|
|
50
50
|
// genesis block doesn't have valid signature
|
|
51
|
-
if (block.
|
|
51
|
+
if (block.message.slot !== GENESIS_SLOT) sigs.push(getBlockProposerSignatureSet(state, block));
|
|
52
52
|
return sigs;
|
|
53
53
|
}, []);
|
|
54
54
|
|
|
@@ -254,7 +254,7 @@ export async function requestByRange({
|
|
|
254
254
|
if (blocksRequest) {
|
|
255
255
|
requests.push(
|
|
256
256
|
network.sendBeaconBlocksByRange(peerIdStr, blocksRequest).then((blockResponse) => {
|
|
257
|
-
blocks = blockResponse
|
|
257
|
+
blocks = blockResponse;
|
|
258
258
|
})
|
|
259
259
|
);
|
|
260
260
|
}
|
|
@@ -314,7 +314,7 @@ export async function fetchAndValidateBlock({
|
|
|
314
314
|
blockRoot,
|
|
315
315
|
}: Omit<FetchByRootAndValidateBlockProps, "chain">): Promise<SignedBeaconBlock> {
|
|
316
316
|
const response = await network.sendBeaconBlocksByRoot(peerIdStr, [blockRoot]);
|
|
317
|
-
const block = response.at(0)
|
|
317
|
+
const block = response.at(0);
|
|
318
318
|
if (!block) {
|
|
319
319
|
throw new DownloadByRootError({
|
|
320
320
|
code: DownloadByRootErrorCode.MISSING_BLOCK_RESPONSE,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"snappy_bun.d.ts","sourceRoot":"","sources":["../../../src/network/gossip/snappy_bun.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AACrC,eAAO,MAAO,QAAQ,0BAAE,UAAU,0BAAU,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"snappy_bun.js","sourceRoot":"","sources":["../../../src/network/gossip/snappy_bun.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AACrC,MAAM,CAAC,MAAM,EAAC,QAAQ,EAAE,UAAU,EAAC,GAAG,MAAM,CAAC"}
|