diff --git a/service/src/modules/chores.js b/service/src/modules/chores.js index 30de7d9..20ab017 100644 --- a/service/src/modules/chores.js +++ b/service/src/modules/chores.js @@ -3,21 +3,18 @@ import { Data, RPC, Eth, Bsc } from "./index.js"; import Transaction from "../models/Transaction.js"; import Transfer from "../models/Transfer.js"; import Conversion from "../models/Conversion.js"; +import { resolveStartingNonce, reserveNextNonce } from "../utils/nonce.js"; let ethNonce = 0; let bscNonce = 0; -function setupNonce(){ - if ((ethNonce == 0) && (nonces.eth == 0)) { - ethNonce = Eth.getTransactionCount(); - } else if ((nonces.eth > 0) && (nonces.eth > ethNonce)) { - ethNonce = nonces.eth; - } - if ((bscNonce == 0) && (nonces.bsc == 0)) { - bscNonce = Bsc.getTransactionCount(); - } else if ((nonces.bsc > 0) && (nonces.bsc > bscNonce)) { - bscNonce = nonces.bsc; - } +async function setupNonce() { + ethNonce = await resolveStartingNonce(ethNonce, nonces.eth, () => + Eth.getTransactionCount(), + ); + bscNonce = await resolveStartingNonce(bscNonce, nonces.bsc, () => + Bsc.getTransactionCount(), + ); } const expireDate = () => { @@ -71,7 +68,7 @@ async function checkBglTransactions() { blockHash || undefined, confirmations.bgl, ); - setupNonce(); + await setupNonce(); result.transactions .filter( (tx) => @@ -122,20 +119,21 @@ async function checkBglTransactions() { } try { - const AssignedNonce = transfer.chain === "bsc" ? Bsc : Eth; if (transfer.chain === "bsc") { - bscNonce += 1; + const reserved = reserveNextNonce(bscNonce); + bscNonce = reserved.nextNonce; conversion.txid = await Chain.sendWBGL( transfer.to, amount.toString(), - bscNonce + reserved.nonce, ); } else { - ethNonce += 1; + const reserved = reserveNextNonce(ethNonce); + ethNonce = reserved.nextNonce; conversion.txid = await Chain.sendWBGL( transfer.to, amount.toString(), - ethNonce + reserved.nonce, ); } await conversion.save(); diff --git a/service/src/tests/nonce.js b/service/src/tests/nonce.js new file mode 100644 index 0000000..0ba7167 --- /dev/null +++ b/service/src/tests/nonce.js @@ -0,0 +1,59 @@ +import assert from "assert"; +import { + parseConfiguredNonce, + resolveStartingNonce, + reserveNextNonce, +} from "../utils/nonce.js"; + +describe("nonce helpers", function () { + describe("#parseConfiguredNonce", function () { + it("uses the first populated positive integer value", function () { + assert.equal(parseConfiguredNonce(undefined, "", "12"), 12); + }); + + it("returns zero for missing, invalid, or non-positive values", function () { + assert.equal(parseConfiguredNonce(), 0); + assert.equal(parseConfiguredNonce("abc"), 0); + assert.equal(parseConfiguredNonce("-1"), 0); + assert.equal(parseConfiguredNonce("0"), 0); + }); + }); + + describe("#resolveStartingNonce", function () { + it("awaits the chain nonce when no local or configured nonce is available", async function () { + let calls = 0; + const nonce = await resolveStartingNonce(0, 0, async () => { + calls += 1; + return 42; + }); + + assert.equal(nonce, 42); + assert.equal(calls, 1); + }); + + it("uses a configured nonce when it is ahead of the local nonce", async function () { + const nonce = await resolveStartingNonce(5, 8, async () => { + throw new Error("chain nonce should not be fetched"); + }); + + assert.equal(nonce, 8); + }); + + it("keeps the local nonce when it is already ahead", async function () { + const nonce = await resolveStartingNonce(9, 8, async () => { + throw new Error("chain nonce should not be fetched"); + }); + + assert.equal(nonce, 9); + }); + }); + + describe("#reserveNextNonce", function () { + it("returns the nonce to send and the next local counter", function () { + assert.deepEqual(reserveNextNonce(12), { + nonce: 12, + nextNonce: 13, + }); + }); + }); +}); diff --git a/service/src/utils/config.js b/service/src/utils/config.js index 33f8f94..e34894d 100644 --- a/service/src/utils/config.js +++ b/service/src/utils/config.js @@ -1,4 +1,5 @@ -import dotenv from 'dotenv'; +import dotenv from "dotenv"; +import { parseConfiguredNonce } from "./nonce.js"; dotenv.config(); export const env = process.env.NODE_ENV || "development"; @@ -48,6 +49,6 @@ export const confirmations = { export const feePercentage = process.env.FEE_PERCENTAGE || 1; export const nonces = { - bsc: parseInt(process.env.BSC_NONCE) || 0, - eth: parseInt(process.env.ETH__NONCE) || 0, + bsc: parseConfiguredNonce(process.env.BSC_NONCE), + eth: parseConfiguredNonce(process.env.ETH_NONCE, process.env.ETH__NONCE), }; diff --git a/service/src/utils/nonce.js b/service/src/utils/nonce.js new file mode 100644 index 0000000..55a81fa --- /dev/null +++ b/service/src/utils/nonce.js @@ -0,0 +1,29 @@ +export const parseConfiguredNonce = (...values) => { + for (const value of values) { + if (value === undefined || value === null || value === "") { + continue; + } + const parsed = Number.parseInt(value, 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : 0; + } + return 0; +}; + +export const resolveStartingNonce = async ( + currentNonce, + configuredNonce, + fetchCurrentNonce, +) => { + if (currentNonce === 0 && configuredNonce === 0) { + return await fetchCurrentNonce(); + } + if (configuredNonce > 0 && configuredNonce > currentNonce) { + return configuredNonce; + } + return currentNonce; +}; + +export const reserveNextNonce = (currentNonce) => ({ + nonce: currentNonce, + nextNonce: currentNonce + 1, +});