@lodestar/beacon-node 1.47.0-dev.9572bcc698 → 1.47.0-dev.9ba9a5ce85
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 +41 -10
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +1 -1
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +237 -49
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/impl/validator/utils.js +1 -1
- package/lib/api/impl/validator/utils.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +1 -1
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/blocks/index.js +7 -7
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +5 -2
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/chain.d.ts +5 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +6 -2
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +1 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +4 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +4 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/executionPayloadBidPool.d.ts +7 -2
- package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
- package/lib/chain/opPools/executionPayloadBidPool.js +4 -4
- package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
- package/lib/chain/opPools/index.d.ts +1 -1
- package/lib/chain/opPools/index.d.ts.map +1 -1
- package/lib/chain/opPools/index.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts +5 -0
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +47 -6
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +3 -25
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +3 -2
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +1 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/regen/queued.d.ts +2 -2
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +5 -5
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
- package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/stateCache/types.d.ts +3 -3
- package/lib/chain/stateCache/types.d.ts.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +40 -10
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts +15 -3
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +165 -10
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/execution/builder/apiClient.d.ts +70 -0
- package/lib/execution/builder/apiClient.d.ts.map +1 -0
- package/lib/execution/builder/apiClient.js +202 -0
- package/lib/execution/builder/apiClient.js.map +1 -0
- package/lib/execution/builder/validateBid.d.ts +23 -0
- package/lib/execution/builder/validateBid.d.ts.map +1 -0
- package/lib/execution/builder/validateBid.js +119 -0
- package/lib/execution/builder/validateBid.js.map +1 -0
- package/lib/metrics/metrics/lodestar.d.ts +29 -1
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +61 -3
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +5 -4
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/constants.d.ts +7 -0
- package/lib/network/processor/constants.d.ts.map +1 -0
- package/lib/network/processor/constants.js +7 -0
- package/lib/network/processor/constants.js.map +1 -0
- package/lib/network/processor/gossipHandlers.js +1 -1
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipQueues/indexed.d.ts.map +1 -1
- package/lib/network/processor/gossipQueues/indexed.js +0 -1
- package/lib/network/processor/gossipQueues/indexed.js.map +1 -1
- package/lib/network/processor/index.d.ts +3 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +52 -23
- package/lib/network/processor/index.js.map +1 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +5 -0
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +9 -2
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.js +1 -1
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +6 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +48 -17
- package/lib/sync/unknownBlock.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +48 -13
- package/src/api/impl/lodestar/index.ts +1 -1
- package/src/api/impl/validator/index.ts +290 -49
- package/src/api/impl/validator/utils.ts +1 -1
- package/src/chain/archiveStore/archiveStore.ts +1 -1
- package/src/chain/blocks/index.ts +7 -7
- package/src/chain/blocks/verifyBlock.ts +5 -2
- package/src/chain/chain.ts +8 -1
- package/src/chain/errors/executionPayloadBid.ts +6 -0
- package/src/chain/forkChoice/index.ts +4 -0
- package/src/chain/interface.ts +4 -2
- package/src/chain/opPools/executionPayloadBidPool.ts +13 -7
- package/src/chain/opPools/index.ts +1 -1
- package/src/chain/prepareNextSlot.ts +49 -6
- package/src/chain/produceBlock/computeNewStateRoot.ts +3 -38
- package/src/chain/produceBlock/produceBlockBody.ts +1 -1
- package/src/chain/regen/interface.ts +3 -2
- package/src/chain/regen/queued.ts +5 -5
- package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
- package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
- package/src/chain/stateCache/types.ts +3 -3
- package/src/chain/validation/block.ts +43 -10
- package/src/chain/validation/executionPayloadBid.ts +192 -12
- package/src/execution/builder/apiClient.ts +264 -0
- package/src/execution/builder/validateBid.ts +181 -0
- package/src/metrics/metrics/lodestar.ts +64 -4
- package/src/network/network.ts +7 -4
- package/src/network/processor/constants.ts +6 -0
- package/src/network/processor/gossipHandlers.ts +1 -1
- package/src/network/processor/gossipQueues/indexed.ts +0 -1
- package/src/network/processor/index.ts +67 -30
- package/src/node/nodejs.ts +5 -0
- package/src/sync/range/chain.ts +9 -2
- package/src/sync/range/range.ts +1 -1
- package/src/sync/unknownBlock.ts +62 -18
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {routes} from "@lodestar/api";
|
|
2
|
+
import {ProtoBlock} from "@lodestar/fork-choice";
|
|
3
|
+
import {MAX_EXECUTION_PAYMENT, PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
|
|
4
|
+
import {
|
|
5
|
+
computeEpochAtSlot,
|
|
6
|
+
createSingleSignatureSetFromComponents,
|
|
7
|
+
getExecutionPayloadBidSigningRoot,
|
|
8
|
+
isActiveBuilder,
|
|
9
|
+
isGasLimitTargetCompatible,
|
|
10
|
+
isStatePostGloas,
|
|
11
|
+
} from "@lodestar/state-transition";
|
|
12
|
+
import {RootHex, Slot, gloas} from "@lodestar/types";
|
|
13
|
+
import {bigIntMin, byteArrayEquals, prettyGweiToEth, toHex, toRootHex} from "@lodestar/utils";
|
|
14
|
+
import {IBeaconChain} from "../../chain/index.js";
|
|
15
|
+
import {RegenCaller} from "../../chain/regen/index.js";
|
|
16
|
+
import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
|
|
17
|
+
|
|
18
|
+
/** Return the counted bid payment in Gwei, saturated at uint64 max. */
|
|
19
|
+
export function getBuilderBidTotalGwei(bid: gloas.ExecutionPayloadBid, maxExecutionPayment: bigint): bigint {
|
|
20
|
+
return bigIntMin(MAX_EXECUTION_PAYMENT, BigInt(bid.value) + bigIntMin(bid.executionPayment, maxExecutionPayment));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate a bid received from a builder over the builder API in response to a bid request
|
|
25
|
+
* made during block production. Unlike gossip validation, the bid must match the requested
|
|
26
|
+
* slot and parent exactly, may carry a non-zero `executionPayment` which is counted at most at
|
|
27
|
+
* the entry's `maxExecutionPayment`, and is not subject to gossip anti-spam rules.
|
|
28
|
+
*
|
|
29
|
+
* Throws with a description of the failure, the caller drops the bid.
|
|
30
|
+
*/
|
|
31
|
+
export async function validateBuilderApiExecutionPayloadBid(
|
|
32
|
+
chain: IBeaconChain,
|
|
33
|
+
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid,
|
|
34
|
+
request: {
|
|
35
|
+
slot: Slot;
|
|
36
|
+
parentBlock: ProtoBlock;
|
|
37
|
+
parentBlockHash: RootHex;
|
|
38
|
+
parentBlockRoot: RootHex;
|
|
39
|
+
entry: routes.validator.BuilderEntry;
|
|
40
|
+
getParentExecutionRequests: () => Promise<gloas.ExecutionRequests>;
|
|
41
|
+
}
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const bid = signedExecutionPayloadBid.message;
|
|
44
|
+
const {slot, parentBlock, parentBlockHash, parentBlockRoot, entry, getParentExecutionRequests} = request;
|
|
45
|
+
|
|
46
|
+
if (bid.slot !== slot) {
|
|
47
|
+
throw Error(`Bid slot=${bid.slot} does not match requested slot=${slot}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const bidParentBlockHash = toRootHex(bid.parentBlockHash);
|
|
51
|
+
const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
|
|
52
|
+
if (bidParentBlockHash !== parentBlockHash || bidParentBlockRoot !== parentBlockRoot) {
|
|
53
|
+
throw Error(
|
|
54
|
+
`Bid parent parentBlockHash=${bidParentBlockHash} parentBlockRoot=${bidParentBlockRoot} does not match ` +
|
|
55
|
+
`requested parentBlockHash=${parentBlockHash} parentBlockRoot=${parentBlockRoot}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const totalPayment = getBuilderBidTotalGwei(bid, entry.maxExecutionPayment);
|
|
60
|
+
if (totalPayment < entry.minBid) {
|
|
61
|
+
throw Error(
|
|
62
|
+
`Bid total payment=${prettyGweiToEth(totalPayment)} ` +
|
|
63
|
+
`(value=${prettyGweiToEth(bid.value)} ` +
|
|
64
|
+
`executionPayment=${prettyGweiToEth(bid.executionPayment)}) ` +
|
|
65
|
+
`is below minBid=${prettyGweiToEth(entry.minBid)}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const state = await chain.regen
|
|
70
|
+
.getBlockSlotState(parentBlock, slot, {dontTransferCache: true}, RegenCaller.validateGossipExecutionPayloadBid)
|
|
71
|
+
.catch((e: Error) => {
|
|
72
|
+
throw Error(`Unable to regenerate state to validate bid: ${e.message}`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!isStatePostGloas(state)) {
|
|
76
|
+
throw Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (bid.builderIndex >= state.getBuildersLength()) {
|
|
80
|
+
throw Error(`Bid builderIndex=${bid.builderIndex} is out of bounds`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const builder = state.getBuilder(bid.builderIndex);
|
|
84
|
+
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
85
|
+
throw Error(`Bid builderIndex=${bid.builderIndex} is not an active builder`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (builder.version !== PAYLOAD_BUILDER_VERSION) {
|
|
89
|
+
throw Error(`Invalid builder version=${builder.version} expected=${PAYLOAD_BUILDER_VERSION}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// A bid not signed by one of the builder pubkeys the entry accepts bids from must not be accepted
|
|
93
|
+
if (
|
|
94
|
+
entry.builderPubkeys.length > 0 &&
|
|
95
|
+
!entry.builderPubkeys.some((pubkey) => byteArrayEquals(pubkey, builder.pubkey))
|
|
96
|
+
) {
|
|
97
|
+
throw Error(
|
|
98
|
+
`Bid builder pubkey=${toHex(builder.pubkey)} is not in the entry's ` +
|
|
99
|
+
`builderPubkeys=${entry.builderPubkeys.map(toHex).join(",")}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
104
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
105
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
106
|
+
throw Error(`Bid has too many KZG commitments len=${blobKzgCommitmentsLen} limit=${maxBlobsPerBlock}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The coverage check only applies to the staked collateral payment, a pure execution
|
|
110
|
+
// layer payment bid has nothing to cover on-chain
|
|
111
|
+
if (bid.value > 0 && !state.canBuilderCoverBid(bid.builderIndex, bid.value)) {
|
|
112
|
+
throw Error(
|
|
113
|
+
`Builder cannot cover bid value=${prettyGweiToEth(bid.value)} ` + `balance=${prettyGweiToEth(builder.balance)}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
|
|
118
|
+
if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
|
|
119
|
+
throw Error(`Invalid bid prevRandao=${toHex(bid.prevRandao)} expected=${toHex(randaoMix)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The parent's payload does not try to exit the builder
|
|
123
|
+
if (byteArrayEquals(bid.parentBlockHash, state.latestExecutionPayloadBid.blockHash)) {
|
|
124
|
+
const requests = await getParentExecutionRequests();
|
|
125
|
+
if (
|
|
126
|
+
requests.builderExits.some(
|
|
127
|
+
(request) =>
|
|
128
|
+
byteArrayEquals(request.pubkey, builder.pubkey) &&
|
|
129
|
+
byteArrayEquals(request.sourceAddress, builder.executionAddress)
|
|
130
|
+
)
|
|
131
|
+
) {
|
|
132
|
+
throw Error(`Bid builderIndex=${bid.builderIndex} may exit in the parent payload`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The builder must honor the proposer preferences it learned over gossip
|
|
137
|
+
const bidEpoch = computeEpochAtSlot(bid.slot);
|
|
138
|
+
const dependentRootHex = (() => {
|
|
139
|
+
try {
|
|
140
|
+
return getShufflingDependentRoot(chain.forkChoice, bidEpoch, computeEpochAtSlot(parentBlock.slot), parentBlock);
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
})();
|
|
145
|
+
if (dependentRootHex === null) {
|
|
146
|
+
throw Error(`Unable to resolve proposer preferences dependent root for bid slot=${bid.slot}`);
|
|
147
|
+
}
|
|
148
|
+
const proposerPreferences = chain.proposerPreferencesPool.get(bid.slot, dependentRootHex);
|
|
149
|
+
if (proposerPreferences === null) {
|
|
150
|
+
throw Error(`No proposer preferences found for bid slot=${bid.slot} dependentRoot=${dependentRootHex}`);
|
|
151
|
+
}
|
|
152
|
+
if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
|
|
153
|
+
throw Error(
|
|
154
|
+
`Bid feeRecipient=${toHex(bid.feeRecipient)} does not match ` +
|
|
155
|
+
`proposer preferences feeRecipient=${toHex(proposerPreferences.message.feeRecipient)}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const parentPayloadVariant = chain.forkChoice.getBlockHexAndBlockHash(bidParentBlockRoot, bidParentBlockHash);
|
|
160
|
+
if (parentPayloadVariant === null || parentPayloadVariant.executionPayloadBlockHash === null) {
|
|
161
|
+
throw Error(`Unable to resolve parent payload gas limit for bid parentBlockHash=${bidParentBlockHash}`);
|
|
162
|
+
}
|
|
163
|
+
const parentGasLimit = BigInt(parentPayloadVariant.executionPayloadGasLimit);
|
|
164
|
+
const targetGasLimit = proposerPreferences.message.targetGasLimit;
|
|
165
|
+
if (!isGasLimitTargetCompatible(parentGasLimit, bid.gasLimit, targetGasLimit)) {
|
|
166
|
+
throw Error(
|
|
167
|
+
`Bid gasLimit=${bid.gasLimit} is not compatible with ` +
|
|
168
|
+
`parentGasLimit=${parentGasLimit} targetGasLimit=${targetGasLimit}`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const signatureSet = createSingleSignatureSetFromComponents(
|
|
173
|
+
builder.pubkey,
|
|
174
|
+
getExecutionPayloadBidSigningRoot(chain.config, bid),
|
|
175
|
+
signedExecutionPayloadBid.signature
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
|
|
179
|
+
throw Error(`Invalid bid signature builderIndex=${bid.builderIndex}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -83,15 +83,15 @@ export function createLodestarMetrics(
|
|
|
83
83
|
}),
|
|
84
84
|
jobTime: register.histogram<{topic: GossipType}>({
|
|
85
85
|
name: "lodestar_gossip_validation_queue_job_time_seconds",
|
|
86
|
-
help: "Time to process gossip validation queue job in seconds",
|
|
86
|
+
help: "Time to process gossip validation queue job in seconds (accepted messages only)",
|
|
87
87
|
labelNames: ["topic"],
|
|
88
|
-
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2
|
|
88
|
+
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2],
|
|
89
89
|
}),
|
|
90
90
|
jobWaitTime: register.histogram<{topic: GossipType}>({
|
|
91
91
|
name: "lodestar_gossip_validation_queue_job_wait_time_seconds",
|
|
92
92
|
help: "Time from job added to the queue to starting the job in seconds",
|
|
93
93
|
labelNames: ["topic"],
|
|
94
|
-
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2
|
|
94
|
+
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2],
|
|
95
95
|
}),
|
|
96
96
|
concurrency: register.gauge<{topic: GossipType}>({
|
|
97
97
|
name: "lodestar_gossip_validation_queue_concurrency",
|
|
@@ -104,9 +104,10 @@ export function createLodestarMetrics(
|
|
|
104
104
|
help: "Age of the first item of each key in the indexed queues in seconds",
|
|
105
105
|
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 5],
|
|
106
106
|
}),
|
|
107
|
-
queueTime: register.histogram({
|
|
107
|
+
queueTime: register.histogram<{topic: GossipType}>({
|
|
108
108
|
name: "lodestar_gossip_validation_queue_time_seconds",
|
|
109
109
|
help: "Total time an item stays in queue until it is processed in seconds",
|
|
110
|
+
labelNames: ["topic"],
|
|
110
111
|
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 5],
|
|
111
112
|
}),
|
|
112
113
|
},
|
|
@@ -920,6 +921,12 @@ export function createLodestarMetrics(
|
|
|
920
921
|
help: "Count of errors, by error type, while processing blocks",
|
|
921
922
|
labelNames: ["error"],
|
|
922
923
|
}),
|
|
924
|
+
|
|
925
|
+
preStateSource: register.counter<{source: "parentState" | "fallbackPreState" | "preState"}>({
|
|
926
|
+
name: "lodestar_gossip_block_validation_pre_state_source_total",
|
|
927
|
+
help: "Source of the pre-state used for gossip block validation",
|
|
928
|
+
labelNames: ["source"],
|
|
929
|
+
}),
|
|
923
930
|
},
|
|
924
931
|
gossipBlob: {
|
|
925
932
|
recvToValidation: register.histogram({
|
|
@@ -1350,6 +1357,11 @@ export function createLodestarMetrics(
|
|
|
1350
1357
|
help: "Total number of InsertOutcome as a result of adding an execution payload bid from api to the pool",
|
|
1351
1358
|
labelNames: ["insertOutcome"],
|
|
1352
1359
|
}),
|
|
1360
|
+
apiValidationTime: register.histogram({
|
|
1361
|
+
name: "lodestar_api_execution_payload_bid_validation_time_seconds",
|
|
1362
|
+
help: "Time elapsed for signed execution payload bid validation - api path",
|
|
1363
|
+
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.5],
|
|
1364
|
+
}),
|
|
1353
1365
|
},
|
|
1354
1366
|
},
|
|
1355
1367
|
|
|
@@ -1444,6 +1456,16 @@ export function createLodestarMetrics(
|
|
|
1444
1456
|
help: "Checkpoint state cache size",
|
|
1445
1457
|
labelNames: ["type"],
|
|
1446
1458
|
}),
|
|
1459
|
+
persistentTierEpochs: register.gauge<{tier: number}>({
|
|
1460
|
+
name: "lodestar_cp_state_cache_persistent_tier_epochs",
|
|
1461
|
+
help: "Number of epoch keys retained in each on-disk checkpoint state retention tier",
|
|
1462
|
+
labelNames: ["tier"],
|
|
1463
|
+
}),
|
|
1464
|
+
persistentTierStates: register.gauge<{tier: number}>({
|
|
1465
|
+
name: "lodestar_cp_state_cache_persistent_tier_states",
|
|
1466
|
+
help: "Number of persisted checkpoint states retained in each on-disk retention tier",
|
|
1467
|
+
labelNames: ["tier"],
|
|
1468
|
+
}),
|
|
1447
1469
|
reads: register.avgMinMax({
|
|
1448
1470
|
name: "lodestar_cp_state_epoch_reads",
|
|
1449
1471
|
help: "Avg min max of all state cache items total read count",
|
|
@@ -1776,6 +1798,10 @@ export function createLodestarMetrics(
|
|
|
1776
1798
|
name: "lodestar_precompute_next_epoch_transition_waste_total",
|
|
1777
1799
|
help: "Total number of precomputing next epoch transition wasted",
|
|
1778
1800
|
}),
|
|
1801
|
+
predictedReorg: register.counter({
|
|
1802
|
+
name: "lodestar_precompute_next_epoch_transition_predicted_reorg_total",
|
|
1803
|
+
help: "Predicted epoch-boundary reorgs where the strong parent was dialed instead of the weak head",
|
|
1804
|
+
}),
|
|
1779
1805
|
duration: register.histogram({
|
|
1780
1806
|
name: "lodestar_precompute_next_epoch_transition_duration_seconds",
|
|
1781
1807
|
help: "Duration of precomputeNextEpochTransition, including epoch transition and hashTreeRoot",
|
|
@@ -2015,6 +2041,40 @@ export function createLodestarMetrics(
|
|
|
2015
2041
|
}),
|
|
2016
2042
|
},
|
|
2017
2043
|
|
|
2044
|
+
builderApi: {
|
|
2045
|
+
statusChecks: register.counter<{status: "success" | "error"}>({
|
|
2046
|
+
name: "lodestar_builder_api_status_checks_total",
|
|
2047
|
+
help: "Total count of status checks sent to external builders ahead of a proposal",
|
|
2048
|
+
labelNames: ["status"],
|
|
2049
|
+
}),
|
|
2050
|
+
bidRequests: register.counter({
|
|
2051
|
+
name: "lodestar_builder_api_bid_requests_total",
|
|
2052
|
+
help: "Total count of execution payload bid requests sent to external builders",
|
|
2053
|
+
}),
|
|
2054
|
+
bidRequestErrors: register.counter({
|
|
2055
|
+
name: "lodestar_builder_api_bid_request_errors_total",
|
|
2056
|
+
help: "Total count of failed execution payload bid requests to external builders",
|
|
2057
|
+
}),
|
|
2058
|
+
bidsReceived: register.counter({
|
|
2059
|
+
name: "lodestar_builder_api_bids_received_total",
|
|
2060
|
+
help: "Total count of execution payload bids received from external builders",
|
|
2061
|
+
}),
|
|
2062
|
+
bidsDiscarded: register.counter({
|
|
2063
|
+
name: "lodestar_builder_api_bids_discarded_total",
|
|
2064
|
+
help: "Total count of execution payload bids from external builders discarded due to failed validation",
|
|
2065
|
+
}),
|
|
2066
|
+
blockSubmissions: register.counter<{status: "success" | "error"}>({
|
|
2067
|
+
name: "lodestar_builder_api_block_submissions_total",
|
|
2068
|
+
help: "Total count of signed beacon blocks submitted to external builders",
|
|
2069
|
+
labelNames: ["status"],
|
|
2070
|
+
}),
|
|
2071
|
+
preferencesForwarded: register.counter<{status: "success" | "error"}>({
|
|
2072
|
+
name: "lodestar_builder_api_preferences_forwarded_total",
|
|
2073
|
+
help: "Total count of builder preferences forwarded to external builders",
|
|
2074
|
+
labelNames: ["status"],
|
|
2075
|
+
}),
|
|
2076
|
+
},
|
|
2077
|
+
|
|
2018
2078
|
db: {
|
|
2019
2079
|
dbReadReq: register.gauge<{bucket: string}>({
|
|
2020
2080
|
name: "lodestar_db_read_req_total",
|
package/src/network/network.ts
CHANGED
|
@@ -516,7 +516,8 @@ export class Network implements INetwork {
|
|
|
516
516
|
|
|
517
517
|
return this.publishGossip<GossipType.execution_payload_bid>(
|
|
518
518
|
{type: GossipType.execution_payload_bid, boundary},
|
|
519
|
-
signedBid
|
|
519
|
+
signedBid,
|
|
520
|
+
{floodPublish: true}
|
|
520
521
|
);
|
|
521
522
|
}
|
|
522
523
|
|
|
@@ -542,17 +543,19 @@ export class Network implements INetwork {
|
|
|
542
543
|
|
|
543
544
|
private async publishGossip<K extends GossipType>(
|
|
544
545
|
topic: GossipTopicMap[K],
|
|
545
|
-
object: GossipTypeMap[K]
|
|
546
|
+
object: GossipTypeMap[K],
|
|
547
|
+
opts?: PublishOpts
|
|
546
548
|
): Promise<number> {
|
|
547
549
|
const topicStr = stringifyGossipTopic(this.config, topic);
|
|
548
550
|
const sszType = getGossipSSZType(topic);
|
|
549
551
|
const messageData = (sszType.serialize as (object: GossipTypeMap[GossipType]) => Uint8Array)(object);
|
|
550
|
-
const
|
|
552
|
+
const publishOpts: PublishOpts = {
|
|
551
553
|
ignoreDuplicatePublishError: gossipTopicIgnoreDuplicatePublishError[topic.type],
|
|
552
554
|
// Leave undefined unless the topic opts out, so `--network.allowPublishToZeroPeers` still applies
|
|
553
555
|
allowPublishToZeroTopicPeers: gossipTopicAllowPublishToZeroPeers[topic.type] ? true : undefined,
|
|
556
|
+
...opts,
|
|
554
557
|
};
|
|
555
|
-
const sentPeers = await this.core.publishGossip(topicStr, messageData,
|
|
558
|
+
const sentPeers = await this.core.publishGossip(topicStr, messageData, publishOpts);
|
|
556
559
|
|
|
557
560
|
this.logger.verbose("Publish to topic", {topic: topicStr, sentPeers, currentSlot: this.clock.currentSlot});
|
|
558
561
|
return sentPeers;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Max distinct peers tracked per unknown block/payload root.
|
|
3
|
+
* Normally we have 8 mesh peers per topic, and multi topics may point to the same root.
|
|
4
|
+
* Having 32 peers per root seems enough to search for missing blocks/payloads.
|
|
5
|
+
**/
|
|
6
|
+
export const MAX_PEERS_PER_ROOT = 32;
|
|
@@ -1306,7 +1306,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1306
1306
|
|
|
1307
1307
|
// Handle valid payload bid by storing in a bid pool
|
|
1308
1308
|
try {
|
|
1309
|
-
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
|
|
1309
|
+
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
|
|
1310
1310
|
metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
|
|
1311
1311
|
} catch (e) {
|
|
1312
1312
|
logger.error("Error adding to executionPayloadBid pool", {}, e as Error);
|
|
@@ -86,7 +86,6 @@ export class IndexedGossipQueueMinSize<T extends {indexed?: string; queueAddedMs
|
|
|
86
86
|
const now = Date.now();
|
|
87
87
|
// here we mutate item, which is used for gossip validation later
|
|
88
88
|
item.indexed = key;
|
|
89
|
-
item.queueAddedMs = now;
|
|
90
89
|
let queueItem = this.indexedItems.get(key);
|
|
91
90
|
if (queueItem == null) {
|
|
92
91
|
queueItem = {firstSeenMs: now, listItems: new LinkedList<T>()};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {TopicValidatorResult} from "@libp2p/gossipsub";
|
|
1
2
|
import {routes} from "@lodestar/api";
|
|
2
3
|
import {ForkSeq} from "@lodestar/params";
|
|
3
4
|
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
GossipValidatorBatchFn,
|
|
31
32
|
GossipValidatorFn,
|
|
32
33
|
} from "../gossip/interface.js";
|
|
34
|
+
import {MAX_PEERS_PER_ROOT} from "./constants.js";
|
|
33
35
|
import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
|
|
34
36
|
import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
|
|
35
37
|
import {createGossipQueues} from "./gossipQueues/index.js";
|
|
@@ -191,8 +193,12 @@ export class NetworkProcessor {
|
|
|
191
193
|
// in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
|
|
192
194
|
private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
|
|
193
195
|
private awaitingPayloadMessageCount = 0;
|
|
194
|
-
private unknownBlocksBySlot = new MapDef<Slot,
|
|
195
|
-
|
|
196
|
+
private unknownBlocksBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
|
|
197
|
+
() => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
|
|
198
|
+
);
|
|
199
|
+
private unknownEnvelopesBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
|
|
200
|
+
() => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
|
|
201
|
+
);
|
|
196
202
|
|
|
197
203
|
constructor(
|
|
198
204
|
modules: NetworkProcessorModules,
|
|
@@ -271,35 +277,53 @@ export class NetworkProcessor {
|
|
|
271
277
|
* Search block via `ChainEvent.unknownBlockRoot` event
|
|
272
278
|
* Slot is the message slot, which is not necessarily the same as the block's slot, but it can be used for a good prune strategy.
|
|
273
279
|
* In the rare case, if 2 messages on 2 slots search for the same root (for example beacon_attestation) we may emit the same root twice but BlockInputSync should handle it well.
|
|
280
|
+
* We forward every distinct peer that gossiped this root (not just the first) so BlockInputSync can
|
|
281
|
+
* prefer them as by-root download candidates.
|
|
274
282
|
*/
|
|
275
283
|
searchUnknownBlock({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
|
|
276
|
-
if (
|
|
277
|
-
this.chain.seenBlock(root) ||
|
|
278
|
-
this.awaitingMessagesByBlockRoot.has(root) ||
|
|
279
|
-
this.unknownBlocksBySlot.getOrDefault(slot).has(root)
|
|
280
|
-
) {
|
|
284
|
+
if (this.chain.seenBlock(root)) {
|
|
281
285
|
return;
|
|
282
286
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
287
|
+
const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
|
|
288
|
+
const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
|
|
289
|
+
const forwardedPeers = peersForRoot.getOrDefault(root);
|
|
290
|
+
|
|
291
|
+
// brand-new search always emits (peer may be undefined)
|
|
292
|
+
let shouldEmit = !alreadySearching;
|
|
293
|
+
if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
|
|
294
|
+
// First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
|
|
295
|
+
forwardedPeers.add(peer);
|
|
296
|
+
shouldEmit = true;
|
|
297
|
+
}
|
|
298
|
+
if (shouldEmit) {
|
|
299
|
+
this.chain.emitter.emit(ChainEvent.unknownBlockRoot, {rootHex: root, peer, source});
|
|
300
|
+
}
|
|
286
301
|
}
|
|
287
302
|
|
|
288
303
|
/**
|
|
289
304
|
* Search envelope via `ChainEvent.unknownEnvelopeBlockRoot` event
|
|
290
305
|
* Slot is the message slot, which is not necessarily the same as the envelope's slot, but it can be used for a good prune strategy.
|
|
291
306
|
* In the rare case, if 2 messages on 2 slots search for the same root (for example beacon_attestation) we may emit the same root twice but BlockInputSync should handle it well.
|
|
307
|
+
* Same peer-forwarding + dedup behavior as searchUnknownBlock.
|
|
292
308
|
*/
|
|
293
309
|
searchUnknownEnvelope({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
|
|
294
|
-
if (
|
|
295
|
-
this.chain.seenPayloadEnvelope(root) ||
|
|
296
|
-
this.awaitingMessagesByPayloadBlockRoot.has(root) ||
|
|
297
|
-
this.unknownEnvelopesBySlot.getOrDefault(slot).has(root)
|
|
298
|
-
) {
|
|
310
|
+
if (this.chain.seenPayloadEnvelope(root)) {
|
|
299
311
|
return;
|
|
300
312
|
}
|
|
301
|
-
this.unknownEnvelopesBySlot.getOrDefault(slot)
|
|
302
|
-
|
|
313
|
+
const peersForRoot = this.unknownEnvelopesBySlot.getOrDefault(slot);
|
|
314
|
+
// capture "already searching" BEFORE getOrDefault(root) creates the entry
|
|
315
|
+
const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByPayloadBlockRoot.has(root);
|
|
316
|
+
const forwardedPeers = peersForRoot.getOrDefault(root);
|
|
317
|
+
|
|
318
|
+
let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
|
|
319
|
+
if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
|
|
320
|
+
// First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
|
|
321
|
+
forwardedPeers.add(peer);
|
|
322
|
+
shouldEmit = true;
|
|
323
|
+
}
|
|
324
|
+
if (shouldEmit) {
|
|
325
|
+
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
|
|
326
|
+
}
|
|
303
327
|
}
|
|
304
328
|
|
|
305
329
|
private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
|
|
@@ -537,6 +561,7 @@ export class NetworkProcessor {
|
|
|
537
561
|
|
|
538
562
|
private pushPendingGossipsubMessageToQueue(message: PendingGossipsubMessage): void {
|
|
539
563
|
const topicType = message.topic.type;
|
|
564
|
+
message.queueAddedMs = Date.now();
|
|
540
565
|
const droppedCount = this.gossipQueues[topicType].add(message);
|
|
541
566
|
if (droppedCount) {
|
|
542
567
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
@@ -616,7 +641,7 @@ export class NetworkProcessor {
|
|
|
616
641
|
|
|
617
642
|
for (const [slot, roots] of this.unknownBlocksBySlot) {
|
|
618
643
|
if (slot > minSlot) continue;
|
|
619
|
-
for (const rootHex of roots) {
|
|
644
|
+
for (const rootHex of roots.keys()) {
|
|
620
645
|
const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
|
|
621
646
|
if (gossipMessages !== undefined) {
|
|
622
647
|
for (const message of gossipMessages) {
|
|
@@ -641,7 +666,7 @@ export class NetworkProcessor {
|
|
|
641
666
|
|
|
642
667
|
for (const [slot, roots] of this.unknownEnvelopesBySlot) {
|
|
643
668
|
if (slot > minSlot) continue;
|
|
644
|
-
for (const rootHex of roots) {
|
|
669
|
+
for (const rootHex of roots.keys()) {
|
|
645
670
|
const gossipMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
|
|
646
671
|
if (gossipMessages !== undefined) {
|
|
647
672
|
for (const message of gossipMessages) {
|
|
@@ -723,12 +748,20 @@ export class NetworkProcessor {
|
|
|
723
748
|
for (const msg of messageOrArray) {
|
|
724
749
|
msg.startProcessUnixSec = nowSec;
|
|
725
750
|
if (msg.queueAddedMs !== undefined) {
|
|
726
|
-
this.metrics?.gossipValidationQueue.queueTime.observe(
|
|
751
|
+
this.metrics?.gossipValidationQueue.queueTime.observe(
|
|
752
|
+
{topic: msg.topic.type},
|
|
753
|
+
nowSec - msg.queueAddedMs / 1000
|
|
754
|
+
);
|
|
727
755
|
}
|
|
728
756
|
}
|
|
729
757
|
} else {
|
|
730
|
-
// indexed queue is not used here
|
|
731
758
|
messageOrArray.startProcessUnixSec = nowSec;
|
|
759
|
+
if (messageOrArray.queueAddedMs !== undefined) {
|
|
760
|
+
this.metrics?.gossipValidationQueue.queueTime.observe(
|
|
761
|
+
{topic: messageOrArray.topic.type},
|
|
762
|
+
nowSec - messageOrArray.queueAddedMs / 1000
|
|
763
|
+
);
|
|
764
|
+
}
|
|
732
765
|
}
|
|
733
766
|
|
|
734
767
|
const acceptanceArr = Array.isArray(messageOrArray)
|
|
@@ -741,11 +774,11 @@ export class NetworkProcessor {
|
|
|
741
774
|
];
|
|
742
775
|
|
|
743
776
|
if (Array.isArray(messageOrArray)) {
|
|
744
|
-
for (const msg of messageOrArray) {
|
|
745
|
-
this.trackJobTime(msg, messageOrArray.length);
|
|
777
|
+
for (const [i, msg] of messageOrArray.entries()) {
|
|
778
|
+
this.trackJobTime(msg, messageOrArray.length, acceptanceArr[i]);
|
|
746
779
|
}
|
|
747
780
|
} else {
|
|
748
|
-
this.trackJobTime(messageOrArray, 1);
|
|
781
|
+
this.trackJobTime(messageOrArray, 1, acceptanceArr[0]);
|
|
749
782
|
}
|
|
750
783
|
|
|
751
784
|
// Use setTimeout to yield to the macro queue
|
|
@@ -773,17 +806,21 @@ export class NetworkProcessor {
|
|
|
773
806
|
}
|
|
774
807
|
}
|
|
775
808
|
|
|
776
|
-
private trackJobTime(message: PendingGossipsubMessage, numJob: number): void {
|
|
809
|
+
private trackJobTime(message: PendingGossipsubMessage, numJob: number, acceptance: TopicValidatorResult): void {
|
|
777
810
|
if (message.startProcessUnixSec !== null) {
|
|
811
|
+
// record job wait time for all messages
|
|
778
812
|
this.metrics?.gossipValidationQueue.jobWaitTime.observe(
|
|
779
813
|
{topic: message.topic.type},
|
|
780
814
|
message.startProcessUnixSec - message.seenTimestampSec
|
|
781
815
|
);
|
|
782
|
-
//
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
816
|
+
// but only record job time for ACCEPT messages
|
|
817
|
+
if (acceptance === TopicValidatorResult.Accept) {
|
|
818
|
+
// if it takes 64ms to process 64 jobs, the average job time is 1ms
|
|
819
|
+
this.metrics?.gossipValidationQueue.jobTime.observe(
|
|
820
|
+
{topic: message.topic.type},
|
|
821
|
+
(Date.now() / 1000 - message.startProcessUnixSec) / numJob
|
|
822
|
+
);
|
|
823
|
+
}
|
|
787
824
|
}
|
|
788
825
|
}
|
|
789
826
|
|
package/src/node/nodejs.ts
CHANGED
|
@@ -260,6 +260,11 @@ export class BeaconNode {
|
|
|
260
260
|
executionBuilder: opts.executionBuilder.enabled
|
|
261
261
|
? initializeExecutionBuilder(opts.executionBuilder, config, metrics, logger)
|
|
262
262
|
: undefined,
|
|
263
|
+
builderApiClientOpts: {
|
|
264
|
+
timeout: opts.executionBuilder.timeout,
|
|
265
|
+
// Sent with all builder api requests, unless the node runs in private mode
|
|
266
|
+
userAgent: opts.executionBuilder.userAgent,
|
|
267
|
+
},
|
|
263
268
|
});
|
|
264
269
|
|
|
265
270
|
// Load persisted data from disk to in-memory caches
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -652,7 +652,9 @@ export class SyncChain {
|
|
|
652
652
|
|
|
653
653
|
const blockSlots = downloadSuccessOutput.blocks.map((b) => b.slot);
|
|
654
654
|
const envelopeSlots = downloadSuccessOutput.payloadEnvelopes
|
|
655
|
-
? Array.from(downloadSuccessOutput.payloadEnvelopes.
|
|
655
|
+
? Array.from(downloadSuccessOutput.payloadEnvelopes.entries())
|
|
656
|
+
.filter(([, payloadInput]) => payloadInput.hasPayloadEnvelope())
|
|
657
|
+
.map(([slot]) => slot)
|
|
656
658
|
: null;
|
|
657
659
|
|
|
658
660
|
this.logger.debug(logMessage, {
|
|
@@ -684,13 +686,18 @@ export class SyncChain {
|
|
|
684
686
|
*/
|
|
685
687
|
private async processBatch(batch: Batch): Promise<void> {
|
|
686
688
|
const {blocks, payloadEnvelopes, peers} = batch.startProcessing();
|
|
689
|
+
const envelopeSlots = payloadEnvelopes
|
|
690
|
+
? Array.from(payloadEnvelopes.entries())
|
|
691
|
+
.filter(([, payloadInput]) => payloadInput.hasPayloadEnvelope())
|
|
692
|
+
.map(([slot]) => slot)
|
|
693
|
+
: null;
|
|
687
694
|
|
|
688
695
|
const logCtx = {
|
|
689
696
|
id: this.logId,
|
|
690
697
|
...batch.getMetadata(),
|
|
691
698
|
blockCount: blocks.length,
|
|
692
699
|
blockSlots: prettyPrintIndices(blocks.map((b) => b.slot)),
|
|
693
|
-
...(
|
|
700
|
+
...(envelopeSlots ? {envelopeSlots: prettyPrintIndices(envelopeSlots)} : {}),
|
|
694
701
|
peers: peers.map(prettyPrintPeerIdStr).join(","),
|
|
695
702
|
};
|
|
696
703
|
this.logger.verbose("Processing batch", logCtx);
|
package/src/sync/range/range.ts
CHANGED
|
@@ -212,7 +212,7 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
212
212
|
for (const block of blocks) {
|
|
213
213
|
await this.chain.processBlock(block, flags);
|
|
214
214
|
const payloadEnvelope = payloadEnvelopes?.get(block.slot);
|
|
215
|
-
if (payloadEnvelope) {
|
|
215
|
+
if (payloadEnvelope?.hasPayloadEnvelope()) {
|
|
216
216
|
await this.chain.processExecutionPayload(payloadEnvelope);
|
|
217
217
|
}
|
|
218
218
|
}
|