@lodestar/beacon-node 1.50.0-dev.f2d839a25c → 1.50.0-dev.fc618d94e9

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 (245) 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/pool/index.js +4 -4
  8. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  9. package/lib/api/impl/validator/index.d.ts +1 -0
  10. package/lib/api/impl/validator/index.d.ts.map +1 -1
  11. package/lib/api/impl/validator/index.js +17 -1
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
  14. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  15. package/lib/chain/archiveStore/archiveStore.js +7 -3
  16. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
  18. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  19. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
  20. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  21. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
  22. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  23. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
  24. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  25. package/lib/chain/archiveStore/historicalState/worker.js +1 -1
  26. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  27. package/lib/chain/archiveStore/interface.d.ts +1 -1
  28. package/lib/chain/archiveStore/interface.d.ts.map +1 -1
  29. package/lib/chain/archiveStore/utils/pruneHistory.d.ts +4 -1
  30. package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
  31. package/lib/chain/archiveStore/utils/pruneHistory.js +4 -3
  32. package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
  33. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  34. package/lib/chain/blocks/importBlock.js +4 -0
  35. package/lib/chain/blocks/importBlock.js.map +1 -1
  36. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
  37. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  38. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  39. package/lib/chain/blocks/verifyBlock.js +1 -1
  40. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
  42. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  43. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
  44. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  45. package/lib/chain/chain.d.ts +6 -4
  46. package/lib/chain/chain.d.ts.map +1 -1
  47. package/lib/chain/chain.js +43 -16
  48. package/lib/chain/chain.js.map +1 -1
  49. package/lib/chain/emitter.d.ts +7 -1
  50. package/lib/chain/emitter.d.ts.map +1 -1
  51. package/lib/chain/emitter.js +5 -0
  52. package/lib/chain/emitter.js.map +1 -1
  53. package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
  54. package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
  55. package/lib/chain/errors/envelopeReconstructionError.js +2 -0
  56. package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
  57. package/lib/chain/errors/gossipValidation.d.ts +3 -1
  58. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  59. package/lib/chain/errors/gossipValidation.js +2 -0
  60. package/lib/chain/errors/gossipValidation.js.map +1 -1
  61. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  62. package/lib/chain/forkChoice/index.js +2 -0
  63. package/lib/chain/forkChoice/index.js.map +1 -1
  64. package/lib/chain/initState.d.ts +2 -9
  65. package/lib/chain/initState.d.ts.map +1 -1
  66. package/lib/chain/initState.js +5 -38
  67. package/lib/chain/initState.js.map +1 -1
  68. package/lib/chain/interface.d.ts +2 -2
  69. package/lib/chain/interface.d.ts.map +1 -1
  70. package/lib/chain/opPools/opPool.d.ts +5 -4
  71. package/lib/chain/opPools/opPool.d.ts.map +1 -1
  72. package/lib/chain/opPools/opPool.js +62 -18
  73. package/lib/chain/opPools/opPool.js.map +1 -1
  74. package/lib/chain/opPools/proposerPreferencesPool.d.ts +4 -4
  75. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  76. package/lib/chain/opPools/proposerPreferencesPool.js +14 -11
  77. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  78. package/lib/chain/opPools/utils.d.ts +4 -1
  79. package/lib/chain/opPools/utils.d.ts.map +1 -1
  80. package/lib/chain/opPools/utils.js +11 -1
  81. package/lib/chain/opPools/utils.js.map +1 -1
  82. package/lib/chain/options.d.ts +1 -1
  83. package/lib/chain/options.d.ts.map +1 -1
  84. package/lib/chain/options.js +1 -1
  85. package/lib/chain/options.js.map +1 -1
  86. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  87. package/lib/chain/prepareNextSlot.js +5 -2
  88. package/lib/chain/prepareNextSlot.js.map +1 -1
  89. package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
  90. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  91. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
  92. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  93. package/lib/chain/produceBlock/produceBlockBody.js +6 -2
  94. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  95. package/lib/chain/regen/regen.d.ts +2 -0
  96. package/lib/chain/regen/regen.d.ts.map +1 -1
  97. package/lib/chain/regen/regen.js +10 -4
  98. package/lib/chain/regen/regen.js.map +1 -1
  99. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
  100. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  101. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
  102. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  103. package/lib/chain/validation/attesterSlashing.d.ts +4 -4
  104. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  105. package/lib/chain/validation/attesterSlashing.js +3 -0
  106. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  107. package/lib/chain/validation/proposerSlashing.d.ts +3 -3
  108. package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
  109. package/lib/chain/validation/proposerSlashing.js +3 -0
  110. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  111. package/lib/chain/validatorMonitor.d.ts +5 -1
  112. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  113. package/lib/chain/validatorMonitor.js +64 -54
  114. package/lib/chain/validatorMonitor.js.map +1 -1
  115. package/lib/db/beacon.d.ts.map +1 -1
  116. package/lib/db/beacon.js +2 -0
  117. package/lib/db/beacon.js.map +1 -1
  118. package/lib/db/repositories/proposerSlashing.d.ts +1 -0
  119. package/lib/db/repositories/proposerSlashing.d.ts.map +1 -1
  120. package/lib/db/repositories/proposerSlashing.js +5 -1
  121. package/lib/db/repositories/proposerSlashing.js.map +1 -1
  122. package/lib/execution/engine/disabled.d.ts +0 -2
  123. package/lib/execution/engine/disabled.d.ts.map +1 -1
  124. package/lib/execution/engine/disabled.js +0 -6
  125. package/lib/execution/engine/disabled.js.map +1 -1
  126. package/lib/execution/engine/http.d.ts +1 -3
  127. package/lib/execution/engine/http.d.ts.map +1 -1
  128. package/lib/execution/engine/http.js +2 -17
  129. package/lib/execution/engine/http.js.map +1 -1
  130. package/lib/execution/engine/interface.d.ts +2 -4
  131. package/lib/execution/engine/interface.d.ts.map +1 -1
  132. package/lib/execution/engine/mock.d.ts +0 -2
  133. package/lib/execution/engine/mock.d.ts.map +1 -1
  134. package/lib/execution/engine/mock.js +0 -8
  135. package/lib/execution/engine/mock.js.map +1 -1
  136. package/lib/execution/engine/types.d.ts +0 -8
  137. package/lib/execution/engine/types.d.ts.map +1 -1
  138. package/lib/execution/engine/types.js.map +1 -1
  139. package/lib/index.d.ts +1 -1
  140. package/lib/index.d.ts.map +1 -1
  141. package/lib/index.js +1 -1
  142. package/lib/index.js.map +1 -1
  143. package/lib/metrics/metrics.d.ts +7 -3
  144. package/lib/metrics/metrics.d.ts.map +1 -1
  145. package/lib/metrics/metrics.js +4 -4
  146. package/lib/metrics/metrics.js.map +1 -1
  147. package/lib/network/metadata.d.ts +1 -1
  148. package/lib/network/metadata.d.ts.map +1 -1
  149. package/lib/network/metadata.js +17 -17
  150. package/lib/network/metadata.js.map +1 -1
  151. package/lib/network/peers/datastore.d.ts +9 -9
  152. package/lib/network/peers/datastore.d.ts.map +1 -1
  153. package/lib/network/peers/datastore.js +36 -36
  154. package/lib/network/peers/datastore.js.map +1 -1
  155. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  156. package/lib/network/processor/gossipHandlers.js +7 -4
  157. package/lib/network/processor/gossipHandlers.js.map +1 -1
  158. package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
  159. package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
  160. package/lib/network/processor/gossipValidatorFn.js +7 -3
  161. package/lib/network/processor/gossipValidatorFn.js.map +1 -1
  162. package/lib/network/processor/index.d.ts +9 -0
  163. package/lib/network/processor/index.d.ts.map +1 -1
  164. package/lib/network/processor/index.js +77 -10
  165. package/lib/network/processor/index.js.map +1 -1
  166. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
  167. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +41 -26
  168. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  169. package/lib/node/nodejs.d.ts.map +1 -1
  170. package/lib/node/nodejs.js +25 -3
  171. package/lib/node/nodejs.js.map +1 -1
  172. package/lib/sync/range/batch.d.ts +6 -0
  173. package/lib/sync/range/batch.d.ts.map +1 -1
  174. package/lib/sync/range/batch.js +10 -0
  175. package/lib/sync/range/batch.js.map +1 -1
  176. package/lib/sync/range/chain.d.ts.map +1 -1
  177. package/lib/sync/range/chain.js +5 -1
  178. package/lib/sync/range/chain.js.map +1 -1
  179. package/lib/sync/range/range.d.ts.map +1 -1
  180. package/lib/sync/range/range.js +50 -18
  181. package/lib/sync/range/range.js.map +1 -1
  182. package/lib/sync/unknownBlock.d.ts +9 -5
  183. package/lib/sync/unknownBlock.d.ts.map +1 -1
  184. package/lib/sync/unknownBlock.js +88 -46
  185. package/lib/sync/unknownBlock.js.map +1 -1
  186. package/lib/util/execution.d.ts +11 -11
  187. package/lib/util/execution.d.ts.map +1 -1
  188. package/lib/util/execution.js +6 -2
  189. package/lib/util/execution.js.map +1 -1
  190. package/package.json +16 -16
  191. package/src/api/impl/beacon/blocks/index.ts +7 -4
  192. package/src/api/impl/beacon/index.ts +15 -0
  193. package/src/api/impl/beacon/pool/index.ts +4 -4
  194. package/src/api/impl/validator/index.ts +16 -2
  195. package/src/chain/archiveStore/archiveStore.ts +10 -4
  196. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
  197. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
  198. package/src/chain/archiveStore/historicalState/types.ts +2 -2
  199. package/src/chain/archiveStore/historicalState/worker.ts +1 -2
  200. package/src/chain/archiveStore/interface.ts +1 -1
  201. package/src/chain/archiveStore/utils/pruneHistory.ts +6 -4
  202. package/src/chain/blocks/importBlock.ts +4 -0
  203. package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
  204. package/src/chain/blocks/verifyBlock.ts +1 -0
  205. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
  206. package/src/chain/chain.ts +50 -19
  207. package/src/chain/emitter.ts +7 -1
  208. package/src/chain/errors/envelopeReconstructionError.ts +3 -0
  209. package/src/chain/errors/gossipValidation.ts +2 -0
  210. package/src/chain/forkChoice/index.ts +2 -0
  211. package/src/chain/initState.ts +8 -60
  212. package/src/chain/interface.ts +2 -2
  213. package/src/chain/opPools/opPool.ts +91 -18
  214. package/src/chain/opPools/proposerPreferencesPool.ts +14 -10
  215. package/src/chain/opPools/utils.ts +31 -3
  216. package/src/chain/options.ts +2 -2
  217. package/src/chain/prepareNextSlot.ts +6 -3
  218. package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
  219. package/src/chain/produceBlock/produceBlockBody.ts +8 -1
  220. package/src/chain/regen/regen.ts +12 -4
  221. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
  222. package/src/chain/validation/attesterSlashing.ts +7 -4
  223. package/src/chain/validation/proposerSlashing.ts +7 -4
  224. package/src/chain/validatorMonitor.ts +75 -57
  225. package/src/db/beacon.ts +2 -0
  226. package/src/db/repositories/proposerSlashing.ts +6 -1
  227. package/src/execution/engine/disabled.ts +0 -8
  228. package/src/execution/engine/http.ts +1 -30
  229. package/src/execution/engine/interface.ts +2 -6
  230. package/src/execution/engine/mock.ts +1 -19
  231. package/src/execution/engine/types.ts +0 -10
  232. package/src/index.ts +1 -1
  233. package/src/metrics/metrics.ts +21 -6
  234. package/src/network/metadata.ts +17 -17
  235. package/src/network/peers/datastore.ts +36 -36
  236. package/src/network/processor/gossipHandlers.ts +7 -4
  237. package/src/network/processor/gossipValidatorFn.ts +13 -3
  238. package/src/network/processor/index.ts +89 -10
  239. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +42 -28
  240. package/src/node/nodejs.ts +31 -3
  241. package/src/sync/range/batch.ts +11 -0
  242. package/src/sync/range/chain.ts +6 -1
  243. package/src/sync/range/range.ts +50 -18
  244. package/src/sync/unknownBlock.ts +99 -50
  245. package/src/util/execution.ts +17 -9
@@ -16,23 +16,23 @@ type AwaitGenerator<T, TReturn = any, TNext = any> = Generator<T, TReturn, TNext
16
16
  * This has a memory issue because all peer data stays in memory and loaded at startup time
17
17
  * This is written for libp2p >=0.35, we maintain the same mechanism but with bounded data structure
18
18
  * This datastore includes a memory datastore and fallback to db datastore
19
- * Use an in-memory datastore with last accessed time and _maxMemoryItems, on start it's empty (lazy load)
19
+ * Use an in-memory datastore with last accessed time and maxMemoryItems, on start it's empty (lazy load)
20
20
  * - get: Search in-memory datastore first, if not found search from db.
21
21
  * - If found from db, add back to the in-memory datastore
22
22
  * - Update lastAccessedMs
23
- * - put: move oldest items from memory to db if there are more than _maxMemoryItems items in memory
24
- * - update memory datastore, only update db datastore if there are at least _threshold dirty items
23
+ * - put: move oldest items from memory to db if there are more than maxMemoryItems items in memory
24
+ * - update memory datastore, only update db datastore if there are at least threshold dirty items
25
25
  * - Update lastAccessedMs
26
26
  */
27
27
  export class Eth2PeerDataStore extends BaseDatastore {
28
- private _dbDatastore: LevelDatastore;
29
- private _memoryDatastore: Map<string, MemoryItem>;
28
+ private dbDatastore: LevelDatastore;
29
+ private memoryDatastore: Map<string, MemoryItem>;
30
30
  /** Same to PersistentPeerStore of the old libp2p implementation */
31
- private _dirtyItems = new Set<string>();
31
+ private dirtyItems = new Set<string>();
32
32
  /** If there are more dirty items than threshold, commit data to db */
33
- private _threshold: number;
33
+ private threshold: number;
34
34
  /** If there are more memory items than this, prune oldest ones from memory and move to db */
35
- private _maxMemoryItems: number;
35
+ private maxMemoryItems: number;
36
36
 
37
37
  constructor(
38
38
  dbDatastore: LevelDatastore | string,
@@ -47,18 +47,18 @@ export class Eth2PeerDataStore extends BaseDatastore {
47
47
  throw Error(`Threshold ${threshold} should be at most maxMemoryItems ${maxMemoryItems}`);
48
48
  }
49
49
 
50
- this._dbDatastore = typeof dbDatastore === "string" ? new LevelDatastore(dbDatastore) : dbDatastore;
51
- this._memoryDatastore = new Map();
52
- this._threshold = threshold;
53
- this._maxMemoryItems = maxMemoryItems;
50
+ this.dbDatastore = typeof dbDatastore === "string" ? new LevelDatastore(dbDatastore) : dbDatastore;
51
+ this.memoryDatastore = new Map();
52
+ this.threshold = threshold;
53
+ this.maxMemoryItems = maxMemoryItems;
54
54
  }
55
55
 
56
56
  async open(): Promise<void> {
57
- return this._dbDatastore.open();
57
+ return this.dbDatastore.open();
58
58
  }
59
59
 
60
60
  async close(): Promise<void> {
61
- return this._dbDatastore.close();
61
+ return this.dbDatastore.close();
62
62
  }
63
63
 
64
64
  async put(key: Key, val: Uint8Array, _options?: AbortOptions): Promise<Key> {
@@ -67,23 +67,23 @@ export class Eth2PeerDataStore extends BaseDatastore {
67
67
 
68
68
  /**
69
69
  * Same interface to put with "fromDb" option, if this item is updated back from db
70
- * Move oldest items from memory data store to db if it's over this._maxMemoryItems
70
+ * Move oldest items from memory data store to db if it's over this.maxMemoryItems
71
71
  */
72
72
  async _put(key: Key, val: Uint8Array, fromDb = false): Promise<Key> {
73
- while (this._memoryDatastore.size >= this._maxMemoryItems) {
73
+ while (this.memoryDatastore.size >= this.maxMemoryItems) {
74
74
  // it's likely this is called only 1 time
75
75
  await this.pruneMemoryDatastore();
76
76
  }
77
77
 
78
78
  const keyStr = key.toString();
79
- const memoryItem = this._memoryDatastore.get(keyStr);
79
+ const memoryItem = this.memoryDatastore.get(keyStr);
80
80
  if (memoryItem) {
81
81
  // update existing
82
82
  memoryItem.lastAccessedMs = Date.now();
83
83
  memoryItem.data = val;
84
84
  } else {
85
85
  // new
86
- this._memoryDatastore.set(keyStr, {data: val, lastAccessedMs: Date.now()});
86
+ this.memoryDatastore.set(keyStr, {data: val, lastAccessedMs: Date.now()});
87
87
  }
88
88
 
89
89
  if (!fromDb) await this._addDirtyItem(keyStr);
@@ -98,15 +98,15 @@ export class Eth2PeerDataStore extends BaseDatastore {
98
98
  */
99
99
  async get(key: Key, options?: AbortOptions): Promise<Uint8Array> {
100
100
  const keyStr = key.toString();
101
- const memoryItem = this._memoryDatastore.get(keyStr);
101
+ const memoryItem = this.memoryDatastore.get(keyStr);
102
102
  if (memoryItem) {
103
103
  memoryItem.lastAccessedMs = Date.now();
104
104
  return memoryItem.data;
105
105
  }
106
106
 
107
107
  // this throws error if not found
108
- const dbValue = await this._dbDatastore.get(key, options);
109
- // don't call this._memoryDatastore.set directly
108
+ const dbValue = await this.dbDatastore.get(key, options);
109
+ // don't call this.memoryDatastore.set directly
110
110
  // we want to get through prune() logic with fromDb as true
111
111
  await this._put(key, dbValue, true);
112
112
  return dbValue;
@@ -125,30 +125,30 @@ export class Eth2PeerDataStore extends BaseDatastore {
125
125
  }
126
126
 
127
127
  async delete(key: Key, options?: AbortOptions): Promise<void> {
128
- this._memoryDatastore.delete(key.toString());
129
- await this._dbDatastore.delete(key, options);
128
+ this.memoryDatastore.delete(key.toString());
129
+ await this.dbDatastore.delete(key, options);
130
130
  }
131
131
 
132
132
  async *_all(q: Query, options?: AbortOptions): AwaitGenerator<Pair> {
133
- for (const [key, value] of this._memoryDatastore.entries()) {
133
+ for (const [key, value] of this.memoryDatastore.entries()) {
134
134
  yield {
135
135
  key: new Key(key),
136
136
  value: value.data,
137
137
  };
138
138
  }
139
- yield* this._dbDatastore.query(q, options);
139
+ yield* this.dbDatastore.query(q, options);
140
140
  }
141
141
 
142
142
  async *_allKeys(q: KeyQuery, options?: AbortOptions): AwaitGenerator<Key> {
143
- for (const key of this._memoryDatastore.keys()) {
143
+ for (const key of this.memoryDatastore.keys()) {
144
144
  yield new Key(key);
145
145
  }
146
- yield* this._dbDatastore.queryKeys(q, options);
146
+ yield* this.dbDatastore.queryKeys(q, options);
147
147
  }
148
148
 
149
149
  private async _addDirtyItem(keyStr: string): Promise<void> {
150
- this._dirtyItems.add(keyStr);
151
- if (this._dirtyItems.size >= this._threshold) {
150
+ this.dirtyItems.add(keyStr);
151
+ if (this.dirtyItems.size >= this.threshold) {
152
152
  try {
153
153
  await this._commitData();
154
154
  } catch (_e) {}
@@ -156,15 +156,15 @@ export class Eth2PeerDataStore extends BaseDatastore {
156
156
  }
157
157
 
158
158
  private async _commitData(): Promise<void> {
159
- const batch = this._dbDatastore.batch();
160
- for (const keyStr of this._dirtyItems) {
161
- const memoryItem = this._memoryDatastore.get(keyStr);
159
+ const batch = this.dbDatastore.batch();
160
+ for (const keyStr of this.dirtyItems) {
161
+ const memoryItem = this.memoryDatastore.get(keyStr);
162
162
  if (memoryItem) {
163
163
  batch.put(new Key(keyStr), memoryItem.data);
164
164
  }
165
165
  }
166
166
  await batch.commit();
167
- this._dirtyItems.clear();
167
+ this.dirtyItems.clear();
168
168
  }
169
169
 
170
170
  /**
@@ -175,7 +175,7 @@ export class Eth2PeerDataStore extends BaseDatastore {
175
175
  let oldestKey: string | undefined = undefined;
176
176
  let oldestValue: Uint8Array | undefined = undefined;
177
177
 
178
- for (const [key, value] of this._memoryDatastore) {
178
+ for (const [key, value] of this.memoryDatastore) {
179
179
  if (value.lastAccessedMs < oldestAccessedMs) {
180
180
  oldestAccessedMs = value.lastAccessedMs;
181
181
  oldestKey = key;
@@ -184,8 +184,8 @@ export class Eth2PeerDataStore extends BaseDatastore {
184
184
  }
185
185
 
186
186
  if (oldestKey && oldestValue) {
187
- await this._dbDatastore.put(new Key(oldestKey), oldestValue);
188
- this._memoryDatastore.delete(oldestKey);
187
+ await this.dbDatastore.put(new Key(oldestKey), oldestValue);
188
+ this.memoryDatastore.delete(oldestKey);
189
189
  }
190
190
  }
191
191
  }
@@ -1065,12 +1065,12 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1065
1065
  const {serializedData} = gossipData;
1066
1066
  const {fork} = topic.boundary;
1067
1067
  const attesterSlashing = sszDeserialize(topic, serializedData);
1068
- await validateGossipAttesterSlashing(chain, attesterSlashing);
1068
+ const verifiedDomains = await validateGossipAttesterSlashing(chain, attesterSlashing);
1069
1069
 
1070
1070
  // Handler - deferred to next event loop so the validation result propagates first
1071
1071
  callInNextEventLoop(() => {
1072
1072
  try {
1073
- chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
1073
+ chain.opPool.insertAttesterSlashing(fork, attesterSlashing, verifiedDomains);
1074
1074
  chain.forkChoice.onAttesterSlashing(attesterSlashing);
1075
1075
  chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
1076
1076
  } catch (e) {
@@ -1089,12 +1089,12 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1089
1089
  }: GossipHandlerParamGeneric<GossipType.proposer_slashing>) => {
1090
1090
  const {serializedData} = gossipData;
1091
1091
  const proposerSlashing = sszDeserialize(topic, serializedData);
1092
- await validateGossipProposerSlashing(chain, proposerSlashing);
1092
+ const verifiedDomain = await validateGossipProposerSlashing(chain, proposerSlashing);
1093
1093
 
1094
1094
  // Handler - deferred to next event loop so the validation result propagates first
1095
1095
  callInNextEventLoop(() => {
1096
1096
  try {
1097
- chain.opPool.insertProposerSlashing(proposerSlashing);
1097
+ chain.opPool.insertProposerSlashing(proposerSlashing, verifiedDomain);
1098
1098
  chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
1099
1099
  } catch (e) {
1100
1100
  logger.debug(
@@ -1341,6 +1341,9 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1341
1341
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1342
1342
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1343
1343
  core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
1344
+ // The builder may have signed another envelope that is valid, keeping this one would make
1345
+ // by-root and range sync reuse it and never import the payload
1346
+ chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
1344
1347
  // Misbehaving peer, but could highlight an issue in another client
1345
1348
  logLevel = LogLevel.warn;
1346
1349
  break;
@@ -20,6 +20,7 @@ import {
20
20
  VoluntaryExitErrorCode,
21
21
  } from "../../chain/errors/index.js";
22
22
  import {Metrics} from "../../metrics/index.js";
23
+ import {PeerIdStr} from "../../util/peerId.js";
23
24
  import {INetworkCore} from "../core/index.js";
24
25
  import {
25
26
  BatchGossipHandlerFn,
@@ -40,6 +41,8 @@ export type ValidatorFnModules = {
40
41
  core: INetworkCore;
41
42
  };
42
43
 
44
+ export type OnFatalPeerFn = (peer: PeerIdStr) => void;
45
+
43
46
  type RejectPeerActionRule = {default: PeerAction; byCode?: Record<string, PeerAction>};
44
47
 
45
48
  /**
@@ -112,7 +115,7 @@ const gossipRejectPeerAction: Record<GossipType, RejectPeerActionRule> = {
112
115
  [GossipType.light_client_optimistic_update]: {default: PeerAction.HighToleranceError},
113
116
  };
114
117
 
115
- function rejectPeerAction(type: GossipType, code: string): PeerAction {
118
+ export function rejectPeerAction(type: GossipType, code: string): PeerAction {
116
119
  const rule = gossipRejectPeerAction[type];
117
120
  return rule.byCode?.[code] ?? rule.default;
118
121
  }
@@ -123,7 +126,8 @@ function rejectPeerAction(type: GossipType, code: string): PeerAction {
123
126
  */
124
127
  export function getGossipValidatorBatchFn(
125
128
  gossipHandlers: GossipHandlers,
126
- modules: ValidatorFnModules
129
+ modules: ValidatorFnModules,
130
+ onFatalPeer: OnFatalPeerFn
127
131
  ): GossipValidatorBatchFn {
128
132
  const {logger, metrics, core} = modules;
129
133
 
@@ -173,6 +177,7 @@ export function getGossipValidatorBatchFn(
173
177
  metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
174
178
  const peerAction = rejectPeerAction(type, e.type.code);
175
179
  core.reportPeer(propagationSource, peerAction, e.type.code);
180
+ if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
176
181
  logger.debug(
177
182
  `Gossip validation ${type} rejected`,
178
183
  {peer: propagationSource, clientAgent, clientVersion, peerAction},
@@ -208,7 +213,11 @@ export function getGossipValidatorBatchFn(
208
213
  *
209
214
  * @see getGossipHandlers for reasoning on why GossipHandlerFn are used for gossip validation.
210
215
  */
211
- export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: ValidatorFnModules): GossipValidatorFn {
216
+ export function getGossipValidatorFn(
217
+ gossipHandlers: GossipHandlers,
218
+ modules: ValidatorFnModules,
219
+ onFatalPeer: OnFatalPeerFn
220
+ ): GossipValidatorFn {
212
221
  const {logger, metrics, core} = modules;
213
222
 
214
223
  return async function gossipValidatorFn({
@@ -260,6 +269,7 @@ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: Va
260
269
  metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
261
270
  const peerAction = rejectPeerAction(type, e.type.code);
262
271
  core.reportPeer(propagationSource, peerAction, e.type.code);
272
+ if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
263
273
  logger.debug(
264
274
  `Gossip validation ${type} rejected`,
265
275
  {peer: propagationSource, clientAgent, clientVersion, peerAction},
@@ -22,6 +22,7 @@ import {
22
22
  getParentRootFromSignedBeaconBlockSerialized,
23
23
  getPayloadPresentFromPayloadAttestationMessageSerialized,
24
24
  } from "../../util/sszBytes.js";
25
+ import {INetworkCore} from "../core/index.js";
25
26
  import {NetworkEvent, NetworkEventBus} from "../events.js";
26
27
  import {
27
28
  GossipHandlers,
@@ -34,7 +35,12 @@ import {MAX_PEERS_PER_ROOT} from "./constants.js";
34
35
  import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
35
36
  import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
36
37
  import {createGossipQueues} from "./gossipQueues/index.js";
37
- import {ValidatorFnModules, getGossipValidatorBatchFn, getGossipValidatorFn} from "./gossipValidatorFn.js";
38
+ import {
39
+ ValidatorFnModules,
40
+ getGossipValidatorBatchFn,
41
+ getGossipValidatorFn,
42
+ rejectPeerAction,
43
+ } from "./gossipValidatorFn.js";
38
44
  import {PendingGossipsubMessage} from "./types.js";
39
45
 
40
46
  export * from "./types.js";
@@ -102,6 +108,12 @@ type SearchedRootEntry = {
102
108
  */
103
109
  const DEFAULT_EARLIEST_PERMISSIBLE_SLOT_DISTANCE = 32;
104
110
 
111
+ /**
112
+ * The network worker bans a Fatal peer only on its next PeerManager heartbeat (30s). Until then the main thread
113
+ * would keep validating that peer's messages, so drop them here. 3 slots (36s) outlasts one heartbeat.
114
+ */
115
+ export const FATAL_PEER_EXPIRY_SLOTS = 3;
116
+
105
117
  type WorkOpts = {
106
118
  bypassQueue?: boolean;
107
119
  };
@@ -232,6 +244,7 @@ type SearchUnknownRootTarget =
232
244
  */
233
245
  export class NetworkProcessor {
234
246
  private readonly chain: IBeaconChain;
247
+ private readonly core: INetworkCore;
235
248
  private readonly events: NetworkEventBus;
236
249
  private readonly logger: Logger;
237
250
  private readonly metrics: Metrics | null;
@@ -256,23 +269,31 @@ export class NetworkProcessor {
256
269
  () => new MapDef<RootHex, SearchedRootEntry>(() => ({}))
257
270
  );
258
271
  private bufferedRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
272
+ /** peer -> clock slot when it was Fatal'ed */
273
+ private readonly fatalPeers = new Map<PeerIdStr, Slot>();
259
274
 
260
275
  constructor(
261
276
  modules: NetworkProcessorModules,
262
277
  private readonly opts: NetworkProcessorOpts
263
278
  ) {
264
- const {chain, events, logger, metrics} = modules;
279
+ const {chain, core, events, logger, metrics} = modules;
265
280
  this.chain = chain;
281
+ this.core = core;
266
282
  this.events = events;
267
283
  this.metrics = metrics;
268
284
  this.logger = logger;
269
285
  this.events = events;
270
286
  this.gossipQueues = createGossipQueues();
271
287
  this.gossipTopicConcurrency = mapValues(this.gossipQueues, () => 0);
272
- this.gossipValidatorFn = getGossipValidatorFn(modules.gossipHandlers ?? getGossipHandlers(modules, opts), modules);
288
+ this.gossipValidatorFn = getGossipValidatorFn(
289
+ modules.gossipHandlers ?? getGossipHandlers(modules, opts),
290
+ modules,
291
+ this.onFatalPeer
292
+ );
273
293
  this.gossipValidatorBatchFn = getGossipValidatorBatchFn(
274
294
  modules.gossipHandlers ?? getGossipHandlers(modules, opts),
275
- modules
295
+ modules,
296
+ this.onFatalPeer
276
297
  );
277
298
 
278
299
  events.on(NetworkEvent.pendingGossipsubMessage, this.onPendingGossipsubMessage);
@@ -406,19 +427,47 @@ export class NetworkProcessor {
406
427
  }
407
428
  }
408
429
 
430
+ private onFatalPeer = (peer: PeerIdStr): void => {
431
+ this.fatalPeers.set(peer, this.chain.clock.currentSlot);
432
+ };
433
+
409
434
  private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
410
435
  const topicType = message.topic.type;
436
+ if (this.fatalPeers.has(message.propagationSource)) {
437
+ this.metrics?.networkProcessor.gossipValidationError.inc({topic: topicType, error: GossipErrorCode.FATAL_PEER});
438
+ // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
439
+ return;
440
+ }
441
+
411
442
  const extractBlockSlotRootFn = this.extractBlockSlotRootFns[topicType];
443
+ if (extractBlockSlotRootFn === undefined) {
444
+ // some messages don't have slot and root
445
+ this.pushPendingGossipsubMessageToQueue(message);
446
+ return;
447
+ }
412
448
 
413
449
  // 1st extract round: make sure slot is in range and if block root is not available
414
450
  // proactively search for it + queue the message
415
- const slotRoot = extractBlockSlotRootFn
416
- ? extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork)
417
- : null;
451
+ const slotRoot = extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork);
418
452
  if (slotRoot === null) {
419
- // some messages don't have slot and root
420
- // if the msg.data is invalid, message will be rejected when deserializing data in later phase (gossipValidatorFn)
421
- this.pushPendingGossipsubMessageToQueue(message);
453
+ // DOS protection: null means malformed bytes or slot >= 2^32, neither is forwarded by honest peers.
454
+ const {propagationSource, clientAgent, clientVersion} = message;
455
+ const code = GossipErrorCode.INVALID_SLOT;
456
+ const peerAction = rejectPeerAction(topicType, code);
457
+ this.metrics?.networkProcessor.gossipValidationReject.inc({topic: topicType});
458
+ this.core.reportPeer(propagationSource, peerAction, code);
459
+ this.logger.debug(`Gossip validation ${topicType} rejected due to null slot`, {
460
+ peer: propagationSource,
461
+ clientAgent,
462
+ clientVersion,
463
+ peerAction,
464
+ code,
465
+ });
466
+ this.events.emit(NetworkEvent.gossipMessageValidationResult, {
467
+ msgId: message.msgId,
468
+ propagationSource,
469
+ acceptance: TopicValidatorResult.Reject,
470
+ });
422
471
  return;
423
472
  }
424
473
 
@@ -872,6 +921,10 @@ export class NetworkProcessor {
872
921
  for (const slot of this.bufferedRootsBySlot.keys()) {
873
922
  if (slot <= minSlot) this.bufferedRootsBySlot.delete(slot);
874
923
  }
924
+
925
+ for (const [peer, slot] of this.fatalPeers) {
926
+ if (clockSlot - slot > FATAL_PEER_EXPIRY_SLOTS) this.fatalPeers.delete(peer);
927
+ }
875
928
  };
876
929
 
877
930
  private executeWork(): void {
@@ -927,6 +980,32 @@ export class NetworkProcessor {
927
980
  private async processPendingGossipsubMessage(
928
981
  messageOrArray: PendingGossipsubMessage | PendingGossipsubMessage[]
929
982
  ): Promise<void> {
983
+ // drop messages from peers Fatal'ed after these were queued or started awaiting a block/envelope
984
+ // No need to report the dropped jobs to gossip. They will be eventually pruned from the mcache
985
+ if (this.fatalPeers.size > 0) {
986
+ if (Array.isArray(messageOrArray)) {
987
+ const allowed: PendingGossipsubMessage[] = [];
988
+ for (const msg of messageOrArray) {
989
+ if (this.fatalPeers.has(msg.propagationSource)) {
990
+ this.metrics?.networkProcessor.gossipValidationError.inc({
991
+ topic: msg.topic.type,
992
+ error: GossipErrorCode.FATAL_PEER,
993
+ });
994
+ } else {
995
+ allowed.push(msg);
996
+ }
997
+ }
998
+ if (allowed.length === 0) return;
999
+ messageOrArray = allowed;
1000
+ } else if (this.fatalPeers.has(messageOrArray.propagationSource)) {
1001
+ this.metrics?.networkProcessor.gossipValidationError.inc({
1002
+ topic: messageOrArray.topic.type,
1003
+ error: GossipErrorCode.FATAL_PEER,
1004
+ });
1005
+ return;
1006
+ }
1007
+ }
1008
+
930
1009
  const nowSec = Date.now() / 1000;
931
1010
  if (Array.isArray(messageOrArray)) {
932
1011
  for (const msg of messageOrArray) {
@@ -6,6 +6,7 @@ import {toRootHex} from "@lodestar/utils";
6
6
  import {EnvelopeReconstructionError} from "../../../chain/errors/index.js";
7
7
  import {IBeaconChain} from "../../../chain/index.js";
8
8
  import {IBeaconDb} from "../../../db/index.js";
9
+ import {MAX_BODIES_PER_REQUEST} from "../../../util/execution.js";
9
10
  import {ExecutionPayloadEnvelopesByRootRequest} from "../../../util/types.js";
10
11
  import {prettyPrintPeerId} from "../../util.js";
11
12
 
@@ -19,10 +20,15 @@ export async function* onExecutionPayloadEnvelopesByRoot(
19
20
  // The gloas req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
20
21
  // Archival nodes may still serve older retained payloads to allow genesis sync.
21
22
 
22
- // Resolve slots first so archived header envelopes are rebuilt in one EL batch, not one call per root
23
+ // Resolve slots first so archived envelopes can be reconstructed in EL batches.
24
+ // Duplicate roots are served once, since each archived envelope costs an EL fetch and a rebuild.
23
25
  const requests: {blockSlot: Slot; blockRootHex: RootHex}[] = [];
26
+ const seenRoots = new Set<RootHex>();
24
27
  for (const root of requestBody) {
25
28
  const rootHex = toRootHex(root);
29
+ if (seenRoots.has(rootHex)) continue;
30
+ seenRoots.add(rootHex);
31
+
26
32
  const block = chain.forkChoice.getBlockHexDefaultStatus(rootHex);
27
33
  // If the block is not in fork choice, it may be finalized. Attempt to find its slot in block archive
28
34
  const slot = block ? block.slot : await db.blockArchive.getSlotByRoot(root);
@@ -41,35 +47,43 @@ export async function* onExecutionPayloadEnvelopesByRoot(
41
47
  requests.push({blockSlot: slot, blockRootHex: rootHex});
42
48
  }
43
49
 
44
- let envelopesBytes: (Uint8Array | null)[];
45
- try {
46
- // by-root allows omission, so a mismatched envelope is left out rather than failing the response
47
- envelopesBytes = await chain.getSerializedExecutionPayloadEnvelopes(requests, "omit");
48
- } catch (e) {
49
- if (e instanceof EnvelopeReconstructionError) {
50
- throw new ResponseError(
51
- RespStatus.RESOURCE_UNAVAILABLE,
52
- `Failed to reconstruct archived envelopes: ${e.message}`
53
- );
50
+ // Yield between EL batches so the first response does not wait for the entire request.
51
+ let yielded = 0;
52
+ for (let i = 0; i < requests.length; i += MAX_BODIES_PER_REQUEST) {
53
+ const batch = requests.slice(i, i + MAX_BODIES_PER_REQUEST);
54
+ let envelopesBytes: (Uint8Array | null)[];
55
+ try {
56
+ // By-root allows omission, so an envelope that cannot be rebuilt is left out rather than failing the response.
57
+ envelopesBytes = await chain.getSerializedExecutionPayloadEnvelopes(batch, "omit");
58
+ } catch (e) {
59
+ if (e instanceof EnvelopeReconstructionError) {
60
+ // By-root permits partial responses, so an EL failure can end the response here.
61
+ if (yielded > 0) return;
62
+ throw new ResponseError(
63
+ RespStatus.RESOURCE_UNAVAILABLE,
64
+ `Failed to reconstruct archived envelopes: ${e.message}`
65
+ );
66
+ }
67
+ throw e;
54
68
  }
55
- throw e;
56
- }
57
69
 
58
- for (let i = 0; i < requests.length; i++) {
59
- const {blockSlot, blockRootHex} = requests[i];
60
- const envelopeBytes = envelopesBytes[i];
61
- if (envelopeBytes) {
62
- yield {
63
- data: envelopeBytes,
64
- boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(blockSlot)),
65
- };
66
- } else {
67
- chain.logger.debug("Cannot serve ExecutionPayloadEnvelopesByRoot: envelope not found", {
68
- slot: blockSlot,
69
- root: blockRootHex,
70
- peer: prettyPrintPeerId(peerId),
71
- client: peerClient,
72
- });
70
+ for (let j = 0; j < batch.length; j++) {
71
+ const {blockSlot, blockRootHex} = batch[j];
72
+ const envelopeBytes = envelopesBytes[j];
73
+ if (envelopeBytes) {
74
+ yielded++;
75
+ yield {
76
+ data: envelopeBytes,
77
+ boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(blockSlot)),
78
+ };
79
+ } else {
80
+ chain.logger.debug("Cannot serve ExecutionPayloadEnvelopesByRoot: envelope not found", {
81
+ slot: blockSlot,
82
+ root: blockRootHex,
83
+ peer: prettyPrintPeerId(peerId),
84
+ client: peerClient,
85
+ });
86
+ }
73
87
  }
74
88
  }
75
89
  }
@@ -1,6 +1,12 @@
1
1
  import {setMaxListeners} from "node:events";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Registry} from "prom-client";
4
+ import {
5
+ init as initNativeMetrics,
6
+ registerLocalValidator as registerNativeLocalValidator,
7
+ scrapeMetrics as scrapeNativeMetrics,
8
+ unregisterLocalValidator as unregisterNativeLocalValidator,
9
+ } from "@chainsafe/lodestar-z/metrics";
4
10
  import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
5
11
  import {hasher} from "@chainsafe/persistent-merkle-tree";
6
12
  import {BeaconApiMethods} from "@lodestar/api/beacon/server";
@@ -171,7 +177,13 @@ export class BeaconNode {
171
177
  // monitoring relies on metrics data
172
178
  opts.monitoring.endpoint
173
179
  ) {
174
- metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries);
180
+ if (opts.chain.nativeStateTransition) {
181
+ initNativeMetrics();
182
+ }
183
+
184
+ metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries, {
185
+ includeStateTransitionMetrics: !opts.chain.nativeStateTransition,
186
+ });
175
187
  initBeaconMetrics(metrics, anchorState);
176
188
  // Since the db is instantiated before this, metrics must be injected manually afterwards
177
189
  db.setMetrics(metrics.db, metrics.flatFileStore);
@@ -185,7 +197,13 @@ export class BeaconNode {
185
197
  config,
186
198
  anchorState.genesisTime,
187
199
  logger.child({module: LoggerModule.vmon}),
188
- opts.validatorMonitor
200
+ opts.validatorMonitor,
201
+ opts.chain.nativeStateTransition
202
+ ? {
203
+ registerLocalValidator: registerNativeLocalValidator,
204
+ unregisterLocalValidator: unregisterNativeLocalValidator,
205
+ }
206
+ : null
189
207
  )
190
208
  : null;
191
209
 
@@ -303,7 +321,17 @@ export class BeaconNode {
303
321
  const metricsServer = opts.metrics.enabled
304
322
  ? await getHttpMetricsServer(opts.metrics, {
305
323
  register: (metrics as Metrics).register,
306
- getOtherMetrics: async () => Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]),
324
+ getOtherMetrics: async () => {
325
+ const otherMetrics = await Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]);
326
+ if (opts.chain.nativeStateTransition) {
327
+ try {
328
+ otherMetrics.push(scrapeNativeMetrics());
329
+ } catch (e) {
330
+ logger.warn("Failed to scrape native state-transition metrics", {}, e as Error);
331
+ }
332
+ }
333
+ return otherMetrics;
334
+ },
307
335
  logger: logger.child({module: LoggerModule.metrics}),
308
336
  })
309
337
  : null;
@@ -773,6 +773,7 @@ export class Batch {
773
773
  // remove any downloaded blocks and re-attempt
774
774
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
775
775
  this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
776
+ this.resetRequests();
776
777
  }
777
778
 
778
779
  private onProcessingError(attempt: FailedAttempt): void {
@@ -784,6 +785,16 @@ export class Batch {
784
785
  // remove any downloaded blocks and re-attempt
785
786
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
786
787
  this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
788
+ this.resetRequests();
789
+ }
790
+
791
+ /**
792
+ * After a partial download `requests` only cover the data still missing from the downloaded blocks,
793
+ * e.g. a single column. Once those blocks are dropped, request the whole batch again, otherwise
794
+ * every peer fails the retry for lack of blocks to validate the data against.
795
+ */
796
+ private resetRequests(): void {
797
+ this.requests = this.getRequests([]);
787
798
  }
788
799
 
789
800
  /** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
@@ -225,6 +225,9 @@ export class SyncChain {
225
225
  Math.floor((localFinalizedEpoch - this.lastEpochWithProcessBlocks) / EPOCHS_PER_BATCH) * EPOCHS_PER_BATCH;
226
226
  this.advanceChain(lastEpochWithProcessBlocksAligned);
227
227
 
228
+ // stopSyncing() cleared the retry timer, but peers can still be in rate limit backoff
229
+ this.scheduleRateLimitBackoffRetry();
230
+
228
231
  // Potentially download new batches and process pending
229
232
  this.triggerBatchDownloader();
230
233
  this.triggerBatchProcessor();
@@ -417,8 +420,10 @@ export class SyncChain {
417
420
  this.rateLimitBackoffTimeout = setTimeout(
418
421
  () => {
419
422
  this.rateLimitBackoffTimeout = undefined;
420
- this.triggerBatchDownloader();
423
+ // Must run before the downloader, it drops expired peers without triggering a download. If it ran
424
+ // after, a backoff expiring in between would leave the peer idle with no retry scheduled.
421
425
  this.scheduleRateLimitBackoffRetry();
426
+ this.triggerBatchDownloader();
422
427
  },
423
428
  Math.max(0, retryAt - now)
424
429
  );