@aztec/ethereum 3.0.0-canary.a9708bd → 3.0.0-devnet.3

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 (144) hide show
  1. package/dest/client.d.ts +1 -1
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/config.d.ts +11 -6
  4. package/dest/config.d.ts.map +1 -1
  5. package/dest/config.js +124 -64
  6. package/dest/contracts/empire_base.d.ts +1 -1
  7. package/dest/contracts/empire_base.d.ts.map +1 -1
  8. package/dest/contracts/empire_slashing_proposer.d.ts +2 -2
  9. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  10. package/dest/contracts/empire_slashing_proposer.js +1 -1
  11. package/dest/contracts/fee_asset_handler.d.ts +3 -3
  12. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  13. package/dest/contracts/governance.js +7 -3
  14. package/dest/contracts/governance_proposer.d.ts +1 -2
  15. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  16. package/dest/contracts/governance_proposer.js +1 -2
  17. package/dest/contracts/multicall.d.ts +3 -5
  18. package/dest/contracts/multicall.d.ts.map +1 -1
  19. package/dest/contracts/multicall.js +6 -4
  20. package/dest/contracts/rollup.d.ts +39 -19
  21. package/dest/contracts/rollup.d.ts.map +1 -1
  22. package/dest/contracts/rollup.js +84 -88
  23. package/dest/contracts/slasher_contract.d.ts +10 -0
  24. package/dest/contracts/slasher_contract.d.ts.map +1 -1
  25. package/dest/contracts/slasher_contract.js +18 -0
  26. package/dest/contracts/tally_slashing_proposer.d.ts +22 -3
  27. package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -1
  28. package/dest/contracts/tally_slashing_proposer.js +55 -5
  29. package/dest/deploy_l1_contracts.d.ts +22 -7
  30. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  31. package/dest/deploy_l1_contracts.js +555 -362
  32. package/dest/index.d.ts +1 -1
  33. package/dest/index.d.ts.map +1 -1
  34. package/dest/index.js +1 -1
  35. package/dest/l1_artifacts.d.ts +8729 -6014
  36. package/dest/l1_artifacts.d.ts.map +1 -1
  37. package/dest/l1_artifacts.js +10 -5
  38. package/dest/l1_contract_addresses.d.ts +5 -1
  39. package/dest/l1_contract_addresses.d.ts.map +1 -1
  40. package/dest/l1_contract_addresses.js +16 -26
  41. package/dest/l1_reader.d.ts +1 -1
  42. package/dest/l1_reader.d.ts.map +1 -1
  43. package/dest/l1_reader.js +8 -8
  44. package/dest/l1_tx_utils/config.d.ts +59 -0
  45. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  46. package/dest/l1_tx_utils/config.js +73 -0
  47. package/dest/l1_tx_utils/constants.d.ts +6 -0
  48. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  49. package/dest/l1_tx_utils/constants.js +14 -0
  50. package/dest/l1_tx_utils/factory.d.ts +24 -0
  51. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  52. package/dest/l1_tx_utils/factory.js +12 -0
  53. package/dest/l1_tx_utils/index.d.ts +10 -0
  54. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  55. package/dest/l1_tx_utils/index.js +10 -0
  56. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  57. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  58. package/dest/l1_tx_utils/interfaces.js +4 -0
  59. package/dest/l1_tx_utils/l1_tx_utils.d.ts +95 -0
  60. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  61. package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
  62. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  63. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  64. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  65. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +81 -0
  66. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  67. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +294 -0
  68. package/dest/l1_tx_utils/signer.d.ts +4 -0
  69. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  70. package/dest/l1_tx_utils/signer.js +16 -0
  71. package/dest/l1_tx_utils/types.d.ts +67 -0
  72. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  73. package/dest/l1_tx_utils/types.js +26 -0
  74. package/dest/l1_tx_utils/utils.d.ts +4 -0
  75. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  76. package/dest/l1_tx_utils/utils.js +14 -0
  77. package/dest/publisher_manager.d.ts +7 -2
  78. package/dest/publisher_manager.d.ts.map +1 -1
  79. package/dest/publisher_manager.js +36 -8
  80. package/dest/queries.d.ts.map +1 -1
  81. package/dest/queries.js +11 -12
  82. package/dest/test/chain_monitor.d.ts +11 -0
  83. package/dest/test/chain_monitor.d.ts.map +1 -1
  84. package/dest/test/chain_monitor.js +81 -12
  85. package/dest/test/delayed_tx_utils.d.ts +2 -2
  86. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  87. package/dest/test/delayed_tx_utils.js +2 -2
  88. package/dest/test/eth_cheat_codes.d.ts +32 -6
  89. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  90. package/dest/test/eth_cheat_codes.js +115 -28
  91. package/dest/test/rollup_cheat_codes.d.ts +11 -9
  92. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  93. package/dest/test/rollup_cheat_codes.js +38 -6
  94. package/dest/test/upgrade_utils.d.ts.map +1 -1
  95. package/dest/test/upgrade_utils.js +3 -2
  96. package/dest/utils.d.ts.map +1 -1
  97. package/dest/utils.js +10 -161
  98. package/dest/zkPassportVerifierAddress.js +1 -1
  99. package/package.json +7 -7
  100. package/src/client.ts +1 -1
  101. package/src/config.ts +136 -68
  102. package/src/contracts/empire_base.ts +1 -1
  103. package/src/contracts/empire_slashing_proposer.ts +7 -3
  104. package/src/contracts/fee_asset_handler.ts +1 -1
  105. package/src/contracts/governance.ts +3 -3
  106. package/src/contracts/governance_proposer.ts +3 -4
  107. package/src/contracts/multicall.ts +12 -10
  108. package/src/contracts/rollup.ts +104 -106
  109. package/src/contracts/slasher_contract.ts +22 -0
  110. package/src/contracts/tally_slashing_proposer.ts +54 -6
  111. package/src/deploy_l1_contracts.ts +570 -328
  112. package/src/index.ts +1 -1
  113. package/src/l1_artifacts.ts +14 -6
  114. package/src/l1_contract_addresses.ts +17 -26
  115. package/src/l1_reader.ts +9 -9
  116. package/src/l1_tx_utils/README.md +177 -0
  117. package/src/l1_tx_utils/config.ts +140 -0
  118. package/src/l1_tx_utils/constants.ts +18 -0
  119. package/src/l1_tx_utils/factory.ts +64 -0
  120. package/src/l1_tx_utils/index.ts +12 -0
  121. package/src/l1_tx_utils/interfaces.ts +86 -0
  122. package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
  123. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  124. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +372 -0
  125. package/src/l1_tx_utils/signer.ts +28 -0
  126. package/src/l1_tx_utils/types.ts +85 -0
  127. package/src/l1_tx_utils/utils.ts +16 -0
  128. package/src/publisher_manager.ts +51 -9
  129. package/src/queries.ts +13 -8
  130. package/src/test/chain_monitor.ts +89 -9
  131. package/src/test/delayed_tx_utils.ts +2 -2
  132. package/src/test/eth_cheat_codes.ts +142 -29
  133. package/src/test/rollup_cheat_codes.ts +54 -14
  134. package/src/test/upgrade_utils.ts +3 -2
  135. package/src/utils.ts +13 -185
  136. package/src/zkPassportVerifierAddress.ts +1 -1
  137. package/dest/l1_tx_utils.d.ts +0 -250
  138. package/dest/l1_tx_utils.d.ts.map +0 -1
  139. package/dest/l1_tx_utils.js +0 -826
  140. package/dest/l1_tx_utils_with_blobs.d.ts +0 -19
  141. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  142. package/dest/l1_tx_utils_with_blobs.js +0 -85
  143. package/src/l1_tx_utils.ts +0 -1105
  144. package/src/l1_tx_utils_with_blobs.ts +0 -144
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './constants.js';
2
2
  export * from './deploy_l1_contracts.js';
3
3
  export * from './chain.js';
4
- export * from './l1_tx_utils.js';
4
+ export * from './l1_tx_utils/index.js';
5
5
  export * from './l1_contract_addresses.js';
6
6
  export * from './l1_reader.js';
7
7
  export * from './utils.js';
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  CoinIssuerAbi,
3
3
  CoinIssuerBytecode,
4
+ DateGatedRelayerAbi,
5
+ DateGatedRelayerBytecode,
4
6
  EmpireSlasherDeploymentExtLibAbi,
5
7
  EmpireSlasherDeploymentExtLibBytecode,
6
8
  EmpireSlashingProposerAbi,
@@ -31,10 +33,10 @@ import {
31
33
  RegisterNewRollupVersionPayloadBytecode,
32
34
  RegistryAbi,
33
35
  RegistryBytecode,
34
- RewardDeploymentExtLibAbi,
35
- RewardDeploymentExtLibBytecode,
36
36
  RewardDistributorAbi,
37
37
  RewardDistributorBytecode,
38
+ RewardExtLibAbi,
39
+ RewardExtLibBytecode,
38
40
  RollupAbi,
39
41
  RollupBytecode,
40
42
  RollupLinkReferences,
@@ -100,10 +102,10 @@ export const RollupArtifact = {
100
102
  contractAbi: ValidatorOperationsExtLibAbi,
101
103
  contractBytecode: ValidatorOperationsExtLibBytecode as Hex,
102
104
  },
103
- RewardDeploymentExtLib: {
104
- name: 'RewardDeploymentExtLib',
105
- contractAbi: RewardDeploymentExtLibAbi,
106
- contractBytecode: RewardDeploymentExtLibBytecode as Hex,
105
+ RewardExtLib: {
106
+ name: 'RewardExtLib',
107
+ contractAbi: RewardExtLibAbi,
108
+ contractBytecode: RewardExtLibBytecode as Hex,
107
109
  },
108
110
  TallySlasherDeploymentExtLib: {
109
111
  name: 'TallySlasherDeploymentExtLib',
@@ -149,6 +151,12 @@ export const CoinIssuerArtifact = {
149
151
  contractBytecode: CoinIssuerBytecode as Hex,
150
152
  };
151
153
 
154
+ export const DateGatedRelayerArtifact = {
155
+ name: 'DateGatedRelayer',
156
+ contractAbi: DateGatedRelayerAbi,
157
+ contractBytecode: DateGatedRelayerBytecode as Hex,
158
+ };
159
+
152
160
  export const GovernanceProposerArtifact = {
153
161
  name: 'GovernanceProposer',
154
162
  contractAbi: GovernanceProposerAbi,
@@ -32,6 +32,7 @@ export type L1ContractAddresses = {
32
32
  stakingAssetHandlerAddress?: EthAddress | undefined;
33
33
  zkPassportVerifierAddress?: EthAddress | undefined;
34
34
  gseAddress?: EthAddress | undefined;
35
+ dateGatedRelayerAddress?: EthAddress | undefined;
35
36
  };
36
37
 
37
38
  export const L1ContractAddressesSchema = z.object({
@@ -51,80 +52,70 @@ export const L1ContractAddressesSchema = z.object({
51
52
  stakingAssetHandlerAddress: schemas.EthAddress.optional(),
52
53
  zkPassportVerifierAddress: schemas.EthAddress.optional(),
53
54
  gseAddress: schemas.EthAddress.optional(),
55
+ dateGatedRelayerAddress: schemas.EthAddress.optional(),
54
56
  }) satisfies ZodFor<L1ContractAddresses>;
55
57
 
56
58
  const parseEnv = (val: string) => EthAddress.fromString(val);
57
59
 
58
60
  export const l1ContractAddressesMapping: ConfigMappingsType<
59
- Omit<L1ContractAddresses, 'gseAddress' | 'zkPassportVerifierAddress'>
61
+ Omit<L1ContractAddresses, 'gseAddress' | 'zkPassportVerifierAddress' | 'dateGatedRelayerAddress'>
60
62
  > = {
61
- rollupAddress: {
62
- env: 'ROLLUP_CONTRACT_ADDRESS',
63
- description: 'The deployed L1 rollup contract address.',
64
- parseEnv,
65
- },
66
63
  registryAddress: {
67
64
  env: 'REGISTRY_CONTRACT_ADDRESS',
68
65
  description: 'The deployed L1 registry contract address.',
69
66
  parseEnv,
70
67
  },
68
+ slashFactoryAddress: {
69
+ env: 'SLASH_FACTORY_CONTRACT_ADDRESS',
70
+ description: 'The deployed L1 slashFactory contract address',
71
+ parseEnv,
72
+ },
73
+ feeAssetHandlerAddress: {
74
+ env: 'FEE_ASSET_HANDLER_CONTRACT_ADDRESS',
75
+ description: 'The deployed L1 feeAssetHandler contract address',
76
+ parseEnv,
77
+ },
78
+ rollupAddress: {
79
+ description: 'The deployed L1 rollup contract address.',
80
+ parseEnv,
81
+ },
71
82
  inboxAddress: {
72
- env: 'INBOX_CONTRACT_ADDRESS',
73
83
  description: 'The deployed L1 inbox contract address.',
74
84
  parseEnv,
75
85
  },
76
86
  outboxAddress: {
77
- env: 'OUTBOX_CONTRACT_ADDRESS',
78
87
  description: 'The deployed L1 outbox contract address.',
79
88
  parseEnv,
80
89
  },
81
90
  feeJuiceAddress: {
82
- env: 'FEE_JUICE_CONTRACT_ADDRESS',
83
91
  description: 'The deployed L1 Fee Juice contract address.',
84
92
  parseEnv,
85
93
  },
86
94
  stakingAssetAddress: {
87
- env: 'STAKING_ASSET_CONTRACT_ADDRESS',
88
95
  description: 'The deployed L1 staking asset contract address.',
89
96
  parseEnv,
90
97
  },
91
98
  feeJuicePortalAddress: {
92
- env: 'FEE_JUICE_PORTAL_CONTRACT_ADDRESS',
93
99
  description: 'The deployed L1 Fee Juice portal contract address.',
94
100
  parseEnv,
95
101
  },
96
102
  coinIssuerAddress: {
97
- env: 'COIN_ISSUER_CONTRACT_ADDRESS',
98
103
  description: 'The deployed L1 coinIssuer contract address',
99
104
  parseEnv,
100
105
  },
101
106
  rewardDistributorAddress: {
102
- env: 'REWARD_DISTRIBUTOR_CONTRACT_ADDRESS',
103
107
  description: 'The deployed L1 rewardDistributor contract address',
104
108
  parseEnv,
105
109
  },
106
110
  governanceProposerAddress: {
107
- env: 'GOVERNANCE_PROPOSER_CONTRACT_ADDRESS',
108
111
  description: 'The deployed L1 governanceProposer contract address',
109
112
  parseEnv,
110
113
  },
111
114
  governanceAddress: {
112
- env: 'GOVERNANCE_CONTRACT_ADDRESS',
113
115
  description: 'The deployed L1 governance contract address',
114
116
  parseEnv,
115
117
  },
116
- slashFactoryAddress: {
117
- env: 'SLASH_FACTORY_CONTRACT_ADDRESS',
118
- description: 'The deployed L1 slashFactory contract address',
119
- parseEnv,
120
- },
121
- feeAssetHandlerAddress: {
122
- env: 'FEE_ASSET_HANDLER_CONTRACT_ADDRESS',
123
- description: 'The deployed L1 feeAssetHandler contract address',
124
- parseEnv,
125
- },
126
118
  stakingAssetHandlerAddress: {
127
- env: 'STAKING_ASSET_HANDLER_CONTRACT_ADDRESS',
128
119
  description: 'The deployed L1 stakingAssetHandler contract address',
129
120
  parseEnv,
130
121
  },
package/src/l1_reader.ts CHANGED
@@ -4,7 +4,7 @@ import { type L1ContractAddresses, l1ContractAddressesMapping } from './l1_contr
4
4
 
5
5
  /** Configuration of the L1GlobalReader. */
6
6
  export interface L1ReaderConfig {
7
- /** The RPC Url of the ethereum host. */
7
+ /** List of URLs of Ethereum RPC nodes that services will connect to (comma separated). */
8
8
  l1RpcUrls: string[];
9
9
  /** The chain ID of the ethereum host. */
10
10
  l1ChainId: number;
@@ -15,20 +15,20 @@ export interface L1ReaderConfig {
15
15
  }
16
16
 
17
17
  export const l1ReaderConfigMappings: ConfigMappingsType<L1ReaderConfig> = {
18
- l1RpcUrls: {
19
- env: 'ETHEREUM_HOSTS',
20
- description: 'The RPC Url of the ethereum host.',
21
- parseEnv: (val: string) => val.split(',').map(url => url.trim()),
18
+ l1Contracts: {
19
+ description: 'The deployed L1 contract addresses',
20
+ nested: l1ContractAddressesMapping,
22
21
  },
23
22
  l1ChainId: {
24
23
  env: 'L1_CHAIN_ID',
25
24
  parseEnv: (val: string) => +val,
26
- defaultValue: 31337,
27
25
  description: 'The chain ID of the ethereum host.',
28
26
  },
29
- l1Contracts: {
30
- description: 'The deployed L1 contract addresses',
31
- nested: l1ContractAddressesMapping,
27
+ l1RpcUrls: {
28
+ env: 'ETHEREUM_HOSTS',
29
+ description: 'List of URLs of Ethereum RPC nodes that services will connect to (comma separated).',
30
+ parseEnv: (val: string) => val.split(',').map(url => url.trim()),
31
+ defaultValue: [],
32
32
  },
33
33
  viemPollingIntervalMS: {
34
34
  env: 'L1_READER_VIEM_POLLING_INTERVAL_MS',
@@ -0,0 +1,177 @@
1
+ # L1 Transaction Utils
2
+
3
+ This module handles sending L1 txs, including simulating txs, choosing gas prices, estimating gas limits, monitoring sent txs, speeding them up, and cancelling them. Each instance of `L1TxUtils` is stateful, corresponds to a given **publisher** EOA, and tracks its in-flight txs.
4
+
5
+ ## Usage context
6
+
7
+ Aside from bootstrapping (such as deploying L1 contracts), the Aztec node sends txs to L1 for the following purposes:
8
+
9
+ ### Sequencing
10
+
11
+ As a sequencer (ie block proposer), the node sends blob txs proposing new L2 blocks. These txs may be part of a multicall where the proposer also votes for a proposal, slashes other validators, or invalidates a block. If block building fails, the sequencer may send a multicall without a block (and hence without blobs). These actions have a specific set of L1 blocks in which they may land (ie an L2 slot, which lasts 2-6 L1 slots), after which they "expire" and revert if mined. On each L2 slot, at most one L1 tx is in-flight.
12
+
13
+ A given block proposer is chosen at random. While chances are low, it could be the case that the same proposer is chosen for two L2 slots in a row.
14
+
15
+ There is an edge case in which block building fails at the beginning of the slot (for instance, if there are not enough L2 txs to build the block), which means only a vote or a slash is sent to L1, but then the block does get built, and is submitted in a separate L1 tx.
16
+
17
+ ### Proving
18
+
19
+ As a prover, the node sends a tx with a validity proof for an epoch. These txs also have an expiration window, after which they revert if they'd land. No blobs are used. The cost is 1M-4M gas, and these txs are sent at most once per epoch, which is about 96-384 L1 slots.
20
+
21
+ Provers typically try proving all epochs. Today the proof submission window is set to one epoch, meaning that each epoch must be proven during the next, so there is no overlap. If this window were to be extended, then we could have multiple L1 proving txs in flight, which must land in order.
22
+
23
+ ## Properties
24
+
25
+ From the usage context above, we know that:
26
+
27
+ - Each publisher EOA has typically only one in-flight tx at a time.
28
+ - Every tx has an expiration time after which they'd revert if mined.
29
+
30
+ ## State transitions
31
+
32
+ We keep all our **publishers** split by scope, where the scope may be _proving_ or _sequencing_. If sequencing, publishers are also scoped by validator address, so a node that runs multiple validators may use different publisher accounts for each validator, to avoid publicly linking them. Note that a publisher may belong to more than one scope.
33
+
34
+ Each publisher account is in one of the following states, which is reflected from the state of the tx with the highest nonce it has sent:
35
+
36
+ - `idle`: Ready to send a tx
37
+ - `sent`: A tx has been sent and we are awaiting for it to be mined
38
+ - `speed-up`: The tx has been replaced with the same tx but higher gas price
39
+ - `cancelled`: The tx has expired so it has been replaced with a noop tx
40
+ - `not-mined`: The tx has expired or was dropped and we are no longer monitoring it
41
+ - `mined`: The tx or one of its replacements (ie a tx with the same nonce) has been mined
42
+
43
+ With the following state transitions:
44
+
45
+ | From | To | Condition | Effect |
46
+ |-|-|-|-|
47
+ | `idle` | `sent` | `send_tx` | A new tx is sent and nonce is consumed |
48
+ | `sent` | `speed-up`| `time_since_last_sent > stall_time && retry_attempts < max_retries` | The requested tx is replaced with an equivalent but higher gas price |
49
+ | `sent`, `speed-up` | `not-mined` | `current_time > tx_timeout && !cancel_on_timeout` | The tx times out, nonce manager is reset |
50
+ | `sent`, `speed-up` | `cancelled` | `current_time > tx_timeout && cancel_on_timeout` | The tx times out and we replace it with a noop |
51
+ | `sent`, `speed-up`, `cancelled` | `mined` | `get_nonce(latest) > tx_nonce` | The tx or a replacement is mined |
52
+ | `cancelled` | `not-mined` | `current_time > cancel_tx_timeout` | Cancellation times out, nonce manager is reset |
53
+ | `cancelled` | `not-mined` | `nonce no longer in mempool && time_passed > unseen_considered_dropped` | Cancel tx dropped from mempool, nonce manager is reset |
54
+
55
+ Note that we do not transition back to `idle`.
56
+
57
+ ## Nonce Management
58
+
59
+ The `L1TxUtils` class uses a `NonceManager` from viem to track and manage nonces for the publisher account:
60
+
61
+ - **Nonce consumption**: When sending a new transaction, the nonce is consumed from the nonce manager, which increments the internal counter.
62
+ - **Nonce reset**: The nonce manager is reset in the following scenarios:
63
+ - When a regular tx times out without being cancelled (`NOT_MINED` state)
64
+ - When a cancellation tx is dropped from the mempool
65
+ - When a cancellation tx itself times out
66
+ - When we decide not to send a cancellation due to interruption or the original tx being dropped
67
+
68
+ The reset allows the next transaction to reuse the nonce if the current tx is no longer in the mempool by the time this next transaction is sent.
69
+
70
+ ## Time checks
71
+
72
+ All time checks for speed ups and time outs are based on L1 time, not local time. When se send a tx, we assign the `sent_at` time to the time of the most recent L1 block. Using L1 time means that speed ups and time outs can be expressed in terms of L1 slots. It also means that we will wait for a new L1 block to be mined and check if our tx is present in that block before computing time outs or speed ups.
73
+
74
+ An edge case here is that, if an L1 slot is missed (ie there is no L1 block for that slot), we won't update time outs during that 12s period. Given how infrequent these are, we are fine with this tradeoff.
75
+
76
+ ## Pseudocode
77
+
78
+ ```python
79
+ def send_and_monitor_tx(tx_request):
80
+ # Always consume a fresh nonce from the nonce manager
81
+ nonce = nonce_manager.consume()
82
+
83
+ # Build and send the transaction
84
+ tx = make_tx(tx_request, nonce)
85
+ state = create_state(tx, status='sent')
86
+ txs.push(state)
87
+ l1.send_tx(tx)
88
+
89
+ # State transitions differ based on whether this is a cancel tx
90
+ is_cancel_tx = state.cancelTxHashes.length > 0
91
+
92
+ # Monitor loop
93
+ loop:
94
+ # Check if interrupted
95
+ if interrupted:
96
+ break
97
+
98
+ # Check if the tx was mined
99
+ current_nonce = l1.get_nonce(latest)
100
+ if current_nonce > nonce:
101
+ # Try to find the receipt from all tx attempts
102
+ for tx in state.txHashes + state.cancelTxHashes:
103
+ if receipt = l1.get_tx_receipt(tx):
104
+ state.status = 'mined'
105
+ return receipt
106
+ # Unknown tx was mined with our nonce
107
+ state.status = 'mined'
108
+ raise unknown_mined_tx_error
109
+
110
+ # Check if cancel tx dropped from mempool (only for cancellations)
111
+ pending_nonce = l1.get_nonce(pending)
112
+ if is_cancel_tx and pending_nonce < nonce and time_passed > unseen_considered_dropped:
113
+ state.status = 'idle'
114
+ nonce_manager.reset()
115
+ raise dropped_transaction_error
116
+
117
+ # Check if tx has timed out
118
+ if is_timed_out(state, l1_timestamp):
119
+ if is_cancel_tx or !cancel_on_timeout:
120
+ # Either already a cancel tx or configured not to cancel
121
+ state.status = 'not-mined'
122
+ nonce_manager.reset()
123
+ raise timeout_error
124
+ else:
125
+ # Send cancellation in background
126
+ run_in_background attempt_tx_cancellation(state)
127
+ raise timeout_error
128
+
129
+ # Speed up if stalled and have retries left
130
+ if time_since_last_sent > stall_time and attempts < max_attempts:
131
+ replacement_tx = make_tx(tx_request, nonce, bump_gas_price(state.gasPrice))
132
+ state.status = is_cancel_tx ? 'cancelled' : 'speed-up'
133
+ state.tx_hashes.push(send_tx(replacement_tx))
134
+ state.last_sent_at = now
135
+ continue
136
+
137
+ sleep(check_interval)
138
+
139
+ def attempt_tx_cancellation(state):
140
+ # Check if original tx still in mempool
141
+ if l1.get_nonce(pending) < state.nonce:
142
+ state.status = 'not-mined'
143
+ nonce_manager.reset()
144
+ return
145
+
146
+ # Send noop tx with same nonce but higher gas
147
+ cancel_tx = make_noop_tx(state.nonce, bump_gas_price(state.gasPrice))
148
+ state.cancelTxHashes.push(send_tx(cancel_tx))
149
+ state.status = 'cancelled'
150
+
151
+ # Monitor the cancellation in background
152
+ run_in_background monitor_transaction(state)
153
+ ```
154
+
155
+ ## Publisher selection
156
+
157
+ When sending a tx for a given scope, we choose from all publishers for the scope in the following order:
158
+
159
+ - `idle`: The publisher is ready to be used and has not sent any txs recently.
160
+ - `mined`: The publisher has mined a tx and is ready for a new one.
161
+ - `speed-up`, `sent`: There is a tx in-flight, new one will be enqueued after (not eligible unless `publisherAllowInvalidStates` is set).
162
+ - `cancelled`: There is a tx in-flight caused by a time-out mining the previous one, new tx will be enqueued after (not eligible unless `publisherAllowInvalidStates` is set).
163
+ - `not-mined`: The previous tx timed out or was dropped, new tx will reuse the same nonce if previous one is no longer in the mempool, or pick the next otherwise (not eligible unless `publisherAllowInvalidStates` is enabled).
164
+
165
+ If there is more than one publisher in the same state to choose from, we prefer choosing based on:
166
+ 1. Highest balance first
167
+ 2. Least recently used (based on `lastMinedAtBlockNumber`)
168
+
169
+ Available publishers should be filtered by balance, ensuring that the given EOA has enough funds to send the tx, and possibly replace it with a larger gas price. If we detect a publisher account has not enough gas, we should warn (bonus points if we warn before running out).
170
+
171
+ Note that selection is not handled by the `L1TxUtils` class but by the `PublisherManager`.
172
+
173
+ ## API
174
+
175
+ - `sendTransaction`: Sends an L1 tx and returns the tx hash. Returns when the tx has been sent. Consumes a nonce from the nonce manager.
176
+ - `monitorTransaction`: Monitors a sent tx and speeds up or cancels it. Returns when mined or timed out. May reset the nonce manager on timeout.
177
+ - `sendAndMonitorTransaction`: Combines sending and monitoring in a single call.
@@ -0,0 +1,140 @@
1
+ import {
2
+ type ConfigMappingsType,
3
+ bigintConfigHelper,
4
+ booleanConfigHelper,
5
+ getConfigFromMappings,
6
+ getDefaultConfig,
7
+ numberConfigHelper,
8
+ } from '@aztec/foundation/config';
9
+
10
+ export interface L1TxUtilsConfig {
11
+ /**
12
+ * How much to increase calculated gas limit.
13
+ */
14
+ gasLimitBufferPercentage?: number;
15
+ /**
16
+ * Maximum gas price in gwei
17
+ */
18
+ maxGwei?: bigint;
19
+ /**
20
+ * Maximum blob fee per gas in gwei
21
+ */
22
+ maxBlobGwei?: bigint;
23
+ /**
24
+ * Priority fee bump percentage
25
+ */
26
+ priorityFeeBumpPercentage?: number;
27
+ /**
28
+ * How much to increase priority fee by each attempt (percentage)
29
+ */
30
+ priorityFeeRetryBumpPercentage?: number;
31
+ /**
32
+ * Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage config
33
+ */
34
+ fixedPriorityFeePerGas?: number;
35
+ /**
36
+ * Maximum number of speed-up attempts
37
+ */
38
+ maxSpeedUpAttempts?: number;
39
+ /**
40
+ * How often to check tx status
41
+ */
42
+ checkIntervalMs?: number;
43
+ /**
44
+ * How long before considering tx stalled
45
+ */
46
+ stallTimeMs?: number;
47
+ /**
48
+ * How long to wait for a tx to be mined before giving up
49
+ */
50
+ txTimeoutMs?: number;
51
+ /**
52
+ * Whether to attempt to cancel a tx if it's not mined after txTimeoutMs
53
+ */
54
+ cancelTxOnTimeout?: boolean;
55
+ /**
56
+ * How long to wait for a cancellation tx to be mined after its last attempt before giving up
57
+ */
58
+ txCancellationFinalTimeoutMs?: number;
59
+ /**
60
+ * How long a tx nonce can be unseen in the mempool before considering it dropped
61
+ */
62
+ txUnseenConsideredDroppedMs?: number;
63
+ }
64
+
65
+ export const l1TxUtilsConfigMappings: ConfigMappingsType<L1TxUtilsConfig> = {
66
+ gasLimitBufferPercentage: {
67
+ description: 'How much to increase calculated gas limit by (percentage)',
68
+ env: 'L1_GAS_LIMIT_BUFFER_PERCENTAGE',
69
+ ...numberConfigHelper(20),
70
+ },
71
+ maxGwei: {
72
+ description: 'Maximum gas price in gwei',
73
+ env: 'L1_GAS_PRICE_MAX',
74
+ ...bigintConfigHelper(500n),
75
+ },
76
+ maxBlobGwei: {
77
+ description: 'Maximum blob fee per gas in gwei',
78
+ env: 'L1_BLOB_FEE_PER_GAS_MAX',
79
+ ...bigintConfigHelper(1_500n),
80
+ },
81
+ priorityFeeBumpPercentage: {
82
+ description: 'How much to increase priority fee by each attempt (percentage)',
83
+ env: 'L1_PRIORITY_FEE_BUMP_PERCENTAGE',
84
+ ...numberConfigHelper(20),
85
+ },
86
+ priorityFeeRetryBumpPercentage: {
87
+ description: 'How much to increase priority fee by each retry attempt (percentage)',
88
+ env: 'L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE',
89
+ ...numberConfigHelper(50),
90
+ },
91
+ fixedPriorityFeePerGas: {
92
+ description: 'Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage',
93
+ env: 'L1_FIXED_PRIORITY_FEE_PER_GAS',
94
+ ...numberConfigHelper(0),
95
+ },
96
+ maxSpeedUpAttempts: {
97
+ description: 'Maximum number of speed-up attempts',
98
+ env: 'L1_TX_MONITOR_MAX_ATTEMPTS',
99
+ ...numberConfigHelper(3),
100
+ },
101
+ checkIntervalMs: {
102
+ description: 'How often to check tx status',
103
+ env: 'L1_TX_MONITOR_CHECK_INTERVAL_MS',
104
+ ...numberConfigHelper(1_000),
105
+ },
106
+ stallTimeMs: {
107
+ description: 'How long before considering tx stalled',
108
+ env: 'L1_TX_MONITOR_STALL_TIME_MS',
109
+ ...numberConfigHelper(24_000), // 24s, 2 ethereum slots
110
+ },
111
+ txTimeoutMs: {
112
+ description: 'How long to wait for a tx to be mined before giving up. Set to 0 to disable.',
113
+ env: 'L1_TX_MONITOR_TX_TIMEOUT_MS',
114
+ ...numberConfigHelper(120_000), // 2 mins
115
+ },
116
+ cancelTxOnTimeout: {
117
+ description: "Whether to attempt to cancel a tx if it's not mined after txTimeoutMs",
118
+ env: 'L1_TX_MONITOR_CANCEL_TX_ON_TIMEOUT',
119
+ ...booleanConfigHelper(true),
120
+ },
121
+ txCancellationFinalTimeoutMs: {
122
+ description: 'How long to wait for a cancellation tx after its last attempt before giving up',
123
+ env: 'L1_TX_MONITOR_TX_CANCELLATION_TIMEOUT_MS',
124
+ ...numberConfigHelper(24 * 12 * 1000), // 24 L1 blocks
125
+ },
126
+ txUnseenConsideredDroppedMs: {
127
+ description: 'How long a tx nonce can be unseen in the mempool before considering it dropped',
128
+ env: 'L1_TX_MONITOR_TX_UNSEEN_CONSIDERED_DROPPED_MS',
129
+ ...numberConfigHelper(6 * 12 * 1000), // 6 L1 blocks
130
+ },
131
+ };
132
+
133
+ // We abuse the fact that all mappings above have a non null default value and force-type this to Required
134
+ export const defaultL1TxUtilsConfig = getDefaultConfig<L1TxUtilsConfig>(
135
+ l1TxUtilsConfigMappings,
136
+ ) as Required<L1TxUtilsConfig>;
137
+
138
+ export function getL1TxUtilsConfigEnvVars(): L1TxUtilsConfig {
139
+ return getConfigFromMappings(l1TxUtilsConfigMappings);
140
+ }
@@ -0,0 +1,18 @@
1
+ // 1_000_000_000 Gwei = 1 ETH
2
+ // 1_000_000_000 Wei = 1 Gwei
3
+ // 1_000_000_000_000_000_000 Wei = 1 ETH
4
+ export const WEI_CONST = 1_000_000_000n;
5
+
6
+ // @note using this large gas limit to avoid the issue of `gas limit too low` when estimating gas in reth
7
+ export const LARGE_GAS_LIMIT = 12_000_000n;
8
+
9
+ // setting a minimum bump percentage to 10% due to geth's implementation
10
+ // https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/legacypool/list.go#L298
11
+ export const MIN_REPLACEMENT_BUMP_PERCENTAGE = 10;
12
+
13
+ // setting a minimum bump percentage to 100% due to geth's implementation
14
+ // https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/blobpool/config.go#L34
15
+ export const MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE = 100;
16
+
17
+ // Avg ethereum block time is ~12s
18
+ export const BLOCK_TIME_MS = 12_000;
@@ -0,0 +1,64 @@
1
+ import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import type { Logger } from '@aztec/foundation/log';
3
+ import { DateProvider } from '@aztec/foundation/timer';
4
+
5
+ import type { TransactionSerializable } from 'viem';
6
+
7
+ import type { EthSigner } from '../eth-signer/eth-signer.js';
8
+ import type { ExtendedViemWalletClient, ViemClient } from '../types.js';
9
+ import type { L1TxUtilsConfig } from './config.js';
10
+ import type { IL1TxMetrics, IL1TxStore } from './interfaces.js';
11
+ import { L1TxUtils } from './l1_tx_utils.js';
12
+ import { createViemSigner } from './signer.js';
13
+ import type { SigningCallback } from './types.js';
14
+
15
+ export function createL1TxUtilsFromViemWallet(
16
+ client: ExtendedViemWalletClient,
17
+ deps?: {
18
+ logger?: Logger;
19
+ dateProvider?: DateProvider;
20
+ store?: IL1TxStore;
21
+ metrics?: IL1TxMetrics;
22
+ },
23
+ config?: Partial<L1TxUtilsConfig> & { debugMaxGasLimit?: boolean },
24
+ ): L1TxUtils {
25
+ return new L1TxUtils(
26
+ client,
27
+ EthAddress.fromString(client.account.address),
28
+ createViemSigner(client),
29
+ deps?.logger,
30
+ deps?.dateProvider,
31
+ config,
32
+ config?.debugMaxGasLimit ?? false,
33
+ deps?.store,
34
+ deps?.metrics,
35
+ );
36
+ }
37
+
38
+ export function createL1TxUtilsFromEthSigner(
39
+ client: ViemClient,
40
+ signer: EthSigner,
41
+ deps?: {
42
+ logger?: Logger;
43
+ dateProvider?: DateProvider;
44
+ store?: IL1TxStore;
45
+ metrics?: IL1TxMetrics;
46
+ },
47
+ config?: Partial<L1TxUtilsConfig> & { debugMaxGasLimit?: boolean },
48
+ ): L1TxUtils {
49
+ const callback: SigningCallback = async (transaction: TransactionSerializable, _signingAddress) => {
50
+ return (await signer.signTransaction(transaction)).toViemTransactionSignature();
51
+ };
52
+
53
+ return new L1TxUtils(
54
+ client,
55
+ signer.address,
56
+ callback,
57
+ deps?.logger,
58
+ deps?.dateProvider,
59
+ config,
60
+ config?.debugMaxGasLimit ?? false,
61
+ deps?.store,
62
+ deps?.metrics,
63
+ );
64
+ }
@@ -0,0 +1,12 @@
1
+ export * from './config.js';
2
+ export * from './constants.js';
3
+ export * from './factory.js';
4
+ export * from './interfaces.js';
5
+ export * from './l1_tx_utils.js';
6
+ export * from './readonly_l1_tx_utils.js';
7
+ export * from './signer.js';
8
+ export * from './types.js';
9
+ export * from './utils.js';
10
+
11
+ // Note: We intentionally do not export l1_tx_utils_with_blobs.js
12
+ // to avoid accidentally importing blob-lib dependency.