Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(experimental): Add an env var to limit the max gas price for resends #800

Merged
merged 5 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion src/tests/math.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { getPercentile } from "../utils/math";
import { bigMath, getPercentile } from "../utils/math";

describe("getPercentile", () => {
it("should correctly calculate the p50 (median) of a sorted array", () => {
Expand Down Expand Up @@ -27,3 +27,81 @@ describe("getPercentile", () => {
expect(getPercentile(numbers, 50)).toBe(0);
});
});

describe("bigMath", () => {
describe("min", () => {
it("should return the smaller of two positive numbers", () => {
const a = 5n;
const b = 10n;
expect(bigMath.min(a, b)).toBe(5n);
});

it("should return the smaller of two negative numbers", () => {
const a = -10n;
const b = -5n;
expect(bigMath.min(a, b)).toBe(-10n);
});

it("should handle equal numbers", () => {
const a = 5n;
const b = 5n;
expect(bigMath.min(a, b)).toBe(5n);
});

it("should handle zero and positive number", () => {
const a = 0n;
const b = 5n;
expect(bigMath.min(a, b)).toBe(0n);
});

it("should handle zero and negative number", () => {
const a = 0n;
const b = -5n;
expect(bigMath.min(a, b)).toBe(-5n);
});

it("should handle very large numbers", () => {
const a = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
const b = BigInt(Number.MAX_SAFE_INTEGER);
expect(bigMath.min(a, b)).toBe(b);
});
});

describe("max", () => {
it("should return the larger of two positive numbers", () => {
const a = 5n;
const b = 10n;
expect(bigMath.max(a, b)).toBe(10n);
});

it("should return the larger of two negative numbers", () => {
const a = -10n;
const b = -5n;
expect(bigMath.max(a, b)).toBe(-5n);
});

it("should handle equal numbers", () => {
const a = 5n;
const b = 5n;
expect(bigMath.max(a, b)).toBe(5n);
});

it("should handle zero and positive number", () => {
const a = 0n;
const b = 5n;
expect(bigMath.max(a, b)).toBe(5n);
});

it("should handle zero and negative number", () => {
const a = 0n;
const b = -5n;
expect(bigMath.max(a, b)).toBe(0n);
});

it("should handle very large numbers", () => {
const a = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
const b = BigInt(Number.MAX_SAFE_INTEGER);
expect(bigMath.max(a, b)).toBe(a);
});
});
});
6 changes: 5 additions & 1 deletion src/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ export const env = createEnv({
/**
* Experimental env vars. These may be renamed or removed in future non-major releases.
*/
// Sets how long the mine worker waits for a transaction receipt before considering the transaction dropped (default: 30 minutes).
// Sets how long the mine worker waits for a transaction receipt before considering the transaction dropped. Default: 30 minutes
EXPERIMENTAL__MINE_WORKER_TIMEOUT_SECONDS: z.coerce
.number()
.default(30 * 60),
// Sets the max gas price for a transaction attempt. Most RPCs reject transactions above a certain gas price. Default: 10^18 wei.
EXPERIMENTAL__MAX_GAS_PRICE_WEI: z.coerce.number().default(10 ** 18),
},
clientPrefix: "NEVER_USED",
client: {},
Expand Down Expand Up @@ -144,6 +146,8 @@ export const env = createEnv({
NONCE_MAP_COUNT: process.env.NONCE_MAP_COUNT,
EXPERIMENTAL__MINE_WORKER_TIMEOUT_SECONDS:
process.env.EXPERIMENTAL__MINE_WORKER_TIMEOUT_SECONDS,
EXPERIMENTAL__MAX_GAS_PRICE_WEI:
process.env.EXPERIMENTAL__MAX_GAS_PRICE_WEI,
METRICS_PORT: process.env.METRICS_PORT,
METRICS_ENABLED: process.env.METRICS_ENABLED,
},
Expand Down
5 changes: 5 additions & 0 deletions src/utils/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ export const getPercentile = (arr: number[], percentile: number): number => {
const index = Math.floor((percentile / 100) * (arr.length - 1));
return arr[index];
};

export const BigIntMath = {
min: (a: bigint, b: bigint) => (a < b ? a : b),
max: (a: bigint, b: bigint) => (a > b ? a : b),
};
18 changes: 11 additions & 7 deletions src/worker/tasks/sendTransactionWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
isReplacementGasFeeTooLow,
wrapError,
} from "../../utils/error";
import { BigIntMath } from "../../utils/math";
import { getChecksumAddress } from "../../utils/primitiveTypes";
import { recordMetrics } from "../../utils/prometheus";
import { redis } from "../../utils/redis/redis";
Expand Down Expand Up @@ -565,34 +566,37 @@ const _minutesFromNow = (minutes: number) =>
* @param populatedTransaction The transaction with estimated gas from RPC.
* @param resendCount The resend attempt #. Example: 2 = the transaction was initially sent, then resent once. This is the second resend attempt.
*/
export const _updateGasFees = (
export function _updateGasFees(
populatedTransaction: PopulatedTransaction,
resendCount: number,
overrides: SentTransaction["overrides"],
): PopulatedTransaction => {
): PopulatedTransaction {
if (resendCount === 0) {
return populatedTransaction;
}

const multiplier = BigInt(Math.min(10, resendCount * 2));

const multiplier = BigIntMath.min(10n, BigInt(resendCount) * 2n);
const updated = { ...populatedTransaction };

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

if (updated.gasPrice && !overrides?.gasPrice) {
updated.gasPrice *= multiplier;
const newGasPrice = updated.gasPrice * multiplier;
updated.gasPrice = BigIntMath.min(newGasPrice, MAX_GAS_PRICE_WEI);
}
if (updated.maxPriorityFeePerGas && !overrides?.maxPriorityFeePerGas) {
updated.maxPriorityFeePerGas *= multiplier;
}
if (updated.maxFeePerGas && !overrides?.maxFeePerGas) {
updated.maxFeePerGas =
const newMaxFeePerGas =
updated.maxFeePerGas * 2n + (updated.maxPriorityFeePerGas ?? 0n);
updated.maxFeePerGas = BigIntMath.min(newMaxFeePerGas, MAX_GAS_PRICE_WEI);
}

return updated;
};
}

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