Skip to content

Commit 04429a2

Browse files
committed
log released nonces
1 parent b2cec11 commit 04429a2

File tree

8 files changed

+51
-35
lines changed

8 files changed

+51
-35
lines changed

src/db/wallets/walletNonce.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,42 @@ const lastUsedNonceKey = (chainId: number, walletAddress: Address) =>
1212
`nonce:${chainId}:${normalizeAddress(walletAddress)}`;
1313

1414
/**
15-
* The "unused nonces" list stores unsorted nonces to be reused or cancelled.
15+
* The "recycled nonces" list stores unsorted nonces to be reused or cancelled.
1616
* Example: [ "25", "23", "24" ]
1717
*/
18-
const unusedNoncesKey = (chainId: number, walletAddress: Address) =>
19-
`nonce-unused:${chainId}:${normalizeAddress(walletAddress)}`;
18+
const recycledNoncesKey = (chainId: number, walletAddress: Address) =>
19+
`nonce-recycled:${chainId}:${normalizeAddress(walletAddress)}`;
2020

2121
/**
2222
* Acquire an unused nonce.
2323
* This should be used to send an EOA transaction with this nonce.
2424
* @param chainId
2525
* @param walletAddress
26-
* @returns number - The next unused nonce value for this wallet.
26+
* @returns number
2727
*/
28-
export const acquireNonce = async (chainId: number, walletAddress: Address) => {
29-
// Acquire an unused nonce, if any.
30-
let nonce = await _acquireUnusedNonce(chainId, walletAddress);
28+
export const acquireNonce = async (
29+
chainId: number,
30+
walletAddress: Address,
31+
): Promise<{ nonce: number; isRecycledNonce: boolean }> => {
32+
// Acquire an recylced nonce, if any.
33+
let nonce = await _acquireRecycledNonce(chainId, walletAddress);
3134
if (nonce !== null) {
32-
return nonce;
35+
return { nonce, isRecycledNonce: true };
3336
}
3437

3538
// Else increment the last used nonce.
3639
const key = lastUsedNonceKey(chainId, walletAddress);
3740
nonce = await redis.incr(key);
3841
if (nonce === 1) {
3942
// If INCR returned 1, the nonce was not set.
40-
// This may be a newly imported wallet. Sync and return the onchain nonce.
41-
return await _syncNonce(chainId, walletAddress);
43+
// This may be a newly imported wallet.
44+
nonce = await _syncNonce(chainId, walletAddress);
4245
}
43-
return nonce;
46+
return { nonce, isRecycledNonce: false };
4447
};
4548

4649
/**
47-
* Adds an unused nonce that can be reused by a future transaction.
50+
* Recycles a nonce to be used by a future transaction.
4851
* This should be used if the current transaction that acquired this nonce is not valid.
4952
* @param chainId
5053
* @param walletAddress
@@ -55,18 +58,21 @@ export const releaseNonce = async (
5558
walletAddress: Address,
5659
nonce: number,
5760
) => {
58-
const key = unusedNoncesKey(chainId, walletAddress);
61+
const key = recycledNoncesKey(chainId, walletAddress);
5962
await redis.rpush(key, nonce);
6063
};
6164

6265
/**
63-
* Acquires an unused nonce.
66+
* Acquires a recycled nonce that is unused.
6467
* @param chainId
6568
* @param walletAddress
6669
* @returns
6770
*/
68-
const _acquireUnusedNonce = async (chainId: number, walletAddress: Address) => {
69-
const key = unusedNoncesKey(chainId, walletAddress);
71+
const _acquireRecycledNonce = async (
72+
chainId: number,
73+
walletAddress: Address,
74+
) => {
75+
const key = recycledNoncesKey(chainId, walletAddress);
7076
const res = await redis.lpop(key);
7177
return res ? parseInt(res) : null;
7278
};
@@ -87,7 +93,14 @@ const _syncNonce = async (
8793

8894
const key = lastUsedNonceKey(chainId, walletAddress);
8995
await redis.set(key, transactionCount);
90-
return transactionCount;
96+
97+
// Set last used nonce to transactionCount - 1.
98+
// But don't lower the Redis nonce.
99+
100+
// @TODO:
101+
// Set to max(value, transactionCount-1) -> 6
102+
103+
return await redis.incr(key);
91104
};
92105

93106
/**
@@ -110,7 +123,7 @@ export const inspectNonce = async (chainId: number, walletAddress: Address) => {
110123
export const deleteAllNonces = async () => {
111124
const keys = [
112125
...(await redis.keys("nonce:*")),
113-
...(await redis.keys("nonce-unused:*")),
126+
...(await redis.keys("nonce-recycled:*")),
114127
];
115128
if (keys.length > 0) {
116129
await redis.del(keys);

src/server/routes/backend-wallet/simulateTransaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { randomUUID } from "crypto";
33
import { FastifyInstance } from "fastify";
44
import { StatusCodes } from "http-status-codes";
55
import { Address, Hex } from "thirdweb";
6-
import { simulateQueuedTransaction } from "../../../utils/transaction/simulateQueuedTransaction";
6+
import { doSimulateTransaction } from "../../../utils/transaction/simulateQueuedTransaction";
77
import { QueuedTransaction } from "../../../utils/transaction/types";
88
import { createCustomError } from "../../middleware/error";
99
import {
@@ -110,7 +110,7 @@ export async function simulateTransaction(fastify: FastifyInstance) {
110110
}),
111111
};
112112

113-
const simulateError = await simulateQueuedTransaction(queuedTransaction);
113+
const simulateError = await doSimulateTransaction(queuedTransaction);
114114
if (simulateError) {
115115
throw createCustomError(
116116
simulateError,

src/utils/error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ethers } from "ethers";
22
import { getChainMetadata } from "thirdweb/chains";
33
import { getChain } from "./chain";
44
import { isEthersErrorCode } from "./ethers";
5-
import { simulateQueuedTransaction } from "./transaction/simulateQueuedTransaction";
5+
import { doSimulateTransaction } from "./transaction/simulateQueuedTransaction";
66
import { AnyTransaction } from "./transaction/types";
77

88
export const prettifyError = async (
@@ -17,7 +17,7 @@ export const prettifyError = async (
1717
}
1818

1919
if (isEthersErrorCode(error, ethers.errors.UNPREDICTABLE_GAS_LIMIT)) {
20-
const simulateError = await simulateQueuedTransaction(transaction);
20+
const simulateError = await doSimulateTransaction(transaction);
2121
if (simulateError) {
2222
return simulateError;
2323
}

src/utils/transaction/insertTransaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createCustomError } from "../../server/middleware/error";
44
import { SendTransactionQueue } from "../../worker/queues/sendTransactionQueue";
55
import { normalizeAddress } from "../primitiveTypes";
66
import { reportUsage } from "../usage";
7-
import { simulateQueuedTransaction } from "./simulateQueuedTransaction";
7+
import { doSimulateTransaction } from "./simulateQueuedTransaction";
88
import { InsertedTransaction, QueuedTransaction } from "./types";
99

1010
interface InsertTransactionData {
@@ -54,7 +54,7 @@ export const insertTransaction = async (
5454

5555
// Simulate the transaction.
5656
if (shouldSimulate) {
57-
const error = await simulateQueuedTransaction(queuedTransaction);
57+
const error = await doSimulateTransaction(queuedTransaction);
5858
if (error) {
5959
throw createCustomError(
6060
`Simulation failed: ${error.replace(/[\r\n]+/g, " --- ")}`,

src/utils/transaction/simulateQueuedTransaction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { AnyTransaction } from "./types";
2020
* @param transaction
2121
* @throws if there is a simulation error
2222
*/
23-
export const simulateQueuedTransaction = async (
23+
export const doSimulateTransaction = async (
2424
transaction: AnyTransaction,
2525
): Promise<string | null> => {
2626
const {

src/worker/queues/queues.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ export const defaultJobOptions: JobsOptions = {
66
attempts: 3,
77
// Retries after 5s, 10s, 20s.
88
backoff: { type: "exponential", delay: 5_000 },
9-
// Purges completed jobs past 2000 or 5 days.
9+
// Purges completed jobs.
1010
removeOnComplete: {
1111
age: 60 * 60 * 24 * 7,
12-
count: 2000,
12+
count: 10_000,
1313
},
1414
};
1515

src/worker/tasks/cancelUnusedNoncesWorker.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ import { CANCEL_UNUSED_NONCES_QUEUE_NAME } from "../queues/cancelUnusedNoncesQue
1010
import { logWorkerExceptions } from "../queues/queues";
1111

1212
/**
13-
* Responsibilities:
14-
* - Loop through each unused nonce in all wallets.
15-
* - Cancel the transaction.
16-
* - If failed, add it back to the unused nonces list.
13+
* Sends a cancel transaction for all recycled nonces.
1714
*/
1815
const handler: Processor<any, void, string> = async (job: Job<string>) => {
19-
const keys = await redis.keys("nonce-unused:*");
16+
const keys = await redis.keys("nonce-recycled:*");
2017

2118
for (const key of keys) {
2219
const { chainId, walletAddress } = fromUnusedNoncesKey(key);
@@ -41,6 +38,7 @@ const handler: Processor<any, void, string> = async (job: Job<string>) => {
4138
if (isEthersErrorCode(e, ethers.errors.NONCE_EXPIRED)) {
4239
ignore.push(nonce);
4340
} else {
41+
job.log(`Releasing nonce: ${nonce}`);
4442
await releaseNonce(chainId, walletAddress, nonce);
4543
fail.push(nonce);
4644
}
@@ -49,7 +47,7 @@ const handler: Processor<any, void, string> = async (job: Job<string>) => {
4947

5048
const message = `Cancelling nonces for ${key}. success=${success} fail=${fail} ignored=${ignore}`;
5149
job.log(message);
52-
logger({ level: "info", message, service: "worker" });
50+
logger({ service: "worker", level: "info", message });
5351
}
5452
}
5553
};

src/worker/tasks/sendTransactionWorker.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ const _sendTransaction = async (
142142
}
143143

144144
// Acquire an unused nonce for this transaction.
145-
const nonce = await acquireNonce(chainId, from);
145+
const { nonce, isRecycledNonce } = await acquireNonce(chainId, from);
146+
job.log(`Acquired nonce ${nonce}. isRecycledNonce=${isRecycledNonce}`);
146147
populatedTransaction.nonce = nonce;
147-
148148
job.log(`Sending transaction: ${stringify(populatedTransaction)}`);
149149

150150
// Send transaction to RPC.
@@ -160,6 +160,7 @@ const _sendTransaction = async (
160160
} catch (error: unknown) {
161161
// Release the nonce if it has not expired.
162162
if (!isEthersErrorCode(error, ethers.errors.NONCE_EXPIRED)) {
163+
job.log(`Releasing nonce: ${nonce}`);
163164
await releaseNonce(chainId, from, nonce);
164165
}
165166
throw error;
@@ -200,6 +201,10 @@ const _resendTransaction = async (
200201
client: thirdwebClient,
201202
chain: await getChain(chainId),
202203
...sentTransaction,
204+
// Unset gas values so it can be re-populated.
205+
gasPrice: undefined,
206+
maxFeePerGas: undefined,
207+
maxPriorityFeePerGas: undefined,
203208
},
204209
});
205210
if (populatedTransaction.gasPrice) {

0 commit comments

Comments
 (0)