@lodestar/beacon-node 1.45.0-dev.0cff4e6916 → 1.45.0-dev.246e0ddef4
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/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +2 -42
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +46 -3
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +11 -2
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +39 -3
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +37 -3
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +4 -12
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -10
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/network/core/metrics.d.ts +65 -0
- package/lib/network/core/metrics.d.ts.map +1 -1
- package/lib/network/core/metrics.js +94 -0
- package/lib/network/core/metrics.js.map +1 -1
- package/lib/network/discv5/utils.d.ts.map +1 -1
- package/lib/network/discv5/utils.js +12 -3
- package/lib/network/discv5/utils.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +7 -0
- 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 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +6 -1
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +23 -3
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +10 -0
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/score/score.d.ts +3 -1
- package/lib/network/peers/score/score.d.ts.map +1 -1
- package/lib/network/peers/score/score.js +6 -1
- package/lib/network/peers/score/score.js.map +1 -1
- package/lib/network/peers/score/store.d.ts.map +1 -1
- package/lib/network/peers/score/store.js +6 -1
- package/lib/network/peers/score/store.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.js +14 -0
- package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
- package/lib/network/processor/gossipHandlers.js +2 -2
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/pool/index.ts +0 -52
- package/src/api/impl/beacon/state/index.ts +52 -1
- package/src/api/impl/beacon/state/utils.ts +57 -4
- package/src/api/impl/validator/index.ts +45 -2
- package/src/chain/produceBlock/produceBlockBody.ts +9 -17
- package/src/chain/validation/executionPayloadBid.ts +14 -9
- package/src/network/core/metrics.ts +94 -0
- package/src/network/discv5/utils.ts +11 -3
- package/src/network/gossip/encoding.ts +7 -0
- package/src/network/gossip/gossipsub.ts +2 -1
- package/src/network/gossip/topic.ts +28 -4
- package/src/network/peers/peerManager.ts +11 -0
- package/src/network/peers/score/score.ts +8 -1
- package/src/network/peers/score/store.ts +8 -1
- package/src/network/peers/utils/prioritizePeers.ts +15 -0
- package/src/network/processor/gossipHandlers.ts +1 -1
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/ChainSafe/lodestar/issues"
|
|
13
13
|
},
|
|
14
|
-
"version": "1.45.0-dev.
|
|
14
|
+
"version": "1.45.0-dev.246e0ddef4",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
@@ -134,17 +134,17 @@
|
|
|
134
134
|
"@libp2p/peer-id": "^6.0.4",
|
|
135
135
|
"@libp2p/prometheus-metrics": "^5.0.14",
|
|
136
136
|
"@libp2p/tcp": "^11.0.13",
|
|
137
|
-
"@lodestar/api": "^1.45.0-dev.
|
|
138
|
-
"@lodestar/config": "^1.45.0-dev.
|
|
139
|
-
"@lodestar/db": "^1.45.0-dev.
|
|
140
|
-
"@lodestar/fork-choice": "^1.45.0-dev.
|
|
141
|
-
"@lodestar/logger": "^1.45.0-dev.
|
|
142
|
-
"@lodestar/params": "^1.45.0-dev.
|
|
143
|
-
"@lodestar/reqresp": "^1.45.0-dev.
|
|
144
|
-
"@lodestar/state-transition": "^1.45.0-dev.
|
|
145
|
-
"@lodestar/types": "^1.45.0-dev.
|
|
146
|
-
"@lodestar/utils": "^1.45.0-dev.
|
|
147
|
-
"@lodestar/validator": "^1.45.0-dev.
|
|
137
|
+
"@lodestar/api": "^1.45.0-dev.246e0ddef4",
|
|
138
|
+
"@lodestar/config": "^1.45.0-dev.246e0ddef4",
|
|
139
|
+
"@lodestar/db": "^1.45.0-dev.246e0ddef4",
|
|
140
|
+
"@lodestar/fork-choice": "^1.45.0-dev.246e0ddef4",
|
|
141
|
+
"@lodestar/logger": "^1.45.0-dev.246e0ddef4",
|
|
142
|
+
"@lodestar/params": "^1.45.0-dev.246e0ddef4",
|
|
143
|
+
"@lodestar/reqresp": "^1.45.0-dev.246e0ddef4",
|
|
144
|
+
"@lodestar/state-transition": "^1.45.0-dev.246e0ddef4",
|
|
145
|
+
"@lodestar/types": "^1.45.0-dev.246e0ddef4",
|
|
146
|
+
"@lodestar/utils": "^1.45.0-dev.246e0ddef4",
|
|
147
|
+
"@lodestar/validator": "^1.45.0-dev.246e0ddef4",
|
|
148
148
|
"@multiformats/multiaddr": "^13.0.1",
|
|
149
149
|
"datastore-core": "^11.0.2",
|
|
150
150
|
"datastore-fs": "^11.0.2",
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"@libp2p/interface-internal": "^3.0.13",
|
|
168
168
|
"@libp2p/logger": "^6.2.2",
|
|
169
169
|
"@libp2p/utils": "^7.0.13",
|
|
170
|
-
"@lodestar/spec-test-util": "^1.45.0-dev.
|
|
170
|
+
"@lodestar/spec-test-util": "^1.45.0-dev.246e0ddef4",
|
|
171
171
|
"@types/js-yaml": "^4.0.5",
|
|
172
172
|
"@types/qs": "^6.9.7",
|
|
173
173
|
"@types/tmp": "^0.2.3",
|
|
@@ -184,5 +184,5 @@
|
|
|
184
184
|
"beacon",
|
|
185
185
|
"blockchain"
|
|
186
186
|
],
|
|
187
|
-
"gitHead": "
|
|
187
|
+
"gitHead": "3b38b2746a5568e5809a81fbe2c835f1adb0b315"
|
|
188
188
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {ApplicationMethods} from "@lodestar/api/server";
|
|
3
3
|
import {
|
|
4
|
-
ForkName,
|
|
5
4
|
ForkPostElectra,
|
|
6
5
|
ForkPreElectra,
|
|
7
6
|
SYNC_COMMITTEE_SUBNET_SIZE,
|
|
@@ -17,15 +16,12 @@ import {
|
|
|
17
16
|
GossipAction,
|
|
18
17
|
PayloadAttestationError,
|
|
19
18
|
PayloadAttestationErrorCode,
|
|
20
|
-
ProposerPreferencesError,
|
|
21
|
-
ProposerPreferencesErrorCode,
|
|
22
19
|
SyncCommitteeError,
|
|
23
20
|
} from "../../../../chain/errors/index.js";
|
|
24
21
|
import {validateApiAttesterSlashing} from "../../../../chain/validation/attesterSlashing.js";
|
|
25
22
|
import {validateApiBlsToExecutionChange} from "../../../../chain/validation/blsToExecutionChange.js";
|
|
26
23
|
import {toElectraSingleAttestation, validateApiAttestation} from "../../../../chain/validation/index.js";
|
|
27
24
|
import {validateApiPayloadAttestationMessage} from "../../../../chain/validation/payloadAttestationMessage.js";
|
|
28
|
-
import {validateGossipProposerPreferences} from "../../../../chain/validation/proposerPreferences.js";
|
|
29
25
|
import {validateApiProposerSlashing} from "../../../../chain/validation/proposerSlashing.js";
|
|
30
26
|
import {validateApiSyncCommittee} from "../../../../chain/validation/syncCommittee.js";
|
|
31
27
|
import {validateApiVoluntaryExit} from "../../../../chain/validation/voluntaryExit.js";
|
|
@@ -66,54 +62,6 @@ export function getBeaconPoolApi({
|
|
|
66
62
|
return {data: chain.payloadAttestationPool.getAll(slot), meta: {version: fork}};
|
|
67
63
|
},
|
|
68
64
|
|
|
69
|
-
async getPoolProposerPreferences({slot}) {
|
|
70
|
-
const fork = chain.config.getForkName(slot ?? chain.clock.currentSlot);
|
|
71
|
-
if (!isForkPostGloas(fork)) {
|
|
72
|
-
throw new ApiError(400, `Proposer preferences pool is not supported before Gloas fork=${fork}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {data: chain.proposerPreferencesPool.getAll(slot), meta: {version: fork}};
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
async submitSignedProposerPreferences({signedProposerPreferences}) {
|
|
79
|
-
const failures: FailureList = [];
|
|
80
|
-
|
|
81
|
-
await Promise.all(
|
|
82
|
-
signedProposerPreferences.map(async (signed, i) => {
|
|
83
|
-
try {
|
|
84
|
-
await validateGossipProposerPreferences(chain, signed);
|
|
85
|
-
|
|
86
|
-
await network.publishProposerPreferences(signed);
|
|
87
|
-
chain.emitter.emit(routes.events.EventType.proposerPreferences, {
|
|
88
|
-
version: ForkName.gloas,
|
|
89
|
-
data: signed,
|
|
90
|
-
});
|
|
91
|
-
} catch (e) {
|
|
92
|
-
const logCtx = {
|
|
93
|
-
slot: signed.message.proposalSlot,
|
|
94
|
-
validatorIndex: signed.message.validatorIndex,
|
|
95
|
-
dependentRoot: toRootHex(signed.message.dependentRoot),
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
if (e instanceof ProposerPreferencesError && e.type.code === ProposerPreferencesErrorCode.ALREADY_KNOWN) {
|
|
99
|
-
logger.debug("Ignoring known signed proposer preferences", logCtx);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
failures.push({index: i, message: (e as Error).message});
|
|
104
|
-
logger.verbose(`Error on submitSignedProposerPreferences [${i}]`, logCtx, e as Error);
|
|
105
|
-
if (e instanceof ProposerPreferencesError && e.action === GossipAction.REJECT) {
|
|
106
|
-
chain.persistInvalidSszValue(ssz.gloas.SignedProposerPreferences, signed, "api_reject");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
if (failures.length > 0) {
|
|
113
|
-
throw new IndexedError("Error processing signed proposer preferences", failures);
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
|
|
117
65
|
async getPoolAttesterSlashingsV2() {
|
|
118
66
|
const fork = chain.config.getForkName(chain.clock.currentSlot);
|
|
119
67
|
return {data: chain.opPool.getAllAttesterSlashings(), meta: {version: fork}};
|
|
@@ -9,13 +9,15 @@ import {
|
|
|
9
9
|
isStatePostAltair,
|
|
10
10
|
isStatePostElectra,
|
|
11
11
|
isStatePostFulu,
|
|
12
|
+
isStatePostGloas,
|
|
12
13
|
} from "@lodestar/state-transition";
|
|
13
|
-
import {ValidatorIndex, getValidatorStatus, ssz} from "@lodestar/types";
|
|
14
|
+
import {ValidatorIndex, getBuilderStatus, getValidatorStatus, ssz} from "@lodestar/types";
|
|
14
15
|
import {ApiError} from "../../errors.js";
|
|
15
16
|
import {ApiModules} from "../../types.js";
|
|
16
17
|
import {assertUniqueItems} from "../../utils.js";
|
|
17
18
|
import {
|
|
18
19
|
filterStateValidatorsByStatus,
|
|
20
|
+
getStateBuilderIndex,
|
|
19
21
|
getStateResponseWithRegen,
|
|
20
22
|
getStateValidatorIndex,
|
|
21
23
|
toValidatorResponse,
|
|
@@ -143,6 +145,55 @@ export function getBeaconStateApi({
|
|
|
143
145
|
return this.getStateValidators(args, context);
|
|
144
146
|
},
|
|
145
147
|
|
|
148
|
+
async getStateBuilders({stateId, builderIds = [], statuses = []}) {
|
|
149
|
+
const {state, executionOptimistic, finalized} = await getState(stateId);
|
|
150
|
+
if (!isStatePostGloas(state)) {
|
|
151
|
+
throw new ApiError(400, `Builders are not supported for pre-gloas state fork=${state.forkName}`);
|
|
152
|
+
}
|
|
153
|
+
const finalizedEpoch = state.finalizedCheckpoint.epoch;
|
|
154
|
+
|
|
155
|
+
const builderResponses: routes.beacon.BuilderResponse[] = [];
|
|
156
|
+
if (builderIds.length) {
|
|
157
|
+
assertUniqueItems(builderIds, "Duplicate builder IDs provided");
|
|
158
|
+
|
|
159
|
+
for (const id of builderIds) {
|
|
160
|
+
const resp = getStateBuilderIndex(id, state);
|
|
161
|
+
if (resp.valid) {
|
|
162
|
+
const builderIndex = resp.builderIndex;
|
|
163
|
+
const builder = state.getBuilder(builderIndex);
|
|
164
|
+
const status = getBuilderStatus(builder, finalizedEpoch);
|
|
165
|
+
if (statuses.length && !statuses.includes(status)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
builderResponses.push({index: builderIndex, status, builder});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
data: builderResponses,
|
|
173
|
+
meta: {executionOptimistic, finalized},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (statuses.length) {
|
|
178
|
+
assertUniqueItems(statuses, "Duplicate statuses provided");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const buildersLength = state.getBuildersLength();
|
|
182
|
+
for (let builderIndex = 0; builderIndex < buildersLength; builderIndex++) {
|
|
183
|
+
const builder = state.getBuilder(builderIndex);
|
|
184
|
+
const status = getBuilderStatus(builder, finalizedEpoch);
|
|
185
|
+
if (statuses.length && !statuses.includes(status)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
builderResponses.push({index: builderIndex, status, builder});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
data: builderResponses,
|
|
193
|
+
meta: {executionOptimistic, finalized},
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
|
|
146
197
|
async postStateValidatorIdentities({stateId, validatorIds = []}) {
|
|
147
198
|
const {state, executionOptimistic, finalized} = await getState(stateId);
|
|
148
199
|
const {pubkeyCache} = chain;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {CheckpointWithHex, IForkChoice} from "@lodestar/fork-choice";
|
|
3
3
|
import {GENESIS_SLOT} from "@lodestar/params";
|
|
4
|
-
import {IBeaconStateView, PubkeyCache} from "@lodestar/state-transition";
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import {IBeaconStateView, IBeaconStateViewGloas, PubkeyCache} from "@lodestar/state-transition";
|
|
5
|
+
import {
|
|
6
|
+
BLSPubkey,
|
|
7
|
+
BuilderIndex,
|
|
8
|
+
Epoch,
|
|
9
|
+
RootHex,
|
|
10
|
+
Slot,
|
|
11
|
+
ValidatorIndex,
|
|
12
|
+
getValidatorStatus,
|
|
13
|
+
phase0,
|
|
14
|
+
} from "@lodestar/types";
|
|
15
|
+
import {byteArrayEquals, fromHex} from "@lodestar/utils";
|
|
7
16
|
import {IBeaconChain} from "../../../../chain/index.js";
|
|
8
17
|
import {ApiError, ValidationError} from "../../errors.js";
|
|
9
18
|
|
|
@@ -78,6 +87,50 @@ export function toValidatorResponse(
|
|
|
78
87
|
};
|
|
79
88
|
}
|
|
80
89
|
|
|
90
|
+
type StateBuilderIndexResponse =
|
|
91
|
+
| {valid: true; builderIndex: BuilderIndex}
|
|
92
|
+
| {valid: false; code: number; reason: string};
|
|
93
|
+
|
|
94
|
+
export function getStateBuilderIndex(
|
|
95
|
+
id: routes.beacon.BuilderId | BLSPubkey,
|
|
96
|
+
state: IBeaconStateViewGloas
|
|
97
|
+
): StateBuilderIndexResponse {
|
|
98
|
+
if (typeof id === "string") {
|
|
99
|
+
// mutate `id` and fallthrough to below
|
|
100
|
+
if (id.startsWith("0x")) {
|
|
101
|
+
try {
|
|
102
|
+
id = fromHex(id);
|
|
103
|
+
} catch (_e) {
|
|
104
|
+
return {valid: false, code: 400, reason: "Invalid pubkey hex encoding"};
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
id = Number(id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (typeof id === "number") {
|
|
112
|
+
const builderIndex = id;
|
|
113
|
+
// builder is invalid or added later than given stateId
|
|
114
|
+
if (!Number.isSafeInteger(builderIndex) || builderIndex < 0) {
|
|
115
|
+
return {valid: false, code: 400, reason: "Invalid builder index"};
|
|
116
|
+
}
|
|
117
|
+
if (builderIndex >= state.getBuildersLength()) {
|
|
118
|
+
return {valid: false, code: 404, reason: "Builder index from future state"};
|
|
119
|
+
}
|
|
120
|
+
return {valid: true, builderIndex};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// typeof id === Uint8Array
|
|
124
|
+
// There is no builder pubkey cache, linear scan over the registry
|
|
125
|
+
const buildersLength = state.getBuildersLength();
|
|
126
|
+
for (let builderIndex = 0; builderIndex < buildersLength; builderIndex++) {
|
|
127
|
+
if (byteArrayEquals(state.getBuilder(builderIndex).pubkey, id)) {
|
|
128
|
+
return {valid: true, builderIndex};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {valid: false, code: 404, reason: "Builder pubkey not found in state"};
|
|
132
|
+
}
|
|
133
|
+
|
|
81
134
|
export function filterStateValidatorsByStatus(
|
|
82
135
|
statuses: string[],
|
|
83
136
|
state: IBeaconStateView,
|
|
@@ -122,7 +175,7 @@ export function getStateValidatorIndex(
|
|
|
122
175
|
if (typeof id === "number") {
|
|
123
176
|
const validatorIndex = id;
|
|
124
177
|
// validator is invalid or added later than given stateId
|
|
125
|
-
if (!Number.isSafeInteger(validatorIndex)) {
|
|
178
|
+
if (!Number.isSafeInteger(validatorIndex) || validatorIndex < 0) {
|
|
126
179
|
return {valid: false, code: 400, reason: "Invalid validator index"};
|
|
127
180
|
}
|
|
128
181
|
if (validatorIndex >= state.validatorCount) {
|
|
@@ -65,6 +65,8 @@ import {
|
|
|
65
65
|
AttestationError,
|
|
66
66
|
AttestationErrorCode,
|
|
67
67
|
GossipAction,
|
|
68
|
+
ProposerPreferencesError,
|
|
69
|
+
ProposerPreferencesErrorCode,
|
|
68
70
|
SyncCommitteeError,
|
|
69
71
|
SyncCommitteeErrorCode,
|
|
70
72
|
} from "../../../chain/errors/index.js";
|
|
@@ -74,6 +76,7 @@ import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/prod
|
|
|
74
76
|
import {RegenCaller} from "../../../chain/regen/index.js";
|
|
75
77
|
import {CheckpointHex} from "../../../chain/stateCache/types.js";
|
|
76
78
|
import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
|
|
79
|
+
import {validateGossipProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
|
|
77
80
|
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
|
|
78
81
|
import {ZERO_HASH} from "../../../constants/index.js";
|
|
79
82
|
import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
|
|
@@ -1118,8 +1121,8 @@ export function getValidatorApi(
|
|
|
1118
1121
|
|
|
1119
1122
|
const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
|
|
1120
1123
|
if (!block) {
|
|
1121
|
-
// No block is seen at slot. Return
|
|
1122
|
-
|
|
1124
|
+
// No canonical block is seen at slot. Return 204 so vc can skip casting payload attestation.
|
|
1125
|
+
return {data: undefined, meta: {version: fork}, status: 204};
|
|
1123
1126
|
}
|
|
1124
1127
|
|
|
1125
1128
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
|
|
@@ -1771,6 +1774,46 @@ export function getValidatorApi(
|
|
|
1771
1774
|
});
|
|
1772
1775
|
},
|
|
1773
1776
|
|
|
1777
|
+
async submitProposerPreferences({signedProposerPreferences}) {
|
|
1778
|
+
const failures: FailureList = [];
|
|
1779
|
+
|
|
1780
|
+
await Promise.all(
|
|
1781
|
+
signedProposerPreferences.map(async (signed, i) => {
|
|
1782
|
+
try {
|
|
1783
|
+
await validateGossipProposerPreferences(chain, signed);
|
|
1784
|
+
|
|
1785
|
+
chain.proposerPreferencesPool.add(signed);
|
|
1786
|
+
await network.publishProposerPreferences(signed);
|
|
1787
|
+
chain.emitter.emit(routes.events.EventType.proposerPreferences, {
|
|
1788
|
+
version: config.getForkName(signed.message.proposalSlot),
|
|
1789
|
+
data: signed,
|
|
1790
|
+
});
|
|
1791
|
+
} catch (e) {
|
|
1792
|
+
const logCtx = {
|
|
1793
|
+
slot: signed.message.proposalSlot,
|
|
1794
|
+
validatorIndex: signed.message.validatorIndex,
|
|
1795
|
+
dependentRoot: toRootHex(signed.message.dependentRoot),
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
if (e instanceof ProposerPreferencesError && e.type.code === ProposerPreferencesErrorCode.ALREADY_KNOWN) {
|
|
1799
|
+
logger.debug("Ignoring known signed proposer preferences", logCtx);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
failures.push({index: i, message: (e as Error).message});
|
|
1804
|
+
logger.verbose(`Error on submitProposerPreferences [${i}]`, logCtx, e as Error);
|
|
1805
|
+
if (e instanceof ProposerPreferencesError && e.action === GossipAction.REJECT) {
|
|
1806
|
+
chain.persistInvalidSszValue(ssz.gloas.SignedProposerPreferences, signed, "api_reject");
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
})
|
|
1810
|
+
);
|
|
1811
|
+
|
|
1812
|
+
if (failures.length > 0) {
|
|
1813
|
+
throw new IndexedError("Error processing signed proposer preferences", failures);
|
|
1814
|
+
}
|
|
1815
|
+
},
|
|
1816
|
+
|
|
1774
1817
|
async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
|
|
1775
1818
|
const fork = config.getForkName(slot);
|
|
1776
1819
|
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
ValidatorIndex,
|
|
44
44
|
Wei,
|
|
45
45
|
altair,
|
|
46
|
+
bellatrix,
|
|
46
47
|
capella,
|
|
47
48
|
deneb,
|
|
48
49
|
electra,
|
|
@@ -830,28 +831,19 @@ export function getPayloadAttributesForSSE(
|
|
|
830
831
|
feeRecipient,
|
|
831
832
|
});
|
|
832
833
|
|
|
833
|
-
|
|
834
|
-
if (isForkPostGloas(fork)) {
|
|
835
|
-
const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(
|
|
836
|
-
toRootHex(parentBlockRoot),
|
|
837
|
-
toRootHex(parentBlockHash)
|
|
838
|
-
);
|
|
839
|
-
if (parentBlock?.executionPayloadBlockHash == null) {
|
|
840
|
-
throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
|
|
841
|
-
}
|
|
842
|
-
parentBlockNumber = parentBlock.executionPayloadNumber;
|
|
843
|
-
} else {
|
|
844
|
-
parentBlockNumber = prepareState.payloadBlockNumber;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
const ssePayloadAttributes: SSEPayloadAttributes = {
|
|
834
|
+
const ssePayloadAttributes = {
|
|
848
835
|
proposerIndex: prepareState.getBeaconProposer(prepareSlot),
|
|
849
836
|
proposalSlot: prepareSlot,
|
|
850
|
-
parentBlockNumber,
|
|
851
837
|
parentBlockRoot,
|
|
852
838
|
parentBlockHash,
|
|
853
839
|
payloadAttributes,
|
|
854
|
-
};
|
|
840
|
+
} as SSEPayloadAttributes;
|
|
841
|
+
|
|
842
|
+
if (!isForkPostGloas(fork)) {
|
|
843
|
+
// Removed in Gloas, builders can get the block number from the EL via the block hash if required
|
|
844
|
+
(ssePayloadAttributes as bellatrix.SSEPayloadAttributes).parentBlockNumber = prepareState.payloadBlockNumber;
|
|
845
|
+
}
|
|
846
|
+
|
|
855
847
|
return ssePayloadAttributes;
|
|
856
848
|
}
|
|
857
849
|
|
|
@@ -37,9 +37,11 @@ async function validateExecutionPayloadBid(
|
|
|
37
37
|
const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
|
|
38
38
|
const parentBlockHashHex = toRootHex(bid.parentBlockHash);
|
|
39
39
|
|
|
40
|
-
// [IGNORE] `bid.slot` is the current slot or the next slot.
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
|
|
41
|
+
if (
|
|
42
|
+
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
|
|
43
|
+
!chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot - 1)
|
|
44
|
+
) {
|
|
43
45
|
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
|
|
44
46
|
code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
|
|
45
47
|
builderIndex: bid.builderIndex,
|
|
@@ -122,17 +124,20 @@ async function validateExecutionPayloadBid(
|
|
|
122
124
|
throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
// [REJECT] `bid.builder_index` is
|
|
126
|
-
// `
|
|
127
|
-
|
|
128
|
-
try
|
|
129
|
-
|
|
130
|
-
} catch {
|
|
127
|
+
// [REJECT] `bid.builder_index` is within bounds -- i.e. `bid.builder_index < len(state.builders)`.
|
|
128
|
+
// `state.getBuilder` returns a lazy SSZ `getReadonly` view that is not bounds-checked eagerly; an
|
|
129
|
+
// out-of-range index only throws (`LeafNode has no right node`) on deferred field access (e.g. inside
|
|
130
|
+
// `isActiveBuilder`), escaping a try/catch around `getBuilder`. Check the length explicitly instead.
|
|
131
|
+
if (bid.builderIndex >= state.getBuildersLength()) {
|
|
131
132
|
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
132
133
|
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
133
134
|
builderIndex: bid.builderIndex,
|
|
134
135
|
});
|
|
135
136
|
}
|
|
137
|
+
|
|
138
|
+
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
|
|
139
|
+
// `is_active_builder(state, bid.builder_index)` returns `True`.
|
|
140
|
+
const builder = state.getBuilder(bid.builderIndex);
|
|
136
141
|
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
|
|
137
142
|
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
138
143
|
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
|
|
@@ -127,6 +127,100 @@ export function createNetworkCoreMetrics(register: RegistryMetricCreator) {
|
|
|
127
127
|
name: "lodestar_peer_manager_starved_bool",
|
|
128
128
|
help: "Whether lodestar is starved of data while syncing",
|
|
129
129
|
}),
|
|
130
|
+
/**
|
|
131
|
+
* Core selection/pruning phase of the heartbeat, split out of heartbeatDuration so it
|
|
132
|
+
* can be attributed on its own. Divide by peersEvaluated for time-per-peer.
|
|
133
|
+
*/
|
|
134
|
+
prioritizePeersDuration: register.histogram({
|
|
135
|
+
name: "lodestar_peer_manager_prioritize_peers_seconds",
|
|
136
|
+
help: "prioritizePeers function duration in seconds, the core peer selection/pruning algorithm",
|
|
137
|
+
buckets: [0.0005, 0.001, 0.005, 0.01, 0.05, 0.1],
|
|
138
|
+
}),
|
|
139
|
+
/**
|
|
140
|
+
* Score decay + map prune over every tracked peer, split out of heartbeatDuration.
|
|
141
|
+
* This loop iterates the whole score store, so divide by scoreMapSize (not
|
|
142
|
+
* peersEvaluated) for time-per-peer.
|
|
143
|
+
*/
|
|
144
|
+
scoreUpdateDuration: register.histogram({
|
|
145
|
+
name: "lodestar_peer_score_update_seconds",
|
|
146
|
+
help: "Peer score store update (decay + prune over all peers) duration in seconds",
|
|
147
|
+
buckets: [0.0005, 0.001, 0.005, 0.01, 0.05, 0.1],
|
|
148
|
+
}),
|
|
149
|
+
/**
|
|
150
|
+
* Denominator for prioritizePeersDuration: it scales with the number of connected
|
|
151
|
+
* healthy peers, so time-per-peer (prioritizePeersDuration / this) is the comparable
|
|
152
|
+
* figure. For scoreUpdateDuration use scoreMapSize instead.
|
|
153
|
+
*/
|
|
154
|
+
peersEvaluated: register.histogram({
|
|
155
|
+
name: "lodestar_peer_manager_peers_evaluated_count",
|
|
156
|
+
help: "Number of connected healthy peers evaluated by prioritizePeers per heartbeat, denominator for prioritize_peers_seconds",
|
|
157
|
+
buckets: [0, 25, 50, 75, 100, 150, 200],
|
|
158
|
+
}),
|
|
159
|
+
/**
|
|
160
|
+
* Every peer the manager intends to disconnect. Covers both the top-of-heartbeat
|
|
161
|
+
* bad-score disconnects ("banned"/"score_too_low") and the prioritization reasons;
|
|
162
|
+
* the older peersRequestedToDisconnect gauge only recorded the latter. Counts intent,
|
|
163
|
+
* not completion (goodbye is fire-and-forget).
|
|
164
|
+
*/
|
|
165
|
+
peersPruned: register.counter<{reason: string}>({
|
|
166
|
+
name: "lodestar_peer_manager_peers_pruned_total",
|
|
167
|
+
help: "Total peers the peer manager intends to disconnect, labeled by reason (incl. bad-score and prioritization reasons)",
|
|
168
|
+
labelNames: ["reason"],
|
|
169
|
+
}),
|
|
170
|
+
/**
|
|
171
|
+
* Actual peer count per active subnet, to check the min-peers-per-subnet invariant.
|
|
172
|
+
* Buckets straddle the target (TARGET_SUBNET_PEERS = 6).
|
|
173
|
+
*/
|
|
174
|
+
peersPerActiveSubnet: register.histogram<{type: SubnetType}>({
|
|
175
|
+
name: "lodestar_peer_manager_peers_per_active_subnet",
|
|
176
|
+
help: "Histogram of connected peer count per active subnet, labeled by subnet type",
|
|
177
|
+
labelNames: ["type"],
|
|
178
|
+
buckets: [0, 2, 4, 6, 8, 12],
|
|
179
|
+
}),
|
|
180
|
+
/**
|
|
181
|
+
* Live outbound ratio, to check the OUTBOUND_PEERS_RATIO (10%) invariant (issue #2215).
|
|
182
|
+
* Denominator is connected healthy peers (banned/disconnected already removed).
|
|
183
|
+
*/
|
|
184
|
+
outboundPeersRatio: register.gauge({
|
|
185
|
+
name: "lodestar_peer_manager_outbound_peers_ratio",
|
|
186
|
+
help: "Ratio of outbound peers to total connected healthy peers, verifies the outbound peers invariant",
|
|
187
|
+
}),
|
|
188
|
+
/**
|
|
189
|
+
* Score state crossings (Healthy/Disconnected/Banned). Sensitive to the decay formula,
|
|
190
|
+
* thresholds, and gossip-score weighting.
|
|
191
|
+
*/
|
|
192
|
+
scoreStateTransitions: register.counter<{from: string; to: string}>({
|
|
193
|
+
name: "lodestar_peer_score_state_transitions_total",
|
|
194
|
+
help: "Total peer score state transitions, labeled by from and to state (Healthy/Disconnected/Banned)",
|
|
195
|
+
labelNames: ["from", "to"],
|
|
196
|
+
}),
|
|
197
|
+
/**
|
|
198
|
+
* Entry count of the score store (not bytes). Reflects the prune-to-MAX_ENTRIES /
|
|
199
|
+
* SCORE_THRESHOLD retention logic. Also the denominator for scoreUpdateDuration
|
|
200
|
+
* time-per-peer (that loop iterates the whole store, not just connected peers).
|
|
201
|
+
*/
|
|
202
|
+
scoreMapSize: register.gauge({
|
|
203
|
+
name: "lodestar_peer_manager_score_map_size",
|
|
204
|
+
help: "Current number of entries in the peer score store",
|
|
205
|
+
}),
|
|
206
|
+
/**
|
|
207
|
+
* Entry count of the connectedPeers map (not bytes). Should track libp2p_peers; a
|
|
208
|
+
* persistent gap indicates the connection leak guarded by leakedConnectionsCount.
|
|
209
|
+
*/
|
|
210
|
+
connectedPeersMapSize: register.gauge({
|
|
211
|
+
name: "lodestar_peer_manager_connected_peers_map_size",
|
|
212
|
+
help: "Current number of entries in the peer manager connectedPeers map",
|
|
213
|
+
}),
|
|
214
|
+
/**
|
|
215
|
+
* assertPeerRelevance outcome per Status exchange: "relevant", an irrelevant reason
|
|
216
|
+
* code, or "error". Fires on every status evaluation (inbound requests and our own
|
|
217
|
+
* requestStatus responses both route through onStatus).
|
|
218
|
+
*/
|
|
219
|
+
relevanceCheck: register.counter<{result: string}>({
|
|
220
|
+
name: "lodestar_peer_relevance_check_total",
|
|
221
|
+
help: "Total peer relevance checks on Status, labeled by result (relevant or irrelevant reason code)",
|
|
222
|
+
labelNames: ["result"],
|
|
223
|
+
}),
|
|
130
224
|
},
|
|
131
225
|
leakedConnectionsCount: register.gauge({
|
|
132
226
|
name: "lodestar_peer_manager_leaked_connections_count",
|
|
@@ -13,9 +13,17 @@ export enum ENRRelevance {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function enrRelevance(enr: ENR, config: BeaconConfig, clock: IClock): ENRRelevance {
|
|
16
|
-
// We are not interested in peers that don't advertise at least one transport (tcp or quic)
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
// We are not interested in peers that don't advertise at least one transport (tcp or quic).
|
|
17
|
+
let multiaddrTCP: ReturnType<ENR["getLocationMultiaddr"]>;
|
|
18
|
+
let multiaddrQUIC: ReturnType<ENR["getLocationMultiaddr"]>;
|
|
19
|
+
// Malformed ENR endpoint fields can make multiaddr conversion throw.
|
|
20
|
+
// Treat endpoints that cannot be converted as unusable.
|
|
21
|
+
try {
|
|
22
|
+
multiaddrTCP = enr.getLocationMultiaddr(ENRKey.tcp);
|
|
23
|
+
multiaddrQUIC = enr.getLocationMultiaddr(ENRKey.quic);
|
|
24
|
+
} catch {
|
|
25
|
+
return ENRRelevance.no_transport;
|
|
26
|
+
}
|
|
19
27
|
if (!multiaddrTCP && !multiaddrQUIC) {
|
|
20
28
|
return ENRRelevance.no_transport;
|
|
21
29
|
}
|
|
@@ -52,6 +52,13 @@ export function fastMsgIdFn(rpcMsg: RPC.Message): string {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export function msgIdToStrFn(msgId: Uint8Array): string {
|
|
55
|
+
// Spec mandates a 20-byte gossipsub message-id, but control-message IDs from peers
|
|
56
|
+
// can be any length. Reject non-20-byte IDs to avoid a Buffer.set() RangeError on
|
|
57
|
+
// longer IDs and stale shared-buffer bytes on shorter ones.
|
|
58
|
+
// Ref: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.10/specs/phase0/p2p-interface.md?plain=1#L486
|
|
59
|
+
if (msgId.length !== 20) {
|
|
60
|
+
throw Error(`Expect msgId to be 20 bytes, got ${msgId.length}`);
|
|
61
|
+
}
|
|
55
62
|
// this is the same logic to `toHex(msgId)` with better performance
|
|
56
63
|
sharedMsgIdBuf.set(msgId);
|
|
57
64
|
return `0x${sharedMsgIdBuf.toString("hex")}`;
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
computeGossipPeerScoreParams,
|
|
36
36
|
gossipScoreThresholds,
|
|
37
37
|
} from "./scoringParameters.js";
|
|
38
|
-
import {GossipTopicCache, getCoreTopicsAtFork, stringifyGossipTopic} from "./topic.js";
|
|
38
|
+
import {GossipTopicCache, getAllowedTopics, getCoreTopicsAtFork, stringifyGossipTopic} from "./topic.js";
|
|
39
39
|
|
|
40
40
|
/** As specified in https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md */
|
|
41
41
|
const GOSSIPSUB_HEARTBEAT_INTERVAL = 0.7 * 1000;
|
|
@@ -140,6 +140,7 @@ export class Eth2Gossipsub {
|
|
|
140
140
|
const gossipsubInstance = gossipsub({
|
|
141
141
|
globalSignaturePolicy: StrictNoSign,
|
|
142
142
|
allowPublishToZeroTopicPeers: allowPublishToZeroPeers,
|
|
143
|
+
allowedTopics: getAllowedTopics(networkConfig),
|
|
143
144
|
D: gossipsubD ?? GOSSIP_D,
|
|
144
145
|
Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
|
|
145
146
|
Dhi: gossipsubDHigh ?? GOSSIP_D_HIGH,
|
|
@@ -246,7 +246,7 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr:
|
|
|
246
246
|
export function getCoreTopicsAtFork(
|
|
247
247
|
networkConfig: NetworkConfig,
|
|
248
248
|
fork: ForkName,
|
|
249
|
-
opts: {subscribeAllSubnets?: boolean; disableLightClientServer?: boolean}
|
|
249
|
+
opts: {subscribeAllSubnets?: boolean; subscribeAllColumnSubnets?: boolean; disableLightClientServer?: boolean}
|
|
250
250
|
): GossipTopicTypeMap[keyof GossipTopicTypeMap][] {
|
|
251
251
|
// Common topics for all forks
|
|
252
252
|
const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [
|
|
@@ -266,7 +266,7 @@ export function getCoreTopicsAtFork(
|
|
|
266
266
|
|
|
267
267
|
// After fulu also track data_column_sidecar_{index}
|
|
268
268
|
if (ForkSeq[fork] >= ForkSeq.fulu) {
|
|
269
|
-
topics.push(...getDataColumnSidecarTopics(networkConfig));
|
|
269
|
+
topics.push(...getDataColumnSidecarTopics(networkConfig, opts.subscribeAllColumnSubnets));
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// After Deneb and before Fulu also track blob_sidecar_{subnet_id}
|
|
@@ -309,15 +309,39 @@ export function getCoreTopicsAtFork(
|
|
|
309
309
|
return topics;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Build the complete set of valid gossip topic strings across every fork boundary.
|
|
314
|
+
*/
|
|
315
|
+
export function getAllowedTopics(networkConfig: NetworkConfig): Set<string> {
|
|
316
|
+
const {config} = networkConfig;
|
|
317
|
+
const allowedTopics = new Set<string>();
|
|
318
|
+
|
|
319
|
+
for (const boundary of config.forkBoundariesAscendingEpochOrder) {
|
|
320
|
+
const topics = getCoreTopicsAtFork(networkConfig, boundary.fork, {
|
|
321
|
+
subscribeAllSubnets: true,
|
|
322
|
+
subscribeAllColumnSubnets: true,
|
|
323
|
+
disableLightClientServer: false,
|
|
324
|
+
});
|
|
325
|
+
for (const topic of topics) {
|
|
326
|
+
allowedTopics.add(stringifyGossipTopic(config, {...topic, boundary}));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return allowedTopics;
|
|
331
|
+
}
|
|
332
|
+
|
|
312
333
|
/**
|
|
313
334
|
* Pick data column subnets to subscribe to post-fulu.
|
|
314
335
|
*/
|
|
315
336
|
export function getDataColumnSidecarTopics(
|
|
316
|
-
networkConfig: NetworkConfig
|
|
337
|
+
networkConfig: NetworkConfig,
|
|
338
|
+
subscribeAllColumnSubnets = false
|
|
317
339
|
): GossipTopicTypeMap[keyof GossipTopicTypeMap][] {
|
|
318
340
|
const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [];
|
|
319
341
|
|
|
320
|
-
const subnets =
|
|
342
|
+
const subnets = subscribeAllColumnSubnets
|
|
343
|
+
? Array.from({length: networkConfig.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT}, (_, i) => i)
|
|
344
|
+
: networkConfig.custodyConfig.sampledSubnets;
|
|
321
345
|
for (const subnet of subnets) {
|
|
322
346
|
topics.push({type: GossipType.data_column_sidecar, subnet});
|
|
323
347
|
}
|