Salvium: various fixes in paymentprocessor and addresses validation for upcoming carrot hf
This commit is contained in:
@@ -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];
|
||||
|
||||
77
lib/utils.js
77
lib/utils.js
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user