Skip to content

Commit 196ef61

Browse files
arcoravenfarhanW3
andauthored
fix: remove gas limit on retries (#442)
* wip * updates for cancelTx * helper to multiple gas overrides * fix: add tests * better typing * update server language --------- Co-authored-by: farhanW3 <farhan@thirdweb.com>
1 parent 6eb255d commit 196ef61

File tree

8 files changed

+141
-106
lines changed

8 files changed

+141
-106
lines changed

src/db/configuration/getConfiguration.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,13 @@ export const getConfiguration = async (): Promise<Config> => {
178178
maxTxsToProcess: 30,
179179
minedTxListenerCronSchedule: "*/5 * * * * *",
180180
maxTxsToUpdate: 50,
181-
retryTxListenerCronSchedule: "*/30 * * * * *",
182-
minEllapsedBlocksBeforeRetry: 15,
181+
retryTxListenerCronSchedule: "*/15 * * * * *",
182+
minEllapsedBlocksBeforeRetry: 12,
183183
maxFeePerGasForRetries: ethers.utils
184-
.parseUnits("1000", "gwei")
184+
.parseUnits("10000", "gwei")
185185
.toString(),
186186
maxPriorityFeePerGasForRetries: ethers.utils
187-
.parseUnits("1000", "gwei")
187+
.parseUnits("10000", "gwei")
188188
.toString(),
189189
maxRetriesPerTx: 3,
190190
authDomain: "thirdweb.com",

src/server/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,11 @@ export const initServer = async () => {
9292
logger({
9393
service: "server",
9494
level: "info",
95-
message: `Listening on ${env.ENABLE_HTTPS ? "https://" : "http://"}${
96-
env.HOST
97-
}:${env.PORT}`,
95+
message: `Listening on ${
96+
env.ENABLE_HTTPS ? "https://" : "http://"
97+
}localhost:${
98+
env.PORT
99+
}. Manage your Engine from https://thirdweb.com/dashboard/engine.`,
98100
});
99101

100102
writeOpenApiToFile(server);

src/server/utils/transaction.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { TransactionResponse } from "@ethersproject/abstract-provider";
22
import { getDefaultGasOverrides } from "@thirdweb-dev/sdk";
3-
import { BigNumber } from "ethers";
43
import { StatusCodes } from "http-status-codes";
54
import { getTxById } from "../../db/transactions/getTxById";
65
import { updateTx } from "../../db/transactions/updateTx";
6+
import { PrismaTransaction } from "../../schema/prisma";
77
import { getSdk } from "../../utils/cache/getSdk";
8+
import { multiplyGasOverrides } from "../../utils/gas";
89
import { createCustomError } from "../middleware/error";
910
import { TransactionStatusEnum } from "../schemas/transaction";
1011

1112
interface CancelTransactionAndUpdateParams {
1213
queueId: string;
14+
pgtx?: PrismaTransaction;
1315
}
1416

1517
export const cancelTransactionAndUpdate = async ({
1618
queueId,
19+
pgtx,
1720
}: CancelTransactionAndUpdateParams) => {
18-
const txData = await getTxById({ queueId });
21+
const txData = await getTxById({ queueId, pgtx });
1922
if (!txData) {
2023
return {
2124
message: `Transaction ${queueId} not found.`,
@@ -67,30 +70,31 @@ export const cancelTransactionAndUpdate = async ({
6770
switch (txData.status) {
6871
case TransactionStatusEnum.Errored:
6972
error = createCustomError(
70-
`Cannot cancel errored transaction with queueId ${queueId}. Error: ${txData.errorMessage}`,
73+
`Transaction has already errored: ${txData.errorMessage}`,
7174
StatusCodes.BAD_REQUEST,
7275
"TransactionErrored",
7376
);
7477
break;
7578
case TransactionStatusEnum.Cancelled:
7679
error = createCustomError(
77-
`Transaction already cancelled with queueId ${queueId}`,
80+
"Transaction is already cancelled.",
7881
StatusCodes.BAD_REQUEST,
7982
"TransactionAlreadyCancelled",
8083
);
8184
break;
8285
case TransactionStatusEnum.Queued:
8386
await updateTx({
8487
queueId,
88+
pgtx,
8589
data: {
8690
status: TransactionStatusEnum.Cancelled,
8791
},
8892
});
89-
message = "Transaction cancelled on-database successfully.";
93+
message = "Transaction cancelled successfully.";
9094
break;
9195
case TransactionStatusEnum.Mined:
9296
error = createCustomError(
93-
`Transaction already mined with queueId ${queueId}`,
97+
"Transaction already mined.",
9498
StatusCodes.BAD_REQUEST,
9599
"TransactionAlreadyMined",
96100
);
@@ -105,10 +109,8 @@ export const cancelTransactionAndUpdate = async ({
105109
const txReceipt = await sdk
106110
.getProvider()
107111
.getTransactionReceipt(txData.transactionHash!);
108-
109112
if (txReceipt) {
110-
message =
111-
"Transaction already mined. Cannot cancel transaction on-chain.";
113+
message = "Transaction already mined.";
112114
break;
113115
}
114116

@@ -119,17 +121,14 @@ export const cancelTransactionAndUpdate = async ({
119121
data: "0x",
120122
value: "0x00",
121123
nonce: txData.nonce!,
122-
...gasOverrides,
123-
maxFeePerGas: BigNumber.from(gasOverrides.maxFeePerGas).mul(2),
124-
maxPriorityFeePerGas: BigNumber.from(
125-
gasOverrides.maxPriorityFeePerGas,
126-
).mul(2),
124+
...multiplyGasOverrides(gasOverrides, 2),
127125
});
128126

129-
message = "Cancellation Transaction sent on chain successfully.";
127+
message = "Transaction cancelled successfully.";
130128

131129
await updateTx({
132130
queueId,
131+
pgtx,
133132
data: {
134133
status: TransactionStatusEnum.Cancelled,
135134
},

src/tests/gas.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Transactions } from ".prisma/client";
22
import { getDefaultGasOverrides } from "@thirdweb-dev/sdk";
33
import { BigNumber, ethers, providers } from "ethers";
4-
import { getGasSettingsForRetry } from "../utils/gas";
4+
import { getGasSettingsForRetry, multiplyGasOverrides } from "../utils/gas";
55

66
jest.mock("@thirdweb-dev/sdk");
77
const mockGetDefaultGasOverrides =
@@ -139,3 +139,36 @@ describe("getGasSettingsForRetry", () => {
139139
});
140140
});
141141
});
142+
143+
describe("multiplyGasOverrides", () => {
144+
const gasOverridesLegacy = {
145+
gasPrice: BigNumber.from(100),
146+
};
147+
const gasOverridesEip1155 = {
148+
maxFeePerGas: BigNumber.from(50),
149+
maxPriorityFeePerGas: BigNumber.from(10),
150+
};
151+
152+
it("should multiply gasPrice by given factor", () => {
153+
const result = multiplyGasOverrides(gasOverridesLegacy, 2);
154+
expect(result.gasPrice).toEqual(BigNumber.from(200));
155+
});
156+
157+
it("should multiply maxFeePerGas and maxPriorityFeePerGas by given factor", () => {
158+
const result = multiplyGasOverrides(gasOverridesEip1155, 3);
159+
expect(result.maxFeePerGas).toEqual(BigNumber.from(150));
160+
expect(result.maxPriorityFeePerGas).toEqual(BigNumber.from(30));
161+
});
162+
163+
it("should handle non-integer multiplication factor", () => {
164+
const result = multiplyGasOverrides(gasOverridesEip1155, 1.5);
165+
expect(result.maxFeePerGas).toEqual(BigNumber.from(75));
166+
expect(result.maxPriorityFeePerGas).toEqual(BigNumber.from(15));
167+
});
168+
169+
it("should handle multiplication by zero", () => {
170+
const result = multiplyGasOverrides(gasOverridesEip1155, 0);
171+
expect(result.maxFeePerGas).toEqual(BigNumber.from(0));
172+
expect(result.maxPriorityFeePerGas).toEqual(BigNumber.from(0));
173+
});
174+
});

src/utils/gas.ts

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { getDefaultGasOverrides } from "@thirdweb-dev/sdk";
33
import { BigNumber, providers } from "ethers";
44
import { maxBN } from "./bigNumber";
55

6+
type gasOverridesReturnType = Awaited<
7+
ReturnType<typeof getDefaultGasOverrides>
8+
>;
9+
610
/**
711
*
812
* @param tx
@@ -12,43 +16,70 @@ import { maxBN } from "./bigNumber";
1216
export const getGasSettingsForRetry = async (
1317
tx: Transactions,
1418
provider: providers.StaticJsonRpcProvider,
15-
): ReturnType<typeof getDefaultGasOverrides> => {
16-
// Default: get gas settings from chain.
17-
const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } =
18-
await getDefaultGasOverrides(provider);
19+
): Promise<gasOverridesReturnType> => {
20+
// Default: Use 2x gas settings from chain.
21+
const defaultGasOverrides = multiplyGasOverrides(
22+
await getDefaultGasOverrides(provider),
23+
2,
24+
);
1925

2026
// Handle legacy gas format.
21-
if (gasPrice) {
22-
const newGasPrice = gasPrice.mul(2);
27+
if (defaultGasOverrides.gasPrice) {
2328
// Gas settings must be 10% higher than a previous attempt.
24-
const minGasPrice = BigNumber.from(tx.gasPrice!).mul(110).div(100);
25-
29+
const minGasOverrides = multiplyGasOverrides(
30+
{
31+
gasPrice: BigNumber.from(tx.gasPrice),
32+
},
33+
1.1,
34+
);
2635
return {
27-
gasPrice: maxBN(newGasPrice, minGasPrice),
36+
gasPrice: maxBN(defaultGasOverrides.gasPrice, minGasOverrides.gasPrice),
2837
};
2938
}
3039

3140
// Handle EIP 1559 gas format.
32-
let newMaxFeePerGas = maxFeePerGas.mul(2);
33-
let newMaxPriorityFeePerGas = maxPriorityFeePerGas.mul(2);
34-
3541
if (tx.retryGasValues) {
3642
// If this tx is manually retried, override with provided gas settings.
37-
newMaxFeePerGas = BigNumber.from(tx.retryMaxFeePerGas!);
38-
newMaxPriorityFeePerGas = BigNumber.from(tx.retryMaxPriorityFeePerGas!);
43+
defaultGasOverrides.maxFeePerGas = BigNumber.from(tx.retryMaxFeePerGas!);
44+
defaultGasOverrides.maxPriorityFeePerGas = BigNumber.from(
45+
tx.retryMaxPriorityFeePerGas!,
46+
);
3947
}
4048

41-
// Gas settings muset be 10% higher than a previous attempt.
42-
const minMaxFeePerGas = BigNumber.from(tx.maxFeePerGas!).mul(110).div(100);
43-
const minMaxPriorityFeePerGas = BigNumber.from(tx.maxPriorityFeePerGas!)
44-
.mul(110)
45-
.div(100);
46-
49+
// Gas settings must be 10% higher than a previous attempt.
50+
const minGasOverrides = multiplyGasOverrides(
51+
{
52+
maxFeePerGas: BigNumber.from(tx.maxFeePerGas!),
53+
maxPriorityFeePerGas: BigNumber.from(tx.maxPriorityFeePerGas!),
54+
},
55+
1.1,
56+
);
4757
return {
48-
maxFeePerGas: maxBN(newMaxFeePerGas, minMaxFeePerGas),
58+
maxFeePerGas: maxBN(
59+
defaultGasOverrides.maxFeePerGas,
60+
minGasOverrides.maxFeePerGas,
61+
),
4962
maxPriorityFeePerGas: maxBN(
50-
newMaxPriorityFeePerGas,
51-
minMaxPriorityFeePerGas,
63+
defaultGasOverrides.maxPriorityFeePerGas,
64+
minGasOverrides.maxPriorityFeePerGas,
5265
),
5366
};
5467
};
68+
69+
export const multiplyGasOverrides = <T extends gasOverridesReturnType>(
70+
gasOverrides: T,
71+
mul: number,
72+
): T => {
73+
if (gasOverrides.gasPrice) {
74+
return {
75+
gasPrice: multiplyBN(gasOverrides.gasPrice, mul),
76+
} as T;
77+
}
78+
return {
79+
maxFeePerGas: multiplyBN(gasOverrides.maxFeePerGas, mul),
80+
maxPriorityFeePerGas: multiplyBN(gasOverrides.maxPriorityFeePerGas, mul),
81+
} as T;
82+
};
83+
84+
const multiplyBN = (n: BigNumber, mul: number): BigNumber =>
85+
Number.isInteger(mul) ? n.mul(mul) : n.mul(mul * 10_000).div(10_000);

src/worker/tasks/processTx.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export const processTx = async () => {
153153
await Promise.all([
154154
sdk.wallet.getNonce("pending"),
155155
getDefaultGasOverrides(provider),
156-
await provider.getBlockNumber(),
156+
provider.getBlockNumber(),
157157
]);
158158

159159
// - Take the larger of the nonces, and update database nonce to mempool value if mempool is greater

src/worker/tasks/retryTx.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const retryTx = async () => {
2121
async (pgtx) => {
2222
const tx = await getTxToRetry({ pgtx });
2323
if (!tx) {
24+
// Nothing to retry.
2425
return;
2526
}
2627

@@ -49,30 +50,14 @@ export const retryTx = async () => {
4950
return;
5051
}
5152

52-
const gasOverrides = await getGasSettingsForRetry(tx, provider);
53-
if (
54-
gasOverrides.maxFeePerGas?.gt(config.maxFeePerGasForRetries) ||
55-
gasOverrides.maxPriorityFeePerGas?.gt(
56-
config.maxPriorityFeePerGasForRetries,
57-
)
58-
) {
59-
// Return if gas settings exceed configured limits. Try again later.
60-
logger({
61-
service: "worker",
62-
level: "warn",
63-
queueId: tx.id,
64-
message: `${tx.chainId} chain gas price is higher than maximum threshold MaxFeePerGas: ${config.maxFeePerGasForRetries}, MaxPriorityFeePerGas: ${config.maxPriorityFeePerGasForRetries}`,
65-
});
66-
return;
67-
}
68-
6953
logger({
7054
service: "worker",
7155
level: "info",
7256
queueId: tx.id,
7357
message: `Retrying with nonce ${tx.nonce}`,
7458
});
7559

60+
const gasOverrides = await getGasSettingsForRetry(tx, provider);
7661
let res: ethers.providers.TransactionResponse;
7762
const txRequest = {
7863
to: tx.toAddress!,

0 commit comments

Comments
 (0)