@lodestar/beacon-node 1.50.0-dev.f0b11f836e → 1.50.0-dev.f2fb26d453
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 +1 -6
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -2
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/chain.d.ts +4 -5
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +13 -11
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.d.ts +0 -5
- package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.js +0 -2
- package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +1 -3
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +0 -2
- package/lib/chain/errors/gossipValidation.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +2 -5
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -3
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +2 -6
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +0 -6
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +0 -10
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +0 -3
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +3 -7
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +3 -7
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +0 -9
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +10 -77
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +18 -50
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +4 -5
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/execution.d.ts +11 -9
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +1 -5
- package/lib/util/execution.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +4 -7
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -2
- package/src/chain/chain.ts +16 -14
- package/src/chain/errors/envelopeReconstructionError.ts +0 -3
- package/src/chain/errors/gossipValidation.ts +0 -2
- package/src/chain/interface.ts +2 -2
- package/src/chain/prepareNextSlot.ts +3 -6
- package/src/chain/produceBlock/produceBlockBody.ts +1 -8
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +0 -11
- package/src/network/processor/gossipHandlers.ts +0 -3
- package/src/network/processor/gossipValidatorFn.ts +3 -13
- package/src/network/processor/index.ts +10 -89
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
- package/src/sync/range/range.ts +18 -50
- package/src/sync/unknownBlock.ts +4 -5
- package/src/util/execution.ts +8 -16
|
@@ -9,8 +9,6 @@ export enum GossipErrorCode {
|
|
|
9
9
|
INVALID_SERIALIZED_BYTES_ERROR_CODE = "GOSSIP_ERROR_INVALID_SERIALIZED_BYTES",
|
|
10
10
|
PAST_SLOT = "GOSSIP_ERROR_PAST_SLOT",
|
|
11
11
|
FUTURE_SLOT = "GOSSIP_ERROR_FUTURE_SLOT",
|
|
12
|
-
FATAL_PEER = "GOSSIP_ERROR_FATAL_PEER",
|
|
13
|
-
INVALID_SLOT = "GOSSIP_ERROR_INVALID_SLOT",
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
export class GossipActionError<T extends {code: string}> extends LodestarError<T> {
|
package/src/chain/interface.ts
CHANGED
|
@@ -30,7 +30,7 @@ import {Metrics} from "../metrics/metrics.js";
|
|
|
30
30
|
import {BufferPool} from "../util/bufferPool.js";
|
|
31
31
|
import {IClock} from "../util/clock.js";
|
|
32
32
|
import {CustodyConfig} from "../util/dataColumns.js";
|
|
33
|
-
import {
|
|
33
|
+
import {ReconstructMismatchPolicy} from "../util/execution.js";
|
|
34
34
|
import {SerializedCache} from "../util/serializedCache.js";
|
|
35
35
|
import {IArchiveStore} from "./archiveStore/interface.js";
|
|
36
36
|
import {CheckpointBalancesCache} from "./balancesCache.js";
|
|
@@ -239,7 +239,7 @@ export interface IBeaconChain {
|
|
|
239
239
|
getSerializedExecutionPayloadEnvelope(blockSlot: Slot, blockRootHex: string): Promise<Uint8Array | null>;
|
|
240
240
|
getSerializedExecutionPayloadEnvelopes(
|
|
241
241
|
requests: {blockSlot: Slot; blockRootHex: RootHex}[],
|
|
242
|
-
|
|
242
|
+
onMismatch?: ReconstructMismatchPolicy
|
|
243
243
|
): Promise<(Uint8Array | null)[]>;
|
|
244
244
|
getExecutionPayloadEnvelope(
|
|
245
245
|
blockSlot: Slot,
|
|
@@ -244,10 +244,6 @@ export class PrepareNextSlotScheduler {
|
|
|
244
244
|
parentBlockHash = preparedState.latestExecutionPayloadHeader.blockHash;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
// The payload_attributes event must carry the same hashes we send to the EL
|
|
248
|
-
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
249
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
|
|
250
|
-
|
|
251
247
|
let payloadAttributes: PayloadAttributes | undefined;
|
|
252
248
|
// If emitPayloadAttributes is true emit a SSE payloadAttributes event for
|
|
253
249
|
// every slot. Without the flag, only emit the event if we are proposing in the next slot.
|
|
@@ -260,8 +256,6 @@ export class PrepareNextSlotScheduler {
|
|
|
260
256
|
prepareSlot,
|
|
261
257
|
parentBlockRoot: fromHex(updatedHead.blockRoot),
|
|
262
258
|
parentBlockHash,
|
|
263
|
-
safeBlockHash,
|
|
264
|
-
finalizedBlockHash,
|
|
265
259
|
feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
|
|
266
260
|
});
|
|
267
261
|
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
|
|
@@ -273,6 +267,9 @@ export class PrepareNextSlotScheduler {
|
|
|
273
267
|
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
|
|
274
268
|
this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
|
|
275
269
|
|
|
270
|
+
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
271
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
|
|
272
|
+
|
|
276
273
|
// awaiting here instead of throwing an async call because there is no other task
|
|
277
274
|
// left for scheduler and this gives nice semantics to catch and log errors in the
|
|
278
275
|
// try/catch wrapper here.
|
|
@@ -845,8 +845,6 @@ export function getPayloadAttributesForSSE(
|
|
|
845
845
|
prepareSlot,
|
|
846
846
|
parentBlockRoot,
|
|
847
847
|
parentBlockHash,
|
|
848
|
-
safeBlockHash,
|
|
849
|
-
finalizedBlockHash,
|
|
850
848
|
feeRecipient,
|
|
851
849
|
}: {
|
|
852
850
|
/**
|
|
@@ -857,8 +855,6 @@ export function getPayloadAttributesForSSE(
|
|
|
857
855
|
prepareSlot: Slot;
|
|
858
856
|
parentBlockRoot: Root;
|
|
859
857
|
parentBlockHash: Bytes32;
|
|
860
|
-
safeBlockHash: RootHex;
|
|
861
|
-
finalizedBlockHash: RootHex;
|
|
862
858
|
feeRecipient: string;
|
|
863
859
|
}
|
|
864
860
|
): SSEPayloadAttributes {
|
|
@@ -878,10 +874,7 @@ export function getPayloadAttributesForSSE(
|
|
|
878
874
|
payloadAttributes,
|
|
879
875
|
} as SSEPayloadAttributes;
|
|
880
876
|
|
|
881
|
-
if (isForkPostGloas(fork)) {
|
|
882
|
-
(ssePayloadAttributes as gloas.SSEPayloadAttributes).safeBlockHash = fromHex(safeBlockHash);
|
|
883
|
-
(ssePayloadAttributes as gloas.SSEPayloadAttributes).finalizedBlockHash = fromHex(finalizedBlockHash);
|
|
884
|
-
} else {
|
|
877
|
+
if (!isForkPostGloas(fork)) {
|
|
885
878
|
// Removed in Gloas, builders can get the block number from the EL via the block hash if required
|
|
886
879
|
(ssePayloadAttributes as bellatrix.SSEPayloadAttributes).parentBlockNumber = prepareState.payloadBlockNumber;
|
|
887
880
|
}
|
|
@@ -264,17 +264,6 @@ export class SeenPayloadEnvelopeInput {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
/**
|
|
268
|
-
* Removes a PayloadEnvelopeInput whose envelope failed import with a definitive invalid verdict. The envelope
|
|
269
|
-
* cannot be replaced in place, sync recreates the entry via `getOrReload()` or `add()` to fetch another one.
|
|
270
|
-
* No-op if the cached entry is a different object, it may already be the recreated one.
|
|
271
|
-
*/
|
|
272
|
-
removeInvalid(payloadInput: PayloadEnvelopeInput): void {
|
|
273
|
-
if (this.payloadInputs.get(payloadInput.blockRootHex) === payloadInput) {
|
|
274
|
-
this.evictPayloadInput(payloadInput, "invalid");
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
267
|
pruneBelowParent(parentBlock: ProtoBlock): void {
|
|
279
268
|
for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
|
|
280
269
|
// Only evict once the payload is FULL (revealed/imported) — on an EMPTY/PENDING branch we may
|
|
@@ -1341,9 +1341,6 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1341
1341
|
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1342
1342
|
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1343
1343
|
core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
|
|
1344
|
-
// The builder may have signed another envelope that is valid, keeping this one would make
|
|
1345
|
-
// by-root and range sync reuse it and never import the payload
|
|
1346
|
-
chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
|
|
1347
1344
|
// Misbehaving peer, but could highlight an issue in another client
|
|
1348
1345
|
logLevel = LogLevel.warn;
|
|
1349
1346
|
break;
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
VoluntaryExitErrorCode,
|
|
21
21
|
} from "../../chain/errors/index.js";
|
|
22
22
|
import {Metrics} from "../../metrics/index.js";
|
|
23
|
-
import {PeerIdStr} from "../../util/peerId.js";
|
|
24
23
|
import {INetworkCore} from "../core/index.js";
|
|
25
24
|
import {
|
|
26
25
|
BatchGossipHandlerFn,
|
|
@@ -41,8 +40,6 @@ export type ValidatorFnModules = {
|
|
|
41
40
|
core: INetworkCore;
|
|
42
41
|
};
|
|
43
42
|
|
|
44
|
-
export type OnFatalPeerFn = (peer: PeerIdStr) => void;
|
|
45
|
-
|
|
46
43
|
type RejectPeerActionRule = {default: PeerAction; byCode?: Record<string, PeerAction>};
|
|
47
44
|
|
|
48
45
|
/**
|
|
@@ -115,7 +112,7 @@ const gossipRejectPeerAction: Record<GossipType, RejectPeerActionRule> = {
|
|
|
115
112
|
[GossipType.light_client_optimistic_update]: {default: PeerAction.HighToleranceError},
|
|
116
113
|
};
|
|
117
114
|
|
|
118
|
-
|
|
115
|
+
function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
119
116
|
const rule = gossipRejectPeerAction[type];
|
|
120
117
|
return rule.byCode?.[code] ?? rule.default;
|
|
121
118
|
}
|
|
@@ -126,8 +123,7 @@ export function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
|
126
123
|
*/
|
|
127
124
|
export function getGossipValidatorBatchFn(
|
|
128
125
|
gossipHandlers: GossipHandlers,
|
|
129
|
-
modules: ValidatorFnModules
|
|
130
|
-
onFatalPeer: OnFatalPeerFn
|
|
126
|
+
modules: ValidatorFnModules
|
|
131
127
|
): GossipValidatorBatchFn {
|
|
132
128
|
const {logger, metrics, core} = modules;
|
|
133
129
|
|
|
@@ -177,7 +173,6 @@ export function getGossipValidatorBatchFn(
|
|
|
177
173
|
metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
|
|
178
174
|
const peerAction = rejectPeerAction(type, e.type.code);
|
|
179
175
|
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
180
|
-
if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
|
|
181
176
|
logger.debug(
|
|
182
177
|
`Gossip validation ${type} rejected`,
|
|
183
178
|
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|
|
@@ -213,11 +208,7 @@ export function getGossipValidatorBatchFn(
|
|
|
213
208
|
*
|
|
214
209
|
* @see getGossipHandlers for reasoning on why GossipHandlerFn are used for gossip validation.
|
|
215
210
|
*/
|
|
216
|
-
export function getGossipValidatorFn(
|
|
217
|
-
gossipHandlers: GossipHandlers,
|
|
218
|
-
modules: ValidatorFnModules,
|
|
219
|
-
onFatalPeer: OnFatalPeerFn
|
|
220
|
-
): GossipValidatorFn {
|
|
211
|
+
export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: ValidatorFnModules): GossipValidatorFn {
|
|
221
212
|
const {logger, metrics, core} = modules;
|
|
222
213
|
|
|
223
214
|
return async function gossipValidatorFn({
|
|
@@ -269,7 +260,6 @@ export function getGossipValidatorFn(
|
|
|
269
260
|
metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
|
|
270
261
|
const peerAction = rejectPeerAction(type, e.type.code);
|
|
271
262
|
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
272
|
-
if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
|
|
273
263
|
logger.debug(
|
|
274
264
|
`Gossip validation ${type} rejected`,
|
|
275
265
|
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
getParentRootFromSignedBeaconBlockSerialized,
|
|
23
23
|
getPayloadPresentFromPayloadAttestationMessageSerialized,
|
|
24
24
|
} from "../../util/sszBytes.js";
|
|
25
|
-
import {INetworkCore} from "../core/index.js";
|
|
26
25
|
import {NetworkEvent, NetworkEventBus} from "../events.js";
|
|
27
26
|
import {
|
|
28
27
|
GossipHandlers,
|
|
@@ -35,12 +34,7 @@ import {MAX_PEERS_PER_ROOT} from "./constants.js";
|
|
|
35
34
|
import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
|
|
36
35
|
import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
|
|
37
36
|
import {createGossipQueues} from "./gossipQueues/index.js";
|
|
38
|
-
import {
|
|
39
|
-
ValidatorFnModules,
|
|
40
|
-
getGossipValidatorBatchFn,
|
|
41
|
-
getGossipValidatorFn,
|
|
42
|
-
rejectPeerAction,
|
|
43
|
-
} from "./gossipValidatorFn.js";
|
|
37
|
+
import {ValidatorFnModules, getGossipValidatorBatchFn, getGossipValidatorFn} from "./gossipValidatorFn.js";
|
|
44
38
|
import {PendingGossipsubMessage} from "./types.js";
|
|
45
39
|
|
|
46
40
|
export * from "./types.js";
|
|
@@ -108,12 +102,6 @@ type SearchedRootEntry = {
|
|
|
108
102
|
*/
|
|
109
103
|
const DEFAULT_EARLIEST_PERMISSIBLE_SLOT_DISTANCE = 32;
|
|
110
104
|
|
|
111
|
-
/**
|
|
112
|
-
* The network worker bans a Fatal peer only on its next PeerManager heartbeat (30s). Until then the main thread
|
|
113
|
-
* would keep validating that peer's messages, so drop them here. 3 slots (36s) outlasts one heartbeat.
|
|
114
|
-
*/
|
|
115
|
-
export const FATAL_PEER_EXPIRY_SLOTS = 3;
|
|
116
|
-
|
|
117
105
|
type WorkOpts = {
|
|
118
106
|
bypassQueue?: boolean;
|
|
119
107
|
};
|
|
@@ -244,7 +232,6 @@ type SearchUnknownRootTarget =
|
|
|
244
232
|
*/
|
|
245
233
|
export class NetworkProcessor {
|
|
246
234
|
private readonly chain: IBeaconChain;
|
|
247
|
-
private readonly core: INetworkCore;
|
|
248
235
|
private readonly events: NetworkEventBus;
|
|
249
236
|
private readonly logger: Logger;
|
|
250
237
|
private readonly metrics: Metrics | null;
|
|
@@ -269,31 +256,23 @@ export class NetworkProcessor {
|
|
|
269
256
|
() => new MapDef<RootHex, SearchedRootEntry>(() => ({}))
|
|
270
257
|
);
|
|
271
258
|
private bufferedRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
|
|
272
|
-
/** peer -> clock slot when it was Fatal'ed */
|
|
273
|
-
private readonly fatalPeers = new Map<PeerIdStr, Slot>();
|
|
274
259
|
|
|
275
260
|
constructor(
|
|
276
261
|
modules: NetworkProcessorModules,
|
|
277
262
|
private readonly opts: NetworkProcessorOpts
|
|
278
263
|
) {
|
|
279
|
-
const {chain,
|
|
264
|
+
const {chain, events, logger, metrics} = modules;
|
|
280
265
|
this.chain = chain;
|
|
281
|
-
this.core = core;
|
|
282
266
|
this.events = events;
|
|
283
267
|
this.metrics = metrics;
|
|
284
268
|
this.logger = logger;
|
|
285
269
|
this.events = events;
|
|
286
270
|
this.gossipQueues = createGossipQueues();
|
|
287
271
|
this.gossipTopicConcurrency = mapValues(this.gossipQueues, () => 0);
|
|
288
|
-
this.gossipValidatorFn = getGossipValidatorFn(
|
|
289
|
-
modules.gossipHandlers ?? getGossipHandlers(modules, opts),
|
|
290
|
-
modules,
|
|
291
|
-
this.onFatalPeer
|
|
292
|
-
);
|
|
272
|
+
this.gossipValidatorFn = getGossipValidatorFn(modules.gossipHandlers ?? getGossipHandlers(modules, opts), modules);
|
|
293
273
|
this.gossipValidatorBatchFn = getGossipValidatorBatchFn(
|
|
294
274
|
modules.gossipHandlers ?? getGossipHandlers(modules, opts),
|
|
295
|
-
modules
|
|
296
|
-
this.onFatalPeer
|
|
275
|
+
modules
|
|
297
276
|
);
|
|
298
277
|
|
|
299
278
|
events.on(NetworkEvent.pendingGossipsubMessage, this.onPendingGossipsubMessage);
|
|
@@ -427,47 +406,19 @@ export class NetworkProcessor {
|
|
|
427
406
|
}
|
|
428
407
|
}
|
|
429
408
|
|
|
430
|
-
private onFatalPeer = (peer: PeerIdStr): void => {
|
|
431
|
-
this.fatalPeers.set(peer, this.chain.clock.currentSlot);
|
|
432
|
-
};
|
|
433
|
-
|
|
434
409
|
private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
|
|
435
410
|
const topicType = message.topic.type;
|
|
436
|
-
if (this.fatalPeers.has(message.propagationSource)) {
|
|
437
|
-
this.metrics?.networkProcessor.gossipValidationError.inc({topic: topicType, error: GossipErrorCode.FATAL_PEER});
|
|
438
|
-
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
411
|
const extractBlockSlotRootFn = this.extractBlockSlotRootFns[topicType];
|
|
443
|
-
if (extractBlockSlotRootFn === undefined) {
|
|
444
|
-
// some messages don't have slot and root
|
|
445
|
-
this.pushPendingGossipsubMessageToQueue(message);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
412
|
|
|
449
413
|
// 1st extract round: make sure slot is in range and if block root is not available
|
|
450
414
|
// proactively search for it + queue the message
|
|
451
|
-
const slotRoot = extractBlockSlotRootFn
|
|
415
|
+
const slotRoot = extractBlockSlotRootFn
|
|
416
|
+
? extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork)
|
|
417
|
+
: null;
|
|
452
418
|
if (slotRoot === null) {
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const peerAction = rejectPeerAction(topicType, code);
|
|
457
|
-
this.metrics?.networkProcessor.gossipValidationReject.inc({topic: topicType});
|
|
458
|
-
this.core.reportPeer(propagationSource, peerAction, code);
|
|
459
|
-
this.logger.debug(`Gossip validation ${topicType} rejected due to null slot`, {
|
|
460
|
-
peer: propagationSource,
|
|
461
|
-
clientAgent,
|
|
462
|
-
clientVersion,
|
|
463
|
-
peerAction,
|
|
464
|
-
code,
|
|
465
|
-
});
|
|
466
|
-
this.events.emit(NetworkEvent.gossipMessageValidationResult, {
|
|
467
|
-
msgId: message.msgId,
|
|
468
|
-
propagationSource,
|
|
469
|
-
acceptance: TopicValidatorResult.Reject,
|
|
470
|
-
});
|
|
419
|
+
// some messages don't have slot and root
|
|
420
|
+
// if the msg.data is invalid, message will be rejected when deserializing data in later phase (gossipValidatorFn)
|
|
421
|
+
this.pushPendingGossipsubMessageToQueue(message);
|
|
471
422
|
return;
|
|
472
423
|
}
|
|
473
424
|
|
|
@@ -921,10 +872,6 @@ export class NetworkProcessor {
|
|
|
921
872
|
for (const slot of this.bufferedRootsBySlot.keys()) {
|
|
922
873
|
if (slot <= minSlot) this.bufferedRootsBySlot.delete(slot);
|
|
923
874
|
}
|
|
924
|
-
|
|
925
|
-
for (const [peer, slot] of this.fatalPeers) {
|
|
926
|
-
if (clockSlot - slot > FATAL_PEER_EXPIRY_SLOTS) this.fatalPeers.delete(peer);
|
|
927
|
-
}
|
|
928
875
|
};
|
|
929
876
|
|
|
930
877
|
private executeWork(): void {
|
|
@@ -980,32 +927,6 @@ export class NetworkProcessor {
|
|
|
980
927
|
private async processPendingGossipsubMessage(
|
|
981
928
|
messageOrArray: PendingGossipsubMessage | PendingGossipsubMessage[]
|
|
982
929
|
): Promise<void> {
|
|
983
|
-
// drop messages from peers Fatal'ed after these were queued or started awaiting a block/envelope
|
|
984
|
-
// No need to report the dropped jobs to gossip. They will be eventually pruned from the mcache
|
|
985
|
-
if (this.fatalPeers.size > 0) {
|
|
986
|
-
if (Array.isArray(messageOrArray)) {
|
|
987
|
-
const allowed: PendingGossipsubMessage[] = [];
|
|
988
|
-
for (const msg of messageOrArray) {
|
|
989
|
-
if (this.fatalPeers.has(msg.propagationSource)) {
|
|
990
|
-
this.metrics?.networkProcessor.gossipValidationError.inc({
|
|
991
|
-
topic: msg.topic.type,
|
|
992
|
-
error: GossipErrorCode.FATAL_PEER,
|
|
993
|
-
});
|
|
994
|
-
} else {
|
|
995
|
-
allowed.push(msg);
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
if (allowed.length === 0) return;
|
|
999
|
-
messageOrArray = allowed;
|
|
1000
|
-
} else if (this.fatalPeers.has(messageOrArray.propagationSource)) {
|
|
1001
|
-
this.metrics?.networkProcessor.gossipValidationError.inc({
|
|
1002
|
-
topic: messageOrArray.topic.type,
|
|
1003
|
-
error: GossipErrorCode.FATAL_PEER,
|
|
1004
|
-
});
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
930
|
const nowSec = Date.now() / 1000;
|
|
1010
931
|
if (Array.isArray(messageOrArray)) {
|
|
1011
932
|
for (const msg of messageOrArray) {
|
|
@@ -53,7 +53,7 @@ export async function* onExecutionPayloadEnvelopesByRoot(
|
|
|
53
53
|
const batch = requests.slice(i, i + MAX_BODIES_PER_REQUEST);
|
|
54
54
|
let envelopesBytes: (Uint8Array | null)[];
|
|
55
55
|
try {
|
|
56
|
-
// By-root allows omission, so
|
|
56
|
+
// By-root allows omission, so a mismatched envelope is left out rather than failing the response.
|
|
57
57
|
envelopesBytes = await chain.getSerializedExecutionPayloadEnvelopes(batch, "omit");
|
|
58
58
|
} catch (e) {
|
|
59
59
|
if (e instanceof EnvelopeReconstructionError) {
|
package/src/sync/range/range.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {IBeaconStateViewGloas, computeStartSlotAtEpoch, isStatePostGloas} from "
|
|
|
5
5
|
import {Epoch, Status, fulu} from "@lodestar/types";
|
|
6
6
|
import {Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
7
7
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
8
|
-
import {PayloadError, PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
9
8
|
import {AttestationImportOpt, ImportBlockOpts} from "../../chain/blocks/index.js";
|
|
10
9
|
import {assertLinearChainSegment} from "../../chain/blocks/utils/chainSegment.js";
|
|
11
10
|
import {BlockError} from "../../chain/errors/index.js";
|
|
@@ -208,50 +207,22 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
208
207
|
blsVerifyOnMainThread: false,
|
|
209
208
|
};
|
|
210
209
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
await this.chain.processExecutionPayload(payloadEnvelope);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
} else {
|
|
222
|
-
const {orphaned, skipped} = await this.chain.processChainSegment(blocks, payloadEnvelopes, flags);
|
|
223
|
-
// We log orphaned payloads to work with different clients
|
|
224
|
-
// in the future, consider applying penalties in certain conditions
|
|
225
|
-
// make sure the payload source was from range sync in that case
|
|
226
|
-
for (const {slot, payloadEnvelopeInput} of orphaned) {
|
|
227
|
-
const {peerIdStr, source} = payloadEnvelopeInput.getPayloadEnvelopeSource();
|
|
228
|
-
let client = "unknown";
|
|
229
|
-
if (peerIdStr !== undefined) {
|
|
230
|
-
try {
|
|
231
|
-
client = this.getConnectedPeerSyncMeta(peerIdStr).client;
|
|
232
|
-
} catch {
|
|
233
|
-
// peer disconnected since serving the envelope, keep "unknown"
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
this.logger.debug("Orphaned payload envelope in range sync batch", {
|
|
237
|
-
slot,
|
|
238
|
-
root: payloadEnvelopeInput.blockRootHex,
|
|
239
|
-
peer: peerIdStr ?? "unknown",
|
|
240
|
-
source,
|
|
241
|
-
client,
|
|
242
|
-
skipped,
|
|
243
|
-
});
|
|
210
|
+
if (this.opts?.disableProcessAsChainSegment) {
|
|
211
|
+
// Should only be used for debugging or testing
|
|
212
|
+
for (const block of blocks) {
|
|
213
|
+
await this.chain.processBlock(block, flags);
|
|
214
|
+
const payloadEnvelope = payloadEnvelopes?.get(block.slot);
|
|
215
|
+
if (payloadEnvelope?.hasPayloadEnvelope()) {
|
|
216
|
+
await this.chain.processExecutionPayload(payloadEnvelope);
|
|
244
217
|
}
|
|
245
218
|
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const {payloadInput} = e;
|
|
254
|
-
const {peerIdStr, source} = payloadInput.getPayloadEnvelopeSource();
|
|
219
|
+
} else {
|
|
220
|
+
const {orphaned, skipped} = await this.chain.processChainSegment(blocks, payloadEnvelopes, flags);
|
|
221
|
+
// We log orphaned payloads to work with different clients
|
|
222
|
+
// in the future, consider applying penalties in certain conditions
|
|
223
|
+
// make sure the payload source was from range sync in that case
|
|
224
|
+
for (const {slot, payloadEnvelopeInput} of orphaned) {
|
|
225
|
+
const {peerIdStr, source} = payloadEnvelopeInput.getPayloadEnvelopeSource();
|
|
255
226
|
let client = "unknown";
|
|
256
227
|
if (peerIdStr !== undefined) {
|
|
257
228
|
try {
|
|
@@ -260,18 +231,15 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
260
231
|
// peer disconnected since serving the envelope, keep "unknown"
|
|
261
232
|
}
|
|
262
233
|
}
|
|
263
|
-
this.logger.debug("
|
|
264
|
-
slot
|
|
265
|
-
root:
|
|
234
|
+
this.logger.debug("Orphaned payload envelope in range sync batch", {
|
|
235
|
+
slot,
|
|
236
|
+
root: payloadEnvelopeInput.blockRootHex,
|
|
266
237
|
peer: peerIdStr ?? "unknown",
|
|
267
238
|
source,
|
|
268
239
|
client,
|
|
269
|
-
|
|
240
|
+
skipped,
|
|
270
241
|
});
|
|
271
|
-
// The batch is downloaded again, it would otherwise reuse the cached envelope and fail the same way
|
|
272
|
-
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
|
|
273
242
|
}
|
|
274
|
-
throw e;
|
|
275
243
|
}
|
|
276
244
|
};
|
|
277
245
|
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -1426,23 +1426,20 @@ export class BlockInputSync {
|
|
|
1426
1426
|
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
1427
1427
|
break;
|
|
1428
1428
|
|
|
1429
|
-
// The invalid envelope is evicted from the seen cache in the cases below, otherwise the next
|
|
1430
|
-
// attempt would reuse it instead of fetching another envelope from peers
|
|
1431
1429
|
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1432
1430
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1433
|
-
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1434
1431
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
|
|
1435
1432
|
break;
|
|
1436
1433
|
|
|
1437
1434
|
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1438
1435
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1439
|
-
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1440
1436
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
|
|
1441
1437
|
break;
|
|
1442
1438
|
|
|
1443
1439
|
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
1440
|
+
// TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
|
|
1441
|
+
// reintroducing envelope replacement / recreation flows.
|
|
1444
1442
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1445
|
-
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1446
1443
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
|
|
1447
1444
|
break;
|
|
1448
1445
|
|
|
@@ -1882,6 +1879,8 @@ export class BlockInputSync {
|
|
|
1882
1879
|
|
|
1883
1880
|
// Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
|
|
1884
1881
|
private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
|
|
1882
|
+
// Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
|
|
1883
|
+
// later validation/finalization logic decides when it can leave memory.
|
|
1885
1884
|
if (this.pendingPayloads.delete(rootHex)) {
|
|
1886
1885
|
this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
|
|
1887
1886
|
}
|
package/src/util/execution.ts
CHANGED
|
@@ -266,15 +266,14 @@ type SlotEnvelopeBytes = {slot: Slot; envelopeBytes: Uint8Array};
|
|
|
266
266
|
|
|
267
267
|
type RangeEntry = ArchivedEnvelope & {slot: Slot};
|
|
268
268
|
|
|
269
|
-
/** What
|
|
270
|
-
export type
|
|
269
|
+
/** What a body root mismatch means on a given serving path */
|
|
270
|
+
export type ReconstructMismatchPolicy = "throw" | "omit";
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
export type RebuildMiss = {slot: Slot; reason: "unavailable" | "mismatch"; error: EnvelopeReconstructionError};
|
|
272
|
+
export type RebuildMiss =
|
|
273
|
+
/** EL does not have the block, or has pruned its block access list */
|
|
274
|
+
| {slot: Slot; reason: "unavailable"}
|
|
275
|
+
/** An EL body does not hash to its stored root (local inconsistency) */
|
|
276
|
+
| {slot: Slot; reason: "mismatch"; error: EnvelopeReconstructionError};
|
|
278
277
|
|
|
279
278
|
/**
|
|
280
279
|
* Stream finalized envelopes over [startSlot, endSlot) as serialized bytes, rebuilding header
|
|
@@ -406,14 +405,7 @@ async function reconstructEnvelopesBatch(
|
|
|
406
405
|
// A zero-length block access list cannot be valid, RLP encodes an empty list as 0xc0
|
|
407
406
|
if (body == null || body.withdrawals == null || body.blockAccessList == null || body.blockAccessList.length === 0) {
|
|
408
407
|
metrics?.payloadEnvelopeReconstruction.envelopes.inc({result: "unavailable"});
|
|
409
|
-
return {
|
|
410
|
-
slot,
|
|
411
|
-
reason: "unavailable",
|
|
412
|
-
error: new EnvelopeReconstructionError(
|
|
413
|
-
{code: EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE, slot},
|
|
414
|
-
`execution client cannot serve the payload body or block access list for archived envelope slot=${slot}`
|
|
415
|
-
),
|
|
416
|
-
};
|
|
408
|
+
return {slot, reason: "unavailable"};
|
|
417
409
|
}
|
|
418
410
|
try {
|
|
419
411
|
const envelope = signedHeaderEnvelopeToFull(headerEnvelope, {
|