@aztec/ethereum 0.0.1-commit.f2ce05ee → 0.0.1-commit.f5a9928

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 (181) hide show
  1. package/dest/client.d.ts +27 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +63 -8
  4. package/dest/config.d.ts +17 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +38 -10
  7. package/dest/contracts/chain_state_override.d.ts +78 -0
  8. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  9. package/dest/contracts/chain_state_override.js +137 -0
  10. package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
  11. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  12. package/dest/contracts/fee_asset_price_oracle.js +656 -0
  13. package/dest/contracts/governance.d.ts +135 -35
  14. package/dest/contracts/governance.d.ts.map +1 -1
  15. package/dest/contracts/governance.js +229 -18
  16. package/dest/contracts/governance_proposer.d.ts +33 -2
  17. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  18. package/dest/contracts/governance_proposer.js +54 -6
  19. package/dest/contracts/gse.d.ts +4 -2
  20. package/dest/contracts/gse.d.ts.map +1 -1
  21. package/dest/contracts/gse.js +2 -2
  22. package/dest/contracts/inbox.d.ts +3 -3
  23. package/dest/contracts/inbox.d.ts.map +1 -1
  24. package/dest/contracts/inbox.js +12 -7
  25. package/dest/contracts/index.d.ts +4 -3
  26. package/dest/contracts/index.d.ts.map +1 -1
  27. package/dest/contracts/index.js +3 -2
  28. package/dest/contracts/multicall.d.ts +127 -12
  29. package/dest/contracts/multicall.d.ts.map +1 -1
  30. package/dest/contracts/multicall.js +201 -94
  31. package/dest/contracts/outbox.d.ts +27 -4
  32. package/dest/contracts/outbox.d.ts.map +1 -1
  33. package/dest/contracts/outbox.js +34 -7
  34. package/dest/contracts/registry.d.ts +3 -1
  35. package/dest/contracts/registry.d.ts.map +1 -1
  36. package/dest/contracts/registry.js +30 -1
  37. package/dest/contracts/rollup.d.ts +5437 -34
  38. package/dest/contracts/rollup.d.ts.map +1 -1
  39. package/dest/contracts/rollup.js +448 -104
  40. package/dest/contracts/slashing_proposer.d.ts +171 -0
  41. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  42. package/dest/contracts/slashing_proposer.js +753 -0
  43. package/dest/contracts/watch_event.d.ts +36 -0
  44. package/dest/contracts/watch_event.d.ts.map +1 -0
  45. package/dest/contracts/watch_event.js +66 -0
  46. package/dest/deploy_aztec_l1_contracts.d.ts +5 -9
  47. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  48. package/dest/deploy_aztec_l1_contracts.js +48 -27
  49. package/dest/deploy_l1_contract.js +3 -3
  50. package/dest/foundry_binary.d.ts +13 -0
  51. package/dest/foundry_binary.d.ts.map +1 -0
  52. package/dest/foundry_binary.js +52 -0
  53. package/dest/generated/l1-contracts-defaults.d.ts +3 -3
  54. package/dest/generated/l1-contracts-defaults.js +3 -3
  55. package/dest/l1_artifacts.d.ts +15524 -15847
  56. package/dest/l1_artifacts.d.ts.map +1 -1
  57. package/dest/l1_artifacts.js +9 -24
  58. package/dest/l1_contract_addresses.d.ts +61 -65
  59. package/dest/l1_contract_addresses.d.ts.map +1 -1
  60. package/dest/l1_contract_addresses.js +101 -82
  61. package/dest/l1_reader.d.ts +5 -5
  62. package/dest/l1_reader.d.ts.map +1 -1
  63. package/dest/l1_reader.js +9 -7
  64. package/dest/l1_tx_utils/config.d.ts +7 -1
  65. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  66. package/dest/l1_tx_utils/config.js +17 -4
  67. package/dest/l1_tx_utils/factory.d.ts +18 -10
  68. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  69. package/dest/l1_tx_utils/factory.js +17 -7
  70. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +1 -1
  71. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +1 -1
  72. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +15 -15
  73. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +9 -15
  75. package/dest/l1_tx_utils/index-blobs.d.ts +3 -3
  76. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -1
  77. package/dest/l1_tx_utils/index-blobs.js +2 -2
  78. package/dest/l1_tx_utils/index.d.ts +2 -1
  79. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  80. package/dest/l1_tx_utils/index.js +1 -0
  81. package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -7
  82. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  83. package/dest/l1_tx_utils/l1_tx_utils.js +156 -79
  84. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
  85. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  86. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +57 -23
  87. package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
  88. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  89. package/dest/{test → l1_tx_utils}/tx_delayer.js +67 -37
  90. package/dest/publisher_manager.d.ts +34 -8
  91. package/dest/publisher_manager.d.ts.map +1 -1
  92. package/dest/publisher_manager.js +119 -8
  93. package/dest/queries.d.ts +13 -2
  94. package/dest/queries.d.ts.map +1 -1
  95. package/dest/queries.js +62 -3
  96. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  97. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  98. package/dest/test/blob_kzg_warmup.js +64 -0
  99. package/dest/test/chain_monitor.d.ts +55 -4
  100. package/dest/test/chain_monitor.d.ts.map +1 -1
  101. package/dest/test/chain_monitor.js +104 -3
  102. package/dest/test/eth_cheat_codes.d.ts +31 -6
  103. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  104. package/dest/test/eth_cheat_codes.js +67 -44
  105. package/dest/test/index.d.ts +2 -3
  106. package/dest/test/index.d.ts.map +1 -1
  107. package/dest/test/index.js +1 -2
  108. package/dest/test/recording_rpc_server.d.ts +18 -0
  109. package/dest/test/recording_rpc_server.d.ts.map +1 -0
  110. package/dest/test/recording_rpc_server.js +68 -0
  111. package/dest/test/rollup_cheat_codes.d.ts +60 -3
  112. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  113. package/dest/test/rollup_cheat_codes.js +86 -7
  114. package/dest/test/start_anvil.d.ts +23 -3
  115. package/dest/test/start_anvil.d.ts.map +1 -1
  116. package/dest/test/start_anvil.js +182 -32
  117. package/dest/test/upgrade_utils.js +2 -2
  118. package/dest/utils.d.ts +9 -2
  119. package/dest/utils.d.ts.map +1 -1
  120. package/dest/utils.js +59 -24
  121. package/package.json +6 -8
  122. package/src/client.ts +72 -2
  123. package/src/config.ts +58 -14
  124. package/src/contracts/chain_state_override.ts +200 -0
  125. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  126. package/src/contracts/governance.ts +313 -13
  127. package/src/contracts/governance_proposer.ts +58 -9
  128. package/src/contracts/gse.ts +7 -2
  129. package/src/contracts/inbox.ts +13 -5
  130. package/src/contracts/index.ts +3 -2
  131. package/src/contracts/multicall.ts +243 -105
  132. package/src/contracts/outbox.ts +42 -8
  133. package/src/contracts/registry.ts +31 -1
  134. package/src/contracts/rollup.ts +507 -109
  135. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +119 -45
  136. package/src/contracts/watch_event.ts +110 -0
  137. package/src/deploy_aztec_l1_contracts.ts +75 -39
  138. package/src/deploy_l1_contract.ts +3 -3
  139. package/src/foundry_binary.ts +57 -0
  140. package/src/generated/l1-contracts-defaults.ts +3 -3
  141. package/src/l1_artifacts.ts +12 -35
  142. package/src/l1_contract_addresses.ts +120 -94
  143. package/src/l1_reader.ts +17 -10
  144. package/src/l1_tx_utils/config.ts +23 -3
  145. package/src/l1_tx_utils/factory.ts +31 -31
  146. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +1 -1
  147. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +1 -1
  148. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +43 -54
  149. package/src/l1_tx_utils/index-blobs.ts +2 -2
  150. package/src/l1_tx_utils/index.ts +1 -0
  151. package/src/l1_tx_utils/l1_tx_utils.ts +149 -64
  152. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +57 -17
  153. package/src/{test → l1_tx_utils}/tx_delayer.ts +93 -56
  154. package/src/publisher_manager.ts +143 -12
  155. package/src/queries.ts +70 -4
  156. package/src/test/blob_kzg_warmup.ts +65 -0
  157. package/src/test/chain_monitor.ts +148 -3
  158. package/src/test/eth_cheat_codes.ts +63 -47
  159. package/src/test/index.ts +1 -2
  160. package/src/test/recording_rpc_server.ts +69 -0
  161. package/src/test/rollup_cheat_codes.ts +115 -6
  162. package/src/test/start_anvil.ts +212 -32
  163. package/src/test/upgrade_utils.ts +2 -2
  164. package/src/utils.ts +56 -29
  165. package/dest/contracts/empire_slashing_proposer.d.ts +0 -67
  166. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  167. package/dest/contracts/empire_slashing_proposer.js +0 -207
  168. package/dest/contracts/tally_slashing_proposer.d.ts +0 -140
  169. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  170. package/dest/contracts/tally_slashing_proposer.js +0 -320
  171. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  172. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  173. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  174. package/dest/test/delayed_tx_utils.d.ts +0 -13
  175. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  176. package/dest/test/delayed_tx_utils.js +0 -28
  177. package/dest/test/tx_delayer.d.ts +0 -36
  178. package/dest/test/tx_delayer.d.ts.map +0 -1
  179. package/src/contracts/empire_slashing_proposer.ts +0 -259
  180. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  181. package/src/test/delayed_tx_utils.ts +0 -52
@@ -0,0 +1,58 @@
1
+ import { type Logger, type LoggerBindings } from '@aztec/foundation/log';
2
+ import type { DateProvider } from '@aztec/foundation/timer';
3
+ import { type Client, type Hex } from 'viem';
4
+ import type { ViemClient } from '../types.js';
5
+ export declare function waitUntilBlock<T extends Client>(client: T, blockNumber: number | bigint, logger?: Logger, timeout?: number): Promise<boolean>;
6
+ export declare function waitUntilL1Timestamp<T extends Client>(client: T, timestamp: number | bigint, logger?: Logger, timeout?: number): Promise<boolean>;
7
+ /** Manages tx delaying for testing, intercepting sendRawTransaction calls to delay or cancel them. */
8
+ export declare class Delayer {
9
+ dateProvider: DateProvider;
10
+ private logger;
11
+ maxInclusionTimeIntoSlot: number | undefined;
12
+ ethereumSlotDuration: bigint;
13
+ nextWait: {
14
+ l1Timestamp: bigint;
15
+ timeoutSeconds?: number;
16
+ } | {
17
+ l1BlockNumber: bigint;
18
+ timeoutSeconds?: number;
19
+ } | {
20
+ indefinitely: true;
21
+ } | undefined;
22
+ sentTxHashes: Hex[];
23
+ cancelledTxs: Hex[];
24
+ constructor(dateProvider: DateProvider, opts: {
25
+ ethereumSlotDuration: bigint | number;
26
+ }, bindings: LoggerBindings);
27
+ /** Returns the logger instance used by this delayer. */
28
+ getLogger(): Logger;
29
+ /** Returns the hashes of all effectively sent txs. */
30
+ getSentTxHashes(): `0x${string}`[];
31
+ /** Returns the raw hex for all cancelled txs. */
32
+ getCancelledTxs(): Hex[];
33
+ /** Delays the next tx to be sent so it lands on the given L1 block number. */
34
+ pauseNextTxUntilBlock(l1BlockNumber: number | bigint, timeoutSeconds?: number): void;
35
+ /** Delays the next tx to be sent so it lands on the given timestamp. */
36
+ pauseNextTxUntilTimestamp(l1Timestamp: number | bigint, timeoutSeconds?: number): void;
37
+ /** Delays the next tx to be sent indefinitely. */
38
+ cancelNextTx(): void;
39
+ /**
40
+ * Sets max inclusion time into slot. If more than this many seconds have passed
41
+ * since the last L1 block was mined, then any tx will not be mined in the current
42
+ * L1 slot but will be deferred for the next one.
43
+ */
44
+ setMaxInclusionTimeIntoSlot(seconds: number | undefined): void;
45
+ }
46
+ /**
47
+ * Creates a new Delayer instance. Exposed so callers can create a single shared delayer
48
+ * and pass it to multiple `wrapClientWithDelayer` calls.
49
+ */
50
+ export declare function createDelayer(dateProvider: DateProvider, opts: {
51
+ ethereumSlotDuration: bigint | number;
52
+ }, bindings: LoggerBindings): Delayer;
53
+ /**
54
+ * Wraps a viem client with tx delaying logic. Returns the wrapped client.
55
+ * The delayer intercepts sendRawTransaction calls and delays them based on the delayer's state.
56
+ */
57
+ export declare function wrapClientWithDelayer<T extends ViemClient>(client: T, delayer: Delayer): T;
58
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHhfZGVsYXllci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2wxX3R4X3V0aWxzL3R4X2RlbGF5ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFFLEtBQUssY0FBYyxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBRXZGLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRzVELE9BQU8sRUFDTCxLQUFLLE1BQU0sRUFDWCxLQUFLLEdBQUcsRUFVVCxNQUFNLE1BQU0sQ0FBQztBQUVkLE9BQU8sS0FBSyxFQUE0QixVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFJeEUsd0JBQWdCLGNBQWMsQ0FBQyxDQUFDLFNBQVMsTUFBTSxFQUM3QyxNQUFNLEVBQUUsQ0FBQyxFQUNULFdBQVcsRUFBRSxNQUFNLEdBQUcsTUFBTSxFQUM1QixNQUFNLENBQUMsRUFBRSxNQUFNLEVBQ2YsT0FBTyxDQUFDLEVBQUUsTUFBTSxvQkFpQmpCO0FBRUQsd0JBQWdCLG9CQUFvQixDQUFDLENBQUMsU0FBUyxNQUFNLEVBQ25ELE1BQU0sRUFBRSxDQUFDLEVBQ1QsU0FBUyxFQUFFLE1BQU0sR0FBRyxNQUFNLEVBQzFCLE1BQU0sQ0FBQyxFQUFFLE1BQU0sRUFDZixPQUFPLENBQUMsRUFBRSxNQUFNLG9CQXdCakI7QUFFRCxzR0FBc0c7QUFDdEcscUJBQWEsT0FBTztJQWNULFlBQVksRUFBRSxZQUFZO0lBYm5DLE9BQU8sQ0FBQyxNQUFNLENBQVM7SUFFaEIsd0JBQXdCLEVBQUUsTUFBTSxHQUFHLFNBQVMsQ0FBYTtJQUN6RCxvQkFBb0IsRUFBRSxNQUFNLENBQUM7SUFDN0IsUUFBUSxFQUNYO1FBQUUsV0FBVyxFQUFFLE1BQU0sQ0FBQztRQUFDLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQTtLQUFFLEdBQ2hEO1FBQUUsYUFBYSxFQUFFLE1BQU0sQ0FBQztRQUFDLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQTtLQUFFLEdBQ2xEO1FBQUUsWUFBWSxFQUFFLElBQUksQ0FBQTtLQUFFLEdBQ3RCLFNBQVMsQ0FBYTtJQUNuQixZQUFZLEVBQUUsR0FBRyxFQUFFLENBQU07SUFDekIsWUFBWSxFQUFFLEdBQUcsRUFBRSxDQUFNO0lBRWhDLFlBQ1MsWUFBWSxFQUFFLFlBQVksRUFDakMsSUFBSSxFQUFFO1FBQUUsb0JBQW9CLEVBQUUsTUFBTSxHQUFHLE1BQU0sQ0FBQTtLQUFFLEVBQy9DLFFBQVEsRUFBRSxjQUFjLEVBSXpCO0lBRUQsd0RBQXdEO0lBQ3hELFNBQVMsSUFBSSxNQUFNLENBRWxCO0lBRUQsc0RBQXNEO0lBQ3RELGVBQWUsb0JBRWQ7SUFFRCxpREFBaUQ7SUFDakQsZUFBZSxJQUFJLEdBQUcsRUFBRSxDQUV2QjtJQUVELDhFQUE4RTtJQUM5RSxxQkFBcUIsQ0FBQyxhQUFhLEVBQUUsTUFBTSxHQUFHLE1BQU0sRUFBRSxjQUFjLENBQUMsRUFBRSxNQUFNLFFBRTVFO0lBRUQsd0VBQXdFO0lBQ3hFLHlCQUF5QixDQUFDLFdBQVcsRUFBRSxNQUFNLEdBQUcsTUFBTSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE1BQU0sUUFFOUU7SUFFRCxrREFBa0Q7SUFDbEQsWUFBWSxTQUVYO0lBRUQ7Ozs7T0FJRztJQUNILDJCQUEyQixDQUFDLE9BQU8sRUFBRSxNQUFNLEdBQUcsU0FBUyxRQUV0RDtDQUNGO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQWdCLGFBQWEsQ0FDM0IsWUFBWSxFQUFFLFlBQVksRUFDMUIsSUFBSSxFQUFFO0lBQUUsb0JBQW9CLEVBQUUsTUFBTSxHQUFHLE1BQU0sQ0FBQTtDQUFFLEVBQy9DLFFBQVEsRUFBRSxjQUFjLEdBQ3ZCLE9BQU8sQ0FFVDtBQWFEOzs7R0FHRztBQUNILHdCQUFnQixxQkFBcUIsQ0FBQyxDQUFDLFNBQVMsVUFBVSxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsT0FBTyxFQUFFLE9BQU8sR0FBRyxDQUFDLENBdUgxRiJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tx_delayer.d.ts","sourceRoot":"","sources":["../../src/l1_tx_utils/tx_delayer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;AAEvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG5D,OAAO,EACL,KAAK,MAAM,EACX,KAAK,GAAG,EAUT,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAA4B,UAAU,EAAE,MAAM,aAAa,CAAC;AAIxE,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAC7C,MAAM,EAAE,CAAC,EACT,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,oBAiBjB;AAED,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,MAAM,EACnD,MAAM,EAAE,CAAC,EACT,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,oBAwBjB;AAED,sGAAsG;AACtG,qBAAa,OAAO;IAcT,YAAY,EAAE,YAAY;IAbnC,OAAO,CAAC,MAAM,CAAS;IAEhB,wBAAwB,EAAE,MAAM,GAAG,SAAS,CAAa;IACzD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EACX;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAClD;QAAE,YAAY,EAAE,IAAI,CAAA;KAAE,GACtB,SAAS,CAAa;IACnB,YAAY,EAAE,GAAG,EAAE,CAAM;IACzB,YAAY,EAAE,GAAG,EAAE,CAAM;IAEhC,YACS,YAAY,EAAE,YAAY,EACjC,IAAI,EAAE;QAAE,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,EAC/C,QAAQ,EAAE,cAAc,EAIzB;IAED,wDAAwD;IACxD,SAAS,IAAI,MAAM,CAElB;IAED,sDAAsD;IACtD,eAAe,oBAEd;IAED,iDAAiD;IACjD,eAAe,IAAI,GAAG,EAAE,CAEvB;IAED,8EAA8E;IAC9E,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,QAE5E;IAED,wEAAwE;IACxE,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,QAE9E;IAED,kDAAkD;IAClD,YAAY,SAEX;IAED;;;;OAIG;IACH,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,QAEtD;CACF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE;IAAE,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,EAC/C,QAAQ,EAAE,cAAc,GACvB,OAAO,CAET;AAaD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,CAAC,CAuH1F"}
@@ -2,8 +2,7 @@ import { omit } from '@aztec/foundation/collection';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
3
  import { retryUntil } from '@aztec/foundation/retry';
4
4
  import { inspect } from 'util';
5
- import { keccak256, parseTransaction, publicActions, serializeTransaction, walletActions } from 'viem';
6
- import { isExtendedClient } from '../types.js';
5
+ import { keccak256, parseTransaction, publicActions, recoverTransactionAddress, serializeTransaction, walletActions } from 'viem';
7
6
  const MAX_WAIT_TIME_SECONDS = 180;
8
7
  export function waitUntilBlock(client, blockNumber, logger, timeout) {
9
8
  const publicClient = 'getBlockNumber' in client && typeof client.getBlockNumber === 'function' ? client : client.extend(publicActions);
@@ -35,59 +34,80 @@ export function waitUntilL1Timestamp(client, timestamp, logger, timeout) {
35
34
  return currentTs >= BigInt(timestamp);
36
35
  }, `Wait until L1 timestamp ${timestamp}`, timeout ?? MAX_WAIT_TIME_SECONDS, 0.1);
37
36
  }
38
- class DelayerImpl {
37
+ /** Manages tx delaying for testing, intercepting sendRawTransaction calls to delay or cancel them. */ export class Delayer {
39
38
  dateProvider;
40
39
  logger;
41
- constructor(dateProvider, opts){
40
+ maxInclusionTimeIntoSlot;
41
+ ethereumSlotDuration;
42
+ nextWait;
43
+ sentTxHashes;
44
+ cancelledTxs;
45
+ constructor(dateProvider, opts, bindings){
42
46
  this.dateProvider = dateProvider;
43
- this.logger = createLogger('ethereum:tx_delayer');
44
47
  this.maxInclusionTimeIntoSlot = undefined;
45
48
  this.nextWait = undefined;
46
49
  this.sentTxHashes = [];
47
50
  this.cancelledTxs = [];
48
51
  this.ethereumSlotDuration = BigInt(opts.ethereumSlotDuration);
52
+ this.logger = createLogger('ethereum:tx_delayer', bindings);
49
53
  }
50
- maxInclusionTimeIntoSlot;
51
- ethereumSlotDuration;
52
- nextWait;
53
- sentTxHashes;
54
- cancelledTxs;
55
- getSentTxHashes() {
54
+ /** Returns the logger instance used by this delayer. */ getLogger() {
55
+ return this.logger;
56
+ }
57
+ /** Returns the hashes of all effectively sent txs. */ getSentTxHashes() {
56
58
  return this.sentTxHashes;
57
59
  }
58
- getCancelledTxs() {
60
+ /** Returns the raw hex for all cancelled txs. */ getCancelledTxs() {
59
61
  return this.cancelledTxs;
60
62
  }
61
- pauseNextTxUntilBlock(l1BlockNumber) {
63
+ /** Delays the next tx to be sent so it lands on the given L1 block number. */ pauseNextTxUntilBlock(l1BlockNumber, timeoutSeconds) {
62
64
  this.nextWait = {
63
- l1BlockNumber: BigInt(l1BlockNumber)
65
+ l1BlockNumber: BigInt(l1BlockNumber),
66
+ timeoutSeconds
64
67
  };
65
68
  }
66
- pauseNextTxUntilTimestamp(l1Timestamp) {
69
+ /** Delays the next tx to be sent so it lands on the given timestamp. */ pauseNextTxUntilTimestamp(l1Timestamp, timeoutSeconds) {
67
70
  this.nextWait = {
68
- l1Timestamp: BigInt(l1Timestamp)
71
+ l1Timestamp: BigInt(l1Timestamp),
72
+ timeoutSeconds
69
73
  };
70
74
  }
71
- cancelNextTx() {
75
+ /** Delays the next tx to be sent indefinitely. */ cancelNextTx() {
72
76
  this.nextWait = {
73
77
  indefinitely: true
74
78
  };
75
79
  }
76
- setMaxInclusionTimeIntoSlot(seconds) {
80
+ /**
81
+ * Sets max inclusion time into slot. If more than this many seconds have passed
82
+ * since the last L1 block was mined, then any tx will not be mined in the current
83
+ * L1 slot but will be deferred for the next one.
84
+ */ setMaxInclusionTimeIntoSlot(seconds) {
77
85
  this.maxInclusionTimeIntoSlot = seconds;
78
86
  }
79
87
  }
80
88
  /**
81
- * Returns a new client (without modifying the one passed in) with an injected tx delayer.
82
- * The delayer can be used to hold off the next tx to be sent until a given block number.
83
- * TODO(#10824): This doesn't play along well with blob txs for some reason.
84
- */ export function withDelayer(client, dateProvider, opts) {
85
- if (!isExtendedClient(client)) {
86
- throw new Error('withDelayer has to be instantiated with a wallet viem client.');
89
+ * Creates a new Delayer instance. Exposed so callers can create a single shared delayer
90
+ * and pass it to multiple `wrapClientWithDelayer` calls.
91
+ */ export function createDelayer(dateProvider, opts, bindings) {
92
+ return new Delayer(dateProvider, opts, bindings);
93
+ }
94
+ /** Tries to recover the sender address from a serialized signed transaction. */ async function tryRecoverSender(serializedTransaction) {
95
+ try {
96
+ return await recoverTransactionAddress({
97
+ serializedTransaction: serializedTransaction
98
+ });
99
+ } catch {
100
+ return undefined;
87
101
  }
88
- const logger = createLogger('ethereum:tx_delayer');
89
- const delayer = new DelayerImpl(dateProvider, opts);
90
- const extended = client// Tweak sendRawTransaction so it uses the delay defined in the delayer.
102
+ }
103
+ /**
104
+ * Wraps a viem client with tx delaying logic. Returns the wrapped client.
105
+ * The delayer intercepts sendRawTransaction calls and delays them based on the delayer's state.
106
+ */ export function wrapClientWithDelayer(client, delayer) {
107
+ const logger = delayer.getLogger();
108
+ // Cast to ExtendedViemWalletClient for the extend chain since it has sendRawTransaction.
109
+ // The sendRawTransaction override is applied to all clients regardless of type.
110
+ const withRawTx = client// Tweak sendRawTransaction so it uses the delay defined in the delayer.
91
111
  // Note that this will only work with local accounts (ie accounts for which we have the private key).
92
112
  // Transactions signed by the node will not be delayed since they use sendTransaction directly,
93
113
  // but we do not use them in our codebase at all.
@@ -97,6 +117,7 @@ class DelayerImpl {
97
117
  let txHash;
98
118
  const { serializedTransaction } = args[0];
99
119
  const publicClient = client;
120
+ const sender = await tryRecoverSender(serializedTransaction);
100
121
  if (delayer.nextWait !== undefined) {
101
122
  // Check if we have been instructed to delay the next tx.
102
123
  const waitUntil = delayer.nextWait;
@@ -105,13 +126,16 @@ class DelayerImpl {
105
126
  txHash = computeTxHash(serializedTransaction);
106
127
  // Cancel tx outright if instructed
107
128
  if ('indefinitely' in waitUntil && waitUntil.indefinitely) {
108
- logger.info(`Cancelling tx ${txHash}`);
129
+ logger.info(`Cancelling tx ${txHash}`, {
130
+ sender
131
+ });
109
132
  delayer.cancelledTxs.push(serializedTransaction);
110
133
  return Promise.resolve(txHash);
111
134
  }
112
135
  // Or wait until the desired block number or timestamp
113
- wait = 'l1BlockNumber' in waitUntil ? waitUntilBlock(publicClient, waitUntil.l1BlockNumber - 1n, logger) : 'l1Timestamp' in waitUntil ? waitUntilL1Timestamp(publicClient, waitUntil.l1Timestamp - delayer.ethereumSlotDuration, logger) : undefined;
136
+ wait = 'l1BlockNumber' in waitUntil ? waitUntilBlock(publicClient, waitUntil.l1BlockNumber - 1n, logger, waitUntil.timeoutSeconds) : 'l1Timestamp' in waitUntil ? waitUntilL1Timestamp(publicClient, waitUntil.l1Timestamp - delayer.ethereumSlotDuration, logger, waitUntil.timeoutSeconds) : undefined;
114
137
  logger.info(`Delaying tx ${txHash} until ${inspect(waitUntil)}`, {
138
+ sender,
115
139
  argsLen: args.length,
116
140
  ...omit(parseTransaction(serializedTransaction), 'data', 'sidecars')
117
141
  });
@@ -124,6 +148,7 @@ class DelayerImpl {
124
148
  const now = delayer.dateProvider.now();
125
149
  txHash = computeTxHash(serializedTransaction);
126
150
  const logData = {
151
+ sender,
127
152
  ...omit(parseTransaction(serializedTransaction), 'data', 'sidecars'),
128
153
  lastBlockTimestamp,
129
154
  now,
@@ -150,29 +175,34 @@ class DelayerImpl {
150
175
  computedTxHash: txHash
151
176
  });
152
177
  }
153
- logger.info(`Sent previously delayed tx ${clientTxHash}`);
178
+ logger.info(`Sent previously delayed tx ${clientTxHash}`, {
179
+ sender
180
+ });
154
181
  delayer.sentTxHashes.push(clientTxHash);
155
182
  }).catch((err)=>logger.error(`Error sending tx after delay`, err));
156
183
  return Promise.resolve(txHash);
157
184
  } else {
158
185
  const txHash = await client.sendRawTransaction(...args);
159
- logger.debug(`Sent tx immediately ${txHash}`);
186
+ logger.debug(`Sent tx immediately ${txHash}`, {
187
+ sender
188
+ });
160
189
  delayer.sentTxHashes.push(txHash);
161
190
  return txHash;
162
191
  }
163
192
  }
164
- }))// Re-extend with sendTransaction so it uses the modified sendRawTransaction.
193
+ }));
194
+ // Only re-bind wallet actions (sendTransaction, writeContract, deployContract) for wallet clients.
195
+ // This is needed for tests that use wallet actions directly rather than sendRawTransaction.
196
+ const isWalletClient = 'account' in client && client.account !== undefined;
197
+ const extended = isWalletClient ? withRawTx// Re-extend with sendTransaction so it uses the modified sendRawTransaction.
165
198
  .extend((client)=>({
166
199
  sendTransaction: walletActions(client).sendTransaction
167
200
  }))// And with the actions that depend on the modified sendTransaction
168
201
  .extend((client)=>({
169
202
  writeContract: walletActions(client).writeContract,
170
203
  deployContract: walletActions(client).deployContract
171
- }));
172
- return {
173
- client: extended,
174
- delayer
175
- };
204
+ })) : withRawTx;
205
+ return extended;
176
206
  }
177
207
  /**
178
208
  * Compute the tx hash given the serialized tx. Note that if this is a blob tx, we need to
@@ -1,16 +1,42 @@
1
1
  import { type LoggerBindings } from '@aztec/foundation/log';
2
+ import { RunningPromise } from '@aztec/foundation/running-promise';
2
3
  import { L1TxUtils } from './l1_tx_utils/index.js';
3
4
  export type PublisherFilter<UtilsType extends L1TxUtils> = (utils: UtilsType) => boolean;
5
+ /** Config accepted by PublisherManager. */
6
+ type PublisherManagerConfig = {
7
+ publisherAllowInvalidStates?: boolean;
8
+ publisherFundingThreshold?: bigint;
9
+ publisherFundingAmount?: bigint;
10
+ };
4
11
  export declare class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
5
- private publishers;
12
+ protected publishers: UtilsType[];
6
13
  private log;
7
14
  private config;
8
- constructor(publishers: UtilsType[], config: {
9
- publisherAllowInvalidStates?: boolean;
10
- }, bindings?: LoggerBindings);
11
- /** Loads the state of all publishers and resumes monitoring any pending txs */
12
- loadState(): Promise<void>;
15
+ private static readonly FUNDING_CHECK_INTERVAL_MS;
16
+ protected funder?: UtilsType;
17
+ protected readonly fundingPromise?: RunningPromise;
18
+ private started;
19
+ constructor(publishers: UtilsType[], config: PublisherManagerConfig, opts?: {
20
+ bindings?: LoggerBindings;
21
+ funder?: UtilsType;
22
+ });
23
+ /**
24
+ * Clears any interrupted flag left by a previous {@link stop} so publishing works again after a restart,
25
+ * loads the state of all publishers and the funder, and starts periodic funding checks. Idempotent: a
26
+ * start while already started is a no-op, so it never re-runs `loadStateAndResumeMonitoring` (which
27
+ * would spawn a duplicate background monitor per pending nonce). Lifecycle calls are expected to be
28
+ * serialized by the caller.
29
+ */
30
+ start(): Promise<void>;
31
+ /**
32
+ * Stops the funding loop, interrupts all publishers so no further L1 txs are sent, and waits (bounded)
33
+ * for their in-flight tx monitor loops to wind down. Idempotent, and the manager may be restarted
34
+ * afterwards via {@link start}, which clears the interrupted flag.
35
+ */
36
+ stop(): Promise<void>;
13
37
  getAvailablePublisher(filter?: PublisherFilter<UtilsType>): Promise<UtilsType>;
14
- interrupt(): void;
38
+ private triggerFundingIfNeeded;
39
+ private fundPublishers;
15
40
  }
16
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGlzaGVyX21hbmFnZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9wdWJsaXNoZXJfbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQWUsS0FBSyxjQUFjLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFFdkYsT0FBTyxFQUFFLFNBQVMsRUFBZ0IsTUFBTSx3QkFBd0IsQ0FBQztBQXdCakUsTUFBTSxNQUFNLGVBQWUsQ0FBQyxTQUFTLFNBQVMsU0FBUyxJQUFJLENBQUMsS0FBSyxFQUFFLFNBQVMsS0FBSyxPQUFPLENBQUM7QUFFekYscUJBQWEsZ0JBQWdCLENBQUMsU0FBUyxTQUFTLFNBQVMsR0FBRyxTQUFTO0lBS2pFLE9BQU8sQ0FBQyxVQUFVO0lBSnBCLE9BQU8sQ0FBQyxHQUFHLENBQVM7SUFDcEIsT0FBTyxDQUFDLE1BQU0sQ0FBNEM7SUFFMUQsWUFDVSxVQUFVLEVBQUUsU0FBUyxFQUFFLEVBQy9CLE1BQU0sRUFBRTtRQUFFLDJCQUEyQixDQUFDLEVBQUUsT0FBTyxDQUFBO0tBQUUsRUFDakQsUUFBUSxDQUFDLEVBQUUsY0FBYyxFQU0xQjtJQUVELCtFQUErRTtJQUNsRSxTQUFTLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUV0QztJQVFZLHFCQUFxQixDQUFDLE1BQU0sR0FBRSxlQUFlLENBQUMsU0FBUyxDQUFjLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQStDdEc7SUFFTSxTQUFTLFNBRWY7Q0FDRiJ9
41
+ export {};
42
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGlzaGVyX21hbmFnZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9wdWJsaXNoZXJfbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQWUsS0FBSyxjQUFjLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFDdkYsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBR25FLE9BQU8sRUFBRSxTQUFTLEVBQWdCLE1BQU0sd0JBQXdCLENBQUM7QUF3QmpFLE1BQU0sTUFBTSxlQUFlLENBQUMsU0FBUyxTQUFTLFNBQVMsSUFBSSxDQUFDLEtBQUssRUFBRSxTQUFTLEtBQUssT0FBTyxDQUFDO0FBRXpGLDJDQUEyQztBQUMzQyxLQUFLLHNCQUFzQixHQUFHO0lBQzVCLDJCQUEyQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBQ3RDLHlCQUF5QixDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ25DLHNCQUFzQixDQUFDLEVBQUUsTUFBTSxDQUFDO0NBQ2pDLENBQUM7QUFFRixxQkFBYSxnQkFBZ0IsQ0FBQyxTQUFTLFNBQVMsU0FBUyxHQUFHLFNBQVM7SUFTakUsU0FBUyxDQUFDLFVBQVUsRUFBRSxTQUFTLEVBQUU7SUFSbkMsT0FBTyxDQUFDLEdBQUcsQ0FBUztJQUNwQixPQUFPLENBQUMsTUFBTSxDQUF5QjtJQUN2QyxPQUFPLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyx5QkFBeUIsQ0FBaUI7SUFDbEUsU0FBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLFNBQVMsQ0FBQztJQUM3QixTQUFTLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBQyxFQUFFLGNBQWMsQ0FBQztJQUNuRCxPQUFPLENBQUMsT0FBTyxDQUFTO0lBRXhCLFlBQ1ksVUFBVSxFQUFFLFNBQVMsRUFBRSxFQUNqQyxNQUFNLEVBQUUsc0JBQXNCLEVBQzlCLElBQUksQ0FBQyxFQUFFO1FBQUUsUUFBUSxDQUFDLEVBQUUsY0FBYyxDQUFDO1FBQUMsTUFBTSxDQUFDLEVBQUUsU0FBUyxDQUFBO0tBQUUsRUE2QnpEO0lBRUQ7Ozs7OztPQU1HO0lBQ1UsS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FtQmxDO0lBRUQ7Ozs7T0FJRztJQUNVLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBV2pDO0lBUVkscUJBQXFCLENBQUMsTUFBTSxHQUFFLGVBQWUsQ0FBQyxTQUFTLENBQWMsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBd0R0RztZQUdhLHNCQUFzQjtZQXFDdEIsY0FBYztDQVU3QiJ9
@@ -1 +1 @@
1
- {"version":3,"file":"publisher_manager.d.ts","sourceRoot":"","sources":["../src/publisher_manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;AAEvF,OAAO,EAAE,SAAS,EAAgB,MAAM,wBAAwB,CAAC;AAwBjE,MAAM,MAAM,eAAe,CAAC,SAAS,SAAS,SAAS,IAAI,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;AAEzF,qBAAa,gBAAgB,CAAC,SAAS,SAAS,SAAS,GAAG,SAAS;IAKjE,OAAO,CAAC,UAAU;IAJpB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,MAAM,CAA4C;IAE1D,YACU,UAAU,EAAE,SAAS,EAAE,EAC/B,MAAM,EAAE;QAAE,2BAA2B,CAAC,EAAE,OAAO,CAAA;KAAE,EACjD,QAAQ,CAAC,EAAE,cAAc,EAM1B;IAED,+EAA+E;IAClE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAEtC;IAQY,qBAAqB,CAAC,MAAM,GAAE,eAAe,CAAC,SAAS,CAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CA+CtG;IAEM,SAAS,SAEf;CACF"}
1
+ {"version":3,"file":"publisher_manager.d.ts","sourceRoot":"","sources":["../src/publisher_manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAE,SAAS,EAAgB,MAAM,wBAAwB,CAAC;AAwBjE,MAAM,MAAM,eAAe,CAAC,SAAS,SAAS,SAAS,IAAI,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;AAEzF,2CAA2C;AAC3C,KAAK,sBAAsB,GAAG;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,qBAAa,gBAAgB,CAAC,SAAS,SAAS,SAAS,GAAG,SAAS;IASjE,SAAS,CAAC,UAAU,EAAE,SAAS,EAAE;IARnC,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAiB;IAClE,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC7B,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACnD,OAAO,CAAC,OAAO,CAAS;IAExB,YACY,UAAU,EAAE,SAAS,EAAE,EACjC,MAAM,EAAE,sBAAsB,EAC9B,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE,EA6BzD;IAED;;;;;;OAMG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAmBlC;IAED;;;;OAIG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAWjC;IAQY,qBAAqB,CAAC,MAAM,GAAE,eAAe,CAAC,SAAS,CAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CAwDtG;YAGa,sBAAsB;YAqCtB,cAAc;CAU7B"}
@@ -1,5 +1,7 @@
1
1
  import { pick } from '@aztec/foundation/collection';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
+ import { RunningPromise } from '@aztec/foundation/running-promise';
4
+ import { Multicall3 } from './contracts/multicall.js';
3
5
  import { TxUtilsState } from './l1_tx_utils/index.js';
4
6
  // Defines the order in which we prioritise publishers based on their state (first is better)
5
7
  const sortOrder = [
@@ -25,15 +27,72 @@ export class PublisherManager {
25
27
  publishers;
26
28
  log;
27
29
  config;
28
- constructor(publishers, config, bindings){
30
+ static FUNDING_CHECK_INTERVAL_MS = 2 * 60 * 1000;
31
+ funder;
32
+ fundingPromise;
33
+ started;
34
+ constructor(publishers, config, opts){
29
35
  this.publishers = publishers;
30
- this.log = createLogger('publisher:manager', bindings);
36
+ this.started = false;
37
+ this.funder = opts?.funder;
38
+ this.log = createLogger('publisher:manager', opts?.bindings);
31
39
  this.log.info(`PublisherManager initialized with ${publishers.length} publishers.`);
32
40
  this.publishers = publishers;
33
- this.config = pick(config, 'publisherAllowInvalidStates');
41
+ this.config = pick(config, 'publisherAllowInvalidStates', 'publisherFundingThreshold', 'publisherFundingAmount');
42
+ const hasThreshold = this.config.publisherFundingThreshold !== undefined;
43
+ const hasAmount = this.config.publisherFundingAmount !== undefined;
44
+ if (hasThreshold !== hasAmount) {
45
+ this.log.warn(`Incomplete funding config: both publisherFundingThreshold and publisherFundingAmount must be set`);
46
+ }
47
+ if (this.funder) {
48
+ const funderAddress = this.funder.getSenderAddress();
49
+ if (publishers.some((p)=>p.getSenderAddress().equals(funderAddress))) {
50
+ this.log.error(`Funding account ${funderAddress} is also a publisher, disabling funding to avoid self-funding`);
51
+ this.funder = undefined;
52
+ }
53
+ }
54
+ if (this.funder && hasThreshold && hasAmount) {
55
+ this.fundingPromise = new RunningPromise(()=>this.triggerFundingIfNeeded(), this.log, PublisherManager.FUNDING_CHECK_INTERVAL_MS);
56
+ }
34
57
  }
35
- /** Loads the state of all publishers and resumes monitoring any pending txs */ async loadState() {
36
- await Promise.all(this.publishers.map((pub)=>pub.loadStateAndResumeMonitoring()));
58
+ /**
59
+ * Clears any interrupted flag left by a previous {@link stop} so publishing works again after a restart,
60
+ * loads the state of all publishers and the funder, and starts periodic funding checks. Idempotent: a
61
+ * start while already started is a no-op, so it never re-runs `loadStateAndResumeMonitoring` (which
62
+ * would spawn a duplicate background monitor per pending nonce). Lifecycle calls are expected to be
63
+ * serialized by the caller.
64
+ */ async start() {
65
+ if (this.started) {
66
+ this.log.debug('PublisherManager already started, ignoring start');
67
+ return;
68
+ }
69
+ // Clear the interrupted flag set by a previous stop() so a restarted manager can publish again.
70
+ // On a first start this is a no-op (the flag is already clear).
71
+ this.publishers.forEach((pub)=>pub.restart());
72
+ this.funder?.restart();
73
+ await Promise.all([
74
+ ...this.publishers.map((pub)=>pub.loadStateAndResumeMonitoring()),
75
+ this.funder?.loadStateAndResumeMonitoring()
76
+ ]);
77
+ this.fundingPromise?.start();
78
+ // Marked started only once fully up, so a start that failed to load state can be retried.
79
+ this.started = true;
80
+ }
81
+ /**
82
+ * Stops the funding loop, interrupts all publishers so no further L1 txs are sent, and waits (bounded)
83
+ * for their in-flight tx monitor loops to wind down. Idempotent, and the manager may be restarted
84
+ * afterwards via {@link start}, which clears the interrupted flag.
85
+ */ async stop() {
86
+ this.started = false;
87
+ await this.fundingPromise?.stop();
88
+ this.publishers.forEach((pub)=>pub.interrupt());
89
+ this.funder?.interrupt();
90
+ // Wait for in-flight tx monitor loops to observe the interrupt, so no L1 requests are still
91
+ // being issued after shutdown (e.g. against an anvil instance a test is about to tear down).
92
+ await Promise.all([
93
+ ...this.publishers.map((pub)=>pub.waitMonitoringStopped()),
94
+ this.funder?.waitMonitoringStopped()
95
+ ]);
37
96
  }
38
97
  // Finds and prioritises available publishers based on
39
98
  // 1. Validity as per the provided filter function
@@ -67,8 +126,16 @@ export class PublisherManager {
67
126
  publisher: pub
68
127
  };
69
128
  }));
129
+ // Discount unfunded publishers: a publisher with no ETH cannot send a tx, so it must never win
130
+ // selection regardless of how favourable its state or last-used time is. Fall back to the full
131
+ // set only if every candidate is unfunded, so behaviour is no worse than before.
132
+ let fundedPublishers = publishersWithBalance.filter((p)=>p.balance > 0n);
133
+ if (fundedPublishers.length === 0) {
134
+ this.log.warn(`All candidate publishers have zero balance; selecting from unfunded publishers.`);
135
+ fundedPublishers = publishersWithBalance;
136
+ }
70
137
  // Sort based on state, then balance, then time since last use
71
- const sortedPublishers = publishersWithBalance.sort((a, b)=>{
138
+ const sortedPublishers = fundedPublishers.sort((a, b)=>{
72
139
  const stateComparison = sortOrder.indexOf(a.publisher.state) - sortOrder.indexOf(b.publisher.state);
73
140
  if (stateComparison !== 0) {
74
141
  return stateComparison;
@@ -82,7 +149,51 @@ export class PublisherManager {
82
149
  });
83
150
  return sortedPublishers[0].publisher;
84
151
  }
85
- interrupt() {
86
- this.publishers.forEach((pub)=>pub.interrupt());
152
+ /** Check all publisher balances and fund those below threshold. */ async triggerFundingIfNeeded() {
153
+ const { funder, config } = this;
154
+ if (!funder || config.publisherFundingThreshold === undefined || config.publisherFundingAmount === undefined) {
155
+ return;
156
+ }
157
+ const allBalances = await Promise.all(this.publishers.map(async (pub)=>({
158
+ balance: await pub.getSenderBalance(),
159
+ publisher: pub
160
+ })));
161
+ const lowBalance = allBalances.filter((p)=>p.balance < config.publisherFundingThreshold);
162
+ if (lowBalance.length === 0) {
163
+ return;
164
+ }
165
+ const fundingAmount = config.publisherFundingAmount;
166
+ const funderBalance = await funder.getSenderBalance();
167
+ if (funderBalance < 10n * fundingAmount) {
168
+ this.log.warn(`Funding account balance is low`, {
169
+ funderBalance,
170
+ threshold: 10n * fundingAmount
171
+ });
172
+ }
173
+ const affordableCount = Number(funderBalance / fundingAmount);
174
+ if (affordableCount === 0) {
175
+ this.log.error(`Funding account balance too low to fund any publisher`, {
176
+ funderBalance,
177
+ fundingAmount
178
+ });
179
+ return;
180
+ }
181
+ if (affordableCount < lowBalance.length) {
182
+ this.log.warn(`Funder can only afford ${affordableCount}/${lowBalance.length} publishers`, {
183
+ funderBalance,
184
+ fundingAmount
185
+ });
186
+ }
187
+ const toFund = lowBalance.slice(0, affordableCount).map((p)=>p.publisher);
188
+ await this.fundPublishers(toFund);
189
+ }
190
+ /** Fund publishers via a single Multicall3 aggregate3Value transaction. */ async fundPublishers(publishers) {
191
+ const fundingAmount = this.config.publisherFundingAmount;
192
+ const calls = publishers.map((pub)=>({
193
+ to: pub.getSenderAddress().toString(),
194
+ value: fundingAmount
195
+ }));
196
+ await Multicall3.forwardValue(calls, this.funder, this.log);
197
+ this.log.info(`Funded ${publishers.length} publishers`);
87
198
  }
88
199
  }
package/dest/queries.d.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import { type Block } from 'viem';
2
3
  import { type L1ContractsConfig } from './config.js';
3
- import type { ViemPublicClient } from './types.js';
4
+ import type { ViemClient, ViemPublicClient } from './types.js';
5
+ /**
6
+ * Returns the L1 finalized block, or `undefined` if the chain does not yet have one
7
+ * (common on freshly started devnets). Rethrows any other RPC error.
8
+ */
9
+ export declare function getFinalizedL1Block(client: ViemClient): Promise<Block<bigint, false, 'finalized'> | undefined>;
10
+ /**
11
+ * Returns true if the error originates from an RPC call that failed because
12
+ * the "finalized" (or "safe") block tag is not yet available on the chain.
13
+ */
14
+ export declare function isFinalizedBlockTagNotFoundError(err: unknown): boolean;
4
15
  /** Reads the L1ContractsConfig from L1 contracts. */
5
16
  export declare function getL1ContractsConfig(publicClient: ViemPublicClient, addresses: {
6
17
  governanceAddress: EthAddress;
@@ -11,4 +22,4 @@ export declare function getL1ContractsConfig(publicClient: ViemPublicClient, add
11
22
  rollupVersion: number;
12
23
  genesisArchiveTreeRoot: `0x${string}`;
13
24
  }>;
14
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcmllcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3F1ZXJpZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRTNELE9BQU8sRUFBNEIsS0FBSyxpQkFBaUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUsvRSxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUVuRCxxREFBcUQ7QUFDckQsd0JBQXNCLG9CQUFvQixDQUN4QyxZQUFZLEVBQUUsZ0JBQWdCLEVBQzlCLFNBQVMsRUFBRTtJQUFFLGlCQUFpQixFQUFFLFVBQVUsQ0FBQztJQUFDLGFBQWEsQ0FBQyxFQUFFLFVBQVUsQ0FBQTtDQUFFLEdBQ3ZFLE9BQU8sQ0FDUixJQUFJLENBQUMsaUJBQWlCLEVBQUUsc0JBQXNCLENBQUMsR0FBRztJQUNoRCxZQUFZLEVBQUUsTUFBTSxDQUFDO0lBQ3JCLGFBQWEsRUFBRSxNQUFNLENBQUM7SUFDdEIsYUFBYSxFQUFFLE1BQU0sQ0FBQztJQUN0QixzQkFBc0IsRUFBRSxLQUFLLE1BQU0sRUFBRSxDQUFDO0NBQ3ZDLENBQ0YsQ0F1R0EifQ==
25
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcmllcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3F1ZXJpZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRTNELE9BQU8sRUFBYSxLQUFLLEtBQUssRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUU3QyxPQUFPLEVBQTRCLEtBQUssaUJBQWlCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFLL0UsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLGdCQUFnQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRS9EOzs7R0FHRztBQUNILHdCQUFzQixtQkFBbUIsQ0FBQyxNQUFNLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxXQUFXLENBQUMsR0FBRyxTQUFTLENBQUMsQ0FTcEg7QUFtQkQ7OztHQUdHO0FBQ0gsd0JBQWdCLGdDQUFnQyxDQUFDLEdBQUcsRUFBRSxPQUFPLEdBQUcsT0FBTyxDQWtCdEU7QUFVRCxxREFBcUQ7QUFDckQsd0JBQXNCLG9CQUFvQixDQUN4QyxZQUFZLEVBQUUsZ0JBQWdCLEVBQzlCLFNBQVMsRUFBRTtJQUFFLGlCQUFpQixFQUFFLFVBQVUsQ0FBQztJQUFDLGFBQWEsQ0FBQyxFQUFFLFVBQVUsQ0FBQTtDQUFFLEdBQ3ZFLE9BQU8sQ0FDUixJQUFJLENBQUMsaUJBQWlCLEVBQUUsc0JBQXNCLENBQUMsR0FBRztJQUNoRCxZQUFZLEVBQUUsTUFBTSxDQUFDO0lBQ3JCLGFBQWEsRUFBRSxNQUFNLENBQUM7SUFDdEIsYUFBYSxFQUFFLE1BQU0sQ0FBQztJQUN0QixzQkFBc0IsRUFBRSxLQUFLLE1BQU0sRUFBRSxDQUFDO0NBQ3ZDLENBQ0YsQ0F1R0EifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAA4B,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAK/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,qDAAqD;AACrD,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE;IAAE,iBAAiB,EAAE,UAAU,CAAC;IAAC,aAAa,CAAC,EAAE,UAAU,CAAA;CAAE,GACvE,OAAO,CACR,IAAI,CAAC,iBAAiB,EAAE,sBAAsB,CAAC,GAAG;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,KAAK,MAAM,EAAE,CAAC;CACvC,CACF,CAuGA"}
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,MAAM,CAAC;AAE7C,OAAO,EAA4B,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAK/E,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/D;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,GAAG,SAAS,CAAC,CASpH;AAmBD;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAkBtE;AAUD,qDAAqD;AACrD,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE;IAAE,iBAAiB,EAAE,UAAU,CAAC;IAAC,aAAa,CAAC,EAAE,UAAU,CAAA;CAAE,GACvE,OAAO,CACR,IAAI,CAAC,iBAAiB,EAAE,sBAAsB,CAAC,GAAG;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,KAAK,MAAM,EAAE,CAAC;CACvC,CACF,CAuGA"}
package/dest/queries.js CHANGED
@@ -1,9 +1,68 @@
1
1
  import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import { BaseError } from 'viem';
2
3
  import { DefaultL1ContractsConfig } from './config.js';
3
4
  import { ReadOnlyGovernanceContract } from './contracts/governance.js';
4
5
  import { GovernanceProposerContract } from './contracts/governance_proposer.js';
5
6
  import { InboxContract } from './contracts/inbox.js';
6
7
  import { RollupContract } from './contracts/rollup.js';
8
+ /**
9
+ * Returns the L1 finalized block, or `undefined` if the chain does not yet have one
10
+ * (common on freshly started devnets). Rethrows any other RPC error.
11
+ */ export async function getFinalizedL1Block(client) {
12
+ try {
13
+ return await client.getBlock({
14
+ blockTag: 'finalized',
15
+ includeTransactions: false
16
+ });
17
+ } catch (err) {
18
+ if (isFinalizedBlockTagNotFoundError(err)) {
19
+ return undefined;
20
+ }
21
+ throw err;
22
+ }
23
+ }
24
+ // Error messages returned by popular Ethereum execution clients when
25
+ // eth_getBlockByNumber / eth_call is called with blockTag "finalized" or
26
+ // "safe" and no such block has been produced yet:
27
+ //
28
+ // geth "finalized block not found"
29
+ // "safe block not found"
30
+ // reth "block not found: finalized"
31
+ // "block not found: safe"
32
+ // nethermind "Unknown block error" (same for both tags)
33
+ // besu "Unknown block"
34
+ // erigon 'block "finalized" not available (head block: N)'
35
+ // 'block "safe" not available (head block: N)'
36
+ //
37
+ // A combined regex covers all five:
38
+ const FINALIZED_BLOCK_TAG_NOT_FOUND_MESSAGE_RE = /(finalized|safe) block not found|block not found: (finalized|safe)|unknown block|block "(finalized|safe)" not available/i;
39
+ /**
40
+ * Returns true if the error originates from an RPC call that failed because
41
+ * the "finalized" (or "safe") block tag is not yet available on the chain.
42
+ */ export function isFinalizedBlockTagNotFoundError(err) {
43
+ if (!err) {
44
+ return false;
45
+ }
46
+ if (err instanceof BaseError) {
47
+ const hit = err.walk((e)=>matchesFinalizedBlockTagNotFound(e));
48
+ if (hit) {
49
+ return true;
50
+ }
51
+ }
52
+ for(let cur = err, i = 0; cur && i < 10; cur = cur.cause, i++){
53
+ if (matchesFinalizedBlockTagNotFound(cur)) {
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ function matchesFinalizedBlockTagNotFound(e) {
60
+ if (!e) {
61
+ return false;
62
+ }
63
+ const text = `${e.details ?? ''} ${e.message ?? ''} ${e.shortMessage ?? ''}`;
64
+ return FINALIZED_BLOCK_TAG_NOT_FOUND_MESSAGE_RE.test(text);
65
+ }
7
66
  /** Reads the L1ContractsConfig from L1 contracts. */ export async function getL1ContractsConfig(publicClient, addresses) {
8
67
  const governance = new ReadOnlyGovernanceContract(addresses.governanceAddress.toString(), publicClient);
9
68
  const governanceProposerAddress = await governance.getGovernanceProposerAddress();
@@ -33,8 +92,8 @@ import { RollupContract } from './contracts/rollup.js';
33
92
  slasherProposer?.getRoundSize() ?? 0n,
34
93
  slasherProposer?.getLifetimeInRounds() ?? 0n,
35
94
  slasherProposer?.getExecutionDelayInRounds() ?? 0n,
36
- slasherProposer?.type === 'tally' ? slasherProposer.getSlashOffsetInRounds() : 0n,
37
- slasherProposer?.type === 'tally' ? slasherProposer.getSlashingAmounts() : [
95
+ slasherProposer?.getSlashOffsetInRounds() ?? 0n,
96
+ slasherProposer?.getSlashingAmounts() ?? [
38
97
  0n,
39
98
  0n,
40
99
  0n
@@ -74,7 +133,7 @@ import { RollupContract } from './contracts/rollup.js';
74
133
  rollupVersion: Number(rollupVersion),
75
134
  genesisArchiveTreeRoot: genesisArchiveTreeRoot.toString(),
76
135
  exitDelaySeconds: Number(exitDelay),
77
- slasherFlavor: slasherProposer?.type ?? 'tally',
136
+ slasherEnabled: !!slasherProposer,
78
137
  slashingOffsetInRounds: Number(slashingOffsetInRounds),
79
138
  slashAmountSmall: slashingAmounts[0],
80
139
  slashAmountMedium: slashingAmounts[1],
@@ -0,0 +1,19 @@
1
+ import type { Logger } from '@aztec/foundation/log';
2
+ import type { ExtendedViemWalletClient } from '../types.js';
3
+ /**
4
+ * Warms both KZG trusted setups in parallel for tests against a local anvil:
5
+ * - anvil's own setup, loaded lazily when it first validates a blob sidecar in `eth_sendRawTransaction`
6
+ * - our `@crate-crypto/node-eth-kzg` singleton (`getKzg()`, a ~2.2s synchronous precomp build)
7
+ *
8
+ * Without this, the first checkpoint publish pays both serially (~4.4s). The recipe does all RPC round
9
+ * trips up front, fires a single bare raw send carrying a fake (garbage) blob sidecar that anvil rejects
10
+ * after loading its trusted setup, yields once to flush the socket write, then synchronously builds our
11
+ * own precomp tables while anvil warms in parallel. The fake tx is rejected at admission, so it never
12
+ * enters the pool, mines no block, and burns no nonce — sending it from the passed client is harmless.
13
+ *
14
+ * Best-effort: any failure is debug-logged and swallowed, leaving lazy init as the fallback. Never throws.
15
+ *
16
+ * @param l1Client - The L1 client whose account sends the (rejected) warm-up blob tx.
17
+ */
18
+ export declare function warmBlobKzg(l1Client: ExtendedViemWalletClient, logger?: Logger): Promise<void>;
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvYl9remdfd2FybXVwLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVzdC9ibG9iX2t6Z193YXJtdXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFcEQsT0FBTyxLQUFLLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFNUQ7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCx3QkFBc0IsV0FBVyxDQUFDLFFBQVEsRUFBRSx3QkFBd0IsRUFBRSxNQUFNLENBQUMsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQTRDcEcifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blob_kzg_warmup.d.ts","sourceRoot":"","sources":["../../src/test/blob_kzg_warmup.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,wBAAwB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CpG"}