@lodestar/beacon-node 1.47.0-dev.57f6ac1a8b → 1.47.0-dev.59d71c272a
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 +43 -11
- 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 +13 -1
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/events/index.d.ts.map +1 -1
- package/lib/api/impl/events/index.js +2 -0
- package/lib/api/impl/events/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 +232 -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/api/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +7 -0
- package/lib/api/rest/base.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/importExecutionPayload.d.ts +3 -2
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +10 -8
- 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 +14 -6
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
- package/lib/chain/chain.d.ts +7 -3
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +16 -4
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +1 -0
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +6 -1
- package/lib/chain/errors/blockError.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 +6 -3
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
- 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 +2 -1
- package/lib/chain/opPools/index.d.ts.map +1 -1
- package/lib/chain/opPools/index.js +1 -0
- package/lib/chain/opPools/index.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +24 -6
- package/lib/chain/prepareNextSlot.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 +5 -2
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +3 -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/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.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/executionPayloadBid.d.ts +13 -3
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +146 -10
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/voluntaryExit.d.ts +9 -1
- package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
- package/lib/chain/validation/voluntaryExit.js +46 -1
- package/lib/chain/validation/voluntaryExit.js.map +1 -1
- package/lib/execution/builder/apiClient.d.ts +64 -0
- package/lib/execution/builder/apiClient.d.ts.map +1 -0
- package/lib/execution/builder/apiClient.js +184 -0
- package/lib/execution/builder/apiClient.js.map +1 -0
- package/lib/execution/builder/validateBid.d.ts +22 -0
- package/lib/execution/builder/validateBid.d.ts.map +1 -0
- package/lib/execution/builder/validateBid.js +111 -0
- package/lib/execution/builder/validateBid.js.map +1 -0
- package/lib/metrics/metrics/lodestar.d.ts +40 -5
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +68 -2
- 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.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +6 -2
- 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 +41 -17
- package/lib/network/processor/index.js.map +1 -1
- package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
- package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
- package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
- package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +7 -0
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/batch.d.ts +23 -5
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +61 -22
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +2 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +23 -10
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/types.d.ts +32 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +33 -0
- package/lib/sync/types.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 +143 -68
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +16 -11
- 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 +3 -1
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/package.json +34 -34
- package/src/api/impl/beacon/blocks/index.ts +50 -14
- package/src/api/impl/beacon/pool/index.ts +15 -1
- package/src/api/impl/events/index.ts +3 -1
- package/src/api/impl/lodestar/index.ts +1 -1
- package/src/api/impl/validator/index.ts +285 -49
- package/src/api/impl/validator/utils.ts +1 -1
- package/src/api/rest/base.ts +7 -0
- package/src/chain/archiveStore/archiveStore.ts +1 -1
- package/src/chain/blocks/importExecutionPayload.ts +12 -10
- package/src/chain/blocks/index.ts +18 -6
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
- package/src/chain/chain.ts +18 -2
- package/src/chain/errors/blockError.ts +8 -2
- package/src/chain/forkChoice/index.ts +4 -0
- package/src/chain/interface.ts +6 -2
- package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
- package/src/chain/opPools/executionPayloadBidPool.ts +13 -7
- package/src/chain/opPools/index.ts +2 -1
- package/src/chain/prepareNextSlot.ts +24 -6
- package/src/chain/produceBlock/produceBlockBody.ts +1 -1
- package/src/chain/regen/interface.ts +5 -2
- package/src/chain/regen/queued.ts +5 -5
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
- 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/executionPayloadBid.ts +167 -12
- package/src/chain/validation/voluntaryExit.ts +59 -2
- package/src/execution/builder/apiClient.ts +244 -0
- package/src/execution/builder/validateBid.ts +166 -0
- package/src/metrics/metrics/lodestar.ts +77 -7
- package/src/network/network.ts +7 -4
- package/src/network/processor/constants.ts +6 -0
- package/src/network/processor/gossipHandlers.ts +6 -2
- package/src/network/processor/gossipQueues/indexed.ts +0 -1
- package/src/network/processor/index.ts +53 -21
- package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
- package/src/node/nodejs.ts +8 -0
- package/src/sync/range/batch.ts +83 -28
- package/src/sync/range/chain.ts +26 -11
- package/src/sync/types.ts +33 -0
- package/src/sync/unknownBlock.ts +167 -73
- package/src/sync/utils/downloadByRange.ts +21 -12
- package/src/sync/utils/downloadByRoot.ts +3 -1
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
}
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const bid = signedExecutionPayloadBid.message;
|
|
43
|
+
const {slot, parentBlock, parentBlockHash, parentBlockRoot, entry} = request;
|
|
44
|
+
|
|
45
|
+
if (bid.slot !== slot) {
|
|
46
|
+
throw Error(`Bid slot=${bid.slot} does not match requested slot=${slot}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const bidParentBlockHash = toRootHex(bid.parentBlockHash);
|
|
50
|
+
const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
|
|
51
|
+
if (bidParentBlockHash !== parentBlockHash || bidParentBlockRoot !== parentBlockRoot) {
|
|
52
|
+
throw Error(
|
|
53
|
+
`Bid parent parentBlockHash=${bidParentBlockHash} parentBlockRoot=${bidParentBlockRoot} does not match ` +
|
|
54
|
+
`requested parentBlockHash=${parentBlockHash} parentBlockRoot=${parentBlockRoot}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const totalPayment = getBuilderBidTotalGwei(bid, entry.maxExecutionPayment);
|
|
59
|
+
if (totalPayment < entry.minBid) {
|
|
60
|
+
throw Error(
|
|
61
|
+
`Bid total payment=${prettyGweiToEth(totalPayment)} ` +
|
|
62
|
+
`(value=${prettyGweiToEth(bid.value)} ` +
|
|
63
|
+
`executionPayment=${prettyGweiToEth(bid.executionPayment)}) ` +
|
|
64
|
+
`is below minBid=${prettyGweiToEth(entry.minBid)}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const state = await chain.regen
|
|
69
|
+
.getBlockSlotState(parentBlock, slot, {dontTransferCache: true}, RegenCaller.validateGossipExecutionPayloadBid)
|
|
70
|
+
.catch((e: Error) => {
|
|
71
|
+
throw Error(`Unable to regenerate state to validate bid: ${e.message}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!isStatePostGloas(state)) {
|
|
75
|
+
throw Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (bid.builderIndex >= state.getBuildersLength()) {
|
|
79
|
+
throw Error(`Bid builderIndex=${bid.builderIndex} is out of bounds`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const builder = state.getBuilder(bid.builderIndex);
|
|
83
|
+
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
84
|
+
throw Error(`Bid builderIndex=${bid.builderIndex} is not an active builder`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (builder.version !== PAYLOAD_BUILDER_VERSION) {
|
|
88
|
+
throw Error(`Invalid builder version=${builder.version} expected=${PAYLOAD_BUILDER_VERSION}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// A bid not signed by one of the builder pubkeys the entry accepts bids from must not be accepted
|
|
92
|
+
if (
|
|
93
|
+
entry.builderPubkeys.length > 0 &&
|
|
94
|
+
!entry.builderPubkeys.some((pubkey) => byteArrayEquals(pubkey, builder.pubkey))
|
|
95
|
+
) {
|
|
96
|
+
throw Error(
|
|
97
|
+
`Bid builder pubkey=${toHex(builder.pubkey)} is not in the entry's ` +
|
|
98
|
+
`builderPubkeys=${entry.builderPubkeys.map(toHex).join(",")}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
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
|
+
// The coverage check only applies to the staked collateral payment, a pure execution
|
|
109
|
+
// layer payment bid has nothing to cover on-chain
|
|
110
|
+
if (bid.value > 0 && !state.canBuilderCoverBid(bid.builderIndex, bid.value)) {
|
|
111
|
+
throw Error(
|
|
112
|
+
`Builder cannot cover bid value=${prettyGweiToEth(bid.value)} ` + `balance=${prettyGweiToEth(builder.balance)}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
|
|
117
|
+
if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
|
|
118
|
+
throw Error(`Invalid bid prevRandao=${toHex(bid.prevRandao)} expected=${toHex(randaoMix)}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The builder must honor the proposer preferences it learned over gossip
|
|
122
|
+
const bidEpoch = computeEpochAtSlot(bid.slot);
|
|
123
|
+
const dependentRootHex = (() => {
|
|
124
|
+
try {
|
|
125
|
+
return getShufflingDependentRoot(chain.forkChoice, bidEpoch, computeEpochAtSlot(parentBlock.slot), parentBlock);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
})();
|
|
130
|
+
if (dependentRootHex === null) {
|
|
131
|
+
throw Error(`Unable to resolve proposer preferences dependent root for bid slot=${bid.slot}`);
|
|
132
|
+
}
|
|
133
|
+
const proposerPreferences = chain.proposerPreferencesPool.get(bid.slot, dependentRootHex);
|
|
134
|
+
if (proposerPreferences === null) {
|
|
135
|
+
throw Error(`No proposer preferences found for bid slot=${bid.slot} dependentRoot=${dependentRootHex}`);
|
|
136
|
+
}
|
|
137
|
+
if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
|
|
138
|
+
throw Error(
|
|
139
|
+
`Bid feeRecipient=${toHex(bid.feeRecipient)} does not match ` +
|
|
140
|
+
`proposer preferences feeRecipient=${toHex(proposerPreferences.message.feeRecipient)}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const parentPayloadVariant = chain.forkChoice.getBlockHexAndBlockHash(bidParentBlockRoot, bidParentBlockHash);
|
|
145
|
+
if (parentPayloadVariant === null || parentPayloadVariant.executionPayloadBlockHash === null) {
|
|
146
|
+
throw Error(`Unable to resolve parent payload gas limit for bid parentBlockHash=${bidParentBlockHash}`);
|
|
147
|
+
}
|
|
148
|
+
const parentGasLimit = BigInt(parentPayloadVariant.executionPayloadGasLimit);
|
|
149
|
+
const targetGasLimit = proposerPreferences.message.targetGasLimit;
|
|
150
|
+
if (!isGasLimitTargetCompatible(parentGasLimit, bid.gasLimit, targetGasLimit)) {
|
|
151
|
+
throw Error(
|
|
152
|
+
`Bid gasLimit=${bid.gasLimit} is not compatible with ` +
|
|
153
|
+
`parentGasLimit=${parentGasLimit} targetGasLimit=${targetGasLimit}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const signatureSet = createSingleSignatureSetFromComponents(
|
|
158
|
+
builder.pubkey,
|
|
159
|
+
getExecutionPayloadBidSigningRoot(chain.config, bid),
|
|
160
|
+
signedExecutionPayloadBid.signature
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
|
|
164
|
+
throw Error(`Invalid bid signature builderIndex=${bid.builderIndex}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -4,7 +4,10 @@ import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
|
|
|
4
4
|
import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
|
|
5
5
|
import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
|
|
6
6
|
import {PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
PayloadEnvelopeInputPruneReason,
|
|
9
|
+
PayloadEnvelopeInputSource,
|
|
10
|
+
} from "../../chain/blocks/payloadEnvelopeInput/index.js";
|
|
8
11
|
import {JobQueueItemType} from "../../chain/bls/index.js";
|
|
9
12
|
import {AttestationErrorCode, BlockErrorCode} from "../../chain/errors/index.js";
|
|
10
13
|
import {
|
|
@@ -21,7 +24,7 @@ import {ExecutionPayloadStatus} from "../../execution/index.js";
|
|
|
21
24
|
import {GossipType} from "../../network/index.js";
|
|
22
25
|
import {CannotAcceptWorkReason, ReprocessRejectReason} from "../../network/processor/index.js";
|
|
23
26
|
import {BackfillSyncMethod} from "../../sync/backfill/backfill.js";
|
|
24
|
-
import {PendingBlockType} from "../../sync/types.js";
|
|
27
|
+
import {DroppedItemReason, PendingBlockType} from "../../sync/types.js";
|
|
25
28
|
import {PeerSyncType, RangeSyncType} from "../../sync/utils/remoteSyncType.js";
|
|
26
29
|
import {AllocSource} from "../../util/bufferPool.js";
|
|
27
30
|
import {DataColumnReconstructionCode} from "../../util/dataColumns.js";
|
|
@@ -101,9 +104,10 @@ export function createLodestarMetrics(
|
|
|
101
104
|
help: "Age of the first item of each key in the indexed queues in seconds",
|
|
102
105
|
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 5],
|
|
103
106
|
}),
|
|
104
|
-
queueTime: register.histogram({
|
|
107
|
+
queueTime: register.histogram<{topic: GossipType}>({
|
|
105
108
|
name: "lodestar_gossip_validation_queue_time_seconds",
|
|
106
109
|
help: "Total time an item stays in queue until it is processed in seconds",
|
|
110
|
+
labelNames: ["topic"],
|
|
107
111
|
buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 5],
|
|
108
112
|
}),
|
|
109
113
|
},
|
|
@@ -677,9 +681,15 @@ export function createLodestarMetrics(
|
|
|
677
681
|
name: "lodestar_sync_unknown_block_downloaded_blocks_error_total",
|
|
678
682
|
help: "Total number of downloaded blocks errors in UnknownBlockSync",
|
|
679
683
|
}),
|
|
680
|
-
removedBlocks: register.gauge({
|
|
684
|
+
removedBlocks: register.gauge<{reason: DroppedItemReason}>({
|
|
681
685
|
name: "lodestar_sync_unknown_block_removed_blocks_total",
|
|
682
|
-
help: "Total
|
|
686
|
+
help: "Total pending blocks dropped from BlockInputSync without completing, by reason",
|
|
687
|
+
labelNames: ["reason"],
|
|
688
|
+
}),
|
|
689
|
+
removedPayloads: register.gauge<{reason: DroppedItemReason}>({
|
|
690
|
+
name: "lodestar_sync_unknown_block_removed_payloads_total",
|
|
691
|
+
help: "Total pending payloads dropped from BlockInputSync without completing, by reason",
|
|
692
|
+
labelNames: ["reason"],
|
|
683
693
|
}),
|
|
684
694
|
elapsedTimeTillReceived: register.histogram({
|
|
685
695
|
name: "lodestar_sync_unknown_block_elapsed_time_till_received",
|
|
@@ -1225,6 +1235,12 @@ export function createLodestarMetrics(
|
|
|
1225
1235
|
name: "lodestar_oppool_attester_slashing_pool_size",
|
|
1226
1236
|
help: "Current size of the AttesterSlashingPool",
|
|
1227
1237
|
}),
|
|
1238
|
+
deferredVoluntaryExitPool: {
|
|
1239
|
+
size: register.gauge({
|
|
1240
|
+
name: "lodestar_oppool_deferred_voluntary_exit_pool_size",
|
|
1241
|
+
help: "Current size of the DeferredVoluntaryExitPool",
|
|
1242
|
+
}),
|
|
1243
|
+
},
|
|
1228
1244
|
proposerSlashingPoolSize: register.gauge({
|
|
1229
1245
|
name: "lodestar_oppool_proposer_slashing_pool_size",
|
|
1230
1246
|
help: "Current size of the ProposerSlashingPool",
|
|
@@ -1335,6 +1351,11 @@ export function createLodestarMetrics(
|
|
|
1335
1351
|
help: "Total number of InsertOutcome as a result of adding an execution payload bid from api to the pool",
|
|
1336
1352
|
labelNames: ["insertOutcome"],
|
|
1337
1353
|
}),
|
|
1354
|
+
apiValidationTime: register.histogram({
|
|
1355
|
+
name: "lodestar_api_execution_payload_bid_validation_time_seconds",
|
|
1356
|
+
help: "Time elapsed for signed execution payload bid validation - api path",
|
|
1357
|
+
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.5],
|
|
1358
|
+
}),
|
|
1338
1359
|
},
|
|
1339
1360
|
},
|
|
1340
1361
|
|
|
@@ -1429,6 +1450,16 @@ export function createLodestarMetrics(
|
|
|
1429
1450
|
help: "Checkpoint state cache size",
|
|
1430
1451
|
labelNames: ["type"],
|
|
1431
1452
|
}),
|
|
1453
|
+
persistentTierEpochs: register.gauge<{tier: number}>({
|
|
1454
|
+
name: "lodestar_cp_state_cache_persistent_tier_epochs",
|
|
1455
|
+
help: "Number of epoch keys retained in each on-disk checkpoint state retention tier",
|
|
1456
|
+
labelNames: ["tier"],
|
|
1457
|
+
}),
|
|
1458
|
+
persistentTierStates: register.gauge<{tier: number}>({
|
|
1459
|
+
name: "lodestar_cp_state_cache_persistent_tier_states",
|
|
1460
|
+
help: "Number of persisted checkpoint states retained in each on-disk retention tier",
|
|
1461
|
+
labelNames: ["tier"],
|
|
1462
|
+
}),
|
|
1432
1463
|
reads: register.avgMinMax({
|
|
1433
1464
|
name: "lodestar_cp_state_epoch_reads",
|
|
1434
1465
|
help: "Avg min max of all state cache items total read count",
|
|
@@ -1643,9 +1674,15 @@ export function createLodestarMetrics(
|
|
|
1643
1674
|
name: "lodestar_seen_payload_envelope_input_cache_serialized_object_refs",
|
|
1644
1675
|
help: "Number of serialized-cache object refs retained by cached PayloadEnvelopeInputs",
|
|
1645
1676
|
}),
|
|
1646
|
-
created: register.counter({
|
|
1677
|
+
created: register.counter<{source: PayloadEnvelopeInputSource}>({
|
|
1647
1678
|
name: "lodestar_seen_payload_envelope_input_cache_items_created_total",
|
|
1648
|
-
help: "Number of PayloadEnvelopeInputs created",
|
|
1679
|
+
help: "Number of PayloadEnvelopeInputs created by source",
|
|
1680
|
+
labelNames: ["source"],
|
|
1681
|
+
}),
|
|
1682
|
+
pruned: register.counter<{reason: PayloadEnvelopeInputPruneReason}>({
|
|
1683
|
+
name: "lodestar_seen_payload_envelope_input_cache_items_pruned_total",
|
|
1684
|
+
help: "Number of PayloadEnvelopeInputs evicted by reason",
|
|
1685
|
+
labelNames: ["reason"],
|
|
1649
1686
|
}),
|
|
1650
1687
|
},
|
|
1651
1688
|
},
|
|
@@ -1755,6 +1792,10 @@ export function createLodestarMetrics(
|
|
|
1755
1792
|
name: "lodestar_precompute_next_epoch_transition_waste_total",
|
|
1756
1793
|
help: "Total number of precomputing next epoch transition wasted",
|
|
1757
1794
|
}),
|
|
1795
|
+
predictedReorg: register.counter({
|
|
1796
|
+
name: "lodestar_precompute_next_epoch_transition_predicted_reorg_total",
|
|
1797
|
+
help: "Predicted epoch-boundary reorgs where the strong parent was dialed instead of the weak head",
|
|
1798
|
+
}),
|
|
1758
1799
|
duration: register.histogram({
|
|
1759
1800
|
name: "lodestar_precompute_next_epoch_transition_duration_seconds",
|
|
1760
1801
|
help: "Duration of precomputeNextEpochTransition, including epoch transition and hashTreeRoot",
|
|
@@ -1994,6 +2035,35 @@ export function createLodestarMetrics(
|
|
|
1994
2035
|
}),
|
|
1995
2036
|
},
|
|
1996
2037
|
|
|
2038
|
+
builderApi: {
|
|
2039
|
+
bidRequests: register.counter({
|
|
2040
|
+
name: "lodestar_builder_api_bid_requests_total",
|
|
2041
|
+
help: "Total count of execution payload bid requests sent to external builders",
|
|
2042
|
+
}),
|
|
2043
|
+
bidRequestErrors: register.counter({
|
|
2044
|
+
name: "lodestar_builder_api_bid_request_errors_total",
|
|
2045
|
+
help: "Total count of failed execution payload bid requests to external builders",
|
|
2046
|
+
}),
|
|
2047
|
+
bidsReceived: register.counter({
|
|
2048
|
+
name: "lodestar_builder_api_bids_received_total",
|
|
2049
|
+
help: "Total count of execution payload bids received from external builders",
|
|
2050
|
+
}),
|
|
2051
|
+
bidsDiscarded: register.counter({
|
|
2052
|
+
name: "lodestar_builder_api_bids_discarded_total",
|
|
2053
|
+
help: "Total count of execution payload bids from external builders discarded due to failed validation",
|
|
2054
|
+
}),
|
|
2055
|
+
blockSubmissions: register.counter<{status: "success" | "error"}>({
|
|
2056
|
+
name: "lodestar_builder_api_block_submissions_total",
|
|
2057
|
+
help: "Total count of signed beacon blocks submitted to external builders",
|
|
2058
|
+
labelNames: ["status"],
|
|
2059
|
+
}),
|
|
2060
|
+
preferencesForwarded: register.counter<{status: "success" | "error"}>({
|
|
2061
|
+
name: "lodestar_builder_api_preferences_forwarded_total",
|
|
2062
|
+
help: "Total count of builder preferences forwarded to external builders",
|
|
2063
|
+
labelNames: ["status"],
|
|
2064
|
+
}),
|
|
2065
|
+
},
|
|
2066
|
+
|
|
1997
2067
|
db: {
|
|
1998
2068
|
dbReadReq: register.gauge<{bucket: string}>({
|
|
1999
2069
|
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;
|
|
@@ -196,7 +196,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
196
196
|
forkName: fork,
|
|
197
197
|
sampledColumns: chain.custodyConfig.sampledColumns,
|
|
198
198
|
custodyColumns: chain.custodyConfig.custodyColumns,
|
|
199
|
-
|
|
199
|
+
seenTimestampSec,
|
|
200
|
+
source: PayloadEnvelopeInputSource.gossip,
|
|
200
201
|
});
|
|
201
202
|
}
|
|
202
203
|
|
|
@@ -640,6 +641,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
640
641
|
metrics?.gossipBlock.elapsedTimeTillProcessed.observe(delaySec);
|
|
641
642
|
|
|
642
643
|
if (isForkPostGloas(blockInput.forkName)) {
|
|
644
|
+
// we should have the payloadInput in the seen cache so no need getOrReload() here
|
|
643
645
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockInput.blockRootHex);
|
|
644
646
|
// This payloadInput should have been created just after gossip validation
|
|
645
647
|
if (!payloadInput) {
|
|
@@ -1189,6 +1191,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1189
1191
|
chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.gossip, delaySec, signedEnvelope);
|
|
1190
1192
|
|
|
1191
1193
|
const blockRootHex = toRootHex(envelope.beaconBlockRoot);
|
|
1194
|
+
// a gossip payload cannot be `MAX_PAYLOAD_ENVELOPE_INPUT_CACHE_SIZE` slots after the block
|
|
1195
|
+
// otherwise it'll get to UnknownBlockInput flow
|
|
1192
1196
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
|
|
1193
1197
|
|
|
1194
1198
|
if (!payloadInput) {
|
|
@@ -1302,7 +1306,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1302
1306
|
|
|
1303
1307
|
// Handle valid payload bid by storing in a bid pool
|
|
1304
1308
|
try {
|
|
1305
|
-
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
|
|
1309
|
+
const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
|
|
1306
1310
|
metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
|
|
1307
1311
|
} catch (e) {
|
|
1308
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>()};
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
GossipValidatorBatchFn,
|
|
31
31
|
GossipValidatorFn,
|
|
32
32
|
} from "../gossip/interface.js";
|
|
33
|
+
import {MAX_PEERS_PER_ROOT} from "./constants.js";
|
|
33
34
|
import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
|
|
34
35
|
import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
|
|
35
36
|
import {createGossipQueues} from "./gossipQueues/index.js";
|
|
@@ -191,8 +192,12 @@ export class NetworkProcessor {
|
|
|
191
192
|
// in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
|
|
192
193
|
private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
|
|
193
194
|
private awaitingPayloadMessageCount = 0;
|
|
194
|
-
private unknownBlocksBySlot = new MapDef<Slot,
|
|
195
|
-
|
|
195
|
+
private unknownBlocksBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
|
|
196
|
+
() => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
|
|
197
|
+
);
|
|
198
|
+
private unknownEnvelopesBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
|
|
199
|
+
() => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
|
|
200
|
+
);
|
|
196
201
|
|
|
197
202
|
constructor(
|
|
198
203
|
modules: NetworkProcessorModules,
|
|
@@ -271,35 +276,53 @@ export class NetworkProcessor {
|
|
|
271
276
|
* Search block via `ChainEvent.unknownBlockRoot` event
|
|
272
277
|
* 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
278
|
* 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.
|
|
279
|
+
* We forward every distinct peer that gossiped this root (not just the first) so BlockInputSync can
|
|
280
|
+
* prefer them as by-root download candidates.
|
|
274
281
|
*/
|
|
275
282
|
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
|
-
) {
|
|
283
|
+
if (this.chain.seenBlock(root)) {
|
|
281
284
|
return;
|
|
282
285
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
|
|
287
|
+
const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
|
|
288
|
+
const forwardedPeers = peersForRoot.getOrDefault(root);
|
|
289
|
+
|
|
290
|
+
// brand-new search always emits (peer may be undefined)
|
|
291
|
+
let shouldEmit = !alreadySearching;
|
|
292
|
+
if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
|
|
293
|
+
// First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
|
|
294
|
+
forwardedPeers.add(peer);
|
|
295
|
+
shouldEmit = true;
|
|
296
|
+
}
|
|
297
|
+
if (shouldEmit) {
|
|
298
|
+
this.chain.emitter.emit(ChainEvent.unknownBlockRoot, {rootHex: root, peer, source});
|
|
299
|
+
}
|
|
286
300
|
}
|
|
287
301
|
|
|
288
302
|
/**
|
|
289
303
|
* Search envelope via `ChainEvent.unknownEnvelopeBlockRoot` event
|
|
290
304
|
* 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
305
|
* 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.
|
|
306
|
+
* Same peer-forwarding + dedup behavior as searchUnknownBlock.
|
|
292
307
|
*/
|
|
293
308
|
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
|
-
) {
|
|
309
|
+
if (this.chain.seenPayloadEnvelope(root)) {
|
|
299
310
|
return;
|
|
300
311
|
}
|
|
301
|
-
this.unknownEnvelopesBySlot.getOrDefault(slot)
|
|
302
|
-
|
|
312
|
+
const peersForRoot = this.unknownEnvelopesBySlot.getOrDefault(slot);
|
|
313
|
+
// capture "already searching" BEFORE getOrDefault(root) creates the entry
|
|
314
|
+
const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByPayloadBlockRoot.has(root);
|
|
315
|
+
const forwardedPeers = peersForRoot.getOrDefault(root);
|
|
316
|
+
|
|
317
|
+
let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
|
|
318
|
+
if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
|
|
319
|
+
// First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
|
|
320
|
+
forwardedPeers.add(peer);
|
|
321
|
+
shouldEmit = true;
|
|
322
|
+
}
|
|
323
|
+
if (shouldEmit) {
|
|
324
|
+
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
|
|
325
|
+
}
|
|
303
326
|
}
|
|
304
327
|
|
|
305
328
|
private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
|
|
@@ -537,6 +560,7 @@ export class NetworkProcessor {
|
|
|
537
560
|
|
|
538
561
|
private pushPendingGossipsubMessageToQueue(message: PendingGossipsubMessage): void {
|
|
539
562
|
const topicType = message.topic.type;
|
|
563
|
+
message.queueAddedMs = Date.now();
|
|
540
564
|
const droppedCount = this.gossipQueues[topicType].add(message);
|
|
541
565
|
if (droppedCount) {
|
|
542
566
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
@@ -616,7 +640,7 @@ export class NetworkProcessor {
|
|
|
616
640
|
|
|
617
641
|
for (const [slot, roots] of this.unknownBlocksBySlot) {
|
|
618
642
|
if (slot > minSlot) continue;
|
|
619
|
-
for (const rootHex of roots) {
|
|
643
|
+
for (const rootHex of roots.keys()) {
|
|
620
644
|
const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
|
|
621
645
|
if (gossipMessages !== undefined) {
|
|
622
646
|
for (const message of gossipMessages) {
|
|
@@ -641,7 +665,7 @@ export class NetworkProcessor {
|
|
|
641
665
|
|
|
642
666
|
for (const [slot, roots] of this.unknownEnvelopesBySlot) {
|
|
643
667
|
if (slot > minSlot) continue;
|
|
644
|
-
for (const rootHex of roots) {
|
|
668
|
+
for (const rootHex of roots.keys()) {
|
|
645
669
|
const gossipMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
|
|
646
670
|
if (gossipMessages !== undefined) {
|
|
647
671
|
for (const message of gossipMessages) {
|
|
@@ -723,12 +747,20 @@ export class NetworkProcessor {
|
|
|
723
747
|
for (const msg of messageOrArray) {
|
|
724
748
|
msg.startProcessUnixSec = nowSec;
|
|
725
749
|
if (msg.queueAddedMs !== undefined) {
|
|
726
|
-
this.metrics?.gossipValidationQueue.queueTime.observe(
|
|
750
|
+
this.metrics?.gossipValidationQueue.queueTime.observe(
|
|
751
|
+
{topic: msg.topic.type},
|
|
752
|
+
nowSec - msg.queueAddedMs / 1000
|
|
753
|
+
);
|
|
727
754
|
}
|
|
728
755
|
}
|
|
729
756
|
} else {
|
|
730
|
-
// indexed queue is not used here
|
|
731
757
|
messageOrArray.startProcessUnixSec = nowSec;
|
|
758
|
+
if (messageOrArray.queueAddedMs !== undefined) {
|
|
759
|
+
this.metrics?.gossipValidationQueue.queueTime.observe(
|
|
760
|
+
{topic: messageOrArray.topic.type},
|
|
761
|
+
nowSec - messageOrArray.queueAddedMs / 1000
|
|
762
|
+
);
|
|
763
|
+
}
|
|
732
764
|
}
|
|
733
765
|
|
|
734
766
|
const acceptanceArr = Array.isArray(messageOrArray)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {routes} from "@lodestar/api";
|
|
2
|
+
import {Logger} from "@lodestar/utils";
|
|
3
|
+
import {IBeaconChain} from "../chain/index.js";
|
|
4
|
+
import {INetwork} from "../network/index.js";
|
|
5
|
+
import {ClockEvent} from "../util/clock.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* On every epoch, drain the deferred voluntary exit pool and publish the exits that have become
|
|
9
|
+
* processable. Lives at the node layer rather than inside `BeaconChain` because publishing needs
|
|
10
|
+
* `network`, which the chain does not (and should not) depend on.
|
|
11
|
+
*/
|
|
12
|
+
export function startDeferredVoluntaryExitPublisher({
|
|
13
|
+
chain,
|
|
14
|
+
network,
|
|
15
|
+
logger,
|
|
16
|
+
signal,
|
|
17
|
+
}: {
|
|
18
|
+
chain: IBeaconChain;
|
|
19
|
+
network: INetwork;
|
|
20
|
+
logger: Logger;
|
|
21
|
+
signal: AbortSignal;
|
|
22
|
+
}): void {
|
|
23
|
+
const onEpoch = async (): Promise<void> => {
|
|
24
|
+
try {
|
|
25
|
+
// Use the cached head state directly rather than getHeadStateAtCurrentEpoch, which would regen
|
|
26
|
+
// the head forward to the current epoch. At init from a stale db-finalized state that regen can
|
|
27
|
+
// be many epoch transitions. The head state is fine here: this is a best-effort drain that
|
|
28
|
+
// re-runs every epoch, so a just-processable exit is republished next tick.
|
|
29
|
+
const state = chain.getHeadState();
|
|
30
|
+
const exits = chain.deferredVoluntaryExitPool.drainProcessableExits(state);
|
|
31
|
+
for (const exit of exits) {
|
|
32
|
+
try {
|
|
33
|
+
chain.opPool.insertVoluntaryExit(exit);
|
|
34
|
+
chain.emitter.emit(routes.events.EventType.voluntaryExit, exit);
|
|
35
|
+
await network.publishVoluntaryExit(exit);
|
|
36
|
+
logger.info("Voluntary exit successfully published for validator", {
|
|
37
|
+
validatorIndex: exit.message.validatorIndex,
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
logger.warn(
|
|
41
|
+
"Failed to publish deferred voluntary exit",
|
|
42
|
+
{validatorIndex: exit.message.validatorIndex},
|
|
43
|
+
e as Error
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
logger.warn("Failed to drain deferred voluntary exit pool", {}, e as Error);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
chain.clock.addListener(ClockEvent.epoch, onEpoch);
|
|
53
|
+
signal.addEventListener("abort", () => chain.clock.removeListener(ClockEvent.epoch, onEpoch), {once: true});
|
|
54
|
+
}
|
package/src/node/nodejs.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {Network, getReqRespHandlers} from "../network/index.js";
|
|
|
22
22
|
import {BackfillSync} from "../sync/backfill/index.js";
|
|
23
23
|
import {BeaconSync, IBeaconSync} from "../sync/index.js";
|
|
24
24
|
import {Clock} from "../util/clock.js";
|
|
25
|
+
import {startDeferredVoluntaryExitPublisher} from "./deferredVoluntaryExitPublisher.js";
|
|
25
26
|
import {runNodeNotifier} from "./notifier.js";
|
|
26
27
|
import {IBeaconNodeOptions} from "./options.js";
|
|
27
28
|
|
|
@@ -259,6 +260,11 @@ export class BeaconNode {
|
|
|
259
260
|
executionBuilder: opts.executionBuilder.enabled
|
|
260
261
|
? initializeExecutionBuilder(opts.executionBuilder, config, metrics, logger)
|
|
261
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
|
+
},
|
|
262
268
|
});
|
|
263
269
|
|
|
264
270
|
// Load persisted data from disk to in-memory caches
|
|
@@ -335,6 +341,8 @@ export class BeaconNode {
|
|
|
335
341
|
|
|
336
342
|
void runNodeNotifier({network, chain, sync, config, logger, signal});
|
|
337
343
|
|
|
344
|
+
startDeferredVoluntaryExitPublisher({chain, network, logger, signal});
|
|
345
|
+
|
|
338
346
|
return new BeaconNode({
|
|
339
347
|
opts,
|
|
340
348
|
config,
|