@lodestar/beacon-node 1.50.0-dev.67e8d54549 → 1.50.0-dev.6a7dbb4d5d

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/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.50.0-dev.67e8d54549",
14
+ "version": "1.50.0-dev.6a7dbb4d5d",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -127,17 +127,17 @@
127
127
  "@libp2p/prometheus-metrics": "^5.0.28",
128
128
  "@libp2p/tcp": "^11.0.27",
129
129
  "@libp2p/utils": "^7.4.0",
130
- "@lodestar/api": "^1.50.0-dev.67e8d54549",
131
- "@lodestar/config": "^1.50.0-dev.67e8d54549",
132
- "@lodestar/db": "^1.50.0-dev.67e8d54549",
133
- "@lodestar/fork-choice": "^1.50.0-dev.67e8d54549",
134
- "@lodestar/logger": "^1.50.0-dev.67e8d54549",
135
- "@lodestar/params": "^1.50.0-dev.67e8d54549",
136
- "@lodestar/reqresp": "^1.50.0-dev.67e8d54549",
137
- "@lodestar/state-transition": "^1.50.0-dev.67e8d54549",
138
- "@lodestar/types": "^1.50.0-dev.67e8d54549",
139
- "@lodestar/utils": "^1.50.0-dev.67e8d54549",
140
- "@lodestar/validator": "^1.50.0-dev.67e8d54549",
130
+ "@lodestar/api": "^1.50.0-dev.6a7dbb4d5d",
131
+ "@lodestar/config": "^1.50.0-dev.6a7dbb4d5d",
132
+ "@lodestar/db": "^1.50.0-dev.6a7dbb4d5d",
133
+ "@lodestar/fork-choice": "^1.50.0-dev.6a7dbb4d5d",
134
+ "@lodestar/logger": "^1.50.0-dev.6a7dbb4d5d",
135
+ "@lodestar/params": "^1.50.0-dev.6a7dbb4d5d",
136
+ "@lodestar/reqresp": "^1.50.0-dev.6a7dbb4d5d",
137
+ "@lodestar/state-transition": "^1.50.0-dev.6a7dbb4d5d",
138
+ "@lodestar/types": "^1.50.0-dev.6a7dbb4d5d",
139
+ "@lodestar/utils": "^1.50.0-dev.6a7dbb4d5d",
140
+ "@lodestar/validator": "^1.50.0-dev.6a7dbb4d5d",
141
141
  "@multiformats/multiaddr": "^13.0.1",
142
142
  "datastore-core": "^12.0.1",
143
143
  "datastore-level": "^13.0.1",
@@ -159,8 +159,8 @@
159
159
  "@ethereumjs/rlp": "^4.0.1",
160
160
  "@libp2p/interface-internal": "^3.1.12",
161
161
  "@libp2p/logger": "^6.2.13",
162
- "@lodestar/spec-test-util": "^1.50.0-dev.67e8d54549",
163
- "@lodestar/test-utils": "^1.50.0-dev.67e8d54549",
162
+ "@lodestar/spec-test-util": "^1.50.0-dev.6a7dbb4d5d",
163
+ "@lodestar/test-utils": "^1.50.0-dev.6a7dbb4d5d",
164
164
  "@types/js-yaml": "^4.0.5",
165
165
  "@types/qs": "^6.9.7",
166
166
  "@types/tmp": "^0.2.3",
@@ -175,5 +175,5 @@
175
175
  "beacon",
176
176
  "blockchain"
177
177
  ],
178
- "gitHead": "dcb2dd45248feda5a6df84f617982ac298c9c8ac"
178
+ "gitHead": "e059d098420f9b8dc5fa7b16de95db114ff4ac62"
179
179
  }
@@ -692,8 +692,14 @@ export function getBeaconBlockApi({
692
692
  },
693
693
 
694
694
  async getBlockRoot({blockId}) {
695
- // Fast path: From head state already available in memory get historical blockRoot
696
- const slot = typeof blockId === "string" ? parseInt(blockId) : blockId;
695
+ // Fast path: From head state already available in memory get historical blockRoot.
696
+ // A 0x id is a root. parseInt() accepts that prefix as hex and can return a slot.
697
+ const slot =
698
+ typeof blockId === "number"
699
+ ? blockId
700
+ : blockId.toLowerCase().startsWith("0x")
701
+ ? Number.NaN
702
+ : parseInt(blockId, 10);
697
703
  if (!Number.isNaN(slot)) {
698
704
  const head = chain.forkChoice.getHead();
699
705
 
@@ -1940,7 +1940,7 @@ export function getValidatorApi(
1940
1940
  Array.from(new Set(subscriptions.map(({validatorIndex}) => validatorIndex)))
1941
1941
  );
1942
1942
  } catch (e) {
1943
- logger.error("Error tracking attached validators", {count: subscriptions.length}, e as Error);
1943
+ logger.warn("Error tracking attached validators", {count: subscriptions.length}, e as Error);
1944
1944
  }
1945
1945
 
1946
1946
  notWhileSyncing(chain, sync.state);
@@ -2006,6 +2006,13 @@ export function getValidatorApi(
2006
2006
  },
2007
2007
 
2008
2008
  async prepareBeaconProposer({proposers}) {
2009
+ if (isForkPostGloas(config.getForkName(chain.clock.currentSlot))) {
2010
+ throw new ApiError(
2011
+ 410,
2012
+ "prepareBeaconProposer is no longer supported from gloas, submit signed proposer preferences via POST /eth/v1/validator/proposer_preferences instead"
2013
+ );
2014
+ }
2015
+
2009
2016
  await chain.updateBeaconProposerData(chain.clock.currentEpoch, proposers);
2010
2017
  },
2011
2018
 
@@ -2040,6 +2047,13 @@ export function getValidatorApi(
2040
2047
  },
2041
2048
 
2042
2049
  async registerValidator({registrations}) {
2050
+ if (isForkPostGloas(config.getForkName(chain.clock.currentSlot))) {
2051
+ throw new ApiError(
2052
+ 410,
2053
+ "registerValidator is no longer supported from gloas, submit builder preferences via POST /eth/v1/validator/builder_preferences instead"
2054
+ );
2055
+ }
2056
+
2043
2057
  if (!chain.executionBuilder) {
2044
2058
  throw Error("External builder not configured");
2045
2059
  }
@@ -2075,11 +2089,14 @@ export function getValidatorApi(
2075
2089
 
2076
2090
  async submitProposerPreferences({signedProposerPreferences}) {
2077
2091
  const failures: FailureList = [];
2092
+ // Preferences submitted here are signed by validators using this node, gossiped ones are not
2093
+ const attachedValidatorIndices: ValidatorIndex[] = [];
2078
2094
 
2079
2095
  await Promise.all(
2080
2096
  signedProposerPreferences.map(async (signed, i) => {
2081
2097
  try {
2082
2098
  await validateGossipProposerPreferences(chain, signed);
2099
+ attachedValidatorIndices.push(signed.message.validatorIndex);
2083
2100
 
2084
2101
  chain.proposerPreferencesPool.add(signed);
2085
2102
  await network.publishProposerPreferences(signed);
@@ -2095,6 +2112,7 @@ export function getValidatorApi(
2095
2112
  };
2096
2113
 
2097
2114
  if (e instanceof ProposerPreferencesError && e.type.code === ProposerPreferencesErrorCode.ALREADY_KNOWN) {
2115
+ attachedValidatorIndices.push(signed.message.validatorIndex);
2098
2116
  logger.debug("Ignoring known signed proposer preferences", logCtx);
2099
2117
  return;
2100
2118
  }
@@ -2108,6 +2126,14 @@ export function getValidatorApi(
2108
2126
  })
2109
2127
  );
2110
2128
 
2129
+ if (attachedValidatorIndices.length > 0) {
2130
+ try {
2131
+ await chain.updateAttachedValidators(chain.clock.currentEpoch, attachedValidatorIndices);
2132
+ } catch (e) {
2133
+ logger.warn("Error tracking attached validators", {count: attachedValidatorIndices.length}, e as Error);
2134
+ }
2135
+ }
2136
+
2111
2137
  if (failures.length > 0) {
2112
2138
  throw new IndexedError("Error processing signed proposer preferences", failures);
2113
2139
  }
@@ -15,10 +15,18 @@ export class BeaconProposerCache {
15
15
  this.feeRecipientByValidatorIndex.set(validatorIndex, {epoch, feeRecipient});
16
16
  }
17
17
 
18
- /** Track a validator that uses this node for duties, keeping the fee recipient it may have registered */
19
- touch(epoch: Epoch, validatorIndex: number): void {
20
- const feeRecipient = this.feeRecipientByValidatorIndex.get(validatorIndex)?.feeRecipient;
21
- this.feeRecipientByValidatorIndex.set(validatorIndex, {epoch, feeRecipient});
18
+ /**
19
+ * Track a validator that uses this node for duties, keeping the fee recipient it may have
20
+ * registered. Returns true if the validator was not tracked before.
21
+ */
22
+ track(epoch: Epoch, validatorIndex: number): boolean {
23
+ const entry = this.feeRecipientByValidatorIndex.get(validatorIndex);
24
+ if (entry !== undefined) {
25
+ entry.epoch = epoch;
26
+ return false;
27
+ }
28
+ this.feeRecipientByValidatorIndex.set(validatorIndex, {epoch});
29
+ return true;
22
30
  }
23
31
 
24
32
  has(validatorIndex: number): boolean {
@@ -1937,16 +1937,15 @@ export class BeaconChain implements IBeaconChain {
1937
1937
  }
1938
1938
 
1939
1939
  async updateAttachedValidators(epoch: Epoch, validatorIndices: ValidatorIndex[]): Promise<void> {
1940
- const previousValidatorCount = this.beaconProposerCache.getValidatorIndices().length;
1941
-
1940
+ let hasNewValidators = false;
1942
1941
  for (const validatorIndex of validatorIndices) {
1943
- this.beaconProposerCache.touch(epoch, validatorIndex);
1942
+ if (this.beaconProposerCache.track(epoch, validatorIndex)) {
1943
+ hasNewValidators = true;
1944
+ }
1944
1945
  }
1945
1946
 
1946
- const newValidatorCount = this.beaconProposerCache.getValidatorIndices().length;
1947
-
1948
1947
  // Only update validator custody if we discovered new validators
1949
- if (newValidatorCount > previousValidatorCount) {
1948
+ if (hasNewValidators) {
1950
1949
  const finalizedCheckpoint = this.forkChoice.getFinalizedCheckpoint();
1951
1950
  await this.updateValidatorsCustodyRequirement(finalizedCheckpoint);
1952
1951
  }
@@ -77,6 +77,7 @@ import {IExecutionBuilder, IExecutionEngine, PayloadAttributes, PayloadId} from
77
77
  import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
78
78
  import {fromGraffitiBytes} from "../../util/graffiti.js";
79
79
  import {kzg} from "../../util/kzg.js";
80
+ import {BeaconProposerCache} from "../beaconProposerCache.js";
80
81
  import type {BeaconChain} from "../chain.js";
81
82
  import {CommonBlockBody} from "../interface.js";
82
83
  import {ProposerPreferencesPool} from "../opPools/index.js";
@@ -286,19 +287,12 @@ export async function produceBlockBody<T extends BlockType>(
286
287
  // this into a completely separate function and have pre/post gloas more separated
287
288
  const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
288
289
  const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice);
289
- const pooledPreferences = getPooledProposerPreferences(this, blockSlot, parentBlock.blockRoot);
290
- const feeRecipient =
291
- requestedFeeRecipient ??
292
- (pooledPreferences !== null
293
- ? toHex(pooledPreferences.feeRecipient)
294
- : this.beaconProposerCache.getOrDefault(proposerIndex));
295
- const feeRecipientType = requestedFeeRecipient
296
- ? "requested"
297
- : pooledPreferences !== null
298
- ? "preferences"
299
- : this.beaconProposerCache.get(proposerIndex)
300
- ? "cached"
301
- : "default";
290
+ const {feeRecipient, feeRecipientType} = getProposerFeeRecipient(
291
+ requestedFeeRecipient,
292
+ getPooledProposerPreferences(this, blockSlot, parentBlock.blockRoot),
293
+ this.beaconProposerCache,
294
+ proposerIndex
295
+ );
302
296
  Object.assign(logMeta, {feeRecipientType, feeRecipient});
303
297
 
304
298
  const endExecutionPayload = this.metrics?.executionBlockProductionTimeSteps.startTimer();
@@ -452,13 +446,12 @@ export async function produceBlockBody<T extends BlockType>(
452
446
 
453
447
  const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
454
448
  const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice);
455
- const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex);
456
- const feeRecipientType = requestedFeeRecipient
457
- ? "requested"
458
- : this.beaconProposerCache.get(proposerIndex)
459
- ? "cached"
460
- : "default";
461
-
449
+ const {feeRecipient, feeRecipientType} = getProposerFeeRecipient(
450
+ requestedFeeRecipient,
451
+ null,
452
+ this.beaconProposerCache,
453
+ proposerIndex
454
+ );
462
455
  Object.assign(logMeta, {feeRecipientType, feeRecipient});
463
456
 
464
457
  if (blockType === BlockType.Blinded) {
@@ -1014,6 +1007,26 @@ function getProposerTargetGasLimit(
1014
1007
  return BigInt(parentPayloadVariant.executionPayloadGasLimit);
1015
1008
  }
1016
1009
 
1010
+ /** The proposer's request wins over its signed preferences, which win over its registered proposer data */
1011
+ function getProposerFeeRecipient(
1012
+ requestedFeeRecipient: string | undefined,
1013
+ pooledPreferences: gloas.ProposerPreferences | null,
1014
+ beaconProposerCache: BeaconProposerCache,
1015
+ proposerIndex: ValidatorIndex
1016
+ ): {feeRecipient: string; feeRecipientType: "requested" | "preferences" | "cached" | "default"} {
1017
+ if (requestedFeeRecipient !== undefined) {
1018
+ return {feeRecipient: requestedFeeRecipient, feeRecipientType: "requested"};
1019
+ }
1020
+ if (pooledPreferences !== null) {
1021
+ return {feeRecipient: toHex(pooledPreferences.feeRecipient), feeRecipientType: "preferences"};
1022
+ }
1023
+ const cachedFeeRecipient = beaconProposerCache.get(proposerIndex);
1024
+ if (cachedFeeRecipient !== undefined) {
1025
+ return {feeRecipient: cachedFeeRecipient, feeRecipientType: "cached"};
1026
+ }
1027
+ return {feeRecipient: beaconProposerCache.getOrDefault(proposerIndex), feeRecipientType: "default"};
1028
+ }
1029
+
1017
1030
  /**
1018
1031
  * The preferences the proposer of `slot` signed for the branch of `parentBlockRoot`, the same
1019
1032
  * `(slot, dependent_root)` lookup as gossip bid validation. Null if none are pooled.