@lodestar/beacon-node 1.45.0-dev.d7859e978b → 1.45.0-dev.e6c77a6488
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/index.d.ts +1 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/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 +6 -43
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +48 -5
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +13 -3
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +48 -4
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +2 -2
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +2 -2
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/proof/index.d.ts +1 -1
- package/lib/api/impl/proof/index.d.ts.map +1 -1
- package/lib/api/impl/proof/index.js +2 -2
- package/lib/api/impl/proof/index.js.map +1 -1
- package/lib/api/impl/utils.d.ts +23 -0
- package/lib/api/impl/utils.d.ts.map +1 -1
- package/lib/api/impl/utils.js +44 -1
- package/lib/api/impl/utils.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +0 -14
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +62 -61
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +10 -8
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -2
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +17 -17
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/chain.d.ts +1 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +1 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/interface.d.ts +1 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/options.d.ts +1 -0
- 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/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +4 -12
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +0 -1
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +0 -1
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +1 -0
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +7 -0
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/shufflingCache.d.ts +6 -0
- package/lib/chain/shufflingCache.d.ts.map +1 -1
- package/lib/chain/shufflingCache.js +21 -6
- package/lib/chain/shufflingCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -2
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -10
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +14 -5
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/network/core/metrics.d.ts +65 -0
- package/lib/network/core/metrics.d.ts.map +1 -1
- package/lib/network/core/metrics.js +94 -0
- package/lib/network/core/metrics.js.map +1 -1
- package/lib/network/discv5/utils.d.ts.map +1 -1
- package/lib/network/discv5/utils.js +12 -3
- package/lib/network/discv5/utils.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +7 -0
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +2 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +6 -1
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +23 -3
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/peers/discover.d.ts.map +1 -1
- package/lib/network/peers/discover.js +17 -12
- package/lib/network/peers/discover.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +10 -0
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/score/score.d.ts +3 -1
- package/lib/network/peers/score/score.d.ts.map +1 -1
- package/lib/network/peers/score/score.js +6 -1
- package/lib/network/peers/score/score.js.map +1 -1
- package/lib/network/peers/score/store.d.ts.map +1 -1
- package/lib/network/peers/score/store.js +6 -1
- package/lib/network/peers/score/store.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.js +14 -0
- package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +32 -16
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/sync/range/chain.d.ts +4 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +3 -3
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +10 -0
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +25 -19
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/util/graffiti.d.ts +15 -0
- package/lib/util/graffiti.d.ts.map +1 -1
- package/lib/util/graffiti.js +55 -0
- package/lib/util/graffiti.js.map +1 -1
- package/package.json +18 -18
- package/src/api/impl/beacon/index.ts +1 -1
- package/src/api/impl/beacon/pool/index.ts +5 -53
- package/src/api/impl/beacon/state/index.ts +55 -3
- package/src/api/impl/beacon/state/utils.ts +68 -4
- package/src/api/impl/debug/index.ts +3 -2
- package/src/api/impl/lodestar/index.ts +2 -2
- package/src/api/impl/proof/index.ts +2 -2
- package/src/api/impl/utils.ts +51 -1
- package/src/api/impl/validator/index.ts +70 -66
- package/src/chain/blocks/importBlock.ts +19 -10
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +17 -17
- package/src/chain/chain.ts +0 -3
- package/src/chain/interface.ts +0 -2
- package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
- package/src/chain/options.ts +2 -0
- package/src/chain/produceBlock/produceBlockBody.ts +9 -17
- package/src/chain/seenCache/index.ts +0 -1
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +8 -0
- package/src/chain/shufflingCache.ts +23 -7
- package/src/chain/stateCache/persistentCheckpointsCache.ts +3 -2
- package/src/chain/validation/executionPayloadBid.ts +14 -9
- package/src/chain/validation/proposerPreferences.ts +15 -5
- package/src/network/core/metrics.ts +94 -0
- package/src/network/discv5/utils.ts +11 -3
- package/src/network/gossip/encoding.ts +7 -0
- package/src/network/gossip/gossipsub.ts +2 -1
- package/src/network/gossip/topic.ts +28 -4
- package/src/network/peers/discover.ts +16 -11
- package/src/network/peers/peerManager.ts +11 -0
- package/src/network/peers/score/score.ts +8 -1
- package/src/network/peers/score/store.ts +8 -1
- package/src/network/peers/utils/prioritizePeers.ts +15 -0
- package/src/network/processor/gossipHandlers.ts +35 -16
- package/src/sync/range/chain.ts +4 -5
- package/src/sync/range/range.ts +10 -0
- package/src/sync/utils/downloadByRange.ts +26 -19
- package/src/util/graffiti.ts +78 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
- package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
- package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {CheckpointWithHex, IForkChoice} from "@lodestar/fork-choice";
|
|
3
3
|
import {GENESIS_SLOT} from "@lodestar/params";
|
|
4
|
-
import {IBeaconStateView, PubkeyCache} from "@lodestar/state-transition";
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import {IBeaconStateView, IBeaconStateViewGloas, PubkeyCache} from "@lodestar/state-transition";
|
|
5
|
+
import {
|
|
6
|
+
BLSPubkey,
|
|
7
|
+
BuilderIndex,
|
|
8
|
+
Epoch,
|
|
9
|
+
RootHex,
|
|
10
|
+
Slot,
|
|
11
|
+
ValidatorIndex,
|
|
12
|
+
getValidatorStatus,
|
|
13
|
+
phase0,
|
|
14
|
+
} from "@lodestar/types";
|
|
15
|
+
import {byteArrayEquals, fromHex} from "@lodestar/utils";
|
|
7
16
|
import {IBeaconChain} from "../../../../chain/index.js";
|
|
17
|
+
import {IBeaconSync} from "../../../../sync/index.js";
|
|
8
18
|
import {ApiError, ValidationError} from "../../errors.js";
|
|
19
|
+
import {notWhileSyncing} from "../../utils.js";
|
|
9
20
|
|
|
10
21
|
export function resolveStateId(
|
|
11
22
|
forkChoice: IForkChoice,
|
|
@@ -42,8 +53,17 @@ export function resolveStateId(
|
|
|
42
53
|
|
|
43
54
|
export async function getStateResponseWithRegen(
|
|
44
55
|
chain: IBeaconChain,
|
|
56
|
+
sync: IBeaconSync,
|
|
45
57
|
inStateId: routes.beacon.StateId
|
|
46
58
|
): Promise<{state: IBeaconStateView | Uint8Array; executionOptimistic: boolean; finalized: boolean}> {
|
|
59
|
+
// "head", "finalized" and "justified" resolve to already-available cached states, and "genesis" to a
|
|
60
|
+
// historical DB read - none trigger the forward regen that can walk back past the block-root window
|
|
61
|
+
// (SLOTS_PER_HISTORICAL_ROOT) and wedge a far-behind node. Keep serving those (node observability,
|
|
62
|
+
// dashboards, validator client checks) even while syncing; guard only the regen-capable lookups.
|
|
63
|
+
if (inStateId !== "head" && inStateId !== "finalized" && inStateId !== "justified" && inStateId !== "genesis") {
|
|
64
|
+
notWhileSyncing(chain, sync.state);
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
const stateId = resolveStateId(chain.forkChoice, inStateId);
|
|
48
68
|
|
|
49
69
|
const res =
|
|
@@ -78,6 +98,50 @@ export function toValidatorResponse(
|
|
|
78
98
|
};
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
type StateBuilderIndexResponse =
|
|
102
|
+
| {valid: true; builderIndex: BuilderIndex}
|
|
103
|
+
| {valid: false; code: number; reason: string};
|
|
104
|
+
|
|
105
|
+
export function getStateBuilderIndex(
|
|
106
|
+
id: routes.beacon.BuilderId | BLSPubkey,
|
|
107
|
+
state: IBeaconStateViewGloas
|
|
108
|
+
): StateBuilderIndexResponse {
|
|
109
|
+
if (typeof id === "string") {
|
|
110
|
+
// mutate `id` and fallthrough to below
|
|
111
|
+
if (id.startsWith("0x")) {
|
|
112
|
+
try {
|
|
113
|
+
id = fromHex(id);
|
|
114
|
+
} catch (_e) {
|
|
115
|
+
return {valid: false, code: 400, reason: "Invalid pubkey hex encoding"};
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
id = Number(id);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (typeof id === "number") {
|
|
123
|
+
const builderIndex = id;
|
|
124
|
+
// builder is invalid or added later than given stateId
|
|
125
|
+
if (!Number.isSafeInteger(builderIndex) || builderIndex < 0) {
|
|
126
|
+
return {valid: false, code: 400, reason: "Invalid builder index"};
|
|
127
|
+
}
|
|
128
|
+
if (builderIndex >= state.getBuildersLength()) {
|
|
129
|
+
return {valid: false, code: 404, reason: "Builder index from future state"};
|
|
130
|
+
}
|
|
131
|
+
return {valid: true, builderIndex};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// typeof id === Uint8Array
|
|
135
|
+
// There is no builder pubkey cache, linear scan over the registry
|
|
136
|
+
const buildersLength = state.getBuildersLength();
|
|
137
|
+
for (let builderIndex = 0; builderIndex < buildersLength; builderIndex++) {
|
|
138
|
+
if (byteArrayEquals(state.getBuilder(builderIndex).pubkey, id)) {
|
|
139
|
+
return {valid: true, builderIndex};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {valid: false, code: 404, reason: "Builder pubkey not found in state"};
|
|
143
|
+
}
|
|
144
|
+
|
|
81
145
|
export function filterStateValidatorsByStatus(
|
|
82
146
|
statuses: string[],
|
|
83
147
|
state: IBeaconStateView,
|
|
@@ -122,7 +186,7 @@ export function getStateValidatorIndex(
|
|
|
122
186
|
if (typeof id === "number") {
|
|
123
187
|
const validatorIndex = id;
|
|
124
188
|
// validator is invalid or added later than given stateId
|
|
125
|
-
if (!Number.isSafeInteger(validatorIndex)) {
|
|
189
|
+
if (!Number.isSafeInteger(validatorIndex) || validatorIndex < 0) {
|
|
126
190
|
return {valid: false, code: 400, reason: "Invalid validator index"};
|
|
127
191
|
}
|
|
128
192
|
if (validatorIndex >= state.validatorCount) {
|
|
@@ -39,7 +39,8 @@ function toPayloadStatusName(status: PayloadStatus): "pending" | "empty" | "full
|
|
|
39
39
|
export function getDebugApi({
|
|
40
40
|
chain,
|
|
41
41
|
config,
|
|
42
|
-
|
|
42
|
+
sync,
|
|
43
|
+
}: Pick<ApiModules, "chain" | "config" | "db" | "sync">): ApplicationMethods<routes.debug.Endpoints> {
|
|
43
44
|
return {
|
|
44
45
|
async getDebugChainHeadsV2() {
|
|
45
46
|
const heads = chain.forkChoice.getHeads();
|
|
@@ -132,7 +133,7 @@ export function getDebugApi({
|
|
|
132
133
|
},
|
|
133
134
|
|
|
134
135
|
async getStateV2({stateId}, context) {
|
|
135
|
-
const {state, executionOptimistic, finalized} = await getStateResponseWithRegen(chain, stateId);
|
|
136
|
+
const {state, executionOptimistic, finalized} = await getStateResponseWithRegen(chain, sync, stateId);
|
|
136
137
|
let slot: number, data: Uint8Array | BeaconState;
|
|
137
138
|
if (state instanceof Uint8Array) {
|
|
138
139
|
slot = getStateSlotFromBytes(state);
|
|
@@ -217,7 +217,7 @@ export function getLodestarApi({
|
|
|
217
217
|
},
|
|
218
218
|
|
|
219
219
|
async getHistoricalSummaries({stateId}) {
|
|
220
|
-
const {state, executionOptimistic, finalized} = await getStateResponseWithRegen(chain, stateId);
|
|
220
|
+
const {state, executionOptimistic, finalized} = await getStateResponseWithRegen(chain, sync, stateId);
|
|
221
221
|
|
|
222
222
|
const stateView = state instanceof Uint8Array ? chain.getHeadState().loadOtherState(state) : state;
|
|
223
223
|
|
|
@@ -332,7 +332,7 @@ export function getLodestarApi({
|
|
|
332
332
|
|
|
333
333
|
for (const [epoch, attestationsPerEpoch] of attestations) {
|
|
334
334
|
const slot = computeStartSlotAtEpoch(epoch);
|
|
335
|
-
const {state} = await getStateResponseWithRegen(chain, slot);
|
|
335
|
+
const {state} = await getStateResponseWithRegen(chain, sync, slot);
|
|
336
336
|
const stateView = state instanceof Uint8Array ? chain.getHeadState().loadOtherState(state) : state;
|
|
337
337
|
const shuffling = stateView.getShufflingAtEpoch(epoch);
|
|
338
338
|
for (const attestation of attestationsPerEpoch) {
|
|
@@ -8,7 +8,7 @@ import {ApiModules} from "../types.js";
|
|
|
8
8
|
|
|
9
9
|
export function getProofApi(
|
|
10
10
|
opts: ApiOptions,
|
|
11
|
-
{chain, config}: Pick<ApiModules, "chain" | "config" | "db">
|
|
11
|
+
{chain, config, sync}: Pick<ApiModules, "chain" | "config" | "db" | "sync">
|
|
12
12
|
): ApplicationMethods<routes.proof.Endpoints> {
|
|
13
13
|
// It's currently possible to request gigantic proofs (eg: a proof of the entire beacon state)
|
|
14
14
|
// We want some some sort of resistance against this DoS vector.
|
|
@@ -21,7 +21,7 @@ export function getProofApi(
|
|
|
21
21
|
throw new Error("Requested proof is too large.");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const res = await getStateResponseWithRegen(chain, stateId);
|
|
24
|
+
const res = await getStateResponseWithRegen(chain, sync, stateId);
|
|
25
25
|
|
|
26
26
|
const state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
|
|
27
27
|
|
package/src/api/impl/utils.ts
CHANGED
|
@@ -1,4 +1,54 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
+
import type {IBeaconChain} from "../../chain/index.js";
|
|
3
|
+
import {SyncState} from "../../sync/index.js";
|
|
4
|
+
import {ApiError, NodeIsSyncing} from "./errors.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* If the node is within this many epochs from the head, we declare it to be synced regardless of
|
|
8
|
+
* the network sync state.
|
|
9
|
+
*
|
|
10
|
+
* This helps prevent attacks where nodes can convince us that we're syncing some non-existent
|
|
11
|
+
* finalized head.
|
|
12
|
+
*
|
|
13
|
+
* TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
|
|
14
|
+
* can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
|
|
15
|
+
* a new head. Then the checkpoint state cache grows unbounded with very different states (because
|
|
16
|
+
* they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
|
|
17
|
+
* caches are better.
|
|
18
|
+
*/
|
|
19
|
+
export const SYNC_TOLERANCE_EPOCHS = 1;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reject any request while the node is syncing. Used by endpoints that must not serve while the
|
|
23
|
+
* node is behind — validator duties, and beacon state lookups whose regen could otherwise walk
|
|
24
|
+
* back past the block-root window (`SLOTS_PER_HISTORICAL_ROOT`) and wedge the node. Throws
|
|
25
|
+
* {@link NodeIsSyncing} (503).
|
|
26
|
+
*/
|
|
27
|
+
export function notWhileSyncing(chain: IBeaconChain, syncState: SyncState): void {
|
|
28
|
+
// Consider node synced before or close to genesis
|
|
29
|
+
if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
switch (syncState) {
|
|
34
|
+
case SyncState.SyncingFinalized:
|
|
35
|
+
case SyncState.SyncingHead: {
|
|
36
|
+
const currentSlot = chain.clock.currentSlot;
|
|
37
|
+
const headSlot = chain.forkChoice.getHead().slot;
|
|
38
|
+
if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
|
|
39
|
+
throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case SyncState.Synced:
|
|
46
|
+
return;
|
|
47
|
+
|
|
48
|
+
case SyncState.Stalled:
|
|
49
|
+
throw new NodeIsSyncing("waiting for peers");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
2
52
|
|
|
3
53
|
/**
|
|
4
54
|
* Ensures that the array contains unique values, and throws an ApiError
|
|
@@ -65,6 +65,8 @@ import {
|
|
|
65
65
|
AttestationError,
|
|
66
66
|
AttestationErrorCode,
|
|
67
67
|
GossipAction,
|
|
68
|
+
ProposerPreferencesError,
|
|
69
|
+
ProposerPreferencesErrorCode,
|
|
68
70
|
SyncCommitteeError,
|
|
69
71
|
SyncCommitteeErrorCode,
|
|
70
72
|
} from "../../../chain/errors/index.js";
|
|
@@ -74,37 +76,23 @@ import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/prod
|
|
|
74
76
|
import {RegenCaller} from "../../../chain/regen/index.js";
|
|
75
77
|
import {CheckpointHex} from "../../../chain/stateCache/types.js";
|
|
76
78
|
import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
|
|
79
|
+
import {validateGossipProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
|
|
77
80
|
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
|
|
78
81
|
import {ZERO_HASH} from "../../../constants/index.js";
|
|
79
82
|
import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
|
|
80
83
|
import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
|
|
81
84
|
import {CommitteeSubscription} from "../../../network/subnets/index.js";
|
|
82
|
-
import {SyncState} from "../../../sync/index.js";
|
|
83
85
|
import {callInNextEventLoop} from "../../../util/eventLoop.js";
|
|
84
86
|
import {isOptimisticBlock} from "../../../util/forkChoice.js";
|
|
85
|
-
import {
|
|
87
|
+
import {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
|
|
86
88
|
import {getLodestarClientVersion} from "../../../util/metadata.js";
|
|
87
89
|
import {ApiOptions} from "../../options.js";
|
|
88
90
|
import {getStateResponseWithRegen} from "../beacon/state/utils.js";
|
|
89
91
|
import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
|
|
90
92
|
import {ApiModules} from "../types.js";
|
|
93
|
+
import {notWhileSyncing} from "../utils.js";
|
|
91
94
|
import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";
|
|
92
95
|
|
|
93
|
-
/**
|
|
94
|
-
* If the node is within this many epochs from the head, we declare it to be synced regardless of
|
|
95
|
-
* the network sync state.
|
|
96
|
-
*
|
|
97
|
-
* This helps prevent attacks where nodes can convince us that we're syncing some non-existent
|
|
98
|
-
* finalized head.
|
|
99
|
-
*
|
|
100
|
-
* TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
|
|
101
|
-
* can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
|
|
102
|
-
* a new head. Then the checkpoint state cache grows unbounded with very different states (because
|
|
103
|
-
* they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
|
|
104
|
-
* caches are better.
|
|
105
|
-
*/
|
|
106
|
-
export const SYNC_TOLERANCE_EPOCHS = 1;
|
|
107
|
-
|
|
108
96
|
/**
|
|
109
97
|
* Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
|
|
110
98
|
* Post this time, race execution and builder to pick whatever resolves first.
|
|
@@ -338,36 +326,6 @@ export function getValidatorApi(
|
|
|
338
326
|
return null;
|
|
339
327
|
}
|
|
340
328
|
|
|
341
|
-
/**
|
|
342
|
-
* Reject any request while the node is syncing
|
|
343
|
-
*/
|
|
344
|
-
function notWhileSyncing(): void {
|
|
345
|
-
// Consider node synced before or close to genesis
|
|
346
|
-
if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const syncState = sync.state;
|
|
351
|
-
switch (syncState) {
|
|
352
|
-
case SyncState.SyncingFinalized:
|
|
353
|
-
case SyncState.SyncingHead: {
|
|
354
|
-
const currentSlot = chain.clock.currentSlot;
|
|
355
|
-
const headSlot = chain.forkChoice.getHead().slot;
|
|
356
|
-
if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
|
|
357
|
-
throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
case SyncState.Synced:
|
|
364
|
-
return;
|
|
365
|
-
|
|
366
|
-
case SyncState.Stalled:
|
|
367
|
-
throw new NodeIsSyncing("waiting for peers");
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
329
|
/**
|
|
372
330
|
* Post merge, the CL and EL could be out of step in the sync, and could result in
|
|
373
331
|
* Syncing status of the chain head. To be precise:
|
|
@@ -570,7 +528,7 @@ export function getValidatorApi(
|
|
|
570
528
|
builderBoostFactor?: bigint,
|
|
571
529
|
{feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {}
|
|
572
530
|
): Promise<ProduceBlindedBlockOrBlockContentsRes> {
|
|
573
|
-
notWhileSyncing();
|
|
531
|
+
notWhileSyncing(chain, sync.state);
|
|
574
532
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
575
533
|
|
|
576
534
|
const parentBlock = chain.getProposerHead(slot);
|
|
@@ -611,7 +569,10 @@ export function getValidatorApi(
|
|
|
611
569
|
}
|
|
612
570
|
|
|
613
571
|
const graffitiBytes = toGraffitiBytes(
|
|
614
|
-
graffiti
|
|
572
|
+
getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
|
|
573
|
+
private: opts.private,
|
|
574
|
+
graffitiAppend: chain.opts.graffitiAppend,
|
|
575
|
+
})
|
|
615
576
|
);
|
|
616
577
|
|
|
617
578
|
const loggerContext = {
|
|
@@ -911,7 +872,7 @@ export function getValidatorApi(
|
|
|
911
872
|
throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
|
|
912
873
|
}
|
|
913
874
|
|
|
914
|
-
notWhileSyncing();
|
|
875
|
+
notWhileSyncing(chain, sync.state);
|
|
915
876
|
await waitForSlot(slot);
|
|
916
877
|
|
|
917
878
|
const parentBlock = chain.getProposerHead(slot);
|
|
@@ -921,7 +882,10 @@ export function getValidatorApi(
|
|
|
921
882
|
metrics?.blockProductionSlotDelta.set(slot - parentSlot);
|
|
922
883
|
|
|
923
884
|
const graffitiBytes = toGraffitiBytes(
|
|
924
|
-
graffiti
|
|
885
|
+
getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
|
|
886
|
+
private: opts.private,
|
|
887
|
+
graffitiAppend: chain.opts.graffitiAppend,
|
|
888
|
+
})
|
|
925
889
|
);
|
|
926
890
|
|
|
927
891
|
// TODO GLOAS: respect builderSelection (MaxProfit, BuilderAlways, ExecutionAlways, etc.) to let
|
|
@@ -1026,7 +990,7 @@ export function getValidatorApi(
|
|
|
1026
990
|
},
|
|
1027
991
|
|
|
1028
992
|
async produceAttestationData({committeeIndex, slot}) {
|
|
1029
|
-
notWhileSyncing();
|
|
993
|
+
notWhileSyncing(chain, sync.state);
|
|
1030
994
|
|
|
1031
995
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
1032
996
|
|
|
@@ -1107,13 +1071,13 @@ export function getValidatorApi(
|
|
|
1107
1071
|
throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
|
|
1108
1072
|
}
|
|
1109
1073
|
|
|
1110
|
-
notWhileSyncing();
|
|
1074
|
+
notWhileSyncing(chain, sync.state);
|
|
1111
1075
|
await waitForSlot(slot);
|
|
1112
1076
|
|
|
1113
1077
|
const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
|
|
1114
1078
|
if (!block) {
|
|
1115
|
-
// No block is seen at slot. Return
|
|
1116
|
-
|
|
1079
|
+
// No canonical block is seen at slot. Return 204 so vc can skip casting payload attestation.
|
|
1080
|
+
return {data: undefined, meta: {version: fork}, status: 204};
|
|
1117
1081
|
}
|
|
1118
1082
|
|
|
1119
1083
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
|
|
@@ -1192,7 +1156,7 @@ export function getValidatorApi(
|
|
|
1192
1156
|
},
|
|
1193
1157
|
|
|
1194
1158
|
async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
|
|
1195
|
-
notWhileSyncing();
|
|
1159
|
+
notWhileSyncing(chain, sync.state);
|
|
1196
1160
|
|
|
1197
1161
|
const currentEpoch = currentEpochWithDisparity();
|
|
1198
1162
|
const nextEpoch = currentEpoch + 1;
|
|
@@ -1239,7 +1203,7 @@ export function getValidatorApi(
|
|
|
1239
1203
|
// requested epoch is within that range, we can use the head state at current epoch
|
|
1240
1204
|
state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
|
|
1241
1205
|
} else {
|
|
1242
|
-
const res = await getStateResponseWithRegen(chain, startSlot);
|
|
1206
|
+
const res = await getStateResponseWithRegen(chain, sync, startSlot);
|
|
1243
1207
|
|
|
1244
1208
|
state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
|
|
1245
1209
|
|
|
@@ -1333,7 +1297,7 @@ export function getValidatorApi(
|
|
|
1333
1297
|
},
|
|
1334
1298
|
|
|
1335
1299
|
async getAttesterDuties({epoch, indices}) {
|
|
1336
|
-
notWhileSyncing();
|
|
1300
|
+
notWhileSyncing(chain, sync.state);
|
|
1337
1301
|
|
|
1338
1302
|
if (indices.length === 0) {
|
|
1339
1303
|
throw new ApiError(400, "No validator to get attester duties");
|
|
@@ -1393,7 +1357,7 @@ export function getValidatorApi(
|
|
|
1393
1357
|
},
|
|
1394
1358
|
|
|
1395
1359
|
async getPtcDuties({epoch, indices}) {
|
|
1396
|
-
notWhileSyncing();
|
|
1360
|
+
notWhileSyncing(chain, sync.state);
|
|
1397
1361
|
|
|
1398
1362
|
if (indices.length === 0) {
|
|
1399
1363
|
throw new ApiError(400, "No validator to get PTC duties");
|
|
@@ -1455,7 +1419,7 @@ export function getValidatorApi(
|
|
|
1455
1419
|
* @param validatorIndices an array of the validator indices for which to obtain the duties.
|
|
1456
1420
|
*/
|
|
1457
1421
|
async getSyncCommitteeDuties({epoch, indices}) {
|
|
1458
|
-
notWhileSyncing();
|
|
1422
|
+
notWhileSyncing(chain, sync.state);
|
|
1459
1423
|
|
|
1460
1424
|
if (indices.length === 0) {
|
|
1461
1425
|
throw new ApiError(400, "No validator to get attester duties");
|
|
@@ -1502,7 +1466,7 @@ export function getValidatorApi(
|
|
|
1502
1466
|
},
|
|
1503
1467
|
|
|
1504
1468
|
async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
|
|
1505
|
-
notWhileSyncing();
|
|
1469
|
+
notWhileSyncing(chain, sync.state);
|
|
1506
1470
|
|
|
1507
1471
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
1508
1472
|
|
|
@@ -1525,7 +1489,7 @@ export function getValidatorApi(
|
|
|
1525
1489
|
},
|
|
1526
1490
|
|
|
1527
1491
|
async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
|
|
1528
|
-
notWhileSyncing();
|
|
1492
|
+
notWhileSyncing(chain, sync.state);
|
|
1529
1493
|
|
|
1530
1494
|
const seenTimestampSec = Date.now() / 1000;
|
|
1531
1495
|
const failures: FailureList = [];
|
|
@@ -1586,7 +1550,7 @@ export function getValidatorApi(
|
|
|
1586
1550
|
* https://github.com/ethereum/beacon-APIs/pull/137
|
|
1587
1551
|
*/
|
|
1588
1552
|
async publishContributionAndProofs({contributionAndProofs}) {
|
|
1589
|
-
notWhileSyncing();
|
|
1553
|
+
notWhileSyncing(chain, sync.state);
|
|
1590
1554
|
|
|
1591
1555
|
const failures: FailureList = [];
|
|
1592
1556
|
|
|
@@ -1635,7 +1599,7 @@ export function getValidatorApi(
|
|
|
1635
1599
|
},
|
|
1636
1600
|
|
|
1637
1601
|
async prepareBeaconCommitteeSubnet({subscriptions}) {
|
|
1638
|
-
notWhileSyncing();
|
|
1602
|
+
notWhileSyncing(chain, sync.state);
|
|
1639
1603
|
|
|
1640
1604
|
await network.prepareBeaconCommitteeSubnets(
|
|
1641
1605
|
subscriptions.map(({validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex}) => ({
|
|
@@ -1668,7 +1632,7 @@ export function getValidatorApi(
|
|
|
1668
1632
|
* https://github.com/ethereum/beacon-APIs/pull/136
|
|
1669
1633
|
*/
|
|
1670
1634
|
async prepareSyncCommitteeSubnets({subscriptions}) {
|
|
1671
|
-
notWhileSyncing();
|
|
1635
|
+
notWhileSyncing(chain, sync.state);
|
|
1672
1636
|
|
|
1673
1637
|
// A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
|
|
1674
1638
|
const subs: CommitteeSubscription[] = [];
|
|
@@ -1765,6 +1729,46 @@ export function getValidatorApi(
|
|
|
1765
1729
|
});
|
|
1766
1730
|
},
|
|
1767
1731
|
|
|
1732
|
+
async submitProposerPreferences({signedProposerPreferences}) {
|
|
1733
|
+
const failures: FailureList = [];
|
|
1734
|
+
|
|
1735
|
+
await Promise.all(
|
|
1736
|
+
signedProposerPreferences.map(async (signed, i) => {
|
|
1737
|
+
try {
|
|
1738
|
+
await validateGossipProposerPreferences(chain, signed);
|
|
1739
|
+
|
|
1740
|
+
chain.proposerPreferencesPool.add(signed);
|
|
1741
|
+
await network.publishProposerPreferences(signed);
|
|
1742
|
+
chain.emitter.emit(routes.events.EventType.proposerPreferences, {
|
|
1743
|
+
version: config.getForkName(signed.message.proposalSlot),
|
|
1744
|
+
data: signed,
|
|
1745
|
+
});
|
|
1746
|
+
} catch (e) {
|
|
1747
|
+
const logCtx = {
|
|
1748
|
+
slot: signed.message.proposalSlot,
|
|
1749
|
+
validatorIndex: signed.message.validatorIndex,
|
|
1750
|
+
dependentRoot: toRootHex(signed.message.dependentRoot),
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
if (e instanceof ProposerPreferencesError && e.type.code === ProposerPreferencesErrorCode.ALREADY_KNOWN) {
|
|
1754
|
+
logger.debug("Ignoring known signed proposer preferences", logCtx);
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
failures.push({index: i, message: (e as Error).message});
|
|
1759
|
+
logger.verbose(`Error on submitProposerPreferences [${i}]`, logCtx, e as Error);
|
|
1760
|
+
if (e instanceof ProposerPreferencesError && e.action === GossipAction.REJECT) {
|
|
1761
|
+
chain.persistInvalidSszValue(ssz.gloas.SignedProposerPreferences, signed, "api_reject");
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
})
|
|
1765
|
+
);
|
|
1766
|
+
|
|
1767
|
+
if (failures.length > 0) {
|
|
1768
|
+
throw new IndexedError("Error processing signed proposer preferences", failures);
|
|
1769
|
+
}
|
|
1770
|
+
},
|
|
1771
|
+
|
|
1768
1772
|
async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
|
|
1769
1773
|
const fork = config.getForkName(slot);
|
|
1770
1774
|
|
|
@@ -1772,7 +1776,7 @@ export function getValidatorApi(
|
|
|
1772
1776
|
throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
|
|
1773
1777
|
}
|
|
1774
1778
|
|
|
1775
|
-
notWhileSyncing();
|
|
1779
|
+
notWhileSyncing(chain, sync.state);
|
|
1776
1780
|
await waitForSlot(slot);
|
|
1777
1781
|
|
|
1778
1782
|
const blockRootHex = toRootHex(beaconBlockRoot);
|
|
@@ -9,7 +9,14 @@ import {
|
|
|
9
9
|
NotReorgedReason,
|
|
10
10
|
getSafeExecutionBlockHash,
|
|
11
11
|
} from "@lodestar/fork-choice";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ForkPostAltair,
|
|
14
|
+
ForkPostElectra,
|
|
15
|
+
ForkSeq,
|
|
16
|
+
GENESIS_EPOCH,
|
|
17
|
+
MAX_SEED_LOOKAHEAD,
|
|
18
|
+
SLOTS_PER_EPOCH,
|
|
19
|
+
} from "@lodestar/params";
|
|
13
20
|
import {
|
|
14
21
|
IBeaconStateView,
|
|
15
22
|
RootCache,
|
|
@@ -117,18 +124,13 @@ export async function importBlock(
|
|
|
117
124
|
executionStatus = parentBlock.executionStatus;
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
// getBeaconProposerOrNull will return null if head state is more than one epoch away
|
|
121
|
-
// from block slot. We skip proposer boost canonical check as we cannot determine the canonical proposer
|
|
122
|
-
const expectedProposerIndex: number | null = this.getHeadState().getBeaconProposerOrNull(blockSlot);
|
|
123
|
-
|
|
124
127
|
const blockSummary = this.forkChoice.onBlock(
|
|
125
128
|
block.message,
|
|
126
129
|
postState,
|
|
127
130
|
blockDelaySec,
|
|
128
131
|
currentSlot,
|
|
129
132
|
executionStatus,
|
|
130
|
-
dataAvailabilityStatus
|
|
131
|
-
expectedProposerIndex
|
|
133
|
+
dataAvailabilityStatus
|
|
132
134
|
);
|
|
133
135
|
|
|
134
136
|
// This adds the state necessary to process the next block
|
|
@@ -419,7 +421,7 @@ export async function importBlock(
|
|
|
419
421
|
}
|
|
420
422
|
} catch (e) {
|
|
421
423
|
if (isStartSlotOfEpoch(proposalSlot)) {
|
|
422
|
-
notOverrideFcuReason = NotReorgedReason.
|
|
424
|
+
notOverrideFcuReason = NotReorgedReason.AtEpochBoundary;
|
|
423
425
|
} else {
|
|
424
426
|
this.logger.warn("Unable to get beacon proposer. Do not override fcu.", {proposalSlot}, e as Error);
|
|
425
427
|
}
|
|
@@ -483,8 +485,15 @@ export async function importBlock(
|
|
|
483
485
|
// Cache shufflings when crossing an epoch boundary
|
|
484
486
|
const parentEpoch = computeEpochAtSlot(parentBlockSlot);
|
|
485
487
|
if (parentEpoch < blockEpoch) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
+
const previousEpoch = blockEpoch === GENESIS_EPOCH ? GENESIS_EPOCH : blockEpoch - 1;
|
|
489
|
+
if (
|
|
490
|
+
!this.shufflingCache.has(previousEpoch, postState.previousDecisionRoot) ||
|
|
491
|
+
!this.shufflingCache.has(blockEpoch, postState.currentDecisionRoot) ||
|
|
492
|
+
!this.shufflingCache.has(blockEpoch + 1, postState.nextDecisionRoot)
|
|
493
|
+
) {
|
|
494
|
+
this.shufflingCache.processState(postState);
|
|
495
|
+
this.logger.verbose("Processed shuffling for next epoch", {parentEpoch, blockEpoch, slot: blockSlot});
|
|
496
|
+
}
|
|
488
497
|
}
|
|
489
498
|
|
|
490
499
|
if (blockSlot % SLOTS_PER_EPOCH === 0) {
|
|
@@ -8,28 +8,24 @@ import {ImportPayloadOpts} from "./types.js";
|
|
|
8
8
|
// TODO GLOAS: Set to be equal to DEFAULT_MAX_PENDING_UNFINALIZED_PAYLOAD_ENVELOPE_WRITES for now
|
|
9
9
|
const QUEUE_MAX_LENGTH = 16;
|
|
10
10
|
|
|
11
|
-
enum PayloadEnvelopeImportStatus {
|
|
12
|
-
queued = "queued",
|
|
13
|
-
importing = "importing",
|
|
14
|
-
imported = "imported",
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
/**
|
|
18
12
|
* PayloadEnvelopeProcessor processes payload envelope jobs in a queued fashion, one after the other.
|
|
19
13
|
*
|
|
20
14
|
* Jobs are enqueued only on envelope arrival (gossip or API). The envelope may reach us before
|
|
21
15
|
* the sampled data columns; importExecutionPayload awaits `verifyPayloadsDataAvailability`
|
|
22
16
|
* internally, so a queued job can pend for up to `PAYLOAD_DATA_AVAILABILITY_TIMEOUT` while
|
|
23
|
-
* waiting for columns. Duplicate triggers for the same payloadInput are deduped
|
|
17
|
+
* waiting for columns. Duplicate triggers for the same payloadInput are deduped by sharing the
|
|
18
|
+
* in-flight import promise: every caller observes the real import outcome. Resolving duplicates
|
|
19
|
+
* early instead would let BlockInputSync mark a still-pending payload as processed, re-queue it,
|
|
20
|
+
* and spin re-processing the same root until OOM while the import is blocked (e.g. EL syncing).
|
|
24
21
|
*/
|
|
25
22
|
export class PayloadEnvelopeProcessor {
|
|
26
23
|
readonly jobQueue: JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>;
|
|
27
|
-
private readonly
|
|
24
|
+
private readonly imports = new WeakMap<PayloadEnvelopeInput, Promise<void>>();
|
|
28
25
|
|
|
29
26
|
constructor(chain: BeaconChain, metrics: Metrics | null, signal: AbortSignal) {
|
|
30
27
|
this.jobQueue = new JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>(
|
|
31
28
|
(payloadInput, opts) => {
|
|
32
|
-
this.importStatus.set(payloadInput, PayloadEnvelopeImportStatus.importing);
|
|
33
29
|
return processExecutionPayload.call(chain, payloadInput, signal, opts);
|
|
34
30
|
},
|
|
35
31
|
{maxLength: QUEUE_MAX_LENGTH, noYieldIfOneItem: true, signal},
|
|
@@ -38,24 +34,28 @@ export class PayloadEnvelopeProcessor {
|
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
async processPayloadEnvelopeJob(payloadInput: PayloadEnvelopeInput, opts: ImportPayloadOpts = {}): Promise<void> {
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
const existing = this.imports.get(payloadInput);
|
|
38
|
+
if (existing) {
|
|
39
|
+
return existing;
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
await this.jobQueue.waitForSpace();
|
|
46
43
|
|
|
47
44
|
// Re-check after await, as another call may have queued this payload.
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const queued = this.imports.get(payloadInput);
|
|
46
|
+
if (queued) {
|
|
47
|
+
return queued;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
this.
|
|
50
|
+
const importPromise = this.jobQueue.push(payloadInput, opts);
|
|
51
|
+
this.imports.set(payloadInput, importPromise);
|
|
53
52
|
|
|
54
53
|
try {
|
|
55
|
-
await
|
|
56
|
-
this.importStatus.set(payloadInput, PayloadEnvelopeImportStatus.imported);
|
|
54
|
+
await importPromise;
|
|
57
55
|
} catch (e) {
|
|
58
|
-
|
|
56
|
+
// Drop the failed import so a later attempt can retry once the failure cause is resolved
|
|
57
|
+
// (e.g. BLOCK_NOT_IN_FORK_CHOICE after the block lands in fork choice)
|
|
58
|
+
this.imports.delete(payloadInput);
|
|
59
59
|
throw e;
|
|
60
60
|
}
|
|
61
61
|
}
|
package/src/chain/chain.ts
CHANGED
|
@@ -107,7 +107,6 @@ import {
|
|
|
107
107
|
SeenExecutionPayloadBids,
|
|
108
108
|
SeenPayloadAttesters,
|
|
109
109
|
SeenPayloadEnvelopeInput,
|
|
110
|
-
SeenProposerPreferences,
|
|
111
110
|
SeenSyncCommitteeMessages,
|
|
112
111
|
} from "./seenCache/index.js";
|
|
113
112
|
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
|
|
@@ -189,7 +188,6 @@ export class BeaconChain implements IBeaconChain {
|
|
|
189
188
|
readonly seenPayloadAttesters = new SeenPayloadAttesters();
|
|
190
189
|
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
|
|
191
190
|
readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
|
|
192
|
-
readonly seenProposerPreferences = new SeenProposerPreferences();
|
|
193
191
|
readonly seenBlockProposers = new SeenBlockProposers();
|
|
194
192
|
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
|
|
195
193
|
readonly seenContributionAndProof: SeenContributionAndProof;
|
|
@@ -1473,7 +1471,6 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1473
1471
|
this.payloadAttestationPool.prune(slot);
|
|
1474
1472
|
this.executionPayloadBidPool.prune(slot);
|
|
1475
1473
|
this.seenExecutionPayloadBids.prune(slot);
|
|
1476
|
-
this.seenProposerPreferences.prune(slot);
|
|
1477
1474
|
this.proposerPreferencesPool.prune(slot);
|
|
1478
1475
|
this.seenAttestationDatas.onSlot(slot);
|
|
1479
1476
|
this.reprocessController.onSlot(slot);
|