Skip to content

Commit 85b2aff

Browse files
authored
feat(experimental): Add an env var to limit the max gas price for resends (#800)
* set max gas price per wei * bigMath * add unit tests * rename
1 parent c7f1237 commit 85b2aff

File tree

4 files changed

+100
-9
lines changed

4 files changed

+100
-9
lines changed

src/tests/math.test.ts

+79-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from "vitest";
2-
import { getPercentile } from "../utils/math";
2+
import { bigMath, getPercentile } from "../utils/math";
33

44
describe("getPercentile", () => {
55
it("should correctly calculate the p50 (median) of a sorted array", () => {
@@ -27,3 +27,81 @@ describe("getPercentile", () => {
2727
expect(getPercentile(numbers, 50)).toBe(0);
2828
});
2929
});
30+
31+
describe("bigMath", () => {
32+
describe("min", () => {
33+
it("should return the smaller of two positive numbers", () => {
34+
const a = 5n;
35+
const b = 10n;
36+
expect(bigMath.min(a, b)).toBe(5n);
37+
});
38+
39+
it("should return the smaller of two negative numbers", () => {
40+
const a = -10n;
41+
const b = -5n;
42+
expect(bigMath.min(a, b)).toBe(-10n);
43+
});
44+
45+
it("should handle equal numbers", () => {
46+
const a = 5n;
47+
const b = 5n;
48+
expect(bigMath.min(a, b)).toBe(5n);
49+
});
50+
51+
it("should handle zero and positive number", () => {
52+
const a = 0n;
53+
const b = 5n;
54+
expect(bigMath.min(a, b)).toBe(0n);
55+
});
56+
57+
it("should handle zero and negative number", () => {
58+
const a = 0n;
59+
const b = -5n;
60+
expect(bigMath.min(a, b)).toBe(-5n);
61+
});
62+
63+
it("should handle very large numbers", () => {
64+
const a = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
65+
const b = BigInt(Number.MAX_SAFE_INTEGER);
66+
expect(bigMath.min(a, b)).toBe(b);
67+
});
68+
});
69+
70+
describe("max", () => {
71+
it("should return the larger of two positive numbers", () => {
72+
const a = 5n;
73+
const b = 10n;
74+
expect(bigMath.max(a, b)).toBe(10n);
75+
});
76+
77+
it("should return the larger of two negative numbers", () => {
78+
const a = -10n;
79+
const b = -5n;
80+
expect(bigMath.max(a, b)).toBe(-5n);
81+
});
82+
83+
it("should handle equal numbers", () => {
84+
const a = 5n;
85+
const b = 5n;
86+
expect(bigMath.max(a, b)).toBe(5n);
87+
});
88+
89+
it("should handle zero and positive number", () => {
90+
const a = 0n;
91+
const b = 5n;
92+
expect(bigMath.max(a, b)).toBe(5n);
93+
});
94+
95+
it("should handle zero and negative number", () => {
96+
const a = 0n;
97+
const b = -5n;
98+
expect(bigMath.max(a, b)).toBe(0n);
99+
});
100+
101+
it("should handle very large numbers", () => {
102+
const a = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
103+
const b = BigInt(Number.MAX_SAFE_INTEGER);
104+
expect(bigMath.max(a, b)).toBe(a);
105+
});
106+
});
107+
});

src/utils/env.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,12 @@ export const env = createEnv({
102102
/**
103103
* Experimental env vars. These may be renamed or removed in future non-major releases.
104104
*/
105-
// Sets how long the mine worker waits for a transaction receipt before considering the transaction dropped (default: 30 minutes).
105+
// Sets how long the mine worker waits for a transaction receipt before considering the transaction dropped. Default: 30 minutes
106106
EXPERIMENTAL__MINE_WORKER_TIMEOUT_SECONDS: z.coerce
107107
.number()
108108
.default(30 * 60),
109+
// Sets the max gas price for a transaction attempt. Most RPCs reject transactions above a certain gas price. Default: 10^18 wei.
110+
EXPERIMENTAL__MAX_GAS_PRICE_WEI: z.coerce.number().default(10 ** 18),
109111
},
110112
clientPrefix: "NEVER_USED",
111113
client: {},
@@ -144,6 +146,8 @@ export const env = createEnv({
144146
NONCE_MAP_COUNT: process.env.NONCE_MAP_COUNT,
145147
EXPERIMENTAL__MINE_WORKER_TIMEOUT_SECONDS:
146148
process.env.EXPERIMENTAL__MINE_WORKER_TIMEOUT_SECONDS,
149+
EXPERIMENTAL__MAX_GAS_PRICE_WEI:
150+
process.env.EXPERIMENTAL__MAX_GAS_PRICE_WEI,
147151
METRICS_PORT: process.env.METRICS_PORT,
148152
METRICS_ENABLED: process.env.METRICS_ENABLED,
149153
},

src/utils/math.ts

+5
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ export const getPercentile = (arr: number[], percentile: number): number => {
77
const index = Math.floor((percentile / 100) * (arr.length - 1));
88
return arr[index];
99
};
10+
11+
export const BigIntMath = {
12+
min: (a: bigint, b: bigint) => (a < b ? a : b),
13+
max: (a: bigint, b: bigint) => (a > b ? a : b),
14+
};

src/worker/tasks/sendTransactionWorker.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
isReplacementGasFeeTooLow,
4040
wrapError,
4141
} from "../../utils/error";
42+
import { BigIntMath } from "../../utils/math";
4243
import { getChecksumAddress } from "../../utils/primitiveTypes";
4344
import { recordMetrics } from "../../utils/prometheus";
4445
import { redis } from "../../utils/redis/redis";
@@ -565,34 +566,37 @@ const _minutesFromNow = (minutes: number) =>
565566
* @param populatedTransaction The transaction with estimated gas from RPC.
566567
* @param resendCount The resend attempt #. Example: 2 = the transaction was initially sent, then resent once. This is the second resend attempt.
567568
*/
568-
export const _updateGasFees = (
569+
export function _updateGasFees(
569570
populatedTransaction: PopulatedTransaction,
570571
resendCount: number,
571572
overrides: SentTransaction["overrides"],
572-
): PopulatedTransaction => {
573+
): PopulatedTransaction {
573574
if (resendCount === 0) {
574575
return populatedTransaction;
575576
}
576577

577-
const multiplier = BigInt(Math.min(10, resendCount * 2));
578-
578+
const multiplier = BigIntMath.min(10n, BigInt(resendCount) * 2n);
579579
const updated = { ...populatedTransaction };
580580

581581
// Update gas fees (unless they were explicitly overridden).
582+
// Do not exceed MAX_GAS_PRICE_WEI.
583+
const MAX_GAS_PRICE_WEI = BigInt(env.EXPERIMENTAL__MAX_GAS_PRICE_WEI);
582584

583585
if (updated.gasPrice && !overrides?.gasPrice) {
584-
updated.gasPrice *= multiplier;
586+
const newGasPrice = updated.gasPrice * multiplier;
587+
updated.gasPrice = BigIntMath.min(newGasPrice, MAX_GAS_PRICE_WEI);
585588
}
586589
if (updated.maxPriorityFeePerGas && !overrides?.maxPriorityFeePerGas) {
587590
updated.maxPriorityFeePerGas *= multiplier;
588591
}
589592
if (updated.maxFeePerGas && !overrides?.maxFeePerGas) {
590-
updated.maxFeePerGas =
593+
const newMaxFeePerGas =
591594
updated.maxFeePerGas * 2n + (updated.maxPriorityFeePerGas ?? 0n);
595+
updated.maxFeePerGas = BigIntMath.min(newMaxFeePerGas, MAX_GAS_PRICE_WEI);
592596
}
593597

594598
return updated;
595-
};
599+
}
596600

597601
// Must be explicitly called for the worker to run on this host.
598602
export const initSendTransactionWorker = () => {

0 commit comments

Comments
 (0)