@lodestar/beacon-node 1.50.0-dev.8ba9d1dd30 → 1.50.0-dev.9c7f3c203f

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 (179) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -1
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/index.js +13 -0
  6. package/lib/api/impl/beacon/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  8. package/lib/api/impl/beacon/state/index.js +23 -3
  9. package/lib/api/impl/beacon/state/index.js.map +1 -1
  10. package/lib/api/impl/debug/index.d.ts.map +1 -1
  11. package/lib/api/impl/debug/index.js +26 -18
  12. package/lib/api/impl/debug/index.js.map +1 -1
  13. package/lib/api/impl/validator/index.d.ts +1 -0
  14. package/lib/api/impl/validator/index.d.ts.map +1 -1
  15. package/lib/api/impl/validator/index.js +23 -1
  16. package/lib/api/impl/validator/index.js.map +1 -1
  17. package/lib/chain/archiveStore/archiveStore.js +1 -1
  18. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  19. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
  20. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  21. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
  22. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  23. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
  24. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  25. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
  26. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  27. package/lib/chain/archiveStore/historicalState/worker.js +1 -1
  28. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  29. package/lib/chain/archiveStore/interface.d.ts +1 -1
  30. package/lib/chain/archiveStore/interface.d.ts.map +1 -1
  31. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
  32. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  34. package/lib/chain/blocks/verifyBlock.js +1 -1
  35. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  36. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
  37. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  38. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
  39. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  40. package/lib/chain/chain.d.ts +7 -4
  41. package/lib/chain/chain.d.ts.map +1 -1
  42. package/lib/chain/chain.js +47 -14
  43. package/lib/chain/chain.js.map +1 -1
  44. package/lib/chain/emitter.d.ts +7 -1
  45. package/lib/chain/emitter.d.ts.map +1 -1
  46. package/lib/chain/emitter.js +5 -0
  47. package/lib/chain/emitter.js.map +1 -1
  48. package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
  49. package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
  50. package/lib/chain/errors/envelopeReconstructionError.js +2 -0
  51. package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
  52. package/lib/chain/errors/gossipValidation.d.ts +1 -0
  53. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  54. package/lib/chain/errors/gossipValidation.js +1 -0
  55. package/lib/chain/errors/gossipValidation.js.map +1 -1
  56. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  57. package/lib/chain/forkChoice/index.js +2 -0
  58. package/lib/chain/forkChoice/index.js.map +1 -1
  59. package/lib/chain/interface.d.ts +3 -2
  60. package/lib/chain/interface.d.ts.map +1 -1
  61. package/lib/chain/opPools/proposerPreferencesPool.d.ts +4 -4
  62. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  63. package/lib/chain/opPools/proposerPreferencesPool.js +14 -11
  64. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  65. package/lib/chain/options.d.ts +2 -1
  66. package/lib/chain/options.d.ts.map +1 -1
  67. package/lib/chain/options.js +1 -1
  68. package/lib/chain/options.js.map +1 -1
  69. package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
  70. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  71. package/lib/chain/regen/regen.d.ts +2 -0
  72. package/lib/chain/regen/regen.d.ts.map +1 -1
  73. package/lib/chain/regen/regen.js +10 -4
  74. package/lib/chain/regen/regen.js.map +1 -1
  75. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
  76. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  77. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
  78. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  79. package/lib/chain/validatorMonitor.d.ts +5 -1
  80. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  81. package/lib/chain/validatorMonitor.js +64 -54
  82. package/lib/chain/validatorMonitor.js.map +1 -1
  83. package/lib/execution/engine/disabled.d.ts +0 -2
  84. package/lib/execution/engine/disabled.d.ts.map +1 -1
  85. package/lib/execution/engine/disabled.js +0 -6
  86. package/lib/execution/engine/disabled.js.map +1 -1
  87. package/lib/execution/engine/http.d.ts +1 -3
  88. package/lib/execution/engine/http.d.ts.map +1 -1
  89. package/lib/execution/engine/http.js +2 -17
  90. package/lib/execution/engine/http.js.map +1 -1
  91. package/lib/execution/engine/interface.d.ts +2 -4
  92. package/lib/execution/engine/interface.d.ts.map +1 -1
  93. package/lib/execution/engine/mock.d.ts +0 -2
  94. package/lib/execution/engine/mock.d.ts.map +1 -1
  95. package/lib/execution/engine/mock.js +0 -8
  96. package/lib/execution/engine/mock.js.map +1 -1
  97. package/lib/execution/engine/types.d.ts +0 -8
  98. package/lib/execution/engine/types.d.ts.map +1 -1
  99. package/lib/execution/engine/types.js.map +1 -1
  100. package/lib/metrics/metrics.d.ts +7 -3
  101. package/lib/metrics/metrics.d.ts.map +1 -1
  102. package/lib/metrics/metrics.js +4 -4
  103. package/lib/metrics/metrics.js.map +1 -1
  104. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  105. package/lib/network/processor/gossipHandlers.js +3 -0
  106. package/lib/network/processor/gossipHandlers.js.map +1 -1
  107. package/lib/network/processor/gossipValidatorFn.d.ts +4 -2
  108. package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
  109. package/lib/network/processor/gossipValidatorFn.js +6 -2
  110. package/lib/network/processor/gossipValidatorFn.js.map +1 -1
  111. package/lib/network/processor/index.d.ts +8 -0
  112. package/lib/network/processor/index.d.ts.map +1 -1
  113. package/lib/network/processor/index.js +49 -2
  114. package/lib/network/processor/index.js.map +1 -1
  115. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
  116. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  117. package/lib/node/nodejs.d.ts.map +1 -1
  118. package/lib/node/nodejs.js +25 -3
  119. package/lib/node/nodejs.js.map +1 -1
  120. package/lib/sync/range/batch.d.ts +6 -0
  121. package/lib/sync/range/batch.d.ts.map +1 -1
  122. package/lib/sync/range/batch.js +10 -0
  123. package/lib/sync/range/batch.js.map +1 -1
  124. package/lib/sync/range/chain.d.ts.map +1 -1
  125. package/lib/sync/range/chain.js +5 -1
  126. package/lib/sync/range/chain.js.map +1 -1
  127. package/lib/sync/range/range.d.ts.map +1 -1
  128. package/lib/sync/range/range.js +50 -18
  129. package/lib/sync/range/range.js.map +1 -1
  130. package/lib/sync/unknownBlock.d.ts.map +1 -1
  131. package/lib/sync/unknownBlock.js +5 -4
  132. package/lib/sync/unknownBlock.js.map +1 -1
  133. package/lib/util/execution.d.ts +9 -11
  134. package/lib/util/execution.d.ts.map +1 -1
  135. package/lib/util/execution.js +5 -1
  136. package/lib/util/execution.js.map +1 -1
  137. package/package.json +17 -17
  138. package/src/api/impl/beacon/blocks/index.ts +7 -4
  139. package/src/api/impl/beacon/index.ts +15 -0
  140. package/src/api/impl/beacon/state/index.ts +31 -3
  141. package/src/api/impl/debug/index.ts +28 -19
  142. package/src/api/impl/validator/index.ts +24 -2
  143. package/src/chain/archiveStore/archiveStore.ts +1 -1
  144. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
  145. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
  146. package/src/chain/archiveStore/historicalState/types.ts +2 -2
  147. package/src/chain/archiveStore/historicalState/worker.ts +1 -2
  148. package/src/chain/archiveStore/interface.ts +1 -1
  149. package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
  150. package/src/chain/blocks/verifyBlock.ts +1 -0
  151. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
  152. package/src/chain/chain.ts +64 -17
  153. package/src/chain/emitter.ts +7 -1
  154. package/src/chain/errors/envelopeReconstructionError.ts +3 -0
  155. package/src/chain/errors/gossipValidation.ts +1 -0
  156. package/src/chain/forkChoice/index.ts +2 -0
  157. package/src/chain/interface.ts +3 -2
  158. package/src/chain/opPools/proposerPreferencesPool.ts +14 -10
  159. package/src/chain/options.ts +3 -3
  160. package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
  161. package/src/chain/regen/regen.ts +12 -4
  162. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
  163. package/src/chain/validatorMonitor.ts +75 -57
  164. package/src/execution/engine/disabled.ts +0 -8
  165. package/src/execution/engine/http.ts +1 -30
  166. package/src/execution/engine/interface.ts +2 -6
  167. package/src/execution/engine/mock.ts +1 -19
  168. package/src/execution/engine/types.ts +0 -10
  169. package/src/metrics/metrics.ts +21 -6
  170. package/src/network/processor/gossipHandlers.ts +3 -0
  171. package/src/network/processor/gossipValidatorFn.ts +12 -2
  172. package/src/network/processor/index.ts +55 -2
  173. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
  174. package/src/node/nodejs.ts +31 -3
  175. package/src/sync/range/batch.ts +11 -0
  176. package/src/sync/range/chain.ts +6 -1
  177. package/src/sync/range/range.ts +50 -18
  178. package/src/sync/unknownBlock.ts +5 -4
  179. package/src/util/execution.ts +16 -8
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.8ba9d1dd30",
14
+ "version": "1.50.0-dev.9c7f3c203f",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -104,12 +104,12 @@
104
104
  "@chainsafe/discv5": "^12.0.2",
105
105
  "@chainsafe/enr": "^6.0.2",
106
106
  "@chainsafe/libp2p-noise": "^17.0.0",
107
- "@chainsafe/libp2p-quic": "^2.1.4",
107
+ "@chainsafe/libp2p-quic": "^2.1.5",
108
108
  "@chainsafe/lodestar-z": "catalog:",
109
109
  "@chainsafe/persistent-merkle-tree": "^1.3.1",
110
110
  "@chainsafe/prometheus-gc-stats": "^1.0.0",
111
111
  "@chainsafe/snappy-wasm": "^0.5.2",
112
- "@chainsafe/ssz": "^1.8.0",
112
+ "@chainsafe/ssz": "^1.8.1",
113
113
  "@chainsafe/threads": "^1.11.3",
114
114
  "@crate-crypto/node-eth-kzg": "0.9.1",
115
115
  "@fastify/bearer-auth": "^10.0.1",
@@ -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.8ba9d1dd30",
131
- "@lodestar/config": "^1.50.0-dev.8ba9d1dd30",
132
- "@lodestar/db": "^1.50.0-dev.8ba9d1dd30",
133
- "@lodestar/fork-choice": "^1.50.0-dev.8ba9d1dd30",
134
- "@lodestar/logger": "^1.50.0-dev.8ba9d1dd30",
135
- "@lodestar/params": "^1.50.0-dev.8ba9d1dd30",
136
- "@lodestar/reqresp": "^1.50.0-dev.8ba9d1dd30",
137
- "@lodestar/state-transition": "^1.50.0-dev.8ba9d1dd30",
138
- "@lodestar/types": "^1.50.0-dev.8ba9d1dd30",
139
- "@lodestar/utils": "^1.50.0-dev.8ba9d1dd30",
140
- "@lodestar/validator": "^1.50.0-dev.8ba9d1dd30",
130
+ "@lodestar/api": "^1.50.0-dev.9c7f3c203f",
131
+ "@lodestar/config": "^1.50.0-dev.9c7f3c203f",
132
+ "@lodestar/db": "^1.50.0-dev.9c7f3c203f",
133
+ "@lodestar/fork-choice": "^1.50.0-dev.9c7f3c203f",
134
+ "@lodestar/logger": "^1.50.0-dev.9c7f3c203f",
135
+ "@lodestar/params": "^1.50.0-dev.9c7f3c203f",
136
+ "@lodestar/reqresp": "^1.50.0-dev.9c7f3c203f",
137
+ "@lodestar/state-transition": "^1.50.0-dev.9c7f3c203f",
138
+ "@lodestar/types": "^1.50.0-dev.9c7f3c203f",
139
+ "@lodestar/utils": "^1.50.0-dev.9c7f3c203f",
140
+ "@lodestar/validator": "^1.50.0-dev.9c7f3c203f",
141
141
  "@multiformats/multiaddr": "^13.0.1",
142
142
  "datastore-core": "^12.0.1",
143
143
  "datastore-level": "^13.0.1",
@@ -158,8 +158,8 @@
158
158
  "devDependencies": {
159
159
  "@libp2p/interface-internal": "^3.1.12",
160
160
  "@libp2p/logger": "^6.2.13",
161
- "@lodestar/spec-test-util": "^1.50.0-dev.8ba9d1dd30",
162
- "@lodestar/test-utils": "^1.50.0-dev.8ba9d1dd30",
161
+ "@lodestar/spec-test-util": "^1.50.0-dev.9c7f3c203f",
162
+ "@lodestar/test-utils": "^1.50.0-dev.9c7f3c203f",
163
163
  "@types/js-yaml": "^4.0.5",
164
164
  "@types/qs": "^6.9.7",
165
165
  "@types/tmp": "^0.2.3",
@@ -174,5 +174,5 @@
174
174
  "beacon",
175
175
  "blockchain"
176
176
  ],
177
- "gitHead": "907b54b401ad965598b59cb97ed1a47fb30a2549"
177
+ "gitHead": "5d256138961c5b1558e698a884d3496c9bad74a6"
178
178
  }
@@ -1118,10 +1118,13 @@ export function getBeaconBlockApi({
1118
1118
  : await chain.getExecutionPayloadEnvelope(slot, blockRootHex);
1119
1119
  } catch (e) {
1120
1120
  if (e instanceof EnvelopeReconstructionError) {
1121
- throw new ApiError(
1122
- e.type.code === EnvelopeReconstructionErrorCode.ENGINE_UNAVAILABLE ? 503 : 500,
1123
- `Failed to reconstruct execution payload envelope: ${e.message}`
1124
- );
1121
+ // The envelope is archived but our EL cannot serve it right now (EL down, or it does not
1122
+ // have the body / block access list): 503 so clients retry or ask another node, rather than
1123
+ // 500 for what is not a beacon node fault. A body root mismatch is a local inconsistency.
1124
+ const unavailable =
1125
+ e.type.code === EnvelopeReconstructionErrorCode.ENGINE_UNAVAILABLE ||
1126
+ e.type.code === EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE;
1127
+ throw new ApiError(unavailable ? 503 : 500, `Failed to reconstruct execution payload envelope: ${e.message}`);
1125
1128
  }
1126
1129
  throw e;
1127
1130
  }
@@ -1,5 +1,9 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ApplicationMethods} from "@lodestar/api/server";
3
+ import {isForkPostGloas} from "@lodestar/params";
4
+ import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
5
+ import {toRootHex} from "@lodestar/utils";
6
+ import {ApiError} from "../errors.js";
3
7
  import {ApiModules} from "../types.js";
4
8
  import {getBeaconBlockApi} from "./blocks/index.js";
5
9
  import {getBeaconPoolApi} from "./pool/index.js";
@@ -31,5 +35,16 @@ export function getBeaconApi(
31
35
  },
32
36
  };
33
37
  },
38
+
39
+ async getProposerPreferences({slot, dependentRoot}) {
40
+ // Preferences are broadcast up to one epoch ahead of their proposal slot, the first ones before Gloas is active
41
+ const fork = config.getForkName(slot ?? computeStartSlotAtEpoch(chain.clock.currentEpoch + 1));
42
+ if (!isForkPostGloas(fork)) {
43
+ throw new ApiError(400, `Proposer preferences are not supported before Gloas fork=${fork}`);
44
+ }
45
+
46
+ const preferences = chain.proposerPreferencesPool.getAll(slot, dependentRoot && toRootHex(dependentRoot));
47
+ return {data: preferences, meta: {version: fork}};
48
+ },
34
49
  };
35
50
  }
@@ -1,6 +1,11 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ApplicationMethods} from "@lodestar/api/server";
3
- import {EPOCHS_PER_HISTORICAL_VECTOR, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params";
3
+ import {
4
+ EPOCHS_PER_HISTORICAL_VECTOR,
5
+ MIN_SEED_LOOKAHEAD,
6
+ SLOTS_PER_EPOCH,
7
+ SYNC_COMMITTEE_SUBNET_SIZE,
8
+ } from "@lodestar/params";
4
9
  import {
5
10
  IBeaconStateView,
6
11
  computeEpochAtSlot,
@@ -11,7 +16,7 @@ import {
11
16
  isStatePostFulu,
12
17
  isStatePostGloas,
13
18
  } from "@lodestar/state-transition";
14
- import {ValidatorIndex, getBuilderStatus, getValidatorStatus, ssz} from "@lodestar/types";
19
+ import {ValidatorIndex, getBuilderStatus, getValidatorStatus, mapToGeneralStatus, ssz} from "@lodestar/types";
15
20
  import {ApiError} from "../../errors.js";
16
21
  import {ApiModules} from "../../types.js";
17
22
  import {assertUniqueItems} from "../../utils.js";
@@ -100,7 +105,8 @@ export function getBeaconStateApi({
100
105
  if (resp.valid) {
101
106
  const validatorIndex = resp.validatorIndex;
102
107
  const validator = state.getValidator(validatorIndex);
103
- if (statuses.length && !statuses.includes(getValidatorStatus(validator, currentEpoch))) {
108
+ const status = getValidatorStatus(validator, currentEpoch);
109
+ if (statuses.length && !statuses.includes(status) && !statuses.includes(mapToGeneralStatus(status))) {
104
110
  continue;
105
111
  }
106
112
  const validatorResponse = toValidatorResponse(
@@ -374,6 +380,28 @@ export function getBeaconStateApi({
374
380
  };
375
381
  },
376
382
 
383
+ async getStatePtc({stateId, slot}) {
384
+ const {state, executionOptimistic, finalized} = await getState(stateId);
385
+ if (!isStatePostGloas(state)) {
386
+ throw new ApiError(400, `Cannot retrieve PTC for pre-gloas state fork=${state.forkName}`);
387
+ }
388
+
389
+ const usedSlot = slot ?? state.slot;
390
+ const epoch = computeEpochAtSlot(usedSlot);
391
+ const stateEpoch = computeEpochAtSlot(state.slot);
392
+ if (epoch < config.GLOAS_FORK_EPOCH) {
393
+ throw new ApiError(400, `Cannot retrieve PTC for pre-gloas slot=${usedSlot}`);
394
+ }
395
+ if (epoch < stateEpoch - 1 || epoch > stateEpoch + MIN_SEED_LOOKAHEAD) {
396
+ throw new ApiError(400, `Slot ${usedSlot} is outside the PTC window of state epoch ${stateEpoch}`);
397
+ }
398
+
399
+ return {
400
+ data: {slot: usedSlot, validators: Array.from(state.getPayloadTimelinessCommittee(usedSlot))},
401
+ meta: {executionOptimistic, finalized},
402
+ };
403
+ },
404
+
377
405
  async getPendingDeposits({stateId}, context) {
378
406
  const {state, executionOptimistic, finalized} = await getState(stateId);
379
407
  const fork = state.forkName;
@@ -4,7 +4,7 @@ import {ExecutionStatus, PayloadStatus} from "@lodestar/fork-choice";
4
4
  import {ForkPostDeneb, ZERO_HASH_HEX, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
5
5
  import {computeTimeAtSlot} from "@lodestar/state-transition";
6
6
  import {BeaconState, DataColumnSidecar, DataColumnSidecars, type SignedBeaconBlock, sszTypesFor} from "@lodestar/types";
7
- import {toRootHex} from "@lodestar/utils";
7
+ import {fromHex, toRootHex} from "@lodestar/utils";
8
8
  import {getBlobKzgCommitments} from "../../../util/dataColumns.js";
9
9
  import {isOptimisticBlock} from "../../../util/forkChoice.js";
10
10
  import {getStateSlotFromBytes} from "../../../util/multifork.js";
@@ -74,46 +74,55 @@ export function getDebugApi({
74
74
 
75
75
  async getDebugForkChoiceV2() {
76
76
  const {forkChoice} = chain;
77
+ const nodes = forkChoice.getAllNodes();
78
+ const head = forkChoice.getHead();
77
79
  return {
78
80
  data: {
79
81
  justifiedCheckpoint: forkChoice.getJustifiedCheckpoint(),
80
82
  finalizedCheckpoint: forkChoice.getFinalizedCheckpoint(),
81
- forkChoiceNodes: forkChoice.getAllNodes().map((node) => {
82
- // Payload-specific fields apply only to a revealed Gloas payload = the FULL variant of a
83
- // Gloas block
84
- const ptc = node.payloadStatus === PayloadStatus.FULL ? forkChoice.getPTCVoteCounts(node.blockRoot) : null;
83
+ forkChoiceNodes: nodes.map((node) => {
84
+ const parent = node.parent === undefined ? undefined : nodes[node.parent];
85
+ const ptc = forkChoice.getPTCVoteCounts(node.blockRoot);
85
86
  return {
86
- payloadStatus: toPayloadStatusName(node.payloadStatus),
87
87
  slot: node.slot,
88
88
  blockRoot: node.blockRoot,
89
- parentRoot: node.parentRoot,
89
+ payloadStatus: toPayloadStatusName(node.payloadStatus),
90
+ parentRoot:
91
+ node.parentBlockHash !== null && node.payloadStatus !== PayloadStatus.PENDING
92
+ ? node.blockRoot
93
+ : node.parentRoot,
94
+ parentPayloadStatus: parent === undefined ? null : toPayloadStatusName(parent.payloadStatus),
95
+ justifiedCheckpoint: {epoch: node.justifiedEpoch, root: fromHex(node.justifiedRoot)},
96
+ finalizedCheckpoint: {epoch: node.finalizedEpoch, root: fromHex(node.finalizedRoot)},
90
97
  weight: node.weight,
91
98
  validity: toForkChoiceValidity(node.executionStatus),
92
99
  executionBlockHash: node.executionPayloadBlockHash ?? ZERO_HASH_HEX,
100
+ payloadAttesterCount: ptc?.attesterCount ?? 0,
101
+ payloadAvailabilityYesCount: ptc?.payloadPresentCount ?? 0,
102
+ payloadDataAvailabilityYesCount: ptc?.dataAvailableCount ?? 0,
93
103
  extraData: {
104
+ attestationScore: node.attestationScore,
94
105
  executionOptimistic: isOptimisticBlock(node),
106
+ gasLimit:
107
+ node.payloadStatus === PayloadStatus.FULL && node.executionStatus !== ExecutionStatus.PreMerge
108
+ ? node.executionPayloadGasLimit
109
+ : null,
95
110
  timestamp: computeTimeAtSlot(config, node.slot, chain.genesisTime),
111
+ stateRoot: node.stateRoot,
96
112
  target: node.targetRoot,
97
- justifiedEpoch: node.justifiedEpoch,
98
- finalizedEpoch: node.finalizedEpoch,
99
113
  unrealizedJustifiedEpoch: node.unrealizedJustifiedEpoch,
114
+ unrealizedJustifiedRoot: node.unrealizedJustifiedRoot,
100
115
  unrealizedFinalizedEpoch: node.unrealizedFinalizedEpoch,
101
- payloadAttesterCount: ptc?.attesterCount ?? null,
102
- payloadAvailabilityYesCount: ptc?.payloadPresentCount ?? null,
103
- payloadDataAvailabilityYesCount: ptc?.dataAvailableCount ?? null,
104
- gasLimit:
105
- node.payloadStatus === PayloadStatus.FULL && "executionPayloadGasLimit" in node
106
- ? node.executionPayloadGasLimit
107
- : null,
116
+ unrealizedFinalizedRoot: node.unrealizedFinalizedRoot,
108
117
  },
109
118
  };
110
119
  }),
111
120
  extraData: {
112
- unrealizedJustifiedCheckpoint: forkChoice.getUnrealizedJustifiedCheckpoint(),
113
- unrealizedFinalizedCheckpoint: forkChoice.getUnrealizedFinalizedCheckpoint(),
121
+ head: {blockRoot: head.blockRoot, payloadStatus: toPayloadStatusName(head.payloadStatus)},
114
122
  proposerBoostRoot: forkChoice.getProposerBoostRoot(),
115
123
  previousProposerBoostRoot: forkChoice.getPreviousProposerBoostRoot(),
116
- headRoot: forkChoice.getHeadRoot(),
124
+ unrealizedJustifiedCheckpoint: forkChoice.getUnrealizedJustifiedCheckpoint(),
125
+ unrealizedFinalizedCheckpoint: forkChoice.getUnrealizedFinalizedCheckpoint(),
117
126
  },
118
127
  },
119
128
  };
@@ -189,6 +189,7 @@ export enum EngineBlockSelectionReason {
189
189
  BuilderTimeout = "builder_timeout",
190
190
  BuilderPending = "builder_pending",
191
191
  BuilderNoBid = "builder_no_bid",
192
+ BuilderCircuitBreaker = "builder_circuit_breaker",
192
193
  BuilderCensorship = "builder_censorship",
193
194
  BlockValue = "block_value",
194
195
  EnginePreferred = "engine_preferred",
@@ -1160,8 +1161,21 @@ export function getValidatorApi(
1160
1161
  : {}),
1161
1162
  };
1162
1163
 
1163
- // handle shouldOverrideBuilder separately
1164
- if (
1164
+ if (circuitBreakerActive && engineResult.status === "fulfilled") {
1165
+ source = ProducedBlockSource.engine;
1166
+ bestResult = engineResult;
1167
+ metrics?.blockProductionSelectionResults.inc({
1168
+ source: ProducedBlockSource.engine,
1169
+ reason: EngineBlockSelectionReason.BuilderCircuitBreaker,
1170
+ });
1171
+ logger.info("Selected local block: builder circuit breaker is active", {
1172
+ reason: EngineBlockSelectionReason.BuilderCircuitBreaker,
1173
+ ...logCtx,
1174
+ durationMs: engineResult.durationMs,
1175
+ ...getBlockValueLogInfo(engineResult.value),
1176
+ });
1177
+ } else if (
1178
+ // handle shouldOverrideBuilder separately
1165
1179
  engineResult.status === "fulfilled" &&
1166
1180
  engineResult.value.shouldOverrideBuilder &&
1167
1181
  (builderBidExpected || bidBlockResult.status === "fulfilled")
@@ -1286,6 +1300,10 @@ export function getValidatorApi(
1286
1300
  blobs: blobsBundle.blobs,
1287
1301
  };
1288
1302
 
1303
+ if (chain.opts.persistProducedPayloadEnvelopes) {
1304
+ chain.persistExecutionPayloadEnvelope(blockContents.executionPayloadEnvelope);
1305
+ }
1306
+
1289
1307
  return {
1290
1308
  data: blockContents,
1291
1309
  meta: {version: fork, consensusBlockValue, executionPayloadValue, executionPayloadIncluded: true},
@@ -2173,6 +2191,10 @@ export function getValidatorApi(
2173
2191
  blockHash: toRootHex(executionPayload.blockHash),
2174
2192
  });
2175
2193
 
2194
+ if (chain.opts.persistProducedPayloadEnvelopes) {
2195
+ chain.persistExecutionPayloadEnvelope(envelope);
2196
+ }
2197
+
2176
2198
  return {
2177
2199
  data: envelope,
2178
2200
  meta: {
@@ -148,7 +148,7 @@ export class ArchiveStore {
148
148
  genesisTime: this.chain.clock.genesisTime,
149
149
  dbLocation: this.opts.dbName,
150
150
  dataColumnDir: this.opts.dataColumnDir,
151
- nativeStateView: this.opts.nativeStateView ?? false,
151
+ nativeStateTransition: this.opts.nativeStateTransition ?? false,
152
152
  },
153
153
  config: this.chain.config,
154
154
  metrics: this.metrics,
@@ -17,7 +17,7 @@ export async function getNearestState(
17
17
  slot: number,
18
18
  config: BeaconConfig,
19
19
  db: IBeaconDb,
20
- nativeStateView: boolean
20
+ nativeStateTransition: boolean
21
21
  ): Promise<IBeaconStateView> {
22
22
  const stateBytesArr = await db.stateArchive.binaries({limit: 1, lte: slot, reverse: true});
23
23
  if (!stateBytesArr.length) {
@@ -25,9 +25,7 @@ export async function getNearestState(
25
25
  }
26
26
 
27
27
  const stateBytes = stateBytesArr[0];
28
- return nativeStateView
29
- ? createBeaconStateViewForHistoricalRegen({useNative: true, stateBytes})
30
- : createBeaconStateViewForHistoricalRegen({useNative: false, config, stateBytes});
28
+ return createBeaconStateViewForHistoricalRegen({nativeStateTransition: nativeStateTransition, config, stateBytes});
31
29
  }
32
30
 
33
31
  /**
@@ -37,13 +35,13 @@ export async function getHistoricalState(
37
35
  slot: number,
38
36
  config: BeaconConfig,
39
37
  db: IBeaconDb,
40
- nativeStateView: boolean,
38
+ nativeStateTransition: boolean,
41
39
  metrics?: HistoricalStateRegenMetrics
42
40
  ): Promise<Uint8Array> {
43
41
  const regenTimer = metrics?.regenTime.startTimer();
44
42
 
45
43
  const loadStateTimer = metrics?.loadStateTime.startTimer();
46
- let state = await getNearestState(slot, config, db, nativeStateView).catch((e) => {
44
+ let state = await getNearestState(slot, config, db, nativeStateTransition).catch((e) => {
47
45
  metrics?.regenErrorCount.inc({reason: RegenErrorType.loadState});
48
46
  throw e;
49
47
  });
@@ -54,7 +52,7 @@ export async function getHistoricalState(
54
52
  for await (const block of db.blockArchive.valuesStream({gt: state.slot, lte: slot})) {
55
53
  try {
56
54
  state = state.stateTransition(
57
- block,
55
+ {block},
58
56
  {
59
57
  verifyProposer: false,
60
58
  verifySignatures: false,
@@ -36,7 +36,7 @@ export class HistoricalStateRegen implements HistoricalStateWorkerApi {
36
36
  dataColumnDir: modules.opts.dataColumnDir,
37
37
  metricsEnabled: Boolean(modules.metrics),
38
38
  loggerOpts: modules.logger.toOpts(),
39
- nativeStateView: modules.opts.nativeStateView,
39
+ nativeStateTransition: modules.opts.nativeStateTransition,
40
40
  };
41
41
 
42
42
  const worker = new Worker(path.join(WORKER_DIR, "worker.js"), {
@@ -8,7 +8,7 @@ export type HistoricalStateRegenInitModules = {
8
8
  genesisTime: number;
9
9
  dbLocation: string;
10
10
  dataColumnDir: string;
11
- nativeStateView: boolean;
11
+ nativeStateTransition: boolean;
12
12
  };
13
13
  config: BeaconConfig;
14
14
  logger: LoggerNode;
@@ -29,7 +29,7 @@ export type HistoricalStateWorkerData = {
29
29
  dataColumnDir: string;
30
30
  metricsEnabled: boolean;
31
31
  loggerOpts: LoggerNodeOpts;
32
- nativeStateView: boolean;
32
+ nativeStateTransition: boolean;
33
33
  };
34
34
 
35
35
  export type HistoricalStateWorkerApi = {
@@ -36,7 +36,6 @@ const abortController = new AbortController();
36
36
  const metricsRegister = workerData.metricsEnabled ? new RegistryMetricCreator() : null;
37
37
  let historicalStateRegenMetrics: HistoricalStateRegenMetrics | undefined;
38
38
  let queueMetrics: QueueMetrics | undefined;
39
-
40
39
  if (metricsRegister) {
41
40
  const closeMetrics = collectNodeJSMetrics(metricsRegister, "lodestar_historical_state_worker_");
42
41
  abortController.signal.addEventListener("abort", closeMetrics, {once: true});
@@ -65,7 +64,7 @@ const api: HistoricalStateWorkerApi = {
65
64
  historicalStateRegenMetrics?.regenRequestCount.inc();
66
65
 
67
66
  const stateBytes = await queue.push<Uint8Array>(() =>
68
- getHistoricalState(slot, config, db, workerData.nativeStateView, historicalStateRegenMetrics)
67
+ getHistoricalState(slot, config, db, workerData.nativeStateTransition, historicalStateRegenMetrics)
69
68
  );
70
69
  const result = Transfer(stateBytes, [stateBytes.buffer]) as unknown as Uint8Array;
71
70
 
@@ -25,7 +25,7 @@ export type ArchiveStoreOpts = StatesArchiveOpts & {
25
25
  archiveDataEpochs?: number;
26
26
  pruneHistory?: boolean;
27
27
  serveHistoricalState?: boolean;
28
- nativeStateView?: boolean;
28
+ nativeStateTransition?: boolean;
29
29
  };
30
30
 
31
31
  export type ProposalStats = {
@@ -22,8 +22,9 @@ export enum PayloadEnvelopeInputSource {
22
22
  * - finalized: below the finalized slot
23
23
  * - prune: explicit prune by root
24
24
  * - cap: insertion-order backstop cap (MAX_PAYLOAD_ENVELOPE_INPUT_CACHE_SIZE)
25
+ * - invalid: the cached envelope failed import with a definitive invalid verdict
25
26
  */
26
- export type PayloadEnvelopeInputPruneReason = "belowParent" | "finalized" | "remove" | "cap";
27
+ export type PayloadEnvelopeInputPruneReason = "belowParent" | "finalized" | "remove" | "cap" | "invalid";
27
28
 
28
29
  export type SourceMeta = {
29
30
  source: PayloadEnvelopeInputSource;
@@ -188,6 +188,7 @@ export async function verifyBlocksInEpoch(
188
188
  blockInputs,
189
189
  // hack availability for state transition eval as availability is separately determined
190
190
  blocks.map(() => DataAvailabilityStatus.Available),
191
+ this.serializedCache,
191
192
  this.logger,
192
193
  this.metrics,
193
194
  this.validatorMonitor,
@@ -7,6 +7,7 @@ import {
7
7
  import {ErrorAborted, Logger, byteArrayEquals} from "@lodestar/utils";
8
8
  import {Metrics} from "../../metrics/index.js";
9
9
  import {nextEventLoop} from "../../util/eventLoop.js";
10
+ import {SerializedCache} from "../../util/serializedCache.js";
10
11
  import {BlockError, BlockErrorCode} from "../errors/index.js";
11
12
  import {BlockProcessOpts} from "../options.js";
12
13
  import {ValidatorMonitor} from "../validatorMonitor.js";
@@ -25,6 +26,7 @@ export async function verifyBlocksStateTransitionOnly(
25
26
  preState0: IBeaconStateView,
26
27
  blocks: IBlockInput[],
27
28
  dataAvailabilityStatuses: DataAvailabilityStatus[],
29
+ serializedCache: SerializedCache,
28
30
  logger: Logger,
29
31
  metrics: Metrics | null,
30
32
  validatorMonitor: ValidatorMonitor | null,
@@ -37,7 +39,8 @@ export async function verifyBlocksStateTransitionOnly(
37
39
 
38
40
  for (let i = 0; i < blocks.length; i++) {
39
41
  const {validProposerSignature, validSignatures} = opts;
40
- const block = blocks[i].getBlock();
42
+ const blockInput = blocks[i];
43
+ const block = blockInput.getBlock();
41
44
  const preState = i === 0 ? preState0 : postStates[i - 1];
42
45
  const dataAvailabilityStatus = dataAvailabilityStatuses[i];
43
46
 
@@ -47,7 +50,7 @@ export async function verifyBlocksStateTransitionOnly(
47
50
  let postState: IBeaconStateView;
48
51
  try {
49
52
  postState = preState.stateTransition(
50
- block,
53
+ {block, ssz: serializedCache.get(block)},
51
54
  {
52
55
  // NOTE: Assume valid for now while sending payload to execution engine in parallel
53
56
  // Latter verifyBlocksInEpoch() will make sure that payload is indeed valid
@@ -60,7 +63,7 @@ export async function verifyBlocksStateTransitionOnly(
60
63
  verifySignatures: !useBlsBatchVerify && !validSignatures,
61
64
  dontTransferCache: false,
62
65
  },
63
- {metrics, validatorMonitor}
66
+ {metrics: metrics?.stateTransition, validatorMonitor}
64
67
  );
65
68
  } catch (e) {
66
69
  throw new BlockError(block, {
@@ -71,7 +71,7 @@ import {BufferPool} from "../util/bufferPool.js";
71
71
  import {Clock, ClockEvent, IClock} from "../util/clock.js";
72
72
  import {CustodyConfig, getValidatorsCustodyRequirement} from "../util/dataColumns.js";
73
73
  import {callInNextEventLoop} from "../util/eventLoop.js";
74
- import {ReconstructMismatchPolicy, isRebuildMiss, reconstructExecutionPayloadEnvelopes} from "../util/execution.js";
74
+ import {ReconstructMissPolicy, isRebuildMiss, reconstructExecutionPayloadEnvelopes} from "../util/execution.js";
75
75
  import {ensureDir, writeIfNotExist} from "../util/file.js";
76
76
  import {isOptimisticBlock} from "../util/forkChoice.js";
77
77
  import {JobItemQueue} from "../util/queue/itemQueue.js";
@@ -244,6 +244,7 @@ export class BeaconChain implements IBeaconChain {
244
244
  protected readonly blockProcessor: BlockProcessor;
245
245
  protected readonly payloadEnvelopeProcessor: PayloadEnvelopeProcessor;
246
246
  protected readonly db: IBeaconDb;
247
+ private genesisStateRoot?: RootHex | null;
247
248
  // this is only available if nHistoricalStates is enabled
248
249
  private readonly cpStateDatastore?: CPStateDatastore;
249
250
  private abortController = new AbortController();
@@ -432,6 +433,7 @@ export class BeaconChain implements IBeaconChain {
432
433
  blockStateCache,
433
434
  checkpointStateCache,
434
435
  seenBlockInputCache: this.seenBlockInputCache,
436
+ serializedCache: this.serializedCache,
435
437
  db,
436
438
  metrics,
437
439
  validatorMonitor,
@@ -702,6 +704,37 @@ export class BeaconChain implements IBeaconChain {
702
704
  stateRoot: RootHex,
703
705
  opts?: StateGetOpts
704
706
  ): Promise<{state: IBeaconStateView | Uint8Array; executionOptimistic: boolean; finalized: boolean} | null> {
707
+ const finalizedBlock = this.forkChoice.getFinalizedBlock();
708
+ const finalizedCheckpoint = this.forkChoice.getFinalizedCheckpoint();
709
+ // Checkpoint state only equals the block post-state if the block is at the epoch start slot
710
+ if (
711
+ finalizedBlock.stateRoot === stateRoot &&
712
+ finalizedBlock.slot === computeStartSlotAtEpoch(finalizedCheckpoint.epoch)
713
+ ) {
714
+ const state = this.regen.getCheckpointStateSync({
715
+ epoch: finalizedCheckpoint.epoch,
716
+ rootHex: finalizedCheckpoint.rootHex,
717
+ });
718
+ if (state) {
719
+ return {
720
+ state,
721
+ executionOptimistic: isOptimisticBlock(finalizedBlock),
722
+ finalized: finalizedCheckpoint.epoch !== GENESIS_EPOCH,
723
+ };
724
+ }
725
+ }
726
+
727
+ if (this.genesisStateRoot === undefined) {
728
+ const genesisBlock = await this.db.blockArchive.get(GENESIS_SLOT);
729
+ this.genesisStateRoot = genesisBlock ? toRootHex(genesisBlock.message.stateRoot) : null;
730
+ }
731
+ if (this.genesisStateRoot === stateRoot) {
732
+ const state = await this.db.stateArchive.getBinary(GENESIS_SLOT);
733
+ if (state) {
734
+ return {state, executionOptimistic: false, finalized: finalizedCheckpoint.epoch !== GENESIS_EPOCH};
735
+ }
736
+ }
737
+
705
738
  if (opts?.allowRegen) {
706
739
  const state = await this.regen.getState(stateRoot, RegenCaller.restApi);
707
740
  const block = this.forkChoice.getBlockDefaultStatus(
@@ -934,12 +967,13 @@ export class BeaconChain implements IBeaconChain {
934
967
 
935
968
  /**
936
969
  * Batch variant: archived header envelopes are rebuilt 32 per EL round-trip. Aligned with `requests`.
937
- * A body root mismatch is a local inconsistency: `"throw"` surfaces it, `"omit"` logs it and
938
- * returns null for that entry, for peer-facing paths where the spec allows omission.
970
+ * An archived envelope that cannot be rebuilt (EL-unavailable body or body root mismatch) is not the
971
+ * same as an unknown one: `"throw"` surfaces it, `"omit"` returns null for that entry, for
972
+ * peer-facing paths where the spec allows omission.
939
973
  */
940
974
  async getSerializedExecutionPayloadEnvelopes(
941
975
  requests: {blockSlot: Slot; blockRootHex: RootHex}[],
942
- onMismatch: ReconstructMismatchPolicy = "throw"
976
+ onMiss: ReconstructMissPolicy = "throw"
943
977
  ): Promise<(Uint8Array | null)[]> {
944
978
  const out: (Uint8Array | null)[] = new Array(requests.length).fill(null);
945
979
  const headerEnvelopes: gloas.SignedExecutionPayloadHeaderEnvelope[] = [];
@@ -978,14 +1012,14 @@ export class BeaconChain implements IBeaconChain {
978
1012
  for (let j = 0; j < rebuilt.length; j++) {
979
1013
  const result = rebuilt[j];
980
1014
  if (isRebuildMiss(result)) {
981
- if (result.reason === "mismatch") {
982
- if (onMismatch === "throw") throw result.error;
983
- this.logger.debug(
984
- "Archived envelope failed body root check against EL bodies",
985
- {slot: result.slot},
986
- result.error
987
- );
988
- }
1015
+ if (onMiss === "throw") throw result.error;
1016
+ this.logger.debug(
1017
+ result.reason === "mismatch"
1018
+ ? "Archived envelope failed body root check against EL bodies"
1019
+ : "EL cannot serve bodies for archived envelope, omitting",
1020
+ {slot: result.slot},
1021
+ result.error
1022
+ );
989
1023
  continue;
990
1024
  }
991
1025
  out[headerEnvelopeIdxs[j]] = ssz.gloas.SignedExecutionPayloadEnvelope.serialize(result);
@@ -1016,10 +1050,7 @@ export class BeaconChain implements IBeaconChain {
1016
1050
  const [result] = await reconstructExecutionPayloadEnvelopes(this.executionEngine, this.metrics, [
1017
1051
  archived.headerEnvelope,
1018
1052
  ]);
1019
- if (isRebuildMiss(result)) {
1020
- if (result.reason === "mismatch") throw result.error;
1021
- return null;
1022
- }
1053
+ if (isRebuildMiss(result)) throw result.error;
1023
1054
  return result;
1024
1055
  }
1025
1056
 
@@ -1505,6 +1536,22 @@ export class BeaconChain implements IBeaconChain {
1505
1536
  }
1506
1537
  }
1507
1538
 
1539
+ persistExecutionPayloadEnvelope(envelope: gloas.ExecutionPayloadEnvelope): void {
1540
+ const blockRoot = toRootHex(envelope.beaconBlockRoot);
1541
+ void this.persistSszObject(
1542
+ "ExecutionPayloadEnvelope",
1543
+ ssz.gloas.ExecutionPayloadEnvelope.serialize(envelope),
1544
+ blockRoot,
1545
+ "produced_execution_payload_envelope"
1546
+ ).catch((e) => {
1547
+ this.logger.error(
1548
+ "Error persisting produced execution payload envelope",
1549
+ {slot: envelope.payload.slotNumber, blockRoot},
1550
+ e as Error
1551
+ );
1552
+ });
1553
+ }
1554
+
1508
1555
  /**
1509
1556
  * Invalid state root error is critical and it causes the node to stale most of the time so we want to always
1510
1557
  * persist preState, postState and block for further investigation.
@@ -1700,7 +1747,7 @@ export class BeaconChain implements IBeaconChain {
1700
1747
  // remove date suffixes in file name, and check duplicate to avoid redundant persistence
1701
1748
  await writeIfNotExist(filepath, bytes);
1702
1749
 
1703
- this.logger.debug("Persisted invalid ssz object", {id: logStr, filepath});
1750
+ this.logger.debug("Persisted SSZ object", {id: logStr, filepath});
1704
1751
  }
1705
1752
 
1706
1753
  private onScrapeMetrics(metrics: Metrics): void {