@lodestar/beacon-node 1.49.0-dev.9d1b583d24 → 1.49.0-dev.a1f8bb3877
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/beacon/blocks/index.js +2 -2
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +9 -7
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +14 -3
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/blocks/index.js +2 -2
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/emitter.d.ts +10 -0
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +4 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/attesterSlashingError.d.ts +4 -1
- package/lib/chain/errors/attesterSlashingError.d.ts.map +1 -1
- package/lib/chain/errors/attesterSlashingError.js +1 -0
- package/lib/chain/errors/attesterSlashingError.js.map +1 -1
- package/lib/chain/errors/proposerSlashingError.d.ts +4 -1
- package/lib/chain/errors/proposerSlashingError.d.ts.map +1 -1
- package/lib/chain/errors/proposerSlashingError.js +1 -0
- package/lib/chain/errors/proposerSlashingError.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -6
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +2 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +3 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
- package/lib/chain/validation/attesterSlashing.js +1 -2
- package/lib/chain/validation/attesterSlashing.js.map +1 -1
- package/lib/chain/validation/proposerSlashing.js +1 -2
- package/lib/chain/validation/proposerSlashing.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +2 -2
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/repositories/blockArchive.d.ts +5 -0
- package/lib/db/repositories/blockArchive.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.js +36 -1
- package/lib/db/repositories/blockArchive.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +15 -4
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +28 -1
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts +1 -2
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +5 -10
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +16 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +9 -6
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +23 -8
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +16 -5
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +2 -0
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +88 -7
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +6 -2
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +26 -9
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +19 -5
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/sync/types.d.ts +22 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +24 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +1 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +48 -12
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/sszBytes.d.ts +5 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +13 -1
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/blocks/index.ts +2 -2
- package/src/api/impl/validator/index.ts +9 -7
- package/src/chain/archiveStore/utils/pruneHistory.ts +14 -4
- package/src/chain/blocks/index.ts +2 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -1
- package/src/chain/emitter.ts +7 -2
- package/src/chain/errors/attesterSlashingError.ts +3 -1
- package/src/chain/errors/proposerSlashingError.ts +3 -1
- package/src/chain/produceBlock/produceBlockBody.ts +17 -6
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +3 -2
- package/src/chain/validation/attesterSlashing.ts +1 -2
- package/src/chain/validation/proposerSlashing.ts +1 -2
- package/src/chain/validatorMonitor.ts +12 -2
- package/src/db/repositories/blockArchive.ts +38 -1
- package/src/metrics/metrics/lodestar.ts +30 -3
- package/src/network/gossip/encoding.ts +5 -8
- package/src/network/gossip/gossipsub.ts +19 -2
- package/src/network/gossip/topic.ts +25 -7
- package/src/network/processor/gossipHandlers.ts +18 -5
- package/src/network/processor/gossipValidatorFn.ts +111 -7
- package/src/network/processor/index.ts +37 -9
- package/src/network/reqresp/protocols.ts +31 -6
- package/src/sync/types.ts +26 -3
- package/src/sync/unknownBlock.ts +62 -12
- package/src/util/sszBytes.ts +18 -0
|
@@ -1,24 +1,122 @@
|
|
|
1
1
|
import {TopicValidatorResult} from "@libp2p/gossipsub";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {Logger} from "@lodestar/utils";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AttestationError,
|
|
6
|
+
AttestationErrorCode,
|
|
7
|
+
AttesterSlashingErrorCode,
|
|
8
|
+
BlobSidecarErrorCode,
|
|
9
|
+
BlockErrorCode,
|
|
10
|
+
BlsToExecutionChangeErrorCode,
|
|
11
|
+
DataColumnSidecarErrorCode,
|
|
12
|
+
ExecutionPayloadBidErrorCode,
|
|
13
|
+
ExecutionPayloadEnvelopeErrorCode,
|
|
14
|
+
GossipAction,
|
|
15
|
+
GossipActionError,
|
|
16
|
+
PayloadAttestationErrorCode,
|
|
17
|
+
ProposerPreferencesErrorCode,
|
|
18
|
+
ProposerSlashingErrorCode,
|
|
19
|
+
SyncCommitteeErrorCode,
|
|
20
|
+
VoluntaryExitErrorCode,
|
|
21
|
+
} from "../../chain/errors/index.js";
|
|
5
22
|
import {Metrics} from "../../metrics/index.js";
|
|
23
|
+
import {INetworkCore} from "../core/index.js";
|
|
6
24
|
import {
|
|
7
25
|
BatchGossipHandlerFn,
|
|
8
26
|
GossipHandlerFn,
|
|
9
27
|
GossipHandlers,
|
|
10
28
|
GossipMessageInfo,
|
|
29
|
+
GossipType,
|
|
11
30
|
GossipValidatorBatchFn,
|
|
12
31
|
GossipValidatorFn,
|
|
13
32
|
} from "../gossip/interface.js";
|
|
33
|
+
import {PeerAction} from "../peers/index.js";
|
|
14
34
|
import {prettyPrintPeerIdStr} from "../util.js";
|
|
15
35
|
|
|
16
36
|
export type ValidatorFnModules = {
|
|
17
37
|
config: ChainForkConfig;
|
|
18
38
|
logger: Logger;
|
|
19
39
|
metrics: Metrics | null;
|
|
40
|
+
core: INetworkCore;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type RejectPeerActionRule = {default: PeerAction; byCode?: Record<string, PeerAction>};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* PeerAction mapping based on the topic and specific codes.
|
|
47
|
+
* Invalid signatures are Fatal on every topic.
|
|
48
|
+
*/
|
|
49
|
+
const gossipRejectPeerAction: Record<GossipType, RejectPeerActionRule> = {
|
|
50
|
+
[GossipType.beacon_block]: {
|
|
51
|
+
default: PeerAction.LowToleranceError,
|
|
52
|
+
byCode: {[BlockErrorCode.PROPOSAL_SIGNATURE_INVALID]: PeerAction.Fatal},
|
|
53
|
+
},
|
|
54
|
+
[GossipType.blob_sidecar]: {
|
|
55
|
+
default: PeerAction.LowToleranceError,
|
|
56
|
+
byCode: {[BlobSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID]: PeerAction.Fatal},
|
|
57
|
+
},
|
|
58
|
+
[GossipType.data_column_sidecar]: {
|
|
59
|
+
default: PeerAction.LowToleranceError,
|
|
60
|
+
byCode: {[DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID]: PeerAction.Fatal},
|
|
61
|
+
},
|
|
62
|
+
[GossipType.execution_payload]: {
|
|
63
|
+
default: PeerAction.LowToleranceError,
|
|
64
|
+
byCode: {[ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
65
|
+
},
|
|
66
|
+
[GossipType.beacon_aggregate_and_proof]: {
|
|
67
|
+
default: PeerAction.MidToleranceError,
|
|
68
|
+
byCode: {[AttestationErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
69
|
+
},
|
|
70
|
+
[GossipType.beacon_attestation]: {
|
|
71
|
+
default: PeerAction.MidToleranceError,
|
|
72
|
+
byCode: {[AttestationErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
73
|
+
},
|
|
74
|
+
[GossipType.sync_committee_contribution_and_proof]: {
|
|
75
|
+
default: PeerAction.MidToleranceError,
|
|
76
|
+
byCode: {[SyncCommitteeErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
77
|
+
},
|
|
78
|
+
[GossipType.sync_committee]: {
|
|
79
|
+
default: PeerAction.MidToleranceError,
|
|
80
|
+
byCode: {[SyncCommitteeErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
81
|
+
},
|
|
82
|
+
[GossipType.payload_attestation_message]: {
|
|
83
|
+
default: PeerAction.MidToleranceError,
|
|
84
|
+
byCode: {[PayloadAttestationErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
85
|
+
},
|
|
86
|
+
[GossipType.execution_payload_bid]: {
|
|
87
|
+
default: PeerAction.HighToleranceError,
|
|
88
|
+
byCode: {[ExecutionPayloadBidErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
89
|
+
},
|
|
90
|
+
[GossipType.proposer_preferences]: {
|
|
91
|
+
default: PeerAction.HighToleranceError,
|
|
92
|
+
byCode: {[ProposerPreferencesErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
93
|
+
},
|
|
94
|
+
[GossipType.voluntary_exit]: {
|
|
95
|
+
default: PeerAction.HighToleranceError,
|
|
96
|
+
byCode: {[VoluntaryExitErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
97
|
+
},
|
|
98
|
+
[GossipType.bls_to_execution_change]: {
|
|
99
|
+
default: PeerAction.HighToleranceError,
|
|
100
|
+
byCode: {[BlsToExecutionChangeErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
101
|
+
},
|
|
102
|
+
[GossipType.proposer_slashing]: {
|
|
103
|
+
default: PeerAction.HighToleranceError,
|
|
104
|
+
byCode: {[ProposerSlashingErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
105
|
+
},
|
|
106
|
+
[GossipType.attester_slashing]: {
|
|
107
|
+
default: PeerAction.HighToleranceError,
|
|
108
|
+
byCode: {[AttesterSlashingErrorCode.INVALID_SIGNATURE]: PeerAction.Fatal},
|
|
109
|
+
},
|
|
110
|
+
// light client validators only throw IGNORE
|
|
111
|
+
[GossipType.light_client_finality_update]: {default: PeerAction.HighToleranceError},
|
|
112
|
+
[GossipType.light_client_optimistic_update]: {default: PeerAction.HighToleranceError},
|
|
20
113
|
};
|
|
21
114
|
|
|
115
|
+
function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
116
|
+
const rule = gossipRejectPeerAction[type];
|
|
117
|
+
return rule.byCode?.[code] ?? rule.default;
|
|
118
|
+
}
|
|
119
|
+
|
|
22
120
|
/**
|
|
23
121
|
* Similar to getGossipValidatorFn but return a function to accept a batch of beacon_attestation messages
|
|
24
122
|
* with the same attestation data
|
|
@@ -27,7 +125,7 @@ export function getGossipValidatorBatchFn(
|
|
|
27
125
|
gossipHandlers: GossipHandlers,
|
|
28
126
|
modules: ValidatorFnModules
|
|
29
127
|
): GossipValidatorBatchFn {
|
|
30
|
-
const {logger, metrics} = modules;
|
|
128
|
+
const {logger, metrics, core} = modules;
|
|
31
129
|
|
|
32
130
|
return async function gossipValidatorBatchFn(messageInfos: GossipMessageInfo[]) {
|
|
33
131
|
// all messageInfos have same topic type
|
|
@@ -69,16 +167,19 @@ export function getGossipValidatorBatchFn(
|
|
|
69
167
|
// only beacon_attestation topic is validated in batch
|
|
70
168
|
metrics?.networkProcessor.gossipAttestationIgnoreByReason.inc({reason: e.type.code});
|
|
71
169
|
return TopicValidatorResult.Ignore;
|
|
72
|
-
case GossipAction.REJECT:
|
|
170
|
+
case GossipAction.REJECT: {
|
|
73
171
|
metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
|
|
74
172
|
// only beacon_attestation topic is validated in batch
|
|
75
173
|
metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
|
|
174
|
+
const peerAction = rejectPeerAction(type, e.type.code);
|
|
175
|
+
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
76
176
|
logger.debug(
|
|
77
177
|
`Gossip validation ${type} rejected`,
|
|
78
|
-
{
|
|
178
|
+
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|
|
79
179
|
e
|
|
80
180
|
);
|
|
81
181
|
return TopicValidatorResult.Reject;
|
|
182
|
+
}
|
|
82
183
|
}
|
|
83
184
|
});
|
|
84
185
|
} catch (e) {
|
|
@@ -108,7 +209,7 @@ export function getGossipValidatorBatchFn(
|
|
|
108
209
|
* @see getGossipHandlers for reasoning on why GossipHandlerFn are used for gossip validation.
|
|
109
210
|
*/
|
|
110
211
|
export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: ValidatorFnModules): GossipValidatorFn {
|
|
111
|
-
const {logger, metrics} = modules;
|
|
212
|
+
const {logger, metrics, core} = modules;
|
|
112
213
|
|
|
113
214
|
return async function gossipValidatorFn({
|
|
114
215
|
topic,
|
|
@@ -155,14 +256,17 @@ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: Va
|
|
|
155
256
|
metrics?.networkProcessor.gossipValidationIgnore.inc({topic: type});
|
|
156
257
|
return TopicValidatorResult.Ignore;
|
|
157
258
|
|
|
158
|
-
case GossipAction.REJECT:
|
|
259
|
+
case GossipAction.REJECT: {
|
|
159
260
|
metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
|
|
261
|
+
const peerAction = rejectPeerAction(type, e.type.code);
|
|
262
|
+
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
160
263
|
logger.debug(
|
|
161
264
|
`Gossip validation ${type} rejected`,
|
|
162
|
-
{
|
|
265
|
+
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|
|
163
266
|
e
|
|
164
267
|
);
|
|
165
268
|
return TopicValidatorResult.Reject;
|
|
269
|
+
}
|
|
166
270
|
}
|
|
167
271
|
}
|
|
168
272
|
};
|
|
@@ -86,8 +86,13 @@ export const MAX_AWAITING_MESSAGES_PER_ROOT: Partial<Record<GossipType, number>>
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Track the forwarded peers we already emit to UnknownBlockInput sync.
|
|
89
|
+
* envelopeSlotEmitted: whether the unknownEnvelopeBlockRootSlot event was already emitted for this root.
|
|
89
90
|
*/
|
|
90
|
-
type SearchedRootEntry = {
|
|
91
|
+
type SearchedRootEntry = {
|
|
92
|
+
blockPeerIds?: Set<PeerIdStr>;
|
|
93
|
+
envelopePeerIds?: Set<PeerIdStr>;
|
|
94
|
+
envelopeSlotEmitted?: boolean;
|
|
95
|
+
};
|
|
91
96
|
|
|
92
97
|
/**
|
|
93
98
|
* This is respective to gossipsub seenTTL (which is 550 * 0.7 = 385s), also it's respective
|
|
@@ -347,12 +352,17 @@ export class NetworkProcessor {
|
|
|
347
352
|
}
|
|
348
353
|
|
|
349
354
|
/**
|
|
350
|
-
* Search envelope via `ChainEvent.unknownEnvelopeBlockRoot` event
|
|
355
|
+
* Search envelope via `ChainEvent.unknownEnvelopeBlockRoot` / `ChainEvent.unknownEnvelopeBlockRootSlot` event
|
|
351
356
|
* Slot is the message slot, which is not necessarily the same as the envelope's slot, but it can be used for a good prune strategy.
|
|
352
357
|
* In the rare case, if 2 messages on 2 slots search for the same root (for example beacon_attestation) we may emit the same root twice but BlockInputSync should handle it well.
|
|
353
358
|
* Same peer-forwarding + dedup behavior as searchUnknownBlock.
|
|
354
359
|
*/
|
|
355
|
-
searchUnknownEnvelope(
|
|
360
|
+
searchUnknownEnvelope(
|
|
361
|
+
{slot, root}: SlotRootHex,
|
|
362
|
+
source: BlockInputSource,
|
|
363
|
+
peer?: PeerIdStr,
|
|
364
|
+
slotIsPayloadSlot = false
|
|
365
|
+
): void {
|
|
356
366
|
if (this.chain.seenPayloadEnvelope(root)) {
|
|
357
367
|
return;
|
|
358
368
|
}
|
|
@@ -369,8 +379,17 @@ export class NetworkProcessor {
|
|
|
369
379
|
forwardedPeers.add(peer);
|
|
370
380
|
shouldEmit = true;
|
|
371
381
|
}
|
|
382
|
+
// the payload-slot seed must reach the sync once, even if a slot-less trigger started the search first
|
|
383
|
+
if (slotIsPayloadSlot && !entry.envelopeSlotEmitted) {
|
|
384
|
+
shouldEmit = true;
|
|
385
|
+
}
|
|
372
386
|
if (shouldEmit) {
|
|
373
|
-
|
|
387
|
+
if (slotIsPayloadSlot) {
|
|
388
|
+
entry.envelopeSlotEmitted = true;
|
|
389
|
+
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRootSlot, {rootHex: root, slot, peer, source});
|
|
390
|
+
} else {
|
|
391
|
+
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, peer, source});
|
|
392
|
+
}
|
|
374
393
|
}
|
|
375
394
|
}
|
|
376
395
|
|
|
@@ -494,8 +513,9 @@ export class NetworkProcessor {
|
|
|
494
513
|
if (root == null) break;
|
|
495
514
|
const payloadPresent = getPayloadPresentFromPayloadAttestationMessageSerialized(message.msg.data);
|
|
496
515
|
if (payloadPresent && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
|
|
497
|
-
// payload attestation votes that the payload is available but it is not yet known
|
|
498
|
-
|
|
516
|
+
// payload attestation votes that the payload is available but it is not yet known.
|
|
517
|
+
// the PTC vote's slot is the payload's slot
|
|
518
|
+
this.searchUnknownRoot({slot, root}, false, true, peerId, true);
|
|
499
519
|
// do not await the envelope, payload attestation processing only requires that the block is known
|
|
500
520
|
// also do not reset preprocessResult, we may already await for the block
|
|
501
521
|
}
|
|
@@ -504,7 +524,8 @@ export class NetworkProcessor {
|
|
|
504
524
|
case GossipType.data_column_sidecar: {
|
|
505
525
|
if (root == null) break;
|
|
506
526
|
if (!this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
|
|
507
|
-
|
|
527
|
+
// the sidecar's slot is the block's (and so the payload's) slot
|
|
528
|
+
this.searchUnknownRoot({slot, root}, false, true, peerId, true);
|
|
508
529
|
// do not await the envelope, we can do gossip validation
|
|
509
530
|
// also do not reset preprocessResult, we may already await for the block
|
|
510
531
|
}
|
|
@@ -641,16 +662,23 @@ export class NetworkProcessor {
|
|
|
641
662
|
return true;
|
|
642
663
|
}
|
|
643
664
|
|
|
665
|
+
/**
|
|
666
|
+
* Search block/envelope given a SlotRootHex
|
|
667
|
+
* envelopeSlotIsPayloadSlot: the message slot is the payload's slot or not
|
|
668
|
+
*/
|
|
644
669
|
private searchUnknownRoot(
|
|
645
670
|
slotRoot: SlotRootHex,
|
|
646
671
|
searchBlock: boolean,
|
|
647
672
|
searchEnvelope: boolean,
|
|
648
|
-
peerId: PeerIdStr
|
|
673
|
+
peerId: PeerIdStr,
|
|
674
|
+
envelopeSlotIsPayloadSlot = false
|
|
649
675
|
): void {
|
|
650
676
|
if (!searchBlock && !searchEnvelope) return;
|
|
651
677
|
if (this.tooManySearchedRoots(slotRoot.slot, slotRoot.root)) return;
|
|
652
678
|
if (searchBlock) this.searchUnknownBlock(slotRoot, BlockInputSource.network_processor, peerId);
|
|
653
|
-
if (searchEnvelope)
|
|
679
|
+
if (searchEnvelope) {
|
|
680
|
+
this.searchUnknownEnvelope(slotRoot, BlockInputSource.network_processor, peerId, envelopeSlotIsPayloadSlot);
|
|
681
|
+
}
|
|
654
682
|
}
|
|
655
683
|
|
|
656
684
|
private pushPendingGossipsubMessageToQueue(message: PendingGossipsubMessage): void {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {BeaconConfig} from "@lodestar/config";
|
|
2
|
-
import {ForkName} from "@lodestar/params";
|
|
2
|
+
import {ForkName, isForkPostGloas} from "@lodestar/params";
|
|
3
3
|
import {ContextBytesFactory, ContextBytesType, Encoding, TypeSizes} from "@lodestar/reqresp";
|
|
4
|
+
import {computeMaxGloasDataColumnSidecarSize} from "../../util/sszBytes.js";
|
|
4
5
|
import {rateLimitQuotas} from "./rateLimit.js";
|
|
5
6
|
import {ProtocolNoHandler, ReqRespMethod, Version, requestSszTypeByMethod, responseSszTypeByMethod} from "./types.js";
|
|
6
7
|
|
|
@@ -151,18 +152,42 @@ function toProtocol(protocol: ProtocolSummary) {
|
|
|
151
152
|
encoding: Encoding.SSZ_SNAPPY,
|
|
152
153
|
contextBytes: toContextBytes(protocol.contextBytesType, config),
|
|
153
154
|
inboundRateLimits: rateLimitQuotas(fork, config)[protocol.method],
|
|
154
|
-
requestSizes: requestType === null ? null : clampTypeSizes(requestType, config),
|
|
155
|
-
responseSizes: (fork) =>
|
|
155
|
+
requestSizes: requestType === null ? null : clampTypeSizes(requestType, config.MAX_PAYLOAD_SIZE),
|
|
156
|
+
responseSizes: (fork) =>
|
|
157
|
+
clampResponseTypeSizes(
|
|
158
|
+
responseSszTypeByMethod[protocol.method](fork, protocol.version),
|
|
159
|
+
protocol.method,
|
|
160
|
+
fork,
|
|
161
|
+
config
|
|
162
|
+
),
|
|
156
163
|
};
|
|
157
164
|
};
|
|
158
165
|
}
|
|
159
166
|
|
|
167
|
+
function clampResponseTypeSizes(
|
|
168
|
+
type: TypeSizes,
|
|
169
|
+
method: ReqRespMethod,
|
|
170
|
+
fork: ForkName,
|
|
171
|
+
config: BeaconConfig
|
|
172
|
+
): TypeSizes {
|
|
173
|
+
let maxPayloadSize = config.MAX_PAYLOAD_SIZE;
|
|
174
|
+
if (isForkPostGloas(fork)) {
|
|
175
|
+
switch (method) {
|
|
176
|
+
case ReqRespMethod.DataColumnSidecarsByRange:
|
|
177
|
+
case ReqRespMethod.DataColumnSidecarsByRoot:
|
|
178
|
+
maxPayloadSize = Math.min(maxPayloadSize, computeMaxGloasDataColumnSidecarSize(config));
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return clampTypeSizes(type, maxPayloadSize);
|
|
183
|
+
}
|
|
184
|
+
|
|
160
185
|
/**
|
|
161
|
-
* Length-prefix must be within the SSZ type bounds or
|
|
186
|
+
* Length-prefix must be within the SSZ type bounds or the configured payload limit, whichever is smaller.
|
|
162
187
|
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/phase0/p2p-interface.md#encoding-strategies
|
|
163
188
|
*/
|
|
164
|
-
function clampTypeSizes(type: TypeSizes,
|
|
165
|
-
return {minSize: type.minSize, maxSize: Math.min(type.maxSize,
|
|
189
|
+
function clampTypeSizes(type: TypeSizes, maxPayloadSize: number): TypeSizes {
|
|
190
|
+
return {minSize: type.minSize, maxSize: Math.min(type.maxSize, maxPayloadSize)};
|
|
166
191
|
}
|
|
167
192
|
|
|
168
193
|
function toContextBytes(type: ContextBytesType, config: BeaconConfig): ContextBytesFactory {
|
package/src/sync/types.ts
CHANGED
|
@@ -23,12 +23,36 @@ export enum PendingBlockType {
|
|
|
23
23
|
* Payload analog of UNKNOWN_BLOCK_ROOT: we have a beacon block root but not its execution payload envelope.
|
|
24
24
|
*/
|
|
25
25
|
UNKNOWN_PAYLOAD_BLOCK_ROOT = "unknown_payload_block_root",
|
|
26
|
+
/**
|
|
27
|
+
* Same as UNKNOWN_PAYLOAD_BLOCK_ROOT, but the trigger also carries the payload's slot.
|
|
28
|
+
*/
|
|
29
|
+
UNKNOWN_PAYLOAD_BLOCK_ROOT_SLOT = "unknown_payload_block_root_slot",
|
|
26
30
|
/**
|
|
27
31
|
* Payload analog of INCOMPLETE_BLOCK_INPUT: we have a partial payload input that did not complete in time.
|
|
28
32
|
*/
|
|
29
33
|
INCOMPLETE_PAYLOAD_ENVELOPE = "incomplete_payload_envelope",
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
export enum DownloadResult {
|
|
37
|
+
/** payload fully downloaded, ready to process */
|
|
38
|
+
Resolved = "resolved",
|
|
39
|
+
/** payload fully downloaded, but it was already imported (via gossip) while we were downloading */
|
|
40
|
+
Late = "late",
|
|
41
|
+
/** envelope downloaded, held until the block lands in fork choice */
|
|
42
|
+
WaitingForBlock = "waiting_for_block",
|
|
43
|
+
/** fetch failed, the pending entry is retained for retry */
|
|
44
|
+
Failed = "failed",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export enum FetchResult {
|
|
48
|
+
SuccessResolved = "success_resolved",
|
|
49
|
+
SuccessMissingParent = "success_missing_parent",
|
|
50
|
+
SuccessLate = "success_late",
|
|
51
|
+
SuccessWaitingForBlock = "success_waiting_for_block",
|
|
52
|
+
FailureTriedAllPeers = "failure_tried_all_peers",
|
|
53
|
+
FailureMaxAttempts = "failure_max_attempts",
|
|
54
|
+
}
|
|
55
|
+
|
|
32
56
|
export enum PendingBlockInputStatus {
|
|
33
57
|
pending = "pending",
|
|
34
58
|
fetching = "fetching",
|
|
@@ -110,9 +134,8 @@ export type PendingPayloadInput = {
|
|
|
110
134
|
export type PendingPayloadRootHex = {
|
|
111
135
|
status: PendingPayloadInputStatus.pending | PendingPayloadInputStatus.fetching;
|
|
112
136
|
rootHex: RootHex;
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
// payload/block slot. See BlockInputSync.resolvePayloadSlot.
|
|
137
|
+
// slot could be via fork choice or ChainEvent.unknownEnvelopeBlockRootSlot
|
|
138
|
+
// while ChainEvent.unknownEnvelopeBlockRoot does not provide one
|
|
116
139
|
slot?: Slot;
|
|
117
140
|
timeAddedSec: number;
|
|
118
141
|
timeSyncedSec?: number;
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -26,7 +26,9 @@ import {MAX_CONCURRENT_REQUESTS} from "./constants.js";
|
|
|
26
26
|
import {SyncOptions} from "./options.js";
|
|
27
27
|
import {
|
|
28
28
|
BlockInputSyncCacheItem,
|
|
29
|
+
DownloadResult,
|
|
29
30
|
DroppedItemReason,
|
|
31
|
+
FetchResult,
|
|
30
32
|
PayloadSyncCacheItem,
|
|
31
33
|
PendingBlockInput,
|
|
32
34
|
PendingBlockInputStatus,
|
|
@@ -63,14 +65,6 @@ type AdvancePendingBlockResult =
|
|
|
63
65
|
| "blocked"
|
|
64
66
|
| "removed";
|
|
65
67
|
|
|
66
|
-
enum FetchResult {
|
|
67
|
-
SuccessResolved = "success_resolved",
|
|
68
|
-
SuccessMissingParent = "success_missing_parent",
|
|
69
|
-
SuccessLate = "success_late",
|
|
70
|
-
FailureTriedAllPeers = "failure_tried_all_peers",
|
|
71
|
-
FailureMaxAttempts = "failure_max_attempts",
|
|
72
|
-
}
|
|
73
|
-
|
|
74
68
|
class UnknownBlockRateLimitedError extends Error {
|
|
75
69
|
constructor(message: string) {
|
|
76
70
|
super(message);
|
|
@@ -158,6 +152,7 @@ export class BlockInputSync {
|
|
|
158
152
|
this.logger.verbose("BlockInputSync enabled.");
|
|
159
153
|
this.chain.emitter.on(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
160
154
|
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
155
|
+
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRootSlot, this.onUnknownEnvelopeBlockRootSlot);
|
|
161
156
|
this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
162
157
|
this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
163
158
|
this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
@@ -185,6 +180,7 @@ export class BlockInputSync {
|
|
|
185
180
|
this.clearRateLimitBackoffTimer();
|
|
186
181
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
187
182
|
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
183
|
+
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRootSlot, this.onUnknownEnvelopeBlockRootSlot);
|
|
188
184
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
189
185
|
this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
190
186
|
this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
@@ -248,6 +244,19 @@ export class BlockInputSync {
|
|
|
248
244
|
}
|
|
249
245
|
};
|
|
250
246
|
|
|
247
|
+
private onUnknownEnvelopeBlockRootSlot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRootSlot]): void => {
|
|
248
|
+
try {
|
|
249
|
+
const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
|
|
250
|
+
if (isNewRoot) {
|
|
251
|
+
this.triggerUnknownBlockSearch();
|
|
252
|
+
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT_SLOT});
|
|
253
|
+
this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
|
|
254
|
+
}
|
|
255
|
+
} catch (e) {
|
|
256
|
+
this.logger.debug("Error handling unknownEnvelopeBlockRootSlot event", {}, e as Error);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
251
260
|
private onIncompletePayloadEnvelope = (data: ChainEventData[ChainEvent.incompletePayloadEnvelope]): void => {
|
|
252
261
|
try {
|
|
253
262
|
this.addByPayloadInput(data.payloadInput, data.peer);
|
|
@@ -1125,15 +1134,25 @@ export class BlockInputSync {
|
|
|
1125
1134
|
return;
|
|
1126
1135
|
}
|
|
1127
1136
|
this.pendingPayloads.set(resultRootHex, pendingPayload);
|
|
1137
|
+
const result = isPendingPayloadEnvelope(pendingPayload)
|
|
1138
|
+
? DownloadResult.WaitingForBlock
|
|
1139
|
+
: this.chain.forkChoice.hasPayloadHexUnsafe(resultRootHex)
|
|
1140
|
+
? DownloadResult.Late
|
|
1141
|
+
: DownloadResult.Resolved;
|
|
1142
|
+
this.metrics?.blockInputSync.downloadedPayloadsResult.inc({result});
|
|
1128
1143
|
|
|
1129
1144
|
if (isPendingPayloadEnvelope(pendingPayload)) {
|
|
1130
1145
|
await this.reconcilePayloadEnvelope(pendingPayload);
|
|
1131
1146
|
} else if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
1147
|
+
this.metrics?.blockInputSync.elapsedTimeTillPayloadReceived.observe(
|
|
1148
|
+
Date.now() / 1000 - computeTimeAtSlot(this.config, pendingPayload.payloadInput.slot, this.chain.genesisTime)
|
|
1149
|
+
);
|
|
1132
1150
|
await this.processPayload(pendingPayload);
|
|
1133
1151
|
}
|
|
1134
1152
|
return;
|
|
1135
1153
|
}
|
|
1136
1154
|
|
|
1155
|
+
this.metrics?.blockInputSync.downloadedPayloadsResult.inc({result: DownloadResult.Failed});
|
|
1137
1156
|
this.logger.debug("Ignoring unknown payload root after failed download", logCtx, res.err);
|
|
1138
1157
|
if (!isPendingPayloadEnvelope(payload)) {
|
|
1139
1158
|
payload.status = PendingPayloadInputStatus.pending;
|
|
@@ -1248,6 +1267,11 @@ export class BlockInputSync {
|
|
|
1248
1267
|
: await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1249
1268
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
1250
1269
|
|
|
1270
|
+
const fetchStartSec = Date.now() / 1000;
|
|
1271
|
+
if (typeof slot === "number") {
|
|
1272
|
+
this.metrics?.blockInputSync.payloadFetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1251
1275
|
let i = 0;
|
|
1252
1276
|
let deferredByRateLimit = false;
|
|
1253
1277
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
@@ -1271,6 +1295,11 @@ export class BlockInputSync {
|
|
|
1271
1295
|
pendingColumns.size > 0
|
|
1272
1296
|
? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1273
1297
|
: "no peer available to download pending payload";
|
|
1298
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe(
|
|
1299
|
+
{result: FetchResult.FailureTriedAllPeers},
|
|
1300
|
+
Date.now() / 1000 - fetchStartSec
|
|
1301
|
+
);
|
|
1302
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureTriedAllPeers}, i);
|
|
1274
1303
|
throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
|
|
1275
1304
|
}
|
|
1276
1305
|
|
|
@@ -1280,7 +1309,12 @@ export class BlockInputSync {
|
|
|
1280
1309
|
try {
|
|
1281
1310
|
if (!envelope) {
|
|
1282
1311
|
envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
|
|
1312
|
+
const slotWasUnknown = typeof slot !== "number";
|
|
1283
1313
|
slot = envelope.message.payload.slotNumber;
|
|
1314
|
+
if (slotWasUnknown) {
|
|
1315
|
+
// we were not able to observe the time into slot when starting the fetch, do it now
|
|
1316
|
+
this.metrics?.blockInputSync.payloadFetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
1317
|
+
}
|
|
1284
1318
|
}
|
|
1285
1319
|
|
|
1286
1320
|
payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
@@ -1289,6 +1323,11 @@ export class BlockInputSync {
|
|
|
1289
1323
|
// envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
|
|
1290
1324
|
// payloadInput may be seeded from the block body during download, so a non-null payloadInput does not
|
|
1291
1325
|
// imply the block is imported.
|
|
1326
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe(
|
|
1327
|
+
{result: FetchResult.SuccessWaitingForBlock},
|
|
1328
|
+
Date.now() / 1000 - fetchStartSec
|
|
1329
|
+
);
|
|
1330
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.SuccessWaitingForBlock}, i);
|
|
1292
1331
|
return {
|
|
1293
1332
|
status: PendingPayloadInputStatus.waitingForBlock,
|
|
1294
1333
|
envelope,
|
|
@@ -1339,6 +1378,12 @@ export class BlockInputSync {
|
|
|
1339
1378
|
});
|
|
1340
1379
|
|
|
1341
1380
|
if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
1381
|
+
// late = the payload got imported (via gossip) while we were downloading, same as the block path
|
|
1382
|
+
const result = this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)
|
|
1383
|
+
? FetchResult.SuccessLate
|
|
1384
|
+
: FetchResult.SuccessResolved;
|
|
1385
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe({result}, Date.now() / 1000 - fetchStartSec);
|
|
1386
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result}, i);
|
|
1342
1387
|
return pendingPayload;
|
|
1343
1388
|
}
|
|
1344
1389
|
|
|
@@ -1373,12 +1418,17 @@ export class BlockInputSync {
|
|
|
1373
1418
|
}
|
|
1374
1419
|
}
|
|
1375
1420
|
|
|
1421
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe(
|
|
1422
|
+
{result: FetchResult.FailureMaxAttempts},
|
|
1423
|
+
Date.now() / 1000 - fetchStartSec
|
|
1424
|
+
);
|
|
1425
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureMaxAttempts}, i - 1);
|
|
1426
|
+
|
|
1376
1427
|
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1377
1428
|
throw new UnknownBlockRateLimitedError(
|
|
1378
1429
|
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`
|
|
1379
1430
|
);
|
|
1380
1431
|
}
|
|
1381
|
-
|
|
1382
1432
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
1383
1433
|
}
|
|
1384
1434
|
|
|
@@ -1483,7 +1533,7 @@ export class BlockInputSync {
|
|
|
1483
1533
|
|
|
1484
1534
|
// no more peer with needed columns to try, throw error
|
|
1485
1535
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
1486
|
-
this.metrics?.blockInputSync.
|
|
1536
|
+
this.metrics?.blockInputSync.fetchTime.observe(
|
|
1487
1537
|
{result: FetchResult.FailureTriedAllPeers},
|
|
1488
1538
|
Date.now() / 1000 - fetchStartSec
|
|
1489
1539
|
);
|
|
@@ -1572,7 +1622,7 @@ export class BlockInputSync {
|
|
|
1572
1622
|
: this.chain.forkChoice.hasBlockHex(cacheItem.blockInput.parentRootHex)
|
|
1573
1623
|
? FetchResult.SuccessResolved
|
|
1574
1624
|
: FetchResult.SuccessMissingParent;
|
|
1575
|
-
this.metrics?.blockInputSync.
|
|
1625
|
+
this.metrics?.blockInputSync.fetchTime.observe({result}, Date.now() / 1000 - fetchStartSec);
|
|
1576
1626
|
this.metrics?.blockInputSync.fetchPeers.set({result}, i);
|
|
1577
1627
|
return cacheItem;
|
|
1578
1628
|
}
|
|
@@ -1606,7 +1656,7 @@ export class BlockInputSync {
|
|
|
1606
1656
|
}
|
|
1607
1657
|
}
|
|
1608
1658
|
|
|
1609
|
-
this.metrics?.blockInputSync.
|
|
1659
|
+
this.metrics?.blockInputSync.fetchTime.observe(
|
|
1610
1660
|
{result: FetchResult.FailureMaxAttempts},
|
|
1611
1661
|
Date.now() / 1000 - fetchStartSec
|
|
1612
1662
|
);
|
package/src/util/sszBytes.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {
|
|
4
|
+
BYTES_PER_CELL,
|
|
4
5
|
BYTES_PER_FIELD_ELEMENT,
|
|
5
6
|
FIELD_ELEMENTS_PER_BLOB,
|
|
6
7
|
ForkName,
|
|
@@ -551,6 +552,23 @@ export function getBeaconBlockRootFromDataColumnSidecarSerialized(data: Uint8Arr
|
|
|
551
552
|
return "0x" + blockRootBuf.toString("hex");
|
|
552
553
|
}
|
|
553
554
|
|
|
555
|
+
// beaconBlockRoot is the last fixed field in the Gloas layout
|
|
556
|
+
const DATA_COLUMN_SIDECAR_GLOAS_FIXED_SIZE = BEACON_BLOCK_ROOT_POSITION_IN_GLOAS_DATA_COLUMN_SIDECAR + ROOT_SIZE; // 56 bytes
|
|
557
|
+
const KZG_PROOF_SIZE = 48; // Bytes48
|
|
558
|
+
const DATA_COLUMN_SIDECAR_GLOAS_PER_BLOB_SIZE = BYTES_PER_CELL + KZG_PROOF_SIZE; // 2048 + 48 = 2096 bytes
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Gloas sidecar size bound uses the largest blob limit across the entire schedule, including future entries.
|
|
562
|
+
* https://github.com/ethereum/consensus-specs/pull/5613
|
|
563
|
+
*/
|
|
564
|
+
export function computeMaxGloasDataColumnSidecarSize(config: ChainForkConfig): number {
|
|
565
|
+
const maxBlobs = config.BLOB_SCHEDULE.reduce(
|
|
566
|
+
(max, entry) => Math.max(max, entry.MAX_BLOBS_PER_BLOCK),
|
|
567
|
+
config.MAX_BLOBS_PER_BLOCK_ELECTRA
|
|
568
|
+
);
|
|
569
|
+
return DATA_COLUMN_SIDECAR_GLOAS_FIXED_SIZE + maxBlobs * DATA_COLUMN_SIDECAR_GLOAS_PER_BLOB_SIZE;
|
|
570
|
+
}
|
|
571
|
+
|
|
554
572
|
/**
|
|
555
573
|
* SignedExecutionPayloadEnvelope SSZ Layout:
|
|
556
574
|
* ├─ 4 bytes: message offset (points to byte 100)
|