@aztec/ethereum 0.0.1-commit.f650c0a5c → 0.0.1-commit.f7ea82942
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 +7 -1
- package/dest/client.d.ts.map +1 -1
- package/dest/client.js +14 -0
- package/dest/contracts/chain_state_override.d.ts +38 -0
- package/dest/contracts/chain_state_override.d.ts.map +1 -0
- package/dest/contracts/chain_state_override.js +100 -0
- package/dest/contracts/inbox.d.ts +3 -3
- package/dest/contracts/inbox.d.ts.map +1 -1
- package/dest/contracts/inbox.js +8 -2
- package/dest/contracts/index.d.ts +2 -1
- package/dest/contracts/index.d.ts.map +1 -1
- package/dest/contracts/index.js +1 -0
- package/dest/contracts/rollup.d.ts +4942 -13
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +103 -11
- package/dest/l1_artifacts.d.ts +440 -864
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/dest/queries.d.ts +13 -2
- package/dest/queries.d.ts.map +1 -1
- package/dest/queries.js +59 -0
- package/dest/test/chain_monitor.d.ts +2 -1
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +5 -0
- package/dest/test/rollup_cheat_codes.d.ts +5 -1
- package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
- package/dest/test/rollup_cheat_codes.js +9 -1
- package/package.json +5 -5
- package/src/client.ts +18 -0
- package/src/contracts/chain_state_override.ts +147 -0
- package/src/contracts/inbox.ts +11 -3
- package/src/contracts/index.ts +1 -0
- package/src/contracts/rollup.ts +108 -19
- package/src/queries.ts +67 -1
- package/src/test/chain_monitor.ts +5 -0
- package/src/test/rollup_cheat_codes.ts +11 -1
package/src/client.ts
CHANGED
|
@@ -36,6 +36,24 @@ export function makeL1HttpTransport(rpcUrls: string[], opts?: { timeout?: number
|
|
|
36
36
|
return fallback(rpcUrls.map(url => http(url, { batch: false, timeout: opts?.timeout })));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Returns the individual RPC URLs underlying a viem public client that was constructed with a
|
|
41
|
+
* fallback HTTP transport (see {@link makeL1HttpTransport}). Returns an empty array if the
|
|
42
|
+
* transport shape is not recognized (e.g. mock clients in tests, or non-fallback transports).
|
|
43
|
+
*/
|
|
44
|
+
export function getRpcUrlsFromClient(client: ViemPublicClient): string[] {
|
|
45
|
+
const transport = client.transport as unknown as {
|
|
46
|
+
transports?: { value?: { url?: string } }[];
|
|
47
|
+
value?: { url?: string };
|
|
48
|
+
url?: string;
|
|
49
|
+
};
|
|
50
|
+
if (Array.isArray(transport?.transports)) {
|
|
51
|
+
return transport.transports.map(t => t?.value?.url).filter((url): url is string => typeof url === 'string');
|
|
52
|
+
}
|
|
53
|
+
const singleUrl = transport?.value?.url ?? transport?.url;
|
|
54
|
+
return typeof singleUrl === 'string' ? [singleUrl] : [];
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
// TODO: Use these methods to abstract the creation of viem clients.
|
|
40
58
|
|
|
41
59
|
/** Returns a viem public client given the L1 config. */
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
2
|
+
import type { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
+
|
|
5
|
+
import type { StateOverride } from 'viem';
|
|
6
|
+
|
|
7
|
+
import { type FeeHeader, RollupContract } from './rollup.js';
|
|
8
|
+
|
|
9
|
+
export type PendingCheckpointOverrideState = {
|
|
10
|
+
archive?: Fr;
|
|
11
|
+
feeHeader?: FeeHeader;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Describes the simulated L1 rollup state that downstream calls should observe. */
|
|
15
|
+
export type SimulationOverridesPlan = {
|
|
16
|
+
pendingCheckpointNumber?: CheckpointNumber;
|
|
17
|
+
pendingCheckpointState?: PendingCheckpointOverrideState;
|
|
18
|
+
disableBlobCheck?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Builds a single-checkpoint simulation plan before it is translated into a viem state override. */
|
|
22
|
+
export class SimulationOverridesBuilder {
|
|
23
|
+
private pendingCheckpointNumber?: CheckpointNumber;
|
|
24
|
+
private pendingCheckpointState?: PendingCheckpointOverrideState;
|
|
25
|
+
private disableBlobCheck = false;
|
|
26
|
+
|
|
27
|
+
/** Starts from an existing plan so callers can extend or specialize it. */
|
|
28
|
+
public static from(plan: SimulationOverridesPlan | undefined): SimulationOverridesBuilder {
|
|
29
|
+
return new SimulationOverridesBuilder().merge(plan);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Merges another plan into this builder. Later values win. */
|
|
33
|
+
public merge(plan: SimulationOverridesPlan | undefined): this {
|
|
34
|
+
if (!plan) {
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.pendingCheckpointNumber = plan.pendingCheckpointNumber;
|
|
39
|
+
this.pendingCheckpointState = plan.pendingCheckpointState
|
|
40
|
+
? { ...(this.pendingCheckpointState ?? {}), ...plan.pendingCheckpointState }
|
|
41
|
+
: this.pendingCheckpointState;
|
|
42
|
+
this.disableBlobCheck = this.disableBlobCheck || (plan.disableBlobCheck ?? false);
|
|
43
|
+
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Sets the checkpoint number that archive and fee header overrides should attach to. */
|
|
48
|
+
public forPendingCheckpoint(pendingCheckpointNumber: CheckpointNumber | undefined): this {
|
|
49
|
+
this.pendingCheckpointNumber = pendingCheckpointNumber;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Overrides the archive root for the configured pending checkpoint. */
|
|
54
|
+
public withPendingArchive(archive: Fr): this {
|
|
55
|
+
this.assertPendingCheckpointNumber();
|
|
56
|
+
this.pendingCheckpointState = { ...(this.pendingCheckpointState ?? {}), archive };
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Overrides the fee header for the configured pending checkpoint. */
|
|
61
|
+
public withPendingFeeHeader(feeHeader: FeeHeader): this {
|
|
62
|
+
this.assertPendingCheckpointNumber();
|
|
63
|
+
this.pendingCheckpointState = { ...(this.pendingCheckpointState ?? {}), feeHeader };
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Disables blob checking for simulations that cannot provide DA inputs. */
|
|
68
|
+
public withoutBlobCheck(): this {
|
|
69
|
+
this.disableBlobCheck = true;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Builds the final plan, or `undefined` when no overrides were configured. */
|
|
74
|
+
public build(): SimulationOverridesPlan | undefined {
|
|
75
|
+
if (!this.pendingCheckpointState && this.pendingCheckpointNumber === undefined && !this.disableBlobCheck) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
pendingCheckpointNumber: this.pendingCheckpointNumber,
|
|
81
|
+
pendingCheckpointState: this.pendingCheckpointState,
|
|
82
|
+
disableBlobCheck: this.disableBlobCheck || undefined,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private assertPendingCheckpointNumber(): void {
|
|
87
|
+
if (this.pendingCheckpointNumber === undefined) {
|
|
88
|
+
throw new Error('pendingCheckpointNumber must be set before attaching archive or fee header overrides');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Translates a simulation plan into the viem state override shape expected by rollup calls. */
|
|
94
|
+
export async function buildSimulationOverridesStateOverride(
|
|
95
|
+
rollup: RollupContract,
|
|
96
|
+
plan: SimulationOverridesPlan | undefined,
|
|
97
|
+
): Promise<StateOverride> {
|
|
98
|
+
if (!plan) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const rollupStateDiff: NonNullable<StateOverride[number]['stateDiff']> = [];
|
|
103
|
+
|
|
104
|
+
if (plan.pendingCheckpointNumber !== undefined) {
|
|
105
|
+
rollupStateDiff.push(
|
|
106
|
+
...extractRollupStateDiff(await rollup.makePendingCheckpointNumberOverride(plan.pendingCheckpointNumber)),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (plan.pendingCheckpointState && plan.pendingCheckpointNumber === undefined) {
|
|
111
|
+
throw new Error('pendingCheckpointState requires pendingCheckpointNumber to be set');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (plan.pendingCheckpointState?.archive) {
|
|
115
|
+
rollupStateDiff.push(
|
|
116
|
+
...extractRollupStateDiff(
|
|
117
|
+
rollup.makeArchiveOverride(plan.pendingCheckpointNumber!, plan.pendingCheckpointState.archive),
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (plan.pendingCheckpointState?.feeHeader) {
|
|
123
|
+
rollupStateDiff.push(
|
|
124
|
+
...extractRollupStateDiff(
|
|
125
|
+
await rollup.makeFeeHeaderOverride(plan.pendingCheckpointNumber!, plan.pendingCheckpointState.feeHeader),
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (plan.disableBlobCheck) {
|
|
131
|
+
rollupStateDiff.push({
|
|
132
|
+
slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
|
|
133
|
+
value: toPaddedHex(0n, true),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (rollupStateDiff.length === 0) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return [{ address: rollup.address, stateDiff: rollupStateDiff }];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function extractRollupStateDiff(override: StateOverride | StateOverride[number] | undefined) {
|
|
145
|
+
const entries = Array.isArray(override) ? override : override ? [override] : [];
|
|
146
|
+
return entries.flatMap(entry => entry.stateDiff ?? []);
|
|
147
|
+
}
|
package/src/contracts/inbox.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { maxBigint } from '@aztec/foundation/bigint';
|
|
1
2
|
import { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
3
4
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
@@ -82,9 +83,16 @@ export class InboxContract {
|
|
|
82
83
|
.map(log => this.mapMessageSentLog(log));
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
/** Fetches MessageSent events for a specific message hash
|
|
86
|
-
async getMessageSentEventByHash(msgHash: Hex,
|
|
87
|
-
|
|
86
|
+
/** Fetches MessageSent events for a specific message hash around a specific block. */
|
|
87
|
+
async getMessageSentEventByHash(msgHash: Hex, aroundL1BlockNumber: bigint): Promise<MessageSentLog> {
|
|
88
|
+
// We don't use blockHash here because we don't want the query to throw if the L1 block number no longer exists on chain
|
|
89
|
+
// due to an L1 reorg. The use case for this method is usually checking if a message still exists on the Inbox after
|
|
90
|
+
// a reorg, so it's possible the message was moved one block up or down, and that the original L1 block where we
|
|
91
|
+
// saw it no longer exists, rendering the block-by-hash approach invalid.
|
|
92
|
+
const [log] = await this.inbox.getEvents.MessageSent(
|
|
93
|
+
{ hash: msgHash },
|
|
94
|
+
{ fromBlock: maxBigint(aroundL1BlockNumber - 5n, 1n), toBlock: aroundL1BlockNumber + 5n },
|
|
95
|
+
);
|
|
88
96
|
return log && this.mapMessageSentLog(log);
|
|
89
97
|
}
|
|
90
98
|
|
package/src/contracts/index.ts
CHANGED
package/src/contracts/rollup.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type Account,
|
|
16
16
|
type GetContractReturnType,
|
|
17
17
|
type Hex,
|
|
18
|
+
type Log,
|
|
18
19
|
type StateOverride,
|
|
19
20
|
type WatchContractEventReturnType,
|
|
20
21
|
encodeAbiParameters,
|
|
@@ -128,6 +129,17 @@ export type L1FeeData = {
|
|
|
128
129
|
blobFee: bigint;
|
|
129
130
|
};
|
|
130
131
|
|
|
132
|
+
/** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
|
|
133
|
+
export enum TempCheckpointLogField {
|
|
134
|
+
HeaderHash = 0,
|
|
135
|
+
BlobCommitmentsHash = 1,
|
|
136
|
+
OutHash = 2,
|
|
137
|
+
AttestationsHash = 3,
|
|
138
|
+
PayloadDigest = 4,
|
|
139
|
+
SlotNumber = 5,
|
|
140
|
+
FeeHeader = 6,
|
|
141
|
+
}
|
|
142
|
+
|
|
131
143
|
/** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
|
|
132
144
|
export type ManaMinFeeComponents = {
|
|
133
145
|
sequencerCost: bigint;
|
|
@@ -514,8 +526,9 @@ export class RollupContract {
|
|
|
514
526
|
return this.rollup.read.getCheckpointReward();
|
|
515
527
|
}
|
|
516
528
|
|
|
517
|
-
async getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
518
|
-
|
|
529
|
+
async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
530
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
531
|
+
return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
|
|
519
532
|
}
|
|
520
533
|
|
|
521
534
|
async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
|
|
@@ -523,18 +536,31 @@ export class RollupContract {
|
|
|
523
536
|
return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
|
|
524
537
|
}
|
|
525
538
|
|
|
526
|
-
async getSlotNumber(): Promise<SlotNumber> {
|
|
527
|
-
|
|
539
|
+
async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
|
|
540
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
541
|
+
return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
|
|
528
542
|
}
|
|
529
543
|
|
|
530
|
-
async getL1FeesAt(timestamp: bigint): Promise<L1FeeData> {
|
|
531
|
-
|
|
544
|
+
async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
|
|
545
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
546
|
+
const result = await this.rollup.read.getL1FeesAt([timestamp], options);
|
|
532
547
|
return {
|
|
533
548
|
baseFee: result.baseFee,
|
|
534
549
|
blobFee: result.blobFee,
|
|
535
550
|
};
|
|
536
551
|
}
|
|
537
552
|
|
|
553
|
+
async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
|
|
554
|
+
const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
|
|
555
|
+
return {
|
|
556
|
+
excessMana: result.excessMana,
|
|
557
|
+
manaUsed: result.manaUsed,
|
|
558
|
+
ethPerFeeAsset: result.ethPerFeeAsset,
|
|
559
|
+
congestionCost: result.congestionCost,
|
|
560
|
+
proverCost: result.proverCost,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
538
564
|
getEthPerFeeAsset(): Promise<bigint> {
|
|
539
565
|
return this.rollup.read.getEthPerFeeAsset();
|
|
540
566
|
}
|
|
@@ -609,8 +635,9 @@ export class RollupContract {
|
|
|
609
635
|
return EthAddress.fromString(result);
|
|
610
636
|
}
|
|
611
637
|
|
|
612
|
-
async getCheckpoint(checkpointNumber: CheckpointNumber): Promise<CheckpointLog> {
|
|
613
|
-
|
|
638
|
+
async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
|
|
639
|
+
await checkBlockTag(options?.blockNumber, this.client);
|
|
640
|
+
const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
|
|
614
641
|
return {
|
|
615
642
|
archive: Fr.fromString(result.archive),
|
|
616
643
|
headerHash: Buffer32.fromString(result.headerHash),
|
|
@@ -643,6 +670,30 @@ export class RollupContract {
|
|
|
643
670
|
);
|
|
644
671
|
}
|
|
645
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Returns the effective pending checkpoint, accounting for potential prunes.
|
|
675
|
+
* When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
|
|
676
|
+
* This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
|
|
677
|
+
* @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
|
|
678
|
+
* @param options - Optional L1 block number to pin the queries to.
|
|
679
|
+
*/
|
|
680
|
+
getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
|
|
681
|
+
return retry(
|
|
682
|
+
async () => {
|
|
683
|
+
const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
|
|
684
|
+
const canPrune = await this.canPruneAtTime(timestamp, options);
|
|
685
|
+
if (canPrune) {
|
|
686
|
+
const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
|
|
687
|
+
return await this.getCheckpoint(provenCheckpointNumber, options);
|
|
688
|
+
}
|
|
689
|
+
const pendingCheckpointNumber = await this.getCheckpointNumber(options);
|
|
690
|
+
return await this.getCheckpoint(pendingCheckpointNumber, options);
|
|
691
|
+
},
|
|
692
|
+
'getting effective pending checkpoint',
|
|
693
|
+
makeBackoff([0.5, 0.5, 0.5]),
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
646
697
|
async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
|
|
647
698
|
const { pending, proven } = await this.rollup.read.getTips();
|
|
648
699
|
return {
|
|
@@ -765,19 +816,11 @@ export class RollupContract {
|
|
|
765
816
|
archive: Buffer,
|
|
766
817
|
account: `0x${string}` | Account,
|
|
767
818
|
timestamp: bigint,
|
|
768
|
-
|
|
769
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
770
|
-
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
771
|
-
} = {},
|
|
819
|
+
stateOverride: StateOverride = [],
|
|
772
820
|
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
|
|
773
821
|
const timeOfNextL1Slot = timestamp;
|
|
774
822
|
const who = typeof account === 'string' ? account : account.address;
|
|
775
823
|
|
|
776
|
-
const stateOverride = RollupContract.mergeStateOverrides(
|
|
777
|
-
await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
|
|
778
|
-
opts.forceArchive ? this.makeArchiveOverride(opts.forceArchive.checkpointNumber, opts.forceArchive.archive) : [],
|
|
779
|
-
);
|
|
780
|
-
|
|
781
824
|
try {
|
|
782
825
|
const {
|
|
783
826
|
result: [slot, checkpointNumber],
|
|
@@ -1182,7 +1225,7 @@ export class RollupContract {
|
|
|
1182
1225
|
}
|
|
1183
1226
|
|
|
1184
1227
|
public listenToCheckpointInvalidated(
|
|
1185
|
-
callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
|
|
1228
|
+
callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
|
|
1186
1229
|
): WatchContractEventReturnType {
|
|
1187
1230
|
return this.rollup.watchEvent.CheckpointInvalidated(
|
|
1188
1231
|
{},
|
|
@@ -1191,7 +1234,7 @@ export class RollupContract {
|
|
|
1191
1234
|
for (const log of logs) {
|
|
1192
1235
|
const args = log.args;
|
|
1193
1236
|
if (args.checkpointNumber !== undefined) {
|
|
1194
|
-
callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
|
|
1237
|
+
callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
|
|
1195
1238
|
}
|
|
1196
1239
|
}
|
|
1197
1240
|
},
|
|
@@ -1224,6 +1267,17 @@ export class RollupContract {
|
|
|
1224
1267
|
);
|
|
1225
1268
|
}
|
|
1226
1269
|
|
|
1270
|
+
/**
|
|
1271
|
+
* Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
|
|
1272
|
+
* The Rollup inherits from Ownable and emits this event in its constructor, so the event
|
|
1273
|
+
* is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
|
|
1274
|
+
* probe to detect RPC nodes that prune historical logs.
|
|
1275
|
+
*/
|
|
1276
|
+
async getOwnershipTransferredEventsAtDeploy() {
|
|
1277
|
+
const l1StartBlock = await this.getL1StartBlock();
|
|
1278
|
+
return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1227
1281
|
/** Fetches CheckpointProposed events within the given block range. */
|
|
1228
1282
|
async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
|
|
1229
1283
|
const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
|
|
@@ -1256,4 +1310,39 @@ export class RollupContract {
|
|
|
1256
1310
|
},
|
|
1257
1311
|
}));
|
|
1258
1312
|
}
|
|
1313
|
+
|
|
1314
|
+
/** Packs pending and proven checkpoint numbers into the chain tips storage format. */
|
|
1315
|
+
static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
|
|
1316
|
+
return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/** Storage slot for the chain tips (offset 0 within the STF storage struct). */
|
|
1320
|
+
static get chainTipsStorageSlot(): bigint {
|
|
1321
|
+
return BigInt(RollupContract.stfStorageSlot);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* Computes the storage slot for a field within a tempCheckpointLog entry.
|
|
1326
|
+
* @param checkpointNumber - The checkpoint number
|
|
1327
|
+
* @param field - The field within the CompressedTempCheckpointLog struct
|
|
1328
|
+
*/
|
|
1329
|
+
async getTempCheckpointLogStorageSlot(
|
|
1330
|
+
checkpointNumber: CheckpointNumber,
|
|
1331
|
+
field: TempCheckpointLogField,
|
|
1332
|
+
): Promise<bigint> {
|
|
1333
|
+
const fieldOffset = BigInt(field);
|
|
1334
|
+
const [epochDuration, proofSubmissionEpochs] = await Promise.all([
|
|
1335
|
+
this.getEpochDuration(),
|
|
1336
|
+
this.getProofSubmissionEpochs(),
|
|
1337
|
+
]);
|
|
1338
|
+
const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
|
|
1339
|
+
const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
|
|
1340
|
+
const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
|
|
1341
|
+
const entryBase = BigInt(
|
|
1342
|
+
keccak256(
|
|
1343
|
+
encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
|
|
1344
|
+
),
|
|
1345
|
+
);
|
|
1346
|
+
return entryBase + fieldOffset;
|
|
1347
|
+
}
|
|
1259
1348
|
}
|
package/src/queries.ts
CHANGED
|
@@ -1,11 +1,77 @@
|
|
|
1
1
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
2
|
|
|
3
|
+
import { BaseError, type Block } from 'viem';
|
|
4
|
+
|
|
3
5
|
import { DefaultL1ContractsConfig, type L1ContractsConfig } from './config.js';
|
|
4
6
|
import { ReadOnlyGovernanceContract } from './contracts/governance.js';
|
|
5
7
|
import { GovernanceProposerContract } from './contracts/governance_proposer.js';
|
|
6
8
|
import { InboxContract } from './contracts/inbox.js';
|
|
7
9
|
import { RollupContract } from './contracts/rollup.js';
|
|
8
|
-
import type { ViemPublicClient } from './types.js';
|
|
10
|
+
import type { ViemClient, ViemPublicClient } from './types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the L1 finalized block, or `undefined` if the chain does not yet have one
|
|
14
|
+
* (common on freshly started devnets). Rethrows any other RPC error.
|
|
15
|
+
*/
|
|
16
|
+
export async function getFinalizedL1Block(client: ViemClient): Promise<Block<bigint, false, 'finalized'> | undefined> {
|
|
17
|
+
try {
|
|
18
|
+
return await client.getBlock({ blockTag: 'finalized', includeTransactions: false });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (isFinalizedBlockTagNotFoundError(err)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Error messages returned by popular Ethereum execution clients when
|
|
28
|
+
// eth_getBlockByNumber / eth_call is called with blockTag "finalized" or
|
|
29
|
+
// "safe" and no such block has been produced yet:
|
|
30
|
+
//
|
|
31
|
+
// geth "finalized block not found"
|
|
32
|
+
// "safe block not found"
|
|
33
|
+
// reth "block not found: finalized"
|
|
34
|
+
// "block not found: safe"
|
|
35
|
+
// nethermind "Unknown block error" (same for both tags)
|
|
36
|
+
// besu "Unknown block"
|
|
37
|
+
// erigon 'block "finalized" not available (head block: N)'
|
|
38
|
+
// 'block "safe" not available (head block: N)'
|
|
39
|
+
//
|
|
40
|
+
// A combined regex covers all five:
|
|
41
|
+
const FINALIZED_BLOCK_TAG_NOT_FOUND_MESSAGE_RE =
|
|
42
|
+
/(finalized|safe) block not found|block not found: (finalized|safe)|unknown block|block "(finalized|safe)" not available/i;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns true if the error originates from an RPC call that failed because
|
|
46
|
+
* the "finalized" (or "safe") block tag is not yet available on the chain.
|
|
47
|
+
*/
|
|
48
|
+
export function isFinalizedBlockTagNotFoundError(err: unknown): boolean {
|
|
49
|
+
if (!err) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (err instanceof BaseError) {
|
|
54
|
+
const hit = err.walk((e: any) => matchesFinalizedBlockTagNotFound(e));
|
|
55
|
+
if (hit) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (let cur: any = err, i = 0; cur && i < 10; cur = cur.cause, i++) {
|
|
61
|
+
if (matchesFinalizedBlockTagNotFound(cur)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function matchesFinalizedBlockTagNotFound(e: any): boolean {
|
|
69
|
+
if (!e) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const text = `${e.details ?? ''} ${e.message ?? ''} ${e.shortMessage ?? ''}`;
|
|
73
|
+
return FINALIZED_BLOCK_TAG_NOT_FOUND_MESSAGE_RE.test(text);
|
|
74
|
+
}
|
|
9
75
|
|
|
10
76
|
/** Reads the L1ContractsConfig from L1 contracts. */
|
|
11
77
|
export async function getL1ContractsConfig(
|
|
@@ -221,6 +221,11 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
|
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
public async waitUntilNextL2Slot(): Promise<void> {
|
|
225
|
+
const targetSlot = SlotNumber.add((await this.run()).l2SlotNumber, 1);
|
|
226
|
+
return this.waitUntilL2Slot(targetSlot);
|
|
227
|
+
}
|
|
228
|
+
|
|
224
229
|
public waitUntilL1Block(block: number | bigint): Promise<void> {
|
|
225
230
|
const targetBlock = typeof block === 'bigint' ? block.valueOf() : block;
|
|
226
231
|
if (this.l1BlockNumber >= targetBlock) {
|
|
@@ -53,7 +53,17 @@ export class RollupCheatCodes {
|
|
|
53
53
|
/** Returns the current slot */
|
|
54
54
|
public async getSlot(): Promise<SlotNumber> {
|
|
55
55
|
const ts = BigInt((await this.client.getBlock()).timestamp);
|
|
56
|
-
return
|
|
56
|
+
return this.getSlotAt(ts);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Returns the slot number at a given timestamp. */
|
|
60
|
+
public async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
|
|
61
|
+
return SlotNumber.fromBigInt(await this.rollup.read.getSlotAt([timestamp]));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Returns the timestamp for the start of a given slot. */
|
|
65
|
+
public async getTimestampForSlot(slot: SlotNumber): Promise<bigint> {
|
|
66
|
+
return await this.rollup.read.getTimestampForSlot([BigInt(slot)]);
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
/** Returns the number of seconds until the start of the given slot based on L1 block timestamp. */
|