Files
salvium-rs/test/rpc.test.js
T
Matt Hess 2c128eb1c0 Add Bulletproofs+ range proof verification (pure JavaScript)
- Implement BP+ verification using @noble/curves for Ed25519 ops
  - MSM 256 points in ~420ms - mobile-friendly, no WASM needed
  - Add verifyBulletproofPlus, verifyBulletproofPlusBatch, verifyRangeProof
  - Add generator initialization with caching (Gi, Hi points)
  - 24 new tests including performance benchmarks
  - Update exports in index.js and package.json
  - Bump version to 0.3.0
2026-01-15 02:09:51 +00:00

446 lines
13 KiB
JavaScript

/**
* RPC Module Tests
*
* Tests for the Salvium RPC client implementations.
* Note: Most tests require a running daemon/wallet RPC server.
* These unit tests focus on module structure and offline functionality.
*/
import {
RPCClient,
DaemonRPC,
WalletRPC,
createDaemonRPC,
createWalletRPC,
RPC_ERROR_CODES,
RPC_STATUS,
PRIORITY,
TRANSFER_TYPE,
DAEMON_MAINNET_URL,
DAEMON_TESTNET_URL,
DAEMON_STAGENET_URL,
ZMQ_MAINNET_URL,
ZMQ_TESTNET_URL,
ZMQ_STAGENET_URL,
WALLET_MAINNET_URL,
WALLET_TESTNET_URL,
WALLET_STAGENET_URL
} from '../src/rpc/index.js';
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(`${name}`);
passed++;
} catch (error) {
console.log(`${name}`);
console.log(` Error: ${error.message}`);
failed++;
}
}
function assertEqual(actual, expected, message = '') {
if (actual !== expected) {
throw new Error(`${message} Expected ${expected}, got ${actual}`);
}
}
function assertExists(value, message = '') {
if (value === undefined || value === null) {
throw new Error(`${message} Value is ${value}`);
}
}
function assertInstanceOf(value, type, message = '') {
if (!(value instanceof type)) {
throw new Error(`${message} Expected instance of ${type.name}`);
}
}
function assertFunction(value, message = '') {
if (typeof value !== 'function') {
throw new Error(`${message} Expected function, got ${typeof value}`);
}
}
// ============================================================
// Module Export Tests
// ============================================================
console.log('\n--- RPC Module Export Tests ---');
test('RPCClient class is exported', () => {
assertFunction(RPCClient);
});
test('DaemonRPC class is exported', () => {
assertFunction(DaemonRPC);
});
test('WalletRPC class is exported', () => {
assertFunction(WalletRPC);
});
test('createDaemonRPC function is exported', () => {
assertFunction(createDaemonRPC);
});
test('createWalletRPC function is exported', () => {
assertFunction(createWalletRPC);
});
test('RPC_ERROR_CODES is exported', () => {
assertExists(RPC_ERROR_CODES);
assertExists(RPC_ERROR_CODES.NETWORK_ERROR);
assertExists(RPC_ERROR_CODES.TIMEOUT_ERROR);
});
test('RPC_STATUS is exported', () => {
assertExists(RPC_STATUS);
assertEqual(RPC_STATUS.OK, 'OK');
assertEqual(RPC_STATUS.BUSY, 'BUSY');
});
test('PRIORITY constants are exported', () => {
assertExists(PRIORITY);
assertEqual(PRIORITY.DEFAULT, 0);
assertEqual(PRIORITY.PRIORITY, 4);
});
test('TRANSFER_TYPE constants are exported', () => {
assertExists(TRANSFER_TYPE);
assertEqual(TRANSFER_TYPE.ALL, 'all');
assertEqual(TRANSFER_TYPE.AVAILABLE, 'available');
});
test('Daemon URLs are exported with correct Salvium ports (from cryptonote_config.h)', () => {
// config::RPC_DEFAULT_PORT values
assertEqual(DAEMON_MAINNET_URL, 'http://localhost:19081');
assertEqual(DAEMON_TESTNET_URL, 'http://localhost:29081');
assertEqual(DAEMON_STAGENET_URL, 'http://localhost:39081');
});
test('ZMQ URLs are exported with correct Salvium ports (from cryptonote_config.h)', () => {
// config::ZMQ_RPC_DEFAULT_PORT values
assertEqual(ZMQ_MAINNET_URL, 'http://localhost:19083');
assertEqual(ZMQ_TESTNET_URL, 'http://localhost:29083');
assertEqual(ZMQ_STAGENET_URL, 'http://localhost:39083');
});
test('Wallet URLs are exported (conventional ports, no source default)', () => {
// No default in salvium-wallet-rpc source - convention is daemon port + 1
assertEqual(WALLET_MAINNET_URL, 'http://localhost:19083');
assertEqual(WALLET_TESTNET_URL, 'http://localhost:29083');
assertEqual(WALLET_STAGENET_URL, 'http://localhost:39083');
});
// ============================================================
// RPCClient Tests
// ============================================================
console.log('\n--- RPCClient Tests ---');
test('RPCClient requires URL', () => {
let threw = false;
try {
new RPCClient({});
} catch (e) {
threw = true;
}
if (!threw) throw new Error('Should have thrown error');
});
test('RPCClient accepts URL', () => {
const client = new RPCClient({ url: 'http://localhost:19081' });
assertEqual(client.url, 'http://localhost:19081');
});
test('RPCClient strips trailing slashes', () => {
const client = new RPCClient({ url: 'http://localhost:19081///' });
assertEqual(client.url, 'http://localhost:19081');
});
test('RPCClient accepts timeout option', () => {
const client = new RPCClient({ url: 'http://localhost:19081', timeout: 60000 });
assertEqual(client.timeout, 60000);
});
test('RPCClient accepts retry options', () => {
const client = new RPCClient({
url: 'http://localhost:19081',
retries: 3,
retryDelay: 2000
});
assertEqual(client.retries, 3);
assertEqual(client.retryDelay, 2000);
});
test('RPCClient accepts auth options', () => {
const client = new RPCClient({
url: 'http://localhost:19081',
username: 'user',
password: 'pass'
});
assertEqual(client.username, 'user');
assertEqual(client.password, 'pass');
});
test('RPCClient.configure updates options', () => {
const client = new RPCClient({ url: 'http://localhost:19081' });
client.configure({ timeout: 5000 });
assertEqual(client.timeout, 5000);
});
// ============================================================
// DaemonRPC Tests
// ============================================================
console.log('\n--- DaemonRPC Tests ---');
test('DaemonRPC extends RPCClient', () => {
const daemon = new DaemonRPC({ url: 'http://localhost:19081' });
assertInstanceOf(daemon, RPCClient);
});
test('DaemonRPC defaults to mainnet port', () => {
const daemon = new DaemonRPC();
assertEqual(daemon.url, 'http://localhost:19081');
});
test('DaemonRPC has network info methods', () => {
const daemon = new DaemonRPC();
assertFunction(daemon.getInfo);
assertFunction(daemon.getHeight);
assertFunction(daemon.getBlockCount);
assertFunction(daemon.syncInfo);
assertFunction(daemon.hardForkInfo);
});
test('DaemonRPC has block operation methods', () => {
const daemon = new DaemonRPC();
assertFunction(daemon.getBlockHash);
assertFunction(daemon.getBlockHeaderByHash);
assertFunction(daemon.getBlockHeaderByHeight);
assertFunction(daemon.getBlockHeadersRange);
assertFunction(daemon.getLastBlockHeader);
assertFunction(daemon.getBlock);
});
test('DaemonRPC has transaction methods', () => {
const daemon = new DaemonRPC();
assertFunction(daemon.getTransactions);
assertFunction(daemon.getTransactionPool);
assertFunction(daemon.sendRawTransaction);
assertFunction(daemon.relayTx);
});
test('DaemonRPC has output methods', () => {
const daemon = new DaemonRPC();
assertFunction(daemon.getOuts);
assertFunction(daemon.getOutputHistogram);
assertFunction(daemon.getOutputDistribution);
assertFunction(daemon.isKeyImageSpent);
});
test('DaemonRPC has mining methods', () => {
const daemon = new DaemonRPC();
assertFunction(daemon.getBlockTemplate);
assertFunction(daemon.submitBlock);
assertFunction(daemon.getMinerData);
assertFunction(daemon.calcPow);
});
test('DaemonRPC has fee estimation methods', () => {
const daemon = new DaemonRPC();
assertFunction(daemon.getFeeEstimate);
assertFunction(daemon.getBaseFeeEstimate);
assertFunction(daemon.getCoinbaseTxSum);
});
test('DaemonRPC has utility methods', () => {
const daemon = new DaemonRPC();
assertFunction(daemon.isSynchronized);
assertFunction(daemon.getNetworkType);
assertFunction(daemon.waitForSync);
});
test('createDaemonRPC creates DaemonRPC instance', () => {
const daemon = createDaemonRPC({ url: 'http://localhost:39081' });
assertInstanceOf(daemon, DaemonRPC);
assertEqual(daemon.url, 'http://localhost:39081');
});
// ============================================================
// WalletRPC Tests
// ============================================================
console.log('\n--- WalletRPC Tests ---');
test('WalletRPC extends RPCClient', () => {
const wallet = new WalletRPC({ url: 'http://localhost:19083' });
assertInstanceOf(wallet, RPCClient);
});
test('WalletRPC defaults to mainnet port', () => {
const wallet = new WalletRPC();
assertEqual(wallet.url, 'http://localhost:19083');
});
test('WalletRPC has wallet management methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.createWallet);
assertFunction(wallet.openWallet);
assertFunction(wallet.closeWallet);
assertFunction(wallet.restoreDeterministicWallet);
assertFunction(wallet.generateFromKeys);
});
test('WalletRPC has address methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.getAddress);
assertFunction(wallet.getAddressIndex);
assertFunction(wallet.createAddress);
assertFunction(wallet.labelAddress);
assertFunction(wallet.validateAddress);
assertFunction(wallet.makeIntegratedAddress);
assertFunction(wallet.splitIntegratedAddress);
});
test('WalletRPC has account methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.getAccounts);
assertFunction(wallet.createAccount);
assertFunction(wallet.labelAccount);
assertFunction(wallet.tagAccounts);
});
test('WalletRPC has balance methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.getBalance);
assertFunction(wallet.getHeight);
assertFunction(wallet.getTransfers);
assertFunction(wallet.getTransferByTxid);
assertFunction(wallet.getPayments);
assertFunction(wallet.incomingTransfers);
});
test('WalletRPC has transfer methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.transfer);
assertFunction(wallet.transferSplit);
assertFunction(wallet.sweepAll);
assertFunction(wallet.sweepSingle);
assertFunction(wallet.sweepDust);
assertFunction(wallet.relayTx);
});
test('WalletRPC has proof methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.getTxKey);
assertFunction(wallet.checkTxKey);
assertFunction(wallet.getTxProof);
assertFunction(wallet.checkTxProof);
assertFunction(wallet.getSpendProof);
assertFunction(wallet.checkSpendProof);
assertFunction(wallet.getReserveProof);
assertFunction(wallet.checkReserveProof);
});
test('WalletRPC has key management methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.queryKey);
assertFunction(wallet.getMnemonic);
assertFunction(wallet.getViewKey);
assertFunction(wallet.getSpendKey);
assertFunction(wallet.exportOutputs);
assertFunction(wallet.importOutputs);
assertFunction(wallet.exportKeyImages);
assertFunction(wallet.importKeyImages);
});
test('WalletRPC has signing methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.sign);
assertFunction(wallet.verify);
});
test('WalletRPC has multisig methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.prepareMultisig);
assertFunction(wallet.makeMultisig);
assertFunction(wallet.exportMultisigInfo);
assertFunction(wallet.importMultisigInfo);
assertFunction(wallet.signMultisig);
assertFunction(wallet.submitMultisig);
});
test('WalletRPC has settings methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.autoRefresh);
assertFunction(wallet.refresh);
assertFunction(wallet.rescanBlockchain);
assertFunction(wallet.setDaemon);
assertFunction(wallet.getAttribute);
assertFunction(wallet.setAttribute);
});
test('WalletRPC has URI methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.makeUri);
assertFunction(wallet.parseUri);
});
test('WalletRPC has utility methods', () => {
const wallet = new WalletRPC();
assertFunction(wallet.getUnlockedBalance);
assertFunction(wallet.getPrimaryAddress);
assertFunction(wallet.sendTo);
assertFunction(wallet.waitForSync);
});
test('createWalletRPC creates WalletRPC instance', () => {
const wallet = createWalletRPC({ url: 'http://localhost:39083' });
assertInstanceOf(wallet, WalletRPC);
assertEqual(wallet.url, 'http://localhost:39083');
});
// ============================================================
// Integration with main module
// ============================================================
console.log('\n--- Main Module Integration Tests ---');
test('RPC exports available from main module', async () => {
const salvium = await import('../src/index.js');
assertFunction(salvium.DaemonRPC);
assertFunction(salvium.WalletRPC);
assertFunction(salvium.createDaemonRPC);
assertFunction(salvium.createWalletRPC);
});
test('RPC namespace available from main module', async () => {
const salvium = await import('../src/index.js');
assertExists(salvium.rpc);
assertFunction(salvium.rpc.DaemonRPC);
assertFunction(salvium.rpc.WalletRPC);
});
// ============================================================
// Summary
// ============================================================
console.log('\n--- RPC Test Summary ---');
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log(`Total: ${passed + failed}`);
if (failed > 0) {
console.log('\n⚠️ Some tests failed!');
process.exit(1);
} else {
console.log('\n✓ All tests passed!');
}