@lodestar/beacon-node 1.48.0-dev.7d85330f92 → 1.48.0-dev.b228e400d1
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 +0 -7
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +4 -1
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +9 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +37 -4
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +3 -2
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +16 -10
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +4 -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 +2 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/lightClient/index.d.ts +3 -3
- package/lib/chain/lightClient/index.d.ts.map +1 -1
- package/lib/chain/lightClient/index.js +21 -23
- package/lib/chain/lightClient/index.js.map +1 -1
- package/lib/chain/lightClient/proofs.d.ts +2 -1
- package/lib/chain/lightClient/proofs.d.ts.map +1 -1
- package/lib/chain/lightClient/proofs.js +5 -1
- package/lib/chain/lightClient/proofs.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +56 -40
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.js +5 -6
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
- package/lib/execution/builder/validateBid.d.ts.map +1 -1
- package/lib/execution/builder/validateBid.js +8 -5
- package/lib/execution/builder/validateBid.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -3
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -5
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/network.js +2 -2
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +382 -278
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +11 -16
- package/lib/network/processor/index.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/blocks/index.ts +0 -10
- package/src/chain/archiveStore/archiveStore.ts +6 -0
- package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -3
- package/src/chain/blocks/importBlock.ts +4 -2
- package/src/chain/blocks/utils/chainSegment.ts +16 -11
- package/src/chain/errors/executionPayloadBid.ts +2 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/lightClient/index.ts +32 -36
- package/src/chain/lightClient/proofs.ts +10 -0
- package/src/chain/validation/executionPayloadBid.ts +62 -44
- package/src/chain/validation/payloadAttestationMessage.ts +8 -9
- package/src/execution/builder/validateBid.ts +10 -6
- package/src/metrics/metrics/lodestar.ts +6 -5
- package/src/network/network.ts +2 -2
- package/src/network/processor/gossipHandlers.ts +489 -344
- package/src/network/processor/index.ts +11 -17
|
@@ -37,17 +37,6 @@ export function assertLinearChainSegment(
|
|
|
37
37
|
|
|
38
38
|
const warnings: OrphanedPayloadEnvelope[] = [];
|
|
39
39
|
|
|
40
|
-
let parentExecHash: string | null = parentBlock?.executionPayloadBlockHash ?? null;
|
|
41
|
-
if (parentBlock !== null) {
|
|
42
|
-
const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot);
|
|
43
|
-
if (parentPayloadInput?.hasPayloadEnvelope()) {
|
|
44
|
-
// Checkpoint-sync first batch: the anchor is stored PENDING with the inherited (grandparent)
|
|
45
|
-
// hash, not its own payload. Its payload envelope is in this batch, so use that blockHash as
|
|
46
|
-
// the real parent EL head.
|
|
47
|
-
parentExecHash = parentPayloadInput.getBlockHashHex();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
40
|
// Track the expected execution payload block hash through the segment, seeded from the FIRST
|
|
52
41
|
// block's own bid
|
|
53
42
|
const firstBlockMessage = blocks[0].getBlock().message;
|
|
@@ -55,6 +44,22 @@ export function assertLinearChainSegment(
|
|
|
55
44
|
? toRootHex(firstBlockMessage.body.signedExecutionPayloadBid.message.parentBlockHash)
|
|
56
45
|
: null;
|
|
57
46
|
|
|
47
|
+
let parentExecHash: string | null = parentBlock?.executionPayloadBlockHash ?? null;
|
|
48
|
+
if (parentBlock !== null) {
|
|
49
|
+
const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot);
|
|
50
|
+
if (parentPayloadInput?.hasPayloadEnvelope()) {
|
|
51
|
+
const parentPayloadBlockHash = parentPayloadInput.getBlockHashHex();
|
|
52
|
+
if (currentExecHash === parentPayloadBlockHash) {
|
|
53
|
+
// Checkpoint-sync first batch: the anchor is stored PENDING with the inherited (grandparent)
|
|
54
|
+
// hash, its own payload is in this batch and is the real parent EL head
|
|
55
|
+
parentExecHash = parentPayloadBlockHash;
|
|
56
|
+
} else {
|
|
57
|
+
// First block builds on the parent's EMPTY variant, the parent's payload in this batch is orphaned
|
|
58
|
+
warnings.push({slot: parentBlock.slot, payloadEnvelopeInput: parentPayloadInput});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
// First block must build on the parent's EL head. PARENT_PAYLOAD_UNKNOWN (vs the in-segment
|
|
59
64
|
// NON_LINEAR_PAYLOAD_ROOTS below) tells range sync this is a parent-link problem, not an in-batch one.
|
|
60
65
|
if (parentExecHash !== null && currentExecHash !== null && currentExecHash !== parentExecHash) {
|
|
@@ -5,6 +5,7 @@ export enum ExecutionPayloadBidErrorCode {
|
|
|
5
5
|
BUILDER_NOT_ELIGIBLE = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_NOT_ELIGIBLE",
|
|
6
6
|
INVALID_BUILDER_VERSION = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_BUILDER_VERSION",
|
|
7
7
|
NON_ZERO_EXECUTION_PAYMENT = "EXECUTION_PAYLOAD_BID_ERROR_NON_ZERO_EXECUTION_PAYMENT",
|
|
8
|
+
BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH",
|
|
8
9
|
INCOMPATIBLE_WITH_HEAD = "EXECUTION_PAYLOAD_BID_ERROR_INCOMPATIBLE_WITH_HEAD",
|
|
9
10
|
BID_ALREADY_KNOWN = "EXECUTION_PAYLOAD_BID_ERROR_BID_ALREADY_KNOWN",
|
|
10
11
|
BID_TOO_LOW = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_LOW",
|
|
@@ -63,6 +64,7 @@ export type ExecutionPayloadBidErrorType =
|
|
|
63
64
|
}
|
|
64
65
|
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT; parentBlockRoot: RootHex}
|
|
65
66
|
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
|
|
67
|
+
| {code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH; blockHash: RootHex}
|
|
66
68
|
| {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
|
|
67
69
|
| {code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
|
|
68
70
|
| {
|
|
@@ -135,6 +135,7 @@ export function initializeForkChoiceFromFinalizedState(
|
|
|
135
135
|
blockRoot: toRootHex(checkpoint.root),
|
|
136
136
|
timeliness: true, // Optimistically assume is timely
|
|
137
137
|
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
138
|
+
importedTimely: true, // Optimistically assume is timely
|
|
138
139
|
proposerIndex: blockHeader.proposerIndex,
|
|
139
140
|
|
|
140
141
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
@@ -233,6 +234,7 @@ export function initializeForkChoiceFromUnfinalizedState(
|
|
|
233
234
|
targetRoot: headRoot,
|
|
234
235
|
timeliness: true, // Optimistically assume is timely
|
|
235
236
|
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
237
|
+
importedTimely: true, // Optimistically assume is timely
|
|
236
238
|
proposerIndex: blockHeader.proposerIndex,
|
|
237
239
|
|
|
238
240
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ForkName,
|
|
6
6
|
ForkPostAltair,
|
|
7
7
|
ForkPostBellatrix,
|
|
8
|
+
ForkPostGloas,
|
|
8
9
|
ForkPreGloas,
|
|
9
10
|
ForkSeq,
|
|
10
11
|
MIN_SYNC_COMMITTEE_PARTICIPANTS,
|
|
@@ -40,7 +41,6 @@ import {
|
|
|
40
41
|
Slot,
|
|
41
42
|
SyncPeriod,
|
|
42
43
|
altair,
|
|
43
|
-
electra,
|
|
44
44
|
phase0,
|
|
45
45
|
ssz,
|
|
46
46
|
} from "@lodestar/types";
|
|
@@ -52,7 +52,12 @@ import {Metrics} from "../../metrics/index.js";
|
|
|
52
52
|
import {IClock} from "../../util/clock.js";
|
|
53
53
|
import {ChainEventEmitter} from "../emitter.js";
|
|
54
54
|
import {LightClientServerError, LightClientServerErrorCode} from "../errors/lightClientError.js";
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
getBlockBodyExecutionBlockHashProof,
|
|
57
|
+
getBlockBodyExecutionHeaderProof,
|
|
58
|
+
getCurrentSyncCommitteeBranch,
|
|
59
|
+
getNextSyncCommitteeBranch,
|
|
60
|
+
} from "./proofs.js";
|
|
56
61
|
|
|
57
62
|
export type LightClientServerOpts = {
|
|
58
63
|
disableLightClientServerOnImportBlockHead?: boolean;
|
|
@@ -61,6 +66,7 @@ export type LightClientServerOpts = {
|
|
|
61
66
|
|
|
62
67
|
type DependentRootHex = RootHex;
|
|
63
68
|
type BlockRooHex = RootHex;
|
|
69
|
+
type LightClientZeroFinality = Pick<LightClientUpdate, "finalityBranch" | "finalizedHeader">;
|
|
64
70
|
|
|
65
71
|
export type SyncAttestedData = {
|
|
66
72
|
attestedHeader: LightClientHeader;
|
|
@@ -199,6 +205,7 @@ export class LightClientServer {
|
|
|
199
205
|
private readonly clock: IClock;
|
|
200
206
|
private readonly signal: AbortSignal;
|
|
201
207
|
private readonly knownSyncCommittee = new MapDef<SyncPeriod, Set<DependentRootHex>>(() => new Set());
|
|
208
|
+
private readonly zeroFinalityByFork = new Map<ForkName, LightClientZeroFinality>();
|
|
202
209
|
private storedCurrentSyncCommittee = false;
|
|
203
210
|
|
|
204
211
|
/**
|
|
@@ -208,10 +215,6 @@ export class LightClientServer {
|
|
|
208
215
|
private checkpointHeaders = new Map<BlockRooHex, LightClientHeader>();
|
|
209
216
|
private latestHeadUpdate: LightClientOptimisticUpdate | null = null;
|
|
210
217
|
|
|
211
|
-
private readonly zero: Pick<
|
|
212
|
-
altair.LightClientUpdate | electra.LightClientUpdate,
|
|
213
|
-
"finalityBranch" | "finalizedHeader"
|
|
214
|
-
>;
|
|
215
218
|
private finalized: LightClientFinalityUpdate | null = null;
|
|
216
219
|
|
|
217
220
|
constructor(
|
|
@@ -227,14 +230,6 @@ export class LightClientServer {
|
|
|
227
230
|
this.logger = logger;
|
|
228
231
|
this.signal = signal;
|
|
229
232
|
|
|
230
|
-
this.zero = {
|
|
231
|
-
// Assign the highest pre-Gloas light-client header because post-Gloas light-client updates are skipped for now.
|
|
232
|
-
finalizedHeader: ssz.electra.LightClientHeader.defaultValue(),
|
|
233
|
-
// Electra finalityBranch has fixed length of 5 whereas altair has 4. The fifth element will be ignored
|
|
234
|
-
// when serializing as altair LightClientUpdate
|
|
235
|
-
finalityBranch: ssz.electra.LightClientUpdate.fields.finalityBranch.defaultValue(),
|
|
236
|
-
};
|
|
237
|
-
|
|
238
233
|
if (metrics) {
|
|
239
234
|
metrics.lightclientServer.highestSlot.addCollect(() => {
|
|
240
235
|
if (this.latestHeadUpdate) {
|
|
@@ -270,14 +265,6 @@ export class LightClientServer {
|
|
|
270
265
|
return;
|
|
271
266
|
}
|
|
272
267
|
|
|
273
|
-
// TODO GLOAS: Light client updates for gloas are not yet updated in the spec.
|
|
274
|
-
// The block body no longer contains execution payload, so `blockToLightClientHeader`
|
|
275
|
-
// cannot construct a header from a gloas block. Skip all light client processing
|
|
276
|
-
// for post-gloas blocks, revisit once there is a spec for it.
|
|
277
|
-
if (this.config.getForkSeq(block.slot) >= ForkSeq.gloas) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
268
|
// What is the syncAggregate signing?
|
|
282
269
|
// From the state-transition
|
|
283
270
|
// ```
|
|
@@ -695,9 +682,16 @@ export class LightClientServer {
|
|
|
695
682
|
}
|
|
696
683
|
} else {
|
|
697
684
|
isFinalized = false;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
685
|
+
let zeroFinality = this.zeroFinalityByFork.get(attestedFork);
|
|
686
|
+
if (!zeroFinality) {
|
|
687
|
+
const {finalityBranch, finalizedHeader} = this.config
|
|
688
|
+
.getPostAltairForkTypes(attestedHeader.beacon.slot)
|
|
689
|
+
.LightClientUpdate.defaultValue();
|
|
690
|
+
zeroFinality = {finalityBranch, finalizedHeader};
|
|
691
|
+
this.zeroFinalityByFork.set(attestedFork, zeroFinality);
|
|
692
|
+
}
|
|
693
|
+
finalityBranch = zeroFinality.finalityBranch;
|
|
694
|
+
finalizedHeader = zeroFinality.finalizedHeader;
|
|
701
695
|
}
|
|
702
696
|
|
|
703
697
|
const newUpdate = {
|
|
@@ -769,22 +763,24 @@ export function sumBits(bits: BitArray): number {
|
|
|
769
763
|
return bits.getTrueBitIndexes().length;
|
|
770
764
|
}
|
|
771
765
|
|
|
772
|
-
|
|
773
|
-
// in Gloas. So we can assume any types here are pre-gloas
|
|
774
|
-
export function blockToLightClientHeader(
|
|
775
|
-
fork: ForkName,
|
|
776
|
-
block: BeaconBlock<ForkPostAltair & ForkPreGloas>
|
|
777
|
-
): LightClientHeader {
|
|
778
|
-
const blockSlot = block.slot;
|
|
766
|
+
export function blockToLightClientHeader(fork: ForkName, block: BeaconBlock<ForkPostAltair>): LightClientHeader {
|
|
779
767
|
const beacon: phase0.BeaconBlockHeader = {
|
|
780
|
-
slot:
|
|
768
|
+
slot: block.slot,
|
|
781
769
|
proposerIndex: block.proposerIndex,
|
|
782
770
|
parentRoot: block.parentRoot,
|
|
783
771
|
stateRoot: block.stateRoot,
|
|
784
|
-
bodyRoot: (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostAltair
|
|
785
|
-
block.body
|
|
786
|
-
),
|
|
772
|
+
bodyRoot: (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostAltair, "BeaconBlockBody">).hashTreeRoot(block.body),
|
|
787
773
|
};
|
|
774
|
+
|
|
775
|
+
if (isForkPostGloas(fork)) {
|
|
776
|
+
const blockBody = block.body as BeaconBlockBody<ForkPostGloas>;
|
|
777
|
+
return {
|
|
778
|
+
beacon,
|
|
779
|
+
executionBlockHash: blockBody.signedExecutionPayloadBid.message.parentBlockHash,
|
|
780
|
+
executionBranch: getBlockBodyExecutionBlockHashProof(fork, blockBody),
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
788
784
|
if (ForkSeq[fork] >= ForkSeq.capella) {
|
|
789
785
|
const blockBody = block.body as BeaconBlockBody<ForkPostBellatrix & ForkPreGloas>;
|
|
790
786
|
const execution = executionPayloadToPayloadHeader(ForkSeq[fork], blockBody.executionPayload);
|
|
@@ -2,11 +2,13 @@ import {Tree} from "@chainsafe/persistent-merkle-tree";
|
|
|
2
2
|
import {
|
|
3
3
|
BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
|
|
4
4
|
CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS,
|
|
5
|
+
EXECUTION_BLOCK_HASH_GINDEX_GLOAS,
|
|
5
6
|
FINALIZED_ROOT_GINDEX,
|
|
6
7
|
FINALIZED_ROOT_GINDEX_ELECTRA,
|
|
7
8
|
FINALIZED_ROOT_GINDEX_GLOAS,
|
|
8
9
|
ForkName,
|
|
9
10
|
ForkPostBellatrix,
|
|
11
|
+
ForkPostGloas,
|
|
10
12
|
NEXT_SYNC_COMMITTEE_GINDEX_GLOAS,
|
|
11
13
|
isForkPostElectra,
|
|
12
14
|
isForkPostGloas,
|
|
@@ -116,3 +118,11 @@ export function getBlockBodyExecutionHeaderProof(
|
|
|
116
118
|
const bodyView = (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostBellatrix, "BeaconBlockBody">).toView(body);
|
|
117
119
|
return new Tree(bodyView.node).getSingleProof(BigInt(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX));
|
|
118
120
|
}
|
|
121
|
+
|
|
122
|
+
export function getBlockBodyExecutionBlockHashProof(
|
|
123
|
+
fork: ForkPostGloas,
|
|
124
|
+
body: BeaconBlockBody<ForkPostGloas>
|
|
125
|
+
): Uint8Array[] {
|
|
126
|
+
const bodyView = (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostGloas, "BeaconBlockBody">).toView(body);
|
|
127
|
+
return new Tree(bodyView.node).getSingleProof(BigInt(EXECUTION_BLOCK_HASH_GINDEX_GLOAS));
|
|
128
|
+
}
|
|
@@ -104,6 +104,37 @@ export async function validateApiExecutionPayloadBid(
|
|
|
104
104
|
const bid = signedExecutionPayloadBid.message;
|
|
105
105
|
const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
|
|
106
106
|
|
|
107
|
+
// [REJECT] `bid.execution_payment` is zero.
|
|
108
|
+
if (bid.executionPayment !== 0n) {
|
|
109
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
110
|
+
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
111
|
+
builderIndex: bid.builderIndex,
|
|
112
|
+
executionPayment: bid.executionPayment,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// [REJECT] The bid's block hash is not equal to its parent block hash -- i.e.
|
|
117
|
+
// `bid.block_hash != bid.parent_block_hash`.
|
|
118
|
+
if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
|
|
119
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
120
|
+
code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH,
|
|
121
|
+
blockHash: toRootHex(bid.blockHash),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
126
|
+
// consensus layer -- i.e. validate that
|
|
127
|
+
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
128
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
129
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
130
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
131
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
132
|
+
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
133
|
+
blobKzgCommitmentsLen,
|
|
134
|
+
commitmentLimit: maxBlobsPerBlock,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
107
138
|
// [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
108
139
|
if (
|
|
109
140
|
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
|
|
@@ -138,28 +169,6 @@ export async function validateApiExecutionPayloadBid(
|
|
|
138
169
|
});
|
|
139
170
|
}
|
|
140
171
|
|
|
141
|
-
// [REJECT] `bid.execution_payment` is zero.
|
|
142
|
-
if (bid.executionPayment !== 0n) {
|
|
143
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
144
|
-
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
145
|
-
builderIndex: bid.builderIndex,
|
|
146
|
-
executionPayment: bid.executionPayment,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
151
|
-
// consensus layer -- i.e. validate that
|
|
152
|
-
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
153
|
-
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
154
|
-
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
155
|
-
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
156
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
157
|
-
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
158
|
-
blobKzgCommitmentsLen,
|
|
159
|
-
commitmentLimit: maxBlobsPerBlock,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
172
|
const state = await chain.regen
|
|
164
173
|
.getBlockSlotState(parentBlock, bid.slot, {dontTransferCache: true}, RegenCaller.validateApiExecutionPayloadBid)
|
|
165
174
|
.catch(() => {
|
|
@@ -257,6 +266,37 @@ async function validateExecutionPayloadBid(
|
|
|
257
266
|
const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
|
|
258
267
|
const bidParentBlockHash = toRootHex(bid.parentBlockHash);
|
|
259
268
|
|
|
269
|
+
// [REJECT] `bid.execution_payment` is zero.
|
|
270
|
+
if (bid.executionPayment !== 0n) {
|
|
271
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
272
|
+
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
273
|
+
builderIndex: bid.builderIndex,
|
|
274
|
+
executionPayment: bid.executionPayment,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// [REJECT] The bid's block hash is not equal to its parent block hash -- i.e.
|
|
279
|
+
// `bid.block_hash != bid.parent_block_hash`.
|
|
280
|
+
if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
|
|
281
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
282
|
+
code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH,
|
|
283
|
+
blockHash: toRootHex(bid.blockHash),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
288
|
+
// consensus layer -- i.e. validate that
|
|
289
|
+
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
290
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
291
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
292
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
293
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
294
|
+
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
295
|
+
blobKzgCommitmentsLen,
|
|
296
|
+
commitmentLimit: maxBlobsPerBlock,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
260
300
|
// [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
261
301
|
if (
|
|
262
302
|
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
|
|
@@ -402,15 +442,6 @@ async function validateExecutionPayloadBid(
|
|
|
402
442
|
});
|
|
403
443
|
}
|
|
404
444
|
|
|
405
|
-
// [REJECT] `bid.execution_payment` is zero.
|
|
406
|
-
if (bid.executionPayment !== 0n) {
|
|
407
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
408
|
-
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
|
|
409
|
-
builderIndex: bid.builderIndex,
|
|
410
|
-
executionPayment: bid.executionPayment,
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
|
|
414
445
|
// [IGNORE] `bid.fee_recipient == proposer_preferences.fee_recipient`.
|
|
415
446
|
if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
|
|
416
447
|
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
@@ -451,19 +482,6 @@ async function validateExecutionPayloadBid(
|
|
|
451
482
|
});
|
|
452
483
|
}
|
|
453
484
|
|
|
454
|
-
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
455
|
-
// consensus layer -- i.e. validate that
|
|
456
|
-
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
457
|
-
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
458
|
-
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
459
|
-
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
460
|
-
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
461
|
-
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
|
462
|
-
blobKzgCommitmentsLen,
|
|
463
|
-
commitmentLimit: maxBlobsPerBlock,
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
485
|
// [IGNORE] this bid is the highest value bid seen for the tuple
|
|
468
486
|
// `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
|
|
469
487
|
// As a DoS prevention measure, the bid must also exceed the current highest bid by a minimum
|
|
@@ -7,7 +7,6 @@ import {RootHex, gloas, ssz} from "@lodestar/types";
|
|
|
7
7
|
import {toRootHex} from "@lodestar/utils";
|
|
8
8
|
import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
|
|
9
9
|
import {IBeaconChain} from "../index.js";
|
|
10
|
-
import {RegenCaller} from "../regen/index.js";
|
|
11
10
|
|
|
12
11
|
export type PayloadAttestationValidationResult = {
|
|
13
12
|
attDataRootHex: RootHex;
|
|
@@ -82,15 +81,15 @@ async function validatePayloadAttestationMessage(
|
|
|
82
81
|
// TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
|
|
83
82
|
// it is possible that the block didn't pass the validation
|
|
84
83
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
84
|
+
// block.slot === data.slot is enforced above, so use the block's post-state directly to avoid
|
|
85
|
+
// getting through regen queue
|
|
86
|
+
const state = chain.regen.getStateSync(block.stateRoot);
|
|
87
|
+
if (state == null) {
|
|
88
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
89
|
+
code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
90
|
+
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
93
91
|
});
|
|
92
|
+
}
|
|
94
93
|
|
|
95
94
|
if (!isStatePostGloas(state)) {
|
|
96
95
|
throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
|
|
@@ -56,6 +56,16 @@ export async function validateBuilderApiExecutionPayloadBid(
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
|
|
60
|
+
throw Error(`Bid block hash ${toRootHex(bid.blockHash)} must not equal its parent block hash`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
64
|
+
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
65
|
+
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
66
|
+
throw Error(`Bid has too many KZG commitments len=${blobKzgCommitmentsLen} limit=${maxBlobsPerBlock}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
const totalPayment = getBuilderBidTotalGwei(bid, entry.maxExecutionPayment);
|
|
60
70
|
if (totalPayment < entry.minBid) {
|
|
61
71
|
throw Error(
|
|
@@ -100,12 +110,6 @@ export async function validateBuilderApiExecutionPayloadBid(
|
|
|
100
110
|
);
|
|
101
111
|
}
|
|
102
112
|
|
|
103
|
-
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
104
|
-
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
105
|
-
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
106
|
-
throw Error(`Bid has too many KZG commitments len=${blobKzgCommitmentsLen} limit=${maxBlobsPerBlock}`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
113
|
// The coverage check only applies to the staked collateral payment, a pure execution
|
|
110
114
|
// layer payment bid has nothing to cover on-chain
|
|
111
115
|
if (bid.value > 0 && !state.canBuilderCoverBid(bid.builderIndex, bid.value)) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {NotReorgedReason} from "@lodestar/fork-choice";
|
|
3
3
|
import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
|
|
4
4
|
import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
|
|
5
|
+
import type {LateCanonicalBlockReason} from "../../chain/archiveStore/utils/archiveBlocks.js";
|
|
5
6
|
import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
|
|
6
7
|
import {PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
7
8
|
import {
|
|
@@ -1047,6 +1048,11 @@ export function createLodestarMetrics(
|
|
|
1047
1048
|
name: "lodestar_import_block_set_head_after_cutoff_total",
|
|
1048
1049
|
help: "Total times an imported block is set as head after ATTESTATION_DUE_BPS of the slot",
|
|
1049
1050
|
}),
|
|
1051
|
+
lateCanonicalBlock: register.counter<{reason: LateCanonicalBlockReason}>({
|
|
1052
|
+
name: "lodestar_import_block_late_canonical_total",
|
|
1053
|
+
help: "Total finalized-canonical blocks this node imported after the attestation cutoff; reason distinguishes this node's processing lag (slow_import) from late reception (late_receive)",
|
|
1054
|
+
labelNames: ["reason"],
|
|
1055
|
+
}),
|
|
1050
1056
|
bySource: register.gauge<{source: BlockInputSource}>({
|
|
1051
1057
|
name: "lodestar_import_block_by_source_total",
|
|
1052
1058
|
help: "Total number of imported blocks by source",
|
|
@@ -1352,11 +1358,6 @@ export function createLodestarMetrics(
|
|
|
1352
1358
|
help: "Total number of InsertOutcome as a result of adding an execution payload bid from gossip to the pool",
|
|
1353
1359
|
labelNames: ["insertOutcome"],
|
|
1354
1360
|
}),
|
|
1355
|
-
apiInsertOutcome: register.counter<{insertOutcome: InsertOutcome}>({
|
|
1356
|
-
name: "lodestar_oppool_execution_payload_bid_pool_api_insert_outcome_total",
|
|
1357
|
-
help: "Total number of InsertOutcome as a result of adding an execution payload bid from api to the pool",
|
|
1358
|
-
labelNames: ["insertOutcome"],
|
|
1359
|
-
}),
|
|
1360
1361
|
apiValidationTime: register.histogram({
|
|
1361
1362
|
name: "lodestar_api_execution_payload_bid_validation_time_seconds",
|
|
1362
1363
|
help: "Time elapsed for signed execution payload bid validation - api path",
|
package/src/network/network.ts
CHANGED
|
@@ -481,7 +481,7 @@ export class Network implements INetwork {
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
async publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise<number> {
|
|
484
|
-
const epoch = computeEpochAtSlot(update.
|
|
484
|
+
const epoch = computeEpochAtSlot(update.attestedHeader.beacon.slot);
|
|
485
485
|
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
|
|
486
486
|
|
|
487
487
|
return this.publishGossip<GossipType.light_client_finality_update>(
|
|
@@ -491,7 +491,7 @@ export class Network implements INetwork {
|
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
async publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise<number> {
|
|
494
|
-
const epoch = computeEpochAtSlot(update.
|
|
494
|
+
const epoch = computeEpochAtSlot(update.attestedHeader.beacon.slot);
|
|
495
495
|
const boundary = this.config.getForkBoundaryAtEpoch(epoch);
|
|
496
496
|
|
|
497
497
|
return this.publishGossip<GossipType.light_client_optimistic_update>(
|