@lodestar/beacon-node 1.45.0-rc.0 → 1.46.0-dev.8fc4e297d2
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 +186 -57
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +172 -88
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +6 -3
- package/lib/api/rest/base.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +2 -17
- 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 +13 -5
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +23 -2
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +48 -6
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +25 -4
- 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/errors/proposerPreferences.d.ts +14 -1
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/errors/proposerPreferences.js +2 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -1
- package/lib/chain/lightClient/index.d.ts.map +1 -1
- package/lib/chain/lightClient/index.js +11 -6
- package/lib/chain/lightClient/index.js.map +1 -1
- package/lib/chain/lightClient/proofs.d.ts.map +1 -1
- package/lib/chain/lightClient/proofs.js +28 -2
- package/lib/chain/lightClient/proofs.js.map +1 -1
- package/lib/chain/lightClient/types.d.ts +3 -1
- package/lib/chain/lightClient/types.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/validation/aggregateAndProof.js +10 -6
- package/lib/chain/validation/aggregateAndProof.js.map +1 -1
- package/lib/chain/validation/attestation.d.ts.map +1 -1
- package/lib/chain/validation/attestation.js +10 -6
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +53 -4
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +2 -2
- 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/chain/validation/proposerPreferences.d.ts +9 -2
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +61 -9
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
- package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
- package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts +2 -2
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +15 -9
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +2 -2
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +74 -231
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +24 -1
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/interface.d.ts +4 -1
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +4 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +19 -11
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +12 -6
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/index.js +20 -11
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/util.d.ts +1 -0
- package/lib/network/util.d.ts.map +1 -1
- package/lib/network/util.js +4 -0
- package/lib/network/util.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +7 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +7 -2
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/execution.js +1 -1
- package/lib/util/execution.js.map +1 -1
- package/package.json +18 -16
- package/src/api/impl/beacon/blocks/index.ts +221 -63
- package/src/api/impl/validator/index.ts +206 -98
- package/src/api/rest/base.ts +8 -3
- package/src/chain/blocks/importBlock.ts +0 -17
- package/src/chain/blocks/utils/chainSegment.ts +16 -5
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
- package/src/chain/chain.ts +31 -2
- package/src/chain/errors/blockError.ts +44 -7
- package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
- package/src/chain/errors/proposerPreferences.ts +16 -1
- package/src/chain/lightClient/index.ts +12 -7
- package/src/chain/lightClient/proofs.ts +36 -1
- package/src/chain/lightClient/types.ts +3 -1
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
- package/src/chain/validation/aggregateAndProof.ts +12 -6
- package/src/chain/validation/attestation.ts +12 -6
- package/src/chain/validation/block.ts +69 -4
- package/src/chain/validation/executionPayloadBid.ts +2 -2
- package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
- package/src/chain/validation/proposerPreferences.ts +71 -9
- package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
- package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
- package/src/network/gossip/encoding.ts +13 -7
- package/src/network/gossip/gossipsub.ts +7 -2
- package/src/network/gossip/topic.ts +30 -0
- package/src/network/interface.ts +3 -1
- package/src/network/network.ts +24 -15
- package/src/network/processor/gossipHandlers.ts +12 -6
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
- package/src/network/reqresp/handlers/index.ts +21 -12
- package/src/network/util.ts +5 -0
- package/src/sync/sync.ts +10 -0
- package/src/sync/unknownBlock.ts +8 -2
- package/src/util/execution.ts +1 -1
|
@@ -8,8 +8,10 @@ import {GossipActionError} from "./gossipValidation.js";
|
|
|
8
8
|
export enum BlockErrorCode {
|
|
9
9
|
/** The prestate cannot be fetched */
|
|
10
10
|
PRESTATE_MISSING = "BLOCK_ERROR_PRESTATE_MISSING",
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The first block of a chain segment references a parentRoot unknown to fork-choice.
|
|
13
|
+
*/
|
|
14
|
+
PARENT_BLOCK_UNKNOWN = "BLOCK_ERROR_PARENT_BLOCK_UNKNOWN",
|
|
13
15
|
/** The block slot is greater than the present slot. */
|
|
14
16
|
FUTURE_SLOT = "BLOCK_ERROR_FUTURE_SLOT",
|
|
15
17
|
/** The block state_root does not match the generated state. */
|
|
@@ -38,7 +40,10 @@ export enum BlockErrorCode {
|
|
|
38
40
|
NOT_FINALIZED_DESCENDANT = "BLOCK_ERROR_NOT_FINALIZED_DESCENDANT",
|
|
39
41
|
/** The provided block is from an later slot than its parent. */
|
|
40
42
|
NOT_LATER_THAN_PARENT = "BLOCK_ERROR_NOT_LATER_THAN_PARENT",
|
|
41
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* A block in the middle of a chain segment does not set its parentRoot to the previous block's root
|
|
45
|
+
* (broken parent-root link inside the segment).
|
|
46
|
+
*/
|
|
42
47
|
NON_LINEAR_PARENT_ROOTS = "BLOCK_ERROR_NON_LINEAR_PARENT_ROOTS",
|
|
43
48
|
/** The slots of the blocks in the chain segment were not strictly increasing. */
|
|
44
49
|
NON_LINEAR_SLOTS = "BLOCK_ERROR_NON_LINEAR_SLOTS",
|
|
@@ -61,16 +66,32 @@ export enum BlockErrorCode {
|
|
|
61
66
|
TRANSACTIONS_TOO_BIG = "BLOCK_ERROR_TRANSACTIONS_TOO_BIG",
|
|
62
67
|
/** Execution engine is unavailable, syncing, or api call errored. Peers must not be downscored on this code */
|
|
63
68
|
EXECUTION_ENGINE_ERROR = "BLOCK_ERROR_EXECUTION_ERROR",
|
|
69
|
+
/**
|
|
70
|
+
* Execution engine returned a definitive INVALID verdict on the payload. Unlike
|
|
71
|
+
* EXECUTION_ENGINE_ERROR this is evidence about the data itself, not a local malfunction, so
|
|
72
|
+
* whoever served the block MAY be downscored. Mirrors PayloadErrorCode.EXECUTION_ENGINE_INVALID.
|
|
73
|
+
*/
|
|
74
|
+
EXECUTION_ENGINE_INVALID = "BLOCK_ERROR_EXECUTION_INVALID",
|
|
64
75
|
/** The blobs are unavailable */
|
|
65
76
|
DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
|
|
66
77
|
/** Block contains too many kzg commitments */
|
|
67
78
|
TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
|
|
68
79
|
/** Bid parent block root does not match block parent root */
|
|
69
80
|
BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
|
|
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",
|
|
70
83
|
/** The parent block's execution payload has been verified as invalid */
|
|
71
84
|
PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
|
|
72
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* [gloas] The first block of a chain segment references a parent execution payload
|
|
87
|
+
* (bid.parent_block_hash) unknown to fork-choice (segment boundary).
|
|
88
|
+
*/
|
|
73
89
|
PARENT_PAYLOAD_UNKNOWN = "BLOCK_ERROR_PARENT_PAYLOAD_UNKNOWN",
|
|
90
|
+
/**
|
|
91
|
+
* [gloas] A block in the middle of a chain segment has a bid.parent_block_hash that does not chain onto the
|
|
92
|
+
* previous block's execution payload (broken payload link inside the segment).
|
|
93
|
+
*/
|
|
94
|
+
NON_LINEAR_PAYLOAD_ROOTS = "BLOCK_ERROR_NON_LINEAR_PAYLOAD_ROOTS",
|
|
74
95
|
/** An execution payload envelope in the chain segment references a block root that does not match its slot's block */
|
|
75
96
|
ENVELOPE_BLOCK_ROOT_MISMATCH = "BLOCK_ERROR_ENVELOPE_BLOCK_ROOT_MISMATCH",
|
|
76
97
|
}
|
|
@@ -80,9 +101,18 @@ type ExecutionErrorStatus = Exclude<
|
|
|
80
101
|
ExecutionPayloadStatus.VALID | ExecutionPayloadStatus.ACCEPTED | ExecutionPayloadStatus.SYNCING
|
|
81
102
|
>;
|
|
82
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Statuses where the execution engine could NOT render a verdict on the payload: it is unreachable
|
|
106
|
+
* (UNAVAILABLE), errored (ELERROR), or returned a malformed/client-specific response
|
|
107
|
+
* (INVALID_BLOCK_HASH, dropped from engine_newPayloadV2+). None of these are evidence about the
|
|
108
|
+
* data, so peers must not be downscored on them. A definitive INVALID carries
|
|
109
|
+
* BlockErrorCode.EXECUTION_ENGINE_INVALID instead.
|
|
110
|
+
*/
|
|
111
|
+
type ExecutionEngineErrorStatus = Exclude<ExecutionErrorStatus, ExecutionPayloadStatus.INVALID>;
|
|
112
|
+
|
|
83
113
|
export type BlockErrorType =
|
|
84
114
|
| {code: BlockErrorCode.PRESTATE_MISSING; error: Error}
|
|
85
|
-
| {code: BlockErrorCode.
|
|
115
|
+
| {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN; parentRoot: RootHex}
|
|
86
116
|
| {code: BlockErrorCode.FUTURE_SLOT; blockSlot: Slot; currentSlot: Slot}
|
|
87
117
|
| {code: BlockErrorCode.STATE_ROOT_MISMATCH}
|
|
88
118
|
| {code: BlockErrorCode.GENESIS_BLOCK}
|
|
@@ -114,12 +144,19 @@ export type BlockErrorType =
|
|
|
114
144
|
| {code: BlockErrorCode.TOO_MUCH_GAS_USED; gasUsed: number; gasLimit: number}
|
|
115
145
|
| {code: BlockErrorCode.SAME_PARENT_HASH; blockHash: RootHex}
|
|
116
146
|
| {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number}
|
|
117
|
-
| {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus:
|
|
147
|
+
| {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionEngineErrorStatus; errorMessage: string}
|
|
148
|
+
| {
|
|
149
|
+
code: BlockErrorCode.EXECUTION_ENGINE_INVALID;
|
|
150
|
+
execStatus: ExecutionPayloadStatus.INVALID;
|
|
151
|
+
errorMessage: string;
|
|
152
|
+
}
|
|
118
153
|
| {code: BlockErrorCode.DATA_UNAVAILABLE}
|
|
119
154
|
| {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
|
|
120
155
|
| {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
|
|
156
|
+
| {code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS; name: string; count: number; limit: number}
|
|
121
157
|
| {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
|
|
122
|
-
| {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex}
|
|
158
|
+
| {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex}
|
|
159
|
+
| {code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS; parentBlockHash: RootHex; expectedBlockHash: RootHex};
|
|
123
160
|
|
|
124
161
|
export class BlockGossipError extends GossipActionError<BlockErrorType> {}
|
|
125
162
|
|
|
@@ -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
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
1
|
+
import {Epoch, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
2
2
|
import {GossipActionError} from "./gossipValidation.js";
|
|
3
3
|
|
|
4
4
|
export enum ProposerPreferencesErrorCode {
|
|
5
5
|
INVALID_EPOCH = "PROPOSER_PREFERENCES_ERROR_INVALID_EPOCH",
|
|
6
6
|
PROPOSAL_SLOT_PASSED = "PROPOSER_PREFERENCES_ERROR_PROPOSAL_SLOT_PASSED",
|
|
7
7
|
UNKNOWN_DEPENDENT_ROOT = "PROPOSER_PREFERENCES_ERROR_UNKNOWN_DEPENDENT_ROOT",
|
|
8
|
+
INVALID_DEPENDENT_ROOT_SLOT = "PROPOSER_PREFERENCES_ERROR_INVALID_DEPENDENT_ROOT_SLOT",
|
|
9
|
+
INVALID_DEPENDENT_ROOT = "PROPOSER_PREFERENCES_ERROR_INVALID_DEPENDENT_ROOT",
|
|
8
10
|
INVALID_PROPOSER = "PROPOSER_PREFERENCES_ERROR_INVALID_PROPOSER",
|
|
9
11
|
ALREADY_KNOWN = "PROPOSER_PREFERENCES_ERROR_ALREADY_KNOWN",
|
|
10
12
|
INVALID_SIGNATURE = "PROPOSER_PREFERENCES_ERROR_INVALID_SIGNATURE",
|
|
@@ -26,6 +28,19 @@ export type ProposerPreferencesErrorType =
|
|
|
26
28
|
proposalSlot: Slot;
|
|
27
29
|
dependentRoot: RootHex;
|
|
28
30
|
}
|
|
31
|
+
| {
|
|
32
|
+
code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT_SLOT;
|
|
33
|
+
dependentRoot: RootHex;
|
|
34
|
+
dependentBlockSlot: Slot;
|
|
35
|
+
dependentRootSlot: Slot;
|
|
36
|
+
dependentEpoch: Epoch;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT;
|
|
40
|
+
dependentRoot: RootHex;
|
|
41
|
+
dependentBlockSlot: Slot;
|
|
42
|
+
dependentEpoch: Epoch;
|
|
43
|
+
}
|
|
29
44
|
| {
|
|
30
45
|
code: ProposerPreferencesErrorCode.INVALID_PROPOSER;
|
|
31
46
|
proposalSlot: Slot;
|
|
@@ -10,9 +10,8 @@ import {
|
|
|
10
10
|
MIN_SYNC_COMMITTEE_PARTICIPANTS,
|
|
11
11
|
SLOTS_PER_EPOCH,
|
|
12
12
|
SYNC_COMMITTEE_SIZE,
|
|
13
|
-
forkPostAltair,
|
|
14
|
-
highestFork,
|
|
15
13
|
isForkPostElectra,
|
|
14
|
+
isForkPostGloas,
|
|
16
15
|
} from "@lodestar/params";
|
|
17
16
|
import {
|
|
18
17
|
type IBeaconStateViewAltair,
|
|
@@ -44,7 +43,6 @@ import {
|
|
|
44
43
|
electra,
|
|
45
44
|
phase0,
|
|
46
45
|
ssz,
|
|
47
|
-
sszTypesFor,
|
|
48
46
|
} from "@lodestar/types";
|
|
49
47
|
import {Logger, MapDef, byteArrayEquals, pruneSetToMax, toRootHex} from "@lodestar/utils";
|
|
50
48
|
import {ZERO_HASH} from "../../constants/index.js";
|
|
@@ -230,8 +228,8 @@ export class LightClientServer {
|
|
|
230
228
|
this.signal = signal;
|
|
231
229
|
|
|
232
230
|
this.zero = {
|
|
233
|
-
// Assign the
|
|
234
|
-
finalizedHeader:
|
|
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(),
|
|
235
233
|
// Electra finalityBranch has fixed length of 5 whereas altair has 4. The fifth element will be ignored
|
|
236
234
|
// when serializing as altair LightClientUpdate
|
|
237
235
|
finalityBranch: ssz.electra.LightClientUpdate.fields.finalityBranch.defaultValue(),
|
|
@@ -658,10 +656,17 @@ export class LightClientServer {
|
|
|
658
656
|
|
|
659
657
|
const attestedFork = this.config.getForkName(attestedHeader.beacon.slot);
|
|
660
658
|
const numWitness = syncCommitteeWitness.witness.length;
|
|
661
|
-
if (
|
|
659
|
+
if (
|
|
660
|
+
isForkPostGloas(attestedFork) &&
|
|
661
|
+
(syncCommitteeWitness.currentSyncCommitteeBranch === undefined ||
|
|
662
|
+
syncCommitteeWitness.nextSyncCommitteeBranch === undefined)
|
|
663
|
+
) {
|
|
664
|
+
throw Error("Expected post-Gloas sync committee branches");
|
|
665
|
+
}
|
|
666
|
+
if (!isForkPostGloas(attestedFork) && isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS_ELECTRA) {
|
|
662
667
|
throw Error(`Expected ${NUM_WITNESS_ELECTRA} witnesses in post-Electra numWitness=${numWitness}`);
|
|
663
668
|
}
|
|
664
|
-
if (!isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS) {
|
|
669
|
+
if (!isForkPostGloas(attestedFork) && !isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS) {
|
|
665
670
|
throw Error(`Expected ${NUM_WITNESS} witnesses in pre-Electra numWitness=${numWitness}`);
|
|
666
671
|
}
|
|
667
672
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {Tree} from "@chainsafe/persistent-merkle-tree";
|
|
2
2
|
import {
|
|
3
3
|
BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
|
|
4
|
+
CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS,
|
|
4
5
|
FINALIZED_ROOT_GINDEX,
|
|
5
6
|
FINALIZED_ROOT_GINDEX_ELECTRA,
|
|
7
|
+
FINALIZED_ROOT_GINDEX_GLOAS,
|
|
6
8
|
ForkName,
|
|
7
9
|
ForkPostBellatrix,
|
|
10
|
+
NEXT_SYNC_COMMITTEE_GINDEX_GLOAS,
|
|
8
11
|
isForkPostElectra,
|
|
12
|
+
isForkPostGloas,
|
|
9
13
|
} from "@lodestar/params";
|
|
10
14
|
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "@lodestar/state-transition";
|
|
11
15
|
import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types";
|
|
@@ -17,6 +21,24 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
|
|
|
17
21
|
let currentSyncCommitteeRoot: Uint8Array;
|
|
18
22
|
let nextSyncCommitteeRoot: Uint8Array;
|
|
19
23
|
|
|
24
|
+
if (isForkPostGloas(fork)) {
|
|
25
|
+
const tree = new Tree(state.node);
|
|
26
|
+
const currentSyncCommitteeGindex = BigInt(CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS);
|
|
27
|
+
const nextSyncCommitteeGindex = BigInt(NEXT_SYNC_COMMITTEE_GINDEX_GLOAS);
|
|
28
|
+
|
|
29
|
+
currentSyncCommitteeRoot = tree.getRoot(currentSyncCommitteeGindex);
|
|
30
|
+
nextSyncCommitteeRoot = tree.getRoot(nextSyncCommitteeGindex);
|
|
31
|
+
witness = [];
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
witness,
|
|
35
|
+
currentSyncCommitteeRoot,
|
|
36
|
+
nextSyncCommitteeRoot,
|
|
37
|
+
currentSyncCommitteeBranch: tree.getSingleProof(currentSyncCommitteeGindex),
|
|
38
|
+
nextSyncCommitteeBranch: tree.getSingleProof(nextSyncCommitteeGindex),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
20
42
|
if (isForkPostElectra(fork)) {
|
|
21
43
|
const n2 = n1.left;
|
|
22
44
|
const n5 = n2.right;
|
|
@@ -60,17 +82,30 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
|
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
export function getNextSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
|
|
85
|
+
if (syncCommitteesWitness.nextSyncCommitteeBranch) {
|
|
86
|
+
return syncCommitteesWitness.nextSyncCommitteeBranch;
|
|
87
|
+
}
|
|
88
|
+
|
|
63
89
|
// Witness branch is sorted by descending gindex
|
|
64
90
|
return [syncCommitteesWitness.currentSyncCommitteeRoot, ...syncCommitteesWitness.witness];
|
|
65
91
|
}
|
|
66
92
|
|
|
67
93
|
export function getCurrentSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
|
|
94
|
+
if (syncCommitteesWitness.currentSyncCommitteeBranch) {
|
|
95
|
+
return syncCommitteesWitness.currentSyncCommitteeBranch;
|
|
96
|
+
}
|
|
97
|
+
|
|
68
98
|
// Witness branch is sorted by descending gindex
|
|
69
99
|
return [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness];
|
|
70
100
|
}
|
|
71
101
|
|
|
72
102
|
export function getFinalizedRootProof(state: CachedBeaconStateAllForks): Uint8Array[] {
|
|
73
|
-
const
|
|
103
|
+
const fork = state.config.getForkName(state.slot);
|
|
104
|
+
const finalizedRootGindex = isForkPostGloas(fork)
|
|
105
|
+
? FINALIZED_ROOT_GINDEX_GLOAS
|
|
106
|
+
: state.epochCtx.isPostElectra()
|
|
107
|
+
? FINALIZED_ROOT_GINDEX_ELECTRA
|
|
108
|
+
: FINALIZED_ROOT_GINDEX;
|
|
74
109
|
return new Tree(state.node).getSingleProof(BigInt(finalizedRootGindex));
|
|
75
110
|
}
|
|
76
111
|
|
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export type SyncCommitteeWitness = {
|
|
29
|
-
/**
|
|
29
|
+
/** Shared witness for pre-Gloas forks where current and next sync committees are siblings. */
|
|
30
30
|
witness: Uint8Array[];
|
|
31
31
|
currentSyncCommitteeRoot: Uint8Array;
|
|
32
32
|
nextSyncCommitteeRoot: Uint8Array;
|
|
33
|
+
currentSyncCommitteeBranch?: Uint8Array[];
|
|
34
|
+
nextSyncCommitteeBranch?: Uint8Array[];
|
|
33
35
|
};
|
|
@@ -355,7 +355,7 @@ export class AggregatedAttestationPool {
|
|
|
355
355
|
// after all committees are processed, we have a list of sameAttDataCons
|
|
356
356
|
for (const consolidation of sameAttDataCons) {
|
|
357
357
|
// Score attestations by profitability to maximize proposer reward
|
|
358
|
-
const flags = getAttestationParticipationStatus(
|
|
358
|
+
const {flags} = getAttestationParticipationStatus(
|
|
359
359
|
ForkSeq[fork],
|
|
360
360
|
consolidation.attData,
|
|
361
361
|
inclusionDistance,
|
|
@@ -17,7 +17,7 @@ import {toRootHex} from "@lodestar/utils";
|
|
|
17
17
|
export class ProposerPreferencesPool {
|
|
18
18
|
private readonly bySlot = new Map<Slot, Map<RootHex, gloas.SignedProposerPreferences>>();
|
|
19
19
|
|
|
20
|
-
/** Lookup for bid validation: matches `(bid.slot,
|
|
20
|
+
/** Lookup for bid validation: matches `(bid.slot, get_shuffling_dependent_root(store, bid.parent_block_root, epoch))`. */
|
|
21
21
|
get(slot: Slot, dependentRootHex: RootHex): gloas.SignedProposerPreferences | null {
|
|
22
22
|
return this.bySlot.get(slot)?.get(dependentRootHex) ?? null;
|
|
23
23
|
}
|
|
@@ -90,13 +90,19 @@ async function validateAggregateAndProof(
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
// [REJECT] If `aggregate.data.index == 1` (payload present for a past
|
|
94
|
-
//
|
|
93
|
+
// [REJECT] If `aggregate.data.index == 1` (payload present for a past block)
|
|
94
|
+
// the corresponding execution payload for `block` passes validation.
|
|
95
95
|
// [IGNORE] When `aggregate.data.index == 1` (payload present for a past block),
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// payload
|
|
99
|
-
|
|
96
|
+
// the corresponding execution payload for `block` has been fully imported, including its
|
|
97
|
+
// data -- i.e. `is_payload_verified(store, aggregate.data.beacon_block_root)` returns `True`
|
|
98
|
+
// (a client MAY queue attestations for processing until the payload is imported and SHOULD
|
|
99
|
+
// request the payload envelope via `ExecutionPayloadEnvelopesByRoot` using
|
|
100
|
+
// `aggregate.data.beacon_block_root`).
|
|
101
|
+
if (
|
|
102
|
+
block !== null &&
|
|
103
|
+
attData.index === 1 &&
|
|
104
|
+
!chain.forkChoice.hasPayloadHexUnsafe(toRootHex(attData.beaconBlockRoot))
|
|
105
|
+
) {
|
|
100
106
|
throw new AttestationError(GossipAction.IGNORE, {
|
|
101
107
|
code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN,
|
|
102
108
|
beaconBlockRoot: toRootHex(attData.beaconBlockRoot),
|
|
@@ -316,13 +316,19 @@ async function validateAttestationNoSignatureCheck(
|
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
// [REJECT] If `attestation.data.index == 1` (payload present for a past
|
|
320
|
-
//
|
|
319
|
+
// [REJECT] If `attestation.data.index == 1` (payload present for a past block),
|
|
320
|
+
// the execution payload for `block` passes validation.
|
|
321
321
|
// [IGNORE] When `attestation.data.index == 1` (payload present for a past block),
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
// payload
|
|
325
|
-
|
|
322
|
+
// the execution payload for `block` has been fully imported, including its data -- i.e.
|
|
323
|
+
// `is_payload_verified(store, attestation.data.beacon_block_root)` returns `True`
|
|
324
|
+
// (a client MAY queue attestations for processing until the payload is imported and
|
|
325
|
+
// SHOULD request the payload envelope via `ExecutionPayloadEnvelopesByRoot` using
|
|
326
|
+
// `attestation.data.beacon_block_root`).
|
|
327
|
+
if (
|
|
328
|
+
block !== null &&
|
|
329
|
+
attData.index === 1 &&
|
|
330
|
+
!chain.forkChoice.hasPayloadHexUnsafe(toRootHex(attData.beaconBlockRoot))
|
|
331
|
+
) {
|
|
326
332
|
throw new AttestationError(GossipAction.IGNORE, {
|
|
327
333
|
code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN,
|
|
328
334
|
beaconBlockRoot: toRootHex(attData.beaconBlockRoot),
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
2
|
import {ExecutionStatus} from "@lodestar/fork-choice";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ForkName,
|
|
5
|
+
MAX_ATTESTATIONS_ELECTRA,
|
|
6
|
+
MAX_ATTESTER_SLASHINGS_ELECTRA,
|
|
7
|
+
MAX_BLS_TO_EXECUTION_CHANGES,
|
|
8
|
+
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
9
|
+
MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
|
|
10
|
+
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
|
|
11
|
+
MAX_PAYLOAD_ATTESTATIONS,
|
|
12
|
+
MAX_PROPOSER_SLASHINGS,
|
|
13
|
+
MAX_VOLUNTARY_EXITS,
|
|
14
|
+
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
|
|
15
|
+
isForkPostBellatrix,
|
|
16
|
+
isForkPostDeneb,
|
|
17
|
+
isForkPostGloas,
|
|
18
|
+
} from "@lodestar/params";
|
|
4
19
|
import {
|
|
5
20
|
computeEpochAtSlot,
|
|
6
21
|
computeStartSlotAtEpoch,
|
|
@@ -88,8 +103,8 @@ export async function validateGossipBlock(
|
|
|
88
103
|
// 2. The parent is unknown to us, we probably want to download it since it might actually
|
|
89
104
|
// descend from the finalized root.
|
|
90
105
|
// (Non-Lighthouse): Since we prune all blocks non-descendant from finalized checking the `db.block` database won't be useful to guard
|
|
91
|
-
// against known bad fork blocks, so we throw
|
|
92
|
-
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.
|
|
106
|
+
// against known bad fork blocks, so we throw PARENT_BLOCK_UNKNOWN for cases (1) and (2)
|
|
107
|
+
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
// [IGNORE] The block's parent (defined by `block.parent_root`) passes all validation
|
|
@@ -164,6 +179,56 @@ export async function validateGossipBlock(
|
|
|
164
179
|
});
|
|
165
180
|
}
|
|
166
181
|
|
|
182
|
+
// [REJECT] The counts of `block.body.parent_execution_requests` are within
|
|
183
|
+
// their respective limits -- i.e. validate that
|
|
184
|
+
// `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
|
|
185
|
+
// `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
|
|
186
|
+
// `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
|
|
187
|
+
// and
|
|
188
|
+
// `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
|
|
189
|
+
// [REJECT] The counts of the block body operations are within their respective
|
|
190
|
+
// limits -- i.e. validate that
|
|
191
|
+
// `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
|
|
192
|
+
// `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
|
|
193
|
+
// `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
|
|
194
|
+
// `len(block.body.deposits) == 0`,
|
|
195
|
+
// `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
|
|
196
|
+
// `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
|
|
197
|
+
// and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
|
|
198
|
+
const body = (block as gloas.BeaconBlock).body;
|
|
199
|
+
const requests = body.parentExecutionRequests;
|
|
200
|
+
const countLimits: [string, number, number][] = [
|
|
201
|
+
["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
|
|
202
|
+
[
|
|
203
|
+
"parentExecutionRequests.consolidations",
|
|
204
|
+
requests.consolidations.length,
|
|
205
|
+
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
|
|
206
|
+
],
|
|
207
|
+
[
|
|
208
|
+
"parentExecutionRequests.builderDeposits",
|
|
209
|
+
requests.builderDeposits.length,
|
|
210
|
+
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
211
|
+
],
|
|
212
|
+
["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
|
|
213
|
+
["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
|
|
214
|
+
["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
|
|
215
|
+
["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
|
|
216
|
+
["deposits", body.deposits.length, 0],
|
|
217
|
+
["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
|
|
218
|
+
["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
|
|
219
|
+
["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
|
|
220
|
+
];
|
|
221
|
+
for (const [name, count, limit] of countLimits) {
|
|
222
|
+
if (count > limit) {
|
|
223
|
+
throw new BlockGossipError(GossipAction.REJECT, {
|
|
224
|
+
code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
|
|
225
|
+
name,
|
|
226
|
+
count,
|
|
227
|
+
limit,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
167
232
|
// TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
|
|
168
233
|
// This requires execution engine integration to verify the parent block hash
|
|
169
234
|
}
|
|
@@ -176,7 +241,7 @@ export async function validateGossipBlock(
|
|
|
176
241
|
const blockState = await chain.regen
|
|
177
242
|
.getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
|
|
178
243
|
.catch(() => {
|
|
179
|
-
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.
|
|
244
|
+
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
|
|
180
245
|
});
|
|
181
246
|
|
|
182
247
|
// in forky condition, make sure to populate ShufflingCache with regened state
|
|
@@ -165,9 +165,9 @@ async function validateExecutionPayloadBid(
|
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
// [
|
|
168
|
+
// [IGNORE] `bid.fee_recipient == proposer_preferences.fee_recipient`.
|
|
169
169
|
if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
|
|
170
|
-
throw new ExecutionPayloadBidError(GossipAction.
|
|
170
|
+
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
171
171
|
code: ExecutionPayloadBidErrorCode.PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH,
|
|
172
172
|
builderIndex: bid.builderIndex,
|
|
173
173
|
bidFeeRecipient: toHex(bid.feeRecipient),
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import {PayloadStatus} from "@lodestar/fork-choice";
|
|
2
|
+
import {
|
|
3
|
+
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
|
|
4
|
+
MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
|
|
5
|
+
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
|
|
6
|
+
MAX_WITHDRAWALS_PER_PAYLOAD,
|
|
7
|
+
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
|
|
8
|
+
} from "@lodestar/params";
|
|
2
9
|
import {
|
|
3
10
|
computeStartSlotAtEpoch,
|
|
4
11
|
getExecutionPayloadEnvelopeSignatureSet,
|
|
@@ -115,6 +122,36 @@ async function validateExecutionPayloadEnvelope(
|
|
|
115
122
|
});
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
// [REJECT] The counts of `execution_requests` are within their respective limits.
|
|
126
|
+
// New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
|
|
127
|
+
// are enforced here in gossip validation.
|
|
128
|
+
const {executionRequests} = envelope;
|
|
129
|
+
const requestCountLimits: [string, number, number][] = [
|
|
130
|
+
["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
|
|
131
|
+
["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
|
|
132
|
+
["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
|
|
133
|
+
["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
|
|
134
|
+
];
|
|
135
|
+
for (const [name, count, limit] of requestCountLimits) {
|
|
136
|
+
if (count > limit) {
|
|
137
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
138
|
+
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
|
|
139
|
+
name,
|
|
140
|
+
count,
|
|
141
|
+
limit,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// [REJECT] The number of withdrawals is within the limit.
|
|
147
|
+
if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
|
|
148
|
+
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
|
|
149
|
+
code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
|
|
150
|
+
count: payload.withdrawals.length,
|
|
151
|
+
limit: MAX_WITHDRAWALS_PER_PAYLOAD,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
118
155
|
// Get the block state to verify the builder's signature.
|
|
119
156
|
const blockState = await chain.regen
|
|
120
157
|
.getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
|
|
2
|
+
import {GENESIS_SLOT, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
3
|
import {
|
|
3
4
|
computeEpochAtSlot,
|
|
5
|
+
computeStartSlotAtEpoch,
|
|
4
6
|
createSingleSignatureSetFromComponents,
|
|
5
7
|
getProposerPreferencesSigningRoot,
|
|
6
8
|
} from "@lodestar/state-transition";
|
|
7
|
-
import {ValidatorIndex, gloas} from "@lodestar/types";
|
|
9
|
+
import {Epoch, Slot, ValidatorIndex, gloas} from "@lodestar/types";
|
|
8
10
|
import {toRootHex} from "@lodestar/utils";
|
|
9
11
|
import {GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode} from "../errors/index.js";
|
|
10
12
|
import {IBeaconChain} from "../index.js";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Validates a gossiped `SignedProposerPreferences` per
|
|
14
|
-
* https://github.com/ethereum/consensus-specs/blob/
|
|
16
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/gloas/p2p-interface.md#new-proposer_preferences
|
|
15
17
|
*/
|
|
16
18
|
export async function validateGossipProposerPreferences(
|
|
17
19
|
chain: IBeaconChain,
|
|
@@ -22,9 +24,9 @@ export async function validateGossipProposerPreferences(
|
|
|
22
24
|
const dependentRootHex = toRootHex(dependentRoot);
|
|
23
25
|
const proposalEpoch = computeEpochAtSlot(proposalSlot);
|
|
24
26
|
|
|
25
|
-
// [IGNORE] `preferences.proposal_slot` is
|
|
27
|
+
// [IGNORE] `preferences.proposal_slot` is within the proposer lookahead.
|
|
26
28
|
const currentEpoch = chain.clock.currentEpoch;
|
|
27
|
-
if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch +
|
|
29
|
+
if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch + MIN_SEED_LOOKAHEAD) {
|
|
28
30
|
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
29
31
|
code: ProposerPreferencesErrorCode.INVALID_EPOCH,
|
|
30
32
|
proposalSlot,
|
|
@@ -44,21 +46,56 @@ export async function validateGossipProposerPreferences(
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
// [IGNORE] The block with root `dependent_root` has been seen by the node.
|
|
49
|
+
const dependentBlock = chain.forkChoice.getBlockHexDefaultStatus(dependentRootHex);
|
|
50
|
+
if (dependentBlock === null) {
|
|
51
|
+
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
52
|
+
code: ProposerPreferencesErrorCode.UNKNOWN_DEPENDENT_ROOT,
|
|
53
|
+
proposalSlot,
|
|
54
|
+
dependentRoot: dependentRootHex,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dependentEpoch = proposalEpoch - MIN_SEED_LOOKAHEAD;
|
|
59
|
+
const dependentRootSlot = getDependentRootSlot(dependentEpoch);
|
|
60
|
+
|
|
61
|
+
// [REJECT] The slot of the block with root `preferences.dependent_root` is strictly less than
|
|
62
|
+
// `compute_start_slot_at_epoch(compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD)`.
|
|
63
|
+
if (dependentBlock.slot > dependentRootSlot) {
|
|
64
|
+
throw new ProposerPreferencesError(GossipAction.REJECT, {
|
|
65
|
+
code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT_SLOT,
|
|
66
|
+
dependentRoot: dependentRootHex,
|
|
67
|
+
dependentBlockSlot: dependentBlock.slot,
|
|
68
|
+
dependentRootSlot,
|
|
69
|
+
dependentEpoch,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// [IGNORE] `is_valid_dependent_root(store, preferences.dependent_root, epoch)` returns `True`,
|
|
74
|
+
// where `epoch` is `compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD`.
|
|
75
|
+
if (!isValidDependentRoot(chain.forkChoice, dependentBlock, dependentEpoch)) {
|
|
76
|
+
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
77
|
+
code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT,
|
|
78
|
+
dependentRoot: dependentRootHex,
|
|
79
|
+
dependentBlockSlot: dependentBlock.slot,
|
|
80
|
+
dependentEpoch,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
// Resolve the proposer lookahead for the message's branch via head state (fast path) or
|
|
48
85
|
// the previous-root checkpoint state (populated by `processSlotsToNearestCheckpoint` for
|
|
49
|
-
// any imported branch crossing into `
|
|
86
|
+
// any imported branch crossing into `dependentEpoch`). The head-state path also handles
|
|
50
87
|
// narrow timing windows where the checkpoint state isn't yet populated.
|
|
51
88
|
const headState = chain.getHeadState();
|
|
52
89
|
let proposers: ValidatorIndex[] | null = null;
|
|
53
90
|
if (headState.epoch === proposalEpoch && headState.currentDecisionRoot === dependentRootHex) {
|
|
54
91
|
proposers = headState.currentProposers;
|
|
55
|
-
} else if (headState.epoch ===
|
|
92
|
+
} else if (headState.epoch === dependentEpoch && headState.nextDecisionRoot === dependentRootHex) {
|
|
56
93
|
proposers = headState.nextProposers;
|
|
57
94
|
} else {
|
|
58
95
|
// Sync lookup only to not trigger disk reload from gossip input.
|
|
59
|
-
const checkpointState = chain.regen.getCheckpointStateSync({epoch:
|
|
96
|
+
const checkpointState = chain.regen.getCheckpointStateSync({epoch: dependentEpoch, rootHex: dependentRootHex});
|
|
60
97
|
if (checkpointState !== null) {
|
|
61
|
-
// State is at `
|
|
98
|
+
// State is at `dependentEpoch`, so proposers for `proposalSlot` (next epoch from
|
|
62
99
|
// the state's perspective) live in `nextProposers`.
|
|
63
100
|
proposers = checkpointState.nextProposers;
|
|
64
101
|
}
|
|
@@ -118,3 +155,28 @@ export async function validateGossipProposerPreferences(
|
|
|
118
155
|
|
|
119
156
|
chain.proposerPreferencesPool.add(signedProposerPreferences);
|
|
120
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check whether `dependentBlock` is a viable shuffling-dependent root for `epoch`:
|
|
161
|
+
* it is before the epoch boundary and either has a direct child at or after
|
|
162
|
+
* the boundary, or is the current fork-choice head.
|
|
163
|
+
*/
|
|
164
|
+
export function isValidDependentRoot(forkChoice: IForkChoice, dependentBlock: ProtoBlock, epoch: Epoch): boolean {
|
|
165
|
+
const epochStartSlot = computeStartSlotAtEpoch(epoch);
|
|
166
|
+
if (dependentBlock.slot > getDependentRootSlot(epoch)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const block of forkChoice.getBlockSummariesByParentRoot(dependentBlock.blockRoot)) {
|
|
171
|
+
if (block.slot >= epochStartSlot) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return dependentBlock.blockRoot === forkChoice.getHeadRoot();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Return the slot used to select a dependent root for `epoch`, clamped to genesis. */
|
|
180
|
+
function getDependentRootSlot(epoch: Epoch): Slot {
|
|
181
|
+
return Math.max(GENESIS_SLOT, computeStartSlotAtEpoch(epoch) - 1);
|
|
182
|
+
}
|