@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
|
@@ -52,12 +52,16 @@ import {
|
|
|
52
52
|
import {
|
|
53
53
|
GWEI_TO_WEI,
|
|
54
54
|
TimeoutError,
|
|
55
|
+
byteArrayEquals,
|
|
55
56
|
defer,
|
|
56
57
|
formatWeiToEth,
|
|
57
58
|
fromHex,
|
|
59
|
+
prettyGweiToEth,
|
|
58
60
|
prettyWeiToEth,
|
|
59
61
|
resolveOrRacePromises,
|
|
62
|
+
sleep,
|
|
60
63
|
toHex,
|
|
64
|
+
toPrintableUrl,
|
|
61
65
|
toRootHex,
|
|
62
66
|
} from "@lodestar/utils";
|
|
63
67
|
import {MAX_BUILDER_BOOST_FACTOR} from "@lodestar/validator";
|
|
@@ -72,6 +76,7 @@ import {
|
|
|
72
76
|
SyncCommitteeErrorCode,
|
|
73
77
|
} from "../../../chain/errors/index.js";
|
|
74
78
|
import {ChainEvent, CommonBlockBody} from "../../../chain/index.js";
|
|
79
|
+
import {PooledExecutionPayloadBid} from "../../../chain/opPools/index.js";
|
|
75
80
|
import {PREPARE_NEXT_SLOT_BPS} from "../../../chain/prepareNextSlot.js";
|
|
76
81
|
import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/produceBlock/index.js";
|
|
77
82
|
import {RegenCaller} from "../../../chain/regen/index.js";
|
|
@@ -80,7 +85,9 @@ import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
|
|
|
80
85
|
import {validateGossipProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
|
|
81
86
|
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
|
|
82
87
|
import {ZERO_HASH} from "../../../constants/index.js";
|
|
88
|
+
import {BUILDER_BID_DEADLINE_MS, BuilderApiBid, decodeBuilderUrl} from "../../../execution/builder/apiClient.js";
|
|
83
89
|
import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
|
|
90
|
+
import {getBuilderBidTotalGwei, validateBuilderApiExecutionPayloadBid} from "../../../execution/builder/validateBid.js";
|
|
84
91
|
import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
|
|
85
92
|
import {CommitteeSubscription} from "../../../network/subnets/index.js";
|
|
86
93
|
import {callInNextEventLoop} from "../../../util/eventLoop.js";
|
|
@@ -112,6 +119,55 @@ import {
|
|
|
112
119
|
const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000;
|
|
113
120
|
/** Overall timeout for execution and block production apis */
|
|
114
121
|
const BLOCK_PRODUCTION_RACE_TIMEOUT_MS = 12_000;
|
|
122
|
+
/** Rejection message of the bid block branch when there is no viable bid to commit to */
|
|
123
|
+
const NO_BID_AVAILABLE = "No builder bid available";
|
|
124
|
+
|
|
125
|
+
type BidCandidate = {
|
|
126
|
+
signedBid: gloas.SignedExecutionPayloadBid;
|
|
127
|
+
/** Total payment in Gwei, counting the execution payment at most at the entry's cap */
|
|
128
|
+
totalGwei: bigint;
|
|
129
|
+
boostFactor: bigint;
|
|
130
|
+
url?: string;
|
|
131
|
+
/** Time in milliseconds from the slot start when the bid was received */
|
|
132
|
+
receivedMs: number;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Return the best bid by boosted total payment. A candidate with the max boost factor is
|
|
137
|
+
* preferred over any other regardless of value, ties prefer a builder api bid over the
|
|
138
|
+
* p2p bid, and the earlier received bid between builder api bids.
|
|
139
|
+
*/
|
|
140
|
+
function selectBestBid(candidates: BidCandidate[]): BidCandidate | null {
|
|
141
|
+
const boostedValue = ({totalGwei, boostFactor}: BidCandidate): bigint => boostFactor * totalGwei;
|
|
142
|
+
let best: BidCandidate | null = null;
|
|
143
|
+
for (const candidate of candidates) {
|
|
144
|
+
if (best === null) {
|
|
145
|
+
best = candidate;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Preserve max boost preference before comparing bid values
|
|
149
|
+
const candidateIsMaxBoost = candidate.boostFactor === MAX_BUILDER_BOOST_FACTOR;
|
|
150
|
+
const bestIsMaxBoost = best.boostFactor === MAX_BUILDER_BOOST_FACTOR;
|
|
151
|
+
if (candidateIsMaxBoost !== bestIsMaxBoost) {
|
|
152
|
+
if (candidateIsMaxBoost) {
|
|
153
|
+
best = candidate;
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const candidateValue = boostedValue(candidate);
|
|
158
|
+
const bestValue = boostedValue(best);
|
|
159
|
+
if (candidateValue > bestValue) {
|
|
160
|
+
best = candidate;
|
|
161
|
+
} else if (
|
|
162
|
+
candidateValue === bestValue &&
|
|
163
|
+
// A tie prefers a builder api bid over the p2p bid, and the earlier received bid otherwise
|
|
164
|
+
(best.url === undefined || (candidate.url !== undefined && candidate.receivedMs < best.receivedMs))
|
|
165
|
+
) {
|
|
166
|
+
best = candidate;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return best;
|
|
170
|
+
}
|
|
115
171
|
|
|
116
172
|
type ProduceBlockContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & {
|
|
117
173
|
data: BlockContents;
|
|
@@ -857,7 +913,7 @@ export function getValidatorApi(
|
|
|
857
913
|
feeRecipient,
|
|
858
914
|
strictFeeRecipientCheck,
|
|
859
915
|
includePayload,
|
|
860
|
-
|
|
916
|
+
builderConfig,
|
|
861
917
|
}) {
|
|
862
918
|
const fork = config.getForkName(slot);
|
|
863
919
|
|
|
@@ -865,7 +921,7 @@ export function getValidatorApi(
|
|
|
865
921
|
throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
|
|
866
922
|
}
|
|
867
923
|
|
|
868
|
-
builderBoostFactor = builderBoostFactor
|
|
924
|
+
const builderBoostFactor = builderConfig.builderBoostFactor;
|
|
869
925
|
if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
|
|
870
926
|
throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
|
|
871
927
|
}
|
|
@@ -892,32 +948,122 @@ export function getValidatorApi(
|
|
|
892
948
|
})
|
|
893
949
|
);
|
|
894
950
|
|
|
895
|
-
// TODO GLOAS: add external builder api support when it is implemented
|
|
896
951
|
const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
|
|
897
952
|
const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
|
|
953
|
+
// Post-gloas every proposal parent has an execution payload hash
|
|
954
|
+
if (bidParentBlockHash === null) {
|
|
955
|
+
throw new ApiError(500, `Unknown parent block hash for proposal parent ${parentBlockRootHex}`);
|
|
956
|
+
}
|
|
898
957
|
const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot, parentBlock);
|
|
899
|
-
// Keep a builder bid as fallback unless the circuit breaker is active
|
|
900
|
-
const builderBid = circuitBreakerActive
|
|
901
|
-
? null
|
|
902
|
-
: chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
|
|
903
958
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
959
|
+
// Fire builder API bid requests while the local payload is built, one request per entry.
|
|
960
|
+
// Any entry failure yields no bid and never fails block production.
|
|
961
|
+
let builderApiBidsPromise: Promise<BuilderApiBid[]> = Promise.resolve([]);
|
|
962
|
+
if (builderConfig.builders.length > 0 && !circuitBreakerActive) {
|
|
963
|
+
try {
|
|
964
|
+
const proposerIndex = chain.getHeadState().getBeaconProposer(slot);
|
|
965
|
+
const proposerPubkey = chain.pubkeyCache.getOrThrow(proposerIndex).toBytes();
|
|
966
|
+
builderApiBidsPromise = chain.builderApiClient.getExecutionPayloadBids(
|
|
967
|
+
builderConfig.builders,
|
|
968
|
+
slot,
|
|
969
|
+
fromHex(bidParentBlockHash),
|
|
970
|
+
parentBlockRoot,
|
|
971
|
+
proposerPubkey
|
|
972
|
+
);
|
|
973
|
+
} catch (e) {
|
|
974
|
+
logger.warn("Unable to request builder API bids", {slot}, e as Error);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// A builder bid is expected if builders are configured or a p2p bid was already received.
|
|
979
|
+
// Used by the censorship override which may run before the bid deadline
|
|
980
|
+
const builderBidExpected =
|
|
981
|
+
builderConfig.builders.length > 0 ||
|
|
982
|
+
(!circuitBreakerActive &&
|
|
983
|
+
chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex) !== null);
|
|
984
|
+
|
|
985
|
+
// Select the p2p bid once builders had time to bid up, matching the deadline advertised
|
|
986
|
+
// on builder API bid requests, unless the circuit breaker is active
|
|
987
|
+
const p2pBidPromise: Promise<PooledExecutionPayloadBid | null> = circuitBreakerActive
|
|
988
|
+
? Promise.resolve(null)
|
|
989
|
+
: sleep(Math.max(0, BUILDER_BID_DEADLINE_MS - chain.clock.msFromSlot(slot))).then(() => {
|
|
990
|
+
const p2pBid = chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
|
|
991
|
+
// Discard p2p bids below the proposer's configured floor on the total payment.
|
|
992
|
+
// A p2p bid's total is just its value since gossip validation enforces executionPayment=0.
|
|
993
|
+
if (p2pBid !== null && BigInt(p2pBid.signedBid.message.value) < builderConfig.minBid) {
|
|
994
|
+
logger.info("Best p2p bid below configured minimum", {
|
|
995
|
+
slot,
|
|
996
|
+
bidValue: prettyGweiToEth(p2pBid.signedBid.message.value),
|
|
997
|
+
minBid: prettyGweiToEth(builderConfig.minBid),
|
|
998
|
+
});
|
|
999
|
+
return null;
|
|
918
1000
|
}
|
|
919
|
-
|
|
920
|
-
|
|
1001
|
+
return p2pBid;
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
// Candidates are ranked by their boosted counted total payment, the p2p bid is governed
|
|
1005
|
+
// by the top-level factors and each builder API bid by its own entry. Ties prefer the
|
|
1006
|
+
// earliest received builder API bid, so the signed block can be routed back to its
|
|
1007
|
+
// builder directly.
|
|
1008
|
+
const bestBidPromise: Promise<BidCandidate | null> = (async () => {
|
|
1009
|
+
const [builderApiBids, p2pBid] = await Promise.all([builderApiBidsPromise, p2pBidPromise]);
|
|
1010
|
+
let parentExecutionRequestsPromise: Promise<gloas.ExecutionRequests> | null = null;
|
|
1011
|
+
|
|
1012
|
+
const candidates = (
|
|
1013
|
+
await Promise.all(
|
|
1014
|
+
builderApiBids.map(async ({url, entry, signedBid, receivedMs}): Promise<BidCandidate | null> => {
|
|
1015
|
+
try {
|
|
1016
|
+
await validateBuilderApiExecutionPayloadBid(chain, signedBid, {
|
|
1017
|
+
slot,
|
|
1018
|
+
parentBlock,
|
|
1019
|
+
parentBlockHash: bidParentBlockHash,
|
|
1020
|
+
parentBlockRoot: parentBlockRootHex,
|
|
1021
|
+
entry,
|
|
1022
|
+
getParentExecutionRequests: () => {
|
|
1023
|
+
parentExecutionRequestsPromise ??= chain.getParentExecutionRequests(parentSlot, parentBlockRootHex);
|
|
1024
|
+
return parentExecutionRequestsPromise;
|
|
1025
|
+
},
|
|
1026
|
+
});
|
|
1027
|
+
return {
|
|
1028
|
+
signedBid,
|
|
1029
|
+
totalGwei: getBuilderBidTotalGwei(signedBid.message, entry.maxExecutionPayment),
|
|
1030
|
+
boostFactor: entry.builderBoostFactor,
|
|
1031
|
+
url,
|
|
1032
|
+
receivedMs,
|
|
1033
|
+
};
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
metrics?.builderApi.bidsDiscarded.inc();
|
|
1036
|
+
logger.warn("Ignoring invalid builder API bid", {slot, builder: toPrintableUrl(url)}, e as Error);
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
})
|
|
1040
|
+
)
|
|
1041
|
+
).filter((candidate): candidate is BidCandidate => candidate !== null);
|
|
1042
|
+
|
|
1043
|
+
if (p2pBid !== null) {
|
|
1044
|
+
candidates.push({
|
|
1045
|
+
signedBid: p2pBid.signedBid,
|
|
1046
|
+
totalGwei: BigInt(p2pBid.signedBid.message.value),
|
|
1047
|
+
boostFactor: builderConfig.builderBoostFactor,
|
|
1048
|
+
receivedMs: p2pBid.receivedMs,
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const best = selectBestBid(candidates);
|
|
1053
|
+
if (candidates.length > 0) {
|
|
1054
|
+
logger.debug("Ranked builder bid candidates", {
|
|
1055
|
+
slot,
|
|
1056
|
+
candidates: candidates
|
|
1057
|
+
.map(
|
|
1058
|
+
(candidate) =>
|
|
1059
|
+
`${candidate.url ?? "p2p"}:total=${prettyGweiToEth(candidate.totalGwei)}:boost=${candidate.boostFactor}:received=${candidate.receivedMs}ms`
|
|
1060
|
+
)
|
|
1061
|
+
.join(","),
|
|
1062
|
+
bidSource: best?.url ?? "p2p",
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
return best;
|
|
1066
|
+
})();
|
|
921
1067
|
|
|
922
1068
|
const commonBlockBodyPromise = chain.produceCommonBlockBody({
|
|
923
1069
|
slot,
|
|
@@ -937,9 +1083,6 @@ export function getValidatorApi(
|
|
|
937
1083
|
};
|
|
938
1084
|
|
|
939
1085
|
metrics?.blockProductionRequests.inc({source: ProducedBlockSource.engine});
|
|
940
|
-
if (builderBid !== null) {
|
|
941
|
-
metrics?.blockProductionRequests.inc({source: ProducedBlockSource.builder});
|
|
942
|
-
}
|
|
943
1086
|
|
|
944
1087
|
const timed = <T>(source: ProducedBlockSource, fn: () => Promise<T>): Promise<T> => {
|
|
945
1088
|
const t = metrics?.blockProductionTime.startTimer();
|
|
@@ -956,19 +1099,25 @@ export function getValidatorApi(
|
|
|
956
1099
|
const enginePromise: ReturnType<typeof chain.produceBlock> = timed(ProducedBlockSource.engine, () =>
|
|
957
1100
|
chain.produceBlock(baseAttrs)
|
|
958
1101
|
).then((engineBlock) => {
|
|
959
|
-
// No need to wait for the bid block if the engine block will always be selected
|
|
960
|
-
// suspected builder censorship or
|
|
961
|
-
|
|
1102
|
+
// No need to wait for the bid block if the engine block will always be selected, either
|
|
1103
|
+
// due to suspected builder censorship, or because no builders are configured and the
|
|
1104
|
+
// boost factor of 0 always prefers the local block over p2p bids
|
|
1105
|
+
if (engineBlock.shouldOverrideBuilder || (builderConfig.builders.length === 0 && builderBoostFactor === 0n)) {
|
|
962
1106
|
controller.abort();
|
|
963
1107
|
}
|
|
964
1108
|
return engineBlock;
|
|
965
1109
|
});
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1110
|
+
const bidBlockPromise: ReturnType<typeof chain.produceBlock> = bestBidPromise.then((candidate) => {
|
|
1111
|
+
if (candidate === null) {
|
|
1112
|
+
throw new Error(NO_BID_AVAILABLE);
|
|
1113
|
+
}
|
|
1114
|
+
metrics?.blockProductionRequests.inc({source: ProducedBlockSource.builder});
|
|
1115
|
+
return timed(ProducedBlockSource.builder, () =>
|
|
1116
|
+
chain.produceBlock({...baseAttrs, builderBid: candidate.signedBid})
|
|
1117
|
+
);
|
|
1118
|
+
});
|
|
970
1119
|
|
|
971
|
-
const [engineResult,
|
|
1120
|
+
const [engineResult, bidBlockResult] = await resolveOrRacePromises([enginePromise, bidBlockPromise], {
|
|
972
1121
|
resolveTimeoutMs: cutoffMs,
|
|
973
1122
|
raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS,
|
|
974
1123
|
signal: controller.signal,
|
|
@@ -977,29 +1126,74 @@ export function getValidatorApi(
|
|
|
977
1126
|
let bestResult: typeof engineResult | null = null;
|
|
978
1127
|
let source: ProducedBlockSource = ProducedBlockSource.engine;
|
|
979
1128
|
|
|
1129
|
+
// Resolved instantly whenever the bid branch produced a block
|
|
1130
|
+
const bestBid = bidBlockResult.status === "fulfilled" ? await bestBidPromise : null;
|
|
1131
|
+
|
|
1132
|
+
const logCtx = {
|
|
1133
|
+
slot,
|
|
1134
|
+
parentSlot,
|
|
1135
|
+
parentBlockRoot: parentBlockRootHex,
|
|
1136
|
+
parentBlockHash: parentBlock.executionPayloadBlockHash,
|
|
1137
|
+
fork,
|
|
1138
|
+
builderBoostFactor,
|
|
1139
|
+
strictFeeRecipientCheck,
|
|
1140
|
+
circuitBreakerActive,
|
|
1141
|
+
builderEntries: builderConfig.builders.length,
|
|
1142
|
+
...(bestBid !== null
|
|
1143
|
+
? {
|
|
1144
|
+
bidSource: bestBid.url !== undefined ? toPrintableUrl(bestBid.url) : "p2p",
|
|
1145
|
+
bidValue: prettyGweiToEth(bestBid.signedBid.message.value),
|
|
1146
|
+
bidExecutionPayment: prettyGweiToEth(bestBid.signedBid.message.executionPayment),
|
|
1147
|
+
// The full bid total and the counted total used during bid selection
|
|
1148
|
+
bidTotal: prettyGweiToEth(
|
|
1149
|
+
BigInt(bestBid.signedBid.message.value) + bestBid.signedBid.message.executionPayment
|
|
1150
|
+
),
|
|
1151
|
+
bidCountedTotal: prettyGweiToEth(bestBid.totalGwei),
|
|
1152
|
+
bidBoostFactor: bestBid.boostFactor,
|
|
1153
|
+
builderIndex: bestBid.signedBid.message.builderIndex,
|
|
1154
|
+
bidBlockHash: toRootHex(bestBid.signedBid.message.blockHash),
|
|
1155
|
+
bidReceivedMs: bestBid.receivedMs,
|
|
1156
|
+
}
|
|
1157
|
+
: {}),
|
|
1158
|
+
};
|
|
1159
|
+
|
|
980
1160
|
// handle shouldOverrideBuilder separately
|
|
981
|
-
if (
|
|
1161
|
+
if (
|
|
1162
|
+
engineResult.status === "fulfilled" &&
|
|
1163
|
+
engineResult.value.shouldOverrideBuilder &&
|
|
1164
|
+
(builderBidExpected || bidBlockResult.status === "fulfilled")
|
|
1165
|
+
) {
|
|
982
1166
|
source = ProducedBlockSource.engine;
|
|
983
1167
|
bestResult = engineResult;
|
|
984
1168
|
metrics?.blockProductionSelectionResults.inc({
|
|
985
1169
|
source: ProducedBlockSource.engine,
|
|
986
1170
|
reason: EngineBlockSelectionReason.BuilderCensorship,
|
|
987
1171
|
});
|
|
988
|
-
logger.warn("Selected local block: censorship suspected in builder bid",
|
|
989
|
-
|
|
1172
|
+
logger.warn("Selected local block: censorship suspected in builder bid", {
|
|
1173
|
+
...logCtx,
|
|
1174
|
+
durationMs: engineResult.durationMs,
|
|
1175
|
+
...getBlockValueLogInfo(engineResult.value),
|
|
1176
|
+
});
|
|
1177
|
+
} else if (engineResult.status === "fulfilled" && bidBlockResult.status === "fulfilled") {
|
|
990
1178
|
const result = selectBlockProductionSourceByBoostFactor({
|
|
991
|
-
builderBoostFactor,
|
|
1179
|
+
builderBoostFactor: bestBid?.boostFactor ?? builderBoostFactor,
|
|
992
1180
|
engineExecutionPayloadValue: engineResult.value.executionPayloadValue,
|
|
993
|
-
// The bid
|
|
994
|
-
builderExecutionPayloadValue:
|
|
1181
|
+
// The bid total payment is its value plus its counted executionPayment, in Gwei
|
|
1182
|
+
builderExecutionPayloadValue: (bestBid?.totalGwei ?? 0n) * GWEI_TO_WEI,
|
|
995
1183
|
});
|
|
996
1184
|
source = result.source;
|
|
997
1185
|
metrics?.blockProductionSelectionResults.inc(result);
|
|
998
|
-
logger.info(`Selected ${source} block`, {
|
|
999
|
-
|
|
1000
|
-
|
|
1186
|
+
logger.info(`Selected ${source} block`, {
|
|
1187
|
+
reason: result.reason,
|
|
1188
|
+
...logCtx,
|
|
1189
|
+
engineDurationMs: engineResult.durationMs,
|
|
1190
|
+
...getBlockValueLogInfo(engineResult.value, ProducedBlockSource.engine),
|
|
1191
|
+
builderDurationMs: bidBlockResult.durationMs,
|
|
1192
|
+
});
|
|
1193
|
+
bestResult = source === ProducedBlockSource.builder ? bidBlockResult : engineResult;
|
|
1194
|
+
} else if (bidBlockResult.status === "fulfilled") {
|
|
1001
1195
|
source = ProducedBlockSource.builder;
|
|
1002
|
-
bestResult =
|
|
1196
|
+
bestResult = bidBlockResult;
|
|
1003
1197
|
const reason =
|
|
1004
1198
|
engineResult.status === "pending"
|
|
1005
1199
|
? BuilderBlockSelectionReason.EnginePending
|
|
@@ -1008,28 +1202,32 @@ export function getValidatorApi(
|
|
|
1008
1202
|
logger.info("Selected builder bid block: no local block produced", {
|
|
1009
1203
|
reason,
|
|
1010
1204
|
...logCtx,
|
|
1205
|
+
durationMs: bidBlockResult.durationMs,
|
|
1206
|
+
...getBlockValueLogInfo(bidBlockResult.value),
|
|
1011
1207
|
error: engineResult.status === "rejected" ? (engineResult.reason as Error).message : undefined,
|
|
1012
1208
|
});
|
|
1013
1209
|
} else if (engineResult.status === "fulfilled") {
|
|
1014
1210
|
source = ProducedBlockSource.engine;
|
|
1015
1211
|
bestResult = engineResult;
|
|
1016
1212
|
const reason =
|
|
1017
|
-
|
|
1213
|
+
bidBlockResult.status === "rejected" && (bidBlockResult.reason as Error).message === NO_BID_AVAILABLE
|
|
1018
1214
|
? EngineBlockSelectionReason.BuilderNoBid
|
|
1019
|
-
:
|
|
1215
|
+
: bidBlockResult.status === "pending"
|
|
1020
1216
|
? EngineBlockSelectionReason.BuilderPending
|
|
1021
1217
|
: EngineBlockSelectionReason.BuilderError;
|
|
1022
1218
|
metrics?.blockProductionSelectionResults.inc({source: ProducedBlockSource.engine, reason});
|
|
1023
1219
|
logger.info("Selected local block: no builder bid block produced", {
|
|
1024
1220
|
reason,
|
|
1025
1221
|
...logCtx,
|
|
1026
|
-
|
|
1222
|
+
durationMs: engineResult.durationMs,
|
|
1223
|
+
...getBlockValueLogInfo(engineResult.value),
|
|
1224
|
+
error: bidBlockResult.status === "rejected" ? (bidBlockResult.reason as Error).message : undefined,
|
|
1027
1225
|
});
|
|
1028
1226
|
}
|
|
1029
1227
|
|
|
1030
1228
|
if (bestResult === null || bestResult.status !== "fulfilled") {
|
|
1031
1229
|
const engineReason = engineResult.status === "rejected" ? engineResult.reason : engineResult.status;
|
|
1032
|
-
const bidReason =
|
|
1230
|
+
const bidReason = bidBlockResult.status === "rejected" ? bidBlockResult.reason : bidBlockResult.status;
|
|
1033
1231
|
logger.error("Block production failed", {
|
|
1034
1232
|
...logCtx,
|
|
1035
1233
|
engineReason: String(engineReason),
|
|
@@ -1093,7 +1291,13 @@ export function getValidatorApi(
|
|
|
1093
1291
|
|
|
1094
1292
|
return {
|
|
1095
1293
|
data: block as gloas.BeaconBlock,
|
|
1096
|
-
meta: {
|
|
1294
|
+
meta: {
|
|
1295
|
+
version: fork,
|
|
1296
|
+
consensusBlockValue,
|
|
1297
|
+
executionPayloadValue,
|
|
1298
|
+
executionPayloadIncluded: false,
|
|
1299
|
+
builderUrl: source === ProducedBlockSource.builder ? bestBid?.url : undefined,
|
|
1300
|
+
},
|
|
1097
1301
|
};
|
|
1098
1302
|
},
|
|
1099
1303
|
|
|
@@ -1877,6 +2081,43 @@ export function getValidatorApi(
|
|
|
1877
2081
|
}
|
|
1878
2082
|
},
|
|
1879
2083
|
|
|
2084
|
+
async submitBuilderPreferences({builderPreferences}) {
|
|
2085
|
+
const failures: FailureList = [];
|
|
2086
|
+
|
|
2087
|
+
await Promise.all(
|
|
2088
|
+
builderPreferences.map(async (entry, i) => {
|
|
2089
|
+
let builder = Buffer.from(entry.url).toString("utf8");
|
|
2090
|
+
try {
|
|
2091
|
+
const url = decodeBuilderUrl(entry.url);
|
|
2092
|
+
builder = toPrintableUrl(url);
|
|
2093
|
+
const proposerIndex = chain.getHeadState().getBeaconProposer(entry.auth.message.slot);
|
|
2094
|
+
const expectedProposerPubkey = chain.pubkeyCache.getOrThrow(proposerIndex).toBytes();
|
|
2095
|
+
if (!byteArrayEquals(entry.proposerPubkey, expectedProposerPubkey)) {
|
|
2096
|
+
throw new ApiError(
|
|
2097
|
+
400,
|
|
2098
|
+
`Invalid proposer pubkey for builder preferences slot=${entry.auth.message.slot}`
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
await chain.builderApiClient.submitBuilderPreferences(url, entry.proposerPubkey, {
|
|
2102
|
+
preferences: {maxExecutionPayment: entry.maxExecutionPayment},
|
|
2103
|
+
auth: entry.auth,
|
|
2104
|
+
});
|
|
2105
|
+
} catch (e) {
|
|
2106
|
+
failures.push({index: i, message: (e as Error).message});
|
|
2107
|
+
logger.verbose(
|
|
2108
|
+
`Error on submitBuilderPreferences [${i}]`,
|
|
2109
|
+
{slot: entry.auth.message.slot, builder},
|
|
2110
|
+
e as Error
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
})
|
|
2114
|
+
);
|
|
2115
|
+
|
|
2116
|
+
if (failures.length > 0) {
|
|
2117
|
+
throw new IndexedError("Error submitting builder preferences", failures);
|
|
2118
|
+
}
|
|
2119
|
+
},
|
|
2120
|
+
|
|
1880
2121
|
async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
|
|
1881
2122
|
const fork = config.getForkName(slot);
|
|
1882
2123
|
|
|
@@ -89,7 +89,7 @@ export function selectBlockProductionSourceByBoostFactor({
|
|
|
89
89
|
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
if (engineExecutionPayloadValue >=
|
|
92
|
+
if (engineExecutionPayloadValue * 100n >= builderExecutionPayloadValue * builderBoostFactor) {
|
|
93
93
|
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.BlockValue};
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -192,7 +192,7 @@ export class ArchiveStore {
|
|
|
192
192
|
|
|
193
193
|
private onCheckpoint = (): void => {
|
|
194
194
|
const headStateRoot = this.chain.forkChoice.getHead().stateRoot;
|
|
195
|
-
this.chain.regen.
|
|
195
|
+
this.chain.regen.onCheckpoint(
|
|
196
196
|
this.chain.forkChoice.getFinalizedCheckpoint().epoch,
|
|
197
197
|
this.chain.forkChoice.getJustifiedCheckpoint().epoch,
|
|
198
198
|
headStateRoot
|
|
@@ -143,17 +143,17 @@ export async function processBlocks(
|
|
|
143
143
|
await importBlock.call(this, fullyVerifiedBlock, opts);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
// PayloadEnvelopeInput is shared and may receive an envelope after the DA snapshot was taken.
|
|
147
|
+
const payloadDA = payloadDAStatuses.get(slot);
|
|
148
|
+
if (payloadDA !== undefined) {
|
|
149
|
+
const payloadInput = payloadEnvelopes?.get(slot);
|
|
150
|
+
if (payloadInput === undefined) {
|
|
151
|
+
throw new Error(`Missing payload input for slot ${slot} after DA verification`);
|
|
152
|
+
}
|
|
148
153
|
if (!payloadInput.isComplete()) {
|
|
149
154
|
// we validated DA before reaching this
|
|
150
155
|
throw new Error(`Payload envelope for slot ${slot} not complete after DA verification`);
|
|
151
156
|
}
|
|
152
|
-
// we already awaited DA in verifyBlocksInEpoch for this segment
|
|
153
|
-
const payloadDA = payloadDAStatuses.get(slot);
|
|
154
|
-
if (payloadDA === undefined) {
|
|
155
|
-
throw new Error(`Missing payload DA status for slot ${slot}`);
|
|
156
|
-
}
|
|
157
157
|
await importExecutionPayload.call(this, payloadInput, payloadDA, {validSignature: false});
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -132,12 +132,15 @@ export async function verifyBlocksInEpoch(
|
|
|
132
132
|
}> =
|
|
133
133
|
fork >= ForkSeq.gloas
|
|
134
134
|
? (async () => {
|
|
135
|
-
//
|
|
135
|
+
// Range sync creates a PayloadEnvelopeInput for every Gloas block, but an entry without
|
|
136
|
+
// an envelope represents a valid EMPTY slot and has no payload DA to verify.
|
|
136
137
|
// A checkpoint-sync batch may include a payload for a slot whose block was filtered
|
|
137
138
|
// out of relevantBlocks (e.g., the anchor at the finalized slot); that payload still
|
|
138
139
|
// needs DA validation so it can be imported in processBlocks.
|
|
139
140
|
const payloadInputsForDa: PayloadEnvelopeInput[] =
|
|
140
|
-
payloadEnvelopes !== null
|
|
141
|
+
payloadEnvelopes !== null
|
|
142
|
+
? Array.from(payloadEnvelopes.values()).filter((payloadInput) => payloadInput.hasPayloadEnvelope())
|
|
143
|
+
: [];
|
|
141
144
|
const {dataAvailabilityStatuses, availableTime} = await verifyPayloadsDataAvailability(
|
|
142
145
|
payloadInputsForDa,
|
|
143
146
|
abortController.signal
|
package/src/chain/chain.ts
CHANGED
|
@@ -61,6 +61,7 @@ import {ProcessShutdownCallback} from "@lodestar/validator";
|
|
|
61
61
|
import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js";
|
|
62
62
|
import {IBeaconDb} from "../db/index.js";
|
|
63
63
|
import {BLOB_SIDECARS_IN_WRAPPER_INDEX} from "../db/repositories/blobSidecars.js";
|
|
64
|
+
import {BuilderApiClient, BuilderApiClientOpts} from "../execution/builder/apiClient.js";
|
|
64
65
|
import {BuilderStatus} from "../execution/builder/http.js";
|
|
65
66
|
import {IExecutionBuilder, IExecutionEngine} from "../execution/index.js";
|
|
66
67
|
import {Metrics} from "../metrics/index.js";
|
|
@@ -165,6 +166,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
165
166
|
readonly executionEngine: IExecutionEngine;
|
|
166
167
|
readonly executionBuilder?: IExecutionBuilder;
|
|
167
168
|
readonly builderCircuitBreaker: BuilderCircuitBreaker;
|
|
169
|
+
readonly builderApiClient: BuilderApiClient;
|
|
168
170
|
// Expose config for convenience in modularized functions
|
|
169
171
|
readonly config: BeaconConfig;
|
|
170
172
|
readonly custodyConfig: CustodyConfig;
|
|
@@ -275,6 +277,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
275
277
|
isAnchorStateFinalized,
|
|
276
278
|
executionEngine,
|
|
277
279
|
executionBuilder,
|
|
280
|
+
builderApiClientOpts,
|
|
278
281
|
}: {
|
|
279
282
|
privateKey: PrivateKey;
|
|
280
283
|
config: BeaconConfig;
|
|
@@ -292,6 +295,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
292
295
|
isAnchorStateFinalized: boolean;
|
|
293
296
|
executionEngine: IExecutionEngine;
|
|
294
297
|
executionBuilder?: IExecutionBuilder;
|
|
298
|
+
builderApiClientOpts?: BuilderApiClientOpts;
|
|
295
299
|
}
|
|
296
300
|
) {
|
|
297
301
|
this.opts = opts;
|
|
@@ -448,6 +452,8 @@ export class BeaconChain implements IBeaconChain {
|
|
|
448
452
|
{forkChoice, logger, metrics}
|
|
449
453
|
);
|
|
450
454
|
|
|
455
|
+
this.builderApiClient = new BuilderApiClient(builderApiClientOpts ?? {}, config, clock, bls, metrics, logger);
|
|
456
|
+
|
|
451
457
|
this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
|
|
452
458
|
config,
|
|
453
459
|
clock,
|
|
@@ -1321,9 +1327,10 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1321
1327
|
}
|
|
1322
1328
|
}
|
|
1323
1329
|
|
|
1324
|
-
predictProposerHead(
|
|
1330
|
+
predictProposerHead(): ProtoBlock {
|
|
1325
1331
|
this.metrics?.forkChoice.requests.inc();
|
|
1326
1332
|
const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.predictProposerHead});
|
|
1333
|
+
const slot = this.clock.currentSlot;
|
|
1327
1334
|
const secFromSlot = this.clock.secFromSlot(slot);
|
|
1328
1335
|
|
|
1329
1336
|
try {
|
|
@@ -9,6 +9,7 @@ export enum ExecutionPayloadBidErrorCode {
|
|
|
9
9
|
BID_ALREADY_KNOWN = "EXECUTION_PAYLOAD_BID_ERROR_BID_ALREADY_KNOWN",
|
|
10
10
|
BID_TOO_LOW = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_LOW",
|
|
11
11
|
BID_TOO_HIGH = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_HIGH",
|
|
12
|
+
BUILDER_MAY_EXIT = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_MAY_EXIT",
|
|
12
13
|
TOO_MANY_KZG_COMMITMENTS = "EXECUTION_PAYLOAD_BID_ERROR_TOO_MANY_KZG_COMMITMENTS",
|
|
13
14
|
UNKNOWN_BLOCK_ROOT = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_BLOCK_ROOT",
|
|
14
15
|
UNKNOWN_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_PARENT_BLOCK_HASH",
|
|
@@ -50,6 +51,11 @@ export type ExecutionPayloadBidErrorType =
|
|
|
50
51
|
}
|
|
51
52
|
| {code: ExecutionPayloadBidErrorCode.BID_TOO_LOW; bidValue: number; currentHighestBid: number}
|
|
52
53
|
| {code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH; bidValue: number; builderBalance: number}
|
|
54
|
+
| {
|
|
55
|
+
code: ExecutionPayloadBidErrorCode.BUILDER_MAY_EXIT;
|
|
56
|
+
builderIndex: BuilderIndex;
|
|
57
|
+
parentBlockRoot: RootHex;
|
|
58
|
+
}
|
|
53
59
|
| {
|
|
54
60
|
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS;
|
|
55
61
|
blobKzgCommitmentsLen: number;
|
|
@@ -134,6 +134,8 @@ export function initializeForkChoiceFromFinalizedState(
|
|
|
134
134
|
stateRoot: toRootHex(blockHeader.stateRoot),
|
|
135
135
|
blockRoot: toRootHex(checkpoint.root),
|
|
136
136
|
timeliness: true, // Optimistically assume is timely
|
|
137
|
+
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
138
|
+
proposerIndex: blockHeader.proposerIndex,
|
|
137
139
|
|
|
138
140
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
139
141
|
justifiedRoot: toRootHex(justifiedCheckpoint.root),
|
|
@@ -230,6 +232,8 @@ export function initializeForkChoiceFromUnfinalizedState(
|
|
|
230
232
|
blockRoot: headRoot,
|
|
231
233
|
targetRoot: headRoot,
|
|
232
234
|
timeliness: true, // Optimistically assume is timely
|
|
235
|
+
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
236
|
+
proposerIndex: blockHeader.proposerIndex,
|
|
233
237
|
|
|
234
238
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
235
239
|
justifiedRoot: toRootHex(justifiedCheckpoint.root),
|
package/src/chain/interface.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
rewards,
|
|
25
25
|
} from "@lodestar/types";
|
|
26
26
|
import {Logger} from "@lodestar/utils";
|
|
27
|
+
import {BuilderApiClient} from "../execution/builder/apiClient.js";
|
|
27
28
|
import {IExecutionBuilder, IExecutionEngine} from "../execution/index.js";
|
|
28
29
|
import {Metrics} from "../metrics/metrics.js";
|
|
29
30
|
import {BufferPool} from "../util/bufferPool.js";
|
|
@@ -99,6 +100,7 @@ export interface IBeaconChain {
|
|
|
99
100
|
readonly executionEngine: IExecutionEngine;
|
|
100
101
|
readonly executionBuilder?: IExecutionBuilder;
|
|
101
102
|
readonly builderCircuitBreaker: BuilderCircuitBreaker;
|
|
103
|
+
readonly builderApiClient: BuilderApiClient;
|
|
102
104
|
// Expose config for convenience in modularized functions
|
|
103
105
|
readonly config: BeaconConfig;
|
|
104
106
|
readonly custodyConfig: CustodyConfig;
|
|
@@ -272,8 +274,8 @@ export interface IBeaconChain {
|
|
|
272
274
|
|
|
273
275
|
recomputeForkChoiceHead(caller: ForkchoiceCaller): ProtoBlock;
|
|
274
276
|
|
|
275
|
-
/** When proposerBoostReorg is enabled,
|
|
276
|
-
predictProposerHead(
|
|
277
|
+
/** When proposerBoostReorg is enabled, predict (as of the current slot) the head block a proposer would build on next slot */
|
|
278
|
+
predictProposerHead(): ProtoBlock;
|
|
277
279
|
|
|
278
280
|
/** When proposerBoostReorg is enabled and we are proposing a block, this is called to determine which head block to build on */
|
|
279
281
|
getProposerHead(slot: Slot): ProtoBlock;
|