@lodestar/beacon-node 1.50.0-dev.11028e782c → 1.50.0-dev.199b9d3a1f
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 +6 -1
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/chain.d.ts +5 -4
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +11 -13
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
- package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.js +2 -0
- package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +3 -1
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +2 -0
- 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 +5 -2
- 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 +6 -2
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/execution/engine/disabled.d.ts +0 -2
- package/lib/execution/engine/disabled.d.ts.map +1 -1
- package/lib/execution/engine/disabled.js +0 -6
- package/lib/execution/engine/disabled.js.map +1 -1
- package/lib/execution/engine/http.d.ts +1 -3
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +2 -17
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +2 -4
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/mock.d.ts +0 -2
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +0 -8
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/types.d.ts +0 -8
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +3 -0
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +7 -3
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +9 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +77 -10
- 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/batch.d.ts +6 -0
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +10 -0
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +50 -18
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +5 -4
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/execution.d.ts +9 -11
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +5 -1
- package/lib/util/execution.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +7 -4
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
- package/src/chain/chain.ts +14 -16
- package/src/chain/errors/envelopeReconstructionError.ts +3 -0
- package/src/chain/errors/gossipValidation.ts +2 -0
- package/src/chain/interface.ts +2 -2
- package/src/chain/prepareNextSlot.ts +6 -3
- package/src/chain/produceBlock/produceBlockBody.ts +8 -1
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
- package/src/execution/engine/disabled.ts +0 -8
- package/src/execution/engine/http.ts +1 -30
- package/src/execution/engine/interface.ts +2 -6
- package/src/execution/engine/mock.ts +1 -19
- package/src/execution/engine/types.ts +0 -10
- package/src/network/processor/gossipHandlers.ts +3 -0
- package/src/network/processor/gossipValidatorFn.ts +13 -3
- package/src/network/processor/index.ts +89 -10
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
- package/src/sync/range/batch.ts +11 -0
- package/src/sync/range/range.ts +50 -18
- package/src/sync/unknownBlock.ts +5 -4
- package/src/util/execution.ts +16 -8
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/ChainSafe/lodestar/issues"
|
|
13
13
|
},
|
|
14
|
-
"version": "1.50.0-dev.
|
|
14
|
+
"version": "1.50.0-dev.199b9d3a1f",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
@@ -127,17 +127,17 @@
|
|
|
127
127
|
"@libp2p/prometheus-metrics": "^5.0.28",
|
|
128
128
|
"@libp2p/tcp": "^11.0.27",
|
|
129
129
|
"@libp2p/utils": "^7.4.0",
|
|
130
|
-
"@lodestar/api": "^1.50.0-dev.
|
|
131
|
-
"@lodestar/config": "^1.50.0-dev.
|
|
132
|
-
"@lodestar/db": "^1.50.0-dev.
|
|
133
|
-
"@lodestar/fork-choice": "^1.50.0-dev.
|
|
134
|
-
"@lodestar/logger": "^1.50.0-dev.
|
|
135
|
-
"@lodestar/params": "^1.50.0-dev.
|
|
136
|
-
"@lodestar/reqresp": "^1.50.0-dev.
|
|
137
|
-
"@lodestar/state-transition": "^1.50.0-dev.
|
|
138
|
-
"@lodestar/types": "^1.50.0-dev.
|
|
139
|
-
"@lodestar/utils": "^1.50.0-dev.
|
|
140
|
-
"@lodestar/validator": "^1.50.0-dev.
|
|
130
|
+
"@lodestar/api": "^1.50.0-dev.199b9d3a1f",
|
|
131
|
+
"@lodestar/config": "^1.50.0-dev.199b9d3a1f",
|
|
132
|
+
"@lodestar/db": "^1.50.0-dev.199b9d3a1f",
|
|
133
|
+
"@lodestar/fork-choice": "^1.50.0-dev.199b9d3a1f",
|
|
134
|
+
"@lodestar/logger": "^1.50.0-dev.199b9d3a1f",
|
|
135
|
+
"@lodestar/params": "^1.50.0-dev.199b9d3a1f",
|
|
136
|
+
"@lodestar/reqresp": "^1.50.0-dev.199b9d3a1f",
|
|
137
|
+
"@lodestar/state-transition": "^1.50.0-dev.199b9d3a1f",
|
|
138
|
+
"@lodestar/types": "^1.50.0-dev.199b9d3a1f",
|
|
139
|
+
"@lodestar/utils": "^1.50.0-dev.199b9d3a1f",
|
|
140
|
+
"@lodestar/validator": "^1.50.0-dev.199b9d3a1f",
|
|
141
141
|
"@multiformats/multiaddr": "^13.0.1",
|
|
142
142
|
"datastore-core": "^12.0.1",
|
|
143
143
|
"datastore-level": "^13.0.1",
|
|
@@ -158,8 +158,8 @@
|
|
|
158
158
|
"devDependencies": {
|
|
159
159
|
"@libp2p/interface-internal": "^3.1.12",
|
|
160
160
|
"@libp2p/logger": "^6.2.13",
|
|
161
|
-
"@lodestar/spec-test-util": "^1.50.0-dev.
|
|
162
|
-
"@lodestar/test-utils": "^1.50.0-dev.
|
|
161
|
+
"@lodestar/spec-test-util": "^1.50.0-dev.199b9d3a1f",
|
|
162
|
+
"@lodestar/test-utils": "^1.50.0-dev.199b9d3a1f",
|
|
163
163
|
"@types/js-yaml": "^4.0.5",
|
|
164
164
|
"@types/qs": "^6.9.7",
|
|
165
165
|
"@types/tmp": "^0.2.3",
|
|
@@ -174,5 +174,5 @@
|
|
|
174
174
|
"beacon",
|
|
175
175
|
"blockchain"
|
|
176
176
|
],
|
|
177
|
-
"gitHead": "
|
|
177
|
+
"gitHead": "82a54df5956f25254f0d96afe6eae94d93554b5e"
|
|
178
178
|
}
|
|
@@ -1118,10 +1118,13 @@ export function getBeaconBlockApi({
|
|
|
1118
1118
|
: await chain.getExecutionPayloadEnvelope(slot, blockRootHex);
|
|
1119
1119
|
} catch (e) {
|
|
1120
1120
|
if (e instanceof EnvelopeReconstructionError) {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1121
|
+
// The envelope is archived but our EL cannot serve it right now (EL down, or it does not
|
|
1122
|
+
// have the body / block access list): 503 so clients retry or ask another node, rather than
|
|
1123
|
+
// 500 for what is not a beacon node fault. A body root mismatch is a local inconsistency.
|
|
1124
|
+
const unavailable =
|
|
1125
|
+
e.type.code === EnvelopeReconstructionErrorCode.ENGINE_UNAVAILABLE ||
|
|
1126
|
+
e.type.code === EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE;
|
|
1127
|
+
throw new ApiError(unavailable ? 503 : 500, `Failed to reconstruct execution payload envelope: ${e.message}`);
|
|
1125
1128
|
}
|
|
1126
1129
|
throw e;
|
|
1127
1130
|
}
|
|
@@ -22,8 +22,9 @@ export enum PayloadEnvelopeInputSource {
|
|
|
22
22
|
* - finalized: below the finalized slot
|
|
23
23
|
* - prune: explicit prune by root
|
|
24
24
|
* - cap: insertion-order backstop cap (MAX_PAYLOAD_ENVELOPE_INPUT_CACHE_SIZE)
|
|
25
|
+
* - invalid: the cached envelope failed import with a definitive invalid verdict
|
|
25
26
|
*/
|
|
26
|
-
export type PayloadEnvelopeInputPruneReason = "belowParent" | "finalized" | "remove" | "cap";
|
|
27
|
+
export type PayloadEnvelopeInputPruneReason = "belowParent" | "finalized" | "remove" | "cap" | "invalid";
|
|
27
28
|
|
|
28
29
|
export type SourceMeta = {
|
|
29
30
|
source: PayloadEnvelopeInputSource;
|
package/src/chain/chain.ts
CHANGED
|
@@ -71,7 +71,7 @@ import {BufferPool} from "../util/bufferPool.js";
|
|
|
71
71
|
import {Clock, ClockEvent, IClock} from "../util/clock.js";
|
|
72
72
|
import {CustodyConfig, getValidatorsCustodyRequirement} from "../util/dataColumns.js";
|
|
73
73
|
import {callInNextEventLoop} from "../util/eventLoop.js";
|
|
74
|
-
import {
|
|
74
|
+
import {ReconstructMissPolicy, isRebuildMiss, reconstructExecutionPayloadEnvelopes} from "../util/execution.js";
|
|
75
75
|
import {ensureDir, writeIfNotExist} from "../util/file.js";
|
|
76
76
|
import {isOptimisticBlock} from "../util/forkChoice.js";
|
|
77
77
|
import {JobItemQueue} from "../util/queue/itemQueue.js";
|
|
@@ -934,12 +934,13 @@ export class BeaconChain implements IBeaconChain {
|
|
|
934
934
|
|
|
935
935
|
/**
|
|
936
936
|
* Batch variant: archived header envelopes are rebuilt 32 per EL round-trip. Aligned with `requests`.
|
|
937
|
-
*
|
|
938
|
-
* returns null for that entry, for
|
|
937
|
+
* An archived envelope that cannot be rebuilt (EL-unavailable body or body root mismatch) is not the
|
|
938
|
+
* same as an unknown one: `"throw"` surfaces it, `"omit"` returns null for that entry, for
|
|
939
|
+
* peer-facing paths where the spec allows omission.
|
|
939
940
|
*/
|
|
940
941
|
async getSerializedExecutionPayloadEnvelopes(
|
|
941
942
|
requests: {blockSlot: Slot; blockRootHex: RootHex}[],
|
|
942
|
-
|
|
943
|
+
onMiss: ReconstructMissPolicy = "throw"
|
|
943
944
|
): Promise<(Uint8Array | null)[]> {
|
|
944
945
|
const out: (Uint8Array | null)[] = new Array(requests.length).fill(null);
|
|
945
946
|
const headerEnvelopes: gloas.SignedExecutionPayloadHeaderEnvelope[] = [];
|
|
@@ -978,14 +979,14 @@ export class BeaconChain implements IBeaconChain {
|
|
|
978
979
|
for (let j = 0; j < rebuilt.length; j++) {
|
|
979
980
|
const result = rebuilt[j];
|
|
980
981
|
if (isRebuildMiss(result)) {
|
|
981
|
-
if (
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
"Archived envelope failed body root check against EL bodies"
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
982
|
+
if (onMiss === "throw") throw result.error;
|
|
983
|
+
this.logger.debug(
|
|
984
|
+
result.reason === "mismatch"
|
|
985
|
+
? "Archived envelope failed body root check against EL bodies"
|
|
986
|
+
: "EL cannot serve bodies for archived envelope, omitting",
|
|
987
|
+
{slot: result.slot},
|
|
988
|
+
result.error
|
|
989
|
+
);
|
|
989
990
|
continue;
|
|
990
991
|
}
|
|
991
992
|
out[headerEnvelopeIdxs[j]] = ssz.gloas.SignedExecutionPayloadEnvelope.serialize(result);
|
|
@@ -1016,10 +1017,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1016
1017
|
const [result] = await reconstructExecutionPayloadEnvelopes(this.executionEngine, this.metrics, [
|
|
1017
1018
|
archived.headerEnvelope,
|
|
1018
1019
|
]);
|
|
1019
|
-
if (isRebuildMiss(result))
|
|
1020
|
-
if (result.reason === "mismatch") throw result.error;
|
|
1021
|
-
return null;
|
|
1022
|
-
}
|
|
1020
|
+
if (isRebuildMiss(result)) throw result.error;
|
|
1023
1021
|
return result;
|
|
1024
1022
|
}
|
|
1025
1023
|
|
|
@@ -4,6 +4,8 @@ import {LodestarError} from "@lodestar/utils";
|
|
|
4
4
|
export enum EnvelopeReconstructionErrorCode {
|
|
5
5
|
/** The EL failed to answer engine_getPayloadBodiesByHashV2 (transport / EL down). Transient. */
|
|
6
6
|
ENGINE_UNAVAILABLE = "ENVELOPE_RECONSTRUCTION_ERROR_ENGINE_UNAVAILABLE",
|
|
7
|
+
/** The EL does not have the block or its block access list (pruned, or not yet re-indexed after a restart). May recover. */
|
|
8
|
+
BODY_UNAVAILABLE = "ENVELOPE_RECONSTRUCTION_ERROR_BODY_UNAVAILABLE",
|
|
7
9
|
/** A body returned by the EL does not hash to the root stored in the header envelope. Local DB/EL inconsistency. */
|
|
8
10
|
BODY_ROOT_MISMATCH = "ENVELOPE_RECONSTRUCTION_ERROR_BODY_ROOT_MISMATCH",
|
|
9
11
|
/** A by-range stream stopped short at a slot that cannot be served; the response so far is still valid */
|
|
@@ -12,6 +14,7 @@ export enum EnvelopeReconstructionErrorCode {
|
|
|
12
14
|
|
|
13
15
|
export type EnvelopeReconstructionErrorType =
|
|
14
16
|
| {code: EnvelopeReconstructionErrorCode.ENGINE_UNAVAILABLE}
|
|
17
|
+
| {code: EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE; slot: Slot}
|
|
15
18
|
| {
|
|
16
19
|
code: EnvelopeReconstructionErrorCode.BODY_ROOT_MISMATCH;
|
|
17
20
|
slot: Slot;
|
|
@@ -9,6 +9,8 @@ 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",
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
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 {ReconstructMissPolicy} 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
|
+
onMiss?: ReconstructMissPolicy
|
|
243
243
|
): Promise<(Uint8Array | null)[]>;
|
|
244
244
|
getExecutionPayloadEnvelope(
|
|
245
245
|
blockSlot: Slot,
|
|
@@ -244,6 +244,10 @@ 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
|
+
|
|
247
251
|
let payloadAttributes: PayloadAttributes | undefined;
|
|
248
252
|
// If emitPayloadAttributes is true emit a SSE payloadAttributes event for
|
|
249
253
|
// every slot. Without the flag, only emit the event if we are proposing in the next slot.
|
|
@@ -256,6 +260,8 @@ export class PrepareNextSlotScheduler {
|
|
|
256
260
|
prepareSlot,
|
|
257
261
|
parentBlockRoot: fromHex(updatedHead.blockRoot),
|
|
258
262
|
parentBlockHash,
|
|
263
|
+
safeBlockHash,
|
|
264
|
+
finalizedBlockHash,
|
|
259
265
|
feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
|
|
260
266
|
});
|
|
261
267
|
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
|
|
@@ -267,9 +273,6 @@ export class PrepareNextSlotScheduler {
|
|
|
267
273
|
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
|
|
268
274
|
this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
|
|
269
275
|
|
|
270
|
-
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
271
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
|
|
272
|
-
|
|
273
276
|
// awaiting here instead of throwing an async call because there is no other task
|
|
274
277
|
// left for scheduler and this gives nice semantics to catch and log errors in the
|
|
275
278
|
// try/catch wrapper here.
|
|
@@ -845,6 +845,8 @@ export function getPayloadAttributesForSSE(
|
|
|
845
845
|
prepareSlot,
|
|
846
846
|
parentBlockRoot,
|
|
847
847
|
parentBlockHash,
|
|
848
|
+
safeBlockHash,
|
|
849
|
+
finalizedBlockHash,
|
|
848
850
|
feeRecipient,
|
|
849
851
|
}: {
|
|
850
852
|
/**
|
|
@@ -855,6 +857,8 @@ export function getPayloadAttributesForSSE(
|
|
|
855
857
|
prepareSlot: Slot;
|
|
856
858
|
parentBlockRoot: Root;
|
|
857
859
|
parentBlockHash: Bytes32;
|
|
860
|
+
safeBlockHash: RootHex;
|
|
861
|
+
finalizedBlockHash: RootHex;
|
|
858
862
|
feeRecipient: string;
|
|
859
863
|
}
|
|
860
864
|
): SSEPayloadAttributes {
|
|
@@ -874,7 +878,10 @@ export function getPayloadAttributesForSSE(
|
|
|
874
878
|
payloadAttributes,
|
|
875
879
|
} as SSEPayloadAttributes;
|
|
876
880
|
|
|
877
|
-
if (
|
|
881
|
+
if (isForkPostGloas(fork)) {
|
|
882
|
+
(ssePayloadAttributes as gloas.SSEPayloadAttributes).safeBlockHash = fromHex(safeBlockHash);
|
|
883
|
+
(ssePayloadAttributes as gloas.SSEPayloadAttributes).finalizedBlockHash = fromHex(finalizedBlockHash);
|
|
884
|
+
} else {
|
|
878
885
|
// Removed in Gloas, builders can get the block number from the EL via the block hash if required
|
|
879
886
|
(ssePayloadAttributes as bellatrix.SSEPayloadAttributes).parentBlockNumber = prepareState.payloadBlockNumber;
|
|
880
887
|
}
|
|
@@ -47,8 +47,9 @@ export type SeenPayloadEnvelopeInputModules = {
|
|
|
47
47
|
* Created whenever we have a block because it needs block bid.
|
|
48
48
|
* Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
|
|
49
49
|
* (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* hold more during forks and range sync, or while an entry's payload is not FULL on the head's
|
|
51
|
+
* branch or its columns are still being gathered. Such entries are evicted on a later block
|
|
52
|
+
* import, on finalization or by the size cap.
|
|
52
53
|
*/
|
|
53
54
|
export class SeenPayloadEnvelopeInput {
|
|
54
55
|
private readonly config: ChainForkConfig;
|
|
@@ -264,18 +265,35 @@ export class SeenPayloadEnvelopeInput {
|
|
|
264
265
|
}
|
|
265
266
|
}
|
|
266
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Removes a PayloadEnvelopeInput whose envelope failed import with a definitive invalid verdict. The envelope
|
|
270
|
+
* cannot be replaced in place, sync recreates the entry via `getOrReload()` or `add()` to fetch another one.
|
|
271
|
+
* No-op if the cached entry is a different object, it may already be the recreated one.
|
|
272
|
+
*/
|
|
273
|
+
removeInvalid(payloadInput: PayloadEnvelopeInput): void {
|
|
274
|
+
if (this.payloadInputs.get(payloadInput.blockRootHex) === payloadInput) {
|
|
275
|
+
this.evictPayloadInput(payloadInput, "invalid");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
267
279
|
pruneBelowParent(parentBlock: ProtoBlock): void {
|
|
268
|
-
for (const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
for (const input of this.payloadInputs.values()) {
|
|
281
|
+
if (
|
|
282
|
+
input.slot < parentBlock.slot &&
|
|
283
|
+
// Don't evict while columns are still being gathered: writeDataColumnsToDb awaits the same
|
|
284
|
+
// hasComputedAllData() before persisting. Such entries are pruned by a later call.
|
|
285
|
+
input.hasComputedAllData() &&
|
|
286
|
+
// Only evict once the payload is FULL (revealed/imported) on the parent's branch. On an EMPTY/PENDING
|
|
287
|
+
// branch we may still need to download the FULL envelope (see #9475), and without the entry gossip
|
|
288
|
+
// for the block is ignored until by-root sync reloads it.
|
|
289
|
+
this.forkChoice.isDescendant(
|
|
290
|
+
input.blockRootHex,
|
|
291
|
+
PayloadStatus.FULL,
|
|
292
|
+
parentBlock.blockRoot,
|
|
293
|
+
parentBlock.payloadStatus
|
|
294
|
+
)
|
|
295
|
+
) {
|
|
296
|
+
this.evictPayloadInput(input, "belowParent");
|
|
279
297
|
}
|
|
280
298
|
}
|
|
281
299
|
}
|
|
@@ -21,18 +21,10 @@ export class ExecutionEngineDisabled implements IExecutionEngine {
|
|
|
21
21
|
throw Error("Execution engine disabled");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
getPayloadBodiesByHash(): Promise<never> {
|
|
25
|
-
throw Error("Execution engine disabled");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
24
|
getPayloadBodiesByHashV2(): Promise<never> {
|
|
29
25
|
throw Error("Execution engine disabled");
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
getPayloadBodiesByRange(): Promise<never> {
|
|
33
|
-
throw Error("Execution engine disabled");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
28
|
getBlobs(): Promise<never> {
|
|
37
29
|
throw Error("Execution engine disabled");
|
|
38
30
|
}
|
|
@@ -31,13 +31,11 @@ import {
|
|
|
31
31
|
BLOB_AND_PROOF_V2_RPC_BYTES,
|
|
32
32
|
EngineApiRpcParamTypes,
|
|
33
33
|
EngineApiRpcReturnTypes,
|
|
34
|
-
ExecutionPayloadBody,
|
|
35
34
|
ExecutionPayloadBodyV2,
|
|
36
35
|
assertReqSizeLimit,
|
|
37
36
|
deserializeBlobAndProofs,
|
|
38
37
|
deserializeBlobAndProofsV2,
|
|
39
38
|
deserializeBlobAndProofsV2IntoBytes,
|
|
40
|
-
deserializeExecutionPayloadBody,
|
|
41
39
|
deserializeExecutionPayloadBodyV2,
|
|
42
40
|
parseExecutionPayload,
|
|
43
41
|
serializeBeaconBlockRoot,
|
|
@@ -46,7 +44,7 @@ import {
|
|
|
46
44
|
serializePayloadAttributes,
|
|
47
45
|
serializeVersionedHashes,
|
|
48
46
|
} from "./types.js";
|
|
49
|
-
import {bytesToData, getExecutionEngineState
|
|
47
|
+
import {bytesToData, getExecutionEngineState} from "./utils.js";
|
|
50
48
|
|
|
51
49
|
export type ExecutionEngineModules = {
|
|
52
50
|
signal: AbortSignal;
|
|
@@ -114,7 +112,6 @@ const notifyNewPayloadOpts: ReqOpts = {routeId: "notifyNewPayload"};
|
|
|
114
112
|
const forkchoiceUpdatedV1Opts: ReqOpts = {routeId: "forkchoiceUpdated"};
|
|
115
113
|
const getPayloadOpts: ReqOpts = {routeId: "getPayload"};
|
|
116
114
|
const getPayloadBodiesByHashOpts: ReqOpts = {routeId: "getPayloadBodiesByHash"};
|
|
117
|
-
const getPayloadBodiesByRangeOpts: ReqOpts = {routeId: "getPayloadBodiesByRange"};
|
|
118
115
|
const getBlobsV1Opts: ReqOpts = {routeId: "getBlobsV1"};
|
|
119
116
|
const getBlobsV2Opts: ReqOpts = {routeId: "getBlobsV2"};
|
|
120
117
|
const getClientVersionOpts: ReqOpts = {routeId: "getClientVersion"};
|
|
@@ -468,16 +465,6 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
468
465
|
this.payloadIdCache.prune();
|
|
469
466
|
}
|
|
470
467
|
|
|
471
|
-
async getPayloadBodiesByHash(_fork: ForkName, blockHashes: RootHex[]): Promise<(ExecutionPayloadBody | null)[]> {
|
|
472
|
-
const method = "engine_getPayloadBodiesByHashV1";
|
|
473
|
-
assertReqSizeLimit(blockHashes.length, 32);
|
|
474
|
-
const response = await this.rpc.fetchWithRetries<
|
|
475
|
-
EngineApiRpcReturnTypes[typeof method],
|
|
476
|
-
EngineApiRpcParamTypes[typeof method]
|
|
477
|
-
>({method, params: [blockHashes]}, getPayloadBodiesByHashOpts);
|
|
478
|
-
return response.map(deserializeExecutionPayloadBody);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
468
|
async getPayloadBodiesByHashV2(blockHashes: RootHex[]): Promise<(ExecutionPayloadBodyV2 | null)[]> {
|
|
482
469
|
const method = "engine_getPayloadBodiesByHashV2";
|
|
483
470
|
assertReqSizeLimit(blockHashes.length, 32);
|
|
@@ -488,22 +475,6 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
488
475
|
return response.map(deserializeExecutionPayloadBodyV2);
|
|
489
476
|
}
|
|
490
477
|
|
|
491
|
-
async getPayloadBodiesByRange(
|
|
492
|
-
_fork: ForkName,
|
|
493
|
-
startBlockNumber: number,
|
|
494
|
-
blockCount: number
|
|
495
|
-
): Promise<(ExecutionPayloadBody | null)[]> {
|
|
496
|
-
const method = "engine_getPayloadBodiesByRangeV1";
|
|
497
|
-
assertReqSizeLimit(blockCount, 32);
|
|
498
|
-
const start = numToQuantity(startBlockNumber);
|
|
499
|
-
const count = numToQuantity(blockCount);
|
|
500
|
-
const response = await this.rpc.fetchWithRetries<
|
|
501
|
-
EngineApiRpcReturnTypes[typeof method],
|
|
502
|
-
EngineApiRpcParamTypes[typeof method]
|
|
503
|
-
>({method, params: [start, count]}, getPayloadBodiesByRangeOpts);
|
|
504
|
-
return response.map(deserializeExecutionPayloadBody);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
478
|
async getBlobs(
|
|
508
479
|
fork: ForkPostFulu,
|
|
509
480
|
versionedHashes: VersionedHashes,
|
|
@@ -13,7 +13,7 @@ import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, ca
|
|
|
13
13
|
import {BlobAndProof} from "@lodestar/types/deneb";
|
|
14
14
|
import {BlobAndProofV2} from "@lodestar/types/fulu";
|
|
15
15
|
import {PayloadId, PayloadIdCache, WithdrawalV1} from "./payloadIdCache.js";
|
|
16
|
-
import {
|
|
16
|
+
import {ExecutionPayloadBodyV2} from "./types.js";
|
|
17
17
|
import {DATA} from "./utils.js";
|
|
18
18
|
|
|
19
19
|
export {PayloadIdCache, type PayloadId, type WithdrawalV1};
|
|
@@ -170,13 +170,9 @@ export interface IExecutionEngine {
|
|
|
170
170
|
shouldOverrideBuilder?: boolean;
|
|
171
171
|
}>;
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/** Amsterdam: like V1 but also returns the block access list (null if the EL no longer has it) */
|
|
173
|
+
/** Amsterdam: payload bodies including the block access list (null if the EL no longer has it) */
|
|
176
174
|
getPayloadBodiesByHashV2(blockHash: DATA[]): Promise<(ExecutionPayloadBodyV2 | null)[]>;
|
|
177
175
|
|
|
178
|
-
getPayloadBodiesByRange(fork: ForkName, start: number, count: number): Promise<(ExecutionPayloadBody | null)[]>;
|
|
179
|
-
|
|
180
176
|
getBlobs(
|
|
181
177
|
fork: ForkPostFulu,
|
|
182
178
|
versionedHashes: VersionedHashes,
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
BlobsBundleRpc,
|
|
23
23
|
EngineApiRpcParamTypes,
|
|
24
24
|
EngineApiRpcReturnTypes,
|
|
25
|
-
ExecutionPayloadBodyRpc,
|
|
26
25
|
ExecutionPayloadBodyV2Rpc,
|
|
27
26
|
ExecutionPayloadRpc,
|
|
28
27
|
ExecutionRequestsRpc,
|
|
@@ -50,7 +49,7 @@ type ExecutionBlock = {
|
|
|
50
49
|
blockHash: RootHex;
|
|
51
50
|
timestamp: number;
|
|
52
51
|
blockNumber: number;
|
|
53
|
-
/** Bodies as received via newPayload, served back by
|
|
52
|
+
/** Bodies as received via newPayload, served back by engine_getPayloadBodiesByHashV2 */
|
|
54
53
|
body: ExecutionPayloadBodyV2Rpc;
|
|
55
54
|
};
|
|
56
55
|
|
|
@@ -148,23 +147,13 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
|
|
|
148
147
|
engine_getPayloadV4: this.getPayloadV5.bind(this),
|
|
149
148
|
engine_getPayloadV5: this.getPayloadV5.bind(this),
|
|
150
149
|
engine_getPayloadV6: this.getPayloadV5.bind(this),
|
|
151
|
-
engine_getPayloadBodiesByHashV1: this.getPayloadBodiesByHash.bind(this),
|
|
152
150
|
engine_getPayloadBodiesByHashV2: this.getPayloadBodiesByHashV2.bind(this),
|
|
153
|
-
engine_getPayloadBodiesByRangeV1: this.getPayloadBodiesByRange.bind(this),
|
|
154
151
|
engine_getClientVersionV1: this.getClientVersionV1.bind(this),
|
|
155
152
|
engine_getBlobsV1: this.getBlobs.bind(this),
|
|
156
153
|
engine_getBlobsV2: this.getBlobsV2.bind(this),
|
|
157
154
|
};
|
|
158
155
|
}
|
|
159
156
|
|
|
160
|
-
private getPayloadBodiesByHash(
|
|
161
|
-
blockHashes: EngineApiRpcParamTypes["engine_getPayloadBodiesByHashV1"][0]
|
|
162
|
-
): EngineApiRpcReturnTypes["engine_getPayloadBodiesByHashV1"] {
|
|
163
|
-
return this.getPayloadBodiesByHashV2(blockHashes).map((body) =>
|
|
164
|
-
body ? {transactions: body.transactions, withdrawals: body.withdrawals} : null
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
157
|
private getPayloadBodiesByHashV2(
|
|
169
158
|
blockHashes: EngineApiRpcParamTypes["engine_getPayloadBodiesByHashV2"][0]
|
|
170
159
|
): EngineApiRpcReturnTypes["engine_getPayloadBodiesByHashV2"] {
|
|
@@ -172,13 +161,6 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
|
|
|
172
161
|
return blockHashes.map((hash) => this.validBlocks.get(hash)?.body ?? null);
|
|
173
162
|
}
|
|
174
163
|
|
|
175
|
-
private getPayloadBodiesByRange(
|
|
176
|
-
_start: EngineApiRpcParamTypes["engine_getPayloadBodiesByRangeV1"][0],
|
|
177
|
-
_count: EngineApiRpcParamTypes["engine_getPayloadBodiesByRangeV1"][1]
|
|
178
|
-
): EngineApiRpcReturnTypes["engine_getPayloadBodiesByRangeV1"] {
|
|
179
|
-
return [] as ExecutionPayloadBodyRpc[];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
164
|
/**
|
|
183
165
|
* Mock manipulator to add predefined responses before execution engine client calls
|
|
184
166
|
*/
|
|
@@ -88,15 +88,8 @@ export type EngineApiRpcParamTypes = {
|
|
|
88
88
|
/**
|
|
89
89
|
* 1. Array of DATA - Array of block_hash field values of the ExecutionPayload structure
|
|
90
90
|
* */
|
|
91
|
-
engine_getPayloadBodiesByHashV1: DATA[][];
|
|
92
91
|
engine_getPayloadBodiesByHashV2: DATA[][];
|
|
93
92
|
|
|
94
|
-
/**
|
|
95
|
-
* 1. start: QUANTITY, 64 bits - Starting block number
|
|
96
|
-
* 2. count: QUANTITY, 64 bits - Number of blocks to return
|
|
97
|
-
*/
|
|
98
|
-
engine_getPayloadBodiesByRangeV1: [start: QUANTITY, count: QUANTITY];
|
|
99
|
-
|
|
100
93
|
/**
|
|
101
94
|
* Object - Instance of ClientVersion
|
|
102
95
|
*/
|
|
@@ -148,11 +141,8 @@ export type EngineApiRpcReturnTypes = {
|
|
|
148
141
|
engine_getPayloadV5: ExecutionPayloadResponse;
|
|
149
142
|
engine_getPayloadV6: ExecutionPayloadResponse;
|
|
150
143
|
|
|
151
|
-
engine_getPayloadBodiesByHashV1: (ExecutionPayloadBodyRpc | null)[];
|
|
152
144
|
engine_getPayloadBodiesByHashV2: (ExecutionPayloadBodyV2Rpc | null)[];
|
|
153
145
|
|
|
154
|
-
engine_getPayloadBodiesByRangeV1: (ExecutionPayloadBodyRpc | null)[];
|
|
155
|
-
|
|
156
146
|
engine_getClientVersionV1: ClientVersionRpc[];
|
|
157
147
|
|
|
158
148
|
engine_getBlobsV1: (BlobAndProofRpc | null)[];
|
|
@@ -1341,6 +1341,9 @@ 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);
|
|
1344
1347
|
// Misbehaving peer, but could highlight an issue in another client
|
|
1345
1348
|
logLevel = LogLevel.warn;
|
|
1346
1349
|
break;
|
|
@@ -20,6 +20,7 @@ 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";
|
|
23
24
|
import {INetworkCore} from "../core/index.js";
|
|
24
25
|
import {
|
|
25
26
|
BatchGossipHandlerFn,
|
|
@@ -40,6 +41,8 @@ export type ValidatorFnModules = {
|
|
|
40
41
|
core: INetworkCore;
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
export type OnFatalPeerFn = (peer: PeerIdStr) => void;
|
|
45
|
+
|
|
43
46
|
type RejectPeerActionRule = {default: PeerAction; byCode?: Record<string, PeerAction>};
|
|
44
47
|
|
|
45
48
|
/**
|
|
@@ -112,7 +115,7 @@ const gossipRejectPeerAction: Record<GossipType, RejectPeerActionRule> = {
|
|
|
112
115
|
[GossipType.light_client_optimistic_update]: {default: PeerAction.HighToleranceError},
|
|
113
116
|
};
|
|
114
117
|
|
|
115
|
-
function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
118
|
+
export function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
116
119
|
const rule = gossipRejectPeerAction[type];
|
|
117
120
|
return rule.byCode?.[code] ?? rule.default;
|
|
118
121
|
}
|
|
@@ -123,7 +126,8 @@ function rejectPeerAction(type: GossipType, code: string): PeerAction {
|
|
|
123
126
|
*/
|
|
124
127
|
export function getGossipValidatorBatchFn(
|
|
125
128
|
gossipHandlers: GossipHandlers,
|
|
126
|
-
modules: ValidatorFnModules
|
|
129
|
+
modules: ValidatorFnModules,
|
|
130
|
+
onFatalPeer: OnFatalPeerFn
|
|
127
131
|
): GossipValidatorBatchFn {
|
|
128
132
|
const {logger, metrics, core} = modules;
|
|
129
133
|
|
|
@@ -173,6 +177,7 @@ export function getGossipValidatorBatchFn(
|
|
|
173
177
|
metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
|
|
174
178
|
const peerAction = rejectPeerAction(type, e.type.code);
|
|
175
179
|
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
180
|
+
if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
|
|
176
181
|
logger.debug(
|
|
177
182
|
`Gossip validation ${type} rejected`,
|
|
178
183
|
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|
|
@@ -208,7 +213,11 @@ export function getGossipValidatorBatchFn(
|
|
|
208
213
|
*
|
|
209
214
|
* @see getGossipHandlers for reasoning on why GossipHandlerFn are used for gossip validation.
|
|
210
215
|
*/
|
|
211
|
-
export function getGossipValidatorFn(
|
|
216
|
+
export function getGossipValidatorFn(
|
|
217
|
+
gossipHandlers: GossipHandlers,
|
|
218
|
+
modules: ValidatorFnModules,
|
|
219
|
+
onFatalPeer: OnFatalPeerFn
|
|
220
|
+
): GossipValidatorFn {
|
|
212
221
|
const {logger, metrics, core} = modules;
|
|
213
222
|
|
|
214
223
|
return async function gossipValidatorFn({
|
|
@@ -260,6 +269,7 @@ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: Va
|
|
|
260
269
|
metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
|
|
261
270
|
const peerAction = rejectPeerAction(type, e.type.code);
|
|
262
271
|
core.reportPeer(propagationSource, peerAction, e.type.code);
|
|
272
|
+
if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
|
|
263
273
|
logger.debug(
|
|
264
274
|
`Gossip validation ${type} rejected`,
|
|
265
275
|
{peer: propagationSource, clientAgent, clientVersion, peerAction},
|