-
Notifications
You must be signed in to change notification settings - Fork 333
/
Copy pathsent_tx.ts
140 lines (130 loc) · 5.45 KB
/
sent_tx.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import { type GetUnencryptedLogsResponse, type PXE, type TxHash, type TxReceipt, TxStatus } from '@aztec/circuit-types';
import { retryUntil } from '@aztec/foundation/retry';
import { type FieldsOf } from '@aztec/foundation/types';
/** Options related to waiting for a tx. */
export type WaitOpts = {
/** The amount of time to ignore TxStatus.DROPPED receipts (in seconds) due to the presumption that it is being propagated by the p2p network. Defaults to 5. */
ignoreDroppedReceiptsFor?: number;
/** The maximum time (in seconds) to wait for the transaction to be mined. Defaults to 60. */
timeout?: number;
/** The maximum time (in seconds) to wait for the transaction to be proven. Defaults to 600. */
provenTimeout?: number;
/** The time interval (in seconds) between retries to fetch the transaction receipt. Defaults to 1. */
interval?: number;
/** Whether to wait for the tx to be proven. */
proven?: boolean;
/** Whether to include information useful for debugging/testing in the receipt. */
debug?: boolean;
/** Whether to accept a revert as a status code for the tx when waiting for it. If false, will throw if the tx reverts. */
dontThrowOnRevert?: boolean;
};
export const DefaultWaitOpts: WaitOpts = {
ignoreDroppedReceiptsFor: 5,
timeout: 60,
provenTimeout: 600,
interval: 1,
debug: false,
};
/**
* The SentTx class represents a sent transaction through the PXE, providing methods to fetch
* its hash, receipt, and mining status.
*/
export class SentTx {
constructor(protected pxe: PXE, protected txHashPromise: Promise<TxHash>) {}
/**
* Retrieves the transaction hash of the SentTx instance.
* The function internally awaits for the 'txHashPromise' to resolve, and then returns the resolved transaction hash.
*
* @returns A promise that resolves to the transaction hash of the SentTx instance.
* TODO(#7717): Don't throw here.
*/
public getTxHash(): Promise<TxHash> {
return this.txHashPromise;
}
/**
* Retrieve the transaction receipt associated with the current SentTx instance.
* The function fetches the transaction hash using 'getTxHash' and then queries
* the PXE to get the corresponding transaction receipt.
*
* @returns A promise that resolves to a TxReceipt object representing the fetched transaction receipt.
*/
public async getReceipt(): Promise<TxReceipt> {
const txHash = await this.getTxHash();
return await this.pxe.getTxReceipt(txHash);
}
/**
* Awaits for a tx to be mined and returns the receipt. Throws if tx is not mined.
* @param opts - Options for configuring the waiting for the tx to be mined.
* @returns The transaction receipt.
*/
public async wait(opts?: WaitOpts): Promise<FieldsOf<TxReceipt>> {
const receipt = await this.waitForReceipt(opts);
if (receipt.status !== TxStatus.SUCCESS && !opts?.dontThrowOnRevert) {
throw new Error(
`Transaction ${await this.getTxHash()} was ${receipt.status}. Reason: ${receipt.error ?? 'unknown'}`,
);
}
if (opts?.proven && receipt.blockNumber !== undefined) {
await this.waitForProven(receipt.blockNumber, opts);
}
if (opts?.debug) {
const txHash = await this.getTxHash();
const { data: tx } = (await this.pxe.getTxEffect(txHash))!;
receipt.debugInfo = {
noteHashes: tx.noteHashes,
nullifiers: tx.nullifiers,
publicDataWrites: tx.publicDataWrites,
l2ToL1Msgs: tx.l2ToL1Msgs,
};
}
return receipt;
}
/**
* Gets unencrypted logs emitted by this tx.
* @remarks This function will wait for the tx to be mined if it hasn't been already.
* @returns The requested logs.
*/
public async getUnencryptedLogs(): Promise<GetUnencryptedLogsResponse> {
await this.wait();
return this.pxe.getUnencryptedLogs({ txHash: await this.getTxHash() });
}
protected async waitForReceipt(opts?: WaitOpts): Promise<TxReceipt> {
const txHash = await this.getTxHash();
const startTime = Date.now();
const ignoreDroppedReceiptsFor = opts?.ignoreDroppedReceiptsFor ?? DefaultWaitOpts.ignoreDroppedReceiptsFor;
return await retryUntil(
async () => {
const txReceipt = await this.pxe.getTxReceipt(txHash);
// If receipt is not yet available, try again
if (txReceipt.status === TxStatus.PENDING) {
return undefined;
}
// If the tx was "dropped", either return it or ignore based on timing.
// We can ignore it at first because the transaction may have been sent to node 1, and now we're asking node 2 for the receipt.
// If we don't allow a short grace period, we could incorrectly return a TxReceipt with status DROPPED.
if (txReceipt.status === TxStatus.DROPPED) {
const elapsedSeconds = (Date.now() - startTime) / 1000;
if (!ignoreDroppedReceiptsFor || elapsedSeconds > ignoreDroppedReceiptsFor) {
return txReceipt;
}
return undefined;
}
return txReceipt;
},
'isMined',
opts?.timeout ?? DefaultWaitOpts.timeout,
opts?.interval ?? DefaultWaitOpts.interval,
);
}
protected async waitForProven(minedBlock: number, opts?: WaitOpts) {
return await retryUntil(
async () => {
const provenBlock = await this.pxe.getProvenBlockNumber();
return provenBlock >= minedBlock ? provenBlock : undefined;
},
'isProven',
opts?.provenTimeout ?? DefaultWaitOpts.provenTimeout,
opts?.interval ?? DefaultWaitOpts.interval,
);
}
}