From 20121533f974caa9d1d580590a0cd769ee9e0192 Mon Sep 17 00:00:00 2001 From: MoneroOcean <33983510+MoneroOcean@users.noreply.github.com> Date: Wed, 20 May 2026 19:46:04 -0700 Subject: [PATCH] Fix BTC-style pool reward accounting --- lib/coins/btrm.js | 2 ++ lib/coins/core/factories.js | 15 +++++++++--- lib/coins/rtm.js | 2 ++ lib/coins/rvn.js | 1 + lib/coins/xna.js | 1 + tests/pool/coin/basics.js | 49 +++++++++++++++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/lib/coins/btrm.js b/lib/coins/btrm.js index 933262f2b..f31a18a6d 100644 --- a/lib/coins/btrm.js +++ b/lib/coins/btrm.js @@ -5,6 +5,8 @@ module.exports = preset.btcSubmitReserve({ port: 10225, coin: "BTRM", blobType: blob: blob.rtm(), rpc: rpc.btc({ createBlockTemplate: btcTemplate.rtm, + headerRewardMode: "sum-vout", + rewardPoolAddressOnly: true, rewardMultiplier: 100000000, difficultyMultiplier: 0xFFFFFFFF }), diff --git a/lib/coins/core/factories.js b/lib/coins/core/factories.js index 113b6e8f7..f8a2b7927 100644 --- a/lib/coins/core/factories.js +++ b/lib/coins/core/factories.js @@ -45,11 +45,20 @@ function createProfile(spec) { function isCoinProfile(value) { return !!(value && value[COIN_PROFILE_SYMBOL] === true); } -function parseBtcReward(block, config) { +function getBtcVoutAddresses(vout) { + if (!vout || !vout.scriptPubKey) return []; + if (Array.isArray(vout.scriptPubKey.addresses)) return vout.scriptPubKey.addresses; + if (typeof vout.scriptPubKey.address === "string") return [vout.scriptPubKey.address]; + return []; +} + +function parseBtcReward(block, config, poolAddress) { block.reward = 0; for (const vout of block.tx[0].vout) { + const addresses = getBtcVoutAddresses(vout); if (config.headerRewardMode === "sum-vout") { - if (config.rewardIgnoreAddress && vout.scriptPubKey.addresses && vout.scriptPubKey.addresses[0] === config.rewardIgnoreAddress) continue; + if (config.rewardPoolAddressOnly && !addresses.includes(poolAddress)) continue; + if (config.rewardIgnoreAddress && addresses.includes(config.rewardIgnoreAddress)) continue; block.reward += vout.value; } else if (vout.value > block.reward) { block.reward = vout.value; @@ -229,7 +238,7 @@ function createBtcRpc(overrides) { config.getAnyBlockHeaderByHash = function getAnyBlockHeaderByHash(ctx) { ctx.runtime.support.rpcPortDaemon2(ctx.port, "", { method: "getblock", params: [ctx.blockHash, 2] }, function onBlock(body) { if (!body || !body.result || !(body.result.tx instanceof Array) || body.result.tx.length < 1) return ctx.callback(true, body); - return ctx.callback(null, parseBtcReward(body.result, config)); + return ctx.callback(null, parseBtcReward(body.result, config, ctx.runtime.getPoolAddress(ctx.profile))); }, ctx.noErrorReport); }; diff --git a/lib/coins/rtm.js b/lib/coins/rtm.js index 52e9ed60c..7cd6360c5 100644 --- a/lib/coins/rtm.js +++ b/lib/coins/rtm.js @@ -5,6 +5,8 @@ module.exports = preset.btcSubmitReserve({ port: 9998, coin: "RTM", blobType: 10 blob: blob.rtm(), rpc: rpc.btc({ createBlockTemplate: btcTemplate.rtm, + headerRewardMode: "sum-vout", + rewardPoolAddressOnly: true, rewardMultiplier: 100000000, difficultyMultiplier: 0xFFFFFFFF }), diff --git a/lib/coins/rvn.js b/lib/coins/rvn.js index eff9048e3..560569b38 100644 --- a/lib/coins/rvn.js +++ b/lib/coins/rvn.js @@ -8,6 +8,7 @@ module.exports = preset.directReserve({ port: 8766, coin: "RVN", blobType: 101, rpc: rpc.btc({ createBlockTemplate: btcTemplate.raven, headerRewardMode: "sum-vout", + rewardPoolAddressOnly: true, rewardMultiplier: 100000000 }), pow: pow.kawpow() diff --git a/lib/coins/xna.js b/lib/coins/xna.js index 82795114d..e2cc93620 100644 --- a/lib/coins/xna.js +++ b/lib/coins/xna.js @@ -8,6 +8,7 @@ module.exports = preset.directReserve({ port: 19001, coin: "XNA", blobType: 101, rpc: rpc.btc({ createBlockTemplate: btcTemplate.raven, headerRewardMode: "sum-vout", + rewardPoolAddressOnly: true, rewardMultiplier: 100000000 }), pow: pow.kawpow(), diff --git a/tests/pool/coin/basics.js b/tests/pool/coin/basics.js index fcab0eb1c..006978ab6 100644 --- a/tests/pool/coin/basics.js +++ b/tests/pool/coin/basics.js @@ -333,6 +333,55 @@ test("wallet reward selectors stay on the coin profiles for asset-aware chains", }), 7); }); + +test("raven-style reward accounting only credits coinbase outputs paid to the pool address", async () => { + const coinFuncs = global.coinFuncs.__realCoinFuncs; + const rvnRpc = coinFuncs.getRpcSettings("RVN"); + const poolAddress = "RPoolRewardAddress111111111111111111"; + const thirdPartyAddress = "RThirdPartyAddress1111111111111111"; + let callbackArgs = null; + + await new Promise((resolve) => { + rvnRpc.getAnyBlockHeaderByHash({ + blockHash: "aa".repeat(32), + callback(err, body) { + callbackArgs = { err, body }; + resolve(); + }, + isOurBlock: true, + noErrorReport: true, + port: 8766, + profile: coinFuncs.getPoolProfile("RVN"), + runtime: { + getPoolAddress() { return poolAddress; }, + support: { + rpcPortDaemon2(_port, _method, params, callback) { + assert.deepEqual(params, { method: "getblock", params: ["aa".repeat(32), 2] }); + callback({ + result: { + hash: "aa".repeat(32), + height: 123, + difficulty: 456, + tx: [{ + vout: [ + { value: 50, scriptPubKey: { addresses: [poolAddress] } }, + { value: 12.5, scriptPubKey: { addresses: [thirdPartyAddress] } }, + { value: 25, scriptPubKey: { address: poolAddress } }, + { value: 1, scriptPubKey: { addresses: [] } } + ] + }] + } + }); + } + } + } + }); + }); + + assert.equal(callbackArgs.err, null); + assert.equal(callbackArgs.body.reward, 7500000000); +}); + test("eth-style hash lookups preserve hex block heights when deriving canonical headers", async () => { const coinFuncs = global.coinFuncs.__realCoinFuncs; const etcRpc = coinFuncs.getRpcSettings("ETC");