From af74b4ab900d6bd862bc7c17bc65e2a782ac10a6 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Mon, 9 Dec 2024 21:50:20 +0800 Subject: [PATCH 1/4] set max gas price per wei --- src/utils/env.ts | 6 +++++- src/worker/tasks/sendTransactionWorker.ts | 25 ++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/utils/env.ts b/src/utils/env.ts index e93a0320..3fa84dcc 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -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: {}, @@ -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, }, diff --git a/src/worker/tasks/sendTransactionWorker.ts b/src/worker/tasks/sendTransactionWorker.ts index c8d25458..a8e29b57 100644 --- a/src/worker/tasks/sendTransactionWorker.ts +++ b/src/worker/tasks/sendTransactionWorker.ts @@ -565,34 +565,39 @@ 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 = Math.min(10, resendCount * 2); const updated = { ...populatedTransaction }; // Update gas fees (unless they were explicitly overridden). + // Do not exceed MAX_GAS_PRICE_WEI. + const MAX_GAS_PRICE_WEI = env.EXPERIMENTAL__MAX_GAS_PRICE_WEI; - if (updated.gasPrice && !overrides?.gasPrice) { - updated.gasPrice *= multiplier; + if (updated.gasPrice) { + const newGasPrice = Number(updated.gasPrice) * multiplier; + updated.gasPrice = BigInt(Math.min(newGasPrice, MAX_GAS_PRICE_WEI)); } if (updated.maxPriorityFeePerGas && !overrides?.maxPriorityFeePerGas) { - updated.maxPriorityFeePerGas *= multiplier; + updated.maxPriorityFeePerGas *= BigInt(multiplier); } if (updated.maxFeePerGas && !overrides?.maxFeePerGas) { - updated.maxFeePerGas = - updated.maxFeePerGas * 2n + (updated.maxPriorityFeePerGas ?? 0n); + const maxPriorityFeePerGas = updated.maxPriorityFeePerGas ?? 0n; + const newMaxFeePerGas = Number( + updated.maxFeePerGas * 2n + maxPriorityFeePerGas, + ); + updated.maxFeePerGas = BigInt(Math.min(newMaxFeePerGas, MAX_GAS_PRICE_WEI)); } return updated; -}; +} // Must be explicitly called for the worker to run on this host. export const initSendTransactionWorker = () => { From 258995fcac1ec3fe9e05738f24bc355de3763b12 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Mon, 9 Dec 2024 21:56:53 +0800 Subject: [PATCH 2/4] bigMath --- src/utils/math.ts | 5 +++++ src/worker/tasks/sendTransactionWorker.ts | 21 ++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/utils/math.ts b/src/utils/math.ts index 2f693d66..e2eafb93 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -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 bigMath = { + min: (a: bigint, b: bigint) => (a < b ? a : b), + max: (a: bigint, b: bigint) => (a > b ? a : b), +}; diff --git a/src/worker/tasks/sendTransactionWorker.ts b/src/worker/tasks/sendTransactionWorker.ts index a8e29b57..62211223 100644 --- a/src/worker/tasks/sendTransactionWorker.ts +++ b/src/worker/tasks/sendTransactionWorker.ts @@ -39,6 +39,7 @@ import { isReplacementGasFeeTooLow, wrapError, } from "../../utils/error"; +import { bigMath } from "../../utils/math"; import { getChecksumAddress } from "../../utils/primitiveTypes"; import { recordMetrics } from "../../utils/prometheus"; import { redis } from "../../utils/redis/redis"; @@ -574,26 +575,24 @@ export function _updateGasFees( return populatedTransaction; } - const multiplier = Math.min(10, resendCount * 2); + const multiplier = bigMath.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 = env.EXPERIMENTAL__MAX_GAS_PRICE_WEI; + const MAX_GAS_PRICE_WEI = BigInt(env.EXPERIMENTAL__MAX_GAS_PRICE_WEI); - if (updated.gasPrice) { - const newGasPrice = Number(updated.gasPrice) * multiplier; - updated.gasPrice = BigInt(Math.min(newGasPrice, MAX_GAS_PRICE_WEI)); + if (updated.gasPrice && !overrides?.gasPrice) { + const newGasPrice = updated.gasPrice * multiplier; + updated.gasPrice = bigMath.min(newGasPrice, MAX_GAS_PRICE_WEI); } if (updated.maxPriorityFeePerGas && !overrides?.maxPriorityFeePerGas) { - updated.maxPriorityFeePerGas *= BigInt(multiplier); + updated.maxPriorityFeePerGas *= multiplier; } if (updated.maxFeePerGas && !overrides?.maxFeePerGas) { - const maxPriorityFeePerGas = updated.maxPriorityFeePerGas ?? 0n; - const newMaxFeePerGas = Number( - updated.maxFeePerGas * 2n + maxPriorityFeePerGas, - ); - updated.maxFeePerGas = BigInt(Math.min(newMaxFeePerGas, MAX_GAS_PRICE_WEI)); + const newMaxFeePerGas = + updated.maxFeePerGas * 2n + (updated.maxPriorityFeePerGas ?? 0n); + updated.maxFeePerGas = bigMath.min(newMaxFeePerGas, MAX_GAS_PRICE_WEI); } return updated; From a2fbc6b9eba4c74bc097eb13a416eefd9414b773 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Mon, 9 Dec 2024 21:58:56 +0800 Subject: [PATCH 3/4] add unit tests --- src/tests/math.test.ts | 80 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/tests/math.test.ts b/src/tests/math.test.ts index 6303d6be..7fe88c30 100644 --- a/src/tests/math.test.ts +++ b/src/tests/math.test.ts @@ -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", () => { @@ -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); + }); + }); +}); From 82d49244d9daae4bafbded16bf0adcf5be380476 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Mon, 9 Dec 2024 21:59:58 +0800 Subject: [PATCH 4/4] rename --- src/utils/math.ts | 2 +- src/worker/tasks/sendTransactionWorker.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/math.ts b/src/utils/math.ts index e2eafb93..5ec56723 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -8,7 +8,7 @@ export const getPercentile = (arr: number[], percentile: number): number => { return arr[index]; }; -export const bigMath = { +export const BigIntMath = { min: (a: bigint, b: bigint) => (a < b ? a : b), max: (a: bigint, b: bigint) => (a > b ? a : b), }; diff --git a/src/worker/tasks/sendTransactionWorker.ts b/src/worker/tasks/sendTransactionWorker.ts index 62211223..9dff3c4b 100644 --- a/src/worker/tasks/sendTransactionWorker.ts +++ b/src/worker/tasks/sendTransactionWorker.ts @@ -39,7 +39,7 @@ import { isReplacementGasFeeTooLow, wrapError, } from "../../utils/error"; -import { bigMath } from "../../utils/math"; +import { BigIntMath } from "../../utils/math"; import { getChecksumAddress } from "../../utils/primitiveTypes"; import { recordMetrics } from "../../utils/prometheus"; import { redis } from "../../utils/redis/redis"; @@ -575,7 +575,7 @@ export function _updateGasFees( return populatedTransaction; } - const multiplier = bigMath.min(10n, BigInt(resendCount) * 2n); + const multiplier = BigIntMath.min(10n, BigInt(resendCount) * 2n); const updated = { ...populatedTransaction }; // Update gas fees (unless they were explicitly overridden). @@ -584,7 +584,7 @@ export function _updateGasFees( if (updated.gasPrice && !overrides?.gasPrice) { const newGasPrice = updated.gasPrice * multiplier; - updated.gasPrice = bigMath.min(newGasPrice, MAX_GAS_PRICE_WEI); + updated.gasPrice = BigIntMath.min(newGasPrice, MAX_GAS_PRICE_WEI); } if (updated.maxPriorityFeePerGas && !overrides?.maxPriorityFeePerGas) { updated.maxPriorityFeePerGas *= multiplier; @@ -592,7 +592,7 @@ export function _updateGasFees( if (updated.maxFeePerGas && !overrides?.maxFeePerGas) { const newMaxFeePerGas = updated.maxFeePerGas * 2n + (updated.maxPriorityFeePerGas ?? 0n); - updated.maxFeePerGas = bigMath.min(newMaxFeePerGas, MAX_GAS_PRICE_WEI); + updated.maxFeePerGas = BigIntMath.min(newMaxFeePerGas, MAX_GAS_PRICE_WEI); } return updated;