@lodestar/beacon-node 1.47.0 → 1.48.0-dev.04d463c59a
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 +4 -0
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +5 -0
- package/lib/api/impl/validator/index.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 +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +2 -1
- 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/interface.d.ts +2 -2
- package/lib/chain/interface.d.ts.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 +24 -1
- 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/regen/interface.d.ts +1 -0
- 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/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 +183 -28
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/execution/builder/apiClient.d.ts +6 -0
- package/lib/execution/builder/apiClient.d.ts.map +1 -1
- package/lib/execution/builder/apiClient.js +19 -1
- package/lib/execution/builder/apiClient.js.map +1 -1
- package/lib/execution/builder/validateBid.d.ts +1 -0
- package/lib/execution/builder/validateBid.d.ts.map +1 -1
- package/lib/execution/builder/validateBid.js +14 -6
- package/lib/execution/builder/validateBid.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +10 -1
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +19 -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/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.map +1 -1
- package/lib/network/processor/index.js +16 -8
- package/lib/network/processor/index.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/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +4 -0
- package/src/api/impl/validator/index.ts +5 -0
- package/src/chain/blocks/index.ts +7 -7
- package/src/chain/blocks/verifyBlock.ts +5 -2
- package/src/chain/chain.ts +2 -1
- package/src/chain/errors/executionPayloadBid.ts +6 -0
- package/src/chain/interface.ts +2 -2
- package/src/chain/prepareNextSlot.ts +26 -1
- package/src/chain/produceBlock/computeNewStateRoot.ts +3 -38
- package/src/chain/regen/interface.ts +1 -0
- package/src/chain/validation/block.ts +43 -10
- package/src/chain/validation/executionPayloadBid.ts +212 -32
- package/src/execution/builder/apiClient.ts +21 -1
- package/src/execution/builder/validateBid.ts +22 -7
- package/src/metrics/metrics/lodestar.ts +21 -4
- package/src/network/network.ts +7 -4
- package/src/network/processor/gossipQueues/indexed.ts +0 -1
- package/src/network/processor/index.ts +25 -11
- package/src/sync/range/chain.ts +9 -2
- package/src/sync/range/range.ts +1 -1
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DataAvailabilityStatus,
|
|
3
|
-
ExecutionPayloadStatus,
|
|
4
|
-
IBeaconStateView,
|
|
5
|
-
StateHashTreeRootSource,
|
|
6
|
-
} from "@lodestar/state-transition";
|
|
1
|
+
import {EMPTY_SIGNATURE, IBeaconStateView} from "@lodestar/state-transition";
|
|
7
2
|
import {BeaconBlock, BlindedBeaconBlock, Gwei, Root} from "@lodestar/types";
|
|
8
|
-
import {ZERO_HASH} from "../../constants/index.js";
|
|
9
3
|
import {Metrics} from "../../metrics/index.js";
|
|
10
4
|
|
|
11
5
|
/**
|
|
@@ -19,35 +13,6 @@ export function computeNewStateRoot(
|
|
|
19
13
|
block: BeaconBlock | BlindedBeaconBlock
|
|
20
14
|
): {newStateRoot: Root; proposerReward: Gwei; postState: IBeaconStateView} {
|
|
21
15
|
// Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const postState = state.stateTransition(
|
|
25
|
-
blockEmptySig,
|
|
26
|
-
{
|
|
27
|
-
// ExecutionPayloadStatus.valid: Assume payload valid, it has been produced by a trusted EL
|
|
28
|
-
executionPayloadStatus: ExecutionPayloadStatus.valid,
|
|
29
|
-
// DataAvailabilityStatus.available: Assume the blobs to be available, have just been produced by trusted EL
|
|
30
|
-
dataAvailabilityStatus: DataAvailabilityStatus.Available,
|
|
31
|
-
// verifyStateRoot: false | the root in the block is zero-ed, it's being computed here
|
|
32
|
-
verifyStateRoot: false,
|
|
33
|
-
// verifyProposer: false | as the block signature is zero-ed
|
|
34
|
-
verifyProposer: false,
|
|
35
|
-
// verifySignatures: false | since the data to assemble the block is trusted
|
|
36
|
-
verifySignatures: false,
|
|
37
|
-
// Preserve cache in source state, since the resulting state is not added to the state cache
|
|
38
|
-
dontTransferCache: true,
|
|
39
|
-
},
|
|
40
|
-
{metrics}
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const {attestations, syncAggregate, slashing} = postState.proposerRewards;
|
|
44
|
-
const proposerReward = BigInt(attestations + syncAggregate + slashing);
|
|
45
|
-
|
|
46
|
-
const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
|
|
47
|
-
source: StateHashTreeRootSource.computeNewStateRoot,
|
|
48
|
-
});
|
|
49
|
-
const newStateRoot = postState.hashTreeRoot();
|
|
50
|
-
hashTreeRootTimer?.();
|
|
51
|
-
|
|
52
|
-
return {newStateRoot, proposerReward, postState};
|
|
16
|
+
const signedBlock = {message: block, signature: EMPTY_SIGNATURE};
|
|
17
|
+
return state.computeNewStateRoot({block: signedBlock}, {metrics});
|
|
53
18
|
}
|
|
@@ -24,6 +24,7 @@ export enum RegenCaller {
|
|
|
24
24
|
validateApiVoluntaryExit = "validateApiVoluntaryExit",
|
|
25
25
|
publishDeferredVoluntaryExits = "publishDeferredVoluntaryExits",
|
|
26
26
|
validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
|
|
27
|
+
validateApiExecutionPayloadBid = "validateApiExecutionPayloadBid",
|
|
27
28
|
validateGossipPayloadAttestationMessage = "validateGossipPayloadAttestationMessage",
|
|
28
29
|
validateGossipProposerPreferences = "validateGossipProposerPreferences",
|
|
29
30
|
onForkChoiceFinalized = "onForkChoiceFinalized",
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
MAX_PROPOSER_SLASHINGS,
|
|
13
13
|
MAX_VOLUNTARY_EXITS,
|
|
14
14
|
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
|
|
15
|
+
MIN_SEED_LOOKAHEAD,
|
|
15
16
|
isForkPostBellatrix,
|
|
16
17
|
isForkPostDeneb,
|
|
17
18
|
isForkPostGloas,
|
|
@@ -249,26 +250,58 @@ export async function validateGossipBlock(
|
|
|
249
250
|
// This requires execution engine integration to verify the parent block hash
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
//
|
|
253
|
-
//
|
|
253
|
+
// For gossip forwarding we only need the state to check the block's proposer index.
|
|
254
|
+
// If the state cannot be regenerated we throw an IGNORE (whereas the spec says we should REJECT for the
|
|
255
|
+
// finalized-ancestor scenario, which is already guarded by the parentBlock lookup above).
|
|
254
256
|
// this is something we should change this in the future to make the code airtight to the spec.
|
|
255
257
|
// [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved).
|
|
256
258
|
// [REJECT] The block's parent (defined by block.parent_root) passes validation.
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
const canUseParentState = blockEpoch - computeEpochAtSlot(parentBlock.slot) <= MIN_SEED_LOOKAHEAD;
|
|
260
|
+
|
|
261
|
+
const getValidationState = async () => {
|
|
262
|
+
const getPreState = () =>
|
|
263
|
+
chain.regen.getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock);
|
|
264
|
+
if (canUseParentState) {
|
|
265
|
+
try {
|
|
266
|
+
const parentState = await chain.regen.getState(parentBlock.stateRoot, RegenCaller.validateGossipBlock);
|
|
267
|
+
chain.metrics?.gossipBlock.preStateSource.inc({source: "parentState"});
|
|
268
|
+
return parentState;
|
|
269
|
+
} catch {
|
|
270
|
+
// if parent state is not in memory, we fall back to disk reload / dial-forward
|
|
271
|
+
chain.metrics?.gossipBlock.preStateSource.inc({source: "fallbackPreState"});
|
|
272
|
+
chain.logger.debug("Parent state not in memory, falling back to getPreState for gossip block validation", {
|
|
273
|
+
slot: blockSlot,
|
|
274
|
+
root: blockRoot,
|
|
275
|
+
parentSlot: parentBlock.slot,
|
|
276
|
+
parentRoot,
|
|
277
|
+
});
|
|
278
|
+
return getPreState();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// parent is >1 epoch behind (deep skip) → beyond proposer-lookahead range, must dial forward
|
|
282
|
+
chain.metrics?.gossipBlock.preStateSource.inc({source: "preState"});
|
|
283
|
+
chain.logger.debug("Cannot use parent state for gossip block validation, dialing forward via getPreState", {
|
|
284
|
+
slot: blockSlot,
|
|
285
|
+
root: blockRoot,
|
|
286
|
+
parentSlot: parentBlock.slot,
|
|
287
|
+
parentRoot,
|
|
261
288
|
});
|
|
289
|
+
return getPreState();
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const state = await getValidationState().catch(() => {
|
|
293
|
+
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
|
|
294
|
+
});
|
|
262
295
|
|
|
263
296
|
// in forky condition, make sure to populate ShufflingCache with regened state
|
|
264
|
-
chain.shufflingCache.processState(
|
|
297
|
+
chain.shufflingCache.processState(state);
|
|
265
298
|
|
|
266
299
|
// [REJECT] The block's execution payload timestamp is correct with respect to the slot
|
|
267
300
|
// -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot).
|
|
268
301
|
if (isForkPostBellatrix(fork) && !isForkPostGloas(fork)) {
|
|
269
302
|
if (!isExecutionBlockBodyType(block.body)) throw Error("Not execution block body type");
|
|
270
303
|
const executionPayload = block.body.executionPayload;
|
|
271
|
-
if (isStatePostBellatrix(
|
|
304
|
+
if (isStatePostBellatrix(state) && state.isExecutionStateType && state.isExecutionEnabled(block)) {
|
|
272
305
|
const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime);
|
|
273
306
|
if (executionPayload.timestamp !== computeTimeAtSlot(config, blockSlot, chain.genesisTime)) {
|
|
274
307
|
throw new BlockGossipError(GossipAction.REJECT, {
|
|
@@ -281,7 +314,7 @@ export async function validateGossipBlock(
|
|
|
281
314
|
}
|
|
282
315
|
|
|
283
316
|
// [REJECT] The proposer index is a valid validator index
|
|
284
|
-
if (proposerIndex >=
|
|
317
|
+
if (proposerIndex >= state.validatorCount) {
|
|
285
318
|
throw new BlockGossipError(GossipAction.REJECT, {code: BlockErrorCode.UNKNOWN_PROPOSER, proposerIndex});
|
|
286
319
|
}
|
|
287
320
|
|
|
@@ -293,7 +326,7 @@ export async function validateGossipBlock(
|
|
|
293
326
|
// shuffling (defined by parent_root/slot). If the proposer_index cannot immediately be verified against the expected
|
|
294
327
|
// shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated --
|
|
295
328
|
// in such a case do not REJECT, instead IGNORE this message.
|
|
296
|
-
if (
|
|
329
|
+
if (state.getBeaconProposer(blockSlot) !== proposerIndex) {
|
|
297
330
|
throw new BlockGossipError(GossipAction.REJECT, {code: BlockErrorCode.INCORRECT_PROPOSER, proposerIndex});
|
|
298
331
|
}
|
|
299
332
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {ProtoBlock} from "@lodestar/fork-choice";
|
|
2
2
|
import {PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
|
|
3
3
|
import {
|
|
4
4
|
computeEpochAtSlot,
|
|
@@ -46,11 +46,12 @@ function getMinBidValue(currentHighestBid: number): number {
|
|
|
46
46
|
/**
|
|
47
47
|
* Check whether a bid builds on one of the paths compatible with the local head branch.
|
|
48
48
|
*
|
|
49
|
-
* Building directly on the parent is allowed for proposer-boost reorgs
|
|
49
|
+
* Building directly on the parent is allowed for proposer-boost reorgs, including at epoch
|
|
50
|
+
* boundaries when the head is weak enough that a reorg onto the strong parent is predicted.
|
|
50
51
|
* Otherwise the bid must build on the local head's full or empty payload variant, as selected for its slot.
|
|
51
52
|
*/
|
|
52
53
|
function isBidCompatibleWithHead(
|
|
53
|
-
|
|
54
|
+
chain: IBeaconChain,
|
|
54
55
|
head: ProtoBlock,
|
|
55
56
|
bidSlot: Slot,
|
|
56
57
|
bidParentBlockRoot: RootHex,
|
|
@@ -60,9 +61,13 @@ function isBidCompatibleWithHead(
|
|
|
60
61
|
const buildsOnParentPayload = bidParentBlockHash === head.parentBlockHash;
|
|
61
62
|
|
|
62
63
|
if (buildsOnParentBlock && buildsOnParentPayload) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
if (!isStartSlotOfEpoch(bidSlot)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
// Epoch-boundary reorgs are allowed, but only propagate the bid if its parent matches the block
|
|
68
|
+
// we predict a proposer would reorg onto — predictProposerHead returns the strong parent only
|
|
69
|
+
// when the head is weak.
|
|
70
|
+
return chain.predictProposerHead().blockRoot === bidParentBlockRoot;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
if (bidParentBlockRoot !== head.blockRoot) {
|
|
@@ -71,18 +76,170 @@ function isBidCompatibleWithHead(
|
|
|
71
76
|
|
|
72
77
|
const buildsOnHeadPayload = bidParentBlockHash === head.executionPayloadBlockHash;
|
|
73
78
|
|
|
74
|
-
if (forkChoice.shouldBuildOnFull(head, bidSlot)) {
|
|
79
|
+
if (chain.forkChoice.shouldBuildOnFull(head, bidSlot)) {
|
|
75
80
|
return buildsOnHeadPayload;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
return buildsOnParentPayload;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Validation for bids submitted via the API by the operator's own builder.
|
|
88
|
+
*
|
|
89
|
+
* Only non-transient checks are applied: slot later than parent, zero execution payment, blob
|
|
90
|
+
* commitment count, builder eligibility, version and balance coverage, prev_randao, and signature.
|
|
91
|
+
* Transient gossip rules (head compatibility, first bid per tuple, value increment, proposer
|
|
92
|
+
* preferences) are not applied, since those only limit forwarding of peers' messages and the
|
|
93
|
+
* builder may legitimately bid on a branch this node does not consider head.
|
|
94
|
+
* The parent-payload builder-exit rule is omitted because this endpoint only accepts bids from
|
|
95
|
+
* the operator's own builder.
|
|
96
|
+
*
|
|
97
|
+
* Throws on any failed check. Also throws if the bid's parent block is unknown or its state is
|
|
98
|
+
* unavailable, since the checks cannot be evaluated against the parent branch.
|
|
99
|
+
*/
|
|
81
100
|
export async function validateApiExecutionPayloadBid(
|
|
82
101
|
chain: IBeaconChain,
|
|
83
102
|
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
|
|
84
|
-
): Promise<
|
|
85
|
-
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
const bid = signedExecutionPayloadBid.message;
|
|
105
|
+
const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
|
|
106
|
+
|
|
107
|
+
// [REJECT] `bid.execution_payment` is zero.
|
|
108
|
+
if (bid.executionPayment !== 0n) {
|
|
109
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
110
|
+
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
111
|
+
builderIndex: bid.builderIndex,
|
|
112
|
+
executionPayment: bid.executionPayment,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
117
|
+
// consensus layer -- i.e. validate that
|
|
118
|
+
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
119
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
120
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
121
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
122
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
123
|
+
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
124
|
+
blobKzgCommitmentsLen,
|
|
125
|
+
commitmentLimit: maxBlobsPerBlock,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
130
|
+
if (
|
|
131
|
+
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
|
|
132
|
+
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot - 1)
|
|
133
|
+
) {
|
|
134
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
135
|
+
code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
|
|
136
|
+
builderIndex: bid.builderIndex,
|
|
137
|
+
slot: bid.slot,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// [IGNORE] `bid.parent_block_root` is the hash tree root of a known beacon block in fork choice.
|
|
142
|
+
// Moved earlier than the spec ordering so we can derive the proposer dependent root for the
|
|
143
|
+
// proposer-preferences lookup below from a known fork-choice block.
|
|
144
|
+
const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(bidParentBlockRoot);
|
|
145
|
+
if (parentBlock === null) {
|
|
146
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
147
|
+
code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
148
|
+
parentBlockRoot: bidParentBlockRoot,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// [REJECT] The bid is for a higher slot than its parent block -- i.e.
|
|
153
|
+
// validate that `bid.slot` is greater than the slot of the block with root
|
|
154
|
+
// `bid.parent_block_root`.
|
|
155
|
+
if (bid.slot <= parentBlock.slot) {
|
|
156
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
157
|
+
code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT,
|
|
158
|
+
parentSlot: parentBlock.slot,
|
|
159
|
+
slot: bid.slot,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const state = await chain.regen
|
|
164
|
+
.getBlockSlotState(parentBlock, bid.slot, {dontTransferCache: true}, RegenCaller.validateApiExecutionPayloadBid)
|
|
165
|
+
.catch(() => {
|
|
166
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
167
|
+
code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
168
|
+
parentBlockRoot: bidParentBlockRoot,
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!isStatePostGloas(state)) {
|
|
173
|
+
throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// [REJECT] `bid.builder_index` is within bounds -- i.e. `bid.builder_index < len(state.builders)`.
|
|
177
|
+
// `state.getBuilder` returns a lazy SSZ `getReadonly` view that is not bounds-checked eagerly; an
|
|
178
|
+
// out-of-range index only throws (`LeafNode has no right node`) on deferred field access (e.g. inside
|
|
179
|
+
// `isActiveBuilder`), escaping a try/catch around `getBuilder`. Check the length explicitly instead.
|
|
180
|
+
if (bid.builderIndex >= state.getBuildersLength()) {
|
|
181
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
182
|
+
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
183
|
+
builderIndex: bid.builderIndex,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
|
|
188
|
+
// `is_active_builder(state, bid.builder_index)` returns `True`.
|
|
189
|
+
const builder = state.getBuilder(bid.builderIndex);
|
|
190
|
+
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
191
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
192
|
+
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
193
|
+
builderIndex: bid.builderIndex,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// [REJECT] The builder version is `PAYLOAD_BUILDER_VERSION` -- i.e.
|
|
198
|
+
// `state.builders[bid.builder_index].version == PAYLOAD_BUILDER_VERSION`.
|
|
199
|
+
if (builder.version !== PAYLOAD_BUILDER_VERSION) {
|
|
200
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
201
|
+
code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION,
|
|
202
|
+
builderIndex: bid.builderIndex,
|
|
203
|
+
version: builder.version,
|
|
204
|
+
expectedVersion: PAYLOAD_BUILDER_VERSION,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// [IGNORE] `bid.value` is less or equal than the builder's excess balance --
|
|
209
|
+
// i.e. `can_builder_cover_bid(state, builder_index, amount)` returns `True`.
|
|
210
|
+
if (!state.canBuilderCoverBid(bid.builderIndex, bid.value)) {
|
|
211
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
212
|
+
code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH,
|
|
213
|
+
bidValue: bid.value,
|
|
214
|
+
builderBalance: builder.balance,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that
|
|
219
|
+
// `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))`.
|
|
220
|
+
const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
|
|
221
|
+
if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
|
|
222
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
223
|
+
code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO,
|
|
224
|
+
builderIndex: bid.builderIndex,
|
|
225
|
+
bidPrevRandao: toHex(bid.prevRandao),
|
|
226
|
+
expectedPrevRandao: toHex(randaoMix),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
|
|
231
|
+
const signatureSet = createSingleSignatureSetFromComponents(
|
|
232
|
+
builder.pubkey,
|
|
233
|
+
getExecutionPayloadBidSigningRoot(chain.config, bid),
|
|
234
|
+
signedExecutionPayloadBid.signature
|
|
235
|
+
);
|
|
236
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
|
|
237
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
238
|
+
code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE,
|
|
239
|
+
builderIndex: bid.builderIndex,
|
|
240
|
+
slot: bid.slot,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
86
243
|
}
|
|
87
244
|
|
|
88
245
|
export async function validateGossipExecutionPayloadBid(
|
|
@@ -100,6 +257,28 @@ async function validateExecutionPayloadBid(
|
|
|
100
257
|
const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
|
|
101
258
|
const bidParentBlockHash = toRootHex(bid.parentBlockHash);
|
|
102
259
|
|
|
260
|
+
// [REJECT] `bid.execution_payment` is zero.
|
|
261
|
+
if (bid.executionPayment !== 0n) {
|
|
262
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
263
|
+
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
264
|
+
builderIndex: bid.builderIndex,
|
|
265
|
+
executionPayment: bid.executionPayment,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
270
|
+
// consensus layer -- i.e. validate that
|
|
271
|
+
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
272
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
273
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
274
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
275
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
276
|
+
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
277
|
+
blobKzgCommitmentsLen,
|
|
278
|
+
commitmentLimit: maxBlobsPerBlock,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
103
282
|
// [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
104
283
|
if (
|
|
105
284
|
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
|
|
@@ -114,7 +293,7 @@ async function validateExecutionPayloadBid(
|
|
|
114
293
|
|
|
115
294
|
// [IGNORE] The bid is compatible with the current head branch.
|
|
116
295
|
const head = chain.forkChoice.getHead();
|
|
117
|
-
if (!isBidCompatibleWithHead(chain
|
|
296
|
+
if (!isBidCompatibleWithHead(chain, head, bid.slot, bidParentBlockRoot, bidParentBlockHash)) {
|
|
118
297
|
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
119
298
|
code: ExecutionPayloadBidErrorCode.INCOMPATIBLE_WITH_HEAD,
|
|
120
299
|
slot: bid.slot,
|
|
@@ -245,15 +424,6 @@ async function validateExecutionPayloadBid(
|
|
|
245
424
|
});
|
|
246
425
|
}
|
|
247
426
|
|
|
248
|
-
// [REJECT] `bid.execution_payment` is zero.
|
|
249
|
-
if (bid.executionPayment !== 0n) {
|
|
250
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
251
|
-
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
252
|
-
builderIndex: bid.builderIndex,
|
|
253
|
-
executionPayment: bid.executionPayment,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
427
|
// [IGNORE] `bid.fee_recipient == proposer_preferences.fee_recipient`.
|
|
258
428
|
if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
|
|
259
429
|
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
@@ -294,19 +464,6 @@ async function validateExecutionPayloadBid(
|
|
|
294
464
|
});
|
|
295
465
|
}
|
|
296
466
|
|
|
297
|
-
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
298
|
-
// consensus layer -- i.e. validate that
|
|
299
|
-
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
300
|
-
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
301
|
-
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
302
|
-
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
303
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
304
|
-
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
305
|
-
blobKzgCommitmentsLen,
|
|
306
|
-
commitmentLimit: maxBlobsPerBlock,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
|
|
310
467
|
// [IGNORE] this bid is the highest value bid seen for the tuple
|
|
311
468
|
// `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
|
|
312
469
|
// As a DoS prevention measure, the bid must also exceed the current highest bid by a minimum
|
|
@@ -342,6 +499,29 @@ async function validateExecutionPayloadBid(
|
|
|
342
499
|
});
|
|
343
500
|
}
|
|
344
501
|
|
|
502
|
+
// [IGNORE] The parent's payload does not try to exit the builder
|
|
503
|
+
if (byteArrayEquals(bid.parentBlockHash, state.latestExecutionPayloadBid.blockHash)) {
|
|
504
|
+
const requests = await chain.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot).catch(() => {
|
|
505
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
506
|
+
code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH,
|
|
507
|
+
parentBlockHash: bidParentBlockHash,
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
if (
|
|
511
|
+
requests.builderExits.some(
|
|
512
|
+
(request) =>
|
|
513
|
+
byteArrayEquals(request.pubkey, builder.pubkey) &&
|
|
514
|
+
byteArrayEquals(request.sourceAddress, builder.executionAddress)
|
|
515
|
+
)
|
|
516
|
+
) {
|
|
517
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
518
|
+
code: ExecutionPayloadBidErrorCode.BUILDER_MAY_EXIT,
|
|
519
|
+
builderIndex: bid.builderIndex,
|
|
520
|
+
parentBlockRoot: bidParentBlockRoot,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
345
525
|
// [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
|
|
346
526
|
const signatureSet = createSingleSignatureSetFromComponents(
|
|
347
527
|
builder.pubkey,
|
|
@@ -201,6 +201,26 @@ export class BuilderApiClient {
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Establish the connection to every known builder outside the bid deadline, a cold TCP/TLS handshake
|
|
206
|
+
* to a distant builder can exhaust `BUILDER_BID_DEADLINE_MS` on its own, and a long idle connection
|
|
207
|
+
* may be half-open and only fail once written to.
|
|
208
|
+
*/
|
|
209
|
+
async checkStatus(): Promise<void> {
|
|
210
|
+
await Promise.all(
|
|
211
|
+
Array.from(this.clients.entries()).map(async ([url, client]) => {
|
|
212
|
+
try {
|
|
213
|
+
(await client.status()).assertOk();
|
|
214
|
+
this.metrics?.builderApi.statusChecks.inc({status: "success"});
|
|
215
|
+
this.logger?.debug("Builder is ready", {builder: toPrintableUrl(url)});
|
|
216
|
+
} catch (e) {
|
|
217
|
+
this.metrics?.builderApi.statusChecks.inc({status: "error"});
|
|
218
|
+
this.logger?.warn("Builder status check failed", {builder: toPrintableUrl(url)}, e as Error);
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
204
224
|
private async getOrCreateClient(
|
|
205
225
|
url: BuilderUrl,
|
|
206
226
|
proposerPubkey: BLSPubkey,
|
|
@@ -237,7 +257,7 @@ export class BuilderApiClient {
|
|
|
237
257
|
{config: this.config, metrics: this.metrics?.builderHttpClient, logger: this.logger}
|
|
238
258
|
);
|
|
239
259
|
this.clients.set(url, client);
|
|
240
|
-
this.logger?.
|
|
260
|
+
this.logger?.debug("External builder registered", {url: toPrintableUrl(url)});
|
|
241
261
|
}
|
|
242
262
|
return client;
|
|
243
263
|
}
|
|
@@ -37,10 +37,11 @@ export async function validateBuilderApiExecutionPayloadBid(
|
|
|
37
37
|
parentBlockHash: RootHex;
|
|
38
38
|
parentBlockRoot: RootHex;
|
|
39
39
|
entry: routes.validator.BuilderEntry;
|
|
40
|
+
getParentExecutionRequests: () => Promise<gloas.ExecutionRequests>;
|
|
40
41
|
}
|
|
41
42
|
): Promise<void> {
|
|
42
43
|
const bid = signedExecutionPayloadBid.message;
|
|
43
|
-
const {slot, parentBlock, parentBlockHash, parentBlockRoot, entry} = request;
|
|
44
|
+
const {slot, parentBlock, parentBlockHash, parentBlockRoot, entry, getParentExecutionRequests} = request;
|
|
44
45
|
|
|
45
46
|
if (bid.slot !== slot) {
|
|
46
47
|
throw Error(`Bid slot=${bid.slot} does not match requested slot=${slot}`);
|
|
@@ -55,6 +56,12 @@ export async function validateBuilderApiExecutionPayloadBid(
|
|
|
55
56
|
);
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
60
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
61
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
62
|
+
throw Error(`Bid has too many KZG commitments len=${blobKzgCommitmentsLen} limit=${maxBlobsPerBlock}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
const totalPayment = getBuilderBidTotalGwei(bid, entry.maxExecutionPayment);
|
|
59
66
|
if (totalPayment < entry.minBid) {
|
|
60
67
|
throw Error(
|
|
@@ -99,12 +106,6 @@ export async function validateBuilderApiExecutionPayloadBid(
|
|
|
99
106
|
);
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
103
|
-
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
104
|
-
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
105
|
-
throw Error(`Bid has too many KZG commitments len=${blobKzgCommitmentsLen} limit=${maxBlobsPerBlock}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
109
|
// The coverage check only applies to the staked collateral payment, a pure execution
|
|
109
110
|
// layer payment bid has nothing to cover on-chain
|
|
110
111
|
if (bid.value > 0 && !state.canBuilderCoverBid(bid.builderIndex, bid.value)) {
|
|
@@ -118,6 +119,20 @@ export async function validateBuilderApiExecutionPayloadBid(
|
|
|
118
119
|
throw Error(`Invalid bid prevRandao=${toHex(bid.prevRandao)} expected=${toHex(randaoMix)}`);
|
|
119
120
|
}
|
|
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
|
+
|
|
121
136
|
// The builder must honor the proposer preferences it learned over gossip
|
|
122
137
|
const bidEpoch = computeEpochAtSlot(bid.slot);
|
|
123
138
|
const dependentRootHex = (() => {
|
|
@@ -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
|
|
|
@@ -2030,6 +2042,11 @@ export function createLodestarMetrics(
|
|
|
2030
2042
|
},
|
|
2031
2043
|
|
|
2032
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
|
+
}),
|
|
2033
2050
|
bidRequests: register.counter({
|
|
2034
2051
|
name: "lodestar_builder_api_bid_requests_total",
|
|
2035
2052
|
help: "Total count of execution payload bid requests sent to external builders",
|
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;
|
|
@@ -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>()};
|