@lodestar/beacon-node 1.45.0-dev.e6d38ecb03 → 1.45.0-dev.f535421f29
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 +43 -2
- 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 +3 -46
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +2 -11
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +3 -39
- 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 +6 -46
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.js +1 -1
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/chain.d.ts +2 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +3 -1
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -1
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -2
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +0 -3
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/options.d.ts +0 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +0 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +12 -4
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +1 -0
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +1 -0
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +16 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
- package/lib/chain/seenCache/seenProposerPreferences.js +26 -0
- package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +3 -11
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +1 -2
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +20 -782
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +3 -23
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +3 -2
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/util/graffiti.d.ts +0 -15
- package/lib/util/graffiti.d.ts.map +1 -1
- package/lib/util/graffiti.js +0 -55
- package/lib/util/graffiti.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/pool/index.ts +53 -0
- package/src/api/impl/beacon/state/index.ts +1 -52
- package/src/api/impl/beacon/state/utils.ts +4 -57
- package/src/api/impl/validator/index.ts +5 -54
- package/src/chain/blocks/importBlock.ts +1 -1
- package/src/chain/chain.ts +3 -0
- package/src/chain/interface.ts +2 -0
- package/src/chain/opPools/proposerPreferencesPool.ts +1 -5
- package/src/chain/options.ts +0 -2
- package/src/chain/produceBlock/produceBlockBody.ts +17 -9
- package/src/chain/seenCache/index.ts +1 -0
- package/src/chain/seenCache/seenProposerPreferences.ts +32 -0
- package/src/chain/validation/proposerPreferences.ts +3 -12
- package/src/network/gossip/gossipsub.ts +1 -2
- package/src/network/gossip/topic.ts +4 -28
- package/src/network/processor/gossipHandlers.ts +2 -1
- package/src/util/graffiti.ts +0 -78
|
@@ -9,15 +9,13 @@ import {
|
|
|
9
9
|
isStatePostAltair,
|
|
10
10
|
isStatePostElectra,
|
|
11
11
|
isStatePostFulu,
|
|
12
|
-
isStatePostGloas,
|
|
13
12
|
} from "@lodestar/state-transition";
|
|
14
|
-
import {ValidatorIndex,
|
|
13
|
+
import {ValidatorIndex, getValidatorStatus, ssz} from "@lodestar/types";
|
|
15
14
|
import {ApiError} from "../../errors.js";
|
|
16
15
|
import {ApiModules} from "../../types.js";
|
|
17
16
|
import {assertUniqueItems} from "../../utils.js";
|
|
18
17
|
import {
|
|
19
18
|
filterStateValidatorsByStatus,
|
|
20
|
-
getStateBuilderIndex,
|
|
21
19
|
getStateResponseWithRegen,
|
|
22
20
|
getStateValidatorIndex,
|
|
23
21
|
toValidatorResponse,
|
|
@@ -145,55 +143,6 @@ export function getBeaconStateApi({
|
|
|
145
143
|
return this.getStateValidators(args, context);
|
|
146
144
|
},
|
|
147
145
|
|
|
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
|
-
|
|
197
146
|
async postStateValidatorIdentities({stateId, validatorIds = []}) {
|
|
198
147
|
const {state, executionOptimistic, finalized} = await getState(stateId);
|
|
199
148
|
const {pubkeyCache} = chain;
|
|
@@ -1,18 +1,9 @@
|
|
|
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,
|
|
5
|
-
import {
|
|
6
|
-
|
|
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";
|
|
4
|
+
import {IBeaconStateView, PubkeyCache} from "@lodestar/state-transition";
|
|
5
|
+
import {BLSPubkey, Epoch, RootHex, Slot, ValidatorIndex, getValidatorStatus, phase0} from "@lodestar/types";
|
|
6
|
+
import {fromHex} from "@lodestar/utils";
|
|
16
7
|
import {IBeaconChain} from "../../../../chain/index.js";
|
|
17
8
|
import {ApiError, ValidationError} from "../../errors.js";
|
|
18
9
|
|
|
@@ -87,50 +78,6 @@ export function toValidatorResponse(
|
|
|
87
78
|
};
|
|
88
79
|
}
|
|
89
80
|
|
|
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
|
-
|
|
134
81
|
export function filterStateValidatorsByStatus(
|
|
135
82
|
statuses: string[],
|
|
136
83
|
state: IBeaconStateView,
|
|
@@ -175,7 +122,7 @@ export function getStateValidatorIndex(
|
|
|
175
122
|
if (typeof id === "number") {
|
|
176
123
|
const validatorIndex = id;
|
|
177
124
|
// validator is invalid or added later than given stateId
|
|
178
|
-
if (!Number.isSafeInteger(validatorIndex)
|
|
125
|
+
if (!Number.isSafeInteger(validatorIndex)) {
|
|
179
126
|
return {valid: false, code: 400, reason: "Invalid validator index"};
|
|
180
127
|
}
|
|
181
128
|
if (validatorIndex >= state.validatorCount) {
|
|
@@ -65,8 +65,6 @@ import {
|
|
|
65
65
|
AttestationError,
|
|
66
66
|
AttestationErrorCode,
|
|
67
67
|
GossipAction,
|
|
68
|
-
ProposerPreferencesError,
|
|
69
|
-
ProposerPreferencesErrorCode,
|
|
70
68
|
SyncCommitteeError,
|
|
71
69
|
SyncCommitteeErrorCode,
|
|
72
70
|
} from "../../../chain/errors/index.js";
|
|
@@ -76,7 +74,6 @@ import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/prod
|
|
|
76
74
|
import {RegenCaller} from "../../../chain/regen/index.js";
|
|
77
75
|
import {CheckpointHex} from "../../../chain/stateCache/types.js";
|
|
78
76
|
import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
|
|
79
|
-
import {validateGossipProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
|
|
80
77
|
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
|
|
81
78
|
import {ZERO_HASH} from "../../../constants/index.js";
|
|
82
79
|
import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
|
|
@@ -85,7 +82,7 @@ import {CommitteeSubscription} from "../../../network/subnets/index.js";
|
|
|
85
82
|
import {SyncState} from "../../../sync/index.js";
|
|
86
83
|
import {callInNextEventLoop} from "../../../util/eventLoop.js";
|
|
87
84
|
import {isOptimisticBlock} from "../../../util/forkChoice.js";
|
|
88
|
-
import {
|
|
85
|
+
import {getDefaultGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
|
|
89
86
|
import {getLodestarClientVersion} from "../../../util/metadata.js";
|
|
90
87
|
import {ApiOptions} from "../../options.js";
|
|
91
88
|
import {getStateResponseWithRegen} from "../beacon/state/utils.js";
|
|
@@ -614,10 +611,7 @@ export function getValidatorApi(
|
|
|
614
611
|
}
|
|
615
612
|
|
|
616
613
|
const graffitiBytes = toGraffitiBytes(
|
|
617
|
-
|
|
618
|
-
private: opts.private,
|
|
619
|
-
graffitiAppend: chain.opts.graffitiAppend,
|
|
620
|
-
})
|
|
614
|
+
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
|
|
621
615
|
);
|
|
622
616
|
|
|
623
617
|
const loggerContext = {
|
|
@@ -927,10 +921,7 @@ export function getValidatorApi(
|
|
|
927
921
|
metrics?.blockProductionSlotDelta.set(slot - parentSlot);
|
|
928
922
|
|
|
929
923
|
const graffitiBytes = toGraffitiBytes(
|
|
930
|
-
|
|
931
|
-
private: opts.private,
|
|
932
|
-
graffitiAppend: chain.opts.graffitiAppend,
|
|
933
|
-
})
|
|
924
|
+
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
|
|
934
925
|
);
|
|
935
926
|
|
|
936
927
|
// TODO GLOAS: respect builderSelection (MaxProfit, BuilderAlways, ExecutionAlways, etc.) to let
|
|
@@ -1121,8 +1112,8 @@ export function getValidatorApi(
|
|
|
1121
1112
|
|
|
1122
1113
|
const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
|
|
1123
1114
|
if (!block) {
|
|
1124
|
-
// No
|
|
1125
|
-
|
|
1115
|
+
// No block is seen at slot. Return 404 so vc can skip casting payload attestation.
|
|
1116
|
+
throw new ApiError(404, `No canonical block found at slot=${slot}`);
|
|
1126
1117
|
}
|
|
1127
1118
|
|
|
1128
1119
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
|
|
@@ -1774,46 +1765,6 @@ export function getValidatorApi(
|
|
|
1774
1765
|
});
|
|
1775
1766
|
},
|
|
1776
1767
|
|
|
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
|
-
|
|
1817
1768
|
async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
|
|
1818
1769
|
const fork = config.getForkName(slot);
|
|
1819
1770
|
|
|
@@ -426,7 +426,7 @@ export async function importBlock(
|
|
|
426
426
|
}
|
|
427
427
|
} catch (e) {
|
|
428
428
|
if (isStartSlotOfEpoch(proposalSlot)) {
|
|
429
|
-
notOverrideFcuReason = NotReorgedReason.
|
|
429
|
+
notOverrideFcuReason = NotReorgedReason.NotShufflingStable;
|
|
430
430
|
} else {
|
|
431
431
|
this.logger.warn("Unable to get beacon proposer. Do not override fcu.", {proposalSlot}, e as Error);
|
|
432
432
|
}
|
package/src/chain/chain.ts
CHANGED
|
@@ -107,6 +107,7 @@ import {
|
|
|
107
107
|
SeenExecutionPayloadBids,
|
|
108
108
|
SeenPayloadAttesters,
|
|
109
109
|
SeenPayloadEnvelopeInput,
|
|
110
|
+
SeenProposerPreferences,
|
|
110
111
|
SeenSyncCommitteeMessages,
|
|
111
112
|
} from "./seenCache/index.js";
|
|
112
113
|
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
|
|
@@ -188,6 +189,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
188
189
|
readonly seenPayloadAttesters = new SeenPayloadAttesters();
|
|
189
190
|
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
|
|
190
191
|
readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
|
|
192
|
+
readonly seenProposerPreferences = new SeenProposerPreferences();
|
|
191
193
|
readonly seenBlockProposers = new SeenBlockProposers();
|
|
192
194
|
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
|
|
193
195
|
readonly seenContributionAndProof: SeenContributionAndProof;
|
|
@@ -1471,6 +1473,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1471
1473
|
this.payloadAttestationPool.prune(slot);
|
|
1472
1474
|
this.executionPayloadBidPool.prune(slot);
|
|
1473
1475
|
this.seenExecutionPayloadBids.prune(slot);
|
|
1476
|
+
this.seenProposerPreferences.prune(slot);
|
|
1474
1477
|
this.proposerPreferencesPool.prune(slot);
|
|
1475
1478
|
this.seenAttestationDatas.onSlot(slot);
|
|
1476
1479
|
this.reprocessController.onSlot(slot);
|
package/src/chain/interface.ts
CHANGED
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
SeenContributionAndProof,
|
|
62
62
|
SeenExecutionPayloadBids,
|
|
63
63
|
SeenPayloadAttesters,
|
|
64
|
+
SeenProposerPreferences,
|
|
64
65
|
SeenSyncCommitteeMessages,
|
|
65
66
|
} from "./seenCache/index.js";
|
|
66
67
|
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
|
|
@@ -132,6 +133,7 @@ export interface IBeaconChain {
|
|
|
132
133
|
readonly seenPayloadAttesters: SeenPayloadAttesters;
|
|
133
134
|
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
|
|
134
135
|
readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
|
|
136
|
+
readonly seenProposerPreferences: SeenProposerPreferences;
|
|
135
137
|
readonly seenBlockProposers: SeenBlockProposers;
|
|
136
138
|
readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
|
|
137
139
|
readonly seenContributionAndProof: SeenContributionAndProof;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {RootHex, Slot,
|
|
1
|
+
import {RootHex, Slot, gloas} from "@lodestar/types";
|
|
2
2
|
import {toRootHex} from "@lodestar/utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -22,10 +22,6 @@ export class ProposerPreferencesPool {
|
|
|
22
22
|
return this.bySlot.get(slot)?.get(dependentRootHex) ?? null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
isKnown(proposalSlot: Slot, dependentRoot: RootHex, validatorIndex: ValidatorIndex): boolean {
|
|
26
|
-
return this.get(proposalSlot, dependentRoot)?.message.validatorIndex === validatorIndex;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
add(signed: gloas.SignedProposerPreferences): void {
|
|
30
26
|
const {proposalSlot, dependentRoot} = signed.message;
|
|
31
27
|
const rootHex = toRootHex(dependentRoot);
|
package/src/chain/options.ts
CHANGED
|
@@ -35,7 +35,6 @@ export type IChainOptions = BlockProcessOpts &
|
|
|
35
35
|
persistOrphanedBlocksDir?: string;
|
|
36
36
|
skipCreateStateCacheIfAvailable?: boolean;
|
|
37
37
|
suggestedFeeRecipient: string;
|
|
38
|
-
graffitiAppend?: boolean;
|
|
39
38
|
maxSkipSlots?: number;
|
|
40
39
|
/** Ensure blobs returned by the execution engine are valid */
|
|
41
40
|
sanityCheckExecutionEngineBlobs?: boolean;
|
|
@@ -108,7 +107,6 @@ export const defaultChainOptions: IChainOptions = {
|
|
|
108
107
|
computeUnrealized: true,
|
|
109
108
|
fastConfirmation: false,
|
|
110
109
|
suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient,
|
|
111
|
-
graffitiAppend: true,
|
|
112
110
|
serveHistoricalState: false,
|
|
113
111
|
assertCorrectProgressiveBalances: false,
|
|
114
112
|
archiveStateEpochFrequency: 1024,
|
|
@@ -43,7 +43,6 @@ import {
|
|
|
43
43
|
ValidatorIndex,
|
|
44
44
|
Wei,
|
|
45
45
|
altair,
|
|
46
|
-
bellatrix,
|
|
47
46
|
capella,
|
|
48
47
|
deneb,
|
|
49
48
|
electra,
|
|
@@ -831,19 +830,28 @@ export function getPayloadAttributesForSSE(
|
|
|
831
830
|
feeRecipient,
|
|
832
831
|
});
|
|
833
832
|
|
|
834
|
-
|
|
833
|
+
let parentBlockNumber: number;
|
|
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 = {
|
|
835
848
|
proposerIndex: prepareState.getBeaconProposer(prepareSlot),
|
|
836
849
|
proposalSlot: prepareSlot,
|
|
850
|
+
parentBlockNumber,
|
|
837
851
|
parentBlockRoot,
|
|
838
852
|
parentBlockHash,
|
|
839
853
|
payloadAttributes,
|
|
840
|
-
}
|
|
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
|
-
|
|
854
|
+
};
|
|
847
855
|
return ssePayloadAttributes;
|
|
848
856
|
}
|
|
849
857
|
|
|
@@ -5,3 +5,4 @@ export {SeenContributionAndProof} from "./seenCommitteeContribution.js";
|
|
|
5
5
|
export {SeenExecutionPayloadBids} from "./seenExecutionPayloadBids.js";
|
|
6
6
|
export {SeenBlockInput} from "./seenGossipBlockInput.js";
|
|
7
7
|
export {PayloadEnvelopeInput, SeenPayloadEnvelopeInput} from "./seenPayloadEnvelopeInput.js";
|
|
8
|
+
export {SeenProposerPreferences} from "./seenProposerPreferences.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
2
|
+
import {MapDef} from "@lodestar/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
|
|
6
|
+
*/
|
|
7
|
+
export class SeenProposerPreferences {
|
|
8
|
+
private readonly validatorByDependentRootBySlot = new MapDef<Slot, Map<RootHex, ValidatorIndex>>(
|
|
9
|
+
() => new Map<RootHex, ValidatorIndex>()
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
isKnown(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean {
|
|
13
|
+
return this.validatorByDependentRootBySlot.get(proposalSlot)?.get(dependentRoot) === validatorIndex;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
add(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): void {
|
|
17
|
+
this.validatorByDependentRootBySlot.getOrDefault(proposalSlot).set(dependentRoot, validatorIndex);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
|
|
22
|
+
* passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
|
|
23
|
+
* on each slot tick.
|
|
24
|
+
*/
|
|
25
|
+
prune(currentSlot: Slot): void {
|
|
26
|
+
for (const slot of this.validatorByDependentRootBySlot.keys()) {
|
|
27
|
+
if (slot < currentSlot) {
|
|
28
|
+
this.validatorByDependentRootBySlot.delete(slot);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -81,7 +81,7 @@ export async function validateGossipProposerPreferences(
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// [IGNORE] First valid message for (dependent_root, proposal_slot, validator_index).
|
|
84
|
-
if (chain.
|
|
84
|
+
if (chain.seenProposerPreferences.isKnown(dependentRootHex, proposalSlot, validatorIndex)) {
|
|
85
85
|
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
86
86
|
code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
|
|
87
87
|
proposalSlot,
|
|
@@ -105,15 +105,6 @@ export async function validateGossipProposerPreferences(
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
throw new ProposerPreferencesError(GossipAction.IGNORE, {
|
|
111
|
-
code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
|
|
112
|
-
proposalSlot,
|
|
113
|
-
validatorIndex,
|
|
114
|
-
dependentRoot: dependentRootHex,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
chain.proposerPreferencesPool.add(signedProposerPreferences);
|
|
108
|
+
// Valid
|
|
109
|
+
chain.seenProposerPreferences.add(dependentRootHex, proposalSlot, validatorIndex);
|
|
119
110
|
}
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
computeGossipPeerScoreParams,
|
|
36
36
|
gossipScoreThresholds,
|
|
37
37
|
} from "./scoringParameters.js";
|
|
38
|
-
import {GossipTopicCache,
|
|
38
|
+
import {GossipTopicCache, 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,7 +140,6 @@ export class Eth2Gossipsub {
|
|
|
140
140
|
const gossipsubInstance = gossipsub({
|
|
141
141
|
globalSignaturePolicy: StrictNoSign,
|
|
142
142
|
allowPublishToZeroTopicPeers: allowPublishToZeroPeers,
|
|
143
|
-
allowedTopics: getAllowedTopics(networkConfig),
|
|
144
143
|
D: gossipsubD ?? GOSSIP_D,
|
|
145
144
|
Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
|
|
146
145
|
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;
|
|
249
|
+
opts: {subscribeAllSubnets?: 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));
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// After Deneb and before Fulu also track blob_sidecar_{subnet_id}
|
|
@@ -309,39 +309,15 @@ 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
|
-
|
|
333
312
|
/**
|
|
334
313
|
* Pick data column subnets to subscribe to post-fulu.
|
|
335
314
|
*/
|
|
336
315
|
export function getDataColumnSidecarTopics(
|
|
337
|
-
networkConfig: NetworkConfig
|
|
338
|
-
subscribeAllColumnSubnets = false
|
|
316
|
+
networkConfig: NetworkConfig
|
|
339
317
|
): GossipTopicTypeMap[keyof GossipTopicTypeMap][] {
|
|
340
318
|
const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [];
|
|
341
319
|
|
|
342
|
-
const subnets =
|
|
343
|
-
? Array.from({length: networkConfig.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT}, (_, i) => i)
|
|
344
|
-
: networkConfig.custodyConfig.sampledSubnets;
|
|
320
|
+
const subnets = networkConfig.custodyConfig.sampledSubnets;
|
|
345
321
|
for (const subnet of subnets) {
|
|
346
322
|
topics.push({type: GossipType.data_column_sidecar, subnet});
|
|
347
323
|
}
|
|
@@ -1273,8 +1273,9 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1273
1273
|
const signedProposerPreferences = sszDeserialize(topic, serializedData);
|
|
1274
1274
|
await validateGossipProposerPreferences(chain, signedProposerPreferences);
|
|
1275
1275
|
|
|
1276
|
+
chain.proposerPreferencesPool.add(signedProposerPreferences);
|
|
1276
1277
|
chain.emitter.emit(routes.events.EventType.proposerPreferences, {
|
|
1277
|
-
version:
|
|
1278
|
+
version: ForkName.gloas,
|
|
1278
1279
|
data: signedProposerPreferences,
|
|
1279
1280
|
});
|
|
1280
1281
|
},
|
package/src/util/graffiti.ts
CHANGED
|
@@ -37,81 +37,3 @@ export function getDefaultGraffiti(
|
|
|
37
37
|
// No EL client info available. We still want to include CL info albeit not spec compliant
|
|
38
38
|
return `${consensusClientVersion.code}${consensusClientVersion.commit.slice(0, 4)}`;
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
function appendLongestFittingSuffix(userGraffiti: string, suffixes: string[]): string {
|
|
42
|
-
const userGraffitiBytes = Buffer.byteLength(userGraffiti, "utf8");
|
|
43
|
-
if (userGraffitiBytes >= GRAFFITI_SIZE) {
|
|
44
|
-
return userGraffiti;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const availableBytes = GRAFFITI_SIZE - userGraffitiBytes;
|
|
48
|
-
|
|
49
|
-
for (const suffix of suffixes) {
|
|
50
|
-
if (Buffer.byteLength(suffix, "utf8") <= availableBytes) {
|
|
51
|
-
return `${userGraffiti}${suffix}`;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return userGraffiti;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Appends the richest available client watermark that fits after user graffiti.
|
|
60
|
-
*
|
|
61
|
-
* Tiers are:
|
|
62
|
-
* - full EL/CL watermark, e.g. " BU9b0eLS80c2"
|
|
63
|
-
* - EL/CL client codes, e.g. " BULS"
|
|
64
|
-
* - CL client code, e.g. " LS"
|
|
65
|
-
*/
|
|
66
|
-
export function appendClientInfoToGraffiti(
|
|
67
|
-
userGraffiti: string,
|
|
68
|
-
consensusClientVersion: ClientVersion,
|
|
69
|
-
executionClientVersion: ClientVersion | null | undefined,
|
|
70
|
-
opts: {private?: boolean} = {}
|
|
71
|
-
): string {
|
|
72
|
-
if (opts.private) {
|
|
73
|
-
return userGraffiti;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Graffiti supplied via the beacon API is decoded from a fixed 32-byte field (see
|
|
77
|
-
// fromGraffitiHex) and arrives right-padded with NUL bytes. Trim only trailing padding
|
|
78
|
-
// NULs; a NUL that appears in the middle of the string is data, not padding.
|
|
79
|
-
let end = userGraffiti.length;
|
|
80
|
-
while (end > 0 && userGraffiti.charCodeAt(end - 1) === 0) {
|
|
81
|
-
end--;
|
|
82
|
-
}
|
|
83
|
-
const graffiti = userGraffiti.slice(0, end);
|
|
84
|
-
|
|
85
|
-
const fullClientInfo = getDefaultGraffiti(consensusClientVersion, executionClientVersion, {private: false});
|
|
86
|
-
if (graffiti.length === 0) {
|
|
87
|
-
return fullClientInfo;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const suffixes =
|
|
91
|
-
executionClientVersion != null
|
|
92
|
-
? [
|
|
93
|
-
` ${fullClientInfo}`,
|
|
94
|
-
` ${executionClientVersion.code}${consensusClientVersion.code}`,
|
|
95
|
-
` ${consensusClientVersion.code}`,
|
|
96
|
-
]
|
|
97
|
-
: [` ${fullClientInfo}`, ` ${consensusClientVersion.code}`];
|
|
98
|
-
|
|
99
|
-
return appendLongestFittingSuffix(graffiti, suffixes);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function getBlockGraffiti(
|
|
103
|
-
userGraffiti: string | undefined,
|
|
104
|
-
consensusClientVersion: ClientVersion,
|
|
105
|
-
executionClientVersion: ClientVersion | null | undefined,
|
|
106
|
-
opts: {private?: boolean; graffitiAppend?: boolean}
|
|
107
|
-
): string {
|
|
108
|
-
if (userGraffiti === undefined) {
|
|
109
|
-
return getDefaultGraffiti(consensusClientVersion, executionClientVersion, opts);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (opts.graffitiAppend === false) {
|
|
113
|
-
return userGraffiti;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return appendClientInfoToGraffiti(userGraffiti, consensusClientVersion, executionClientVersion, opts);
|
|
117
|
-
}
|