Salvium: various fixes in paymentprocessor and addresses validation for upcoming carrot hf

This commit is contained in:
gh14
2025-08-31 21:34:22 +00:00
parent 1a06d8a6ad
commit e2abef8b06
2 changed files with 192 additions and 62 deletions

View File

@@ -27,31 +27,71 @@ if (!config.poolServer.paymentId) config.poolServer.paymentId = {};
if (!config.poolServer.paymentId.addressSeparator) config.poolServer.paymentId.addressSeparator = "+";
if (!config.payments.priority) config.payments.priority = 0;
// how often to attempt payouts
const PAY_INTERVAL_MS =
((config.payments && config.payments.interval) ? config.payments.interval : 60) * 1000;
let payoutTimer = null;
let isRoundActive = false;
function scheduleNext(ms = PAY_INTERVAL_MS) {
if (payoutTimer) clearTimeout(payoutTimer);
payoutTimer = setTimeout(runInterval, ms);
}
function runInterval () {
// Check for Salvium payout blackout
if (utils.isSalviumEnabled()) {
apiInterfaces.rpcDaemon('getblockcount', [], function (error, result) {
if (error) {
log('error', logSystem, 'Error getting block count for Salvium check: %j', [error]);
return;
}
let currentHeight = result.count - 1; // getblockcount returns height + 1
let salviumState = utils.getSalviumState(currentHeight);
if (salviumState === 'payout_blackout') {
log('info', logSystem, 'Salvium payout blackout active at height %d. Skipping payment processing.', [currentHeight]);
return;
}
// Continue with normal payment processing
processSalviumAwarePayments(currentHeight);
});
return;
}
// Non-Salvium payment processing
processSalviumAwarePayments(0);
if (isRoundActive) {
// avoid overlapping rounds
log('warn', logSystem, 'Previous payment round still running — skipping this tick');
return scheduleNext();
}
isRoundActive = true;
const done = (delayMs = PAY_INTERVAL_MS) => {
isRoundActive = false;
scheduleNext(delayMs);
};
// Non-Salvium path
if (!utils.isSalviumEnabled()) {
try {
processSalviumAwarePayments(0);
} catch (e) {
log('error', logSystem, 'Non-Salvium payment round crashed: %j', [e && e.stack || e]);
// small backoff on error
return done(Math.min(15000, PAY_INTERVAL_MS));
}
return done();
}
// Salvium path
apiInterfaces.rpcDaemon('getblockcount', [], function (error, result) {
if (error) {
log('error', logSystem, 'Error getting block count for Salvium check: %j', [error]);
// IMPORTANT: always schedule the next tick, even on error
return done(Math.min(15000, PAY_INTERVAL_MS));
}
log('info', logSystem, 'payout height: %s', [result.count]);
const currentHeight = (result && typeof result.count === 'number') ? (result.count - 1) : 0;
try {
const salviumState = utils.getSalviumState(currentHeight);
if (salviumState === 'payout_blackout') {
log('info', logSystem, 'Salvium payout blackout at height %d — skipping.', [currentHeight]);
return done(); // schedule next normal tick
}
// proceed with payments for this height
processSalviumAwarePayments(currentHeight);
} catch (e) {
log('error', logSystem, 'Salvium payment round crashed at height %d: %j', [currentHeight, e && e.stack || e]);
return done(Math.min(15000, PAY_INTERVAL_MS));
}
return done();
});
}
function processSalviumAwarePayments(currentHeight) {
@@ -172,37 +212,74 @@ function processSalviumAwarePayments(currentHeight) {
// Handle Salvium dual address and carrot switching
if (utils.isSalviumEnabled()) {
let salviumState = utils.getSalviumState(currentHeight);
const salviumState = utils.getSalviumState(currentHeight);
const dualSep =
(config.salvium && config.salvium.addressSeparator) ||
(config.poolServer && config.poolServer.paymentId && config.poolServer.paymentId.addressSeparator) ||
'_';
const pidSep = (config.poolServer && config.poolServer.paymentId && config.poolServer.paymentId.addressSeparator) || '.';
const login = worker; // what miners provided / what you stored per worker
const hasDual = typeof login === 'string' && login.includes(dualSep);
// Helpers to pick candidates
const [cnCandidateRaw, carrotCandidateRaw] = hasDual ? login.split(dualSep) : [login, login];
// Helper: peel payment_id ONLY for cryptonote
const peelPid = (addr) => {
const [a, pid] = String(addr).split(pidSep);
return { addr: a, pid: pid || null };
};
if (salviumState === 'carrot_payouts') {
// Switch to carrot address for payouts
let dualAddress = utils.parseSalviumDualAddress(worker);
if (dualAddress) {
address = dualAddress.carrot;
log('info', logSystem, 'Salvium: Sending payment to carrot address %s', [address]);
// Must pay to CARROT. Accept dual (use right part) OR single carrot.
const carrotPick = hasDual ? carrotCandidateRaw : login;
if (!utils.validateSalviumAddress(carrotPick, 'carrot')) {
log('warn', logSystem,
'Salvium carrot_payouts: invalid carrot address for worker "%s": %s',
[worker, carrotPick]);
// handle per your flow: skip/throw
// e.g., throw new Error('Invalid carrot payout address');
} else {
// For backwards compatibility, if no dual address, try to parse as single address
let addr = address.split(config.poolServer.paymentId.addressSeparator);
address = addr[0];
payment_id = addr[1] || null;
}
} else if (salviumState === 'dual_required') {
// Still pay to cryptonote address but parse dual format
let dualAddress = utils.parseSalviumDualAddress(worker);
if (dualAddress) {
address = dualAddress.cryptonote;
} else {
let addr = address.split(config.poolServer.paymentId.addressSeparator);
address = addr[0];
payment_id = addr[1] || null;
address = carrotPick;
payment_id = null; // carrot payouts never use payment_id
log('info', logSystem, 'Salvium: Paying CARROT address %s', [address]);
}
} else {
// Normal payment processing for other heights
let addr = address.split(config.poolServer.paymentId.addressSeparator);
address = addr[0];
payment_id = addr[1] || null;
// Pay to CRYPTONOTE. Two cases:
// - dual_required OR login contains separator: expect dual OR single CN (no single carrot)
// - otherwise: must be single CN
const mustAllowDual = salviumState === 'dual_required' || hasDual;
const cnPickRaw = hasDual ? cnCandidateRaw : login;
const { addr: cnPick, pid } = peelPid(cnPickRaw);
if (!utils.validateSalviumAddress(cnPick, 'cryptonote')) {
log('warn', logSystem,
'Salvium CN payout: invalid cryptonote address for worker "%s": %s',
[worker, cnPick]);
continue;
// handle per your flow: skip/throw
// e.g., throw new Error('Invalid cryptonote payout address');
} else {
address = cnPick;
payment_id = pid;
if (salviumState === 'dual_required' && !hasDual) {
// Allowed by spec ("...or single cryptonote"), but note it
log('warn', logSystem,
'Salvium dual_required: single cryptonote login accepted for %s',
[worker]);
}
log('info', logSystem, 'Salvium: Paying CRYPTONOTE address %s%s',
[address, payment_id ? ` (pid ${payment_id})` : '']);
}
}
} else {
}
else {
// Standard address parsing for non-Salvium coins
let addr = address.split(config.poolServer.paymentId.addressSeparator);
address = addr[0];

View File

@@ -14,6 +14,17 @@ exports.dateFormat = dateFormat;
let cnUtil = require('cryptoforknote-util');
exports.cnUtil = cnUtil;
require('./configReader.js');
// Initialize log system
let logSystem = 'pool';
require('./exceptionWriter.js')(logSystem);
let threadId = '(Thread ' + process.env.forkId + ') ';
let log = function (severity, system, text, data) {
global.log(severity, system, threadId + text, data);
};
/**
* Generate random instance id
**/
@@ -198,7 +209,7 @@ exports.ringBuffer = function (maxSize) {
// Check if Salvium functionality is enabled
function isSalviumEnabled() {
return config.salvium && config.salvium.enabled === true &&
(config.coin === 'Salvium' || config.symbol === 'SAL' || config.symbol === 'SAL1');
(config.coin === 'Salvium' || config.coin === 'SAL' || config.symbol === 'SAL' || config.symbol === 'SAL1');
}
exports.isSalviumEnabled = isSalviumEnabled;
@@ -216,17 +227,59 @@ exports.getSalviumState = getSalviumState;
// Validate Salvium address with specific prefixes
function validateSalviumAddress(address, type) {
if (!isSalviumEnabled() || !address) return false;
let addressPrefix = getAddressPrefix(address);
if (!addressPrefix) return false;
let prefixes = config.salvium.addressPrefixes[type];
if (!prefixes) return false;
return addressPrefix === parseInt(prefixes.public, 16) ||
addressPrefix === parseInt(prefixes.integrated, 16) ||
addressPrefix === parseInt(prefixes.subaddress, 16);
if (!isSalviumEnabled() || !address) return false;
const sep =
(config.salvium && config.salvium.addressSeparator) ||
(config.poolServer && config.poolServer.paymentId && config.poolServer.paymentId.addressSeparator) ||
"_";
// default to cryptonote, case-insensitive
const normType = (type || "cryptonote").toLowerCase();
// choose which part of a dual address to validate
let candidate = address;
if (address.includes(sep)) {
const parts = address.split(sep);
candidate = normType === "carrot" ? (parts[1] || "") : (parts[0] || "");
}
// get prefix from the selected candidate
const addressPrefix = getAddressPrefix(candidate);
if (!addressPrefix && addressPrefix !== 0) return false;
// pick prefixes set for the requested type
const prefixes =
config.salvium &&
config.salvium.addressPrefixes &&
config.salvium.addressPrefixes[normType];
if (!prefixes) return false;
// support both hex strings ("0x...") and decimal numbers in config
const toInt = (v) => {
if (typeof v === "number") return v;
if (typeof v === "string") {
const s = v.trim().toLowerCase();
if (s.startsWith("0x")) return parseInt(s, 16);
const n = parseInt(s, 10);
return Number.isFinite(n) ? n : NaN;
}
return NaN;
};
const pub = toInt(prefixes.public);
const integ = toInt(prefixes.integrated);
const sub = toInt(prefixes.subaddress);
if (![pub, integ, sub].every(Number.isFinite)) return false;
// Optional: keep your existing debug log
log("warn", logSystem, "prefixy %s : %s : %s", [candidate, addressPrefix, pub]);
return (
addressPrefix === pub ||
addressPrefix === integ ||
addressPrefix === sub
);
}
exports.validateSalviumAddress = validateSalviumAddress;