@lodestar/beacon-node 1.48.0-dev.262a9c1d16 → 1.48.0-dev.293e560a59
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/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +39 -39
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +13 -0
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/chain.d.ts +0 -5
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +38 -67
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +1 -1
- package/lib/chain/emitter.js +1 -1
- package/lib/chain/errors/blockError.d.ts +5 -3
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +2 -2
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/prepareNextSlot.js +13 -13
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +64 -46
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +50 -9
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +29 -0
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/network/reqresp/rateLimit.js +1 -2
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/validator/index.ts +43 -44
- package/src/chain/blocks/importBlock.ts +13 -0
- package/src/chain/chain.ts +46 -74
- package/src/chain/emitter.ts +1 -1
- package/src/chain/errors/blockError.ts +3 -3
- package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
- package/src/chain/prepareNextSlot.ts +16 -16
- package/src/chain/produceBlock/produceBlockBody.ts +71 -50
- package/src/chain/validation/block.ts +66 -9
- package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
- package/src/network/reqresp/rateLimit.ts +1 -2
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/ChainSafe/lodestar/issues"
|
|
13
13
|
},
|
|
14
|
-
"version": "1.48.0-dev.
|
|
14
|
+
"version": "1.48.0-dev.293e560a59",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@chainsafe/persistent-merkle-tree": "^1.3.1",
|
|
110
110
|
"@chainsafe/prometheus-gc-stats": "^1.0.0",
|
|
111
111
|
"@chainsafe/snappy-wasm": "^0.5.0",
|
|
112
|
-
"@chainsafe/ssz": "^1.
|
|
112
|
+
"@chainsafe/ssz": "^1.6.3",
|
|
113
113
|
"@chainsafe/threads": "^1.11.3",
|
|
114
114
|
"@crate-crypto/node-eth-kzg": "0.9.1",
|
|
115
115
|
"@fastify/bearer-auth": "^10.0.1",
|
|
@@ -126,17 +126,17 @@
|
|
|
126
126
|
"@libp2p/peer-id": "^6.0.15",
|
|
127
127
|
"@libp2p/prometheus-metrics": "^5.0.28",
|
|
128
128
|
"@libp2p/tcp": "^11.0.27",
|
|
129
|
-
"@lodestar/api": "^1.48.0-dev.
|
|
130
|
-
"@lodestar/config": "^1.48.0-dev.
|
|
131
|
-
"@lodestar/db": "^1.48.0-dev.
|
|
132
|
-
"@lodestar/fork-choice": "^1.48.0-dev.
|
|
133
|
-
"@lodestar/logger": "^1.48.0-dev.
|
|
134
|
-
"@lodestar/params": "^1.48.0-dev.
|
|
135
|
-
"@lodestar/reqresp": "^1.48.0-dev.
|
|
136
|
-
"@lodestar/state-transition": "^1.48.0-dev.
|
|
137
|
-
"@lodestar/types": "^1.48.0-dev.
|
|
138
|
-
"@lodestar/utils": "^1.48.0-dev.
|
|
139
|
-
"@lodestar/validator": "^1.48.0-dev.
|
|
129
|
+
"@lodestar/api": "^1.48.0-dev.293e560a59",
|
|
130
|
+
"@lodestar/config": "^1.48.0-dev.293e560a59",
|
|
131
|
+
"@lodestar/db": "^1.48.0-dev.293e560a59",
|
|
132
|
+
"@lodestar/fork-choice": "^1.48.0-dev.293e560a59",
|
|
133
|
+
"@lodestar/logger": "^1.48.0-dev.293e560a59",
|
|
134
|
+
"@lodestar/params": "^1.48.0-dev.293e560a59",
|
|
135
|
+
"@lodestar/reqresp": "^1.48.0-dev.293e560a59",
|
|
136
|
+
"@lodestar/state-transition": "^1.48.0-dev.293e560a59",
|
|
137
|
+
"@lodestar/types": "^1.48.0-dev.293e560a59",
|
|
138
|
+
"@lodestar/utils": "^1.48.0-dev.293e560a59",
|
|
139
|
+
"@lodestar/validator": "^1.48.0-dev.293e560a59",
|
|
140
140
|
"@multiformats/multiaddr": "^13.0.1",
|
|
141
141
|
"datastore-core": "^12.0.1",
|
|
142
142
|
"datastore-level": "^13.0.1",
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
"@libp2p/interface-internal": "^3.1.12",
|
|
159
159
|
"@libp2p/logger": "^6.2.13",
|
|
160
160
|
"@libp2p/utils": "^7.4.0",
|
|
161
|
-
"@lodestar/spec-test-util": "^1.48.0-dev.
|
|
161
|
+
"@lodestar/spec-test-util": "^1.48.0-dev.293e560a59",
|
|
162
162
|
"@types/js-yaml": "^4.0.5",
|
|
163
163
|
"@types/qs": "^6.9.7",
|
|
164
164
|
"@types/tmp": "^0.2.3",
|
|
@@ -175,5 +175,5 @@
|
|
|
175
175
|
"beacon",
|
|
176
176
|
"blockchain"
|
|
177
177
|
],
|
|
178
|
-
"gitHead": "
|
|
178
|
+
"gitHead": "43ee31218fa076e8aa40dbe479ef5a8ec88e270e"
|
|
179
179
|
}
|
|
@@ -133,37 +133,40 @@ type BidCandidate = {
|
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
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.
|
|
138
139
|
*/
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}
|
|
163
168
|
}
|
|
164
|
-
|
|
165
|
-
// and the earlier received bid between builder api bids
|
|
166
|
-
return a.receivedMs - b.receivedMs;
|
|
169
|
+
return best;
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
type ProduceBlockContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & {
|
|
@@ -1046,24 +1049,20 @@ export function getValidatorApi(
|
|
|
1046
1049
|
});
|
|
1047
1050
|
}
|
|
1048
1051
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const rankedCandidates = candidates.toSorted(compareBidCandidates);
|
|
1054
|
-
for (const [index, candidate] of rankedCandidates.entries()) {
|
|
1055
|
-
logger.debug("Builder bid candidate", {
|
|
1052
|
+
const best = selectBestBid(candidates);
|
|
1053
|
+
if (candidates.length > 0) {
|
|
1054
|
+
logger.debug("Ranked builder bid candidates", {
|
|
1056
1055
|
slot,
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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",
|
|
1064
1063
|
});
|
|
1065
1064
|
}
|
|
1066
|
-
return
|
|
1065
|
+
return best;
|
|
1067
1066
|
})();
|
|
1068
1067
|
|
|
1069
1068
|
const commonBlockBodyPromise = chain.produceCommonBlockBody({
|
|
@@ -524,6 +524,19 @@ export async function importBlock(
|
|
|
524
524
|
this.metrics?.previousJustifiedEpoch.set(checkpointState.previousJustifiedCheckpoint.epoch);
|
|
525
525
|
this.metrics?.currentJustifiedEpoch.set(justifiedCheckpoint.epoch);
|
|
526
526
|
}
|
|
527
|
+
const finalizedCheckpoint = checkpointState.finalizedCheckpoint;
|
|
528
|
+
const finalizedEpoch = finalizedCheckpoint.epoch;
|
|
529
|
+
const preFinalizedEpoch = parentBlockSummary.finalizedEpoch;
|
|
530
|
+
if (finalizedEpoch > preFinalizedEpoch) {
|
|
531
|
+
this.emitter.emit(routes.events.EventType.finalizedCheckpoint, {
|
|
532
|
+
block: toRootHex(finalizedCheckpoint.root),
|
|
533
|
+
epoch: finalizedCheckpoint.epoch,
|
|
534
|
+
state: toRootHex(checkpointState.hashTreeRoot()),
|
|
535
|
+
executionOptimistic: false,
|
|
536
|
+
});
|
|
537
|
+
this.logger.verbose("Checkpoint finalized", toCheckpointHex(finalizedCheckpoint));
|
|
538
|
+
this.metrics?.finalizedEpoch.set(finalizedCheckpoint.epoch);
|
|
539
|
+
}
|
|
527
540
|
}
|
|
528
541
|
}
|
|
529
542
|
|
package/src/chain/chain.ts
CHANGED
|
@@ -1271,65 +1271,59 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1271
1271
|
try {
|
|
1272
1272
|
const prevHead = this.forkChoice.getHead();
|
|
1273
1273
|
const head = this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicalHead}).head;
|
|
1274
|
-
this.emitHeadEvents(prevHead, head);
|
|
1275
|
-
return head;
|
|
1276
|
-
} catch (e) {
|
|
1277
|
-
this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicalHead});
|
|
1278
|
-
throw e;
|
|
1279
|
-
} finally {
|
|
1280
|
-
timer?.();
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
1274
|
|
|
1284
|
-
|
|
1285
|
-
* Emit `head` and `head_v2` when the fork choice head differs between two observations. Every caller that
|
|
1286
|
-
* can move the head, `updateAndGetHead()` and `updateTime()`, must capture the head before and pass both.
|
|
1287
|
-
*/
|
|
1288
|
-
private emitHeadEvents(prevHead: ProtoBlock, head: ProtoBlock): void {
|
|
1289
|
-
const headRootChanged = head.blockRoot !== prevHead.blockRoot;
|
|
1275
|
+
const headRootChanged = head.blockRoot !== prevHead.blockRoot;
|
|
1290
1276
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1277
|
+
if (!headRootChanged && prevHead.payloadStatus === head.payloadStatus) {
|
|
1278
|
+
return head;
|
|
1279
|
+
}
|
|
1294
1280
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1281
|
+
try {
|
|
1282
|
+
const previousDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.previous);
|
|
1283
|
+
const currentDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.current);
|
|
1284
|
+
const epochTransition = computeStartSlotAtEpoch(computeEpochAtSlot(head.slot)) === head.slot;
|
|
1285
|
+
const executionOptimistic = isOptimisticBlock(head);
|
|
1286
|
+
|
|
1287
|
+
if (headRootChanged) {
|
|
1288
|
+
this.emitter.emit(routes.events.EventType.head, {
|
|
1289
|
+
block: head.blockRoot,
|
|
1290
|
+
epochTransition,
|
|
1291
|
+
slot: head.slot,
|
|
1292
|
+
state: head.stateRoot,
|
|
1293
|
+
previousDutyDependentRoot,
|
|
1294
|
+
currentDutyDependentRoot,
|
|
1295
|
+
executionOptimistic,
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
this.emitter.emit(routes.events.EventType.headV2, {
|
|
1300
|
+
version: this.config.getForkName(head.slot),
|
|
1301
|
+
data: {
|
|
1302
|
+
slot: head.slot,
|
|
1303
|
+
block: head.blockRoot,
|
|
1304
|
+
state: head.stateRoot,
|
|
1305
|
+
payloadStatus: head.payloadStatus === PayloadStatus.FULL ? "full" : "empty",
|
|
1306
|
+
epochTransition,
|
|
1307
|
+
currentEpochDependentRoot: previousDutyDependentRoot,
|
|
1308
|
+
nextEpochDependentRoot: currentDutyDependentRoot,
|
|
1309
|
+
executionOptimistic,
|
|
1310
|
+
},
|
|
1310
1311
|
});
|
|
1312
|
+
} catch (e) {
|
|
1313
|
+
// getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
|
|
1314
|
+
this.logger.debug(
|
|
1315
|
+
"Error emitting head/head_v2 event",
|
|
1316
|
+
{slot: head.slot, root: head.blockRoot, headRootChanged},
|
|
1317
|
+
e as Error
|
|
1318
|
+
);
|
|
1311
1319
|
}
|
|
1312
1320
|
|
|
1313
|
-
|
|
1314
|
-
version: this.config.getForkName(head.slot),
|
|
1315
|
-
data: {
|
|
1316
|
-
slot: head.slot,
|
|
1317
|
-
block: head.blockRoot,
|
|
1318
|
-
state: head.stateRoot,
|
|
1319
|
-
payloadStatus: head.payloadStatus === PayloadStatus.FULL ? "full" : "empty",
|
|
1320
|
-
epochTransition,
|
|
1321
|
-
currentEpochDependentRoot: previousDutyDependentRoot,
|
|
1322
|
-
nextEpochDependentRoot: currentDutyDependentRoot,
|
|
1323
|
-
executionOptimistic,
|
|
1324
|
-
},
|
|
1325
|
-
});
|
|
1321
|
+
return head;
|
|
1326
1322
|
} catch (e) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
e as Error
|
|
1332
|
-
);
|
|
1323
|
+
this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicalHead});
|
|
1324
|
+
throw e;
|
|
1325
|
+
} finally {
|
|
1326
|
+
timer?.();
|
|
1333
1327
|
}
|
|
1334
1328
|
}
|
|
1335
1329
|
|
|
@@ -1618,10 +1612,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1618
1612
|
this.processShutdownCallback(this.forkChoice.irrecoverableError);
|
|
1619
1613
|
}
|
|
1620
1614
|
|
|
1621
|
-
// updateTime() recomputes the head on epoch-boundary checkpoint pull-up and under fast confirmation
|
|
1622
|
-
const prevHead = this.forkChoice.getHead();
|
|
1623
1615
|
this.forkChoice.updateTime(slot);
|
|
1624
|
-
this.emitHeadEvents(prevHead, this.forkChoice.getHead());
|
|
1625
1616
|
this.metrics?.clockSlot.set(slot);
|
|
1626
1617
|
|
|
1627
1618
|
this.attestationPool.prune(slot);
|
|
@@ -1679,25 +1670,6 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1679
1670
|
|
|
1680
1671
|
private async onForkChoiceFinalized(this: BeaconChain, cp: CheckpointWithHex): Promise<void> {
|
|
1681
1672
|
this.logger.verbose("Fork choice finalized", {epoch: cp.epoch, root: cp.rootHex});
|
|
1682
|
-
this.metrics?.finalizedEpoch.set(cp.epoch);
|
|
1683
|
-
const finalizedBlock = this.forkChoice.getBlockHexDefaultStatus(cp.rootHex);
|
|
1684
|
-
if (finalizedBlock) {
|
|
1685
|
-
// The callback runs synchronously inside fork choice checkpoint updates, defer writing to subscribers
|
|
1686
|
-
callInNextEventLoop(() => {
|
|
1687
|
-
this.emitter.emit(routes.events.EventType.finalizedCheckpoint, {
|
|
1688
|
-
block: cp.rootHex,
|
|
1689
|
-
epoch: cp.epoch,
|
|
1690
|
-
state: finalizedBlock.stateRoot,
|
|
1691
|
-
executionOptimistic: isOptimisticBlock(finalizedBlock),
|
|
1692
|
-
});
|
|
1693
|
-
});
|
|
1694
|
-
} else {
|
|
1695
|
-
this.logger.debug("Finalized block not found in fork choice, skip finalized_checkpoint event", {
|
|
1696
|
-
epoch: cp.epoch,
|
|
1697
|
-
root: cp.rootHex,
|
|
1698
|
-
});
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
1673
|
const finalizedSlot = computeStartSlotAtEpoch(cp.epoch);
|
|
1702
1674
|
this.seenBlockProposers.prune(finalizedSlot);
|
|
1703
1675
|
|
package/src/chain/emitter.ts
CHANGED
|
@@ -33,7 +33,7 @@ export enum ChainEvent {
|
|
|
33
33
|
/**
|
|
34
34
|
* This event signals that the fork choice store has been updated.
|
|
35
35
|
*
|
|
36
|
-
* This event is guaranteed to be triggered whenever the fork choice
|
|
36
|
+
* This event is guaranteed to be triggered whenever the fork choice justified checkpoint is updated. This is in response to a newly processed block.
|
|
37
37
|
*/
|
|
38
38
|
forkChoiceFinalized = "forkChoice:finalized",
|
|
39
39
|
/**
|
|
@@ -78,8 +78,8 @@ export enum BlockErrorCode {
|
|
|
78
78
|
TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
|
|
79
79
|
/** Bid parent block root does not match block parent root */
|
|
80
80
|
BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
|
|
81
|
-
/** A block
|
|
82
|
-
|
|
81
|
+
/** A block body operation or parent execution request list exceeds its per-block limit */
|
|
82
|
+
TOO_MANY_BLOCK_OPERATIONS = "BLOCK_ERROR_TOO_MANY_BLOCK_OPERATIONS",
|
|
83
83
|
/** The parent block's execution payload has been verified as invalid */
|
|
84
84
|
PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
|
|
85
85
|
/**
|
|
@@ -153,7 +153,7 @@ export type BlockErrorType =
|
|
|
153
153
|
| {code: BlockErrorCode.DATA_UNAVAILABLE}
|
|
154
154
|
| {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
|
|
155
155
|
| {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
|
|
156
|
-
| {code: BlockErrorCode.
|
|
156
|
+
| {code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS; name: string; count: number; limit: number}
|
|
157
157
|
| {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
|
|
158
158
|
| {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex}
|
|
159
159
|
| {code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS; parentBlockHash: RootHex; expectedBlockHash: RootHex};
|
|
@@ -12,6 +12,8 @@ export enum ExecutionPayloadEnvelopeErrorCode {
|
|
|
12
12
|
BUILDER_INDEX_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BUILDER_INDEX_MISMATCH",
|
|
13
13
|
BLOCK_HASH_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BLOCK_HASH_MISMATCH",
|
|
14
14
|
EXECUTION_REQUESTS_ROOT_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_ROOT_MISMATCH",
|
|
15
|
+
EXECUTION_REQUESTS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_COUNT_EXCEEDED",
|
|
16
|
+
WITHDRAWALS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_WITHDRAWALS_COUNT_EXCEEDED",
|
|
15
17
|
INVALID_SIGNATURE = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_INVALID_SIGNATURE",
|
|
16
18
|
PAYLOAD_ENVELOPE_INPUT_MISSING = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_PAYLOAD_ENVELOPE_INPUT_MISSING",
|
|
17
19
|
}
|
|
@@ -42,6 +44,13 @@ export type ExecutionPayloadEnvelopeErrorType =
|
|
|
42
44
|
envelopeRequestsRoot: RootHex;
|
|
43
45
|
bidRequestsRoot: RootHex;
|
|
44
46
|
}
|
|
47
|
+
| {
|
|
48
|
+
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED;
|
|
49
|
+
name: string;
|
|
50
|
+
count: number;
|
|
51
|
+
limit: number;
|
|
52
|
+
}
|
|
53
|
+
| {code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED; count: number; limit: number}
|
|
45
54
|
| {code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE}
|
|
46
55
|
| {code: ExecutionPayloadEnvelopeErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING; blockRoot: RootHex};
|
|
47
56
|
|
|
@@ -243,22 +243,6 @@ export class PrepareNextSlotScheduler {
|
|
|
243
243
|
parentBlockHash = preparedState.latestExecutionPayloadHeader.blockHash;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
// If emitPayloadAttributes is true emit a SSE payloadAttributes event for
|
|
247
|
-
// every slot. Without the flag, only emit the event if we are proposing in the next slot.
|
|
248
|
-
if (
|
|
249
|
-
(feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
|
|
250
|
-
this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
|
|
251
|
-
) {
|
|
252
|
-
const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
|
|
253
|
-
prepareState: stateAfterParentPayload,
|
|
254
|
-
prepareSlot,
|
|
255
|
-
parentBlockRoot: fromHex(updatedHead.blockRoot),
|
|
256
|
-
parentBlockHash,
|
|
257
|
-
feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
|
|
258
|
-
});
|
|
259
|
-
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
246
|
if (feeRecipient) {
|
|
263
247
|
const preparationTime =
|
|
264
248
|
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
|
|
@@ -289,6 +273,22 @@ export class PrepareNextSlotScheduler {
|
|
|
289
273
|
}
|
|
290
274
|
|
|
291
275
|
this.computeStateHashTreeRoot(preparedState, isEpochTransition);
|
|
276
|
+
|
|
277
|
+
// If emitPayloadAttributes is true emit a SSE payloadAttributes event for
|
|
278
|
+
// every slot. Without the flag, only emit the event if we are proposing in the next slot.
|
|
279
|
+
if (
|
|
280
|
+
(feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
|
|
281
|
+
this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
|
|
282
|
+
) {
|
|
283
|
+
const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
|
|
284
|
+
prepareState: stateAfterParentPayload,
|
|
285
|
+
prepareSlot,
|
|
286
|
+
parentBlockRoot: fromHex(updatedHead.blockRoot),
|
|
287
|
+
parentBlockHash,
|
|
288
|
+
feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
|
|
289
|
+
});
|
|
290
|
+
this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
|
|
291
|
+
}
|
|
292
292
|
} else {
|
|
293
293
|
// Pre-bellatrix only reaches here at an epoch transition to precompute the next epoch state
|
|
294
294
|
const preparedState = await this.chain.regen.getBlockSlotState(
|
|
@@ -528,18 +528,23 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
528
528
|
}
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
531
|
+
if (ForkSeq[fork] >= ForkSeq.deneb) {
|
|
532
|
+
const {blobKzgCommitments} = builderRes;
|
|
533
|
+
if (blobKzgCommitments === undefined) {
|
|
534
|
+
throw Error(`Invalid builder getHeader response for fork=${fork}, missing blobKzgCommitments`);
|
|
535
|
+
}
|
|
535
536
|
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
(blockBody as deneb.BlindedBeaconBlockBody).blobKzgCommitments = blobKzgCommitments;
|
|
538
|
+
Object.assign(logMeta, {blobs: blobKzgCommitments.length});
|
|
539
|
+
}
|
|
538
540
|
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
+
if (ForkSeq[fork] >= ForkSeq.electra) {
|
|
542
|
+
const {executionRequests} = builderRes;
|
|
543
|
+
if (executionRequests === undefined) {
|
|
544
|
+
throw Error(`Invalid builder getHeader response for fork=${fork}, missing executionRequests`);
|
|
545
|
+
}
|
|
546
|
+
(blockBody as electra.BlindedBeaconBlockBody).executionRequests = executionRequests;
|
|
541
547
|
}
|
|
542
|
-
(blockBody as BlindedBeaconBlockBody<ForkName.fulu>).executionRequests = executionRequests;
|
|
543
548
|
}
|
|
544
549
|
|
|
545
550
|
// blockType === BlockType.Full
|
|
@@ -894,38 +899,45 @@ function preparePayloadAttributes(
|
|
|
894
899
|
): SSEPayloadAttributes["payloadAttributes"] {
|
|
895
900
|
const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
|
|
896
901
|
const prevRandao = prepareState.getRandaoMix(prepareState.epoch);
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
902
|
+
const payloadAttributes = {
|
|
903
|
+
timestamp,
|
|
904
|
+
prevRandao,
|
|
905
|
+
suggestedFeeRecipient: feeRecipient,
|
|
906
|
+
};
|
|
900
907
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
908
|
+
if (ForkSeq[fork] >= ForkSeq.capella) {
|
|
909
|
+
if (!isStatePostCapella(prepareState)) {
|
|
910
|
+
throw new Error("Expected Capella state for withdrawals");
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (isStatePostGloas(prepareState)) {
|
|
914
|
+
const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
|
|
915
|
+
if (isExtendingPayload) {
|
|
916
|
+
// applyParentExecutionPayload sets latestBlockHash = parentBid.blockHash, so a mismatch
|
|
917
|
+
// here means the caller did not apply parent payload to prepareState
|
|
918
|
+
if (!byteArrayEquals(prepareState.latestBlockHash, prepareState.latestExecutionPayloadBid.blockHash)) {
|
|
919
|
+
throw new Error("Expected state with parent execution payload applied for withdrawals");
|
|
920
|
+
}
|
|
921
|
+
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
922
|
+
prepareState.getExpectedWithdrawals().expectedWithdrawals;
|
|
923
|
+
} else {
|
|
924
|
+
// When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
|
|
925
|
+
// already deducted from CL balances but never credited on the EL (the envelope
|
|
926
|
+
// was not delivered). The next payload must carry those same withdrawals to
|
|
927
|
+
// restore CL/EL consistency, otherwise validators permanently lose that balance.
|
|
928
|
+
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
929
|
+
prepareState.payloadExpectedWithdrawals;
|
|
909
930
|
}
|
|
910
|
-
withdrawals = prepareState.getExpectedWithdrawals().expectedWithdrawals;
|
|
911
931
|
} else {
|
|
912
|
-
//
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
// restore CL/EL consistency, otherwise validators permanently lose that balance.
|
|
916
|
-
withdrawals = prepareState.payloadExpectedWithdrawals;
|
|
932
|
+
// withdrawals logic is now fork aware as it changes on electra fork post capella
|
|
933
|
+
(payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
|
|
934
|
+
prepareState.getExpectedWithdrawals().expectedWithdrawals;
|
|
917
935
|
}
|
|
918
|
-
} else {
|
|
919
|
-
withdrawals = prepareState.getExpectedWithdrawals().expectedWithdrawals;
|
|
920
936
|
}
|
|
921
937
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
suggestedFeeRecipient: feeRecipient,
|
|
926
|
-
withdrawals,
|
|
927
|
-
parentBeaconBlockRoot: parentBlockRoot,
|
|
928
|
-
};
|
|
938
|
+
if (ForkSeq[fork] >= ForkSeq.deneb) {
|
|
939
|
+
(payloadAttributes as deneb.SSEPayloadAttributes["payloadAttributes"]).parentBeaconBlockRoot = parentBlockRoot;
|
|
940
|
+
}
|
|
929
941
|
|
|
930
942
|
if (ForkSeq[fork] >= ForkSeq.gloas) {
|
|
931
943
|
if (!isStatePostGloas(prepareState)) {
|
|
@@ -1038,28 +1050,37 @@ export async function produceCommonBlockBody<T extends BlockType>(
|
|
|
1038
1050
|
step: BlockProductionStep.attestations,
|
|
1039
1051
|
});
|
|
1040
1052
|
|
|
1041
|
-
const
|
|
1042
|
-
const parentBlockRoot = fromHex(parentBlock.blockRoot);
|
|
1043
|
-
const previousSlot = slot - 1;
|
|
1044
|
-
const syncAggregate = this.syncContributionAndProofPool.getAggregate(previousSlot, parentBlockRoot);
|
|
1045
|
-
this.metrics?.production.producedSyncAggregateParticipants.observe(
|
|
1046
|
-
syncAggregate.syncCommitteeBits.getTrueBitIndexes().length
|
|
1047
|
-
);
|
|
1048
|
-
endSyncAggregate?.({
|
|
1049
|
-
step: BlockProductionStep.syncAggregate,
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
// Live proposal production is supported from Fulu onward.
|
|
1053
|
-
return {
|
|
1053
|
+
const blockBody: Omit<CommonBlockBody, "blsToExecutionChanges" | "syncAggregate"> = {
|
|
1054
1054
|
randaoReveal,
|
|
1055
1055
|
graffiti,
|
|
1056
|
+
// Eth1 data voting is no longer required since electra
|
|
1056
1057
|
eth1Data: currentState.eth1Data,
|
|
1057
1058
|
proposerSlashings: this.opts.disableProposerSlashings === true ? [] : proposerSlashings,
|
|
1058
1059
|
attesterSlashings,
|
|
1059
1060
|
attestations,
|
|
1061
|
+
// Since electra, deposits are processed by the execution layer,
|
|
1062
|
+
// we no longer support handling deposits from earlier forks.
|
|
1060
1063
|
deposits: [],
|
|
1061
1064
|
voluntaryExits,
|
|
1062
|
-
blsToExecutionChanges,
|
|
1063
|
-
syncAggregate,
|
|
1064
1065
|
};
|
|
1066
|
+
|
|
1067
|
+
if (ForkSeq[fork] >= ForkSeq.capella) {
|
|
1068
|
+
(blockBody as CommonBlockBody).blsToExecutionChanges = blsToExecutionChanges;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const endSyncAggregate = stepsMetrics?.startTimer();
|
|
1072
|
+
if (ForkSeq[fork] >= ForkSeq.altair) {
|
|
1073
|
+
const parentBlockRoot = fromHex(parentBlock.blockRoot);
|
|
1074
|
+
const previousSlot = slot - 1;
|
|
1075
|
+
const syncAggregate = this.syncContributionAndProofPool.getAggregate(previousSlot, parentBlockRoot);
|
|
1076
|
+
this.metrics?.production.producedSyncAggregateParticipants.observe(
|
|
1077
|
+
syncAggregate.syncCommitteeBits.getTrueBitIndexes().length
|
|
1078
|
+
);
|
|
1079
|
+
(blockBody as CommonBlockBody).syncAggregate = syncAggregate;
|
|
1080
|
+
}
|
|
1081
|
+
endSyncAggregate?.({
|
|
1082
|
+
step: BlockProductionStep.syncAggregate,
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
return blockBody as CommonBlockBody;
|
|
1065
1086
|
}
|