@aztec/ethereum 0.0.1-commit.d3ec352c → 0.0.1-commit.d58ff9d0

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 (224) hide show
  1. package/dest/client.d.ts +25 -3
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +60 -4
  4. package/dest/config.d.ts +45 -73
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +114 -386
  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/empire_base.d.ts +2 -1
  11. package/dest/contracts/empire_base.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_handler.d.ts +6 -5
  13. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  14. package/dest/contracts/fee_asset_handler.js +11 -9
  15. package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
  16. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  17. package/dest/contracts/fee_asset_price_oracle.js +656 -0
  18. package/dest/contracts/governance.d.ts +110 -33
  19. package/dest/contracts/governance.d.ts.map +1 -1
  20. package/dest/contracts/governance.js +207 -16
  21. package/dest/contracts/governance_proposer.d.ts +34 -1
  22. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  23. package/dest/contracts/governance_proposer.js +447 -10
  24. package/dest/contracts/gse.d.ts +4 -2
  25. package/dest/contracts/gse.d.ts.map +1 -1
  26. package/dest/contracts/gse.js +2 -2
  27. package/dest/contracts/inbox.d.ts +24 -3
  28. package/dest/contracts/inbox.d.ts.map +1 -1
  29. package/dest/contracts/inbox.js +42 -2
  30. package/dest/contracts/index.d.ts +6 -3
  31. package/dest/contracts/index.d.ts.map +1 -1
  32. package/dest/contracts/index.js +5 -2
  33. package/dest/contracts/log.d.ts +13 -0
  34. package/dest/contracts/log.d.ts.map +1 -0
  35. package/dest/contracts/log.js +1 -0
  36. package/dest/contracts/multicall.d.ts +130 -13
  37. package/dest/contracts/multicall.d.ts.map +1 -1
  38. package/dest/contracts/multicall.js +203 -93
  39. package/dest/contracts/outbox.d.ts +64 -0
  40. package/dest/contracts/outbox.d.ts.map +1 -0
  41. package/dest/contracts/outbox.js +113 -0
  42. package/dest/contracts/registry.d.ts +3 -1
  43. package/dest/contracts/registry.d.ts.map +1 -1
  44. package/dest/contracts/registry.js +30 -1
  45. package/dest/contracts/rollup.d.ts +5571 -102
  46. package/dest/contracts/rollup.d.ts.map +1 -1
  47. package/dest/contracts/rollup.js +1076 -192
  48. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +5 -5
  49. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  50. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +18 -13
  51. package/dest/deploy_aztec_l1_contracts.d.ts +267 -0
  52. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -0
  53. package/dest/deploy_aztec_l1_contracts.js +424 -0
  54. package/dest/deploy_l1_contract.d.ts +68 -0
  55. package/dest/deploy_l1_contract.d.ts.map +1 -0
  56. package/dest/deploy_l1_contract.js +312 -0
  57. package/dest/forwarder_proxy.d.ts +32 -0
  58. package/dest/forwarder_proxy.d.ts.map +1 -0
  59. package/dest/forwarder_proxy.js +93 -0
  60. package/dest/foundry_binary.d.ts +13 -0
  61. package/dest/foundry_binary.d.ts.map +1 -0
  62. package/dest/foundry_binary.js +52 -0
  63. package/dest/l1_artifacts.d.ts +19674 -16606
  64. package/dest/l1_artifacts.d.ts.map +1 -1
  65. package/dest/l1_artifacts.js +9 -24
  66. package/dest/l1_contract_addresses.d.ts +61 -65
  67. package/dest/l1_contract_addresses.d.ts.map +1 -1
  68. package/dest/l1_contract_addresses.js +99 -80
  69. package/dest/l1_reader.d.ts +7 -5
  70. package/dest/l1_reader.d.ts.map +1 -1
  71. package/dest/l1_reader.js +15 -7
  72. package/dest/l1_tx_utils/config.d.ts +9 -3
  73. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/config.js +34 -7
  75. package/dest/l1_tx_utils/constants.d.ts +8 -2
  76. package/dest/l1_tx_utils/constants.d.ts.map +1 -1
  77. package/dest/l1_tx_utils/constants.js +27 -2
  78. package/dest/l1_tx_utils/factory.d.ts +18 -10
  79. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  80. package/dest/l1_tx_utils/factory.js +17 -7
  81. package/dest/l1_tx_utils/fee-strategies/index.d.ts +10 -0
  82. package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
  83. package/dest/l1_tx_utils/fee-strategies/index.js +12 -0
  84. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +8 -0
  85. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
  86. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +129 -0
  87. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +23 -0
  88. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
  89. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +191 -0
  90. package/dest/l1_tx_utils/fee-strategies/types.d.ts +51 -0
  91. package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
  92. package/dest/l1_tx_utils/fee-strategies/types.js +3 -0
  93. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +41 -0
  94. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -0
  95. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +42 -0
  96. package/dest/l1_tx_utils/index-blobs.d.ts +3 -0
  97. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -0
  98. package/dest/l1_tx_utils/index-blobs.js +2 -0
  99. package/dest/l1_tx_utils/index.d.ts +4 -1
  100. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  101. package/dest/l1_tx_utils/index.js +3 -0
  102. package/dest/l1_tx_utils/interfaces.d.ts +2 -2
  103. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -1
  104. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +252 -0
  105. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
  106. package/dest/l1_tx_utils/l1_fee_analyzer.js +568 -0
  107. package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -7
  108. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  109. package/dest/l1_tx_utils/l1_tx_utils.js +202 -92
  110. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +4 -15
  111. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  112. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +63 -165
  113. package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
  114. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  115. package/dest/{test → l1_tx_utils}/tx_delayer.js +70 -39
  116. package/dest/l1_tx_utils/types.d.ts +27 -1
  117. package/dest/l1_tx_utils/types.d.ts.map +1 -1
  118. package/dest/l1_tx_utils/types.js +10 -0
  119. package/dest/publisher_manager.d.ts +34 -7
  120. package/dest/publisher_manager.d.ts.map +1 -1
  121. package/dest/publisher_manager.js +119 -8
  122. package/dest/queries.d.ts +14 -3
  123. package/dest/queries.d.ts.map +1 -1
  124. package/dest/queries.js +80 -7
  125. package/dest/test/blob_kzg_warmup.d.ts +19 -0
  126. package/dest/test/blob_kzg_warmup.d.ts.map +1 -0
  127. package/dest/test/blob_kzg_warmup.js +64 -0
  128. package/dest/test/chain_monitor.d.ts +55 -4
  129. package/dest/test/chain_monitor.d.ts.map +1 -1
  130. package/dest/test/chain_monitor.js +105 -5
  131. package/dest/test/eth_cheat_codes.d.ts +49 -6
  132. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  133. package/dest/test/eth_cheat_codes.js +93 -43
  134. package/dest/test/index.d.ts +2 -3
  135. package/dest/test/index.d.ts.map +1 -1
  136. package/dest/test/index.js +1 -2
  137. package/dest/test/rollup_cheat_codes.d.ts +64 -4
  138. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  139. package/dest/test/rollup_cheat_codes.js +109 -8
  140. package/dest/test/start_anvil.d.ts +24 -2
  141. package/dest/test/start_anvil.d.ts.map +1 -1
  142. package/dest/test/start_anvil.js +182 -32
  143. package/dest/test/upgrade_utils.js +2 -2
  144. package/dest/types.d.ts +57 -2
  145. package/dest/types.d.ts.map +1 -1
  146. package/dest/utils.d.ts +24 -4
  147. package/dest/utils.d.ts.map +1 -1
  148. package/dest/utils.js +112 -16
  149. package/package.json +31 -15
  150. package/src/client.ts +66 -2
  151. package/src/config.ts +153 -469
  152. package/src/contracts/README.md +157 -0
  153. package/src/contracts/chain_state_override.ts +200 -0
  154. package/src/contracts/empire_base.ts +1 -1
  155. package/src/contracts/fee_asset_handler.ts +10 -7
  156. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  157. package/src/contracts/governance.ts +279 -13
  158. package/src/contracts/governance_proposer.ts +64 -3
  159. package/src/contracts/gse.ts +7 -2
  160. package/src/contracts/inbox.ts +64 -4
  161. package/src/contracts/index.ts +5 -2
  162. package/src/contracts/log.ts +13 -0
  163. package/src/contracts/multicall.ts +247 -103
  164. package/src/contracts/outbox.ts +132 -0
  165. package/src/contracts/registry.ts +31 -1
  166. package/src/contracts/rollup.ts +789 -154
  167. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +18 -14
  168. package/src/deploy_aztec_l1_contracts.ts +664 -0
  169. package/src/deploy_l1_contract.ts +362 -0
  170. package/src/forwarder_proxy.ts +108 -0
  171. package/src/foundry_binary.ts +57 -0
  172. package/src/l1_artifacts.ts +12 -35
  173. package/src/l1_contract_addresses.ts +120 -92
  174. package/src/l1_reader.ts +25 -10
  175. package/src/l1_tx_utils/config.ts +47 -9
  176. package/src/l1_tx_utils/constants.ts +13 -2
  177. package/src/l1_tx_utils/factory.ts +31 -31
  178. package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
  179. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +163 -0
  180. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +245 -0
  181. package/src/l1_tx_utils/fee-strategies/types.ts +56 -0
  182. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +108 -0
  183. package/src/l1_tx_utils/index-blobs.ts +2 -0
  184. package/src/l1_tx_utils/index.ts +3 -0
  185. package/src/l1_tx_utils/interfaces.ts +1 -1
  186. package/src/l1_tx_utils/l1_fee_analyzer.ts +875 -0
  187. package/src/l1_tx_utils/l1_tx_utils.ts +194 -72
  188. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +77 -211
  189. package/src/{test → l1_tx_utils}/tx_delayer.ts +97 -58
  190. package/src/l1_tx_utils/types.ts +32 -0
  191. package/src/publisher_manager.ts +145 -12
  192. package/src/queries.ts +87 -7
  193. package/src/test/blob_kzg_warmup.ts +65 -0
  194. package/src/test/chain_monitor.ts +150 -4
  195. package/src/test/eth_cheat_codes.ts +84 -47
  196. package/src/test/index.ts +1 -2
  197. package/src/test/rollup_cheat_codes.ts +136 -8
  198. package/src/test/start_anvil.ts +213 -31
  199. package/src/test/upgrade_utils.ts +2 -2
  200. package/src/types.ts +62 -0
  201. package/src/utils.ts +128 -19
  202. package/dest/contracts/empire_slashing_proposer.d.ts +0 -66
  203. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  204. package/dest/contracts/empire_slashing_proposer.js +0 -200
  205. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  206. package/dest/deploy_l1_contracts.d.ts +0 -673
  207. package/dest/deploy_l1_contracts.d.ts.map +0 -1
  208. package/dest/deploy_l1_contracts.js +0 -1491
  209. package/dest/index.d.ts +0 -18
  210. package/dest/index.d.ts.map +0 -1
  211. package/dest/index.js +0 -17
  212. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  213. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  214. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  215. package/dest/test/delayed_tx_utils.d.ts +0 -13
  216. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  217. package/dest/test/delayed_tx_utils.js +0 -28
  218. package/dest/test/tx_delayer.d.ts +0 -36
  219. package/dest/test/tx_delayer.d.ts.map +0 -1
  220. package/src/contracts/empire_slashing_proposer.ts +0 -265
  221. package/src/deploy_l1_contracts.ts +0 -1869
  222. package/src/index.ts +0 -17
  223. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  224. package/src/test/delayed_tx_utils.ts +0 -52
@@ -1,4 +1,5 @@
1
1
  import type { BlobKzgInstance } from '@aztec/blob-lib/types';
2
+ import { TimeoutError } from '@aztec/foundation/error';
2
3
  import { EthAddress } from '@aztec/foundation/eth-address';
3
4
  import type { ViemTransactionSignature } from '@aztec/foundation/eth-signature';
4
5
 
@@ -55,6 +56,12 @@ export type L1TxState = {
55
56
  cancelTxHashes: Hex[];
56
57
  gasLimit: bigint;
57
58
  gasPrice: GasPrice;
59
+ /**
60
+ * Prices used for each attempt (initial send followed by each speed-up), in order. Always set on
61
+ * newly sent txs; optional because states restored from the state store predate the field.
62
+ * In-memory only — not persisted by the state store.
63
+ */
64
+ gasPriceHistory?: GasPrice[];
58
65
  txConfigOverrides: L1TxConfig;
59
66
  request: L1TxRequest;
60
67
  status: TxUtilsState;
@@ -83,3 +90,28 @@ export class DroppedTransactionError extends Error {
83
90
  this.name = 'DroppedTransactionError';
84
91
  }
85
92
  }
93
+
94
+ /** Snapshot of what a timed-out L1 tx tried to pay, taken when the timeout is raised. */
95
+ export type TimedOutTxState = {
96
+ /** Prices used across the initial send and each speed-up, in order (undefined only for restored states). */
97
+ gasPriceHistory?: GasPrice[];
98
+ /** The last price the tx was sent at before timing out. */
99
+ finalGasPrice: GasPrice;
100
+ /** Number of send attempts (initial + speed-ups). */
101
+ attempts: number;
102
+ nonce: number;
103
+ gasLimit: bigint;
104
+ };
105
+
106
+ /**
107
+ * Thrown by sendAndMonitorTransaction when a tx times out. Subclasses TimeoutError so existing
108
+ * `instanceof TimeoutError` checks keep working, while carrying the gas-price ladder for diagnostics.
109
+ */
110
+ export class L1TxTimeoutError extends TimeoutError {
111
+ constructor(
112
+ message: string,
113
+ public readonly txState: TimedOutTxState,
114
+ ) {
115
+ super(message);
116
+ }
117
+ }
@@ -1,6 +1,8 @@
1
1
  import { pick } from '@aztec/foundation/collection';
2
- import { createLogger } from '@aztec/foundation/log';
2
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
3
+ import { RunningPromise } from '@aztec/foundation/running-promise';
3
4
 
5
+ import { Multicall3 } from './contracts/multicall.js';
4
6
  import { L1TxUtils, TxUtilsState } from './l1_tx_utils/index.js';
5
7
 
6
8
  // Defines the order in which we prioritise publishers based on their state (first is better)
@@ -27,22 +29,99 @@ const busyStates: TxUtilsState[] = [
27
29
 
28
30
  export type PublisherFilter<UtilsType extends L1TxUtils> = (utils: UtilsType) => boolean;
29
31
 
32
+ /** Config accepted by PublisherManager. */
33
+ type PublisherManagerConfig = {
34
+ publisherAllowInvalidStates?: boolean;
35
+ publisherFundingThreshold?: bigint;
36
+ publisherFundingAmount?: bigint;
37
+ };
38
+
30
39
  export class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
31
- private log = createLogger('publisher:manager');
32
- private config: { publisherAllowInvalidStates?: boolean };
40
+ private log: Logger;
41
+ private config: PublisherManagerConfig;
42
+ private static readonly FUNDING_CHECK_INTERVAL_MS = 2 * 60 * 1000;
43
+ protected funder?: UtilsType;
44
+ protected readonly fundingPromise?: RunningPromise;
45
+ private started = false;
33
46
 
34
47
  constructor(
35
- private publishers: UtilsType[],
36
- config: { publisherAllowInvalidStates?: boolean },
48
+ protected publishers: UtilsType[],
49
+ config: PublisherManagerConfig,
50
+ opts?: { bindings?: LoggerBindings; funder?: UtilsType },
37
51
  ) {
52
+ this.funder = opts?.funder;
53
+ this.log = createLogger('publisher:manager', opts?.bindings);
38
54
  this.log.info(`PublisherManager initialized with ${publishers.length} publishers.`);
39
55
  this.publishers = publishers;
40
- this.config = pick(config, 'publisherAllowInvalidStates');
56
+ this.config = pick(config, 'publisherAllowInvalidStates', 'publisherFundingThreshold', 'publisherFundingAmount');
57
+
58
+ const hasThreshold = this.config.publisherFundingThreshold !== undefined;
59
+ const hasAmount = this.config.publisherFundingAmount !== undefined;
60
+ if (hasThreshold !== hasAmount) {
61
+ this.log.warn(`Incomplete funding config: both publisherFundingThreshold and publisherFundingAmount must be set`);
62
+ }
63
+
64
+ if (this.funder) {
65
+ const funderAddress = this.funder.getSenderAddress();
66
+ if (publishers.some(p => p.getSenderAddress().equals(funderAddress))) {
67
+ this.log.error(`Funding account ${funderAddress} is also a publisher, disabling funding to avoid self-funding`);
68
+ this.funder = undefined;
69
+ }
70
+ }
71
+
72
+ if (this.funder && hasThreshold && hasAmount) {
73
+ this.fundingPromise = new RunningPromise(
74
+ () => this.triggerFundingIfNeeded(),
75
+ this.log,
76
+ PublisherManager.FUNDING_CHECK_INTERVAL_MS,
77
+ );
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Clears any interrupted flag left by a previous {@link stop} so publishing works again after a restart,
83
+ * loads the state of all publishers and the funder, and starts periodic funding checks. Idempotent: a
84
+ * start while already started is a no-op, so it never re-runs `loadStateAndResumeMonitoring` (which
85
+ * would spawn a duplicate background monitor per pending nonce). Lifecycle calls are expected to be
86
+ * serialized by the caller.
87
+ */
88
+ public async start(): Promise<void> {
89
+ if (this.started) {
90
+ this.log.debug('PublisherManager already started, ignoring start');
91
+ return;
92
+ }
93
+
94
+ // Clear the interrupted flag set by a previous stop() so a restarted manager can publish again.
95
+ // On a first start this is a no-op (the flag is already clear).
96
+ this.publishers.forEach(pub => pub.restart());
97
+ this.funder?.restart();
98
+
99
+ await Promise.all([
100
+ ...this.publishers.map(pub => pub.loadStateAndResumeMonitoring()),
101
+ this.funder?.loadStateAndResumeMonitoring(),
102
+ ]);
103
+
104
+ this.fundingPromise?.start();
105
+ // Marked started only once fully up, so a start that failed to load state can be retried.
106
+ this.started = true;
41
107
  }
42
108
 
43
- /** Loads the state of all publishers and resumes monitoring any pending txs */
44
- public async loadState(): Promise<void> {
45
- await Promise.all(this.publishers.map(pub => pub.loadStateAndResumeMonitoring()));
109
+ /**
110
+ * Stops the funding loop, interrupts all publishers so no further L1 txs are sent, and waits (bounded)
111
+ * for their in-flight tx monitor loops to wind down. Idempotent, and the manager may be restarted
112
+ * afterwards via {@link start}, which clears the interrupted flag.
113
+ */
114
+ public async stop(): Promise<void> {
115
+ this.started = false;
116
+ await this.fundingPromise?.stop();
117
+ this.publishers.forEach(pub => pub.interrupt());
118
+ this.funder?.interrupt();
119
+ // Wait for in-flight tx monitor loops to observe the interrupt, so no L1 requests are still
120
+ // being issued after shutdown (e.g. against an anvil instance a test is about to tear down).
121
+ await Promise.all([
122
+ ...this.publishers.map(pub => pub.waitMonitoringStopped()),
123
+ this.funder?.waitMonitoringStopped(),
124
+ ]);
46
125
  }
47
126
 
48
127
  // Finds and prioritises available publishers based on
@@ -81,8 +160,17 @@ export class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
81
160
  }),
82
161
  );
83
162
 
163
+ // Discount unfunded publishers: a publisher with no ETH cannot send a tx, so it must never win
164
+ // selection regardless of how favourable its state or last-used time is. Fall back to the full
165
+ // set only if every candidate is unfunded, so behaviour is no worse than before.
166
+ let fundedPublishers = publishersWithBalance.filter(p => p.balance > 0n);
167
+ if (fundedPublishers.length === 0) {
168
+ this.log.warn(`All candidate publishers have zero balance; selecting from unfunded publishers.`);
169
+ fundedPublishers = publishersWithBalance;
170
+ }
171
+
84
172
  // Sort based on state, then balance, then time since last use
85
- const sortedPublishers = publishersWithBalance.sort((a, b) => {
173
+ const sortedPublishers = fundedPublishers.sort((a, b) => {
86
174
  const stateComparison = sortOrder.indexOf(a.publisher.state) - sortOrder.indexOf(b.publisher.state);
87
175
  if (stateComparison !== 0) {
88
176
  return stateComparison;
@@ -100,7 +188,52 @@ export class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
100
188
  return sortedPublishers[0].publisher;
101
189
  }
102
190
 
103
- public interrupt() {
104
- this.publishers.forEach(pub => pub.interrupt());
191
+ /** Check all publisher balances and fund those below threshold. */
192
+ private async triggerFundingIfNeeded(): Promise<void> {
193
+ const { funder, config } = this;
194
+ if (!funder || config.publisherFundingThreshold === undefined || config.publisherFundingAmount === undefined) {
195
+ return;
196
+ }
197
+
198
+ const allBalances = await Promise.all(
199
+ this.publishers.map(async pub => ({ balance: await pub.getSenderBalance(), publisher: pub })),
200
+ );
201
+ const lowBalance = allBalances.filter(p => p.balance < config.publisherFundingThreshold!);
202
+ if (lowBalance.length === 0) {
203
+ return;
204
+ }
205
+
206
+ const fundingAmount = config.publisherFundingAmount!;
207
+ const funderBalance = await funder.getSenderBalance();
208
+
209
+ if (funderBalance < 10n * fundingAmount) {
210
+ this.log.warn(`Funding account balance is low`, { funderBalance, threshold: 10n * fundingAmount });
211
+ }
212
+ const affordableCount = Number(funderBalance / fundingAmount);
213
+ if (affordableCount === 0) {
214
+ this.log.error(`Funding account balance too low to fund any publisher`, { funderBalance, fundingAmount });
215
+ return;
216
+ }
217
+ if (affordableCount < lowBalance.length) {
218
+ this.log.warn(`Funder can only afford ${affordableCount}/${lowBalance.length} publishers`, {
219
+ funderBalance,
220
+ fundingAmount,
221
+ });
222
+ }
223
+
224
+ const toFund = lowBalance.slice(0, affordableCount).map(p => p.publisher);
225
+ await this.fundPublishers(toFund);
226
+ }
227
+
228
+ /** Fund publishers via a single Multicall3 aggregate3Value transaction. */
229
+ private async fundPublishers(publishers: UtilsType[]): Promise<void> {
230
+ const fundingAmount = this.config.publisherFundingAmount!;
231
+ const calls = publishers.map(pub => ({
232
+ to: pub.getSenderAddress().toString(),
233
+ value: fundingAmount,
234
+ }));
235
+
236
+ await Multicall3.forwardValue(calls, this.funder!, this.log);
237
+ this.log.info(`Funded ${publishers.length} publishers`);
105
238
  }
106
239
  }
package/src/queries.ts CHANGED
@@ -1,10 +1,77 @@
1
1
  import { EthAddress } from '@aztec/foundation/eth-address';
2
2
 
3
- import type { L1ContractsConfig } from './config.js';
3
+ import { BaseError, type Block } from 'viem';
4
+
5
+ import { DefaultL1ContractsConfig, type L1ContractsConfig } from './config.js';
4
6
  import { ReadOnlyGovernanceContract } from './contracts/governance.js';
5
7
  import { GovernanceProposerContract } from './contracts/governance_proposer.js';
8
+ import { InboxContract } from './contracts/inbox.js';
6
9
  import { RollupContract } from './contracts/rollup.js';
7
- import type { ViemPublicClient } from './types.js';
10
+ import type { ViemClient, ViemPublicClient } from './types.js';
11
+
12
+ /**
13
+ * Returns the L1 finalized block, or `undefined` if the chain does not yet have one
14
+ * (common on freshly started devnets). Rethrows any other RPC error.
15
+ */
16
+ export async function getFinalizedL1Block(client: ViemClient): Promise<Block<bigint, false, 'finalized'> | undefined> {
17
+ try {
18
+ return await client.getBlock({ blockTag: 'finalized', includeTransactions: false });
19
+ } catch (err) {
20
+ if (isFinalizedBlockTagNotFoundError(err)) {
21
+ return undefined;
22
+ }
23
+ throw err;
24
+ }
25
+ }
26
+
27
+ // Error messages returned by popular Ethereum execution clients when
28
+ // eth_getBlockByNumber / eth_call is called with blockTag "finalized" or
29
+ // "safe" and no such block has been produced yet:
30
+ //
31
+ // geth "finalized block not found"
32
+ // "safe block not found"
33
+ // reth "block not found: finalized"
34
+ // "block not found: safe"
35
+ // nethermind "Unknown block error" (same for both tags)
36
+ // besu "Unknown block"
37
+ // erigon 'block "finalized" not available (head block: N)'
38
+ // 'block "safe" not available (head block: N)'
39
+ //
40
+ // A combined regex covers all five:
41
+ const FINALIZED_BLOCK_TAG_NOT_FOUND_MESSAGE_RE =
42
+ /(finalized|safe) block not found|block not found: (finalized|safe)|unknown block|block "(finalized|safe)" not available/i;
43
+
44
+ /**
45
+ * Returns true if the error originates from an RPC call that failed because
46
+ * the "finalized" (or "safe") block tag is not yet available on the chain.
47
+ */
48
+ export function isFinalizedBlockTagNotFoundError(err: unknown): boolean {
49
+ if (!err) {
50
+ return false;
51
+ }
52
+
53
+ if (err instanceof BaseError) {
54
+ const hit = err.walk((e: any) => matchesFinalizedBlockTagNotFound(e));
55
+ if (hit) {
56
+ return true;
57
+ }
58
+ }
59
+
60
+ for (let cur: any = err, i = 0; cur && i < 10; cur = cur.cause, i++) {
61
+ if (matchesFinalizedBlockTagNotFound(cur)) {
62
+ return true;
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+
68
+ function matchesFinalizedBlockTagNotFound(e: any): boolean {
69
+ if (!e) {
70
+ return false;
71
+ }
72
+ const text = `${e.details ?? ''} ${e.message ?? ''} ${e.shortMessage ?? ''}`;
73
+ return FINALIZED_BLOCK_TAG_NOT_FOUND_MESSAGE_RE.test(text);
74
+ }
8
75
 
9
76
  /** Reads the L1ContractsConfig from L1 contracts. */
10
77
  export async function getL1ContractsConfig(
@@ -25,6 +92,8 @@ export async function getL1ContractsConfig(
25
92
  const rollup = new RollupContract(publicClient, rollupAddress.toString());
26
93
  const slasherProposer = await rollup.getSlashingProposer();
27
94
  const slasher = await rollup.getSlasherContract();
95
+ const rollupAddresses = await rollup.getRollupAddresses();
96
+ const inboxContract = new InboxContract(publicClient, rollupAddresses.inboxAddress.toString());
28
97
 
29
98
  const [
30
99
  l1StartBlock,
@@ -35,6 +104,7 @@ export async function getL1ContractsConfig(
35
104
  aztecTargetCommitteeSize,
36
105
  lagInEpochsForValidatorSet,
37
106
  lagInEpochsForRandao,
107
+ inboxLag,
38
108
  activationThreshold,
39
109
  ejectionThreshold,
40
110
  localEjectionThreshold,
@@ -62,6 +132,7 @@ export async function getL1ContractsConfig(
62
132
  rollup.getTargetCommitteeSize(),
63
133
  rollup.getLagInEpochsForValidatorSet(),
64
134
  rollup.getLagInEpochsForRandao(),
135
+ inboxContract.getLag(),
65
136
  rollup.getActivationThreshold(),
66
137
  rollup.getEjectionThreshold(),
67
138
  rollup.getLocalEjectionThreshold(),
@@ -71,8 +142,8 @@ export async function getL1ContractsConfig(
71
142
  slasherProposer?.getRoundSize() ?? 0n,
72
143
  slasherProposer?.getLifetimeInRounds() ?? 0n,
73
144
  slasherProposer?.getExecutionDelayInRounds() ?? 0n,
74
- slasherProposer?.type === 'tally' ? slasherProposer.getSlashOffsetInRounds() : 0n,
75
- slasherProposer?.type === 'tally' ? slasherProposer.getSlashingAmounts() : [0n, 0n, 0n],
145
+ slasherProposer?.getSlashOffsetInRounds() ?? 0n,
146
+ slasherProposer?.getSlashingAmounts() ?? [0n, 0n, 0n],
76
147
  slasher?.getVetoer() ?? EthAddress.ZERO,
77
148
  slasher?.getSlashingDisableDuration() ?? 0,
78
149
  rollup.getManaTarget(),
@@ -91,13 +162,15 @@ export async function getL1ContractsConfig(
91
162
  aztecTargetCommitteeSize: Number(aztecTargetCommitteeSize),
92
163
  lagInEpochsForValidatorSet: Number(lagInEpochsForValidatorSet),
93
164
  lagInEpochsForRandao: Number(lagInEpochsForRandao),
165
+ inboxLag: Number(inboxLag),
94
166
  governanceProposerQuorum: Number(governanceProposerQuorum),
95
167
  governanceProposerRoundSize: Number(governanceProposerRoundSize),
168
+ governanceVotingDuration: DefaultL1ContractsConfig.governanceVotingDuration,
96
169
  activationThreshold,
97
170
  ejectionThreshold,
98
171
  localEjectionThreshold,
99
172
  slashingQuorum: Number(slashingQuorum),
100
- slashingRoundSizeInEpochs: Number(slashingRoundSize / aztecEpochDuration),
173
+ slashingRoundSizeInEpochs: Number(Number(slashingRoundSize) / aztecEpochDuration),
101
174
  slashingLifetimeInRounds: Number(slashingLifetimeInRounds),
102
175
  slashingExecutionDelayInRounds: Number(slashingExecutionDelayInRounds),
103
176
  slashingVetoer,
@@ -105,12 +178,19 @@ export async function getL1ContractsConfig(
105
178
  manaTarget,
106
179
  provingCostPerMana: provingCostPerMana,
107
180
  rollupVersion: Number(rollupVersion),
108
- genesisArchiveTreeRoot,
181
+ genesisArchiveTreeRoot: genesisArchiveTreeRoot.toString(),
109
182
  exitDelaySeconds: Number(exitDelay),
110
- slasherFlavor: slasherProposer?.type ?? 'tally',
183
+ slasherEnabled: !!slasherProposer,
111
184
  slashingOffsetInRounds: Number(slashingOffsetInRounds),
112
185
  slashAmountSmall: slashingAmounts[0],
113
186
  slashAmountMedium: slashingAmounts[1],
114
187
  slashAmountLarge: slashingAmounts[2],
188
+ initialEthPerFeeAsset: DefaultL1ContractsConfig.initialEthPerFeeAsset,
189
+ // Not exposed by the rollup contract; fall back to defaults like the other non-on-chain fields above.
190
+ entryQueueBootstrapValidatorSetSize: DefaultL1ContractsConfig.entryQueueBootstrapValidatorSetSize,
191
+ entryQueueBootstrapFlushSize: DefaultL1ContractsConfig.entryQueueBootstrapFlushSize,
192
+ entryQueueFlushSizeMin: DefaultL1ContractsConfig.entryQueueFlushSizeMin,
193
+ entryQueueFlushSizeQuotient: DefaultL1ContractsConfig.entryQueueFlushSizeQuotient,
194
+ entryQueueMaxFlushSize: DefaultL1ContractsConfig.entryQueueMaxFlushSize,
115
195
  };
116
196
  }
@@ -0,0 +1,65 @@
1
+ import { getBytesPerBlob, getBytesPerCommitment, getKzg } from '@aztec/blob-lib';
2
+ import type { Logger } from '@aztec/foundation/log';
3
+
4
+ import type { ExtendedViemWalletClient } from '../types.js';
5
+
6
+ /**
7
+ * Warms both KZG trusted setups in parallel for tests against a local anvil:
8
+ * - anvil's own setup, loaded lazily when it first validates a blob sidecar in `eth_sendRawTransaction`
9
+ * - our `@crate-crypto/node-eth-kzg` singleton (`getKzg()`, a ~2.2s synchronous precomp build)
10
+ *
11
+ * Without this, the first checkpoint publish pays both serially (~4.4s). The recipe does all RPC round
12
+ * trips up front, fires a single bare raw send carrying a fake (garbage) blob sidecar that anvil rejects
13
+ * after loading its trusted setup, yields once to flush the socket write, then synchronously builds our
14
+ * own precomp tables while anvil warms in parallel. The fake tx is rejected at admission, so it never
15
+ * enters the pool, mines no block, and burns no nonce — sending it from the passed client is harmless.
16
+ *
17
+ * Best-effort: any failure is debug-logged and swallowed, leaving lazy init as the fallback. Never throws.
18
+ *
19
+ * @param l1Client - The L1 client whose account sends the (rejected) warm-up blob tx.
20
+ */
21
+ export async function warmBlobKzg(l1Client: ExtendedViemWalletClient, logger?: Logger): Promise<void> {
22
+ try {
23
+ // A pure-JS fake kzg: returns deterministic garbage of the correct lengths (commitment and proof are
24
+ // both 48 bytes). It makes viem build a syntactically valid sidecar without initializing any native
25
+ // module, so anvil loads its trusted setup to validate the (invalid) proof and then rejects the tx.
26
+ const fakeKzg = {
27
+ blobToKzgCommitment: () => {
28
+ const commitment = new Uint8Array(getBytesPerCommitment());
29
+ commitment.fill(0xab);
30
+ commitment[0] = 0xc0;
31
+ return commitment;
32
+ },
33
+ computeBlobKzgProof: () => {
34
+ const proof = new Uint8Array(getBytesPerCommitment());
35
+ proof.fill(0xcd);
36
+ proof[0] = 0xc0;
37
+ return proof;
38
+ },
39
+ };
40
+
41
+ // All RPC round trips (nonce, fees, gas) happen here, so only a single socket write remains to flush.
42
+ const prepared = await l1Client.prepareTransactionRequest({
43
+ blobs: [new Uint8Array(getBytesPerBlob())],
44
+ kzg: fakeKzg,
45
+ to: '0x0000000000000000000000000000000000000000',
46
+ value: 0n,
47
+ maxFeePerBlobGas: 1_000_000_000_000n,
48
+ });
49
+ const serialized = await l1Client.signTransaction(prepared);
50
+
51
+ // Fire the raw send without awaiting and swallow the expected rejection.
52
+ const sent = l1Client.request({ method: 'eth_sendRawTransaction', params: [serialized] }).catch(() => {});
53
+
54
+ // Flush the single socket write so anvil receives the tx and starts loading its trusted setup in its
55
+ // own process before we block the event loop below.
56
+ await new Promise(resolve => setImmediate(resolve));
57
+
58
+ // Synchronously build our precomp tables (~2.2s, event-loop-blocking); anvil warms in parallel.
59
+ getKzg();
60
+
61
+ await sent;
62
+ } catch (err) {
63
+ logger?.debug('Failed to warm blob KZG; falling back to lazy init', { err });
64
+ }
65
+ }