@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.
Files changed (76) hide show
  1. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/pool/index.js +43 -2
  3. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  4. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/state/index.js +3 -46
  6. package/lib/api/impl/beacon/state/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts +2 -11
  8. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/utils.js +3 -39
  10. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  11. package/lib/api/impl/validator/index.d.ts.map +1 -1
  12. package/lib/api/impl/validator/index.js +6 -46
  13. package/lib/api/impl/validator/index.js.map +1 -1
  14. package/lib/chain/blocks/importBlock.js +1 -1
  15. package/lib/chain/blocks/importBlock.js.map +1 -1
  16. package/lib/chain/chain.d.ts +2 -1
  17. package/lib/chain/chain.d.ts.map +1 -1
  18. package/lib/chain/chain.js +3 -1
  19. package/lib/chain/chain.js.map +1 -1
  20. package/lib/chain/interface.d.ts +2 -1
  21. package/lib/chain/interface.d.ts.map +1 -1
  22. package/lib/chain/interface.js.map +1 -1
  23. package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -2
  24. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  25. package/lib/chain/opPools/proposerPreferencesPool.js +0 -3
  26. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  27. package/lib/chain/options.d.ts +0 -1
  28. package/lib/chain/options.d.ts.map +1 -1
  29. package/lib/chain/options.js +0 -1
  30. package/lib/chain/options.js.map +1 -1
  31. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  32. package/lib/chain/produceBlock/produceBlockBody.js +12 -4
  33. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  34. package/lib/chain/seenCache/index.d.ts +1 -0
  35. package/lib/chain/seenCache/index.d.ts.map +1 -1
  36. package/lib/chain/seenCache/index.js +1 -0
  37. package/lib/chain/seenCache/index.js.map +1 -1
  38. package/lib/chain/seenCache/seenProposerPreferences.d.ts +16 -0
  39. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  40. package/lib/chain/seenCache/seenProposerPreferences.js +26 -0
  41. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  42. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  43. package/lib/chain/validation/proposerPreferences.js +3 -11
  44. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  45. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  46. package/lib/network/gossip/gossipsub.js +1 -2
  47. package/lib/network/gossip/gossipsub.js.map +1 -1
  48. package/lib/network/gossip/topic.d.ts +20 -782
  49. package/lib/network/gossip/topic.d.ts.map +1 -1
  50. package/lib/network/gossip/topic.js +3 -23
  51. package/lib/network/gossip/topic.js.map +1 -1
  52. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  53. package/lib/network/processor/gossipHandlers.js +3 -2
  54. package/lib/network/processor/gossipHandlers.js.map +1 -1
  55. package/lib/util/graffiti.d.ts +0 -15
  56. package/lib/util/graffiti.d.ts.map +1 -1
  57. package/lib/util/graffiti.js +0 -55
  58. package/lib/util/graffiti.js.map +1 -1
  59. package/package.json +14 -14
  60. package/src/api/impl/beacon/pool/index.ts +53 -0
  61. package/src/api/impl/beacon/state/index.ts +1 -52
  62. package/src/api/impl/beacon/state/utils.ts +4 -57
  63. package/src/api/impl/validator/index.ts +5 -54
  64. package/src/chain/blocks/importBlock.ts +1 -1
  65. package/src/chain/chain.ts +3 -0
  66. package/src/chain/interface.ts +2 -0
  67. package/src/chain/opPools/proposerPreferencesPool.ts +1 -5
  68. package/src/chain/options.ts +0 -2
  69. package/src/chain/produceBlock/produceBlockBody.ts +17 -9
  70. package/src/chain/seenCache/index.ts +1 -0
  71. package/src/chain/seenCache/seenProposerPreferences.ts +32 -0
  72. package/src/chain/validation/proposerPreferences.ts +3 -12
  73. package/src/network/gossip/gossipsub.ts +1 -2
  74. package/src/network/gossip/topic.ts +4 -28
  75. package/src/network/processor/gossipHandlers.ts +2 -1
  76. 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, getBuilderStatus, getValidatorStatus, ssz} from "@lodestar/types";
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, 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";
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) || validatorIndex < 0) {
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 {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
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
- getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
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
- getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
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 canonical block is seen at slot. Return 204 so vc can skip casting payload attestation.
1125
- return {data: undefined, meta: {version: fork}, status: 204};
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.AtEpochBoundary;
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
  }
@@ -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);
@@ -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, ValidatorIndex, gloas} from "@lodestar/types";
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);
@@ -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
- const ssePayloadAttributes = {
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
- } 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
-
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.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
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
- // Repeated check - deals with race-condition between preferences submissions
109
- if (chain.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
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, getAllowedTopics, getCoreTopicsAtFork, stringifyGossipTopic} from "./topic.js";
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; subscribeAllColumnSubnets?: boolean; disableLightClientServer?: 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, opts.subscribeAllColumnSubnets));
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 = subscribeAllColumnSubnets
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: config.getForkName(signedProposerPreferences.message.proposalSlot),
1278
+ version: ForkName.gloas,
1278
1279
  data: signedProposerPreferences,
1279
1280
  });
1280
1281
  },
@@ -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
- }