@lodestar/beacon-node 1.36.0-dev.a2a22d66ef → 1.36.0-dev.aec301c48f

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 (259) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +41 -22
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/config/constants.d.ts +4 -2
  5. package/lib/api/impl/config/constants.d.ts.map +1 -1
  6. package/lib/api/impl/config/constants.js +6 -3
  7. package/lib/api/impl/config/constants.js.map +1 -1
  8. package/lib/api/impl/lodestar/index.d.ts +5 -0
  9. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  10. package/lib/api/impl/lodestar/index.js +35 -10
  11. package/lib/api/impl/lodestar/index.js.map +1 -1
  12. package/lib/api/impl/node/utils.js +1 -1
  13. package/lib/api/impl/node/utils.js.map +1 -1
  14. package/lib/api/impl/validator/index.d.ts.map +1 -1
  15. package/lib/api/impl/validator/index.js +2 -2
  16. package/lib/api/impl/validator/index.js.map +1 -1
  17. package/lib/chain/archiveStore/archiveStore.d.ts +9 -0
  18. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  19. package/lib/chain/archiveStore/archiveStore.js +24 -0
  20. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  21. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts +7 -0
  22. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  23. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +31 -5
  24. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  25. package/lib/chain/beaconProposerCache.d.ts +3 -0
  26. package/lib/chain/beaconProposerCache.d.ts.map +1 -1
  27. package/lib/chain/beaconProposerCache.js +4 -6
  28. package/lib/chain/beaconProposerCache.js.map +1 -1
  29. package/lib/chain/chain.d.ts +5 -2
  30. package/lib/chain/chain.d.ts.map +1 -1
  31. package/lib/chain/chain.js +34 -18
  32. package/lib/chain/chain.js.map +1 -1
  33. package/lib/chain/errors/dataColumnSidecarError.d.ts +23 -13
  34. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  35. package/lib/chain/errors/dataColumnSidecarError.js +5 -0
  36. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  37. package/lib/chain/errors/voluntaryExitError.d.ts +16 -2
  38. package/lib/chain/errors/voluntaryExitError.d.ts.map +1 -1
  39. package/lib/chain/errors/voluntaryExitError.js +22 -1
  40. package/lib/chain/errors/voluntaryExitError.js.map +1 -1
  41. package/lib/chain/forkChoice/index.d.ts +9 -1
  42. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  43. package/lib/chain/forkChoice/index.js +109 -4
  44. package/lib/chain/forkChoice/index.js.map +1 -1
  45. package/lib/chain/interface.d.ts +2 -0
  46. package/lib/chain/interface.d.ts.map +1 -1
  47. package/lib/chain/lightClient/index.d.ts +3 -0
  48. package/lib/chain/lightClient/index.d.ts.map +1 -1
  49. package/lib/chain/lightClient/index.js +24 -13
  50. package/lib/chain/lightClient/index.js.map +1 -1
  51. package/lib/chain/opPools/syncContributionAndProofPool.d.ts +3 -1
  52. package/lib/chain/opPools/syncContributionAndProofPool.d.ts.map +1 -1
  53. package/lib/chain/opPools/syncContributionAndProofPool.js +4 -3
  54. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  55. package/lib/chain/options.d.ts +0 -2
  56. package/lib/chain/options.d.ts.map +1 -1
  57. package/lib/chain/options.js +2 -2
  58. package/lib/chain/options.js.map +1 -1
  59. package/lib/chain/stateCache/datastore/db.d.ts +12 -0
  60. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  61. package/lib/chain/stateCache/datastore/db.js +70 -0
  62. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  63. package/lib/chain/stateCache/datastore/file.d.ts +1 -0
  64. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  65. package/lib/chain/stateCache/datastore/file.js +7 -0
  66. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  67. package/lib/chain/stateCache/datastore/types.d.ts +1 -0
  68. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  69. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +16 -1
  70. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  71. package/lib/chain/stateCache/persistentCheckpointsCache.js +31 -1
  72. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  73. package/lib/chain/validation/attestation.d.ts.map +1 -1
  74. package/lib/chain/validation/attestation.js +6 -7
  75. package/lib/chain/validation/attestation.js.map +1 -1
  76. package/lib/chain/validation/block.d.ts.map +1 -1
  77. package/lib/chain/validation/block.js +1 -2
  78. package/lib/chain/validation/block.js.map +1 -1
  79. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  80. package/lib/chain/validation/dataColumnSidecar.js +45 -17
  81. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  82. package/lib/chain/validation/lightClientOptimisticUpdate.d.ts.map +1 -1
  83. package/lib/chain/validation/lightClientOptimisticUpdate.js +1 -2
  84. package/lib/chain/validation/lightClientOptimisticUpdate.js.map +1 -1
  85. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  86. package/lib/chain/validation/voluntaryExit.js +5 -4
  87. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  88. package/lib/constants/constants.d.ts +0 -5
  89. package/lib/constants/constants.d.ts.map +1 -1
  90. package/lib/constants/constants.js +0 -5
  91. package/lib/constants/constants.js.map +1 -1
  92. package/lib/constants/network.d.ts +0 -14
  93. package/lib/constants/network.d.ts.map +1 -1
  94. package/lib/constants/network.js +0 -15
  95. package/lib/constants/network.js.map +1 -1
  96. package/lib/index.d.ts +2 -0
  97. package/lib/index.d.ts.map +1 -1
  98. package/lib/index.js +2 -0
  99. package/lib/index.js.map +1 -1
  100. package/lib/metrics/metrics/lodestar.d.ts +11 -0
  101. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  102. package/lib/metrics/metrics/lodestar.js +19 -1
  103. package/lib/metrics/metrics/lodestar.js.map +1 -1
  104. package/lib/network/core/networkCore.d.ts.map +1 -1
  105. package/lib/network/core/networkCore.js +5 -1
  106. package/lib/network/core/networkCore.js.map +1 -1
  107. package/lib/network/core/networkCoreWorker.js +8 -8
  108. package/lib/network/core/networkCoreWorker.js.map +1 -1
  109. package/lib/network/core/networkCoreWorkerHandler.js +1 -1
  110. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  111. package/lib/network/discv5/utils.d.ts.map +1 -1
  112. package/lib/network/discv5/utils.js +1 -2
  113. package/lib/network/discv5/utils.js.map +1 -1
  114. package/lib/network/discv5/worker.js +2 -7
  115. package/lib/network/discv5/worker.js.map +1 -1
  116. package/lib/network/events.d.ts +1 -0
  117. package/lib/network/events.d.ts.map +1 -1
  118. package/lib/network/gossip/encoding.js +1 -1
  119. package/lib/network/gossip/encoding.js.map +1 -1
  120. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  121. package/lib/network/gossip/gossipsub.js +1 -7
  122. package/lib/network/gossip/gossipsub.js.map +1 -1
  123. package/lib/network/gossip/snappy_bun.d.ts +3 -0
  124. package/lib/network/gossip/snappy_bun.d.ts.map +1 -0
  125. package/lib/network/gossip/snappy_bun.js +3 -0
  126. package/lib/network/gossip/snappy_bun.js.map +1 -0
  127. package/lib/network/metadata.d.ts +1 -1
  128. package/lib/network/metadata.d.ts.map +1 -1
  129. package/lib/network/metadata.js +1 -0
  130. package/lib/network/metadata.js.map +1 -1
  131. package/lib/network/options.d.ts +0 -1
  132. package/lib/network/options.d.ts.map +1 -1
  133. package/lib/network/options.js.map +1 -1
  134. package/lib/network/peers/discover.js +2 -2
  135. package/lib/network/peers/discover.js.map +1 -1
  136. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  137. package/lib/network/processor/gossipHandlers.js +14 -8
  138. package/lib/network/processor/gossipHandlers.js.map +1 -1
  139. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  140. package/lib/network/reqresp/ReqRespBeaconNode.js +3 -1
  141. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  142. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts +2 -1
  143. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  144. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -3
  145. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  146. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +5 -5
  147. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  148. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js +4 -4
  149. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js.map +1 -1
  150. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts +2 -1
  151. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  152. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +9 -1
  153. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  154. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts +2 -1
  155. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  156. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +9 -1
  157. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  158. package/lib/network/reqresp/handlers/index.js +6 -6
  159. package/lib/network/reqresp/handlers/index.js.map +1 -1
  160. package/lib/network/reqresp/types.d.ts +1 -0
  161. package/lib/network/reqresp/types.d.ts.map +1 -1
  162. package/lib/network/subnets/attnetsService.d.ts +1 -1
  163. package/lib/network/subnets/attnetsService.js +6 -6
  164. package/lib/network/subnets/attnetsService.js.map +1 -1
  165. package/lib/network/subnets/util.d.ts +3 -2
  166. package/lib/network/subnets/util.d.ts.map +1 -1
  167. package/lib/network/subnets/util.js +6 -6
  168. package/lib/network/subnets/util.js.map +1 -1
  169. package/lib/node/nodejs.d.ts +2 -1
  170. package/lib/node/nodejs.d.ts.map +1 -1
  171. package/lib/node/nodejs.js +2 -1
  172. package/lib/node/nodejs.js.map +1 -1
  173. package/lib/sync/range/range.d.ts.map +1 -1
  174. package/lib/sync/range/range.js +2 -1
  175. package/lib/sync/range/range.js.map +1 -1
  176. package/lib/sync/utils/downloadByRange.d.ts +58 -13
  177. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  178. package/lib/sync/utils/downloadByRange.js +201 -82
  179. package/lib/sync/utils/downloadByRange.js.map +1 -1
  180. package/lib/sync/utils/remoteSyncType.d.ts +2 -1
  181. package/lib/sync/utils/remoteSyncType.d.ts.map +1 -1
  182. package/lib/sync/utils/remoteSyncType.js +19 -4
  183. package/lib/sync/utils/remoteSyncType.js.map +1 -1
  184. package/lib/util/blobs.d.ts +1 -1
  185. package/lib/util/blobs.d.ts.map +1 -1
  186. package/lib/util/blobs.js +53 -20
  187. package/lib/util/blobs.js.map +1 -1
  188. package/lib/util/clock.d.ts.map +1 -1
  189. package/lib/util/clock.js +3 -4
  190. package/lib/util/clock.js.map +1 -1
  191. package/lib/util/profile.d.ts +6 -4
  192. package/lib/util/profile.d.ts.map +1 -1
  193. package/lib/util/profile.js +40 -3
  194. package/lib/util/profile.js.map +1 -1
  195. package/lib/util/sszBytes.d.ts +2 -0
  196. package/lib/util/sszBytes.d.ts.map +1 -1
  197. package/lib/util/sszBytes.js +25 -0
  198. package/lib/util/sszBytes.js.map +1 -1
  199. package/package.json +31 -24
  200. package/src/api/impl/beacon/blocks/index.ts +47 -25
  201. package/src/api/impl/config/constants.ts +10 -4
  202. package/src/api/impl/lodestar/index.ts +42 -10
  203. package/src/api/impl/node/utils.ts +1 -1
  204. package/src/api/impl/validator/index.ts +5 -2
  205. package/src/chain/archiveStore/archiveStore.ts +27 -0
  206. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +32 -5
  207. package/src/chain/beaconProposerCache.ts +4 -8
  208. package/src/chain/chain.ts +50 -25
  209. package/src/chain/errors/dataColumnSidecarError.ts +27 -13
  210. package/src/chain/errors/voluntaryExitError.ts +30 -2
  211. package/src/chain/forkChoice/index.ts +178 -2
  212. package/src/chain/interface.ts +2 -0
  213. package/src/chain/lightClient/index.ts +26 -12
  214. package/src/chain/opPools/syncContributionAndProofPool.ts +3 -2
  215. package/src/chain/options.ts +2 -3
  216. package/src/chain/stateCache/datastore/db.ts +89 -1
  217. package/src/chain/stateCache/datastore/file.ts +8 -0
  218. package/src/chain/stateCache/datastore/types.ts +1 -0
  219. package/src/chain/stateCache/persistentCheckpointsCache.ts +45 -2
  220. package/src/chain/validation/attestation.ts +6 -7
  221. package/src/chain/validation/block.ts +1 -2
  222. package/src/chain/validation/dataColumnSidecar.ts +54 -19
  223. package/src/chain/validation/lightClientOptimisticUpdate.ts +3 -2
  224. package/src/chain/validation/voluntaryExit.ts +14 -4
  225. package/src/constants/constants.ts +0 -6
  226. package/src/constants/network.ts +0 -19
  227. package/src/index.ts +2 -0
  228. package/src/metrics/metrics/lodestar.ts +22 -1
  229. package/src/network/core/networkCore.ts +5 -1
  230. package/src/network/core/networkCoreWorker.ts +9 -9
  231. package/src/network/core/networkCoreWorkerHandler.ts +1 -1
  232. package/src/network/discv5/utils.ts +1 -2
  233. package/src/network/discv5/worker.ts +2 -7
  234. package/src/network/events.ts +1 -1
  235. package/src/network/gossip/encoding.ts +1 -1
  236. package/src/network/gossip/gossipsub.ts +1 -10
  237. package/src/network/gossip/snappy_bun.ts +2 -0
  238. package/src/network/metadata.ts +3 -1
  239. package/src/network/options.ts +0 -1
  240. package/src/network/peers/discover.ts +2 -2
  241. package/src/network/processor/gossipHandlers.ts +16 -7
  242. package/src/network/reqresp/ReqRespBeaconNode.ts +3 -1
  243. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +18 -3
  244. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +6 -6
  245. package/src/network/reqresp/handlers/blobSidecarsByRoot.ts +5 -5
  246. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +13 -1
  247. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +13 -1
  248. package/src/network/reqresp/handlers/index.ts +6 -6
  249. package/src/network/reqresp/types.ts +1 -0
  250. package/src/network/subnets/attnetsService.ts +6 -6
  251. package/src/network/subnets/util.ts +12 -12
  252. package/src/node/nodejs.ts +3 -0
  253. package/src/sync/range/range.ts +2 -1
  254. package/src/sync/utils/downloadByRange.ts +259 -103
  255. package/src/sync/utils/remoteSyncType.ts +23 -4
  256. package/src/util/blobs.ts +64 -20
  257. package/src/util/clock.ts +3 -4
  258. package/src/util/profile.ts +45 -3
  259. package/src/util/sszBytes.ts +30 -0
@@ -8,13 +8,18 @@ export enum DataColumnSidecarErrorCode {
8
8
  MISMATCHED_LENGTHS = "DATA_COLUMN_SIDECAR_ERROR_MISMATCHED_LENGTHS",
9
9
  INVALID_SUBNET = "DATA_COLUMN_SIDECAR_ERROR_INVALID_SUBNET",
10
10
  INVALID_KZG_PROOF = "DATA_COLUMN_SIDECAR_ERROR_INVALID_KZG_PROOF",
11
+ TOO_MANY_KZG_COMMITMENTS = "DATA_COLUMN_SIDECAR_ERROR_TOO_MANY_KZG_COMMITMENTS",
11
12
 
12
13
  // Validation errors when validating against an existing block
13
14
 
15
+ /** Block and sidecars header root mismatch */
16
+ INCORRECT_HEADER_ROOT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_HEADER_ROOT",
14
17
  /** Block and sidecars data column count mismatch */
15
18
  INCORRECT_SIDECAR_COUNT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_SIDECAR_COUNT",
16
19
  /** Sidecar doesn't match block */
17
20
  INCORRECT_BLOCK = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_BLOCK",
21
+ /** Sidecar cell count not as expected */
22
+ INCORRECT_CELL_COUNT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_CELL_COUNT",
18
23
  /** Sidecar kzg proof count not as expected */
19
24
  INCORRECT_KZG_COMMITMENTS_COUNT = "DATA_COLUMN_SIDECAR_ERROR_INCORRECT_KZG_COMMITMENTS_COUNT",
20
25
  /** Sidecar kzg proof count not as expected */
@@ -34,42 +39,51 @@ export enum DataColumnSidecarErrorCode {
34
39
  }
35
40
 
36
41
  export type DataColumnSidecarErrorType =
37
- | {code: DataColumnSidecarErrorCode.INVALID_INDEX; slot: Slot; columnIdx: number}
38
- | {code: DataColumnSidecarErrorCode.NO_COMMITMENTS; slot: Slot; columnIdx: number}
42
+ | {code: DataColumnSidecarErrorCode.INVALID_INDEX; slot: Slot; columnIndex: number}
43
+ | {code: DataColumnSidecarErrorCode.NO_COMMITMENTS; slot: Slot; columnIndex: number}
39
44
  | {
40
45
  code: DataColumnSidecarErrorCode.MISMATCHED_LENGTHS;
41
46
  columnLength: number;
42
47
  commitmentsLength: number;
43
48
  proofsLength: number;
44
49
  }
45
- | {code: DataColumnSidecarErrorCode.INVALID_SUBNET; columnIdx: number; gossipSubnet: SubnetID}
46
- | {code: DataColumnSidecarErrorCode.ALREADY_KNOWN; columnIdx: number; slot: Slot}
50
+ | {code: DataColumnSidecarErrorCode.INVALID_SUBNET; columnIndex: number; gossipSubnet: SubnetID}
51
+ | {
52
+ code: DataColumnSidecarErrorCode.TOO_MANY_KZG_COMMITMENTS;
53
+ slot: number;
54
+ columnIndex: number;
55
+ count: number;
56
+ limit: number;
57
+ }
58
+ | {code: DataColumnSidecarErrorCode.ALREADY_KNOWN; columnIndex: number; slot: Slot}
47
59
  | {code: DataColumnSidecarErrorCode.FUTURE_SLOT; blockSlot: Slot; currentSlot: Slot}
48
60
  | {code: DataColumnSidecarErrorCode.WOULD_REVERT_FINALIZED_SLOT; blockSlot: Slot; finalizedSlot: Slot}
49
61
  | {code: DataColumnSidecarErrorCode.PARENT_UNKNOWN; parentRoot: RootHex}
50
62
  | {code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID}
51
63
  | {code: DataColumnSidecarErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
52
- | {code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID; slot: Slot; columnIdx: number}
53
- | {code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF; slot: Slot; columnIdx: number}
64
+ | {code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID; slot: Slot; columnIndex: number}
65
+ | {code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF; slot: Slot; columnIndex: number}
54
66
  | {code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_COUNT; slot: number; expected: number; actual: number}
55
67
  | {
56
68
  code: DataColumnSidecarErrorCode.INCORRECT_BLOCK;
57
69
  slot: number;
58
- columnIdx: number;
70
+ columnIndex: number;
59
71
  expected: string;
60
72
  actual: string;
61
73
  }
62
74
  | {
63
- code: DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT;
75
+ code: DataColumnSidecarErrorCode.INCORRECT_HEADER_ROOT;
64
76
  slot: number;
65
- columnIdx: number;
66
- expected: number;
67
- actual: number;
77
+ expected: string;
78
+ actual: string;
68
79
  }
69
80
  | {
70
- code: DataColumnSidecarErrorCode.INCORRECT_KZG_PROOF_COUNT;
81
+ code:
82
+ | DataColumnSidecarErrorCode.INCORRECT_CELL_COUNT
83
+ | DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT
84
+ | DataColumnSidecarErrorCode.INCORRECT_KZG_PROOF_COUNT;
71
85
  slot: number;
72
- columnIdx: number;
86
+ columnIndex: number;
73
87
  expected: number;
74
88
  actual: number;
75
89
  }
@@ -1,13 +1,41 @@
1
+ import {VoluntaryExitValidity} from "@lodestar/state-transition";
1
2
  import {GossipActionError} from "./gossipValidation.js";
2
3
 
3
4
  export enum VoluntaryExitErrorCode {
4
5
  ALREADY_EXISTS = "VOLUNTARY_EXIT_ERROR_ALREADY_EXISTS",
5
- INVALID = "VOLUNTARY_EXIT_ERROR_INVALID",
6
+ INACTIVE = "VOLUNTARY_EXIT_ERROR_INACTIVE",
7
+ ALREADY_EXITED = "VOLUNTARY_EXIT_ERROR_ALREADY_EXITED",
8
+ EARLY_EPOCH = "VOLUNTARY_EXIT_ERROR_EARLY_EPOCH",
9
+ SHORT_TIME_ACTIVE = "VOLUNTARY_EXIT_ERROR_SHORT_TIME_ACTIVE",
10
+ PENDING_WITHDRAWALS = "VOLUNTARY_EXIT_ERROR_PENDING_WITHDRAWALS",
6
11
  INVALID_SIGNATURE = "VOLUNTARY_EXIT_ERROR_INVALID_SIGNATURE",
7
12
  }
8
13
  export type VoluntaryExitErrorType =
9
14
  | {code: VoluntaryExitErrorCode.ALREADY_EXISTS}
10
- | {code: VoluntaryExitErrorCode.INVALID}
15
+ | {code: VoluntaryExitErrorCode.INACTIVE}
16
+ | {code: VoluntaryExitErrorCode.ALREADY_EXITED}
17
+ | {code: VoluntaryExitErrorCode.EARLY_EPOCH}
18
+ | {code: VoluntaryExitErrorCode.SHORT_TIME_ACTIVE}
19
+ | {code: VoluntaryExitErrorCode.PENDING_WITHDRAWALS}
11
20
  | {code: VoluntaryExitErrorCode.INVALID_SIGNATURE};
12
21
 
13
22
  export class VoluntaryExitError extends GossipActionError<VoluntaryExitErrorType> {}
23
+
24
+ export function voluntaryExitValidityToErrorCode(
25
+ validity: Exclude<VoluntaryExitValidity, VoluntaryExitValidity.valid>
26
+ ): VoluntaryExitErrorCode {
27
+ switch (validity) {
28
+ case VoluntaryExitValidity.inactive:
29
+ return VoluntaryExitErrorCode.INACTIVE;
30
+ case VoluntaryExitValidity.alreadyExited:
31
+ return VoluntaryExitErrorCode.ALREADY_EXITED;
32
+ case VoluntaryExitValidity.earlyEpoch:
33
+ return VoluntaryExitErrorCode.EARLY_EPOCH;
34
+ case VoluntaryExitValidity.shortTimeActive:
35
+ return VoluntaryExitErrorCode.SHORT_TIME_ACTIVE;
36
+ case VoluntaryExitValidity.pendingWithdrawals:
37
+ return VoluntaryExitErrorCode.PENDING_WITHDRAWALS;
38
+ case VoluntaryExitValidity.invalidSignature:
39
+ return VoluntaryExitErrorCode.INVALID_SIGNATURE;
40
+ }
41
+ }
@@ -5,17 +5,22 @@ import {
5
5
  ForkChoiceStore,
6
6
  JustifiedBalancesGetter,
7
7
  ProtoArray,
8
+ ProtoBlock,
8
9
  ForkChoiceOpts as RawForkChoiceOpts,
9
10
  } from "@lodestar/fork-choice";
11
+ import {ZERO_HASH_HEX} from "@lodestar/params";
10
12
  import {
11
13
  CachedBeaconStateAllForks,
12
14
  DataAvailabilityStatus,
13
15
  computeAnchorCheckpoint,
16
+ computeEpochAtSlot,
17
+ computeStartSlotAtEpoch,
18
+ getBlockRootAtSlot,
14
19
  getEffectiveBalanceIncrementsZeroInactive,
15
20
  isExecutionStateType,
16
21
  isMergeTransitionComplete,
17
22
  } from "@lodestar/state-transition";
18
- import {Slot} from "@lodestar/types";
23
+ import {Slot, ssz} from "@lodestar/types";
19
24
  import {Logger, toRootHex} from "@lodestar/utils";
20
25
  import {GENESIS_SLOT} from "../../constants/index.js";
21
26
  import {Metrics} from "../../metrics/index.js";
@@ -35,6 +40,43 @@ export enum ForkchoiceCaller {
35
40
  * Fork Choice extended with a ChainEventEmitter
36
41
  */
37
42
  export function initializeForkChoice(
43
+ config: ChainForkConfig,
44
+ emitter: ChainEventEmitter,
45
+ currentSlot: Slot,
46
+ state: CachedBeaconStateAllForks,
47
+ isFinalizedState: boolean,
48
+ opts: ForkChoiceOpts,
49
+ justifiedBalancesGetter: JustifiedBalancesGetter,
50
+ metrics: Metrics | null,
51
+ logger?: Logger
52
+ ): ForkChoice {
53
+ return isFinalizedState
54
+ ? initializeForkChoiceFromFinalizedState(
55
+ config,
56
+ emitter,
57
+ currentSlot,
58
+ state,
59
+ opts,
60
+ justifiedBalancesGetter,
61
+ metrics,
62
+ logger
63
+ )
64
+ : initializeForkChoiceFromUnfinalizedState(
65
+ config,
66
+ emitter,
67
+ currentSlot,
68
+ state,
69
+ opts,
70
+ justifiedBalancesGetter,
71
+ metrics,
72
+ logger
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Initialize forkchoice from a finalized state.
78
+ */
79
+ export function initializeForkChoiceFromFinalizedState(
38
80
  config: ChainForkConfig,
39
81
  emitter: ChainEventEmitter,
40
82
  currentSlot: Slot,
@@ -82,7 +124,7 @@ export function initializeForkChoice(
82
124
  parentRoot: toRootHex(blockHeader.parentRoot),
83
125
  stateRoot: toRootHex(blockHeader.stateRoot),
84
126
  blockRoot: toRootHex(checkpoint.root),
85
- timeliness: true, // Optimisitcally assume is timely
127
+ timeliness: true, // Optimistically assume is timely
86
128
 
87
129
  justifiedEpoch: justifiedCheckpoint.epoch,
88
130
  justifiedRoot: toRootHex(justifiedCheckpoint.root),
@@ -105,6 +147,140 @@ export function initializeForkChoice(
105
147
  },
106
148
  currentSlot
107
149
  ),
150
+ state.validators.length,
151
+ metrics,
152
+ opts,
153
+ logger
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Initialize forkchoice from an unfinalized state.
159
+ */
160
+ export function initializeForkChoiceFromUnfinalizedState(
161
+ config: ChainForkConfig,
162
+ emitter: ChainEventEmitter,
163
+ currentSlot: Slot,
164
+ unfinalizedState: CachedBeaconStateAllForks,
165
+ opts: ForkChoiceOpts,
166
+ justifiedBalancesGetter: JustifiedBalancesGetter,
167
+ metrics: Metrics | null,
168
+ logger?: Logger
169
+ ): ForkChoice {
170
+ const {blockHeader} = computeAnchorCheckpoint(config, unfinalizedState);
171
+ const finalizedCheckpoint = unfinalizedState.finalizedCheckpoint.toValue();
172
+ const justifiedCheckpoint = unfinalizedState.currentJustifiedCheckpoint.toValue();
173
+ const headRoot = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader));
174
+
175
+ const logCtx = {
176
+ currentSlot: currentSlot,
177
+ stateSlot: unfinalizedState.slot,
178
+ headSlot: blockHeader.slot,
179
+ headRoot: headRoot,
180
+ finalizedEpoch: finalizedCheckpoint.epoch,
181
+ finalizedRoot: toRootHex(finalizedCheckpoint.root),
182
+ justifiedEpoch: justifiedCheckpoint.epoch,
183
+ justifiedRoot: toRootHex(justifiedCheckpoint.root),
184
+ };
185
+ logger?.warn("Initializing fork choice from unfinalized state", logCtx);
186
+
187
+ // this is not the justified state, but there is no other ways to get justified balances
188
+ const justifiedBalances = getEffectiveBalanceIncrementsZeroInactive(unfinalizedState);
189
+ const store = new ForkChoiceStore(
190
+ currentSlot,
191
+ justifiedCheckpoint,
192
+ finalizedCheckpoint,
193
+ justifiedBalances,
194
+ justifiedBalancesGetter,
195
+ {
196
+ onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
197
+ onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
198
+ }
199
+ );
200
+
201
+ // this is the same to the finalized state
202
+ const headBlock: ProtoBlock = {
203
+ slot: blockHeader.slot,
204
+ parentRoot: toRootHex(blockHeader.parentRoot),
205
+ stateRoot: toRootHex(blockHeader.stateRoot),
206
+ blockRoot: headRoot,
207
+ targetRoot: headRoot,
208
+ timeliness: true, // Optimistically assume is timely
209
+
210
+ justifiedEpoch: justifiedCheckpoint.epoch,
211
+ justifiedRoot: toRootHex(justifiedCheckpoint.root),
212
+ finalizedEpoch: finalizedCheckpoint.epoch,
213
+ finalizedRoot: toRootHex(finalizedCheckpoint.root),
214
+ unrealizedJustifiedEpoch: justifiedCheckpoint.epoch,
215
+ unrealizedJustifiedRoot: toRootHex(justifiedCheckpoint.root),
216
+ unrealizedFinalizedEpoch: finalizedCheckpoint.epoch,
217
+ unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root),
218
+
219
+ ...(isExecutionStateType(unfinalizedState) && isMergeTransitionComplete(unfinalizedState)
220
+ ? {
221
+ executionPayloadBlockHash: toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash),
222
+ executionPayloadNumber: unfinalizedState.latestExecutionPayloadHeader.blockNumber,
223
+ executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing,
224
+ }
225
+ : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}),
226
+
227
+ dataAvailabilityStatus: DataAvailabilityStatus.PreData,
228
+ };
229
+
230
+ const parentSlot = blockHeader.slot - 1;
231
+ const parentEpoch = computeEpochAtSlot(parentSlot);
232
+ // parent of head block
233
+ const parentBlock: ProtoBlock = {
234
+ ...headBlock,
235
+ slot: parentSlot,
236
+ // link this to the dummy justified block
237
+ parentRoot: toRootHex(justifiedCheckpoint.root),
238
+ // dummy data, we're not able to regen state before headBlock
239
+ stateRoot: ZERO_HASH_HEX,
240
+ blockRoot: headBlock.parentRoot,
241
+ targetRoot: toRootHex(getBlockRootAtSlot(unfinalizedState, computeStartSlotAtEpoch(parentEpoch))),
242
+ };
243
+
244
+ const justifiedBlock: ProtoBlock = {
245
+ ...headBlock,
246
+ slot: computeStartSlotAtEpoch(justifiedCheckpoint.epoch),
247
+ // link this to the finalized root so that getAncestors can find the finalized block
248
+ parentRoot: toRootHex(finalizedCheckpoint.root),
249
+ // dummy data, we're not able to regen state before headBlock
250
+ stateRoot: ZERO_HASH_HEX,
251
+ blockRoot: toRootHex(justifiedCheckpoint.root),
252
+ // same to blockRoot
253
+ targetRoot: toRootHex(justifiedCheckpoint.root),
254
+ };
255
+
256
+ const finalizedBlock: ProtoBlock = {
257
+ ...headBlock,
258
+ slot: computeStartSlotAtEpoch(finalizedCheckpoint.epoch),
259
+ // we don't care parent of finalized block
260
+ parentRoot: ZERO_HASH_HEX,
261
+ // dummy data, we're not able to regen state before headBlock
262
+ stateRoot: ZERO_HASH_HEX,
263
+ blockRoot: toRootHex(finalizedCheckpoint.root),
264
+ // same to blockRoot
265
+ targetRoot: toRootHex(finalizedCheckpoint.root),
266
+ };
267
+
268
+ const protoArray = ProtoArray.initialize(finalizedBlock, currentSlot);
269
+ protoArray.onBlock(justifiedBlock, currentSlot);
270
+ protoArray.onBlock(parentBlock, currentSlot);
271
+ protoArray.onBlock(headBlock, currentSlot);
272
+
273
+ logger?.verbose("Initialized protoArray successfully", {...logCtx, length: protoArray.length()});
274
+
275
+ // forkchoiceConstructor is only used for some test cases
276
+ // production code use ForkChoice constructor directly
277
+ const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice;
278
+
279
+ return new forkchoiceConstructor(
280
+ config,
281
+ store,
282
+ protoArray,
283
+ unfinalizedState.validators.length,
108
284
  metrics,
109
285
  opts,
110
286
  logger
@@ -178,6 +178,8 @@ export interface IBeaconChain {
178
178
  stateRoot: RootHex,
179
179
  opts?: StateGetOpts
180
180
  ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null>;
181
+ /** Return serialized bytes of a persisted checkpoint state */
182
+ getPersistedCheckpointState(checkpoint?: phase0.Checkpoint): Promise<Uint8Array | null>;
181
183
  /** Returns a cached state by checkpoint */
182
184
  getStateByCheckpoint(
183
185
  checkpoint: CheckpointWithHex
@@ -14,6 +14,7 @@ import {
14
14
  ForkPreGloas,
15
15
  ForkSeq,
16
16
  MIN_SYNC_COMMITTEE_PARTICIPANTS,
17
+ SLOTS_PER_EPOCH,
17
18
  SYNC_COMMITTEE_SIZE,
18
19
  forkPostAltair,
19
20
  highestFork,
@@ -51,6 +52,7 @@ import {IBeaconDb} from "../../db/index.js";
51
52
  import {NUM_WITNESS, NUM_WITNESS_ELECTRA} from "../../db/repositories/lightclientSyncCommitteeWitness.js";
52
53
  import {Metrics} from "../../metrics/index.js";
53
54
  import {byteArrayEquals} from "../../util/bytes.js";
55
+ import {IClock} from "../../util/clock.js";
54
56
  import {ChainEventEmitter} from "../emitter.js";
55
57
  import {LightClientServerError, LightClientServerErrorCode} from "../errors/lightClientError.js";
56
58
  import {
@@ -86,6 +88,7 @@ export type SyncAttestedData = {
86
88
 
87
89
  type LightClientServerModules = {
88
90
  config: ChainForkConfig;
91
+ clock: IClock;
89
92
  db: IBeaconDb;
90
93
  metrics: Metrics | null;
91
94
  emitter: ChainEventEmitter;
@@ -201,6 +204,7 @@ export class LightClientServer {
201
204
  private readonly metrics: Metrics | null;
202
205
  private readonly emitter: ChainEventEmitter;
203
206
  private readonly logger: Logger;
207
+ private readonly clock: IClock;
204
208
  private readonly knownSyncCommittee = new MapDef<SyncPeriod, Set<DependentRootHex>>(() => new Set());
205
209
  private storedCurrentSyncCommittee = false;
206
210
 
@@ -221,8 +225,9 @@ export class LightClientServer {
221
225
  private readonly opts: LightClientServerOpts,
222
226
  modules: LightClientServerModules
223
227
  ) {
224
- const {config, db, metrics, emitter, logger} = modules;
228
+ const {config, clock, db, metrics, emitter, logger} = modules;
225
229
  this.config = config;
230
+ this.clock = clock;
226
231
  this.db = db;
227
232
  this.metrics = metrics;
228
233
  this.emitter = emitter;
@@ -533,12 +538,19 @@ export class LightClientServer {
533
538
  // Fork of LightClientOptimisticUpdate and LightClientFinalityUpdate is based off on attested header's fork
534
539
  const attestedFork = this.config.getForkName(attestedHeader.beacon.slot);
535
540
 
536
- // Emit update
537
- // Note: Always emit optimistic update even if we have emitted one with higher or equal attested_header.slot
538
- this.emitter.emit(routes.events.EventType.lightClientOptimisticUpdate, {
539
- version: attestedFork,
540
- data: headerUpdate,
541
- });
541
+ // Check if node is syncing / too far behind to avoid emitting stale light client updates
542
+ const isStaleLightClientUpdate = this.clock.currentSlot - signatureSlot > SLOTS_PER_EPOCH;
543
+
544
+ if (!isStaleLightClientUpdate) {
545
+ // Emit update
546
+ // Note: Always emit optimistic update even if we have emitted one with higher or equal attested_header.slot
547
+ this.emitter.emit(routes.events.EventType.lightClientOptimisticUpdate, {
548
+ version: attestedFork,
549
+ data: headerUpdate,
550
+ });
551
+ } else {
552
+ this.metrics?.lightclientServer.staleLightClientUpdates.inc();
553
+ }
542
554
 
543
555
  // Persist latest best update for getLatestHeadUpdate()
544
556
  // TODO: Once SyncAggregate are constructed from P2P too, count bits to decide "best"
@@ -569,11 +581,13 @@ export class LightClientServer {
569
581
  };
570
582
  this.metrics?.lightclientServer.onSyncAggregate.inc({event: "update_latest_finalized_update"});
571
583
 
572
- // Note: Ignores gossip rule to always emit finality_update with higher finalized_header.slot, for simplicity
573
- this.emitter.emit(routes.events.EventType.lightClientFinalityUpdate, {
574
- version: attestedFork,
575
- data: this.finalized,
576
- });
584
+ if (!isStaleLightClientUpdate) {
585
+ // Note: Ignores gossip rule to always emit finality_update with higher finalized_header.slot, for simplicity
586
+ this.emitter.emit(routes.events.EventType.lightClientFinalityUpdate, {
587
+ version: attestedFork,
588
+ data: this.finalized,
589
+ });
590
+ }
577
591
  }
578
592
  }
579
593
 
@@ -1,10 +1,10 @@
1
1
  import {Signature, aggregateSignatures} from "@chainsafe/blst";
2
2
  import {BitArray} from "@chainsafe/ssz";
3
+ import {ChainForkConfig} from "@lodestar/config";
3
4
  import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params";
4
5
  import {G2_POINT_AT_INFINITY} from "@lodestar/state-transition";
5
6
  import {Root, Slot, SubnetID, altair, ssz} from "@lodestar/types";
6
7
  import {Logger, MapDef, toRootHex} from "@lodestar/utils";
7
- import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/constants.js";
8
8
  import {Metrics} from "../../metrics/metrics.js";
9
9
  import {IClock} from "../../util/clock.js";
10
10
  import {InsertOutcome, OpPoolError, OpPoolErrorCode} from "./types.js";
@@ -51,6 +51,7 @@ export class SyncContributionAndProofPool {
51
51
  private lowestPermissibleSlot = 0;
52
52
 
53
53
  constructor(
54
+ private readonly config: ChainForkConfig,
54
55
  private readonly clock: IClock,
55
56
  private readonly metrics: Metrics | null = null,
56
57
  private logger: Logger | null = null
@@ -92,7 +93,7 @@ export class SyncContributionAndProofPool {
92
93
 
93
94
  // Reject ContributionAndProofs of previous slots
94
95
  // for api ContributionAndProofs, we allow them to be added to the pool
95
- if (!priority && slot < this.clock.slotWithPastTolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)) {
96
+ if (!priority && slot < this.clock.slotWithPastTolerance(this.config.MAXIMUM_GOSSIP_CLOCK_DISPARITY / 1000)) {
96
97
  return InsertOutcome.Late;
97
98
  }
98
99
 
@@ -8,6 +8,7 @@ import {ShufflingCacheOpts} from "./shufflingCache.js";
8
8
  import {DEFAULT_MAX_BLOCK_STATES, FIFOBlockStateCacheOpts} from "./stateCache/fifoBlockStateCache.js";
9
9
  import {
10
10
  DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY,
11
+ DEFAULT_MAX_CP_STATE_ON_DISK,
11
12
  PersistentCheckpointStateCacheOpts,
12
13
  } from "./stateCache/persistentCheckpointsCache.js";
13
14
  import {ValidatorMonitorOpts} from "./validatorMonitor.js";
@@ -41,8 +42,6 @@ export type IChainOptions = BlockProcessOpts &
41
42
  maxCachedBlobSidecars?: number;
42
43
  /** Max number of produced block roots (blinded or full) cached for broadcast validations */
43
44
  maxCachedProducedRoots?: number;
44
- /** Subscribe to and custody all data column sidecar subnets */
45
- supernode?: boolean;
46
45
  initialCustodyGroupCount?: number;
47
46
  broadcastValidationStrictness?: string;
48
47
  minSameMessageSignatureSetsToBatch: number;
@@ -118,7 +117,6 @@ export const defaultChainOptions: IChainOptions = {
118
117
  archiveMode: DEFAULT_ARCHIVE_MODE,
119
118
  pruneHistory: false,
120
119
  emitPayloadAttributes: false,
121
- supernode: false,
122
120
  // for gossip block validation, it's unlikely we see a reorg with 32 slots
123
121
  // for attestation validation, having this value ensures we don't have to regen states most of the time
124
122
  maxSkipSlots: 32,
@@ -135,4 +133,5 @@ export const defaultChainOptions: IChainOptions = {
135
133
  nHistoricalStatesFileDataStore: true,
136
134
  maxBlockStates: DEFAULT_MAX_BLOCK_STATES,
137
135
  maxCPStateEpochsInMemory: DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY,
136
+ maxCPStateEpochsOnDisk: DEFAULT_MAX_CP_STATE_ON_DISK,
138
137
  };
@@ -1,5 +1,11 @@
1
- import {phase0, ssz} from "@lodestar/types";
1
+ import {SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {Epoch, phase0, ssz} from "@lodestar/types";
3
+ import {MapDef} from "@lodestar/utils";
2
4
  import {IBeaconDb} from "../../../db/interface.js";
5
+ import {
6
+ getLastProcessedSlotFromBeaconStateSerialized,
7
+ getSlotFromBeaconStateSerialized,
8
+ } from "../../../util/sszBytes.js";
3
9
  import {CPStateDatastore, DatastoreKey} from "./types.js";
4
10
 
5
11
  /**
@@ -22,6 +28,13 @@ export class DbCPStateDatastore implements CPStateDatastore {
22
28
  return this.db.checkpointState.getBinary(serializedCheckpoint);
23
29
  }
24
30
 
31
+ async readLatestSafe(): Promise<Uint8Array | null> {
32
+ const allKeys = await this.readKeys();
33
+ if (allKeys.length === 0) return null;
34
+
35
+ return getLatestSafeDatastoreKey(allKeys, this.read.bind(this));
36
+ }
37
+
25
38
  async readKeys(): Promise<DatastoreKey[]> {
26
39
  return this.db.checkpointState.keys();
27
40
  }
@@ -34,3 +47,78 @@ export function datastoreKeyToCheckpoint(key: DatastoreKey): phase0.Checkpoint {
34
47
  export function checkpointToDatastoreKey(cp: phase0.Checkpoint): DatastoreKey {
35
48
  return ssz.phase0.Checkpoint.serialize(cp);
36
49
  }
50
+
51
+ /**
52
+ * Get the latest safe checkpoint state the node can use to boot from
53
+ * - it should be the checkpoint state that's unique in its epoch
54
+ * - its last processed block slot should be at epoch boundary or last slot of previous epoch
55
+ * - state slot should be at epoch boundary
56
+ * - state slot should be equal to epoch * SLOTS_PER_EPOCH
57
+ *
58
+ * return the serialized data of Current Root Checkpoint State (CRCS) or Previous Root Checkpoint State (PRCS)
59
+ *
60
+ */
61
+ export async function getLatestSafeDatastoreKey(
62
+ allKeys: DatastoreKey[],
63
+ readFn: (key: DatastoreKey) => Promise<Uint8Array | null>
64
+ ): Promise<Uint8Array | null> {
65
+ const checkpointsByEpoch = new MapDef<Epoch, DatastoreKey[]>(() => []);
66
+ for (const key of allKeys) {
67
+ const cp = datastoreKeyToCheckpoint(key);
68
+ checkpointsByEpoch.getOrDefault(cp.epoch).push(key);
69
+ }
70
+
71
+ const dataStoreKeyByEpoch: Map<Epoch, DatastoreKey> = new Map();
72
+ for (const [epoch, keys] of checkpointsByEpoch.entries()) {
73
+ // only consider epochs with a single checkpoint to avoid ambiguity from forks
74
+ if (keys.length === 1) {
75
+ dataStoreKeyByEpoch.set(epoch, keys[0]);
76
+ }
77
+ }
78
+
79
+ const epochsDesc = Array.from(dataStoreKeyByEpoch.keys()).sort((a, b) => b - a);
80
+ for (const epoch of epochsDesc) {
81
+ const datastoreKey = dataStoreKeyByEpoch.get(epoch);
82
+ if (datastoreKey == null) {
83
+ // should not happen
84
+ continue;
85
+ }
86
+
87
+ const stateBytes = await readFn(datastoreKey);
88
+ if (stateBytes == null) {
89
+ // should not happen
90
+ continue;
91
+ }
92
+
93
+ const lastProcessedSlot = getLastProcessedSlotFromBeaconStateSerialized(stateBytes);
94
+ if (lastProcessedSlot == null) {
95
+ // cannot extract last processed slot from serialized state, skip
96
+ continue;
97
+ }
98
+
99
+ const stateSlot = getSlotFromBeaconStateSerialized(stateBytes);
100
+ if (stateSlot == null) {
101
+ // cannot extract slot from serialized state, skip
102
+ continue;
103
+ }
104
+
105
+ if (lastProcessedSlot !== stateSlot && lastProcessedSlot !== stateSlot - 1) {
106
+ // not CRCS or PRCS, skip
107
+ continue;
108
+ }
109
+
110
+ if (stateSlot % SLOTS_PER_EPOCH !== 0) {
111
+ // not at epoch boundary, skip
112
+ continue;
113
+ }
114
+
115
+ if (stateSlot !== SLOTS_PER_EPOCH * epoch) {
116
+ // should not happen after above checks, but just to be safe
117
+ continue;
118
+ }
119
+
120
+ return stateBytes;
121
+ }
122
+
123
+ return null;
124
+ }
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import {phase0, ssz} from "@lodestar/types";
3
3
  import {fromHex, toHex} from "@lodestar/utils";
4
4
  import {ensureDir, readFile, readFileNames, removeFile, writeIfNotExist} from "../../../util/file.js";
5
+ import {getLatestSafeDatastoreKey} from "./db.js";
5
6
  import {CPStateDatastore, DatastoreKey} from "./types.js";
6
7
 
7
8
  const CHECKPOINT_STATES_FOLDER = "checkpoint_states";
@@ -44,6 +45,13 @@ export class FileCPStateDatastore implements CPStateDatastore {
44
45
  return readFile(filePath);
45
46
  }
46
47
 
48
+ async readLatestSafe(): Promise<Uint8Array | null> {
49
+ const allKeys = await this.readKeys();
50
+ if (allKeys.length === 0) return null;
51
+
52
+ return getLatestSafeDatastoreKey(allKeys, this.read.bind(this));
53
+ }
54
+
47
55
  async readKeys(): Promise<DatastoreKey[]> {
48
56
  const fileNames = await readFileNames(this.folderPath);
49
57
  return fileNames
@@ -8,6 +8,7 @@ export interface CPStateDatastore {
8
8
  write: (cpKey: phase0.Checkpoint, stateBytes: Uint8Array) => Promise<DatastoreKey>;
9
9
  remove: (key: DatastoreKey) => Promise<void>;
10
10
  read: (key: DatastoreKey) => Promise<Uint8Array | null>;
11
+ readLatestSafe: () => Promise<Uint8Array | null>;
11
12
  readKeys: () => Promise<DatastoreKey[]>;
12
13
  init?: () => Promise<void>;
13
14
  }