@lodestar/beacon-node 1.43.0-dev.66d2c102e3 → 1.43.0-dev.6f485b1b61
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 +13 -3
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +45 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +0 -1
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +68 -2
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +4 -1
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +16 -31
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +9 -3
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +37 -15
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +35 -21
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/types.d.ts +2 -1
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +8 -0
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +2 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +30 -12
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +25 -8
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +0 -11
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +0 -4
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +8 -1
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/errors/proposerPreferences.js +1 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -1
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +6 -1
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
- package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.js +26 -4
- package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +16 -18
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +12 -3
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +34 -22
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +1 -4
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +1 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +21 -11
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +70 -20
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
- package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -8
- 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 +39 -17
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +2 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/interface.d.ts +1 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +5 -0
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +28 -10
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.js +5 -5
- package/lib/network/processor/index.js.map +1 -1
- package/lib/node/nodejs.js +2 -2
- package/lib/node/nodejs.js.map +1 -1
- package/lib/node/notifier.js +1 -7
- package/lib/node/notifier.js.map +1 -1
- package/lib/sync/constants.d.ts +3 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +3 -4
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +23 -3
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +191 -36
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +13 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +61 -9
- 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 +14 -3
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +13 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +7 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +138 -57
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +29 -8
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +104 -42
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRoot.js +10 -0
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +8 -6
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +16 -3
- package/src/api/impl/beacon/pool/index.ts +83 -1
- package/src/api/impl/debug/index.ts +0 -1
- package/src/api/impl/validator/index.ts +82 -1
- package/src/chain/blocks/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +16 -50
- package/src/chain/blocks/importExecutionPayload.ts +51 -20
- package/src/chain/blocks/index.ts +32 -15
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
- package/src/chain/blocks/types.ts +2 -1
- package/src/chain/blocks/utils/chainSegment.ts +8 -0
- package/src/chain/blocks/verifyBlock.ts +45 -13
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
- package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
- package/src/chain/chain.ts +29 -7
- package/src/chain/emitter.ts +0 -11
- package/src/chain/errors/proposerPreferences.ts +9 -1
- package/src/chain/initState.ts +9 -1
- package/src/chain/opPools/payloadAttestationPool.ts +29 -8
- package/src/chain/prepareNextSlot.ts +21 -29
- package/src/chain/produceBlock/produceBlockBody.ts +45 -27
- package/src/chain/regen/queued.ts +2 -7
- package/src/chain/regen/regen.ts +2 -7
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +90 -24
- package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
- package/src/chain/validation/executionPayloadBid.ts +11 -8
- package/src/chain/validation/proposerPreferences.ts +37 -18
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +38 -11
- package/src/network/processor/index.ts +5 -5
- package/src/node/nodejs.ts +2 -2
- package/src/node/notifier.ts +1 -8
- package/src/sync/constants.ts +4 -4
- package/src/sync/range/batch.ts +240 -42
- package/src/sync/range/chain.ts +77 -10
- package/src/sync/range/range.ts +16 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +170 -60
- package/src/sync/utils/downloadByRange.ts +166 -44
- package/src/sync/utils/downloadByRoot.ts +12 -0
- package/src/util/sszBytes.ts +8 -6
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
+
import {CheckpointWithHex, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
|
|
2
3
|
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
3
|
-
import {RootHex
|
|
4
|
+
import {RootHex} from "@lodestar/types";
|
|
4
5
|
import {Logger} from "@lodestar/utils";
|
|
5
6
|
import {Metrics} from "../../metrics/metrics.js";
|
|
7
|
+
import {IClock} from "../../util/clock.js";
|
|
6
8
|
import {SerializedCache} from "../../util/serializedCache.js";
|
|
7
|
-
import {
|
|
9
|
+
import {isDaOutOfRange} from "../blocks/blockInput/index.js";
|
|
10
|
+
import {CreateFromBidProps, CreateFromBlockProps, PayloadEnvelopeInput} from "../blocks/payloadEnvelopeInput/index.js";
|
|
8
11
|
import {ChainEvent, ChainEventEmitter} from "../emitter.js";
|
|
9
12
|
|
|
10
13
|
export type {PayloadEnvelopeInputState} from "../blocks/payloadEnvelopeInput/index.js";
|
|
11
14
|
export {PayloadEnvelopeInput} from "../blocks/payloadEnvelopeInput/index.js";
|
|
12
15
|
|
|
13
16
|
export type SeenPayloadEnvelopeInputModules = {
|
|
17
|
+
config: ChainForkConfig;
|
|
18
|
+
clock: IClock;
|
|
19
|
+
forkChoice: IForkChoice;
|
|
14
20
|
chainEvents: ChainEventEmitter;
|
|
15
21
|
signal: AbortSignal;
|
|
16
22
|
serializedCache: SerializedCache;
|
|
@@ -21,17 +27,16 @@ export type SeenPayloadEnvelopeInputModules = {
|
|
|
21
27
|
/**
|
|
22
28
|
* Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
|
|
23
29
|
*
|
|
24
|
-
* Created
|
|
25
|
-
* - `prepareNextSlot` calls `pruneBelow(headParentSlot)` every slot once the head we'll build
|
|
26
|
-
* on is known.
|
|
27
|
-
* - `onFinalized` calls `pruneBelow(finalizedSlot)` on every finalization for bulk cleanup.
|
|
28
|
-
*
|
|
30
|
+
* Created whenever we have a block because it needs block bid.
|
|
29
31
|
* Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
|
|
30
32
|
* (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
|
|
31
33
|
* transiently hold more during forks, range-sync bursts, or when `prepareNextSlot` skips
|
|
32
34
|
* ticks; subsequent ticks settle it back.
|
|
33
35
|
*/
|
|
34
36
|
export class SeenPayloadEnvelopeInput {
|
|
37
|
+
private readonly config: ChainForkConfig;
|
|
38
|
+
private readonly clock: IClock;
|
|
39
|
+
private readonly forkChoice: IForkChoice;
|
|
35
40
|
private readonly chainEvents: ChainEventEmitter;
|
|
36
41
|
private readonly signal: AbortSignal;
|
|
37
42
|
private readonly serializedCache: SerializedCache;
|
|
@@ -39,7 +44,19 @@ export class SeenPayloadEnvelopeInput {
|
|
|
39
44
|
private readonly logger?: Logger;
|
|
40
45
|
private payloadInputs = new Map<RootHex, PayloadEnvelopeInput>();
|
|
41
46
|
|
|
42
|
-
constructor({
|
|
47
|
+
constructor({
|
|
48
|
+
config,
|
|
49
|
+
clock,
|
|
50
|
+
forkChoice,
|
|
51
|
+
chainEvents,
|
|
52
|
+
signal,
|
|
53
|
+
serializedCache,
|
|
54
|
+
metrics,
|
|
55
|
+
logger,
|
|
56
|
+
}: SeenPayloadEnvelopeInputModules) {
|
|
57
|
+
this.config = config;
|
|
58
|
+
this.clock = clock;
|
|
59
|
+
this.forkChoice = forkChoice;
|
|
43
60
|
this.chainEvents = chainEvents;
|
|
44
61
|
this.signal = signal;
|
|
45
62
|
this.serializedCache = serializedCache;
|
|
@@ -58,23 +75,68 @@ export class SeenPayloadEnvelopeInput {
|
|
|
58
75
|
});
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
this.chainEvents.on(ChainEvent.forkChoiceFinalized, this.
|
|
78
|
+
this.chainEvents.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
|
|
62
79
|
this.signal.addEventListener("abort", () => {
|
|
63
|
-
this.chainEvents.off(ChainEvent.forkChoiceFinalized, this.
|
|
80
|
+
this.chainEvents.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
|
|
64
81
|
});
|
|
65
82
|
}
|
|
66
83
|
|
|
67
|
-
private
|
|
68
|
-
|
|
84
|
+
private pruneFinalized = (checkpoint: CheckpointWithHex): void => {
|
|
85
|
+
const finalizedSlot = computeStartSlotAtEpoch(checkpoint.epoch);
|
|
86
|
+
let deletedCount = 0;
|
|
87
|
+
for (const [, input] of this.payloadInputs) {
|
|
88
|
+
if (input.slot < finalizedSlot) {
|
|
89
|
+
this.evictPayloadInput(input);
|
|
90
|
+
deletedCount++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.logger?.debug("SeenPayloadEnvelopeInput.pruneFinalized deleted entries", {
|
|
95
|
+
finalizedSlot,
|
|
96
|
+
finalizedRoot: checkpoint.rootHex,
|
|
97
|
+
deletedCount,
|
|
98
|
+
});
|
|
69
99
|
};
|
|
70
100
|
|
|
71
|
-
add(props: CreateFromBlockProps): PayloadEnvelopeInput {
|
|
72
|
-
|
|
73
|
-
|
|
101
|
+
add(props: Omit<CreateFromBlockProps, "daOutOfRange">): PayloadEnvelopeInput {
|
|
102
|
+
const existing = this.payloadInputs.get(props.blockRootHex);
|
|
103
|
+
if (existing !== undefined) {
|
|
104
|
+
this.logger?.verbose("SeenPayloadEnvelopeInput.add reused existing entry", {
|
|
105
|
+
slot: existing.slot,
|
|
106
|
+
root: props.blockRootHex,
|
|
107
|
+
});
|
|
108
|
+
return existing;
|
|
109
|
+
}
|
|
110
|
+
const daOutOfRange = isDaOutOfRange(this.config, props.forkName, props.block.message.slot, this.clock.currentEpoch);
|
|
111
|
+
const input = PayloadEnvelopeInput.createFromBlock({...props, daOutOfRange});
|
|
112
|
+
this.payloadInputs.set(props.blockRootHex, input);
|
|
113
|
+
this.metrics?.seenCache.payloadEnvelopeInput.created.inc();
|
|
114
|
+
this.logger?.verbose("SeenPayloadEnvelopeInput.add created new entry", {
|
|
115
|
+
slot: input.slot,
|
|
116
|
+
root: props.blockRootHex,
|
|
117
|
+
daOutOfRange,
|
|
118
|
+
});
|
|
119
|
+
return input;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Used at chain initialization to seed the anchor block's PayloadEnvelopeInput from
|
|
124
|
+
* `state.latestExecutionPayloadBid`.
|
|
125
|
+
*/
|
|
126
|
+
addFromBid(props: Omit<CreateFromBidProps, "daOutOfRange">): PayloadEnvelopeInput {
|
|
127
|
+
const existing = this.payloadInputs.get(props.blockRootHex);
|
|
128
|
+
if (existing !== undefined) {
|
|
129
|
+
return existing;
|
|
74
130
|
}
|
|
75
|
-
const
|
|
131
|
+
const daOutOfRange = isDaOutOfRange(this.config, props.forkName, props.slot, this.clock.currentEpoch);
|
|
132
|
+
const input = PayloadEnvelopeInput.createFromBid({...props, daOutOfRange});
|
|
76
133
|
this.payloadInputs.set(props.blockRootHex, input);
|
|
77
134
|
this.metrics?.seenCache.payloadEnvelopeInput.created.inc();
|
|
135
|
+
this.logger?.verbose("SeenPayloadEnvelopeInput.addFromBid created new entry", {
|
|
136
|
+
slot: input.slot,
|
|
137
|
+
root: props.blockRootHex,
|
|
138
|
+
daOutOfRange,
|
|
139
|
+
});
|
|
78
140
|
return input;
|
|
79
141
|
}
|
|
80
142
|
|
|
@@ -90,15 +152,19 @@ export class SeenPayloadEnvelopeInput {
|
|
|
90
152
|
return this.payloadInputs.size;
|
|
91
153
|
}
|
|
92
154
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
155
|
+
pruneBelowParent(parentBlock: ProtoBlock): void {
|
|
156
|
+
for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
|
|
157
|
+
if (block.slot < parentBlock.slot) {
|
|
158
|
+
const input = this.payloadInputs.get(block.blockRoot);
|
|
159
|
+
if (input) {
|
|
160
|
+
this.evictPayloadInput(input);
|
|
161
|
+
this.logger?.verbose("SeenPayloadEnvelopeInput.pruneBelowParent deleted", {
|
|
162
|
+
slot: block.slot,
|
|
163
|
+
root: block.blockRoot,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
99
166
|
}
|
|
100
167
|
}
|
|
101
|
-
this.logger?.debug("SeenPayloadEnvelopeInput.pruneBelow deleted entries", {slot, deletedCount});
|
|
102
168
|
}
|
|
103
169
|
|
|
104
170
|
private evictPayloadInput(payloadInput: PayloadEnvelopeInput): void {
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
import {Slot, ValidatorIndex} from "@lodestar/types";
|
|
1
|
+
import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
2
2
|
import {MapDef} from "@lodestar/utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Tracks signed proposer preferences we've already seen per (proposal_slot, validator_index).
|
|
5
|
+
* Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
|
|
6
6
|
*/
|
|
7
7
|
export class SeenProposerPreferences {
|
|
8
|
-
private readonly
|
|
8
|
+
private readonly validatorByDependentRootBySlot = new MapDef<Slot, Map<RootHex, ValidatorIndex>>(
|
|
9
|
+
() => new Map<RootHex, ValidatorIndex>()
|
|
10
|
+
);
|
|
9
11
|
|
|
10
|
-
isKnown(proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean {
|
|
11
|
-
return this.
|
|
12
|
+
isKnown(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean {
|
|
13
|
+
return this.validatorByDependentRootBySlot.get(proposalSlot)?.get(dependentRoot) === validatorIndex;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
add(proposalSlot: Slot, validatorIndex: ValidatorIndex): void {
|
|
15
|
-
this.
|
|
16
|
+
add(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): void {
|
|
17
|
+
this.validatorByDependentRootBySlot.getOrDefault(proposalSlot).set(dependentRoot, validatorIndex);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
|
-
* Entries are only load-bearing while `proposal_slot >
|
|
20
|
-
* `[IGNORE] proposal_slot >
|
|
21
|
+
* Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
|
|
22
|
+
* passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
|
|
23
|
+
* on each slot tick.
|
|
21
24
|
*/
|
|
22
25
|
prune(currentSlot: Slot): void {
|
|
23
|
-
for (const slot of this.
|
|
26
|
+
for (const slot of this.validatorByDependentRootBySlot.keys()) {
|
|
24
27
|
if (slot < currentSlot) {
|
|
25
|
-
this.
|
|
28
|
+
this.validatorByDependentRootBySlot.delete(slot);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
}
|
|
@@ -48,9 +48,12 @@ async function validateExecutionPayloadBid(
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// [IGNORE]
|
|
52
|
-
//
|
|
53
|
-
//
|
|
51
|
+
// [IGNORE] A `SignedProposerPreferences` matching `bid.slot` and the bid's branch has been
|
|
52
|
+
// seen — i.e. `proposal_slot == bid.slot` AND `dependent_root ==
|
|
53
|
+
// get_proposer_dependent_root(parent_state, compute_epoch_at_slot(bid.slot))`,
|
|
54
|
+
// where `parent_state` is the post-state of `bid.parent_block_root`.
|
|
55
|
+
// This is the message referenced as `proposer_preferences` in the following REJECT rules.
|
|
56
|
+
// TODO GLOAS: Implement once a ProposerPreferencesPool exists.
|
|
54
57
|
|
|
55
58
|
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
|
|
56
59
|
// `is_active_builder(state, bid.builder_index)` returns `True`.
|
|
@@ -71,11 +74,11 @@ async function validateExecutionPayloadBid(
|
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
// [REJECT] `bid.fee_recipient
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
// TODO GLOAS: Implement
|
|
77
|
+
// [REJECT] `bid.fee_recipient == proposer_preferences.fee_recipient`.
|
|
78
|
+
// [REJECT] `bid.gas_limit == proposer_preferences.gas_limit`.
|
|
79
|
+
// Both compared against the matching `proposer_preferences` defined above (same branch
|
|
80
|
+
// via dependent_root, same proposal_slot).
|
|
81
|
+
// TODO GLOAS: Implement once a ProposerPreferencesPool exists.
|
|
79
82
|
|
|
80
83
|
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
81
84
|
// consensus layer -- i.e. validate that
|
|
@@ -3,12 +3,11 @@ import {
|
|
|
3
3
|
computeEpochAtSlot,
|
|
4
4
|
createSingleSignatureSetFromComponents,
|
|
5
5
|
getProposerPreferencesSigningRoot,
|
|
6
|
-
isStatePostGloas,
|
|
7
6
|
} from "@lodestar/state-transition";
|
|
8
|
-
import {gloas} from "@lodestar/types";
|
|
7
|
+
import {ValidatorIndex, gloas} from "@lodestar/types";
|
|
8
|
+
import {toRootHex} from "@lodestar/utils";
|
|
9
9
|
import {GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode} from "../errors/index.js";
|
|
10
10
|
import {IBeaconChain} from "../index.js";
|
|
11
|
-
import {RegenCaller} from "../regen/index.js";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Validates a gossiped `SignedProposerPreferences` per
|
|
@@ -19,7 +18,8 @@ export async function validateGossipProposerPreferences(
|
|
|
19
18
|
signedProposerPreferences: gloas.SignedProposerPreferences
|
|
20
19
|
): Promise<void> {
|
|
21
20
|
const preferences = signedProposerPreferences.message;
|
|
22
|
-
const {proposalSlot, validatorIndex} = preferences;
|
|
21
|
+
const {proposalSlot, validatorIndex, dependentRoot} = preferences;
|
|
22
|
+
const dependentRootHex = toRootHex(dependentRoot);
|
|
23
23
|
const proposalEpoch = computeEpochAtSlot(proposalSlot);
|
|
24
24
|
|
|
25
25
|
// [IGNORE] `preferences.proposal_slot` is in the current or next epoch.
|
|
@@ -42,32 +42,51 @@ export async function validateGossipProposerPreferences(
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// [IGNORE] The block with root `dependent_root` has been seen by the node.
|
|
46
|
+
// Resolve the proposer lookahead for the message's branch via head state (fast path) or
|
|
47
|
+
// the previous-root checkpoint state (populated by `processSlotsToNearestCheckpoint` for
|
|
48
|
+
// any imported branch crossing into `proposalEpoch - 1`). The head-state path also handles
|
|
49
|
+
// narrow timing windows where the checkpoint state isn't yet populated.
|
|
50
|
+
const headState = chain.getHeadState();
|
|
51
|
+
let proposers: ValidatorIndex[] | null = null;
|
|
52
|
+
if (headState.epoch === proposalEpoch && headState.currentDecisionRoot === dependentRootHex) {
|
|
53
|
+
proposers = headState.currentProposers;
|
|
54
|
+
} else if (headState.epoch === proposalEpoch - 1 && headState.nextDecisionRoot === dependentRootHex) {
|
|
55
|
+
proposers = headState.nextProposers;
|
|
56
|
+
} else {
|
|
57
|
+
// Sync lookup only to not trigger disk reload from gossip input.
|
|
58
|
+
const checkpointState = chain.regen.getCheckpointStateSync({epoch: proposalEpoch - 1, rootHex: dependentRootHex});
|
|
59
|
+
if (checkpointState !== null) {
|
|
60
|
+
// State is at `proposalEpoch - 1`, so proposers for `proposalSlot` (next epoch from
|
|
61
|
+
// the state's perspective) live in `nextProposers`.
|
|
62
|
+
proposers = checkpointState.nextProposers;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (proposers === null) {
|
|
66
|
+
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
67
|
+
code: ProposerPreferencesErrorCode.UNKNOWN_DEPENDENT_ROOT,
|
|
68
|
+
proposalSlot,
|
|
69
|
+
dependentRoot: dependentRootHex,
|
|
70
|
+
});
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
// [REJECT] `preferences
|
|
51
|
-
|
|
52
|
-
// returns True.
|
|
53
|
-
const epochOffset = proposalEpoch - state.epoch;
|
|
54
|
-
const proposers = epochOffset === 0 ? state.currentProposers : state.nextProposers;
|
|
55
|
-
const expectedProposer = proposers[proposalSlot % SLOTS_PER_EPOCH];
|
|
56
|
-
if (epochOffset < 0 || epochOffset > 1 || expectedProposer !== validatorIndex) {
|
|
73
|
+
// [REJECT] `is_valid_proposal_slot(state, preferences)` returns True.
|
|
74
|
+
if (proposers[proposalSlot % SLOTS_PER_EPOCH] !== validatorIndex) {
|
|
57
75
|
throw new ProposerPreferencesError(GossipAction.REJECT, {
|
|
58
76
|
code: ProposerPreferencesErrorCode.INVALID_PROPOSER,
|
|
59
77
|
proposalSlot,
|
|
60
78
|
validatorIndex,
|
|
79
|
+
dependentRoot: dependentRootHex,
|
|
61
80
|
});
|
|
62
81
|
}
|
|
63
82
|
|
|
64
|
-
// [IGNORE]
|
|
65
|
-
|
|
66
|
-
if (chain.seenProposerPreferences.isKnown(proposalSlot, validatorIndex)) {
|
|
83
|
+
// [IGNORE] First valid message for (dependent_root, proposal_slot, validator_index).
|
|
84
|
+
if (chain.seenProposerPreferences.isKnown(dependentRootHex, proposalSlot, validatorIndex)) {
|
|
67
85
|
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
68
86
|
code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
|
|
69
87
|
proposalSlot,
|
|
70
88
|
validatorIndex,
|
|
89
|
+
dependentRoot: dependentRootHex,
|
|
71
90
|
});
|
|
72
91
|
}
|
|
73
92
|
|
|
@@ -87,5 +106,5 @@ export async function validateGossipProposerPreferences(
|
|
|
87
106
|
}
|
|
88
107
|
|
|
89
108
|
// Valid
|
|
90
|
-
chain.seenProposerPreferences.add(proposalSlot, validatorIndex);
|
|
109
|
+
chain.seenProposerPreferences.add(dependentRootHex, proposalSlot, validatorIndex);
|
|
91
110
|
}
|
package/src/network/interface.ts
CHANGED
|
@@ -112,6 +112,7 @@ export interface INetwork extends INetworkCorePublic {
|
|
|
112
112
|
publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise<number>;
|
|
113
113
|
publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise<number>;
|
|
114
114
|
publishSignedExecutionPayloadEnvelope(signedEnvelope: gloas.SignedExecutionPayloadEnvelope): Promise<number>;
|
|
115
|
+
publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number>;
|
|
115
116
|
|
|
116
117
|
// Debug
|
|
117
118
|
dumpGossipQueue(gossipType: GossipType): Promise<PendingGossipsubMessage[]>;
|
package/src/network/network.ts
CHANGED
|
@@ -515,6 +515,17 @@ export class Network implements INetwork {
|
|
|
515
515
|
);
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
+
async publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number> {
|
|
519
|
+
const epoch = computeEpochAtSlot(payloadAttestationMessage.data.slot);
|
|
520
|
+
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
|
|
521
|
+
|
|
522
|
+
return this.publishGossip<GossipType.payload_attestation_message>(
|
|
523
|
+
{type: GossipType.payload_attestation_message, boundary},
|
|
524
|
+
payloadAttestationMessage,
|
|
525
|
+
{ignoreDuplicatePublishError: true}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
518
529
|
private async publishGossip<K extends GossipType>(
|
|
519
530
|
topic: GossipTopicMap[K],
|
|
520
531
|
object: GossipTypeMap[K],
|
|
@@ -52,6 +52,8 @@ import {
|
|
|
52
52
|
ExecutionPayloadEnvelopeErrorCode,
|
|
53
53
|
GossipAction,
|
|
54
54
|
GossipActionError,
|
|
55
|
+
PayloadAttestationError,
|
|
56
|
+
PayloadAttestationErrorCode,
|
|
55
57
|
SyncCommitteeError,
|
|
56
58
|
} from "../../chain/errors/index.js";
|
|
57
59
|
import {IBeaconChain} from "../../chain/interface.js";
|
|
@@ -183,6 +185,18 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
183
185
|
});
|
|
184
186
|
try {
|
|
185
187
|
await validateGossipBlock(config, chain, signedBlock, fork);
|
|
188
|
+
|
|
189
|
+
if (isForkPostGloas(fork)) {
|
|
190
|
+
chain.seenPayloadEnvelopeInputCache.add({
|
|
191
|
+
blockRootHex,
|
|
192
|
+
block: signedBlock as SignedBeaconBlock<ForkPostGloas>,
|
|
193
|
+
forkName: fork,
|
|
194
|
+
sampledColumns: chain.custodyConfig.sampledColumns,
|
|
195
|
+
custodyColumns: chain.custodyConfig.custodyColumns,
|
|
196
|
+
timeCreatedSec: seenTimestampSec,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
186
200
|
const blockInputMeta = blockInput.getLogMeta();
|
|
187
201
|
|
|
188
202
|
const recvToValidation = Date.now() / 1000 - seenTimestampSec;
|
|
@@ -593,6 +607,24 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
593
607
|
// Returns the delay between the start of `block.slot` and `current time`
|
|
594
608
|
const delaySec = chain.clock.secFromSlot(slot);
|
|
595
609
|
metrics?.gossipBlock.elapsedTimeTillProcessed.observe(delaySec);
|
|
610
|
+
|
|
611
|
+
if (isForkPostGloas(blockInput.forkName)) {
|
|
612
|
+
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockInput.blockRootHex);
|
|
613
|
+
// This payloadInput should have been created just after gossip validation
|
|
614
|
+
if (!payloadInput) {
|
|
615
|
+
throw Error(
|
|
616
|
+
`PayloadEnvelopeInput not seeded for block ${blockInput.blockRootHex} during gossip processing`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Immediately attempt fetch of data columns from execution engine as the bid contains kzg commitments
|
|
621
|
+
// which is all the information we need so there is no reason to delay until execution payload arrives
|
|
622
|
+
// TODO GLOAS: If we want EL retries after this initial attempt, add an explicit retry policy here
|
|
623
|
+
// (for example later in the slot). Do not couple retries to incoming gossip columns.
|
|
624
|
+
// Columns fetched here feed payloadInput.addColumn, which resolves waitForAllData for any
|
|
625
|
+
// in-flight importExecutionPayload. No processExecutionPayload trigger needed from this path.
|
|
626
|
+
chain.getBlobsTracker.triggerGetBlobs(payloadInput);
|
|
627
|
+
}
|
|
596
628
|
})
|
|
597
629
|
.catch((e) => {
|
|
598
630
|
// Adjust verbosity based on error type
|
|
@@ -1070,13 +1102,6 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1070
1102
|
const {beaconBlockRoot} = signedEnvelope.message;
|
|
1071
1103
|
const slot = signedEnvelope.message.payload.slotNumber;
|
|
1072
1104
|
logger.debug("Gossip envelope has error", {slot, root: toRootHex(beaconBlockRoot), code: e.type.code});
|
|
1073
|
-
if (e.type.code === ExecutionPayloadEnvelopeErrorCode.BLOCK_ROOT_UNKNOWN) {
|
|
1074
|
-
chain.emitter.emit(ChainEvent.envelopeUnknownBlock, {
|
|
1075
|
-
envelope: signedEnvelope,
|
|
1076
|
-
peer: peerIdStr,
|
|
1077
|
-
source: BlockInputSource.gossip,
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
1105
|
|
|
1081
1106
|
if (e.action === GossipAction.REJECT) {
|
|
1082
1107
|
chain.persistInvalidSszValue(
|
|
@@ -1301,10 +1326,12 @@ export async function validateGossipFnRetryUnknownRoot<T>(
|
|
|
1301
1326
|
try {
|
|
1302
1327
|
return await fn();
|
|
1303
1328
|
} catch (e) {
|
|
1304
|
-
|
|
1305
|
-
e instanceof AttestationError &&
|
|
1306
|
-
|
|
1307
|
-
|
|
1329
|
+
const isUnknownAttestationRoot =
|
|
1330
|
+
e instanceof AttestationError && e.type.code === AttestationErrorCode.UNKNOWN_OR_PREFINALIZED_BEACON_BLOCK_ROOT;
|
|
1331
|
+
const isUnknownPayloadAttestationRoot =
|
|
1332
|
+
e instanceof PayloadAttestationError && e.type.code === PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT;
|
|
1333
|
+
|
|
1334
|
+
if (isUnknownAttestationRoot || isUnknownPayloadAttestationRoot) {
|
|
1308
1335
|
if (unknownBlockRootRetries === 0) {
|
|
1309
1336
|
// Trigger unknown block root search here
|
|
1310
1337
|
const rootHex = toRootHex(blockRoot);
|
|
@@ -76,6 +76,7 @@ type WorkOpts = {
|
|
|
76
76
|
*/
|
|
77
77
|
const executeGossipWorkOrderObj: Record<GossipType, WorkOpts> = {
|
|
78
78
|
[GossipType.beacon_block]: {bypassQueue: true},
|
|
79
|
+
[GossipType.execution_payload]: {bypassQueue: true},
|
|
79
80
|
[GossipType.blob_sidecar]: {bypassQueue: true},
|
|
80
81
|
[GossipType.data_column_sidecar]: {bypassQueue: true},
|
|
81
82
|
[GossipType.beacon_aggregate_and_proof]: {},
|
|
@@ -88,7 +89,6 @@ const executeGossipWorkOrderObj: Record<GossipType, WorkOpts> = {
|
|
|
88
89
|
[GossipType.sync_committee]: {},
|
|
89
90
|
[GossipType.light_client_finality_update]: {},
|
|
90
91
|
[GossipType.light_client_optimistic_update]: {},
|
|
91
|
-
[GossipType.execution_payload]: {bypassQueue: true},
|
|
92
92
|
[GossipType.payload_attestation_message]: {},
|
|
93
93
|
[GossipType.execution_payload_bid]: {},
|
|
94
94
|
[GossipType.proposer_preferences]: {},
|
|
@@ -444,8 +444,7 @@ export class NetworkProcessor {
|
|
|
444
444
|
}
|
|
445
445
|
case GossipType.execution_payload: {
|
|
446
446
|
// extractBlockSlotRootFn does not return a root for this topic.
|
|
447
|
-
// Extract beacon_block_root directly
|
|
448
|
-
// Do NOT await the block — the handler runs immediately; BlockInputSync handles recovery.
|
|
447
|
+
// Extract beacon_block_root directly
|
|
449
448
|
const blockRoot = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(message.msg.data);
|
|
450
449
|
if (blockRoot && !this.chain.forkChoice.hasBlockHexUnsafe(blockRoot)) {
|
|
451
450
|
this.searchUnknownBlock(
|
|
@@ -453,9 +452,10 @@ export class NetworkProcessor {
|
|
|
453
452
|
BlockInputSource.network_processor,
|
|
454
453
|
message.propagationSource.toString()
|
|
455
454
|
);
|
|
455
|
+
// We always want to await the block
|
|
456
|
+
// This allows us to properly forward the payload envelope
|
|
457
|
+
preprocessResult = {action: PreprocessAction.AwaitBlock, root: blockRoot};
|
|
456
458
|
}
|
|
457
|
-
// do not await the block, we want UnknownBlockSync to handle it.
|
|
458
|
-
preprocessResult = {action: PreprocessAction.PushToQueue};
|
|
459
459
|
break;
|
|
460
460
|
}
|
|
461
461
|
case GossipType.execution_payload_bid: {
|
package/src/node/nodejs.ts
CHANGED
|
@@ -221,7 +221,7 @@ export class BeaconNode {
|
|
|
221
221
|
|
|
222
222
|
let executionEngineOpts = opts.executionEngine;
|
|
223
223
|
if (opts.executionEngine.mode === "mock") {
|
|
224
|
-
const
|
|
224
|
+
const latestEth1BlockHash =
|
|
225
225
|
isStatePostBellatrix(anchorState) && anchorState.isExecutionStateType
|
|
226
226
|
? isStatePostGloas(anchorState)
|
|
227
227
|
? toRootHex(anchorState.latestBlockHash)
|
|
@@ -230,7 +230,7 @@ export class BeaconNode {
|
|
|
230
230
|
executionEngineOpts = {
|
|
231
231
|
...opts.executionEngine,
|
|
232
232
|
genesisBlockHash: ZERO_HASH_HEX,
|
|
233
|
-
eth1BlockHash,
|
|
233
|
+
eth1BlockHash: opts.executionEngine.eth1BlockHash ?? latestEth1BlockHash,
|
|
234
234
|
genesisTime: anchorState.genesisTime,
|
|
235
235
|
config,
|
|
236
236
|
};
|
package/src/node/notifier.ts
CHANGED
|
@@ -167,14 +167,7 @@ function getHeadExecutionInfo(
|
|
|
167
167
|
return [];
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
// arrives, in that case the exec-block row surfaces the inherited parent anchor (from the
|
|
172
|
-
// bid), which is already validated. Normalize to "valid" to avoid leaking internal
|
|
173
|
-
// fork-choice bookkeeping into the log. Once the payload envelope arrives and the FULL
|
|
174
|
-
// variant becomes head, executionStatus is Valid/Syncing naturally.
|
|
175
|
-
// TODO GLOAS: revisit once optimistic sync is implemented
|
|
176
|
-
const executionStatusStr =
|
|
177
|
-
headInfo.executionStatus === ExecutionStatus.PayloadSeparated ? "valid" : headInfo.executionStatus.toLowerCase();
|
|
170
|
+
const executionStatusStr = headInfo.executionStatus.toLowerCase();
|
|
178
171
|
|
|
179
172
|
// Add execution status to notifier only if head is on/post bellatrix
|
|
180
173
|
if (isStatePostBellatrix(headState) && headState.isExecutionStateType) {
|
package/src/sync/constants.ts
CHANGED
|
@@ -5,10 +5,10 @@ export const PARALLEL_HEAD_CHAINS = 2;
|
|
|
5
5
|
export const MIN_FINALIZED_CHAIN_VALIDATED_EPOCHS = 10;
|
|
6
6
|
|
|
7
7
|
/** The number of times to retry a batch before it is considered failed. */
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export const
|
|
8
|
+
export const MAX_BATCH_DOWNLOAD_ATTEMPTS = 5;
|
|
9
|
+
|
|
10
|
+
/** Backoff before assigning more range-sync batches to a peer that rate-limited us. */
|
|
11
|
+
export const RATE_LIMITED_PEER_BACKOFF_MS = 5_000;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Consider batch faulty after downloading and processing this number of times
|