@humanode-hyperlane/rebalancer 27.2.2 → 1029.1.1

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 (295) hide show
  1. package/dist/bridges/LiFiBridge.d.ts +3 -3
  2. package/dist/bridges/LiFiBridge.d.ts.map +1 -1
  3. package/dist/bridges/LiFiBridge.js +66 -55
  4. package/dist/bridges/LiFiBridge.js.map +1 -1
  5. package/dist/bridges/LiFiBridge.test.js +213 -0
  6. package/dist/bridges/LiFiBridge.test.js.map +1 -1
  7. package/dist/config/RebalancerConfig.d.ts +4 -3
  8. package/dist/config/RebalancerConfig.d.ts.map +1 -1
  9. package/dist/config/RebalancerConfig.js +7 -5
  10. package/dist/config/RebalancerConfig.js.map +1 -1
  11. package/dist/config/RebalancerConfig.test.js +278 -2
  12. package/dist/config/RebalancerConfig.test.js.map +1 -1
  13. package/dist/config/types.d.ts +707 -2664
  14. package/dist/config/types.d.ts.map +1 -1
  15. package/dist/config/types.js +62 -7
  16. package/dist/config/types.js.map +1 -1
  17. package/dist/core/InventoryRebalancer.d.ts +13 -19
  18. package/dist/core/InventoryRebalancer.d.ts.map +1 -1
  19. package/dist/core/InventoryRebalancer.js +404 -276
  20. package/dist/core/InventoryRebalancer.js.map +1 -1
  21. package/dist/core/InventoryRebalancer.test.js +814 -25
  22. package/dist/core/InventoryRebalancer.test.js.map +1 -1
  23. package/dist/core/Rebalancer.d.ts.map +1 -1
  24. package/dist/core/Rebalancer.js +12 -6
  25. package/dist/core/Rebalancer.js.map +1 -1
  26. package/dist/core/Rebalancer.test.js +51 -0
  27. package/dist/core/Rebalancer.test.js.map +1 -1
  28. package/dist/core/RebalancerOrchestrator.d.ts.map +1 -1
  29. package/dist/core/RebalancerOrchestrator.test.js +0 -1
  30. package/dist/core/RebalancerOrchestrator.test.js.map +1 -1
  31. package/dist/core/RebalancerService.d.ts.map +1 -1
  32. package/dist/core/RebalancerService.js +3 -2
  33. package/dist/core/RebalancerService.js.map +1 -1
  34. package/dist/core/RebalancerService.test.js +24 -0
  35. package/dist/core/RebalancerService.test.js.map +1 -1
  36. package/dist/e2e/fixtures/routes.d.ts +3 -3
  37. package/dist/e2e/fixtures/routes.d.ts.map +1 -1
  38. package/dist/e2e/harness/BaseLocalDeploymentManager.d.ts.map +1 -1
  39. package/dist/e2e/harness/BaseLocalDeploymentManager.js +3 -2
  40. package/dist/e2e/harness/BaseLocalDeploymentManager.js.map +1 -1
  41. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.d.ts.map +1 -1
  42. package/dist/e2e/harness/Erc20InventoryLocalDeploymentManager.js.map +1 -1
  43. package/dist/e2e/harness/Erc20LocalDeploymentManager.d.ts.map +1 -1
  44. package/dist/e2e/harness/ForkIndexer.d.ts.map +1 -1
  45. package/dist/e2e/harness/ForkIndexer.js.map +1 -1
  46. package/dist/e2e/harness/LocalProvider.d.ts +3 -0
  47. package/dist/e2e/harness/LocalProvider.d.ts.map +1 -0
  48. package/dist/e2e/harness/LocalProvider.js +9 -0
  49. package/dist/e2e/harness/LocalProvider.js.map +1 -0
  50. package/dist/e2e/harness/LocalProvider.test.d.ts +2 -0
  51. package/dist/e2e/harness/LocalProvider.test.d.ts.map +1 -0
  52. package/dist/e2e/harness/LocalProvider.test.js +14 -0
  53. package/dist/e2e/harness/LocalProvider.test.js.map +1 -0
  54. package/dist/e2e/harness/MixedLocalDeploymentManager.d.ts.map +1 -1
  55. package/dist/e2e/harness/MixedLocalDeploymentManager.js.map +1 -1
  56. package/dist/e2e/harness/MockExplorerClient.d.ts.map +1 -1
  57. package/dist/e2e/harness/MockExplorerClient.js.map +1 -1
  58. package/dist/e2e/harness/MockExternalBridge.d.ts.map +1 -1
  59. package/dist/e2e/harness/MockExternalBridge.js.map +1 -1
  60. package/dist/e2e/harness/NativeLocalDeploymentManager.d.ts.map +1 -1
  61. package/dist/e2e/harness/NativeLocalDeploymentManager.js +2 -1
  62. package/dist/e2e/harness/NativeLocalDeploymentManager.js.map +1 -1
  63. package/dist/e2e/harness/SnapshotHelper.d.ts.map +1 -1
  64. package/dist/e2e/harness/SnapshotHelper.js +3 -5
  65. package/dist/e2e/harness/SnapshotHelper.js.map +1 -1
  66. package/dist/e2e/harness/TestHelpers.js +1 -2
  67. package/dist/e2e/harness/TestHelpers.js.map +1 -1
  68. package/dist/e2e/harness/TestRebalancer.d.ts +5 -1
  69. package/dist/e2e/harness/TestRebalancer.d.ts.map +1 -1
  70. package/dist/e2e/harness/TestRebalancer.js +14 -4
  71. package/dist/e2e/harness/TestRebalancer.js.map +1 -1
  72. package/dist/e2e/relayer-denylist.e2e-test.d.ts +2 -0
  73. package/dist/e2e/relayer-denylist.e2e-test.d.ts.map +1 -0
  74. package/dist/e2e/relayer-denylist.e2e-test.js +388 -0
  75. package/dist/e2e/relayer-denylist.e2e-test.js.map +1 -0
  76. package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
  77. package/dist/factories/RebalancerContextFactory.js +61 -31
  78. package/dist/factories/RebalancerContextFactory.js.map +1 -1
  79. package/dist/factories/RebalancerContextFactory.test.js +336 -5
  80. package/dist/factories/RebalancerContextFactory.test.js.map +1 -1
  81. package/dist/index.d.ts +2 -2
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/interfaces/IRebalancer.d.ts +4 -2
  86. package/dist/interfaces/IRebalancer.d.ts.map +1 -1
  87. package/dist/metrics/Metrics.d.ts.map +1 -1
  88. package/dist/metrics/Metrics.js +1 -1
  89. package/dist/metrics/Metrics.js.map +1 -1
  90. package/dist/metrics/PriceGetter.d.ts +1 -0
  91. package/dist/metrics/PriceGetter.d.ts.map +1 -1
  92. package/dist/metrics/PriceGetter.js +14 -4
  93. package/dist/metrics/PriceGetter.js.map +1 -1
  94. package/dist/metrics/PriceGetter.test.d.ts +2 -0
  95. package/dist/metrics/PriceGetter.test.d.ts.map +1 -0
  96. package/dist/metrics/PriceGetter.test.js +84 -0
  97. package/dist/metrics/PriceGetter.test.js.map +1 -0
  98. package/dist/metrics/scripts/metrics.d.ts +4 -4
  99. package/dist/monitor/Monitor.d.ts +1 -0
  100. package/dist/monitor/Monitor.d.ts.map +1 -1
  101. package/dist/monitor/Monitor.js +55 -46
  102. package/dist/monitor/Monitor.js.map +1 -1
  103. package/dist/monitor/Monitor.test.d.ts +2 -0
  104. package/dist/monitor/Monitor.test.d.ts.map +1 -0
  105. package/dist/monitor/Monitor.test.js +346 -0
  106. package/dist/monitor/Monitor.test.js.map +1 -0
  107. package/dist/service.d.ts +1 -1
  108. package/dist/service.d.ts.map +1 -1
  109. package/dist/service.js +9 -5
  110. package/dist/service.js.map +1 -1
  111. package/dist/strategy/BaseStrategy.d.ts.map +1 -1
  112. package/dist/strategy/BaseStrategy.js +13 -11
  113. package/dist/strategy/BaseStrategy.js.map +1 -1
  114. package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -1
  115. package/dist/strategy/CollateralDeficitStrategy.js +2 -2
  116. package/dist/strategy/CollateralDeficitStrategy.js.map +1 -1
  117. package/dist/strategy/CompositeStrategy.d.ts.map +1 -1
  118. package/dist/strategy/CompositeStrategy.js.map +1 -1
  119. package/dist/strategy/CompositeStrategy.test.js.map +1 -1
  120. package/dist/strategy/MinAmountStrategy.d.ts +1 -0
  121. package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
  122. package/dist/strategy/MinAmountStrategy.js +12 -8
  123. package/dist/strategy/MinAmountStrategy.js.map +1 -1
  124. package/dist/strategy/MinAmountStrategy.test.js +189 -2
  125. package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
  126. package/dist/strategy/StrategyFactory.d.ts.map +1 -1
  127. package/dist/strategy/StrategyFactory.js.map +1 -1
  128. package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
  129. package/dist/test/helpers.d.ts +11 -3
  130. package/dist/test/helpers.d.ts.map +1 -1
  131. package/dist/test/helpers.js +9 -11
  132. package/dist/test/helpers.js.map +1 -1
  133. package/dist/test/lifiMocks.d.ts +6 -6
  134. package/dist/test/lifiMocks.d.ts.map +1 -1
  135. package/dist/test/lifiMocks.js +5 -2
  136. package/dist/test/lifiMocks.js.map +1 -1
  137. package/dist/test/pendingTransferMiddlewareMocks.d.ts +17 -0
  138. package/dist/test/pendingTransferMiddlewareMocks.d.ts.map +1 -0
  139. package/dist/test/pendingTransferMiddlewareMocks.js +43 -0
  140. package/dist/test/pendingTransferMiddlewareMocks.js.map +1 -0
  141. package/dist/tracking/ActionTracker.d.ts +4 -0
  142. package/dist/tracking/ActionTracker.d.ts.map +1 -1
  143. package/dist/tracking/ActionTracker.js +76 -9
  144. package/dist/tracking/ActionTracker.js.map +1 -1
  145. package/dist/tracking/ActionTracker.test.js +418 -1
  146. package/dist/tracking/ActionTracker.test.js.map +1 -1
  147. package/dist/tracking/IActionTracker.d.ts +4 -2
  148. package/dist/tracking/IActionTracker.d.ts.map +1 -1
  149. package/dist/tracking/InflightContextAdapter.d.ts.map +1 -1
  150. package/dist/tracking/InflightContextAdapter.js.map +1 -1
  151. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts +64 -0
  152. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.d.ts.map +1 -0
  153. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js +474 -0
  154. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.js.map +1 -0
  155. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts +2 -0
  156. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.d.ts.map +1 -0
  157. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js +1168 -0
  158. package/dist/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.js.map +1 -0
  159. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts +61 -0
  160. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.d.ts.map +1 -0
  161. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js +160 -0
  162. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.js.map +1 -0
  163. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts +2 -0
  164. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.d.ts.map +1 -0
  165. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js +369 -0
  166. package/dist/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.js.map +1 -0
  167. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts +60 -0
  168. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.d.ts.map +1 -0
  169. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js +288 -0
  170. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.js.map +1 -0
  171. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts +2 -0
  172. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.d.ts.map +1 -0
  173. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js +606 -0
  174. package/dist/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.js.map +1 -0
  175. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts +25 -0
  176. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.d.ts.map +1 -0
  177. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js +41 -0
  178. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.js.map +1 -0
  179. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts +2 -0
  180. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.d.ts.map +1 -0
  181. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js +141 -0
  182. package/dist/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.js.map +1 -0
  183. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts +12 -0
  184. package/dist/tracking/pendingTransferMiddlewares/matchBody.d.ts.map +1 -0
  185. package/dist/tracking/pendingTransferMiddlewares/matchBody.js +29 -0
  186. package/dist/tracking/pendingTransferMiddlewares/matchBody.js.map +1 -0
  187. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts +59 -0
  188. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.d.ts.map +1 -0
  189. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js +661 -0
  190. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.js.map +1 -0
  191. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts +2 -0
  192. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.d.ts.map +1 -0
  193. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js +1093 -0
  194. package/dist/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.js.map +1 -0
  195. package/dist/tracking/pendingTransferMiddlewares/types.d.ts +2 -0
  196. package/dist/tracking/pendingTransferMiddlewares/types.d.ts.map +1 -0
  197. package/dist/tracking/pendingTransferMiddlewares/types.js +4 -0
  198. package/dist/tracking/pendingTransferMiddlewares/types.js.map +1 -0
  199. package/dist/tracking/store/InMemoryStore.d.ts.map +1 -1
  200. package/dist/utils/ExplorerClient.d.ts +1 -0
  201. package/dist/utils/ExplorerClient.d.ts.map +1 -1
  202. package/dist/utils/ExplorerClient.js +8 -3
  203. package/dist/utils/ExplorerClient.js.map +1 -1
  204. package/dist/utils/ExplorerClient.test.js +25 -0
  205. package/dist/utils/ExplorerClient.test.js.map +1 -1
  206. package/dist/utils/balanceUtils.d.ts +7 -1
  207. package/dist/utils/balanceUtils.d.ts.map +1 -1
  208. package/dist/utils/balanceUtils.js +39 -1
  209. package/dist/utils/balanceUtils.js.map +1 -1
  210. package/dist/utils/balanceUtils.test.js +55 -1
  211. package/dist/utils/balanceUtils.test.js.map +1 -1
  212. package/dist/utils/blockTag.d.ts +5 -4
  213. package/dist/utils/blockTag.d.ts.map +1 -1
  214. package/dist/utils/blockTag.js +26 -10
  215. package/dist/utils/blockTag.js.map +1 -1
  216. package/dist/utils/blockTag.test.d.ts +2 -0
  217. package/dist/utils/blockTag.test.d.ts.map +1 -0
  218. package/dist/utils/blockTag.test.js +132 -0
  219. package/dist/utils/blockTag.test.js.map +1 -0
  220. package/dist/utils/bridgeUtils.js.map +1 -1
  221. package/dist/utils/errors.d.ts.map +1 -1
  222. package/dist/utils/gasEstimation.d.ts +2 -1
  223. package/dist/utils/gasEstimation.d.ts.map +1 -1
  224. package/dist/utils/gasEstimation.js +16 -13
  225. package/dist/utils/gasEstimation.js.map +1 -1
  226. package/dist/utils/gasEstimation.test.d.ts +2 -0
  227. package/dist/utils/gasEstimation.test.d.ts.map +1 -0
  228. package/dist/utils/gasEstimation.test.js +104 -0
  229. package/dist/utils/gasEstimation.test.js.map +1 -0
  230. package/dist/utils/tokenUtils.d.ts.map +1 -1
  231. package/dist/utils/tokenUtils.js +5 -2
  232. package/dist/utils/tokenUtils.js.map +1 -1
  233. package/package.json +23 -22
  234. package/src/bridges/LiFiBridge.test.ts +270 -0
  235. package/src/bridges/LiFiBridge.ts +90 -71
  236. package/src/config/RebalancerConfig.test.ts +315 -1
  237. package/src/config/RebalancerConfig.ts +6 -3
  238. package/src/config/types.ts +98 -8
  239. package/src/core/InventoryRebalancer.test.ts +1092 -38
  240. package/src/core/InventoryRebalancer.ts +584 -351
  241. package/src/core/Rebalancer.test.ts +84 -0
  242. package/src/core/Rebalancer.ts +22 -6
  243. package/src/core/RebalancerOrchestrator.test.ts +0 -1
  244. package/src/core/RebalancerService.test.ts +35 -0
  245. package/src/core/RebalancerService.ts +3 -2
  246. package/src/e2e/harness/BaseLocalDeploymentManager.ts +3 -1
  247. package/src/e2e/harness/LocalProvider.test.ts +16 -0
  248. package/src/e2e/harness/LocalProvider.ts +10 -0
  249. package/src/e2e/harness/NativeLocalDeploymentManager.ts +2 -1
  250. package/src/e2e/harness/SnapshotHelper.ts +2 -5
  251. package/src/e2e/harness/TestHelpers.ts +3 -3
  252. package/src/e2e/harness/TestRebalancer.ts +21 -1
  253. package/src/e2e/relayer-denylist.e2e-test.ts +558 -0
  254. package/src/factories/RebalancerContextFactory.test.ts +489 -8
  255. package/src/factories/RebalancerContextFactory.ts +86 -36
  256. package/src/index.ts +2 -0
  257. package/src/interfaces/IRebalancer.ts +4 -1
  258. package/src/metrics/Metrics.ts +1 -0
  259. package/src/metrics/PriceGetter.test.ts +113 -0
  260. package/src/metrics/PriceGetter.ts +18 -3
  261. package/src/monitor/Monitor.test.ts +408 -0
  262. package/src/monitor/Monitor.ts +123 -97
  263. package/src/service.ts +14 -8
  264. package/src/strategy/BaseStrategy.ts +18 -15
  265. package/src/strategy/CollateralDeficitStrategy.ts +4 -3
  266. package/src/strategy/MinAmountStrategy.test.ts +238 -2
  267. package/src/strategy/MinAmountStrategy.ts +29 -17
  268. package/src/test/helpers.ts +13 -12
  269. package/src/test/lifiMocks.ts +5 -2
  270. package/src/test/pendingTransferMiddlewareMocks.ts +65 -0
  271. package/src/tracking/ActionTracker.test.ts +635 -3
  272. package/src/tracking/ActionTracker.ts +92 -12
  273. package/src/tracking/IActionTracker.ts +4 -2
  274. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.test.ts +1693 -0
  275. package/src/tracking/pendingTransferMiddlewares/BlacklistIsmMiddleware.ts +784 -0
  276. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.test.ts +554 -0
  277. package/src/tracking/pendingTransferMiddlewares/PendingTransferMiddleware.ts +233 -0
  278. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.test.ts +894 -0
  279. package/src/tracking/pendingTransferMiddlewares/RelayerDenylistMiddleware.ts +404 -0
  280. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.test.ts +205 -0
  281. package/src/tracking/pendingTransferMiddlewares/buildPendingTransferMiddlewares.ts +89 -0
  282. package/src/tracking/pendingTransferMiddlewares/fixtures/mainnet-denylist.json +3556 -0
  283. package/src/tracking/pendingTransferMiddlewares/matchBody.ts +43 -0
  284. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.test.ts +1220 -0
  285. package/src/tracking/pendingTransferMiddlewares/relayerDenylistRules.ts +934 -0
  286. package/src/tracking/pendingTransferMiddlewares/types.ts +3 -0
  287. package/src/utils/ExplorerClient.test.ts +42 -0
  288. package/src/utils/ExplorerClient.ts +14 -3
  289. package/src/utils/balanceUtils.test.ts +87 -1
  290. package/src/utils/balanceUtils.ts +73 -2
  291. package/src/utils/blockTag.test.ts +173 -0
  292. package/src/utils/blockTag.ts +35 -15
  293. package/src/utils/gasEstimation.test.ts +192 -0
  294. package/src/utils/gasEstimation.ts +17 -11
  295. package/src/utils/tokenUtils.ts +5 -2
@@ -0,0 +1,784 @@
1
+ import type { Logger } from 'pino';
2
+
3
+ import { ethers, type providers } from 'ethers';
4
+
5
+ import {
6
+ DomainRoutingIsm__factory,
7
+ IInterchainSecurityModule__factory,
8
+ MailboxClient__factory,
9
+ Mailbox__factory,
10
+ StaticAggregationIsm__factory,
11
+ } from '@humanode-hyperlane/core';
12
+ import {
13
+ type DerivedIsmConfig,
14
+ EthJsonRpcBlockParameterTag,
15
+ EvmIsmReader,
16
+ type IsmConfig,
17
+ IsmType,
18
+ ModuleType,
19
+ type MultiProvider,
20
+ } from '@humanode-hyperlane/sdk';
21
+ import { readBlacklistedIds } from '@humanode-hyperlane/sdk/ism/blacklist';
22
+ import {
23
+ isMissingSelectorCallException,
24
+ isRevertData,
25
+ } from '@humanode-hyperlane/sdk/utils/contract';
26
+ import {
27
+ assert,
28
+ concurrentMap,
29
+ LogLevel,
30
+ ProtocolType,
31
+ } from '@humanode-hyperlane/utils';
32
+
33
+ import { PendingTransferMiddlewareType } from '../../config/types.js';
34
+ import type {
35
+ ExplorerMessage,
36
+ IExplorerClient,
37
+ } from '../../utils/ExplorerClient.js';
38
+
39
+ import { PendingTransferMiddleware } from './PendingTransferMiddleware.js';
40
+ import { normalizeMessageId } from './types.js';
41
+
42
+ const REFRESH_CONCURRENCY = 3;
43
+
44
+ export const BLACKLIST_ISM_REFRESH_FAILED_MESSAGE =
45
+ "BlacklistIsm refresh failed, keeping the destination's last known BlacklistIsms and ids";
46
+
47
+ export const BLACKLIST_ISM_NO_BLOCKING_ROUTES_MESSAGE =
48
+ 'No route pair has a BlacklistIsm that can block a message after refresh, ignoring no pending transfers';
49
+
50
+ /**
51
+ * The ISM rejects every message from the origin, because a routing ISM it
52
+ * needs has no module for it.
53
+ */
54
+ interface UnroutedOrigin {
55
+ readonly unroutedRoutingIsm: string | undefined;
56
+ }
57
+
58
+ /**
59
+ * BlacklistIsms, by lowercase address, that make an ISM reject a message on
60
+ * their own.
61
+ */
62
+ type SufficientBlacklists = Set<string>;
63
+
64
+ type ResolvedBlacklists = SufficientBlacklists | UnroutedOrigin;
65
+
66
+ /**
67
+ * The BlacklistIsms that reject one (origin, destination) pair's messages on
68
+ * their own, and the union of their last known blacklisted ids.
69
+ */
70
+ interface RouteBlacklist {
71
+ readonly destination: number;
72
+ readonly chainName: string;
73
+ readonly blacklistIsms: readonly string[];
74
+ readonly ids: Set<string>;
75
+ }
76
+
77
+ /** One BlacklistIsm read on a destination, shared by the pairs using it. */
78
+ interface BlacklistRead {
79
+ readonly destination: number;
80
+ readonly chainName: string;
81
+ readonly blacklistIsm: string;
82
+ readonly routes: RouteBlacklist[];
83
+ }
84
+
85
+ export interface BlacklistIsmMiddlewareOptions {
86
+ refreshIntervalMs: number;
87
+ routersByDomain: Record<number, string>;
88
+ mailboxByDomain: Record<number, string>;
89
+ multiProvider: MultiProvider;
90
+ }
91
+
92
+ function addressOf(config: IsmConfig): string | undefined {
93
+ if (typeof config === 'string') return config;
94
+
95
+ return 'address' in config && typeof config.address === 'string'
96
+ ? config.address
97
+ : undefined;
98
+ }
99
+
100
+ /**
101
+ * The ISMs on `origin`'s path through `config` that defer to their mailbox's
102
+ * default ISM: mailbox default ISM wrappers, and fallback routing ISMs without
103
+ * a module for the origin.
104
+ */
105
+ function defaultingIsms(config: IsmConfig, origin: string): string[] {
106
+ if (typeof config === 'string') return [];
107
+
108
+ switch (config.type) {
109
+ case IsmType.AGGREGATION:
110
+ case IsmType.STORAGE_AGGREGATION:
111
+ return config.modules.flatMap((module) => defaultingIsms(module, origin));
112
+ case IsmType.FALLBACK_ROUTING:
113
+ case IsmType.MAILBOX_DEFAULT: {
114
+ if ('domains' in config && origin in config.domains) {
115
+ return defaultingIsms(config.domains[origin], origin);
116
+ }
117
+
118
+ const address = addressOf(config);
119
+ assert(address, `Derived ${config.type} config has no address`);
120
+ return [address];
121
+ }
122
+ case IsmType.ROUTING:
123
+ case IsmType.INCREMENTAL_ROUTING:
124
+ return origin in config.domains
125
+ ? defaultingIsms(config.domains[origin], origin)
126
+ : [];
127
+ default:
128
+ return [];
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Combines an aggregation's children: copies of one BlacklistIsm reject
134
+ * together, so children are counted by address, and children that always
135
+ * reject count against the threshold.
136
+ */
137
+ function combineAggregation(
138
+ children: readonly ResolvedBlacklists[],
139
+ threshold: number,
140
+ ): ResolvedBlacklists {
141
+ const resolved: SufficientBlacklists[] = [];
142
+ let alwaysRejecting: UnroutedOrigin | undefined;
143
+ for (const child of children) {
144
+ if (child instanceof Set) resolved.push(child);
145
+ else alwaysRejecting ??= child;
146
+ }
147
+
148
+ const passable = resolved.length;
149
+ if (alwaysRejecting && passable < threshold) return alwaysRejecting;
150
+
151
+ const sufficient: SufficientBlacklists = new Set();
152
+ for (const child of resolved) {
153
+ for (const address of child) {
154
+ const rejecting = resolved.filter((other) => other.has(address));
155
+ if (passable - rejecting.length < threshold) {
156
+ sufficient.add(address);
157
+ }
158
+ }
159
+ }
160
+
161
+ return sufficient;
162
+ }
163
+
164
+ /**
165
+ * Finds the BlacklistIsms that make `config` reject a message from `origin`
166
+ * on their own: with only that BlacklistIsm rejecting and every other module
167
+ * passing, the ISM still rejects. Domain routing ISMs are resolved for the
168
+ * origin; a fallback routing ISM without a module for it and a mailbox
169
+ * default ISM wrapper use their entry in `mailboxDefaults`, the already
170
+ * resolved default ISM of their own mailbox, keyed by their lowercase address.
171
+ * A routing ISM without a module for the origin always rejects.
172
+ * Any other ISM never blocks, including amount routing ISMs, whose module
173
+ * depends on the transfer amount.
174
+ */
175
+ function findSufficientBlacklists(
176
+ config: IsmConfig,
177
+ origin: string,
178
+ mailboxDefaults: ReadonlyMap<string, ResolvedBlacklists>,
179
+ ): ResolvedBlacklists {
180
+ if (typeof config === 'string') return new Set();
181
+
182
+ switch (config.type) {
183
+ case IsmType.BLACKLIST: {
184
+ const address = addressOf(config);
185
+ assert(address, 'Derived BlacklistIsm config has no address');
186
+
187
+ return new Set([address.toLowerCase()]);
188
+ }
189
+ case IsmType.AGGREGATION:
190
+ case IsmType.STORAGE_AGGREGATION:
191
+ return combineAggregation(
192
+ config.modules.map((module) =>
193
+ findSufficientBlacklists(module, origin, mailboxDefaults),
194
+ ),
195
+ config.threshold,
196
+ );
197
+ case IsmType.ROUTING:
198
+ case IsmType.INCREMENTAL_ROUTING:
199
+ if (!(origin in config.domains)) {
200
+ return { unroutedRoutingIsm: addressOf(config) };
201
+ }
202
+
203
+ return findSufficientBlacklists(
204
+ config.domains[origin],
205
+ origin,
206
+ mailboxDefaults,
207
+ );
208
+ case IsmType.FALLBACK_ROUTING:
209
+ if (origin in config.domains) {
210
+ return findSufficientBlacklists(
211
+ config.domains[origin],
212
+ origin,
213
+ mailboxDefaults,
214
+ );
215
+ }
216
+
217
+ return mailboxDefaultFor(config, mailboxDefaults);
218
+ case IsmType.MAILBOX_DEFAULT:
219
+ // DefaultIsm routes every message to its mailbox's default ISM.
220
+ return mailboxDefaultFor(config, mailboxDefaults);
221
+ default:
222
+ return new Set();
223
+ }
224
+ }
225
+
226
+ function mailboxDefaultFor(
227
+ config: Exclude<IsmConfig, string>,
228
+ mailboxDefaults: ReadonlyMap<string, ResolvedBlacklists>,
229
+ ): ResolvedBlacklists {
230
+ const address = addressOf(config);
231
+ assert(address, `Derived ${config.type} config has no address`);
232
+
233
+ const resolved = mailboxDefaults.get(address.toLowerCase());
234
+ assert(
235
+ resolved,
236
+ `ISM ${address} defers to a mailbox default ISM that was not resolved`,
237
+ );
238
+
239
+ return resolved;
240
+ }
241
+
242
+ function routeKey(origin: number, destination: number): string {
243
+ return `${origin}:${destination}`;
244
+ }
245
+
246
+ function deriveIsmConfig(
247
+ multiProvider: MultiProvider,
248
+ destination: number,
249
+ ismAddress: string,
250
+ ): Promise<DerivedIsmConfig> {
251
+ return new EvmIsmReader(
252
+ multiProvider,
253
+ multiProvider.getChainName(destination),
254
+ ).deriveIsmConfigFromAddress(ismAddress);
255
+ }
256
+
257
+ const errorStringInterface = new ethers.utils.Interface([
258
+ 'function Error(string)',
259
+ ]);
260
+
261
+ /** The revert data of `DomainRoutingIsm.module(origin)` for an unset origin. */
262
+ function missingOriginRevertData(origin: number): string {
263
+ return errorStringInterface.encodeFunctionData('Error', [
264
+ `No ISM found for origin: ${origin}`,
265
+ ]);
266
+ }
267
+
268
+ function memoize<T>(
269
+ cache: Map<string, Promise<T>>,
270
+ address: string,
271
+ read: () => Promise<T>,
272
+ ): Promise<T> {
273
+ const key = address.toLowerCase();
274
+ const cached = cache.get(key);
275
+ if (cached) return cached;
276
+
277
+ const pending = read();
278
+ cache.set(key, pending);
279
+ return pending;
280
+ }
281
+
282
+ /**
283
+ * Resolves the default ISMs of a destination's mailboxes one origin at a time,
284
+ * reading only the branch that origin's messages use: domain routing ISMs are
285
+ * followed through `module(origin)`, aggregations through their modules, and
286
+ * only NULL-type ISMs, the only ones that can be a BlacklistIsm, are derived.
287
+ * Other ISMs, including routing ISMs without `module(origin)`, never block and
288
+ * are not read further. Reads shared by several origins or mailboxes are made
289
+ * once.
290
+ */
291
+ class MailboxDefaultIsmReader {
292
+ private readonly provider: providers.Provider;
293
+ private readonly defaultIsms = new Map<string, Promise<string>>();
294
+ private readonly mailboxes = new Map<string, Promise<string>>();
295
+ private readonly resolved = new Map<string, Promise<ResolvedBlacklists>>();
296
+ private readonly moduleTypes = new Map<string, Promise<number>>();
297
+ private readonly aggregations = new Map<
298
+ string,
299
+ Promise<[string[], number]>
300
+ >();
301
+ private readonly derived = new Map<string, Promise<DerivedIsmConfig>>();
302
+
303
+ constructor(
304
+ private readonly multiProvider: MultiProvider,
305
+ private readonly destination: number,
306
+ ) {
307
+ this.provider = multiProvider.getProvider(destination);
308
+ }
309
+
310
+ defaultIsm(mailbox: string): Promise<string> {
311
+ return memoize(this.defaultIsms, mailbox, () =>
312
+ Mailbox__factory.connect(mailbox, this.provider).defaultIsm(),
313
+ );
314
+ }
315
+
316
+ /** The mailbox a mailbox default ISM wrapper or a mailbox client uses. */
317
+ mailboxOf(ism: string): Promise<string> {
318
+ return memoize(this.mailboxes, ism, () =>
319
+ MailboxClient__factory.connect(ism, this.provider).mailbox(),
320
+ );
321
+ }
322
+
323
+ resolve(
324
+ mailbox: string,
325
+ origin: number,
326
+ originName: string,
327
+ ): Promise<ResolvedBlacklists> {
328
+ return memoize(this.resolved, `${mailbox}:${origin}`, async () =>
329
+ this.resolveIsm(
330
+ await this.defaultIsm(mailbox),
331
+ origin,
332
+ originName,
333
+ new Set(),
334
+ ),
335
+ );
336
+ }
337
+
338
+ private async resolveIsm(
339
+ address: string,
340
+ origin: number,
341
+ originName: string,
342
+ visited: ReadonlySet<string>,
343
+ ): Promise<ResolvedBlacklists> {
344
+ const key = address.toLowerCase();
345
+ assert(
346
+ !visited.has(key),
347
+ `Mailbox default ISM ${address} loops back to itself for ${originName}`,
348
+ );
349
+ const path = new Set(visited).add(key);
350
+
351
+ const moduleType = await memoize(this.moduleTypes, address, () =>
352
+ IInterchainSecurityModule__factory.connect(
353
+ address,
354
+ this.provider,
355
+ ).moduleType(),
356
+ );
357
+
358
+ switch (moduleType) {
359
+ case ModuleType.ROUTING: {
360
+ let routed: string;
361
+ try {
362
+ routed = await DomainRoutingIsm__factory.connect(
363
+ address,
364
+ this.provider,
365
+ ).module(origin);
366
+ } catch (error: unknown) {
367
+ if (isRevertData(error, missingOriginRevertData(origin))) {
368
+ return { unroutedRoutingIsm: address };
369
+ }
370
+ if (!isMissingSelectorCallException(error)) throw error;
371
+
372
+ return new Set();
373
+ }
374
+
375
+ return this.resolveIsm(routed, origin, originName, path);
376
+ }
377
+ case ModuleType.AGGREGATION: {
378
+ const [modules, threshold] = await memoize(
379
+ this.aggregations,
380
+ address,
381
+ () =>
382
+ StaticAggregationIsm__factory.connect(
383
+ address,
384
+ this.provider,
385
+ ).modulesAndThreshold('0x'),
386
+ );
387
+
388
+ const children: ResolvedBlacklists[] = [];
389
+ for (const module of modules) {
390
+ children.push(
391
+ await this.resolveIsm(module, origin, originName, path),
392
+ );
393
+ }
394
+
395
+ return combineAggregation(children, threshold);
396
+ }
397
+ case ModuleType.NULL:
398
+ return findSufficientBlacklists(
399
+ await this.derive(address),
400
+ originName,
401
+ new Map(),
402
+ );
403
+ default:
404
+ return new Set();
405
+ }
406
+ }
407
+
408
+ private derive(address: string): Promise<DerivedIsmConfig> {
409
+ return memoize(this.derived, address, () =>
410
+ deriveIsmConfig(this.multiProvider, this.destination, address),
411
+ );
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Reads the destination router's ISM and finds its sufficient BlacklistIsms
417
+ * for every origin. A custom router ISM is derived in full once; the mailbox
418
+ * default ISM, used by routers without one and reached through mailbox default
419
+ * ISM wrappers or fallback routing ISMs, is resolved per origin.
420
+ */
421
+ async function readRecipientIsmBlacklists(
422
+ options: BlacklistIsmMiddlewareOptions,
423
+ destination: number,
424
+ router: string,
425
+ origins: number[],
426
+ ): Promise<Array<[number, ResolvedBlacklists]>> {
427
+ const { multiProvider } = options;
428
+ const mailboxAddress = options.mailboxByDomain[destination];
429
+ assert(
430
+ mailboxAddress,
431
+ `Missing mailbox for destination domain ${destination}`,
432
+ );
433
+
434
+ const defaultIsmReader = new MailboxDefaultIsmReader(
435
+ multiProvider,
436
+ destination,
437
+ );
438
+ // Mailbox.recipientIsm falls back to the default ISM when the router's
439
+ // ISM is unset, exactly as delivery does.
440
+ const [recipientIsm, defaultIsm] = await Promise.all([
441
+ Mailbox__factory.connect(
442
+ mailboxAddress,
443
+ multiProvider.getProvider(destination),
444
+ ).recipientIsm(router),
445
+ defaultIsmReader.defaultIsm(mailboxAddress),
446
+ ]);
447
+ const usesDefault = recipientIsm.toLowerCase() === defaultIsm.toLowerCase();
448
+ const customConfig = usesDefault
449
+ ? undefined
450
+ : await deriveIsmConfig(multiProvider, destination, recipientIsm);
451
+
452
+ const resolved: Array<[number, ResolvedBlacklists]> = [];
453
+ for (const origin of origins) {
454
+ const originName = multiProvider.getChainName(origin);
455
+ if (!customConfig) {
456
+ resolved.push([
457
+ origin,
458
+ await defaultIsmReader.resolve(mailboxAddress, origin, originName),
459
+ ]);
460
+ continue;
461
+ }
462
+
463
+ // Each wrapper or fallback routing ISM defers to its own mailbox.
464
+ const mailboxDefaults = new Map<string, ResolvedBlacklists>();
465
+ for (const ism of defaultingIsms(customConfig, originName)) {
466
+ const ismMailbox = await defaultIsmReader.mailboxOf(ism);
467
+ mailboxDefaults.set(
468
+ ism.toLowerCase(),
469
+ await defaultIsmReader.resolve(ismMailbox, origin, originName),
470
+ );
471
+ }
472
+
473
+ resolved.push([
474
+ origin,
475
+ findSufficientBlacklists(customConfig, originName, mailboxDefaults),
476
+ ]);
477
+ }
478
+
479
+ return resolved;
480
+ }
481
+
482
+ /**
483
+ * Resolves the pairs with one EVM destination to the BlacklistIsms that reject
484
+ * their messages on their own and reads their ids at the finalized block.
485
+ */
486
+ async function resolveDestinationRoutes(
487
+ options: BlacklistIsmMiddlewareOptions,
488
+ destination: number,
489
+ logger: Logger,
490
+ resolvedLevel: LogLevel.Info | LogLevel.Debug,
491
+ ): Promise<Map<string, RouteBlacklist>> {
492
+ const { multiProvider } = options;
493
+ const router = options.routersByDomain[destination];
494
+ const origins = Object.keys(options.routersByDomain)
495
+ .map(Number)
496
+ .filter((origin) => origin !== destination);
497
+ const routes = new Map<string, RouteBlacklist>();
498
+
499
+ let resolved: Array<[number, ResolvedBlacklists]>;
500
+ try {
501
+ resolved = await readRecipientIsmBlacklists(
502
+ options,
503
+ destination,
504
+ router,
505
+ origins,
506
+ );
507
+ } catch (error: unknown) {
508
+ const message = error instanceof Error ? error.message : String(error);
509
+ throw new Error(
510
+ `Failed to resolve the ISM of router ${router} on domain ${destination}: ${message}`,
511
+ );
512
+ }
513
+
514
+ for (const [origin, sufficient] of resolved) {
515
+ if (!(sufficient instanceof Set)) {
516
+ logger.warn(
517
+ {
518
+ router,
519
+ destination,
520
+ origin,
521
+ routingIsm: sufficient.unroutedRoutingIsm,
522
+ },
523
+ 'Routing ISM has no module for origin, skipping BlacklistIsm check for this route pair',
524
+ );
525
+ continue;
526
+ }
527
+
528
+ if (sufficient.size === 0) {
529
+ logger.debug(
530
+ { origin, destination, router },
531
+ 'No BlacklistIsm can block messages on this route pair, skipping',
532
+ );
533
+ continue;
534
+ }
535
+
536
+ const route: RouteBlacklist = {
537
+ destination,
538
+ chainName: multiProvider.getChainName(destination),
539
+ blacklistIsms: [...sufficient],
540
+ ids: new Set(),
541
+ };
542
+ routes.set(routeKey(origin, destination), route);
543
+ logger[resolvedLevel](
544
+ { origin, destination, router, blacklistIsms: route.blacklistIsms },
545
+ 'Resolved BlacklistIsms for pending transfer middleware',
546
+ );
547
+ }
548
+
549
+ await seedRouteIds(routes, multiProvider);
550
+ return routes;
551
+ }
552
+
553
+ /**
554
+ * Resolves every pair with an EVM destination and reads its ids. With no
555
+ * `previous` routes, at startup, any destination's failure throws; on refresh,
556
+ * a destination that fails keeps its `previous` routes and ids.
557
+ */
558
+ async function resolveRouteBlacklists(
559
+ options: BlacklistIsmMiddlewareOptions,
560
+ logger: Logger,
561
+ previous?: ReadonlyMap<string, RouteBlacklist>,
562
+ ): Promise<Map<string, RouteBlacklist>> {
563
+ const routes = new Map<string, RouteBlacklist>();
564
+
565
+ for (const destination of Object.keys(options.routersByDomain).map(Number)) {
566
+ if (
567
+ options.multiProvider.getProtocol(destination) !== ProtocolType.Ethereum
568
+ ) {
569
+ logger.debug(
570
+ { destination },
571
+ 'Skipping BlacklistIsm resolution for non-EVM destination',
572
+ );
573
+ continue;
574
+ }
575
+
576
+ let resolved: Iterable<[string, RouteBlacklist]>;
577
+ try {
578
+ resolved = await resolveDestinationRoutes(
579
+ options,
580
+ destination,
581
+ logger,
582
+ previous ? LogLevel.Debug : LogLevel.Info,
583
+ );
584
+ } catch (error: unknown) {
585
+ if (!previous) throw error;
586
+
587
+ logger.warn(
588
+ {
589
+ destination,
590
+ error: error instanceof Error ? error.message : String(error),
591
+ },
592
+ BLACKLIST_ISM_REFRESH_FAILED_MESSAGE,
593
+ );
594
+ resolved = [...previous].filter(
595
+ ([, route]) => route.destination === destination,
596
+ );
597
+ }
598
+
599
+ for (const [key, route] of resolved) routes.set(key, route);
600
+ }
601
+
602
+ return routes;
603
+ }
604
+
605
+ function readKey(destination: number, blacklistIsm: string): string {
606
+ return `${destination}:${blacklistIsm}`;
607
+ }
608
+
609
+ /** Groups the BlacklistIsm reads of `routes`, one per destination and ISM. */
610
+ function groupReads(routes: Iterable<RouteBlacklist>): BlacklistRead[] {
611
+ const reads = new Map<string, BlacklistRead>();
612
+ for (const route of routes) {
613
+ for (const blacklistIsm of route.blacklistIsms) {
614
+ const key = readKey(route.destination, blacklistIsm);
615
+ const read = reads.get(key) ?? {
616
+ destination: route.destination,
617
+ chainName: route.chainName,
618
+ blacklistIsm,
619
+ routes: [],
620
+ };
621
+ read.routes.push(route);
622
+ reads.set(key, read);
623
+ }
624
+ }
625
+
626
+ return [...reads.values()];
627
+ }
628
+
629
+ /** Adds `ids` to every pair using the BlacklistIsm. */
630
+ function addIds(read: BlacklistRead, ids: readonly string[]): void {
631
+ for (const route of read.routes) {
632
+ for (const id of ids) route.ids.add(id.toLowerCase());
633
+ }
634
+ }
635
+
636
+ /** Reads a BlacklistIsm's blacklisted ids at its destination's finalized block. */
637
+ function readFinalizedIds(
638
+ multiProvider: MultiProvider,
639
+ read: BlacklistRead,
640
+ ): Promise<string[]> {
641
+ return readBlacklistedIds(
642
+ read.chainName,
643
+ read.blacklistIsm,
644
+ multiProvider,
645
+ undefined,
646
+ EthJsonRpcBlockParameterTag.Finalized,
647
+ );
648
+ }
649
+
650
+ /**
651
+ * Reads every pair's BlacklistIsms at their destination's finalized block and
652
+ * adds the ids to the pairs. Throws if any read fails.
653
+ */
654
+ async function seedRouteIds(
655
+ routes: ReadonlyMap<string, RouteBlacklist>,
656
+ multiProvider: MultiProvider,
657
+ ): Promise<void> {
658
+ await concurrentMap(
659
+ REFRESH_CONCURRENCY,
660
+ groupReads(routes.values()),
661
+ async (read) => {
662
+ let ids: string[];
663
+ try {
664
+ ids = await readFinalizedIds(multiProvider, read);
665
+ } catch (error: unknown) {
666
+ const message = error instanceof Error ? error.message : String(error);
667
+ throw new Error(
668
+ `Failed to read the blacklisted ids of BlacklistIsm ${read.blacklistIsm} on domain ${read.destination}: ${message}`,
669
+ { cause: error },
670
+ );
671
+ }
672
+
673
+ addIds(read, ids);
674
+ },
675
+ );
676
+ }
677
+
678
+ /**
679
+ * Ignores messages to EVM destinations that a BlacklistIsm in the destination
680
+ * router's ISM rejects on its own: with that BlacklistIsm rejecting and every
681
+ * other module passing, the ISM still rejects the message. ISMs that only
682
+ * reject when several BlacklistIsms reject together are treated as never
683
+ * blocking, so those transfers stay reserved.
684
+ *
685
+ * `create()` resolves each destination router's ISM for every origin of the
686
+ * route and keeps, per (origin, destination) pair, only those BlacklistIsms
687
+ * and the union of their blacklisted ids. A custom router ISM is derived in
688
+ * full with `EvmIsmReader`; the mailbox default ISM, which routers without one
689
+ * use and which mailbox default ISM wrappers and fallback routing ISMs defer
690
+ * to through their own mailbox, is read only along each origin's branch,
691
+ * deriving only the ISMs that can be a BlacklistIsm. Amount routing ISMs are
692
+ * treated as never blocking, since their module depends on the transfer
693
+ * amount.
694
+ *
695
+ * Once `refreshIntervalMs` has passed since the last attempt, the next query
696
+ * repeats this resolution and replaces the pairs, so a changed router ISM,
697
+ * mailbox default ISM or routing ISM takes effect without a restart. The ISM
698
+ * graph is resolved at the latest block. Each destination is refreshed on its
699
+ * own: one whose resolution or id read fails keeps its previous pairs and ids,
700
+ * and a refresh that finds no pair that can block ignores nothing.
701
+ *
702
+ * Blacklisted ids are read at the destination's finalized block, once per
703
+ * BlacklistIsm however many pairs use it, so an id whose blacklisting is
704
+ * reorged out is never ignored and a new blacklist entry takes effect once it
705
+ * is finalized; the ids derived with the graph are not used. An RPC without
706
+ * `finalized` block tag support makes every read on its destination fail,
707
+ * whether the BlacklistIsm enumerates its ids or they are replayed from its
708
+ * logs.
709
+ */
710
+ export class BlacklistIsmMiddleware extends PendingTransferMiddleware {
711
+ private lastAttemptAt = Date.now();
712
+
713
+ private constructor(
714
+ inner: IExplorerClient,
715
+ private readonly options: BlacklistIsmMiddlewareOptions,
716
+ /** Pairs with at least one BlacklistIsm that rejects on its own. */
717
+ private routes: ReadonlyMap<string, RouteBlacklist>,
718
+ ) {
719
+ super(inner, PendingTransferMiddlewareType.BlacklistIsm);
720
+ }
721
+
722
+ /**
723
+ * Resolves the BlacklistIsms of every route pair and reads their ids at the
724
+ * destination's finalized block. Throws if any EVM destination's ISM cannot
725
+ * be derived, if no route pair has a BlacklistIsm that can block a message,
726
+ * or if a BlacklistIsm's ids cannot be read at the finalized block, which
727
+ * includes a destination RPC without `finalized` block tag support. A pair
728
+ * whose domain routing ISM has no module for the origin is skipped with a
729
+ * warning. Later refreshes repeat this per destination but never throw.
730
+ */
731
+ static async create(
732
+ inner: IExplorerClient,
733
+ options: BlacklistIsmMiddlewareOptions,
734
+ logger: Logger,
735
+ ): Promise<BlacklistIsmMiddleware> {
736
+ const routes = await resolveRouteBlacklists(options, logger);
737
+ if (routes.size === 0) {
738
+ throw new Error(
739
+ 'No route pair has a BlacklistIsm that can block a message; remove the blacklistIsm pending transfer middleware',
740
+ );
741
+ }
742
+
743
+ return new BlacklistIsmMiddleware(inner, options, routes);
744
+ }
745
+
746
+ protected async findIgnored(
747
+ messages: ExplorerMessage[],
748
+ logger: Logger,
749
+ ): Promise<Map<string, string>> {
750
+ await this.refreshIfStale(logger);
751
+
752
+ const ignored = new Map<string, string>();
753
+ for (const msg of messages) {
754
+ const destination = msg.destination_domain_id;
755
+ const route = this.routes.get(
756
+ routeKey(msg.origin_domain_id, destination),
757
+ );
758
+ const messageId = normalizeMessageId(msg.msg_id);
759
+ if (!route?.ids.has(messageId)) continue;
760
+
761
+ ignored.set(
762
+ messageId,
763
+ `Blacklisted by a BlacklistIsm (${route.blacklistIsms.join(', ')}) in the ISM of router ${this.options.routersByDomain[destination]} on domain ${destination}`,
764
+ );
765
+ }
766
+
767
+ return ignored;
768
+ }
769
+
770
+ private async refreshIfStale(logger: Logger): Promise<void> {
771
+ const now = Date.now();
772
+ if (now - this.lastAttemptAt < this.options.refreshIntervalMs) return;
773
+
774
+ this.lastAttemptAt = now;
775
+ this.routes = await resolveRouteBlacklists(
776
+ this.options,
777
+ logger,
778
+ this.routes,
779
+ );
780
+ if (this.routes.size === 0) {
781
+ logger.warn(BLACKLIST_ISM_NO_BLOCKING_ROUTES_MESSAGE);
782
+ }
783
+ }
784
+ }