Skip to content

Commit e646dac

Browse files
committed
Contain XTM submit nonce read failures
1 parent a31c502 commit e646dac

5 files changed

Lines changed: 290 additions & 27 deletions

File tree

lib/coins/core/factories.js

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -810,9 +810,100 @@ const pow = {
810810
c29s: createCyclePowFactory("c29s", "c29s_packed_edges")
811811
};
812812

813-
function readUInt64BufferBE(buf, offset = 0) {
814-
const hi = BigInt(buf.readUInt32BE(offset));
815-
const lo = BigInt(buf.readUInt32BE(offset + 4));
813+
function errorMessage(error) {
814+
return error && error.message ? error.message : String(error);
815+
}
816+
817+
function safeInspect(value) {
818+
if (typeof value === "function") return "[Function " + (value.name || "anonymous") + "]";
819+
if (typeof value === "bigint") return value.toString() + "n";
820+
if (typeof value === "undefined") return "undefined";
821+
try {
822+
return JSON.stringify(value);
823+
} catch (_error) {
824+
try {
825+
return String(value);
826+
} catch (_error2) {
827+
return "[uninspectable]";
828+
}
829+
}
830+
}
831+
832+
function safeGet(value, key) {
833+
try {
834+
return value ? value[key] : undefined;
835+
} catch (error) {
836+
return "[threw " + errorMessage(error) + "]";
837+
}
838+
}
839+
840+
function ctorName(value) {
841+
const ctor = safeGet(value, "constructor");
842+
return ctor && ctor.name ? ctor.name : typeof value;
843+
}
844+
845+
function protoCtorName(value) {
846+
try {
847+
const proto = Object.getPrototypeOf(value);
848+
return proto && proto.constructor && proto.constructor.name ? proto.constructor.name : safeInspect(proto);
849+
} catch (error) {
850+
return "[threw " + errorMessage(error) + "]";
851+
}
852+
}
853+
854+
function describeIndexedValues(value, offset) {
855+
const items = [];
856+
for (let index = offset; index < offset + 8; index++) {
857+
const byte = safeGet(value, index);
858+
items.push(index + ":" + typeof byte + ":" + safeInspect(byte) + ":integer=" + Number.isInteger(byte));
859+
}
860+
return items.join(",");
861+
}
862+
863+
function describeBufferBacking(value, offset) {
864+
if (!Buffer.isBuffer(value)) return "";
865+
try {
866+
const backing = Buffer.from(value.buffer, value.byteOffset, value.length);
867+
const view = new DataView(backing.buffer, backing.byteOffset, backing.byteLength);
868+
return " backingDataViewHi=" + view.getUint32(offset, false) +
869+
" backingDataViewLo=" + view.getUint32(offset + 4, false) +
870+
" backingBytes=" + Array.from(backing.subarray(offset, offset + 8)).join(",") +
871+
" backingHex=" + backing.toString("hex");
872+
} catch (error) {
873+
return " backing=[threw " + errorMessage(error) + "]";
874+
}
875+
}
876+
877+
function describeLegacyNonceRead(value, offset) {
878+
return "receiver={" +
879+
"isBuffer:" + Buffer.isBuffer(value) +
880+
",instanceofBuffer:" + (value instanceof Buffer) +
881+
",ctor:" + ctorName(value) +
882+
",protoCtor:" + protoCtorName(value) +
883+
",length:" + safeInspect(safeGet(value, "length")) +
884+
",byteOffset:" + safeInspect(safeGet(value, "byteOffset")) +
885+
",byteLength:" + safeInspect(safeGet(value, "byteLength")) +
886+
",readUInt32BEOwn:" + Object.prototype.hasOwnProperty.call(value || {}, "readUInt32BE") +
887+
",readUInt32BEIsBufferProto:" + (safeGet(value, "readUInt32BE") === Buffer.prototype.readUInt32BE) +
888+
"} indexed=[" + describeIndexedValues(value, offset) + "]" +
889+
describeBufferBacking(value, offset);
890+
}
891+
892+
function readUInt64BufferBE(buf, offset = 0, label) {
893+
const prefix = "XTM legacy nonce read" + (label ? " " + label : "");
894+
let hiNumber;
895+
let loNumber;
896+
try {
897+
hiNumber = buf.readUInt32BE(offset);
898+
loNumber = buf.readUInt32BE(offset + 4);
899+
} catch (error) {
900+
throw new RangeError(prefix + " failed: " + errorMessage(error) + "; " + describeLegacyNonceRead(buf, offset));
901+
}
902+
if (!Number.isInteger(hiNumber) || !Number.isInteger(loNumber)) {
903+
throw new RangeError(prefix + " returned non-integer uint32 hi=" + hiNumber + " lo=" + loNumber + "; " + describeLegacyNonceRead(buf, offset));
904+
}
905+
const hi = BigInt(hiNumber);
906+
const lo = BigInt(loNumber);
816907
return ((hi << 32n) | lo).toString(10);
817908
}
818909

@@ -1256,15 +1347,15 @@ function submitXtmRxBlock(ctx) {
12561347
const xtmBlock = cloneRpcTemplate(ctx.blockTemplate.xtm_block);
12571348
const powData = ctx.blockData.slice(XTM_T_POW_DATA_OFFSET);
12581349

1259-
xtmBlock.header.nonce = readUInt64BufferBE(ctx.blockData, XTM_T_NONCE_OFFSET);
1350+
xtmBlock.header.nonce = readUInt64BufferBE(ctx.blockData, XTM_T_NONCE_OFFSET, "XTM-T");
12601351
xtmBlock.header.pow.pow_data = powData.every((byte) => byte === 0) ? [] : [...powData];
12611352
ctx.support.rpcPortDaemon(ctx.blockTemplate.port, "SubmitBlock", xtmBlock, ctx.replyFn, true);
12621353
}
12631354

12641355
function submitXtmCBlock(ctx) {
12651356
const xtmBlock = cloneRpcTemplate(ctx.blockTemplate.xtm_block);
12661357

1267-
xtmBlock.header.nonce = readUInt64BufferBE(ctx.blockData, 0);
1358+
xtmBlock.header.nonce = readUInt64BufferBE(ctx.blockData, 0, "XTM-C");
12681359
xtmBlock.header.pow.pow_data = ctx.job.c29_packed_edges;
12691360
ctx.support.rpcPortDaemon(ctx.blockTemplate.port, "SubmitBlock", xtmBlock, ctx.replyFn, true);
12701361
}

lib/pool/share_blocks.js

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ module.exports = function createShareBlockHelpers(deps) {
128128
return formatDiffRows(diffRows);
129129
}
130130

131-
function submitBlock(miner, job, blockTemplate, blockData, resultBuff, hashDiff, isTrustedShare, isParentBlock, portUsedToSubmit, submitBlockCB, submitRetryCount) {
131+
function submitBlock(miner, job, blockTemplate, blockData, resultBuff, hashDiff, isTrustedShare, isParentBlock, portUsedToSubmit, submitBlockCB, submitParams, submitRetryCount) {
132132
const isMainPort = global.config.daemon.port == blockTemplate.port;
133133
const profile = global.coinFuncs.getPoolProfile(blockTemplate.port);
134134
const poolSettings = profile && profile.pool ? profile.pool : {};
@@ -165,7 +165,7 @@ module.exports = function createShareBlockHelpers(deps) {
165165
}
166166

167167
function retrySubmit(port, nextSubmitBlockCB, delayMs) {
168-
return setTimeout(submitBlock, delayMs, miner, job, blockTemplate, blockData, resultBuff, hashDiff, isTrustedShare, isParentBlock, port, nextSubmitBlockCB, currentSubmitRetryCount + 1);
168+
return setTimeout(submitBlock, delayMs, miner, job, blockTemplate, blockData, resultBuff, hashDiff, isTrustedShare, isParentBlock, port, nextSubmitBlockCB, submitParams, currentSubmitRetryCount + 1);
169169
}
170170

171171
function storeResolvedBlock(newBlockHash, isDisplaySubmitPort, reportDiff, reportPort, reportHeight) {
@@ -316,19 +316,33 @@ module.exports = function createShareBlockHelpers(deps) {
316316
return replyFn(rpcResult, rpcStatus, blockTemplate.port, submitBlockCB);
317317
};
318318

319-
poolSettings.submitBlockRpc.call(poolSettings, {
320-
blockData,
321-
blockTemplate,
322-
hashDiff,
323-
isBlockSubmitTestModeEnabled,
324-
job,
325-
portUsedToSubmit,
326-
replyDispatcher: replyFn,
327-
replyFn: stdReplyFn,
328-
suppressFailureEmail: shouldSuppressBlockSubmitFailureEmail(),
329-
submitBlockCB,
330-
support: global.support
331-
});
319+
try {
320+
poolSettings.submitBlockRpc.call(poolSettings, {
321+
blockData,
322+
blockTemplate,
323+
hashDiff,
324+
isBlockSubmitTestModeEnabled,
325+
job,
326+
params: submitParams,
327+
portUsedToSubmit,
328+
replyDispatcher: replyFn,
329+
replyFn: stdReplyFn,
330+
suppressFailureEmail: shouldSuppressBlockSubmitFailureEmail(),
331+
submitBlockCB,
332+
support: global.support
333+
});
334+
} catch (error) {
335+
const message = error && error.message ? error.message : String(error);
336+
console.error(getThreadName() + formatPoolEvent("Block submit exception", {
337+
chain: formatCoinPort(blockTemplate.coin, blockTemplate.port),
338+
height: blockTemplate.height,
339+
miner: miner.logString,
340+
params: submitParams,
341+
trusted: isTrustedShare,
342+
error: error && error.stack ? error.stack : message
343+
}));
344+
return stdReplyFn({ error: { code: -1, message: "SubmitBlock exception: " + message } }, 0);
345+
}
332346
}
333347

334348
return {

lib/pool/shares.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ module.exports = function createShareProcessor(deps) {
466466
ensureWalletTrustEntry(miner);
467467
walletTrust[miner.payout] += job.rewarded_difficulty2;
468468
return verifyShareCB(hashDiff, resultBuff, blockData, false, false);
469-
});
469+
}, params);
470470
return true;
471471
}
472472
const buff = global.coinFuncs.slowHashBuff(convertedBlob, blockTemplate);
@@ -548,9 +548,9 @@ module.exports = function createShareProcessor(deps) {
548548
if (allowUntrustedBlockSubmitTest) {
549549
submitBlock(miner, job, blockTemplate, nextBlockData, resultBuff, hashDiff, true, true, null, function onTestModeSubmit() {
550550
return processShareCB(true);
551-
});
551+
}, params);
552552
} else {
553-
submitBlock(miner, job, blockTemplate, nextBlockData, resultBuff, hashDiff, isTrustedShare, true, null);
553+
submitBlock(miner, job, blockTemplate, nextBlockData, resultBuff, hashDiff, isTrustedShare, true, null, null, params);
554554
}
555555
return allowUntrustedBlockSubmitTest;
556556
}
@@ -578,7 +578,7 @@ module.exports = function createShareProcessor(deps) {
578578
if (!nextBlockData) return false;
579579
const shareBuffer2 = buildChildShareBuffer(nextBlockData);
580580
if (shareBuffer2 === null) return false;
581-
submitBlock(miner, job, blockTemplate.child_template, shareBuffer2, resultBuff, hashDiff, isTrustedShare, false, null);
581+
submitBlock(miner, job, blockTemplate.child_template, shareBuffer2, resultBuff, hashDiff, isTrustedShare, false, null, null, params);
582582
return true;
583583
}
584584

tests/pool/coin/submitters.js

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,53 @@ test("xtm-t SubmitBlock payload roundtrips the miner's Tari RandomXT blob", () =
488488
assert.deepEqual(xtmBlock.header.pow.pow_data, [7, 7, 7]);
489489
});
490490

491+
test("xtm-t legacy nonce read reports non-integer indexed values", () => {
492+
const coinFuncs = global.coinFuncs.__realCoinFuncs;
493+
const xtmTPool = coinFuncs.getPoolSettings("XTM-T");
494+
const miningHash = Buffer.from("31".repeat(XTM_T_MINING_HASH_SIZE), "hex");
495+
const blockData = createXtmTMiningBlob(miningHash, 0n, [4, 5, 6]);
496+
const spoofedBlockData = new Proxy(blockData, {
497+
get(target, prop) {
498+
if (prop === "slice") return target.slice.bind(target);
499+
if (prop === String(XTM_T_MINER_NONCE_OFFSET)) return 0xa5;
500+
if (prop === String(XTM_T_MINER_NONCE_OFFSET + 1)) return 0x1e;
501+
if (prop === String(XTM_T_MINER_NONCE_OFFSET + 2)) return 0x01;
502+
if (prop === String(XTM_T_MINER_NONCE_OFFSET + 3)) return 255.99999856948853;
503+
return Reflect.get(target, prop, target);
504+
}
505+
});
506+
507+
Buffer.from("a51e0200", "hex").copy(blockData, XTM_T_MINER_NONCE_OFFSET);
508+
assert.equal(Buffer.isBuffer(spoofedBlockData), true);
509+
assert.equal(Number.isInteger(spoofedBlockData.readUInt32BE(XTM_T_MINER_NONCE_OFFSET)), false);
510+
511+
assert.throws(function submitSpoofedXtmBlob() {
512+
xtmTPool.submitBlockRpc.call(xtmTPool, {
513+
blockData: spoofedBlockData,
514+
blockTemplate: {
515+
port: 18146,
516+
reserved_offset: XTM_T_POOL_RESERVED_OFFSET,
517+
xtm_block: { header: { nonce: "0", pow: { pow_data: [] } } }
518+
},
519+
replyFn() {},
520+
support: {
521+
rpcPortDaemon() {
522+
throw new Error("non-integer legacy nonce read must fail before submit");
523+
}
524+
}
525+
});
526+
}, function checkError(error) {
527+
assert.match(error.message, /XTM legacy nonce read XTM-T returned non-integer uint32/);
528+
assert.match(error.message, /lo=2770207231\.9999986/);
529+
assert.match(error.message, /isBuffer:true/);
530+
assert.match(error.message, /readUInt32BEIsBufferProto:true/);
531+
assert.match(error.message, /42:number:255\.99999856948853:integer=false/);
532+
assert.match(error.message, /backingDataViewLo=2770207232/);
533+
assert.match(error.message, /backingBytes=0,0,0,0,165,30,2,0/);
534+
return true;
535+
});
536+
});
537+
491538
test("xtm-t SubmitBlock rejects a blob with a corrupted pow_algo byte", () => {
492539
const coinFuncs = global.coinFuncs.__realCoinFuncs;
493540
const xtmTPool = coinFuncs.getPoolSettings("XTM-T");
@@ -577,9 +624,9 @@ test("xtm submit and verify handlers preserve the pre-refactor special-case tari
577624
}
578625
};
579626

580-
blockDataRx.writeUInt32BE(7, 3 + 32);
581-
blockDataRx.writeUInt32BE(1234, 3 + 32 + 4);
582-
blockDataRx[3 + 32 + 8] = 2;
627+
blockDataRx.writeUInt32BE(7, XTM_T_NONCE_OFFSET);
628+
blockDataRx.writeUInt32BE(1234, XTM_T_MINER_NONCE_OFFSET);
629+
blockDataRx[XTM_T_POW_ALGO_OFFSET] = XTM_T_RANDOMXT_POW_ALGO;
583630
blockDataC29.writeBigUInt64BE(15n, 0);
584631

585632
xtmTPool.resolveSubmittedBlockHash({

0 commit comments

Comments
 (0)