@lodestar/beacon-node 1.44.0-dev.1a8c38ee36 → 1.44.0-dev.1b4e24f1ee
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.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +43 -5
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +1 -1
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/config/constants.d.ts +1 -0
- package/lib/api/impl/config/constants.d.ts.map +1 -1
- package/lib/api/impl/config/constants.js +2 -1
- package/lib/api/impl/config/constants.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +69 -12
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.d.ts.map +1 -1
- package/lib/api/impl/lodestar/index.js +28 -0
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +97 -45
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +0 -4
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +5 -2
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +1 -1
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/chain.d.ts +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +10 -2
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +1 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/errors/payloadAttestation.d.ts +6 -0
- package/lib/chain/errors/payloadAttestation.d.ts.map +1 -1
- package/lib/chain/errors/payloadAttestation.js +1 -0
- package/lib/chain/errors/payloadAttestation.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts +4 -4
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +7 -7
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/opPools/executionPayloadBidPool.d.ts +4 -4
- package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
- package/lib/chain/opPools/executionPayloadBidPool.js +6 -4
- package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -0
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +2 -1
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +59 -16
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +2 -1
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +2 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/regen/queued.d.ts +0 -1
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +0 -4
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts +0 -5
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.js +0 -5
- package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +1 -4
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +5 -2
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/stateCache/types.d.ts +0 -2
- package/lib/chain/stateCache/types.d.ts.map +1 -1
- package/lib/chain/stateCache/types.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +34 -7
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +0 -2
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.js +24 -4
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +19 -766
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/interface.d.ts +1 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +5 -0
- 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 -2
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts +2 -2
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +24 -22
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +9 -5
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +13 -3
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts +2 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +16 -6
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts +2 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +15 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
- package/lib/network/reqresp/handlers/index.js +4 -4
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.js +22 -3
- package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
- package/lib/util/dataColumns.d.ts.map +1 -1
- package/lib/util/dataColumns.js +16 -11
- package/lib/util/dataColumns.js.map +1 -1
- package/package.json +14 -16
- package/src/api/impl/beacon/blocks/index.ts +49 -5
- package/src/api/impl/beacon/pool/index.ts +3 -1
- package/src/api/impl/config/constants.ts +2 -0
- package/src/api/impl/debug/index.ts +73 -12
- package/src/api/impl/lodestar/index.ts +30 -0
- package/src/api/impl/validator/index.ts +108 -47
- package/src/chain/archiveStore/archiveStore.ts +0 -5
- package/src/chain/blocks/importBlock.ts +10 -2
- package/src/chain/blocks/importExecutionPayload.ts +1 -1
- package/src/chain/chain.ts +12 -1
- package/src/chain/errors/executionPayloadBid.ts +2 -0
- package/src/chain/errors/payloadAttestation.ts +2 -0
- package/src/chain/forkChoice/index.ts +8 -0
- package/src/chain/opPools/executionPayloadBidPool.ts +10 -9
- package/src/chain/options.ts +1 -0
- package/src/chain/prepareNextSlot.ts +2 -1
- package/src/chain/produceBlock/produceBlockBody.ts +83 -18
- package/src/chain/regen/interface.ts +2 -1
- package/src/chain/regen/queued.ts +0 -5
- package/src/chain/stateCache/fifoBlockStateCache.ts +0 -6
- package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
- package/src/chain/stateCache/types.ts +0 -2
- package/src/chain/validation/executionPayloadBid.ts +36 -7
- package/src/chain/validation/executionPayloadEnvelope.ts +0 -2
- package/src/chain/validation/payloadAttestationMessage.ts +26 -4
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +4 -2
- package/src/network/processor/index.ts +26 -26
- package/src/network/reqresp/handlers/beaconBlocksByRange.ts +12 -5
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -3
- package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -1
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +22 -6
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +20 -1
- package/src/network/reqresp/handlers/index.ts +4 -4
- package/src/network/reqresp/utils/dataColumnResponseValidation.ts +21 -3
- package/src/util/dataColumns.ts +17 -12
|
@@ -143,11 +143,6 @@ export class QueuedStateRegenerator implements IStateRegenerator {
|
|
|
143
143
|
this.blockStateCache.prune(headStateRoot);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
pruneOnFinalized(finalizedEpoch: number): void {
|
|
147
|
-
this.checkpointStateCache.pruneFinalized(finalizedEpoch);
|
|
148
|
-
this.blockStateCache.deleteAllBeforeEpoch(finalizedEpoch);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
146
|
processState(blockRootHex: RootHex, postState: IBeaconStateView): void {
|
|
152
147
|
this.blockStateCache.add(postState);
|
|
153
148
|
this.checkpointStateCache.processState(blockRootHex, postState).catch((e) => {
|
|
@@ -167,12 +167,6 @@ export class FIFOBlockStateCache implements BlockStateCache {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
/**
|
|
171
|
-
* No need for this implementation
|
|
172
|
-
* This is only to conform to the old api
|
|
173
|
-
*/
|
|
174
|
-
deleteAllBeforeEpoch(): void {}
|
|
175
|
-
|
|
176
170
|
/**
|
|
177
171
|
* ONLY FOR DEBUGGING PURPOSES. For lodestar debug API.
|
|
178
172
|
*/
|
|
@@ -414,11 +414,12 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
414
414
|
|
|
415
415
|
/**
|
|
416
416
|
* Prune all checkpoint states before the provided finalized epoch.
|
|
417
|
+
* Driven sequentially from processState() so it never interleaves with persist.
|
|
417
418
|
*/
|
|
418
|
-
pruneFinalized(finalizedEpoch: Epoch): void {
|
|
419
|
+
private async pruneFinalized(finalizedEpoch: Epoch): Promise<void> {
|
|
419
420
|
for (const epoch of this.epochIndex.keys()) {
|
|
420
421
|
if (epoch < finalizedEpoch) {
|
|
421
|
-
this.deleteAllEpochItems(epoch).catch((e) =>
|
|
422
|
+
await this.deleteAllEpochItems(epoch).catch((e) =>
|
|
422
423
|
this.logger.debug("Error delete all epoch items", {epoch, finalizedEpoch}, e as Error)
|
|
423
424
|
);
|
|
424
425
|
}
|
|
@@ -476,6 +477,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
476
477
|
* As of Mar 2024, it takes <=350ms to persist a holesky state on fast server
|
|
477
478
|
*/
|
|
478
479
|
async processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number> {
|
|
480
|
+
// prune finalized in the same flow so a finalized cp state is pruned, never persisted
|
|
481
|
+
await this.pruneFinalized(state.finalizedCheckpoint.epoch);
|
|
482
|
+
|
|
479
483
|
let persistCount = 0;
|
|
480
484
|
// it's important to sort the epochs in ascending order, in case of big reorg we always want to keep the most recent checkpoint states
|
|
481
485
|
const sortedEpochs = Array.from(this.epochIndex.keys()).sort((a, b) => a - b);
|
|
@@ -30,7 +30,6 @@ export interface BlockStateCache {
|
|
|
30
30
|
clear(): void;
|
|
31
31
|
size: number;
|
|
32
32
|
prune(headStateRootHex: RootHex): void;
|
|
33
|
-
deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
|
|
34
33
|
dumpSummary(): routes.lodestar.StateCacheItem[];
|
|
35
34
|
/** Expose beacon states stored in cache. Use with caution */
|
|
36
35
|
getStates(): IterableIterator<IBeaconStateView>;
|
|
@@ -67,7 +66,6 @@ export interface CheckpointStateCache {
|
|
|
67
66
|
getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null>;
|
|
68
67
|
updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
|
|
69
68
|
prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void;
|
|
70
|
-
pruneFinalized(finalizedEpoch: Epoch): void;
|
|
71
69
|
processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number>;
|
|
72
70
|
clear(): void;
|
|
73
71
|
dumpSummary(): routes.lodestar.StateCacheItem[];
|
|
@@ -35,10 +35,6 @@ async function validateExecutionPayloadBid(
|
|
|
35
35
|
const bid = signedExecutionPayloadBid.message;
|
|
36
36
|
const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
|
|
37
37
|
const parentBlockHashHex = toRootHex(bid.parentBlockHash);
|
|
38
|
-
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipExecutionPayloadBid);
|
|
39
|
-
if (!isStatePostGloas(state)) {
|
|
40
|
-
throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
41
|
-
}
|
|
42
38
|
|
|
43
39
|
// [IGNORE] `bid.slot` is the current slot or the next slot.
|
|
44
40
|
const currentSlot = chain.clock.currentSlot;
|
|
@@ -61,6 +57,17 @@ async function validateExecutionPayloadBid(
|
|
|
61
57
|
});
|
|
62
58
|
}
|
|
63
59
|
|
|
60
|
+
// [REJECT] The bid is for a higher slot than its parent block -- i.e.
|
|
61
|
+
// validate that `bid.slot` is greater than the slot of the block with root
|
|
62
|
+
// `bid.parent_block_root`.
|
|
63
|
+
if (bid.slot <= parentBlock.slot) {
|
|
64
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
65
|
+
code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT,
|
|
66
|
+
parentSlot: parentBlock.slot,
|
|
67
|
+
slot: bid.slot,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
// [IGNORE] A `SignedProposerPreferences` matching `bid.slot` and the bid's branch has been
|
|
65
72
|
// seen — i.e. `proposal_slot == bid.slot` AND `dependent_root ==
|
|
66
73
|
// get_proposer_dependent_root(parent_state, compute_epoch_at_slot(bid.slot))`.
|
|
@@ -100,9 +107,31 @@ async function validateExecutionPayloadBid(
|
|
|
100
107
|
});
|
|
101
108
|
}
|
|
102
109
|
|
|
110
|
+
// Use the bid's parent branch state for builder checks
|
|
111
|
+
const state = await chain.regen
|
|
112
|
+
.getBlockSlotState(parentBlock, bid.slot, {dontTransferCache: true}, RegenCaller.validateGossipExecutionPayloadBid)
|
|
113
|
+
.catch(() => {
|
|
114
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
115
|
+
code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
116
|
+
parentBlockRoot: parentBlockRootHex,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!isStatePostGloas(state)) {
|
|
121
|
+
throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
103
124
|
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
|
|
104
125
|
// `is_active_builder(state, bid.builder_index)` returns `True`.
|
|
105
|
-
|
|
126
|
+
let builder: gloas.Builder;
|
|
127
|
+
try {
|
|
128
|
+
builder = state.getBuilder(bid.builderIndex);
|
|
129
|
+
} catch {
|
|
130
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
131
|
+
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
132
|
+
builderIndex: bid.builderIndex,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
106
135
|
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
107
136
|
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
108
137
|
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
@@ -186,11 +215,11 @@ async function validateExecutionPayloadBid(
|
|
|
186
215
|
// [IGNORE] this bid is the highest value bid seen for the tuple
|
|
187
216
|
// `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
|
|
188
217
|
const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex);
|
|
189
|
-
if (bestBid !== null && bestBid.value >= bid.value) {
|
|
218
|
+
if (bestBid !== null && bestBid.message.value >= bid.value) {
|
|
190
219
|
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
191
220
|
code: ExecutionPayloadBidErrorCode.BID_TOO_LOW,
|
|
192
221
|
bidValue: bid.value,
|
|
193
|
-
currentHighestBid: bestBid.value,
|
|
222
|
+
currentHighestBid: bestBid.message.value,
|
|
194
223
|
});
|
|
195
224
|
}
|
|
196
225
|
// [IGNORE] `bid.value` is less or equal than the builder's excess balance --
|
|
@@ -35,8 +35,6 @@ async function validateExecutionPayloadEnvelope(
|
|
|
35
35
|
// [IGNORE] The envelope's block root `envelope.beacon_block_root` has been seen (via
|
|
36
36
|
// gossip or non-gossip sources) (a client MAY queue payload for processing once
|
|
37
37
|
// the block is retrieved).
|
|
38
|
-
// TODO GLOAS: Need to review this, we should queue the envelope for later
|
|
39
|
-
// processing if the block is not yet known, otherwise we would ignore it here
|
|
40
38
|
const block = chain.forkChoice.getBlockDefaultStatus(envelope.beaconBlockRoot);
|
|
41
39
|
if (block === null) {
|
|
42
40
|
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
|
|
@@ -8,6 +8,7 @@ import {RootHex, gloas, ssz} from "@lodestar/types";
|
|
|
8
8
|
import {toRootHex} from "@lodestar/utils";
|
|
9
9
|
import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
|
|
10
10
|
import {IBeaconChain} from "../index.js";
|
|
11
|
+
import {RegenCaller} from "../regen/index.js";
|
|
11
12
|
|
|
12
13
|
export type PayloadAttestationValidationResult = {
|
|
13
14
|
attDataRootHex: RootHex;
|
|
@@ -61,22 +62,43 @@ async function validatePayloadAttestationMessage(
|
|
|
61
62
|
// [IGNORE] The message's block `data.beacon_block_root` has been seen (via
|
|
62
63
|
// gossip or non-gossip sources) (a client MAY queue attestation for processing
|
|
63
64
|
// once the block is retrieved. Note a client might want to request payload after).
|
|
64
|
-
|
|
65
|
+
const block = chain.forkChoice.getBlockDefaultStatus(data.beaconBlockRoot);
|
|
66
|
+
if (!block) {
|
|
65
67
|
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
66
68
|
code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
67
69
|
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
68
70
|
});
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
// [IGNORE] The block referenced by `data.beacon_block_root` is at slot `data.slot`,
|
|
74
|
+
// i.e. the block has `block.slot == data.slot`.
|
|
75
|
+
if (block.slot !== data.slot) {
|
|
76
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
77
|
+
code: PayloadAttestationErrorCode.INVALID_BLOCK_SLOT,
|
|
78
|
+
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
79
|
+
blockSlot: block.slot,
|
|
80
|
+
slot: data.slot,
|
|
81
|
+
});
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
// [REJECT] The message's block `data.beacon_block_root` passes validation.
|
|
77
85
|
// TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
|
|
78
86
|
// it is possible that the block didn't pass the validation
|
|
79
87
|
|
|
88
|
+
// Use the referenced block's branch state for the PTC committee check
|
|
89
|
+
const state = await chain.regen
|
|
90
|
+
.getBlockSlotState(block, data.slot, {dontTransferCache: true}, RegenCaller.validateGossipPayloadAttestationMessage)
|
|
91
|
+
.catch(() => {
|
|
92
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
93
|
+
code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
94
|
+
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!isStatePostGloas(state)) {
|
|
99
|
+
throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
// [REJECT] The message's validator index is within the payload committee in
|
|
81
103
|
// `get_ptc(state, data.slot)`. The `state` is the head state corresponding to
|
|
82
104
|
// processing the block up to the current slot as determined by the fork choice.
|
package/src/network/interface.ts
CHANGED
|
@@ -113,6 +113,7 @@ export interface INetwork extends INetworkCorePublic {
|
|
|
113
113
|
publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise<number>;
|
|
114
114
|
publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise<number>;
|
|
115
115
|
publishSignedExecutionPayloadEnvelope(signedEnvelope: gloas.SignedExecutionPayloadEnvelope): Promise<number>;
|
|
116
|
+
publishSignedExecutionPayloadBid(signedBid: gloas.SignedExecutionPayloadBid): Promise<number>;
|
|
116
117
|
publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number>;
|
|
117
118
|
publishProposerPreferences(signedProposerPreferences: gloas.SignedProposerPreferences): Promise<number>;
|
|
118
119
|
|
package/src/network/network.ts
CHANGED
|
@@ -515,6 +515,17 @@ export class Network implements INetwork {
|
|
|
515
515
|
);
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
+
async publishSignedExecutionPayloadBid(signedBid: gloas.SignedExecutionPayloadBid): Promise<number> {
|
|
519
|
+
const epoch = computeEpochAtSlot(signedBid.message.slot);
|
|
520
|
+
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
|
|
521
|
+
|
|
522
|
+
return this.publishGossip<GossipType.execution_payload_bid>(
|
|
523
|
+
{type: GossipType.execution_payload_bid, boundary},
|
|
524
|
+
signedBid,
|
|
525
|
+
{ignoreDuplicatePublishError: true}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
518
529
|
async publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number> {
|
|
519
530
|
const epoch = computeEpochAtSlot(payloadAttestationMessage.data.slot);
|
|
520
531
|
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
|
|
@@ -1214,8 +1214,10 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1214
1214
|
}
|
|
1215
1215
|
chain.forkChoice.notifyPtcMessages(
|
|
1216
1216
|
toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
|
|
1217
|
+
payloadAttestationMessage.data.slot,
|
|
1217
1218
|
validationResult.validatorCommitteeIndices,
|
|
1218
|
-
payloadAttestationMessage.data.payloadPresent
|
|
1219
|
+
payloadAttestationMessage.data.payloadPresent,
|
|
1220
|
+
payloadAttestationMessage.data.blobDataAvailable
|
|
1219
1221
|
);
|
|
1220
1222
|
},
|
|
1221
1223
|
[GossipType.execution_payload_bid]: async ({
|
|
@@ -1228,7 +1230,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1228
1230
|
|
|
1229
1231
|
// Handle valid payload bid by storing in a bid pool
|
|
1230
1232
|
try {
|
|
1231
|
-
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid
|
|
1233
|
+
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
|
|
1232
1234
|
metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
|
|
1233
1235
|
} catch (e) {
|
|
1234
1236
|
logger.error("Error adding to executionPayloadBid pool", {}, e as Error);
|
|
@@ -186,9 +186,11 @@ export class NetworkProcessor {
|
|
|
186
186
|
// we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
|
|
187
187
|
// to be stored in this Map and reprocessed once the block comes
|
|
188
188
|
private readonly awaitingMessagesByBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
|
|
189
|
+
private awaitingBlockMessageCount = 0;
|
|
189
190
|
// we may not receive the payload for messages that require the FULL payload variant to be processed,
|
|
190
191
|
// in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
|
|
191
192
|
private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
|
|
193
|
+
private awaitingPayloadMessageCount = 0;
|
|
192
194
|
private unknownBlocksBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
|
|
193
195
|
private unknownEnvelopesBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
|
|
194
196
|
|
|
@@ -228,8 +230,8 @@ export class NetworkProcessor {
|
|
|
228
230
|
metrics.gossipValidationQueue.keySize.set({topic}, this.gossipQueues[topic].keySize);
|
|
229
231
|
metrics.gossipValidationQueue.concurrency.set({topic}, this.gossipTopicConcurrency[topic]);
|
|
230
232
|
}
|
|
231
|
-
metrics.awaitingBlockGossipMessages.countPerSlot.set(this.
|
|
232
|
-
metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.
|
|
233
|
+
metrics.awaitingBlockGossipMessages.countPerSlot.set(this.awaitingBlockMessageCount);
|
|
234
|
+
metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.awaitingPayloadMessageCount);
|
|
233
235
|
// specific metric for beacon_attestation topic
|
|
234
236
|
metrics.gossipValidationQueue.keyAge.reset();
|
|
235
237
|
for (const ageMs of this.gossipQueues.beacon_attestation.getDataAgeMs()) {
|
|
@@ -497,7 +499,7 @@ export class NetworkProcessor {
|
|
|
497
499
|
this.pushPendingGossipsubMessageToQueue(message);
|
|
498
500
|
break;
|
|
499
501
|
case PreprocessAction.AwaitBlock: {
|
|
500
|
-
if (this.
|
|
502
|
+
if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
|
|
501
503
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
502
504
|
this.metrics?.awaitingBlockGossipMessages.reject.inc({
|
|
503
505
|
reason: ReprocessRejectReason.reached_limit,
|
|
@@ -509,10 +511,11 @@ export class NetworkProcessor {
|
|
|
509
511
|
this.metrics?.awaitingBlockGossipMessages.queue.inc({topic: topicType});
|
|
510
512
|
const awaitingGossipsubMessages = this.awaitingMessagesByBlockRoot.getOrDefault(preprocessResult.root);
|
|
511
513
|
awaitingGossipsubMessages.add(message);
|
|
514
|
+
this.awaitingBlockMessageCount++;
|
|
512
515
|
break;
|
|
513
516
|
}
|
|
514
517
|
case PreprocessAction.AwaitEnvelope: {
|
|
515
|
-
if (this.
|
|
518
|
+
if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
|
|
516
519
|
this.metrics?.awaitingPayloadGossipMessages.reject.inc({
|
|
517
520
|
reason: ReprocessRejectReason.reached_limit,
|
|
518
521
|
topic: topicType,
|
|
@@ -525,6 +528,7 @@ export class NetworkProcessor {
|
|
|
525
528
|
preprocessResult.root
|
|
526
529
|
);
|
|
527
530
|
awaitingPayloadGossipsubMessages.add(message);
|
|
531
|
+
this.awaitingPayloadMessageCount++;
|
|
528
532
|
break;
|
|
529
533
|
}
|
|
530
534
|
}
|
|
@@ -548,6 +552,12 @@ export class NetworkProcessor {
|
|
|
548
552
|
return;
|
|
549
553
|
}
|
|
550
554
|
|
|
555
|
+
// Atomically remove from map and update counter before async iteration to
|
|
556
|
+
// prevent double-decrement race with onClockSlot during yield points below
|
|
557
|
+
if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
|
|
558
|
+
this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
|
|
559
|
+
}
|
|
560
|
+
|
|
551
561
|
const nowSec = Date.now() / 1000;
|
|
552
562
|
let count = 0;
|
|
553
563
|
// TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
|
|
@@ -567,8 +577,6 @@ export class NetworkProcessor {
|
|
|
567
577
|
await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
|
|
568
578
|
}
|
|
569
579
|
}
|
|
570
|
-
|
|
571
|
-
this.awaitingMessagesByBlockRoot.delete(rootHex);
|
|
572
580
|
};
|
|
573
581
|
|
|
574
582
|
private onPayloadEnvelopeProcessed = async ({blockRoot: rootHex}: {blockRoot: RootHex}): Promise<void> => {
|
|
@@ -577,6 +585,12 @@ export class NetworkProcessor {
|
|
|
577
585
|
return;
|
|
578
586
|
}
|
|
579
587
|
|
|
588
|
+
// Atomically remove from map and update counter before async iteration to
|
|
589
|
+
// prevent double-decrement race with onClockSlot during yield points below
|
|
590
|
+
if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
|
|
591
|
+
this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
|
|
592
|
+
}
|
|
593
|
+
|
|
580
594
|
const nowSec = Date.now() / 1000;
|
|
581
595
|
let count = 0;
|
|
582
596
|
for (const message of waitingGossipsubMessages) {
|
|
@@ -593,8 +607,6 @@ export class NetworkProcessor {
|
|
|
593
607
|
await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
|
|
594
608
|
}
|
|
595
609
|
}
|
|
596
|
-
|
|
597
|
-
this.awaitingMessagesByPayloadBlockRoot.delete(rootHex);
|
|
598
610
|
};
|
|
599
611
|
|
|
600
612
|
private onClockSlot = (clockSlot: Slot): void => {
|
|
@@ -618,7 +630,9 @@ export class NetworkProcessor {
|
|
|
618
630
|
);
|
|
619
631
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
620
632
|
}
|
|
621
|
-
this.awaitingMessagesByBlockRoot.delete(rootHex)
|
|
633
|
+
if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
|
|
634
|
+
this.awaitingBlockMessageCount -= gossipMessages.size;
|
|
635
|
+
}
|
|
622
636
|
}
|
|
623
637
|
}
|
|
624
638
|
this.unknownBlocksBySlot.delete(slot);
|
|
@@ -641,7 +655,9 @@ export class NetworkProcessor {
|
|
|
641
655
|
);
|
|
642
656
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
643
657
|
}
|
|
644
|
-
this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)
|
|
658
|
+
if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
|
|
659
|
+
this.awaitingPayloadMessageCount -= gossipMessages.size;
|
|
660
|
+
}
|
|
645
661
|
}
|
|
646
662
|
}
|
|
647
663
|
this.unknownEnvelopesBySlot.delete(slot);
|
|
@@ -784,20 +800,4 @@ export class NetworkProcessor {
|
|
|
784
800
|
|
|
785
801
|
return null;
|
|
786
802
|
}
|
|
787
|
-
|
|
788
|
-
private get unknownBlockGossipsubMessagesCount(): number {
|
|
789
|
-
let count = 0;
|
|
790
|
-
for (const messages of this.awaitingMessagesByBlockRoot.values()) {
|
|
791
|
-
count += messages.size;
|
|
792
|
-
}
|
|
793
|
-
return count;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
private get unknownPayloadGossipsubMessagesCount(): number {
|
|
797
|
-
let count = 0;
|
|
798
|
-
for (const messages of this.awaitingMessagesByPayloadBlockRoot.values()) {
|
|
799
|
-
count += messages.size;
|
|
800
|
-
}
|
|
801
|
-
return count;
|
|
802
|
-
}
|
|
803
803
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {PeerId} from "@libp2p/interface";
|
|
2
2
|
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
-
import {GENESIS_SLOT, isForkPostDeneb
|
|
3
|
+
import {GENESIS_SLOT, isForkPostDeneb} from "@lodestar/params";
|
|
4
4
|
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
|
|
5
5
|
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
6
6
|
import {deneb, phase0} from "@lodestar/types";
|
|
@@ -29,13 +29,20 @@ export async function* onBeaconBlocksByRange(
|
|
|
29
29
|
// starts above it to avoid duplicate yields. See archiveBlocks.ts for the migration logic.
|
|
30
30
|
const archiveMaxSlot = finalizedSlot;
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// endSlot is exclusive, so highest served slot is endSlot - 1.
|
|
33
|
+
// Throw only when the entire requested range is below earliestAvailableSlot.
|
|
34
|
+
if (endSlot - 1 < chain.earliestAvailableSlot) {
|
|
35
|
+
chain.logger.verbose("Peer requested range before earliestAvailableSlot for BeaconBlocksByRange", {
|
|
35
36
|
peer: prettyPrintPeerId(peerId),
|
|
36
37
|
client: peerClient,
|
|
38
|
+
startSlot,
|
|
39
|
+
count,
|
|
40
|
+
earliestAvailableSlot: chain.earliestAvailableSlot,
|
|
37
41
|
});
|
|
38
|
-
|
|
42
|
+
throw new ResponseError(
|
|
43
|
+
RespStatus.RESOURCE_UNAVAILABLE,
|
|
44
|
+
`Requested range is before earliestAvailableSlot startSlot=${startSlot} count=${count} earliestAvailableSlot=${chain.earliestAvailableSlot}`
|
|
45
|
+
);
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
// Finalized range of blocks
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {PeerId} from "@libp2p/interface";
|
|
2
2
|
import {ChainConfig} from "@lodestar/config";
|
|
3
|
+
import {PayloadStatus} from "@lodestar/fork-choice";
|
|
3
4
|
import {ForkSeq, GENESIS_SLOT} from "@lodestar/params";
|
|
4
5
|
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
|
|
5
6
|
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
@@ -33,12 +34,20 @@ export async function* onDataColumnSidecarsByRange(
|
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
// endSlot is exclusive, so highest served slot is endSlot - 1.
|
|
38
|
+
// Throw only when the entire requested range is below earliestAvailableSlot.
|
|
39
|
+
if (endSlot - 1 < chain.earliestAvailableSlot) {
|
|
40
|
+
chain.logger.verbose("Peer requested range before earliestAvailableSlot for DataColumnSidecarsByRange", {
|
|
38
41
|
peer: prettyPrintPeerId(peerId),
|
|
39
42
|
client: peerClient,
|
|
43
|
+
startSlot,
|
|
44
|
+
count,
|
|
45
|
+
earliestAvailableSlot: chain.earliestAvailableSlot,
|
|
40
46
|
});
|
|
41
|
-
|
|
47
|
+
throw new ResponseError(
|
|
48
|
+
RespStatus.RESOURCE_UNAVAILABLE,
|
|
49
|
+
`Requested range is before earliestAvailableSlot startSlot=${startSlot} count=${count} earliestAvailableSlot=${chain.earliestAvailableSlot}`
|
|
50
|
+
);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
const finalized = db.dataColumnSidecarArchive;
|
|
@@ -104,6 +113,11 @@ export async function* onDataColumnSidecarsByRange(
|
|
|
104
113
|
|
|
105
114
|
// Must include only columns in the range requested
|
|
106
115
|
if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
|
|
116
|
+
// Post-gloas, columns exist only for FULL blocks (pre-gloas blocks are always FULL)
|
|
117
|
+
if (block.payloadStatus !== PayloadStatus.FULL) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
// Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
|
|
108
122
|
// at the time of the start of the request. Spec is clear the chain of columns must be consistent, but on
|
|
109
123
|
// re-org there's no need to abort the request
|
|
@@ -30,7 +30,7 @@ export async function* onDataColumnSidecarsByRoot(
|
|
|
30
30
|
const {blockRoot, columns: requestedColumns} = dataColumnsByRootIdentifier;
|
|
31
31
|
const availableColumns = validateRequestedDataColumns(chain, requestedColumns);
|
|
32
32
|
if (availableColumns.length === 0) {
|
|
33
|
-
|
|
33
|
+
continue;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const blockRootHex = toRootHex(blockRoot);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {PeerId} from "@libp2p/interface";
|
|
1
2
|
import {ChainConfig} from "@lodestar/config";
|
|
2
3
|
import {PayloadStatus} from "@lodestar/fork-choice";
|
|
3
4
|
import {GENESIS_SLOT} from "@lodestar/params";
|
|
@@ -6,23 +7,38 @@ import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
|
6
7
|
import {gloas} from "@lodestar/types";
|
|
7
8
|
import {IBeaconChain} from "../../../chain/index.js";
|
|
8
9
|
import {IBeaconDb} from "../../../db/index.js";
|
|
10
|
+
import {prettyPrintPeerId} from "../../util.js";
|
|
9
11
|
|
|
10
12
|
export async function* onExecutionPayloadEnvelopesByRange(
|
|
11
13
|
request: gloas.ExecutionPayloadEnvelopesByRangeRequest,
|
|
12
14
|
chain: IBeaconChain,
|
|
13
|
-
db: IBeaconDb
|
|
15
|
+
db: IBeaconDb,
|
|
16
|
+
peerId: PeerId,
|
|
17
|
+
peerClient: string
|
|
14
18
|
): AsyncIterable<ResponseOutgoing> {
|
|
15
19
|
const {startSlot, count} = validateExecutionPayloadEnvelopesByRangeRequest(chain.config, request);
|
|
16
20
|
const endSlot = startSlot + count;
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
// endSlot is exclusive, so highest served slot is endSlot - 1.
|
|
23
|
+
// Throw only when the entire requested range is below earliestAvailableSlot.
|
|
24
|
+
if (endSlot - 1 < chain.earliestAvailableSlot) {
|
|
25
|
+
chain.logger.verbose("Peer requested range before earliestAvailableSlot for ExecutionPayloadEnvelopesByRange", {
|
|
26
|
+
peer: prettyPrintPeerId(peerId),
|
|
27
|
+
client: peerClient,
|
|
28
|
+
startSlot,
|
|
29
|
+
count,
|
|
30
|
+
earliestAvailableSlot: chain.earliestAvailableSlot,
|
|
31
|
+
});
|
|
32
|
+
throw new ResponseError(
|
|
33
|
+
RespStatus.RESOURCE_UNAVAILABLE,
|
|
34
|
+
`Requested range is before earliestAvailableSlot startSlot=${startSlot} count=${count} earliestAvailableSlot=${chain.earliestAvailableSlot}`
|
|
35
|
+
);
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
const finalized = db.executionPayloadEnvelopeArchive;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// in the next finalization run
|
|
39
|
+
// Use the finalized block's actual slot as the checkpoint epoch-boundary slot may be skipped
|
|
40
|
+
const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
|
|
41
|
+
// The finalized block's envelope stays in the hot db until the next finalization run
|
|
26
42
|
const archiveMaxSlot = finalizedSlot - 1;
|
|
27
43
|
|
|
28
44
|
// Finalized range of envelopes
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import {PeerId} from "@libp2p/interface";
|
|
1
2
|
import {ResponseOutgoing} from "@lodestar/reqresp";
|
|
2
3
|
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
3
4
|
import {toRootHex} from "@lodestar/utils";
|
|
4
5
|
import {IBeaconChain} from "../../../chain/index.js";
|
|
5
6
|
import {IBeaconDb} from "../../../db/index.js";
|
|
6
7
|
import {ExecutionPayloadEnvelopesByRootRequest} from "../../../util/types.js";
|
|
8
|
+
import {prettyPrintPeerId} from "../../util.js";
|
|
7
9
|
|
|
8
10
|
export async function* onExecutionPayloadEnvelopesByRoot(
|
|
9
11
|
requestBody: ExecutionPayloadEnvelopesByRootRequest,
|
|
10
12
|
chain: IBeaconChain,
|
|
11
|
-
db: IBeaconDb
|
|
13
|
+
db: IBeaconDb,
|
|
14
|
+
peerId: PeerId,
|
|
15
|
+
peerClient: string
|
|
12
16
|
): AsyncIterable<ResponseOutgoing> {
|
|
13
17
|
// The gloas req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
|
|
14
18
|
// Archival nodes may still serve older retained payloads to allow genesis sync.
|
|
@@ -20,6 +24,14 @@ export async function* onExecutionPayloadEnvelopesByRoot(
|
|
|
20
24
|
const slot = block ? block.slot : await db.blockArchive.getSlotByRoot(root);
|
|
21
25
|
|
|
22
26
|
if (slot === null) {
|
|
27
|
+
chain.logger.debug(
|
|
28
|
+
"Cannot serve ExecutionPayloadEnvelopesByRoot: block root not in fork choice or block archive",
|
|
29
|
+
{
|
|
30
|
+
root: rootHex,
|
|
31
|
+
peer: prettyPrintPeerId(peerId),
|
|
32
|
+
client: peerClient,
|
|
33
|
+
}
|
|
34
|
+
);
|
|
23
35
|
continue;
|
|
24
36
|
}
|
|
25
37
|
|
|
@@ -29,6 +41,13 @@ export async function* onExecutionPayloadEnvelopesByRoot(
|
|
|
29
41
|
data: envelopeBytes,
|
|
30
42
|
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(slot)),
|
|
31
43
|
};
|
|
44
|
+
} else {
|
|
45
|
+
chain.logger.debug("Cannot serve ExecutionPayloadEnvelopesByRoot: envelope not found", {
|
|
46
|
+
slot,
|
|
47
|
+
root: rootHex,
|
|
48
|
+
peer: prettyPrintPeerId(peerId),
|
|
49
|
+
client: peerClient,
|
|
50
|
+
});
|
|
32
51
|
}
|
|
33
52
|
}
|
|
34
53
|
}
|
|
@@ -70,13 +70,13 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh
|
|
|
70
70
|
return onDataColumnSidecarsByRoot(body, chain, db, peerId, peerClient);
|
|
71
71
|
},
|
|
72
72
|
|
|
73
|
-
[ReqRespMethod.ExecutionPayloadEnvelopesByRoot]: (req) => {
|
|
73
|
+
[ReqRespMethod.ExecutionPayloadEnvelopesByRoot]: (req, peerId, peerClient) => {
|
|
74
74
|
const body = ExecutionPayloadEnvelopesByRootRequestType(chain.config).deserialize(req.data);
|
|
75
|
-
return onExecutionPayloadEnvelopesByRoot(body, chain, db);
|
|
75
|
+
return onExecutionPayloadEnvelopesByRoot(body, chain, db, peerId, peerClient);
|
|
76
76
|
},
|
|
77
|
-
[ReqRespMethod.ExecutionPayloadEnvelopesByRange]: (req) => {
|
|
77
|
+
[ReqRespMethod.ExecutionPayloadEnvelopesByRange]: (req, peerId, peerClient) => {
|
|
78
78
|
const body = ssz.gloas.ExecutionPayloadEnvelopesByRangeRequest.deserialize(req.data);
|
|
79
|
-
return onExecutionPayloadEnvelopesByRange(body, chain, db);
|
|
79
|
+
return onExecutionPayloadEnvelopesByRange(body, chain, db, peerId, peerClient);
|
|
80
80
|
},
|
|
81
81
|
|
|
82
82
|
[ReqRespMethod.LightClientBootstrap]: (req) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {LogData} from "@lodestar/logger";
|
|
2
|
+
import {ForkSeq} from "@lodestar/params";
|
|
2
3
|
import {RespStatus, ResponseError} from "@lodestar/reqresp";
|
|
3
4
|
import {ColumnIndex, Slot} from "@lodestar/types";
|
|
4
5
|
import {prettyBytes, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
@@ -38,6 +39,13 @@ export async function handleColumnSidecarUnavailability({
|
|
|
38
39
|
|
|
39
40
|
chain.logger.debug("dataColumnSidecar requested unavailable", logData);
|
|
40
41
|
|
|
42
|
+
// Post-gloas, columns exist only for FULL blocks; a finalized block is FULL if its envelope was
|
|
43
|
+
// archived. Bid blobsCount is unreliable here since an EMPTY block's bid may still commit to blobs
|
|
44
|
+
if (blockRoot === undefined && chain.config.getForkSeq(slot) >= ForkSeq.gloas) {
|
|
45
|
+
const envelopeBytes = await db.executionPayloadEnvelopeArchive.getBinary(slot);
|
|
46
|
+
if (!envelopeBytes) return;
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
const blockBytes = blockRoot ? await db.block.getBinary(blockRoot) : await db.blockArchive.getBinary(slot);
|
|
42
50
|
if (!blockBytes) {
|
|
43
51
|
chain.logger.verbose(
|
|
@@ -71,9 +79,19 @@ export function validateRequestedDataColumns(chain: IBeaconChain, requestedColum
|
|
|
71
79
|
throw new ResponseError(RespStatus.INVALID_REQUEST, "dataColumnSidecar requested without column indices");
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
const custodyColumns = chain.custodyConfig
|
|
75
|
-
const availableColumns =
|
|
76
|
-
const missingColumns =
|
|
82
|
+
const {custodyColumns, custodyColumnsIndex} = chain.custodyConfig;
|
|
83
|
+
const availableColumns: ColumnIndex[] = [];
|
|
84
|
+
const missingColumns: ColumnIndex[] = [];
|
|
85
|
+
for (const c of requestedColumns) {
|
|
86
|
+
// `c` is peer-controlled and SSZ-deserialized as `uint64`, so it may exceed
|
|
87
|
+
// `NUMBER_OF_COLUMNS - 1`; `Uint8Array` returns `undefined` for OOB reads,
|
|
88
|
+
// and `undefined !== 0` would silently classify OOB indices as custodied.
|
|
89
|
+
if ((custodyColumnsIndex[c] ?? 0) !== 0) {
|
|
90
|
+
availableColumns.push(c);
|
|
91
|
+
} else {
|
|
92
|
+
missingColumns.push(c);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
77
95
|
|
|
78
96
|
if (missingColumns.length > 0) {
|
|
79
97
|
chain.logger.verbose("Requested dataColumnSidecar for non-custody columns", {
|