@aztec-labs/ethereum 6.0.0-rc.3 → 6.0.0
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/blob_tx.d.ts +24 -0
- package/dest/blob_tx.d.ts.map +1 -0
- package/dest/blob_tx.js +94 -0
- package/dest/bn254/hash_to_point.d.ts +32 -0
- package/dest/bn254/hash_to_point.d.ts.map +1 -0
- package/dest/bn254/hash_to_point.js +80 -0
- package/dest/bn254/index.d.ts +3 -0
- package/dest/bn254/index.d.ts.map +1 -0
- package/dest/bn254/index.js +2 -0
- package/dest/bn254/proof_of_possession_gas.d.ts +29 -0
- package/dest/bn254/proof_of_possession_gas.d.ts.map +1 -0
- package/dest/bn254/proof_of_possession_gas.js +24 -0
- package/dest/client.d.ts +1 -1
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +5 -3
- package/dest/contracts/errors.d.ts +10 -3
- package/dest/contracts/errors.d.ts.map +1 -1
- package/dest/contracts/errors.js +10 -4
- package/dest/contracts/inbox.d.ts +1 -1
- package/dest/contracts/inbox.d.ts.map +1 -1
- package/dest/contracts/inbox.js +2 -0
- package/dest/contracts/log.d.ts +7 -1
- package/dest/contracts/log.d.ts.map +1 -1
- package/dest/contracts/log.js +6 -0
- package/dest/contracts/rollup.d.ts +32 -4
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +63 -7
- package/dest/deploy_aztec_l1_contracts.d.ts +16 -1
- package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_aztec_l1_contracts.js +201 -65
- package/dest/foundry_binary.d.ts +1 -1
- package/dest/foundry_binary.d.ts.map +1 -1
- package/dest/foundry_binary.js +2 -1
- package/dest/l1_tx_utils/amsterdam.d.ts +1 -1
- package/dest/l1_tx_utils/amsterdam.d.ts.map +1 -1
- package/dest/l1_tx_utils/amsterdam.js +4 -1
- package/dest/l1_tx_utils/constants.d.ts +2 -1
- package/dest/l1_tx_utils/constants.d.ts.map +1 -1
- package/dest/l1_tx_utils/constants.js +3 -0
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +1 -1
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -1
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +6 -1
- package/dest/l1_tx_utils/index.d.ts +2 -1
- package/dest/l1_tx_utils/index.d.ts.map +1 -1
- package/dest/l1_tx_utils/index.js +1 -0
- package/dest/l1_tx_utils/l1_tx_utils.d.ts +21 -1
- package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/l1_tx_utils.js +71 -5
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +8 -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 +1 -1
- package/dest/l1_tx_utils/send_cost.d.ts +15 -0
- package/dest/l1_tx_utils/send_cost.d.ts.map +1 -0
- package/dest/l1_tx_utils/send_cost.js +10 -0
- package/dest/l1_tx_utils/signer.d.ts +2 -2
- package/dest/l1_tx_utils/signer.d.ts.map +1 -1
- package/dest/l1_tx_utils/signer.js +2 -2
- package/dest/l1_tx_utils/tx_delayer.d.ts +1 -1
- package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -1
- package/dest/l1_tx_utils/tx_delayer.js +8 -26
- package/dest/publisher_manager.d.ts +127 -13
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +320 -79
- package/dest/test/anvil_hardfork.d.ts +42 -0
- package/dest/test/anvil_hardfork.d.ts.map +1 -0
- package/dest/test/anvil_hardfork.js +107 -0
- package/dest/test/eth_cheat_codes.d.ts +30 -5
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +283 -101
- package/dest/test/index.d.ts +3 -1
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +2 -0
- package/dest/test/l1_hardfork.d.ts +43 -0
- package/dest/test/l1_hardfork.d.ts.map +1 -0
- package/dest/test/l1_hardfork.js +51 -0
- package/dest/test/recording_rpc_server.d.ts +1 -1
- package/dest/test/recording_rpc_server.d.ts.map +1 -1
- package/dest/test/recording_rpc_server.js +1 -0
- package/dest/test/rollup_cheat_codes.d.ts +1 -1
- package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
- package/dest/test/rollup_cheat_codes.js +7 -7
- package/dest/test/start_anvil.d.ts +14 -2
- package/dest/test/start_anvil.d.ts.map +1 -1
- package/dest/test/start_anvil.js +28 -11
- package/package.json +13 -11
- package/src/blob_tx.ts +118 -0
- package/src/bn254/hash_to_point.ts +95 -0
- package/src/bn254/index.ts +2 -0
- package/src/bn254/proof_of_possession_gas.ts +54 -0
- package/src/client.ts +6 -3
- package/src/contracts/errors.ts +12 -4
- package/src/contracts/inbox.ts +4 -0
- package/src/contracts/log.ts +12 -0
- package/src/contracts/rollup.ts +63 -6
- package/src/deploy_aztec_l1_contracts.ts +238 -63
- package/src/foundry_binary.ts +2 -1
- package/src/l1_tx_utils/amsterdam.ts +4 -1
- package/src/l1_tx_utils/constants.ts +4 -0
- package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +11 -1
- package/src/l1_tx_utils/index.ts +1 -0
- package/src/l1_tx_utils/l1_tx_utils.ts +72 -5
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +1 -1
- package/src/l1_tx_utils/send_cost.ts +21 -0
- package/src/l1_tx_utils/signer.ts +3 -2
- package/src/l1_tx_utils/tx_delayer.ts +8 -37
- package/src/publisher_manager.ts +458 -83
- package/src/test/anvil_hardfork.ts +95 -0
- package/src/test/eth_cheat_codes.ts +306 -81
- package/src/test/fixtures/sepolia_blob_tx.json +10 -0
- package/src/test/index.ts +2 -0
- package/src/test/l1_hardfork.ts +71 -0
- package/src/test/recording_rpc_server.ts +1 -0
- package/src/test/rollup_cheat_codes.ts +9 -9
- package/src/test/start_anvil.ts +41 -13
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { gunzipSync } from 'node:zlib';
|
|
5
|
+
import { type EIP1193RequestFn, type Hex, createPublicClient, http } from 'viem';
|
|
6
|
+
|
|
7
|
+
import { resolveFoundryBinary } from '../foundry_binary.js';
|
|
8
|
+
import { isAmsterdamBlock } from '../l1_tx_utils/amsterdam.js';
|
|
9
|
+
import type { EthCheatCodes } from './eth_cheat_codes.js';
|
|
10
|
+
import { type Anvil, startAnvil } from './start_anvil.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Starts a throwaway anvil on `hardfork` and checks that it mines blocks of that fork, so a test that needs a fork
|
|
14
|
+
* the installed foundry cannot run fails within seconds instead of after its whole setup. Never skips.
|
|
15
|
+
*/
|
|
16
|
+
export async function assertAnvilSupportsHardfork(hardfork: string): Promise<void> {
|
|
17
|
+
let res: Awaited<ReturnType<typeof startAnvil>> | undefined;
|
|
18
|
+
try {
|
|
19
|
+
res = await startAnvil({ port: 0, hardfork });
|
|
20
|
+
const client = createPublicClient({ transport: http(res.rpcUrl, { batch: false }) });
|
|
21
|
+
await client.request({ method: 'evm_mine' as any, params: [] as any });
|
|
22
|
+
const block = await client.getBlock({ blockTag: 'latest' });
|
|
23
|
+
if (isAmsterdamBlock(block) !== (hardfork === 'amsterdam')) {
|
|
24
|
+
throw new Error(`anvil --hardfork ${hardfork} mined block ${block.number} with the wrong header shape`);
|
|
25
|
+
}
|
|
26
|
+
} catch (err) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Anvil at ${resolveFoundryBinary('anvil')} cannot run --hardfork ${hardfork}; foundry >= 1.8.5 is required`,
|
|
29
|
+
{ cause: err },
|
|
30
|
+
);
|
|
31
|
+
} finally {
|
|
32
|
+
await res?.stop();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Writes anvil's state, including the historical states it still keeps, as the plain JSON file `--load-state` reads.
|
|
38
|
+
* Without history, reads at blocks before a restart fail on the reloaded anvil. A dump with history easily exceeds
|
|
39
|
+
* viem's default response size limit, so `client` must be one built without it, such as `createExtendedL1Client`'s.
|
|
40
|
+
*/
|
|
41
|
+
export async function dumpAnvilStateWithHistory(client: { request: EIP1193RequestFn }, path: string): Promise<void> {
|
|
42
|
+
const hex = await client.request<{ Method: 'anvil_dumpState'; Parameters: [boolean]; ReturnType: Hex }>({
|
|
43
|
+
method: 'anvil_dumpState',
|
|
44
|
+
params: [true],
|
|
45
|
+
});
|
|
46
|
+
const dump = Buffer.from(hex.slice(2), 'hex');
|
|
47
|
+
const isGzip = dump[0] === 0x1f && dump[1] === 0x8b;
|
|
48
|
+
// Kept as a Buffer: a dump with history can exceed the maximum length of a V8 string.
|
|
49
|
+
await writeFile(path, isGzip ? gunzipSync(dump) : dump);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Moves an interval-mining anvil to another hardfork, continuing its chain. Anvil cannot change hardfork at runtime,
|
|
54
|
+
* so this stops mining, dumps the state with history to `stateDir` using `dumpClient`, stops `anvil`, and starts a new
|
|
55
|
+
* anvil on the same port from the dump with `startOpts`. Mining stays stopped until the old anvil is gone, so no block
|
|
56
|
+
* mined after the dump is lost. Pending txs are lost, since the mempool is not part of the dump.
|
|
57
|
+
*
|
|
58
|
+
* The reloaded anvil would stamp its next block as if no time had passed since the dump, so the first block on the new
|
|
59
|
+
* hardfork is mined at the first L1 slot of the old chain's grid that is not in the past, and interval mining resumes
|
|
60
|
+
* from there.
|
|
61
|
+
*
|
|
62
|
+
* @returns The new anvil, which replaces `anvil`, and the first block mined on `hardfork`.
|
|
63
|
+
*/
|
|
64
|
+
export async function restartAnvilOnHardfork(
|
|
65
|
+
anvil: Anvil,
|
|
66
|
+
cheat: EthCheatCodes,
|
|
67
|
+
dumpClient: { request: EIP1193RequestFn },
|
|
68
|
+
opts: { hardfork: string; stateDir: string; startOpts?: Parameters<typeof startAnvil>[0] },
|
|
69
|
+
): Promise<{ anvil: Anvil; forkBlock: { number: bigint; timestamp: bigint } }> {
|
|
70
|
+
const blockTimeS = await cheat.getIntervalMining();
|
|
71
|
+
if (!blockTimeS) {
|
|
72
|
+
throw new Error('restartAnvilOnHardfork needs an anvil in interval mining');
|
|
73
|
+
}
|
|
74
|
+
await cheat.setIntervalMining(0, { silent: true });
|
|
75
|
+
const tip = await cheat.publicClient.getBlock({ blockTag: 'latest' });
|
|
76
|
+
const statePath = join(opts.stateDir, `anvil-state-${tip.number}.json`);
|
|
77
|
+
await dumpAnvilStateWithHistory(dumpClient, statePath);
|
|
78
|
+
await anvil.stop();
|
|
79
|
+
|
|
80
|
+
const restarted = await startAnvil({
|
|
81
|
+
...opts.startOpts,
|
|
82
|
+
port: anvil.port,
|
|
83
|
+
hardfork: opts.hardfork,
|
|
84
|
+
loadState: statePath,
|
|
85
|
+
l1BlockTime: blockTimeS,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const blockTime = BigInt(blockTimeS);
|
|
89
|
+
const nowS = BigInt(Math.ceil(cheat.dateProvider.now() / 1000));
|
|
90
|
+
const slotsSinceTip = nowS > tip.timestamp ? (nowS - tip.timestamp + blockTime - 1n) / blockTime : 1n;
|
|
91
|
+
await cheat.warp(tip.timestamp + blockTime * slotsSinceTip, { silent: true, resetBlockInterval: true });
|
|
92
|
+
|
|
93
|
+
const forkBlock = await cheat.publicClient.getBlock({ blockNumber: tip.number + 1n });
|
|
94
|
+
return { anvil: restarted.anvil, forkBlock: { number: forkBlock.number, timestamp: forkBlock.timestamp } };
|
|
95
|
+
}
|
|
@@ -3,8 +3,10 @@ import { keccak256 } from '@aztec-labs/foundation/crypto/keccak';
|
|
|
3
3
|
import { EthAddress } from '@aztec-labs/foundation/eth-address';
|
|
4
4
|
import { jsonStringify } from '@aztec-labs/foundation/json-rpc';
|
|
5
5
|
import { createLogger } from '@aztec-labs/foundation/log';
|
|
6
|
+
import { Semaphore } from '@aztec-labs/foundation/queue';
|
|
6
7
|
import { pluralize } from '@aztec-labs/foundation/string';
|
|
7
8
|
import type { DateProvider, TestDateProvider } from '@aztec-labs/foundation/timer';
|
|
9
|
+
import { Buffer } from 'buffer';
|
|
8
10
|
import {
|
|
9
11
|
type Chain,
|
|
10
12
|
type Hex,
|
|
@@ -19,6 +21,66 @@ import { foundry } from 'viem/chains';
|
|
|
19
21
|
|
|
20
22
|
import type { ViemPublicClient } from '../types.js';
|
|
21
23
|
|
|
24
|
+
/** Interval mining that a finished pause hands back to anvil on the next tick of the block grid. */
|
|
25
|
+
type PendingResume = { interval: number; timer: ReturnType<typeof setTimeout> };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Mining state of the test anvil, shared by every {@link EthCheatCodes} instance in the process: e2e builds several over
|
|
29
|
+
* one anvil (the fixture's, `CheatCodes.eth`, each `RollupCheatCodes`), and a pause or pending resume started by one
|
|
30
|
+
* must be visible to the others. Assumes every mining-mode cheat code in the process targets one anvil: other anvils may
|
|
31
|
+
* be started and stopped alongside it, but not paused or switched between mining modes.
|
|
32
|
+
*/
|
|
33
|
+
const anvilMining: {
|
|
34
|
+
/** Serializes changes to anvil's mining mode against the pending resume. Never held while a caller's code runs. */
|
|
35
|
+
lock: Semaphore;
|
|
36
|
+
/** Ports of the anvil the latest pause targeted, which the pause state and any pending resume belong to. */
|
|
37
|
+
ownerPorts?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Mining mode captured by the pause in progress, restored when it ends. `nextTimestamp` is the next block's timestamp
|
|
40
|
+
* a caller chose during the pause, which a resume on the grid would overwrite if no block has used it yet.
|
|
41
|
+
*/
|
|
42
|
+
paused?: { interval: number | null; automine: boolean; nextTimestamp?: number };
|
|
43
|
+
pendingResume?: PendingResume;
|
|
44
|
+
/** Bumped on reset, so a resume being scheduled across a reset does not arm a timer for the next anvil. */
|
|
45
|
+
generation: number;
|
|
46
|
+
} = { lock: new Semaphore(1), generation: 0 };
|
|
47
|
+
|
|
48
|
+
/** Stops a pending resume and returns the interval it would have restored. */
|
|
49
|
+
function takePendingResume(): number | undefined {
|
|
50
|
+
const pending = anvilMining.pendingResume;
|
|
51
|
+
if (!pending) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
clearTimeout(pending.timer);
|
|
55
|
+
anvilMining.pendingResume = undefined;
|
|
56
|
+
return pending.interval;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Cancels any pending interval-mining resume and forgets the pause state, if they belong to the anvil listening on
|
|
61
|
+
* `port`. Called when that anvil is stopped, so no timer outlives it and the next anvil starts clean. Stopping any other
|
|
62
|
+
* anvil, such as a throwaway one a test starts next to its main one, leaves the state alone.
|
|
63
|
+
*/
|
|
64
|
+
export function resetAnvilMiningState(port: number): void {
|
|
65
|
+
// The owner is kept, so the reset on the anvil's close still clears a timer armed while it was shutting down.
|
|
66
|
+
if (!anvilMining.ownerPorts?.includes(String(port))) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
takePendingResume();
|
|
70
|
+
anvilMining.paused = undefined;
|
|
71
|
+
anvilMining.generation++;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A tick missed by at most this long, because a pause ended or the resume timer fired that much after it, is still
|
|
76
|
+
* mined straight away with its grid timestamp: the block lands up to this late rather than leaving a gap in the chain.
|
|
77
|
+
* A tick missed by more is skipped, like a missed L1 slot, and mining resumes on the next one.
|
|
78
|
+
*/
|
|
79
|
+
const OVERDUE_TICK_GRACE_MS = 1000;
|
|
80
|
+
|
|
81
|
+
/** A grid block mined at most this long after its tick restarts anvil's countdown from it; a later one does not. */
|
|
82
|
+
const ON_TIME_TOLERANCE_MS = 100;
|
|
83
|
+
|
|
22
84
|
/**
|
|
23
85
|
* A class that provides utility functions for interacting with ethereum (L1).
|
|
24
86
|
*/
|
|
@@ -118,7 +180,7 @@ export class EthCheatCodes {
|
|
|
118
180
|
* @param numberOfBlocks - The number of blocks to mine
|
|
119
181
|
*/
|
|
120
182
|
public async mine(numberOfBlocks: number | bigint = 1): Promise<void> {
|
|
121
|
-
await this.doMine(Number(numberOfBlocks));
|
|
183
|
+
await this.withMiningLock(() => this.doMine(Number(numberOfBlocks)));
|
|
122
184
|
this.logger.warn(`Mined ${numberOfBlocks} L1 blocks`);
|
|
123
185
|
}
|
|
124
186
|
|
|
@@ -134,9 +196,13 @@ export class EthCheatCodes {
|
|
|
134
196
|
* Mines a single block with evm_mine
|
|
135
197
|
*/
|
|
136
198
|
public async evmMine(): Promise<void> {
|
|
199
|
+
await this.withMiningLock(() => this.doEvmMine());
|
|
200
|
+
this.logger.warn(`Mined 1 L1 block with evm_mine`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async doEvmMine(): Promise<void> {
|
|
137
204
|
try {
|
|
138
205
|
await this.doRpcCall('evm_mine', []);
|
|
139
|
-
this.logger.warn(`Mined 1 L1 block with evm_mine`);
|
|
140
206
|
} catch (err) {
|
|
141
207
|
throw new Error(`Error mining: ${err}`);
|
|
142
208
|
}
|
|
@@ -188,30 +254,44 @@ export class EthCheatCodes {
|
|
|
188
254
|
}
|
|
189
255
|
|
|
190
256
|
/**
|
|
191
|
-
* Get interval mining if set.
|
|
192
|
-
*
|
|
257
|
+
* Get interval mining if set. A pending resume left by {@link execWithPausedAnvil} counts as set, although anvil
|
|
258
|
+
* itself reports interval mining as off until the resume fires.
|
|
193
259
|
*/
|
|
194
|
-
public getIntervalMining(): Promise<number | null> {
|
|
260
|
+
public async getIntervalMining(): Promise<number | null> {
|
|
261
|
+
return await this.withMiningLock(
|
|
262
|
+
async () => anvilMining.pendingResume?.interval ?? (await this.doGetIntervalMining()),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private async doGetIntervalMining(): Promise<number | null> {
|
|
195
267
|
try {
|
|
196
|
-
return this.doRpcCall('anvil_getIntervalMining', []);
|
|
268
|
+
return await this.doRpcCall('anvil_getIntervalMining', []);
|
|
197
269
|
} catch (err) {
|
|
198
270
|
throw new Error(`Error getting interval mining: ${err}`);
|
|
199
271
|
}
|
|
200
272
|
}
|
|
201
273
|
|
|
202
274
|
/**
|
|
203
|
-
* Enable interval mining at the given interval (block time)
|
|
275
|
+
* Enable interval mining at the given interval (block time). Replaces any pending resume left by
|
|
276
|
+
* {@link execWithPausedAnvil}.
|
|
204
277
|
* @param seconds - The interval to use between blocks
|
|
205
278
|
*/
|
|
206
279
|
public async setIntervalMining(seconds: number, opts: { silent?: boolean } = {}): Promise<void> {
|
|
280
|
+
await this.withMiningLock(async () => {
|
|
281
|
+
takePendingResume();
|
|
282
|
+
await this.doSetIntervalMining(seconds);
|
|
283
|
+
});
|
|
284
|
+
if (!opts.silent) {
|
|
285
|
+
this.logger.warn(`Set L1 interval mining to ${seconds} seconds`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private async doSetIntervalMining(seconds: number): Promise<void> {
|
|
207
290
|
try {
|
|
208
291
|
await this.rpcCall('anvil_setIntervalMining', [seconds]);
|
|
209
292
|
} catch (err) {
|
|
210
293
|
throw new Error(`Error setting interval mining: ${err}`);
|
|
211
294
|
}
|
|
212
|
-
if (!opts.silent) {
|
|
213
|
-
this.logger.warn(`Set L1 interval mining to ${seconds} seconds`);
|
|
214
|
-
}
|
|
215
295
|
}
|
|
216
296
|
|
|
217
297
|
/**
|
|
@@ -222,11 +302,14 @@ export class EthCheatCodes {
|
|
|
222
302
|
throw new Error(`Interval mining requires a positive interval, got ${seconds}`);
|
|
223
303
|
}
|
|
224
304
|
|
|
225
|
-
await this.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
305
|
+
await this.withMiningLock(async () => {
|
|
306
|
+
takePendingResume();
|
|
307
|
+
await this.doSetAutomine(false);
|
|
308
|
+
await this.doSetIntervalMining(0);
|
|
309
|
+
await this.doEvmMine();
|
|
310
|
+
await this.syncDateProvider();
|
|
311
|
+
await this.doSetIntervalMining(seconds);
|
|
312
|
+
});
|
|
230
313
|
this.logger.warn(`Started L1 interval mining at ${seconds} seconds from a fresh block`);
|
|
231
314
|
}
|
|
232
315
|
|
|
@@ -235,14 +318,18 @@ export class EthCheatCodes {
|
|
|
235
318
|
* @param automine - The automine status to set
|
|
236
319
|
*/
|
|
237
320
|
public async setAutomine(automine: boolean, opts: { silent?: boolean } = {}): Promise<void> {
|
|
321
|
+
await this.withMiningLock(() => this.doSetAutomine(automine));
|
|
322
|
+
if (!opts.silent) {
|
|
323
|
+
this.logger.warn(`Set L1 automine to ${automine}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async doSetAutomine(automine: boolean): Promise<void> {
|
|
238
328
|
try {
|
|
239
329
|
await this.rpcCall('anvil_setAutomine', [automine]);
|
|
240
330
|
} catch (err) {
|
|
241
331
|
throw new Error(`Error setting automine: ${err}`);
|
|
242
332
|
}
|
|
243
|
-
if (!opts.silent) {
|
|
244
|
-
this.logger.warn(`Set L1 automine to ${automine}`);
|
|
245
|
-
}
|
|
246
333
|
}
|
|
247
334
|
|
|
248
335
|
/**
|
|
@@ -259,17 +346,27 @@ export class EthCheatCodes {
|
|
|
259
346
|
}
|
|
260
347
|
|
|
261
348
|
/**
|
|
262
|
-
* Set the next block timestamp
|
|
349
|
+
* Set the next block timestamp. A resume on the block grid after {@link execWithPausedAnvil} would overwrite it, so
|
|
350
|
+
* interval mining is instead restarted straight away: now if a resume is pending, or when the current pause ends if
|
|
351
|
+
* no block mined during the pause has used the timestamp.
|
|
263
352
|
* @param timestamp - The timestamp to set the next block to
|
|
264
353
|
*/
|
|
265
354
|
public async setNextBlockTimestamp(timestamp: number | Date): Promise<void> {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
355
|
+
const seconds = timestamp instanceof Date ? Math.floor(timestamp.getTime() / 1000) : timestamp;
|
|
356
|
+
await this.withMiningLock(async () => {
|
|
357
|
+
const pendingInterval = takePendingResume();
|
|
358
|
+
if (pendingInterval !== undefined) {
|
|
359
|
+
await this.doSetIntervalMining(pendingInterval);
|
|
360
|
+
}
|
|
361
|
+
if (anvilMining.paused) {
|
|
362
|
+
anvilMining.paused.nextTimestamp = seconds;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
await this.rpcCall('evm_setNextBlockTimestamp', [seconds]);
|
|
366
|
+
} catch (err: any) {
|
|
367
|
+
throw new Error(`Error setting next block timestamp: ${err.message}`);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
273
370
|
this.logger.warn(`Set L1 next block timestamp to ${timestamp}`);
|
|
274
371
|
}
|
|
275
372
|
|
|
@@ -283,31 +380,34 @@ export class EthCheatCodes {
|
|
|
283
380
|
timestamp: number | bigint,
|
|
284
381
|
opts: { silent?: boolean; resetBlockInterval?: boolean } = {},
|
|
285
382
|
): Promise<void> {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (
|
|
292
|
-
await this.
|
|
383
|
+
await this.withMiningLock(async () => {
|
|
384
|
+
let blockInterval: number | null = null;
|
|
385
|
+
try {
|
|
386
|
+
// Load current block interval and disable it. A pending resume would put mining back on the grid from
|
|
387
|
+
// before the warp, so take it over: the warp re-phases the grid onto its own timestamp.
|
|
388
|
+
if (opts.resetBlockInterval) {
|
|
389
|
+
blockInterval = takePendingResume() ?? (await this.doGetIntervalMining());
|
|
390
|
+
if (blockInterval !== null) {
|
|
391
|
+
await this.doSetIntervalMining(0);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Set the timestamp of the next block to be mined
|
|
395
|
+
await this.rpcCall('evm_setNextBlockTimestamp', [Number(timestamp)]);
|
|
396
|
+
// And mine a block so the timestamp goes into effect now
|
|
397
|
+
await this.doMine();
|
|
398
|
+
// Update the injected date provider so it follows L1 time
|
|
399
|
+
if ('setTime' in this.dateProvider) {
|
|
400
|
+
this.dateProvider.setTime(Number(timestamp) * 1000);
|
|
401
|
+
}
|
|
402
|
+
} catch (err) {
|
|
403
|
+
throw new Error(`Error warping: ${err}`);
|
|
404
|
+
} finally {
|
|
405
|
+
// Restore interval mining so the next block is mined in `blockInterval` seconds from this one
|
|
406
|
+
if (opts.resetBlockInterval && blockInterval !== null && blockInterval > 0) {
|
|
407
|
+
await this.doSetIntervalMining(blockInterval);
|
|
293
408
|
}
|
|
294
409
|
}
|
|
295
|
-
|
|
296
|
-
await this.rpcCall('evm_setNextBlockTimestamp', [Number(timestamp)]);
|
|
297
|
-
// And mine a block so the timestamp goes into effect now
|
|
298
|
-
await this.doMine();
|
|
299
|
-
// Update the injected date provider so it follows L1 time
|
|
300
|
-
if ('setTime' in this.dateProvider) {
|
|
301
|
-
this.dateProvider.setTime(Number(timestamp) * 1000);
|
|
302
|
-
}
|
|
303
|
-
} catch (err) {
|
|
304
|
-
throw new Error(`Error warping: ${err}`);
|
|
305
|
-
} finally {
|
|
306
|
-
// Restore interval mining so the next block is mined in `blockInterval` seconds from this one
|
|
307
|
-
if (opts.resetBlockInterval && blockInterval !== null && blockInterval > 0) {
|
|
308
|
-
await this.setIntervalMining(blockInterval, { silent: true });
|
|
309
|
-
}
|
|
310
|
-
}
|
|
410
|
+
});
|
|
311
411
|
if (!opts.silent) {
|
|
312
412
|
this.logger.warn(`Warped L1 timestamp to ${timestamp}`);
|
|
313
413
|
}
|
|
@@ -483,17 +583,19 @@ export class EthCheatCodes {
|
|
|
483
583
|
): Promise<void> {
|
|
484
584
|
this.logger.verbose(`Preparing L1 reorg with depth ${depth}`);
|
|
485
585
|
try {
|
|
486
|
-
await this.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
586
|
+
await this.withMiningLock(() =>
|
|
587
|
+
this.rpcCall('anvil_reorg', [
|
|
588
|
+
depth,
|
|
589
|
+
newBlocks.flatMap((txs, index) =>
|
|
590
|
+
txs.map(tx => [
|
|
591
|
+
typeof tx === 'string'
|
|
592
|
+
? tx
|
|
593
|
+
: { value: 0, ...tx, ...(tx.gas === undefined ? {} : { gas: numberToHex(tx.gas) }) },
|
|
594
|
+
index,
|
|
595
|
+
]),
|
|
596
|
+
),
|
|
597
|
+
]),
|
|
598
|
+
);
|
|
497
599
|
} catch (err) {
|
|
498
600
|
throw new Error(`Error reorging: ${err}`);
|
|
499
601
|
}
|
|
@@ -576,43 +678,166 @@ export class EthCheatCodes {
|
|
|
576
678
|
await this.doRpcCall('anvil_setBlockGasLimit', [toHex(gasLimit)]);
|
|
577
679
|
}
|
|
578
680
|
|
|
681
|
+
/**
|
|
682
|
+
* Runs `fn` with automine and interval mining off, so no block is mined while it runs. Called while another pause is
|
|
683
|
+
* in progress, it just runs `fn` and leaves the mining mode to that pause.
|
|
684
|
+
*
|
|
685
|
+
* Automine comes back straight away. Interval mining comes back in the background, on the grid the chain was
|
|
686
|
+
* already on (see {@link scheduleResume}), so the caller never waits for an L1 tick.
|
|
687
|
+
*/
|
|
579
688
|
public async execWithPausedAnvil<T>(fn: () => Promise<T>): Promise<T> {
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
await this.setIntervalMining(0, { silent: true });
|
|
689
|
+
const ownsPause = await this.withMiningLock(async () => {
|
|
690
|
+
if (anvilMining.paused) {
|
|
691
|
+
return false;
|
|
584
692
|
}
|
|
693
|
+
await this.pauseMining();
|
|
694
|
+
return true;
|
|
695
|
+
});
|
|
696
|
+
if (!ownsPause) {
|
|
697
|
+
return await fn();
|
|
698
|
+
}
|
|
585
699
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
700
|
+
try {
|
|
701
|
+
return await fn();
|
|
702
|
+
} finally {
|
|
703
|
+
await this.withMiningLock(() => this.restoreMining());
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
public async syncDateProvider() {
|
|
708
|
+
const timestamp = await this.lastBlockTimestamp();
|
|
709
|
+
if ('setTime' in this.dateProvider) {
|
|
710
|
+
this.dateProvider.setTime(timestamp * 1000);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
589
713
|
|
|
714
|
+
private async withMiningLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
715
|
+
await anvilMining.lock.acquire();
|
|
716
|
+
try {
|
|
590
717
|
return await fn();
|
|
591
718
|
} finally {
|
|
719
|
+
anvilMining.lock.release();
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private async pauseMining(): Promise<void> {
|
|
724
|
+
// A resume still pending from an earlier pause is taken over: anvil reports interval mining as off meanwhile.
|
|
725
|
+
const interval = takePendingResume() ?? (await this.doGetIntervalMining());
|
|
726
|
+
const paused = { interval, automine: await this.isAutoMining() };
|
|
727
|
+
anvilMining.paused = paused;
|
|
728
|
+
anvilMining.ownerPorts = this.rpcUrls.map(url => new URL(url).port);
|
|
729
|
+
try {
|
|
730
|
+
if (interval !== null) {
|
|
731
|
+
await this.doSetIntervalMining(0);
|
|
732
|
+
}
|
|
733
|
+
if (paused.automine) {
|
|
734
|
+
await this.doSetAutomine(false);
|
|
735
|
+
}
|
|
736
|
+
} catch (err) {
|
|
737
|
+
await this.restoreMining();
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private async restoreMining(): Promise<void> {
|
|
743
|
+
const saved = anvilMining.paused;
|
|
744
|
+
anvilMining.paused = undefined;
|
|
745
|
+
if (saved?.automine) {
|
|
592
746
|
try {
|
|
593
|
-
|
|
594
|
-
if (wasAutoMining) {
|
|
595
|
-
await this.setAutomine(true, { silent: true });
|
|
596
|
-
}
|
|
747
|
+
await this.doSetAutomine(true);
|
|
597
748
|
} catch (err) {
|
|
598
749
|
this.logger.warn(`Failed to reenable automining: ${err}`);
|
|
599
750
|
}
|
|
600
|
-
|
|
751
|
+
}
|
|
752
|
+
if (saved?.interval) {
|
|
601
753
|
try {
|
|
602
|
-
|
|
603
|
-
if (blockInterval !== null) {
|
|
604
|
-
await this.setIntervalMining(blockInterval, { silent: true });
|
|
605
|
-
}
|
|
754
|
+
await this.scheduleResume(saved.interval, saved.nextTimestamp);
|
|
606
755
|
} catch (err) {
|
|
607
|
-
this.logger.warn(`Failed to
|
|
756
|
+
this.logger.warn(`Failed to schedule interval mining on the block grid, restarting it now: ${err}`);
|
|
757
|
+
try {
|
|
758
|
+
await this.doSetIntervalMining(saved.interval);
|
|
759
|
+
} catch (err) {
|
|
760
|
+
this.logger.warn(`Failed to reenable interval mining: ${err}`);
|
|
761
|
+
}
|
|
608
762
|
}
|
|
609
763
|
}
|
|
610
764
|
}
|
|
611
765
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
766
|
+
/**
|
|
767
|
+
* Re-enables interval mining so the next block lands on the grid the chain is already on: the first
|
|
768
|
+
* `latest + k * interval` tick the node clock has not yet passed. Anvil restarts its countdown from zero on
|
|
769
|
+
* `anvil_setIntervalMining`, so re-enabling it right after a pause would mine the next block late while still
|
|
770
|
+
* stamping it `latest + interval`, leaving L1 time behind the node clock for good. Instead mining stays off until
|
|
771
|
+
* that tick, when a background timer mines the tick's block and restarts the countdown from it.
|
|
772
|
+
*
|
|
773
|
+
* A tick the pause ran past by up to {@link OVERDUE_TICK_GRACE_MS} is mined straight away; one missed by more is
|
|
774
|
+
* skipped, so the lag stays bounded, at the cost of a gap in the chain like a missed L1 slot. Must be called holding
|
|
775
|
+
* the mining lock.
|
|
776
|
+
*
|
|
777
|
+
* @param nextTimestamp - A next-block timestamp a caller set during the pause. Unless a block has already used it, it
|
|
778
|
+
* is kept: interval mining restarts straight away instead, so the resume does not overwrite it.
|
|
779
|
+
*/
|
|
780
|
+
private async scheduleResume(interval: number, nextTimestamp?: number): Promise<void> {
|
|
781
|
+
const generation = anvilMining.generation;
|
|
782
|
+
const latest = await this.lastBlockTimestamp();
|
|
783
|
+
if (anvilMining.generation !== generation) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (nextTimestamp !== undefined && latest < nextTimestamp) {
|
|
787
|
+
await this.doSetIntervalMining(interval);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const nowMs = this.dateProvider.now();
|
|
791
|
+
const ticks = Math.max(1, Math.ceil((nowMs - OVERDUE_TICK_GRACE_MS - latest * 1000) / (interval * 1000)));
|
|
792
|
+
const tick = latest + ticks * interval;
|
|
793
|
+
const delayMs = tick * 1000 - nowMs;
|
|
794
|
+
if (delayMs > interval * 1000) {
|
|
795
|
+
// The chain is ahead of the node clock (say, a warp the date provider could not follow), so no tick is due
|
|
796
|
+
// within an interval: restart the countdown now, as anvil would.
|
|
797
|
+
await this.doSetIntervalMining(interval);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
const resume: PendingResume = {
|
|
801
|
+
interval,
|
|
802
|
+
timer: setTimeout(() => void this.resumeOnGrid(resume, latest, tick), Math.max(0, delayMs)),
|
|
803
|
+
};
|
|
804
|
+
anvilMining.pendingResume = resume;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private async resumeOnGrid(resume: PendingResume, latest: number, tick: number) {
|
|
808
|
+
try {
|
|
809
|
+
await this.withMiningLock(async () => {
|
|
810
|
+
if (anvilMining.pendingResume !== resume) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
anvilMining.pendingResume = undefined;
|
|
814
|
+
try {
|
|
815
|
+
const chainMoved = (await this.lastBlockTimestamp()) !== latest;
|
|
816
|
+
const firedMs = this.dateProvider.now();
|
|
817
|
+
if (chainMoved || firedMs - tick * 1000 > OVERDUE_TICK_GRACE_MS) {
|
|
818
|
+
// Blocks were mined or rolled back while mining was off, so the grid may have moved; or the timer ran
|
|
819
|
+
// late (a busy event loop, a wait on the lock), and mining the old tick now would put L1 behind again.
|
|
820
|
+
await this.scheduleResume(resume.interval);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
await this.doRpcCall('evm_setNextBlockTimestamp', [tick]);
|
|
824
|
+
await this.doEvmMine();
|
|
825
|
+
if (this.dateProvider.now() - tick * 1000 > ON_TIME_TOLERANCE_MS) {
|
|
826
|
+
// The tick was mined late, so a countdown restarted now would keep every later block that late too: wait
|
|
827
|
+
// for the next tick and restart it there instead.
|
|
828
|
+
await this.scheduleResume(resume.interval);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
} catch (err) {
|
|
832
|
+
this.logger.warn(`Failed to mine the L1 block due at ${tick}, restarting interval mining off the grid`, {
|
|
833
|
+
err: String(err),
|
|
834
|
+
tick,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
await this.doSetIntervalMining(resume.interval);
|
|
838
|
+
});
|
|
839
|
+
} catch (err) {
|
|
840
|
+
this.logger.error(`Failed to resume L1 interval mining`, err, { interval: resume.interval, tick });
|
|
616
841
|
}
|
|
617
842
|
}
|
|
618
843
|
}
|