Skip to content

Commit b08f479

Browse files
committed
Fix XTM-T proxy nonce layout
1 parent f65e7c4 commit b08f479

10 files changed

Lines changed: 208 additions & 21 deletions

File tree

deployment/deploy.bash

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,9 @@ Description=Tari Daemon
447447
After=network.target
448448
449449
[Service]
450-
ExecStart=/bin/bash -c "(sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18146 18142) & (sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18148 18142) & /usr/local/src/tari/target/release/minotari_node --non-interactive-mode --watch status --disable-splash-screen"
450+
# Tari SubmitBlock JSON bodies can exceed grpc-json-proxy's 1 MiB default when
451+
# the block carries a large proof body.
452+
ExecStart=/bin/bash -c "(sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18146 18142 --max-body-bytes 16777216) & (sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18148 18142 --max-body-bytes 16777216) & /usr/local/src/tari/target/release/minotari_node --non-interactive-mode --watch status --disable-splash-screen"
451453
Restart=always
452454
User=$TARI_USER
453455
Environment=HOME=$TARI_HOME

deployment/leaf.bash

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,9 @@ Description=Tari Daemon
314314
After=network.target
315315
316316
[Service]
317-
ExecStart=/bin/bash -c "(sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18146 18142) & (sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18148 18142) & /usr/local/src/tari/target/release/minotari_node --non-interactive-mode --watch status --disable-splash-screen"
317+
# Tari SubmitBlock JSON bodies can exceed grpc-json-proxy's 1 MiB default when
318+
# the block carries a large proof body.
319+
ExecStart=/bin/bash -c "(sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18146 18142 --max-body-bytes 16777216) & (sleep 2; node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18148 18142 --max-body-bytes 16777216) & /usr/local/src/tari/target/release/minotari_node --non-interactive-mode --watch status --disable-splash-screen"
318320
Restart=always
319321
User=$TARI_USER
320322
Environment=HOME=$TARI_HOME

lib/coins/core/factories.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const XTM_T_POW_ALGO_OFFSET = XTM_T_NONCE_OFFSET + XTM_T_NONCE_SIZE;
1010
const XTM_T_POW_DATA_OFFSET = XTM_T_POW_ALGO_OFFSET + 1;
1111
const XTM_T_POW_DATA_SIZE = 32;
1212
const XTM_T_RANDOMXT_POW_ALGO = 2;
13+
const XTM_T_POOL_RESERVED_OFFSET = XTM_T_POW_DATA_OFFSET;
1314

1415
function cloneValue(value) {
1516
if (Array.isArray(value)) return value.slice();
@@ -482,10 +483,13 @@ function createXtmTRpc(overrides) {
482483
+ XTM_T_RANDOMXT_POW_ALGO.toString(16).padStart(2, "0")
483484
+ "00".repeat(XTM_T_POW_DATA_SIZE),
484485
seed_hash: arr2hex(result.vm_key),
485-
// Tari RandomXT's 76-byte mining blob reserves bytes 35..39 as
486-
// the high half of the big-endian u64 nonce. Keep pool
487-
// uniqueness there; pow_data must stay consensus data.
488-
reserved_offset: XTM_T_NONCE_OFFSET,
486+
// Tari's own XMRig proxy reserves bytes 35..39, but the pool
487+
// needs a full 16-byte Monero-style reserve for xmr-node-proxy.
488+
// RandomXT currently permits up to 32 bytes of pow_data, and
489+
// submitXtmRxBlock copies these mined bytes into SubmitBlock.
490+
// Keep bytes 35..42 for the miner nonce and place the pool
491+
// reserve inside pow_data without touching pow_algo at byte 43.
492+
reserved_offset: XTM_T_POOL_RESERVED_OFFSET,
489493
difficulty: parseInt(result.miner_data.target_difficulty, 10),
490494
reward: parseInt(result.miner_data.reward, 10),
491495
height: parseInt(result.block.header.height, 10),
@@ -974,13 +978,13 @@ function validateXtmCSubmit(ctx) {
974978
}
975979

976980
function buildStandardSubmissionKey(ctx) {
977-
if (ctx.miner.proxy) return `${ctx.params.nonce}_${ctx.params.poolNonce}_${ctx.params.workerNonce}`;
981+
if (ctx.job && ctx.job.usesProxyNonce) return `${ctx.params.nonce}_${ctx.params.poolNonce}_${ctx.params.workerNonce}`;
978982
return ctx.params.nonce;
979983
}
980984

981985
function buildProofSubmissionKey(ctx) {
982986
const proofKey = ctx.params.pow.join(":");
983-
if (ctx.miner.proxy) return `${proofKey}_${ctx.params.poolNonce}_${ctx.params.workerNonce}`;
987+
if (ctx.job && ctx.job.usesProxyNonce) return `${proofKey}_${ctx.params.poolNonce}_${ctx.params.workerNonce}`;
984988
return proofKey;
985989
}
986990

@@ -1236,6 +1240,19 @@ function submitDeroBlock(ctx) {
12361240
}
12371241

12381242
function submitXtmRxBlock(ctx) {
1243+
if (!Buffer.isBuffer(ctx.blockData) ||
1244+
ctx.blockData.length <= XTM_T_POW_ALGO_OFFSET ||
1245+
ctx.blockData[XTM_T_POW_ALGO_OFFSET] !== XTM_T_RANDOMXT_POW_ALGO) {
1246+
const actual = Buffer.isBuffer(ctx.blockData) && ctx.blockData.length > XTM_T_POW_ALGO_OFFSET
1247+
? ctx.blockData[XTM_T_POW_ALGO_OFFSET]
1248+
: "missing";
1249+
return ctx.replyFn({
1250+
error: {
1251+
code: -1,
1252+
message: `Invalid XTM-T pow_algo byte ${actual}; expected ${XTM_T_RANDOMXT_POW_ALGO}`
1253+
}
1254+
}, 0);
1255+
}
12391256
const xtmBlock = cloneRpcTemplate(ctx.blockTemplate.xtm_block);
12401257
const powData = ctx.blockData.slice(XTM_T_POW_DATA_OFFSET);
12411258

@@ -1298,6 +1315,7 @@ const basePoolConfig = {
12981315
acceptSubmittedBlock: acceptDefinedResult,
12991316
resolveSubmittedBlockHash: resolveDefaultSubmittedBlockHash,
13001317
submitBlockRpc: submitCryptonoteBlock,
1318+
disableProxyNonce: false,
13011319
sharedTemplateNonces: false,
13021320
useEthJobId: false
13031321
};

lib/coins/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ function Coin(data) {
484484
this.seed_hash = template.seed_hash;
485485
this.coin = template.coin;
486486
this.port = template.port;
487+
this.disableProxyNonce = template.no_proxy_nonce === true || template.disable_proxy_nonce === true;
487488

488489
if (!setTemplateBlob(this, template, templateConfig)) return;
489490

@@ -510,8 +511,10 @@ function Coin(data) {
510511
if (template.bt_nonce_size === undefined || template.bt_nonce_size >= 16) {
511512
instanceId.copy(this.buffer, this.reserved_offset + 4, 0, 4);
512513
this.extraNonce = 0;
513-
this.clientNonceLocation = this.reserved_offset + 12;
514-
this.clientPoolLocation = this.reserved_offset + 8;
514+
if (!this.disableProxyNonce) {
515+
this.clientNonceLocation = this.reserved_offset + 12;
516+
this.clientPoolLocation = this.reserved_offset + 8;
517+
}
515518
this.nextBlobHex = function nextBlobHex() {
516519
this.buffer.writeUInt32BE(++this.extraNonce, this.reserved_offset);
517520
const blobHex = global.coinFuncs.convertBlob(this.buffer, this.port);

lib/pool/jobs.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,20 @@ module.exports = function createMinerJobs(state) {
5656
const poolSettings = profile && profile.pool ? profile.pool : {};
5757
const blobTypeNum = profile ? profile.blobType : global.coinFuncs.portBlobType(bt.port);
5858
const usesSharedTemplateNonce = poolSettings.sharedTemplateNonces === true;
59+
const usesProxyNonce = miner.proxy &&
60+
!usesSharedTemplateNonce &&
61+
poolSettings.disableProxyNonce !== true &&
62+
bt.disableProxyNonce !== true &&
63+
Number.isInteger(bt.clientPoolLocation) &&
64+
Number.isInteger(bt.clientNonceLocation);
5965

60-
if (!miner.proxy || usesSharedTemplateNonce) {
66+
if (!usesProxyNonce) {
6167
const blobHex = bt.nextBlobHex();
6268
if (!blobHex) return null;
6369
const newJob = buildJob(coin, bt, blobTypeNum, coinDiff, params, {
6470
id: poolSettings.useEthJobId ? getNewEthJobId() : getNewId(),
65-
extraNonce: usesSharedTemplateNonce ? miner.eth_extranonce : bt.extraNonce
71+
extraNonce: usesSharedTemplateNonce ? miner.eth_extranonce : bt.extraNonce,
72+
usesProxyNonce: false
6673
});
6774

6875
miner.cachedJob = poolSettings.buildJobPayload({
@@ -83,6 +90,7 @@ module.exports = function createMinerJobs(state) {
8390
const blobHex = bt.nextBlobWithChildNonceHex();
8491
const newJob = buildJob(coin, bt, blobTypeNum, coinDiff, params, {
8592
extraNonce: bt.extraNonce,
93+
usesProxyNonce: true,
8694
clientPoolLocation: bt.clientPoolLocation,
8795
clientNonceLocation: bt.clientNonceLocation
8896
});

lib/pool/protocol.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ module.exports = function createProtocolHandler(deps) {
642642
rejectBadShare(miner, "Duplicate share");
643643
return false;
644644
}
645-
if (miner.proxy) {
645+
if (job.usesProxyNonce) {
646646
if (!Number.isInteger(params.poolNonce) || !Number.isInteger(params.workerNonce)) {
647647
console.warn(state.threadName + formatPoolEvent("Malformed nonce", { miner: miner.logString, params }));
648648
rejectBadShare(miner, "Duplicate share");

lib/pool/shares.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ module.exports = function createShareProcessor(deps) {
292292
const template = Buffer.alloc(blockTemplate.buffer.length);
293293
blockTemplate.buffer.copy(template);
294294
template.writeUInt32BE(job.extraNonce, blockTemplate.reserved_offset);
295-
if (miner.proxy) {
295+
if (job.usesProxyNonce) {
296296
template.writeUInt32BE(params.poolNonce, job.clientPoolLocation);
297297
template.writeUInt32BE(params.workerNonce, job.clientNonceLocation);
298298
}

tests/deploy/deploy.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ async function verifyDeployInstall(context) {
332332
await appendCheckLog(context, "verified unpatched Monero build");
333333
await execInContainer(context.containerName, "grep -q '^User=taridaemon$' /lib/systemd/system/xtm.service && grep -q '^User=taridaemon$' /lib/systemd/system/xtm_mm.service && grep -q '^Environment=HOME=/home/taridaemon$' /lib/systemd/system/xtm.service && grep -q '^Environment=HOME=/home/taridaemon$' /lib/systemd/system/xtm_mm.service && grep -q '^SupplementaryGroups=hugepages$' /lib/systemd/system/monero.service && grep -q '^LimitMEMLOCK=infinity$' /lib/systemd/system/monero.service && id -nG monerodaemon | grep -qw hugepages");
334334
await appendCheckLog(context, "verified separate Tari user and Monero hugepage access");
335+
await execInContainer(context.containerName, "test $(grep -o -- '--max-body-bytes 16777216' /lib/systemd/system/xtm.service | wc -l) -eq 2");
336+
await appendCheckLog(context, "verified Tari grpc-json-proxy accepts large SubmitBlock payloads");
335337
await execInContainer(context.containerName, "grep -q '^After=network.target monero.service xtm.service$' /lib/systemd/system/xtm_mm.service && grep -q '^PartOf=monero.service xtm.service$' /lib/systemd/system/xtm_mm.service && ! grep -q '^Requires=' /lib/systemd/system/xtm_mm.service && ! grep -q '^ExecStartPre=' /lib/systemd/system/xtm_mm.service");
336338
await execInContainer(context.containerName, "test ! -e /usr/local/sbin/monerod-rpc-wait && test ! -e /usr/local/sbin/xtm-mm-healthcheck && test ! -e /lib/systemd/system/xtm-mm-healthcheck.service && test ! -e /lib/systemd/system/xtm-mm-healthcheck.timer");
337339
await execInContainer(context.containerName, "test -x /home/user/nodejs-pool/fix_daemon.sh && grep -q 'xmr-lag' /home/user/nodejs-pool/fix_daemon.sh && grep -q 'xtm-lag' /home/user/nodejs-pool/fix_daemon.sh && grep -q 'template-stuck' /home/user/nodejs-pool/fix_daemon.sh");

tests/pool/coin/basics.js

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const process = require("node:process");
88
const test = require("node:test");
99

1010
const loadRegistry = require("../../../lib/coins/core/registry.js");
11+
const createMinerJobs = require("../../../lib/pool/jobs.js");
1112
const {
1213
MAIN_PORT,
1314
createBaseTemplate,
@@ -150,7 +151,7 @@ test("BlockTemplate keeps main-template nonce layout stable across nextBlobHex c
150151
);
151152
});
152153

153-
test("BlockTemplate keeps XTM-T pool nonce in the Tari nonce prefix", () => {
154+
test("BlockTemplate keeps XTM-T pool/proxy nonce inside RandomXT pow_data", () => {
154155
const coinFuncs = global.coinFuncs.__realCoinFuncs;
155156
const blob = Buffer.concat([
156157
Buffer.alloc(3),
@@ -165,7 +166,7 @@ test("BlockTemplate keeps XTM-T pool nonce in the Tari nonce prefix", () => {
165166
difficulty: 1,
166167
height: 302,
167168
port: 18146,
168-
reserved_offset: 35,
169+
reserved_offset: 44,
169170
reward: 1,
170171
seed_hash: "22".repeat(32),
171172
xtm_block: { header: { nonce: "0", pow: { pow_data: [] } } }
@@ -174,9 +175,99 @@ test("BlockTemplate keeps XTM-T pool nonce in the Tari nonce prefix", () => {
174175
const nextBlob = Buffer.from(blockTemplate.nextBlobHex(), "hex");
175176

176177
assert.equal(blockTemplate.extraNonce, 1);
177-
assert.equal(nextBlob.readUInt32BE(35), 1);
178+
assert.equal(nextBlob.subarray(35, 43).equals(Buffer.alloc(8)), true);
178179
assert.equal(nextBlob[43], 0x02);
179-
assert.equal(nextBlob.subarray(44).equals(Buffer.alloc(32)), true);
180+
assert.equal(nextBlob.readUInt32BE(44), 1);
181+
assert.equal(blockTemplate.disableProxyNonce, false);
182+
assert.equal(blockTemplate.clientPoolLocation, 52);
183+
assert.equal(blockTemplate.clientNonceLocation, 56);
184+
});
185+
186+
test("proxy miners use standard jobs when proxy nonce layout is disabled", () => {
187+
const originalGetPoolProfile = global.coinFuncs.getPoolProfile;
188+
const calls = [];
189+
const validJobs = [];
190+
const poolSettings = {
191+
sharedTemplateNonces: false,
192+
disableProxyNonce: true,
193+
useEthJobId: false,
194+
buildJobPayload(ctx) {
195+
calls.push("standard");
196+
assert.equal(ctx.newJob.usesProxyNonce, false);
197+
return { blob: ctx.blobHex, job_id: ctx.newJob.id };
198+
},
199+
buildProxyJobPayload() {
200+
calls.push("proxy");
201+
return {};
202+
}
203+
};
204+
const miner = {
205+
proxy: true,
206+
jobLastBlockHash: null,
207+
newDiffToSet: null,
208+
newDiffRecommendation: null,
209+
difficulty: 50,
210+
curr_coin_hash_factor: 1,
211+
curr_coin_min_diff: 1,
212+
cachedJob: null,
213+
validJobs: {
214+
enq(job) {
215+
validJobs.push(job);
216+
}
217+
}
218+
};
219+
let nextBlobCalls = 0;
220+
let childBlobCalls = 0;
221+
const blockTemplate = {
222+
idHash: "xtm-t-no-proxy",
223+
difficulty: 100,
224+
height: 302,
225+
seed_hash: "22".repeat(32),
226+
port: 18146,
227+
block_version: 0,
228+
extraNonce: 0,
229+
disableProxyNonce: true,
230+
clientPoolLocation: 43,
231+
clientNonceLocation: 47,
232+
nextBlobHex() {
233+
nextBlobCalls += 1;
234+
this.extraNonce += 1;
235+
return "aa";
236+
},
237+
nextBlobWithChildNonceHex() {
238+
childBlobCalls += 1;
239+
return "bb";
240+
}
241+
};
242+
243+
try {
244+
global.coinFuncs.getPoolProfile = function getPoolProfile() {
245+
return { blobType: 106, pool: poolSettings };
246+
};
247+
createMinerJobs({})(miner, {
248+
protoVersion: 1,
249+
getCoinJobParams() {},
250+
getNewId() { return "job-1"; },
251+
getNewEthJobId() { return "eth-job-1"; },
252+
getTargetHex() { return "00".repeat(32); },
253+
getRavenTargetHex() { return "00".repeat(32); },
254+
toBigInt(value) { return BigInt(value); },
255+
divideBaseDiff() { return 1; }
256+
});
257+
258+
const payload = miner.getCoinJob("XTM-T", { bt: blockTemplate, algo_name: "rx/0", coinHashFactor: 1 });
259+
260+
assert.deepEqual(payload, { blob: "aa", job_id: "job-1" });
261+
assert.deepEqual(calls, ["standard"]);
262+
assert.equal(nextBlobCalls, 1);
263+
assert.equal(childBlobCalls, 0);
264+
assert.equal(validJobs.length, 1);
265+
assert.equal(validJobs[0].usesProxyNonce, false);
266+
assert.equal(validJobs[0].clientPoolLocation, undefined);
267+
assert.equal(validJobs[0].clientNonceLocation, undefined);
268+
} finally {
269+
global.coinFuncs.getPoolProfile = originalGetPoolProfile;
270+
}
180271
});
181272

182273
test("BlockTemplate uses the SAL blob marker when daemon reserved offset is stale", () => {

0 commit comments

Comments
 (0)