@lodestar/beacon-node 1.44.0-dev.de5436c9ff → 1.44.0-dev.df8a25e36b
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 +7 -24
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +0 -25
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +20 -27
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/emitter.d.ts +2 -1
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +0 -7
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +0 -3
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +6 -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 +3 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/validation/block.d.ts +5 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +4 -14
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +12 -1
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +0 -2
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +10 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +750 -3
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +9 -2
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.js +1 -1
- package/lib/network/processor/index.js.map +1 -1
- package/lib/sync/types.d.ts +9 -1
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +9 -2
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +64 -30
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/sszBytes.js +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +6 -32
- package/src/api/impl/beacon/pool/index.ts +1 -41
- package/src/api/impl/validator/index.ts +29 -39
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -4
- package/src/chain/emitter.ts +3 -2
- package/src/chain/errors/blockError.ts +0 -4
- package/src/chain/errors/executionPayloadBid.ts +7 -0
- package/src/chain/forkChoice/index.ts +5 -0
- package/src/chain/produceBlock/produceBlockBody.ts +1 -1
- package/src/chain/validation/block.ts +12 -16
- package/src/chain/validation/executionPayloadBid.ts +13 -1
- package/src/chain/validation/executionPayloadEnvelope.ts +0 -2
- package/src/metrics/metrics/lodestar.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +9 -2
- package/src/network/processor/index.ts +1 -1
- package/src/sync/types.ts +11 -2
- package/src/sync/unknownBlock.ts +70 -31
- package/src/util/sszBytes.ts +1 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
isForkPostGloas,
|
|
10
10
|
} from "@lodestar/params";
|
|
11
11
|
import {isStatePostAltair} from "@lodestar/state-transition";
|
|
12
|
-
import {
|
|
12
|
+
import {Epoch, SingleAttestation, isElectraAttestation, ssz, sszTypesFor} from "@lodestar/types";
|
|
13
13
|
import {toRootHex} from "@lodestar/utils";
|
|
14
14
|
import {
|
|
15
15
|
AttestationError,
|
|
@@ -40,25 +40,6 @@ export function getBeaconPoolApi({
|
|
|
40
40
|
network,
|
|
41
41
|
}: Pick<ApiModules, "chain" | "logger" | "metrics" | "network">): ApplicationMethods<routes.beacon.pool.Endpoints> {
|
|
42
42
|
return {
|
|
43
|
-
async getPoolAttestations({slot, committeeIndex}) {
|
|
44
|
-
// Already filtered by slot
|
|
45
|
-
let attestations: Attestation[] = chain.aggregatedAttestationPool.getAll(slot);
|
|
46
|
-
const fork = chain.config.getForkName(slot ?? chain.clock.currentSlot);
|
|
47
|
-
|
|
48
|
-
if (isForkPostElectra(fork)) {
|
|
49
|
-
throw new ApiError(
|
|
50
|
-
400,
|
|
51
|
-
`Use getPoolAttestationsV2 to retrieve pool attestations for post-electra fork=${fork}`
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (committeeIndex !== undefined) {
|
|
56
|
-
attestations = attestations.filter((attestation) => committeeIndex === attestation.data.index);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return {data: attestations};
|
|
60
|
-
},
|
|
61
|
-
|
|
62
43
|
async getPoolAttestationsV2({slot, committeeIndex}) {
|
|
63
44
|
// Already filtered by slot
|
|
64
45
|
let attestations = chain.aggregatedAttestationPool.getAll(slot);
|
|
@@ -134,19 +115,6 @@ export function getBeaconPoolApi({
|
|
|
134
115
|
}
|
|
135
116
|
},
|
|
136
117
|
|
|
137
|
-
async getPoolAttesterSlashings() {
|
|
138
|
-
const fork = chain.config.getForkName(chain.clock.currentSlot);
|
|
139
|
-
|
|
140
|
-
if (isForkPostElectra(fork)) {
|
|
141
|
-
throw new ApiError(
|
|
142
|
-
400,
|
|
143
|
-
`Use getPoolAttesterSlashingsV2 to retrieve pool attester slashings for post-electra fork=${fork}`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return {data: chain.opPool.getAllAttesterSlashings()};
|
|
148
|
-
},
|
|
149
|
-
|
|
150
118
|
async getPoolAttesterSlashingsV2() {
|
|
151
119
|
const fork = chain.config.getForkName(chain.clock.currentSlot);
|
|
152
120
|
return {data: chain.opPool.getAllAttesterSlashings(), meta: {version: fork}};
|
|
@@ -164,10 +132,6 @@ export function getBeaconPoolApi({
|
|
|
164
132
|
return {data: chain.opPool.getAllBlsToExecutionChanges().map(({data}) => data)};
|
|
165
133
|
},
|
|
166
134
|
|
|
167
|
-
async submitPoolAttestations({signedAttestations}) {
|
|
168
|
-
await this.submitPoolAttestationsV2({signedAttestations});
|
|
169
|
-
},
|
|
170
|
-
|
|
171
135
|
async submitPoolAttestationsV2({signedAttestations}) {
|
|
172
136
|
const fork = chain.config.getForkName(chain.clock.currentSlot);
|
|
173
137
|
const seenTimestampSec = Date.now() / 1000;
|
|
@@ -247,10 +211,6 @@ export function getBeaconPoolApi({
|
|
|
247
211
|
}
|
|
248
212
|
},
|
|
249
213
|
|
|
250
|
-
async submitPoolAttesterSlashings({attesterSlashing}) {
|
|
251
|
-
await this.submitPoolAttesterSlashingsV2({attesterSlashing});
|
|
252
|
-
},
|
|
253
|
-
|
|
254
214
|
async submitPoolAttesterSlashingsV2({attesterSlashing}) {
|
|
255
215
|
await validateApiAttesterSlashing(chain, attesterSlashing);
|
|
256
216
|
const fork = chain.config.getForkName(Number(attesterSlashing.attestation1.data.slot));
|
|
@@ -1207,7 +1207,10 @@ export function getValidatorApi(
|
|
|
1207
1207
|
const isPostFulu = isForkPostFulu(config.getForkName(startSlot));
|
|
1208
1208
|
const maxFutureEpoch = isPostFulu && nearNextEpoch && opts?.v2 ? nextEpoch + 1 : nextEpoch;
|
|
1209
1209
|
if (currentEpoch >= 0 && epoch > maxFutureEpoch) {
|
|
1210
|
-
throw new ApiError(
|
|
1210
|
+
throw new ApiError(
|
|
1211
|
+
400,
|
|
1212
|
+
`Requested epoch ${epoch} must not be more than ${maxFutureEpoch}, currentEpoch=${currentEpoch}, v2=${opts?.v2 ?? false}`
|
|
1213
|
+
);
|
|
1211
1214
|
}
|
|
1212
1215
|
|
|
1213
1216
|
const head = chain.forkChoice.getHead();
|
|
@@ -1291,17 +1294,35 @@ export function getValidatorApi(
|
|
|
1291
1294
|
duties.push({slot: startSlot + i, validatorIndex: indexes[i], pubkey: pubkeys[i]});
|
|
1292
1295
|
}
|
|
1293
1296
|
|
|
1294
|
-
//
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1297
|
+
// In v2 the dependent root is different after fulu due to deterministic proposer lookahead
|
|
1298
|
+
let dependentRoot = proposerShufflingDecisionRoot(
|
|
1299
|
+
opts?.v2 ? config.getForkName(startSlot) : ForkName.phase0,
|
|
1300
|
+
state,
|
|
1301
|
+
epoch
|
|
1302
|
+
);
|
|
1303
|
+
const logCtx = {
|
|
1304
|
+
epoch,
|
|
1305
|
+
stateSlot: state.slot,
|
|
1306
|
+
stateEpoch: state.epoch,
|
|
1307
|
+
v2: opts?.v2 ?? false,
|
|
1308
|
+
};
|
|
1309
|
+
if (dependentRoot === null) {
|
|
1310
|
+
// fallback to get_proposer_duties() v1, also in lodestar v1.43
|
|
1311
|
+
logger.verbose("Proposer duties decision root not in state, falling back to state epoch", logCtx);
|
|
1312
|
+
dependentRoot = proposerShufflingDecisionRoot(ForkName.phase0, state, state.epoch);
|
|
1313
|
+
}
|
|
1314
|
+
if (dependentRoot === null) {
|
|
1315
|
+
logger.verbose("Proposer duties decision root not in state, falling back to genesis block root", logCtx);
|
|
1316
|
+
dependentRoot = await getGenesisBlockRoot(state);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
const dependentRootHex = toRootHex(dependentRoot);
|
|
1320
|
+
logger.verbose("Computed proposer duties decision root", {...logCtx, dependentRoot: dependentRootHex});
|
|
1300
1321
|
|
|
1301
1322
|
return {
|
|
1302
1323
|
data: duties,
|
|
1303
1324
|
meta: {
|
|
1304
|
-
dependentRoot:
|
|
1325
|
+
dependentRoot: dependentRootHex,
|
|
1305
1326
|
executionOptimistic: isOptimisticBlock(head),
|
|
1306
1327
|
},
|
|
1307
1328
|
};
|
|
@@ -1480,33 +1501,6 @@ export function getValidatorApi(
|
|
|
1480
1501
|
};
|
|
1481
1502
|
},
|
|
1482
1503
|
|
|
1483
|
-
async getAggregatedAttestation({attestationDataRoot, slot}) {
|
|
1484
|
-
notWhileSyncing();
|
|
1485
|
-
|
|
1486
|
-
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
1487
|
-
|
|
1488
|
-
const dataRootHex = toRootHex(attestationDataRoot);
|
|
1489
|
-
const aggregate = chain.attestationPool.getAggregate(slot, dataRootHex, null);
|
|
1490
|
-
const fork = chain.config.getForkName(slot);
|
|
1491
|
-
|
|
1492
|
-
if (isForkPostElectra(fork)) {
|
|
1493
|
-
throw new ApiError(
|
|
1494
|
-
400,
|
|
1495
|
-
`Use getAggregatedAttestationV2 to retrieve aggregated attestations for post-electra fork=${fork}`
|
|
1496
|
-
);
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
if (!aggregate) {
|
|
1500
|
-
throw new ApiError(404, `No aggregated attestation for slot=${slot}, dataRoot=${dataRootHex}`);
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
metrics?.production.producedAggregateParticipants.observe(aggregate.aggregationBits.getTrueBitIndexes().length);
|
|
1504
|
-
|
|
1505
|
-
return {
|
|
1506
|
-
data: aggregate,
|
|
1507
|
-
};
|
|
1508
|
-
},
|
|
1509
|
-
|
|
1510
1504
|
async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
|
|
1511
1505
|
notWhileSyncing();
|
|
1512
1506
|
|
|
@@ -1530,10 +1524,6 @@ export function getValidatorApi(
|
|
|
1530
1524
|
};
|
|
1531
1525
|
},
|
|
1532
1526
|
|
|
1533
|
-
async publishAggregateAndProofs({signedAggregateAndProofs}) {
|
|
1534
|
-
await this.publishAggregateAndProofsV2({signedAggregateAndProofs});
|
|
1535
|
-
},
|
|
1536
|
-
|
|
1537
1527
|
async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
|
|
1538
1528
|
notWhileSyncing();
|
|
1539
1529
|
|
|
@@ -61,10 +61,8 @@ export function verifyExecutionPayloadEnvelope(
|
|
|
61
61
|
`Prev randao mismatch between bid and payload bid=${toHex(bid.prevRandao)} payload=${toHex(payload.prevRandao)}`
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
|
-
if (
|
|
65
|
-
throw new Error(
|
|
66
|
-
`Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${Number(bid.gasLimit)}`
|
|
67
|
-
);
|
|
64
|
+
if (bid.gasLimit !== payload.gasLimit) {
|
|
65
|
+
throw new Error(`Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${bid.gasLimit}`);
|
|
68
66
|
}
|
|
69
67
|
if (!byteArrayEquals(bid.blockHash, payload.blockHash)) {
|
|
70
68
|
throw new Error(
|
package/src/chain/emitter.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {StrictEventEmitter} from "strict-event-emitter-types";
|
|
|
3
3
|
import {routes} from "@lodestar/api";
|
|
4
4
|
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
5
5
|
import {IBeaconStateView} from "@lodestar/state-transition";
|
|
6
|
-
import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
|
|
6
|
+
import {DataColumnSidecar, RootHex, Slot, deneb, phase0} from "@lodestar/types";
|
|
7
7
|
import {PeerIdStr} from "../util/peerId.js";
|
|
8
8
|
import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
|
|
9
9
|
import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
|
|
@@ -94,7 +94,8 @@ export type ChainEventData = {
|
|
|
94
94
|
peer: PeerIdStr;
|
|
95
95
|
source: BlockInputSource;
|
|
96
96
|
};
|
|
97
|
-
|
|
97
|
+
// slot is the message slot, not necessarily the envelope's slot, but useful as a logging/prune hint
|
|
98
|
+
[ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; slot: Slot; peer?: PeerIdStr; source: BlockInputSource};
|
|
98
99
|
};
|
|
99
100
|
|
|
100
101
|
export type IChainEvents = ApiEvents & {
|
|
@@ -61,9 +61,6 @@ export enum BlockErrorCode {
|
|
|
61
61
|
TRANSACTIONS_TOO_BIG = "BLOCK_ERROR_TRANSACTIONS_TOO_BIG",
|
|
62
62
|
/** Execution engine is unavailable, syncing, or api call errored. Peers must not be downscored on this code */
|
|
63
63
|
EXECUTION_ENGINE_ERROR = "BLOCK_ERROR_EXECUTION_ERROR",
|
|
64
|
-
/** The attestation head block is too far behind the attestation slot, causing many skip slots.
|
|
65
|
-
This is deemed a DoS risk */
|
|
66
|
-
TOO_MANY_SKIPPED_SLOTS = "TOO_MANY_SKIPPED_SLOTS",
|
|
67
64
|
/** The blobs are unavailable */
|
|
68
65
|
DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
|
|
69
66
|
/** Block contains too many kzg commitments */
|
|
@@ -89,7 +86,6 @@ export type BlockErrorType =
|
|
|
89
86
|
| {code: BlockErrorCode.FUTURE_SLOT; blockSlot: Slot; currentSlot: Slot}
|
|
90
87
|
| {code: BlockErrorCode.STATE_ROOT_MISMATCH}
|
|
91
88
|
| {code: BlockErrorCode.GENESIS_BLOCK}
|
|
92
|
-
| {code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS; parentSlot: Slot; blockSlot: Slot}
|
|
93
89
|
| {code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT; blockSlot: Slot; finalizedSlot: Slot}
|
|
94
90
|
| {code: BlockErrorCode.ALREADY_KNOWN; root: RootHex}
|
|
95
91
|
| {code: BlockErrorCode.REPEAT_PROPOSAL; proposerIndex: ValidatorIndex}
|
|
@@ -12,6 +12,7 @@ export enum ExecutionPayloadBidErrorCode {
|
|
|
12
12
|
UNKNOWN_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_PARENT_BLOCK_HASH",
|
|
13
13
|
INVALID_SLOT = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SLOT",
|
|
14
14
|
NOT_LATER_THAN_PARENT = "EXECUTION_PAYLOAD_BID_ERROR_NOT_LATER_THAN_PARENT",
|
|
15
|
+
INVALID_PREV_RANDAO = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_PREV_RANDAO",
|
|
15
16
|
INVALID_SIGNATURE = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SIGNATURE",
|
|
16
17
|
NO_MATCHING_PROPOSER_PREFERENCES = "EXECUTION_PAYLOAD_BID_ERROR_NO_MATCHING_PROPOSER_PREFERENCES",
|
|
17
18
|
PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH = "EXECUTION_PAYLOAD_BID_ERROR_PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH",
|
|
@@ -43,6 +44,12 @@ export type ExecutionPayloadBidErrorType =
|
|
|
43
44
|
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
|
|
44
45
|
| {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
|
|
45
46
|
| {code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
|
|
47
|
+
| {
|
|
48
|
+
code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO;
|
|
49
|
+
builderIndex: BuilderIndex;
|
|
50
|
+
bidPrevRandao: string;
|
|
51
|
+
expectedPrevRandao: string;
|
|
52
|
+
}
|
|
46
53
|
| {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: BuilderIndex; slot: Slot}
|
|
47
54
|
| {
|
|
48
55
|
code: ExecutionPayloadBidErrorCode.NO_MATCHING_PROPOSER_PREFERENCES;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {routes} from "@lodestar/api";
|
|
1
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
3
|
import {
|
|
3
4
|
ExecutionStatus,
|
|
@@ -121,6 +122,8 @@ export function initializeForkChoiceFromFinalizedState(
|
|
|
121
122
|
{
|
|
122
123
|
onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
|
|
123
124
|
onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
|
|
125
|
+
onFastConfirmation: ({block, slot, currentSlot}) =>
|
|
126
|
+
emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
|
|
124
127
|
}
|
|
125
128
|
),
|
|
126
129
|
|
|
@@ -214,6 +217,8 @@ export function initializeForkChoiceFromUnfinalizedState(
|
|
|
214
217
|
{
|
|
215
218
|
onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
|
|
216
219
|
onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
|
|
220
|
+
onFastConfirmation: ({block, slot, currentSlot}) =>
|
|
221
|
+
emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
|
|
217
222
|
}
|
|
218
223
|
);
|
|
219
224
|
|
|
@@ -344,7 +344,7 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
344
344
|
blockHash: executionPayload.blockHash,
|
|
345
345
|
prevRandao: currentState.getRandaoMix(currentState.epoch),
|
|
346
346
|
feeRecipient: executionPayload.feeRecipient,
|
|
347
|
-
gasLimit:
|
|
347
|
+
gasLimit: executionPayload.gasLimit,
|
|
348
348
|
builderIndex: BUILDER_INDEX_SELF_BUILD,
|
|
349
349
|
slot: blockSlot,
|
|
350
350
|
value: 0,
|
|
@@ -15,12 +15,17 @@ import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js
|
|
|
15
15
|
import {IBeaconChain} from "../interface.js";
|
|
16
16
|
import {RegenCaller} from "../regen/index.js";
|
|
17
17
|
|
|
18
|
+
export type GossipBlockValidationResult = {
|
|
19
|
+
/** Number of skipped slots between the block and its parent (blockSlot - parentSlot - 1) */
|
|
20
|
+
skippedSlots: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
18
23
|
export async function validateGossipBlock(
|
|
19
24
|
config: ChainForkConfig,
|
|
20
25
|
chain: IBeaconChain,
|
|
21
26
|
signedBlock: SignedBeaconBlock,
|
|
22
27
|
fork: ForkName
|
|
23
|
-
): Promise<
|
|
28
|
+
): Promise<GossipBlockValidationResult> {
|
|
24
29
|
const block = signedBlock.message;
|
|
25
30
|
const blockSlot = block.slot;
|
|
26
31
|
const blockEpoch = computeEpochAtSlot(blockSlot);
|
|
@@ -109,21 +114,6 @@ export async function validateGossipBlock(
|
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
// [IGNORE] The attestation head block is too far behind the attestation slot, causing many skip slots.
|
|
113
|
-
// This is deemed a DoS risk because we need to get the proposerShuffling. To get the shuffling we have
|
|
114
|
-
// to do a bunch of epoch transitions, the longer the distance between the parent and block,
|
|
115
|
-
// the more we have to do. epochTransitions are expensive ~750ms, so we must limit how many a
|
|
116
|
-
// single bad block can trigger
|
|
117
|
-
// Note: Ensure this check is done before calling chain.regen.getBlockSlotStat as this is the function that does various epoch transitions.
|
|
118
|
-
// Note: This validation check is not part of the spec.
|
|
119
|
-
if (chain.opts.maxSkipSlots != null && parentBlock.slot + chain.opts.maxSkipSlots < blockSlot) {
|
|
120
|
-
throw new BlockGossipError(GossipAction.IGNORE, {
|
|
121
|
-
code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS,
|
|
122
|
-
parentSlot: parentBlock.slot,
|
|
123
|
-
blockSlot,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
117
|
// [REJECT] The block is from a higher slot than its parent.
|
|
128
118
|
if (parentBlock.slot >= blockSlot) {
|
|
129
119
|
throw new BlockGossipError(GossipAction.REJECT, {
|
|
@@ -133,6 +123,10 @@ export async function validateGossipBlock(
|
|
|
133
123
|
});
|
|
134
124
|
}
|
|
135
125
|
|
|
126
|
+
// Number of skipped slots between block and parent (non-spec). Previously this gated blocks via
|
|
127
|
+
// maxSkipSlots; now the caller only observes it so legitimate post-skip blocks are no longer ignored.
|
|
128
|
+
const skippedSlots = blockSlot - parentBlock.slot - 1;
|
|
129
|
+
|
|
136
130
|
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
|
|
137
131
|
if (isForkPostDeneb(fork) && !isForkPostGloas(fork)) {
|
|
138
132
|
const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
|
|
@@ -247,4 +241,6 @@ export async function validateGossipBlock(
|
|
|
247
241
|
}
|
|
248
242
|
|
|
249
243
|
chain.seenBlockProposers.add(blockSlot, proposerIndex);
|
|
244
|
+
|
|
245
|
+
return {skippedSlots};
|
|
250
246
|
}
|
|
@@ -175,7 +175,7 @@ async function validateExecutionPayloadBid(
|
|
|
175
175
|
// [IGNORE] `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, target_gas_limit)`,
|
|
176
176
|
// where `parent_gas_limit` is the `gas_limit` of the parent execution payload and
|
|
177
177
|
// `target_gas_limit` is `proposer_preferences.target_gas_limit`.
|
|
178
|
-
const bidGasLimit =
|
|
178
|
+
const bidGasLimit = bid.gasLimit;
|
|
179
179
|
const parentGasLimit = parentPayloadVariant.executionPayloadGasLimit;
|
|
180
180
|
const targetGasLimit = proposerPreferences.message.targetGasLimit;
|
|
181
181
|
if (!isGasLimitTargetCompatible(parentGasLimit, bidGasLimit, targetGasLimit)) {
|
|
@@ -232,6 +232,18 @@ async function validateExecutionPayloadBid(
|
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
// [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that
|
|
236
|
+
// `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))`.
|
|
237
|
+
const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
|
|
238
|
+
if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
|
|
239
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
240
|
+
code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO,
|
|
241
|
+
builderIndex: bid.builderIndex,
|
|
242
|
+
bidPrevRandao: toHex(bid.prevRandao),
|
|
243
|
+
expectedPrevRandao: toHex(randaoMix),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
235
247
|
// [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
|
|
236
248
|
const signatureSet = createSingleSignatureSetFromComponents(
|
|
237
249
|
PublicKey.fromBytes(builder.pubkey),
|
|
@@ -35,8 +35,6 @@ async function validateExecutionPayloadEnvelope(
|
|
|
35
35
|
// [IGNORE] The envelope's block root `envelope.beacon_block_root` has been seen (via
|
|
36
36
|
// gossip or non-gossip sources) (a client MAY queue payload for processing once
|
|
37
37
|
// the block is retrieved).
|
|
38
|
-
// TODO GLOAS: Need to review this, we should queue the envelope for later
|
|
39
|
-
// processing if the block is not yet known, otherwise we would ignore it here
|
|
40
38
|
const block = chain.forkChoice.getBlockDefaultStatus(envelope.beaconBlockRoot);
|
|
41
39
|
if (block === null) {
|
|
42
40
|
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
|
|
@@ -610,6 +610,11 @@ export function createLodestarMetrics(
|
|
|
610
610
|
help: "The origination source of one of the BlockInputSync triggers",
|
|
611
611
|
labelNames: ["source"],
|
|
612
612
|
}),
|
|
613
|
+
payloadSource: register.counter<{source: BlockInputSource}>({
|
|
614
|
+
name: "lodestar_payload_input_sync_source_total",
|
|
615
|
+
help: "Count of payload (execution payload envelope) sync triggers, labeled by their source",
|
|
616
|
+
labelNames: ["source"],
|
|
617
|
+
}),
|
|
613
618
|
pendingBlocks: register.gauge({
|
|
614
619
|
name: "lodestar_sync_unknown_block_pending_blocks_size",
|
|
615
620
|
help: "Current size of UnknownBlockSync pending blocks cache",
|
|
@@ -861,6 +866,12 @@ export function createLodestarMetrics(
|
|
|
861
866
|
labelNames: ["numBlobs"],
|
|
862
867
|
}),
|
|
863
868
|
|
|
869
|
+
skippedSlots: register.histogram({
|
|
870
|
+
name: "lodestar_gossip_block_skipped_slots",
|
|
871
|
+
help: "Number of skipped slots between a gossip block and its parent (blockSlot - parentSlot - 1)",
|
|
872
|
+
buckets: [0, 1, 2, 4, 8, 16, 32],
|
|
873
|
+
}),
|
|
874
|
+
|
|
864
875
|
processBlockErrors: register.gauge<{error: BlockErrorCode | "NOT_BLOCK_ERROR"}>({
|
|
865
876
|
name: "lodestar_gossip_block_process_block_errors",
|
|
866
877
|
help: "Count of errors, by error type, while processing blocks",
|
|
@@ -185,7 +185,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
185
185
|
peerIdStr,
|
|
186
186
|
});
|
|
187
187
|
try {
|
|
188
|
-
await validateGossipBlock(config, chain, signedBlock, fork);
|
|
188
|
+
const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
|
|
189
189
|
|
|
190
190
|
if (isForkPostGloas(fork)) {
|
|
191
191
|
chain.seenPayloadEnvelopeInputCache.add({
|
|
@@ -205,8 +205,15 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
205
205
|
|
|
206
206
|
metrics?.gossipBlock.gossipValidation.recvToValidation.observe(recvToValidation);
|
|
207
207
|
metrics?.gossipBlock.gossipValidation.validationTime.observe(validationTime);
|
|
208
|
+
metrics?.gossipBlock.skippedSlots.observe(skippedSlots);
|
|
208
209
|
|
|
209
|
-
logger.debug("Validated gossip block", {
|
|
210
|
+
logger.debug("Validated gossip block", {
|
|
211
|
+
...blockInputMeta,
|
|
212
|
+
...logCtx,
|
|
213
|
+
recvToValidation,
|
|
214
|
+
validationTime,
|
|
215
|
+
skippedSlots,
|
|
216
|
+
});
|
|
210
217
|
|
|
211
218
|
chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRootHex});
|
|
212
219
|
|
|
@@ -299,7 +299,7 @@ export class NetworkProcessor {
|
|
|
299
299
|
return;
|
|
300
300
|
}
|
|
301
301
|
this.unknownEnvelopesBySlot.getOrDefault(slot).add(root);
|
|
302
|
-
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, peer, source});
|
|
302
|
+
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
|
package/src/sync/types.ts
CHANGED
|
@@ -19,7 +19,14 @@ export enum PendingBlockType {
|
|
|
19
19
|
*/
|
|
20
20
|
INCOMPLETE_BLOCK_INPUT = "IncompleteBlockInput",
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Payload analog of UNKNOWN_BLOCK_ROOT: we have a beacon block root but not its execution payload envelope.
|
|
24
|
+
*/
|
|
25
|
+
UNKNOWN_PAYLOAD_BLOCK_ROOT = "unknown_payload_block_root",
|
|
26
|
+
/**
|
|
27
|
+
* Payload analog of INCOMPLETE_BLOCK_INPUT: we have a partial payload input that did not complete in time.
|
|
28
|
+
*/
|
|
29
|
+
INCOMPLETE_PAYLOAD_ENVELOPE = "incomplete_payload_envelope",
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
export enum PendingBlockInputStatus {
|
|
@@ -70,6 +77,8 @@ export type PendingPayloadInput = {
|
|
|
70
77
|
export type PendingPayloadRootHex = {
|
|
71
78
|
status: PendingPayloadInputStatus.pending | PendingPayloadInputStatus.fetching;
|
|
72
79
|
rootHex: RootHex;
|
|
80
|
+
// message slot hint, may be missing when resolving a parent payload
|
|
81
|
+
slot?: Slot;
|
|
73
82
|
timeAddedSec: number;
|
|
74
83
|
timeSyncedSec?: number;
|
|
75
84
|
peerIdStrings: Set<string>;
|
|
@@ -125,5 +134,5 @@ export function getPayloadSyncCacheItemSlot(payload: PayloadSyncCacheItem): Slot
|
|
|
125
134
|
return payload.envelope.message.payload.slotNumber;
|
|
126
135
|
}
|
|
127
136
|
|
|
128
|
-
return "unknown";
|
|
137
|
+
return payload.slot ?? "unknown";
|
|
129
138
|
}
|