@aztec/ethereum 0.0.1-commit.358457c → 0.0.1-commit.381b1a9
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/config.d.ts +3 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +6 -0
- package/dest/contracts/registry.d.ts +3 -1
- package/dest/contracts/registry.d.ts.map +1 -1
- package/dest/contracts/registry.js +30 -1
- package/dest/contracts/rollup.d.ts +7 -2
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +34 -5
- package/dest/generated/l1-contracts-defaults.d.ts +2 -2
- package/dest/generated/l1-contracts-defaults.js +2 -2
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +4 -3
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +14 -32
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.js +8 -4
- package/dest/test/start_anvil.d.ts +16 -3
- package/dest/test/start_anvil.d.ts.map +1 -1
- package/dest/test/start_anvil.js +129 -29
- package/package.json +5 -5
- package/src/config.ts +12 -0
- package/src/contracts/registry.ts +31 -1
- package/src/contracts/rollup.ts +36 -11
- package/src/generated/l1-contracts-defaults.ts +2 -2
- package/src/l1_tx_utils/l1_tx_utils.ts +14 -21
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +8 -4
- package/src/test/start_anvil.ts +154 -29
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/** Minimal interface matching the @viem/anvil Anvil shape used by callers. */
|
|
2
|
+
export interface Anvil {
|
|
3
|
+
readonly port: number;
|
|
4
|
+
readonly host: string;
|
|
5
|
+
readonly status: 'listening' | 'idle';
|
|
6
|
+
stop(): Promise<void>;
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* Ensures there's a running Anvil instance and returns the RPC URL.
|
|
4
10
|
*/
|
|
@@ -9,12 +15,19 @@ export declare function startAnvil(opts?: {
|
|
|
9
15
|
captureMethodCalls?: boolean;
|
|
10
16
|
accounts?: number;
|
|
11
17
|
chainId?: number;
|
|
12
|
-
/** The hardfork to use
|
|
18
|
+
/** The hardfork to use (e.g. 'cancun', 'latest'). */
|
|
13
19
|
hardfork?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Number of slots per epoch used by anvil to compute the 'finalized' and 'safe' block tags.
|
|
22
|
+
* Anvil reports `finalized = latest - slotsInAnEpoch * 2`.
|
|
23
|
+
* Defaults to 1 so the finalized block advances immediately, making tests that check
|
|
24
|
+
* L1-finality-based logic work without needing hundreds of mined blocks.
|
|
25
|
+
*/
|
|
26
|
+
slotsInAnEpoch?: number;
|
|
14
27
|
}): Promise<{
|
|
15
28
|
anvil: Anvil;
|
|
16
29
|
methodCalls?: string[];
|
|
17
30
|
rpcUrl: string;
|
|
18
31
|
stop: () => Promise<void>;
|
|
19
32
|
}>;
|
|
20
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhcnRfYW52aWwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L3N0YXJ0X2FudmlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQU9BLDhFQUE4RTtBQUM5RSxNQUFNLFdBQVcsS0FBSztJQUNwQixRQUFRLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQztJQUN0QixRQUFRLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQztJQUN0QixRQUFRLENBQUMsTUFBTSxFQUFFLFdBQVcsR0FBRyxNQUFNLENBQUM7SUFDdEMsSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztDQUN2QjtBQUVEOztHQUVHO0FBQ0gsd0JBQXNCLFVBQVUsQ0FDOUIsSUFBSSxHQUFFO0lBQ0osSUFBSSxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ2QsV0FBVyxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ3JCLEdBQUcsQ0FBQyxFQUFFLE9BQU8sQ0FBQztJQUNkLGtCQUFrQixDQUFDLEVBQUUsT0FBTyxDQUFDO0lBQzdCLFFBQVEsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUNsQixPQUFPLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDakIscURBQXFEO0lBQ3JELFFBQVEsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUNsQjs7Ozs7T0FLRztJQUNILGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUNwQixHQUNMLE9BQU8sQ0FBQztJQUFFLEtBQUssRUFBRSxLQUFLLENBQUM7SUFBQyxXQUFXLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUFDLE1BQU0sRUFBRSxNQUFNLENBQUM7SUFBQyxJQUFJLEVBQUUsTUFBTSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7Q0FBRSxDQUFDLENBNEg5RiJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start_anvil.d.ts","sourceRoot":"","sources":["../../src/test/start_anvil.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"start_anvil.d.ts","sourceRoot":"","sources":["../../src/test/start_anvil.ts"],"names":[],"mappings":"AAOA,8EAA8E;AAC9E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,IAAI,GAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACpB,GACL,OAAO,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CA4H9F"}
|
package/dest/test/start_anvil.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
2
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
3
3
|
import { fileURLToPath } from '@aztec/foundation/url';
|
|
4
|
-
import {
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
5
|
import { dirname, resolve } from 'path';
|
|
6
6
|
/**
|
|
7
7
|
* Ensures there's a running Anvil instance and returns the RPC URL.
|
|
@@ -9,47 +9,147 @@ import { dirname, resolve } from 'path';
|
|
|
9
9
|
const anvilBinary = resolve(dirname(fileURLToPath(import.meta.url)), '../../', 'scripts/anvil_kill_wrapper.sh');
|
|
10
10
|
const logger = opts.log ? createLogger('ethereum:anvil') : undefined;
|
|
11
11
|
const methodCalls = opts.captureMethodCalls ? [] : undefined;
|
|
12
|
-
let
|
|
13
|
-
// Start anvil.
|
|
14
|
-
// We go via a wrapper script to ensure if the parent dies, anvil dies.
|
|
12
|
+
let detectedPort;
|
|
15
13
|
const anvil = await retry(async ()=>{
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
host
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
accounts
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
const port = opts.port ?? (process.env.ANVIL_PORT ? parseInt(process.env.ANVIL_PORT) : 8545);
|
|
15
|
+
const args = [
|
|
16
|
+
'--host',
|
|
17
|
+
'127.0.0.1',
|
|
18
|
+
'--port',
|
|
19
|
+
String(port),
|
|
20
|
+
'--accounts',
|
|
21
|
+
String(opts.accounts ?? 20),
|
|
22
|
+
'--gas-limit',
|
|
23
|
+
String(45_000_000),
|
|
24
|
+
'--chain-id',
|
|
25
|
+
String(opts.chainId ?? 31337)
|
|
26
|
+
];
|
|
27
|
+
if (opts.l1BlockTime !== undefined) {
|
|
28
|
+
args.push('--block-time', String(opts.l1BlockTime));
|
|
29
|
+
}
|
|
30
|
+
if (opts.hardfork !== undefined) {
|
|
31
|
+
args.push('--hardfork', opts.hardfork);
|
|
32
|
+
}
|
|
33
|
+
args.push('--slots-in-an-epoch', String(opts.slotsInAnEpoch ?? 1));
|
|
34
|
+
const child = spawn(anvilBinary, args, {
|
|
35
|
+
stdio: [
|
|
36
|
+
'ignore',
|
|
37
|
+
'pipe',
|
|
38
|
+
'pipe'
|
|
39
|
+
],
|
|
40
|
+
env: {
|
|
41
|
+
...process.env,
|
|
42
|
+
RAYON_NUM_THREADS: '1'
|
|
32
43
|
}
|
|
33
44
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
// Wait for "Listening on" or an early exit.
|
|
46
|
+
await new Promise((resolve, reject)=>{
|
|
47
|
+
let stderr = '';
|
|
48
|
+
const onStdout = (data)=>{
|
|
49
|
+
const text = data.toString();
|
|
50
|
+
logger?.debug(text.trim());
|
|
51
|
+
methodCalls?.push(...text.match(/eth_[^\s]+/g) || []);
|
|
52
|
+
if (detectedPort === undefined && text.includes('Listening on')) {
|
|
53
|
+
const match = text.match(/Listening on ([^:]+):(\d+)/);
|
|
54
|
+
if (match) {
|
|
55
|
+
detectedPort = parseInt(match[2]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (detectedPort !== undefined) {
|
|
59
|
+
child.stdout?.removeListener('data', onStdout);
|
|
60
|
+
child.stderr?.removeListener('data', onStderr);
|
|
61
|
+
child.removeListener('close', onClose);
|
|
62
|
+
resolve();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const onStderr = (data)=>{
|
|
66
|
+
stderr += data.toString();
|
|
67
|
+
logger?.debug(data.toString().trim());
|
|
68
|
+
};
|
|
69
|
+
const onClose = (code)=>{
|
|
70
|
+
child.stdout?.removeListener('data', onStdout);
|
|
71
|
+
child.stderr?.removeListener('data', onStderr);
|
|
72
|
+
reject(new Error(`Anvil exited with code ${code} before listening. stderr: ${stderr}`));
|
|
73
|
+
};
|
|
74
|
+
child.stdout?.on('data', onStdout);
|
|
75
|
+
child.stderr?.on('data', onStderr);
|
|
76
|
+
child.once('close', onClose);
|
|
77
|
+
});
|
|
78
|
+
// Continue piping for logging / method-call capture after startup.
|
|
79
|
+
if (logger || opts.captureMethodCalls) {
|
|
80
|
+
child.stdout?.on('data', (data)=>{
|
|
81
|
+
const text = data.toString();
|
|
82
|
+
logger?.debug(text.trim());
|
|
83
|
+
methodCalls?.push(...text.match(/eth_[^\s]+/g) || []);
|
|
84
|
+
});
|
|
85
|
+
child.stderr?.on('data', (data)=>{
|
|
86
|
+
logger?.debug(data.toString().trim());
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
// Consume streams so the child process doesn't block on full pipe buffers.
|
|
90
|
+
child.stdout?.resume();
|
|
91
|
+
child.stderr?.resume();
|
|
37
92
|
}
|
|
38
|
-
return
|
|
93
|
+
return child;
|
|
39
94
|
}, 'Start anvil', makeBackoff([
|
|
40
95
|
5,
|
|
41
96
|
5,
|
|
42
97
|
5
|
|
43
98
|
]));
|
|
44
|
-
if (!
|
|
99
|
+
if (!detectedPort) {
|
|
45
100
|
throw new Error('Failed to start anvil');
|
|
46
101
|
}
|
|
47
|
-
|
|
48
|
-
|
|
102
|
+
const port = detectedPort;
|
|
103
|
+
let status = 'listening';
|
|
104
|
+
anvil.once('close', ()=>{
|
|
105
|
+
status = 'idle';
|
|
106
|
+
});
|
|
107
|
+
const stop = async ()=>{
|
|
108
|
+
if (status === 'idle') {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
await killChild(anvil);
|
|
112
|
+
};
|
|
113
|
+
const anvilObj = {
|
|
114
|
+
port,
|
|
115
|
+
host: '127.0.0.1',
|
|
116
|
+
get status () {
|
|
117
|
+
return status;
|
|
118
|
+
},
|
|
119
|
+
stop
|
|
120
|
+
};
|
|
49
121
|
return {
|
|
50
|
-
anvil,
|
|
122
|
+
anvil: anvilObj,
|
|
51
123
|
methodCalls,
|
|
52
|
-
stop
|
|
124
|
+
stop,
|
|
53
125
|
rpcUrl: `http://127.0.0.1:${port}`
|
|
54
126
|
};
|
|
55
127
|
}
|
|
128
|
+
/** Send SIGTERM, wait up to 5 s, then SIGKILL. All timers are always cleared. */ function killChild(child) {
|
|
129
|
+
return new Promise((resolve)=>{
|
|
130
|
+
if (child.exitCode !== null || child.killed) {
|
|
131
|
+
child.stdout?.destroy();
|
|
132
|
+
child.stderr?.destroy();
|
|
133
|
+
resolve();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
let killTimer;
|
|
137
|
+
const onClose = ()=>{
|
|
138
|
+
if (killTimer !== undefined) {
|
|
139
|
+
clearTimeout(killTimer);
|
|
140
|
+
}
|
|
141
|
+
// Destroy stdio streams so their PipeWrap handles don't keep the event loop alive.
|
|
142
|
+
child.stdout?.destroy();
|
|
143
|
+
child.stderr?.destroy();
|
|
144
|
+
resolve();
|
|
145
|
+
};
|
|
146
|
+
child.once('close', onClose);
|
|
147
|
+
child.kill('SIGTERM');
|
|
148
|
+
killTimer = setTimeout(()=>{
|
|
149
|
+
killTimer = undefined;
|
|
150
|
+
child.kill('SIGKILL');
|
|
151
|
+
}, 5000);
|
|
152
|
+
// Ensure the timer does not prevent Node from exiting.
|
|
153
|
+
killTimer.unref();
|
|
154
|
+
});
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/ethereum",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.381b1a9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./account": "./dest/account.js",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"../package.common.json"
|
|
51
51
|
],
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@aztec/blob-lib": "0.0.1-commit.
|
|
54
|
-
"@aztec/constants": "0.0.1-commit.
|
|
55
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
56
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
53
|
+
"@aztec/blob-lib": "0.0.1-commit.381b1a9",
|
|
54
|
+
"@aztec/constants": "0.0.1-commit.381b1a9",
|
|
55
|
+
"@aztec/foundation": "0.0.1-commit.381b1a9",
|
|
56
|
+
"@aztec/l1-artifacts": "0.0.1-commit.381b1a9",
|
|
57
57
|
"@viem/anvil": "^0.0.10",
|
|
58
58
|
"dotenv": "^16.0.3",
|
|
59
59
|
"lodash.chunk": "^4.2.0",
|
package/src/config.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type GenesisStateConfig = {
|
|
|
19
19
|
testAccounts: boolean;
|
|
20
20
|
/** Whether to populate the genesis state with initial fee juice for the sponsored FPC */
|
|
21
21
|
sponsoredFPC: boolean;
|
|
22
|
+
/** Additional addresses to prefund with fee juice at genesis */
|
|
23
|
+
prefundAddresses: string[];
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export type L1ContractsConfig = {
|
|
@@ -259,6 +261,16 @@ export const genesisStateConfigMappings: ConfigMappingsType<GenesisStateConfig>
|
|
|
259
261
|
description: 'Whether to populate the genesis state with initial fee juice for the sponsored FPC.',
|
|
260
262
|
...booleanConfigHelper(false),
|
|
261
263
|
},
|
|
264
|
+
prefundAddresses: {
|
|
265
|
+
env: 'PREFUND_ADDRESSES',
|
|
266
|
+
description: 'Comma-separated list of Aztec addresses to prefund with fee juice at genesis.',
|
|
267
|
+
parseEnv: (val: string) =>
|
|
268
|
+
val
|
|
269
|
+
.split(',')
|
|
270
|
+
.map(a => a.trim())
|
|
271
|
+
.filter(a => a.length > 0),
|
|
272
|
+
defaultValue: [],
|
|
273
|
+
},
|
|
262
274
|
};
|
|
263
275
|
|
|
264
276
|
export function getL1ContractsConfigEnvVars(): L1ContractsConfig {
|
|
@@ -3,7 +3,7 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
3
3
|
import { RegistryAbi } from '@aztec/l1-artifacts/RegistryAbi';
|
|
4
4
|
import { TestERC20Abi } from '@aztec/l1-artifacts/TestERC20Abi';
|
|
5
5
|
|
|
6
|
-
import { type GetContractReturnType, type Hex, getContract } from 'viem';
|
|
6
|
+
import { type GetContractReturnType, type Hex, getAbiItem, getContract } from 'viem';
|
|
7
7
|
|
|
8
8
|
import type { L1ContractAddresses } from '../l1_contract_addresses.js';
|
|
9
9
|
import type { ViemClient } from '../types.js';
|
|
@@ -128,4 +128,34 @@ export class RegistryContract {
|
|
|
128
128
|
public async getRewardDistributor(): Promise<EthAddress> {
|
|
129
129
|
return EthAddress.fromString(await this.registry.read.getRewardDistributor());
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
/** Returns the L1 timestamp at which the given rollup was registered via addRollup(). */
|
|
133
|
+
public async getCanonicalRollupRegistrationTimestamp(
|
|
134
|
+
rollupAddress: EthAddress,
|
|
135
|
+
fromBlock?: bigint,
|
|
136
|
+
): Promise<bigint | undefined> {
|
|
137
|
+
const event = getAbiItem({ abi: RegistryAbi, name: 'CanonicalRollupUpdated' });
|
|
138
|
+
const start = fromBlock ?? 0n;
|
|
139
|
+
const latestBlock = await this.client.getBlockNumber();
|
|
140
|
+
const chunkSize = 1_000n;
|
|
141
|
+
|
|
142
|
+
for (let from = start; from <= latestBlock; from += chunkSize) {
|
|
143
|
+
const to = from + chunkSize - 1n > latestBlock ? latestBlock : from + chunkSize - 1n;
|
|
144
|
+
const logs = await this.client.getLogs({
|
|
145
|
+
address: this.address.toString(),
|
|
146
|
+
fromBlock: from,
|
|
147
|
+
toBlock: to,
|
|
148
|
+
strict: true,
|
|
149
|
+
event,
|
|
150
|
+
args: { instance: rollupAddress.toString() },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (logs.length > 0) {
|
|
154
|
+
const block = await this.client.getBlock({ blockNumber: logs[0].blockNumber });
|
|
155
|
+
return block.timestamp;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
131
161
|
}
|
package/src/contracts/rollup.ts
CHANGED
|
@@ -379,6 +379,20 @@ export class RollupContract {
|
|
|
379
379
|
return Fr.fromString(await this.rollup.read.archiveAt([0n]));
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
@memoize
|
|
383
|
+
async getVkTreeRoot(): Promise<Fr> {
|
|
384
|
+
const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
|
|
385
|
+
const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
|
|
386
|
+
return Fr.fromString(value ?? '0x0');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@memoize
|
|
390
|
+
async getProtocolContractsHash(): Promise<Fr> {
|
|
391
|
+
const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
|
|
392
|
+
const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
|
|
393
|
+
return Fr.fromString(value ?? '0x0');
|
|
394
|
+
}
|
|
395
|
+
|
|
382
396
|
/**
|
|
383
397
|
* Returns rollup constants used for epoch queries.
|
|
384
398
|
* Return type is `L1RollupConstants` which is defined in stdlib,
|
|
@@ -392,16 +406,25 @@ export class RollupContract {
|
|
|
392
406
|
epochDuration: number;
|
|
393
407
|
proofSubmissionEpochs: number;
|
|
394
408
|
targetCommitteeSize: number;
|
|
409
|
+
rollupManaLimit: number;
|
|
395
410
|
}> {
|
|
396
|
-
const [
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
411
|
+
const [
|
|
412
|
+
l1StartBlock,
|
|
413
|
+
l1GenesisTime,
|
|
414
|
+
slotDuration,
|
|
415
|
+
epochDuration,
|
|
416
|
+
proofSubmissionEpochs,
|
|
417
|
+
targetCommitteeSize,
|
|
418
|
+
rollupManaLimit,
|
|
419
|
+
] = await Promise.all([
|
|
420
|
+
this.getL1StartBlock(),
|
|
421
|
+
this.getL1GenesisTime(),
|
|
422
|
+
this.getSlotDuration(),
|
|
423
|
+
this.getEpochDuration(),
|
|
424
|
+
this.getProofSubmissionEpochs(),
|
|
425
|
+
this.getTargetCommitteeSize(),
|
|
426
|
+
this.getManaLimit(),
|
|
427
|
+
]);
|
|
405
428
|
return {
|
|
406
429
|
l1StartBlock,
|
|
407
430
|
l1GenesisTime,
|
|
@@ -409,6 +432,7 @@ export class RollupContract {
|
|
|
409
432
|
epochDuration: Number(epochDuration),
|
|
410
433
|
proofSubmissionEpochs: Number(proofSubmissionEpochs),
|
|
411
434
|
targetCommitteeSize,
|
|
435
|
+
rollupManaLimit: Number(rollupManaLimit),
|
|
412
436
|
};
|
|
413
437
|
}
|
|
414
438
|
|
|
@@ -503,8 +527,9 @@ export class RollupContract {
|
|
|
503
527
|
return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber());
|
|
504
528
|
}
|
|
505
529
|
|
|
506
|
-
async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
|
|
507
|
-
|
|
530
|
+
async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
531
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
532
|
+
return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
|
|
508
533
|
}
|
|
509
534
|
|
|
510
535
|
async getSlotNumber(): Promise<SlotNumber> {
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
/** Default L1 contracts configuration values from network-defaults.yml */
|
|
5
5
|
export const l1ContractsDefaultEnv = {
|
|
6
6
|
ETHEREUM_SLOT_DURATION: 12,
|
|
7
|
-
AZTEC_SLOT_DURATION:
|
|
7
|
+
AZTEC_SLOT_DURATION: 72,
|
|
8
8
|
AZTEC_EPOCH_DURATION: 32,
|
|
9
9
|
AZTEC_TARGET_COMMITTEE_SIZE: 48,
|
|
10
10
|
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET: 2,
|
|
11
|
-
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO:
|
|
11
|
+
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO: 1,
|
|
12
12
|
AZTEC_ACTIVATION_THRESHOLD: 100000000000000000000,
|
|
13
13
|
AZTEC_EJECTION_THRESHOLD: 50000000000000000000,
|
|
14
14
|
AZTEC_LOCAL_EJECTION_THRESHOLD: 98000000000000000000,
|
|
@@ -14,16 +14,13 @@ import {
|
|
|
14
14
|
type Abi,
|
|
15
15
|
type BlockOverrides,
|
|
16
16
|
type Hex,
|
|
17
|
-
type NonceManager,
|
|
18
17
|
type PrepareTransactionRequestRequest,
|
|
19
18
|
type StateOverride,
|
|
20
19
|
type TransactionReceipt,
|
|
21
20
|
type TransactionSerializable,
|
|
22
|
-
createNonceManager,
|
|
23
21
|
formatGwei,
|
|
24
22
|
serializeTransaction,
|
|
25
23
|
} from 'viem';
|
|
26
|
-
import { jsonRpc } from 'viem/nonce';
|
|
27
24
|
|
|
28
25
|
import type { ViemClient } from '../types.js';
|
|
29
26
|
import { formatViemError } from '../utils.js';
|
|
@@ -47,8 +44,9 @@ import {
|
|
|
47
44
|
const MAX_L1_TX_STATES = 32;
|
|
48
45
|
|
|
49
46
|
export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
50
|
-
protected nonceManager: NonceManager;
|
|
51
47
|
protected txs: L1TxState[] = [];
|
|
48
|
+
/** Last nonce successfully sent to the chain. Used as a lower bound when a fallback RPC node returns a stale count. */
|
|
49
|
+
private lastSentNonce: number | undefined;
|
|
52
50
|
/** Tx delayer for testing. Only set when enableDelayer config is true. */
|
|
53
51
|
public delayer?: Delayer;
|
|
54
52
|
/** KZG instance for blob operations. */
|
|
@@ -68,7 +66,6 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
68
66
|
delayer?: Delayer,
|
|
69
67
|
) {
|
|
70
68
|
super(client, logger, dateProvider, config, debugMaxGasLimit);
|
|
71
|
-
this.nonceManager = createNonceManager({ source: jsonRpc() });
|
|
72
69
|
this.kzg = kzg;
|
|
73
70
|
|
|
74
71
|
// Set up delayer: use provided one or create new
|
|
@@ -110,6 +107,11 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
110
107
|
this.metrics?.recordMinedTx(l1TxState, new Date(l1Timestamp));
|
|
111
108
|
} else if (newState === TxUtilsState.NOT_MINED) {
|
|
112
109
|
this.metrics?.recordDroppedTx(l1TxState);
|
|
110
|
+
// The tx was dropped: the chain nonce reverted to l1TxState.nonce, so our lower bound is
|
|
111
|
+
// no longer valid. Clear it so the next send fetches the real nonce from the chain.
|
|
112
|
+
if (this.lastSentNonce === l1TxState.nonce) {
|
|
113
|
+
this.lastSentNonce = undefined;
|
|
114
|
+
}
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
// Update state in the store
|
|
@@ -244,9 +246,6 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
244
246
|
throw new InterruptError(`Transaction sending is interrupted`);
|
|
245
247
|
}
|
|
246
248
|
|
|
247
|
-
// Check timeout before consuming nonce to avoid leaking a nonce that was never sent.
|
|
248
|
-
// A leaked nonce creates a gap (e.g. nonce 107 consumed but unsent), so all subsequent
|
|
249
|
-
// transactions (108, 109, ...) can never be mined since the chain expects 107 first.
|
|
250
249
|
const now = new Date(await this.getL1Timestamp());
|
|
251
250
|
if (gasConfig.txTimeoutAt && now > gasConfig.txTimeoutAt) {
|
|
252
251
|
throw new TimeoutError(
|
|
@@ -254,11 +253,11 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
254
253
|
);
|
|
255
254
|
}
|
|
256
255
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
256
|
+
const chainNonce = await this.client.getTransactionCount({ address: account, blockTag: 'pending' });
|
|
257
|
+
// If a fallback RPC node returns a stale count (lower than what we last sent), use our
|
|
258
|
+
// local lower bound to avoid sending a duplicate of an already-pending transaction.
|
|
259
|
+
const nonce =
|
|
260
|
+
this.lastSentNonce !== undefined && chainNonce <= this.lastSentNonce ? this.lastSentNonce + 1 : chainNonce;
|
|
262
261
|
|
|
263
262
|
const baseState = { request, gasLimit, blobInputs, gasPrice, nonce };
|
|
264
263
|
const txData = this.makeTxData(baseState, { isCancelTx: false });
|
|
@@ -266,6 +265,8 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
266
265
|
// Send the new tx
|
|
267
266
|
const signedRequest = await this.prepareSignedTransaction(txData);
|
|
268
267
|
const txHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
|
|
268
|
+
// Update after tx is sent successfully
|
|
269
|
+
this.lastSentNonce = nonce;
|
|
269
270
|
|
|
270
271
|
// Create the new state for monitoring
|
|
271
272
|
const l1TxState: L1TxState = {
|
|
@@ -449,7 +450,6 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
449
450
|
{ nonce, account, pendingNonce, timePassed },
|
|
450
451
|
);
|
|
451
452
|
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
452
|
-
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
453
453
|
throw new DroppedTransactionError(nonce, account);
|
|
454
454
|
}
|
|
455
455
|
|
|
@@ -541,12 +541,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
541
541
|
|
|
542
542
|
// Oh no, the transaction has timed out!
|
|
543
543
|
if (isCancelTx || !gasConfig.cancelTxOnTimeout) {
|
|
544
|
-
// If this was already a cancellation tx, or we are configured to not cancel txs, we just mark it as NOT_MINED
|
|
545
|
-
// and reset the nonce manager, so the next tx that comes along can reuse the nonce if/when this tx gets dropped.
|
|
546
|
-
// This is the nastiest scenario for us, since the new tx could acquire the next nonce, but then this tx is dropped,
|
|
547
|
-
// and the new tx would never get mined. Eventually, the new tx would also drop.
|
|
548
544
|
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
549
|
-
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
550
545
|
} else {
|
|
551
546
|
// Otherwise we fire the cancellation without awaiting to avoid blocking the caller,
|
|
552
547
|
// and monitor it in the background so we can speed it up as needed.
|
|
@@ -685,7 +680,6 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
685
680
|
{ nonce, account },
|
|
686
681
|
);
|
|
687
682
|
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
688
|
-
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
689
683
|
return;
|
|
690
684
|
}
|
|
691
685
|
|
|
@@ -697,7 +691,6 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
|
|
|
697
691
|
{ nonce, account, currentNonce },
|
|
698
692
|
);
|
|
699
693
|
await this.updateState(state, TxUtilsState.NOT_MINED);
|
|
700
|
-
this.nonceManager.reset({ address: account, chainId: this.client.chain.id });
|
|
701
694
|
return;
|
|
702
695
|
}
|
|
703
696
|
|
|
@@ -130,9 +130,10 @@ export class ReadOnlyL1TxUtils {
|
|
|
130
130
|
const numBlocks = Math.ceil(gasConfig.stallTimeMs! / BLOCK_TIME_MS);
|
|
131
131
|
for (let i = 0; i < numBlocks; i++) {
|
|
132
132
|
// each block can go up 12.5% from previous baseFee
|
|
133
|
-
|
|
133
|
+
// ceil, (a+b-1)/b, to avoid truncation at small values (e.g. 1 wei blob base fee)
|
|
134
|
+
maxFeePerGas = (maxFeePerGas * (1_000n + 125n) + 999n) / 1_000n;
|
|
134
135
|
// same for blob gas fee
|
|
135
|
-
maxFeePerBlobGas = (maxFeePerBlobGas * (1_000n + 125n)) / 1_000n;
|
|
136
|
+
maxFeePerBlobGas = (maxFeePerBlobGas * (1_000n + 125n) + 999n) / 1_000n;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
if (attempt > 0) {
|
|
@@ -242,13 +243,16 @@ export class ReadOnlyL1TxUtils {
|
|
|
242
243
|
const gasConfig = { ...this.config, ..._gasConfig };
|
|
243
244
|
let initialEstimate = 0n;
|
|
244
245
|
if (_blobInputs) {
|
|
245
|
-
// @note requests with blobs also require maxFeePerBlobGas to be set
|
|
246
|
+
// @note requests with blobs also require maxFeePerBlobGas to be set.
|
|
247
|
+
// Use 2x buffer for maxFeePerBlobGas to avoid stale fees and to pass EIP-4844 validation (even if it is a gas estimation call).
|
|
248
|
+
// 1. maxFeePerBlobGas >= blobBaseFee
|
|
249
|
+
// 2. account balance >= gas * maxFeePerGas + maxFeePerBlobGas * blobCount + value
|
|
246
250
|
const gasPrice = await this.getGasPrice(gasConfig, true, 0);
|
|
247
251
|
initialEstimate = await this.client.estimateGas({
|
|
248
252
|
account,
|
|
249
253
|
...request,
|
|
250
254
|
..._blobInputs,
|
|
251
|
-
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas
|
|
255
|
+
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas! * 2n,
|
|
252
256
|
gas: MAX_L1_TX_LIMIT,
|
|
253
257
|
blockTag: 'latest',
|
|
254
258
|
});
|