@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.
- package/dest/client.d.ts +1 -1
- package/dest/client.d.ts.map +1 -1
- package/dest/config.d.ts +11 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +124 -64
- package/dest/contracts/empire_base.d.ts +1 -1
- package/dest/contracts/empire_base.d.ts.map +1 -1
- package/dest/contracts/empire_slashing_proposer.d.ts +2 -2
- package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
- package/dest/contracts/empire_slashing_proposer.js +1 -1
- package/dest/contracts/fee_asset_handler.d.ts +3 -3
- package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
- package/dest/contracts/governance.js +7 -3
- package/dest/contracts/governance_proposer.d.ts +1 -2
- package/dest/contracts/governance_proposer.d.ts.map +1 -1
- package/dest/contracts/governance_proposer.js +1 -2
- package/dest/contracts/multicall.d.ts +3 -5
- package/dest/contracts/multicall.d.ts.map +1 -1
- package/dest/contracts/multicall.js +6 -4
- package/dest/contracts/rollup.d.ts +39 -19
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +84 -88
- package/dest/contracts/slasher_contract.d.ts +10 -0
- package/dest/contracts/slasher_contract.d.ts.map +1 -1
- package/dest/contracts/slasher_contract.js +18 -0
- package/dest/contracts/tally_slashing_proposer.d.ts +22 -3
- package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -1
- package/dest/contracts/tally_slashing_proposer.js +55 -5
- package/dest/deploy_l1_contracts.d.ts +22 -7
- package/dest/deploy_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_l1_contracts.js +555 -362
- package/dest/index.d.ts +1 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/l1_artifacts.d.ts +8729 -6014
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/dest/l1_artifacts.js +10 -5
- package/dest/l1_contract_addresses.d.ts +5 -1
- package/dest/l1_contract_addresses.d.ts.map +1 -1
- package/dest/l1_contract_addresses.js +16 -26
- package/dest/l1_reader.d.ts +1 -1
- package/dest/l1_reader.d.ts.map +1 -1
- package/dest/l1_reader.js +8 -8
- package/dest/l1_tx_utils/config.d.ts +59 -0
- package/dest/l1_tx_utils/config.d.ts.map +1 -0
- package/dest/l1_tx_utils/config.js +73 -0
- package/dest/l1_tx_utils/constants.d.ts +6 -0
- package/dest/l1_tx_utils/constants.d.ts.map +1 -0
- package/dest/l1_tx_utils/constants.js +14 -0
- package/dest/l1_tx_utils/factory.d.ts +24 -0
- package/dest/l1_tx_utils/factory.d.ts.map +1 -0
- package/dest/l1_tx_utils/factory.js +12 -0
- package/dest/l1_tx_utils/index.d.ts +10 -0
- package/dest/l1_tx_utils/index.d.ts.map +1 -0
- package/dest/l1_tx_utils/index.js +10 -0
- package/dest/l1_tx_utils/interfaces.d.ts +76 -0
- package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
- package/dest/l1_tx_utils/interfaces.js +4 -0
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +95 -0
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
- package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
- package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +81 -0
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
- package/dest/l1_tx_utils/readonly_l1_tx_utils.js +294 -0
- package/dest/l1_tx_utils/signer.d.ts +4 -0
- package/dest/l1_tx_utils/signer.d.ts.map +1 -0
- package/dest/l1_tx_utils/signer.js +16 -0
- package/dest/l1_tx_utils/types.d.ts +67 -0
- package/dest/l1_tx_utils/types.d.ts.map +1 -0
- package/dest/l1_tx_utils/types.js +26 -0
- package/dest/l1_tx_utils/utils.d.ts +4 -0
- package/dest/l1_tx_utils/utils.d.ts.map +1 -0
- package/dest/l1_tx_utils/utils.js +14 -0
- package/dest/publisher_manager.d.ts +7 -2
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +36 -8
- package/dest/queries.d.ts.map +1 -1
- package/dest/queries.js +11 -12
- package/dest/test/chain_monitor.d.ts +11 -0
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +81 -12
- package/dest/test/delayed_tx_utils.d.ts +2 -2
- package/dest/test/delayed_tx_utils.d.ts.map +1 -1
- package/dest/test/delayed_tx_utils.js +2 -2
- package/dest/test/eth_cheat_codes.d.ts +32 -6
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +115 -28
- package/dest/test/rollup_cheat_codes.d.ts +11 -9
- package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
- package/dest/test/rollup_cheat_codes.js +38 -6
- package/dest/test/upgrade_utils.d.ts.map +1 -1
- package/dest/test/upgrade_utils.js +3 -2
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +10 -161
- package/dest/zkPassportVerifierAddress.js +1 -1
- package/package.json +7 -7
- package/src/client.ts +1 -1
- package/src/config.ts +136 -68
- package/src/contracts/empire_base.ts +1 -1
- package/src/contracts/empire_slashing_proposer.ts +7 -3
- package/src/contracts/fee_asset_handler.ts +1 -1
- package/src/contracts/governance.ts +3 -3
- package/src/contracts/governance_proposer.ts +3 -4
- package/src/contracts/multicall.ts +12 -10
- package/src/contracts/rollup.ts +104 -106
- package/src/contracts/slasher_contract.ts +22 -0
- package/src/contracts/tally_slashing_proposer.ts +54 -6
- package/src/deploy_l1_contracts.ts +570 -328
- package/src/index.ts +1 -1
- package/src/l1_artifacts.ts +14 -6
- package/src/l1_contract_addresses.ts +17 -26
- package/src/l1_reader.ts +9 -9
- package/src/l1_tx_utils/README.md +177 -0
- package/src/l1_tx_utils/config.ts +140 -0
- package/src/l1_tx_utils/constants.ts +18 -0
- package/src/l1_tx_utils/factory.ts +64 -0
- package/src/l1_tx_utils/index.ts +12 -0
- package/src/l1_tx_utils/interfaces.ts +86 -0
- package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
- package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +372 -0
- package/src/l1_tx_utils/signer.ts +28 -0
- package/src/l1_tx_utils/types.ts +85 -0
- package/src/l1_tx_utils/utils.ts +16 -0
- package/src/publisher_manager.ts +51 -9
- package/src/queries.ts +13 -8
- package/src/test/chain_monitor.ts +89 -9
- package/src/test/delayed_tx_utils.ts +2 -2
- package/src/test/eth_cheat_codes.ts +142 -29
- package/src/test/rollup_cheat_codes.ts +54 -14
- package/src/test/upgrade_utils.ts +3 -2
- package/src/utils.ts +13 -185
- package/src/zkPassportVerifierAddress.ts +1 -1
- package/dest/l1_tx_utils.d.ts +0 -250
- package/dest/l1_tx_utils.d.ts.map +0 -1
- package/dest/l1_tx_utils.js +0 -826
- package/dest/l1_tx_utils_with_blobs.d.ts +0 -19
- package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
- package/dest/l1_tx_utils_with_blobs.js +0 -85
- package/src/l1_tx_utils.ts +0 -1105
- 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';
|
package/src/l1_artifacts.ts
CHANGED
|
@@ -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
|
-
|
|
104
|
-
name: '
|
|
105
|
-
contractAbi:
|
|
106
|
-
contractBytecode:
|
|
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
|
-
/**
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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.
|