alterdotd-rpc
Version:
Alterdot Client Library to connect to Alterdot (alterdotd) via RPC
373 lines (336 loc) • 9.59 kB
JavaScript
/* eslint-disable */
const http = require('http');
const https = require('https');
const async = require('async');
function RpcClient(opts) {
opts = opts || {};
this.host = opts.host || '127.0.0.1';
this.port = opts.port || 31050;
this.user = opts.user || 'user';
this.pass = opts.pass || 'pass';
this.protocol = opts.protocol === 'http' ? http : https;
this.batchedCalls = null;
this.disableAgent = opts.disableAgent || false;
const queueSize = opts.queue || 16;
const isRejectUnauthorized = typeof opts.rejectUnauthorized !== 'undefined';
this.rejectUnauthorized = isRejectUnauthorized ? opts.rejectUnauthorized : true;
if (RpcClient.config.log) {
this.log = RpcClient.config.log;
} else {
this.log = RpcClient.loggers[RpcClient.config.logger || 'normal'];
}
this.queue = async.queue((task, callback) => {
task(callback);
}, queueSize);
}
const cl = console.log.bind(console);
const noop = function () {
};
RpcClient.loggers = {
none: {
info: noop, warn: noop, err: noop, debug: noop,
},
normal: {
info: cl, warn: cl, err: cl, debug: noop,
},
debug: {
info: cl, warn: cl, err: cl, debug: cl,
},
};
RpcClient.config = {
logger: 'normal', // none, normal, debug,
};
function rpc(request, callback) {
const self = this;
const task = function (taskCallback) {
const newCallback = function () {
callback.apply(undefined, arguments);
taskCallback();
};
innerRpc.call(self, request, newCallback);
};
this.queue.push(task);
}
function innerRpc(request, callback) {
const self = this;
request = JSON.stringify(request);
const auth = Buffer.from(`${self.user}:${self.pass}`).toString('base64');
const options = {
host: self.host,
path: '/',
method: 'POST',
port: self.port,
rejectUnauthorized: self.rejectUnauthorized,
agent: self.disableAgent ? false : undefined,
};
if (self.httpOptions) {
for (const k in self.httpOptions) {
options[k] = self.httpOptions[k];
}
}
let called = false;
const errorMessage = 'Alterdot JSON-RPC: ';
const req = this.protocol.request(options, (res) => {
let buf = '';
res.on('data', (data) => {
buf += data;
});
res.on('end', () => {
if (called) {
return;
}
called = true;
if (res.statusCode === 401) {
callback(new Error(`${errorMessage}Connection Rejected: 401 Unnauthorized`));
return;
}
if (res.statusCode === 403) {
callback(new Error(`${errorMessage}Connection Rejected: 403 Forbidden`));
return;
}
if (res.statusCode === 403) {
callback(new Error(`${errorMessage}Connection Rejected: 403 Forbidden`));
return;
}
if (res.statusCode === 500 && buf.toString('utf8') === 'Work queue depth exceeded') {
const exceededError = new Error(`Alterdot JSON-RPC: ${buf.toString('utf8')}`);
exceededError.code = 429; // Too many requests
callback(exceededError);
return;
}
let parsedBuf;
try {
parsedBuf = JSON.parse(buf);
} catch (e) {
self.log.err(e.stack);
self.log.err(buf);
self.log.err(`HTTP Status code:${res.statusCode}`);
const err = new Error(`${errorMessage}Error Parsing JSON: ${e.message}`);
callback(err);
return;
}
callback(parsedBuf.error, parsedBuf);
});
});
req.on('error', (e) => {
const err = new Error(`${errorMessage}Request Error: ${e.message}`);
if (!called) {
called = true;
callback(err);
}
});
req.setHeader('Content-Length', request.length);
req.setHeader('Content-Type', 'application/json');
req.setHeader('Authorization', `Basic ${auth}`);
req.write(request);
req.end();
}
RpcClient.prototype.batch = function (batchCallback, resultCallback) {
this.batchedCalls = [];
batchCallback();
rpc.call(this, this.batchedCalls, resultCallback);
this.batchedCalls = null;
};
// For definitions of RPC calls, see various files in: https://github.com/Alterdot/Alterdot/tree/master/src
RpcClient.callspec = {
abandonTransaction: 'str',
addMultiSigAddress: 'int str str',
addNode: 'str str',
backupWallet: 'str',
clearBanned: '',
createMultiSig: 'int str',
createRawTransaction: 'str str int',
debug: 'str',
decodeRawTransaction: 'str',
decodeScript: 'str',
disconnectNode: 'str',
dumpPrivKey: 'str',
dumpWallet: 'str',
encryptWallet: 'str',
estimateFee: 'int',
estimatePriority: 'int',
estimateSmartFee: 'int',
estimateSmartPriority: 'int',
fundRawTransaction: 'str bool',
generate: 'int',
generateToAddress: 'int str',
getAccount: 'str',
getAccountAddress: 'str',
getAddressMempool: 'obj',
getAddressUtxos: 'obj',
getAddressBalance: 'obj',
getAddressDeltas: 'obj',
getAddressTxids: 'obj',
getAddressesByAccount: '',
getAddedNodeInfo: 'bool str',
getBalance: 'str int bool',
getBestBlockHash: '',
getBestChainLock: '',
getBlock: 'str bool',
getBlockchainInfo: '',
getBlockCount: '',
getBlockHashes: 'int int',
getBlockHash: 'int',
getBlockHeader: 'str bool',
getBlockHeaders: 'str int bool',
getBlockTemplate: '',
getConnectionCount: '',
getChainTips: 'int int',
getDifficulty: '',
getGenerate: '',
getGovernanceInfo: '',
getInfo: '',
getMemPoolInfo: '',
getMerkleBlocks: 'str str int',
getMiningInfo: '',
getNewAddress: '',
getNetTotals: '',
getNetworkInfo: '',
getNetworkHashps: 'int int',
getPeerInfo: '',
getPoolInfo: '',
getRawMemPool: 'bool',
getRawChangeAddress: '',
getRawTransaction: 'str int',
getReceivedByAccount: 'str int',
getReceivedByAddress: 'str int',
getSpentInfo: 'obj',
getSuperBlockBudget: 'int',
getTransaction: '',
getTxOut: 'str int bool',
getTxOutProof: 'str str',
getTxOutSetInfo: '',
getWalletInfo: '',
help: 'str',
importAddress: 'str str bool',
instantSendToAddress: 'str int str str bool',
gobject: 'str str',
invalidateBlock: 'str',
importPrivKey: 'str str bool',
importPubKey: 'str str bool',
importElectrumWallet: 'str int',
importWallet: 'str',
keyPoolRefill: 'int',
listAccounts: 'int bool',
listAddressGroupings: '',
listBanned: '',
listReceivedByAccount: 'int bool',
listReceivedByAddress: 'int bool',
listSinceBlock: 'str int',
listTransactions: 'str int int bool',
listUnspent: 'int int str',
listLockUnspent: 'bool',
lockUnspent: 'bool obj',
masternode: 'str',
masternodeBroadcast: 'str',
masternodelist: 'str str',
mnsync: '',
move: 'str str float int str',
ping: '',
prioritiseTransaction: 'str float int',
privateSend: 'str',
protx: 'str str str',
quorum: 'str int str str str str int',
reconsiderBlock: 'str',
resendWalletTransactions: '',
resolveDomain: 'str',
sendFrom: 'str str float int str str',
sendMany: 'str obj int str str bool bool',
sendRawTransaction: 'str bool bool',
sendToAddress: 'str float str str',
sentinelPing: 'str',
setAccount: '',
setBan: 'str str int bool',
setGenerate: 'bool int',
setTxFee: 'float',
setMockTime: 'int',
spork: 'str int',
signMessage: 'str str',
signRawTransaction: 'str str str str',
stop: '',
submitBlock: 'str str',
validateAddress: 'str',
verifyMessage: 'str str str',
verifyChain: 'int int',
verifyChainLock: 'str str int',
verifyIsLock: 'str str str int',
verifyTxOutProof: 'str',
voteRaw: 'str int',
waitForNewBlock: 'int',
waitForBlockHeight: 'int int',
walletLock: '',
walletPassPhrase: 'str int bool',
walletPassphraseChange: 'str str',
getUser: 'str',
};
const slice = function (arr, start, end) {
return Array.prototype.slice.call(arr, start, end);
};
function generateRPCMethods(constructor, apiCalls, rpc) {
function createRPCMethod(methodName, argMap) {
return function () {
let limit = arguments.length - 1;
if (this.batchedCalls) {
limit = arguments.length;
}
for (let i = 0; i < limit; i++) {
if (argMap[i]) {
arguments[i] = argMap[i](arguments[i]);
}
}
if (this.batchedCalls) {
this.batchedCalls.push({
jsonrpc: '2.0',
method: methodName,
params: slice(arguments),
id: getRandomId(),
});
} else {
rpc.call(this, {
method: methodName,
params: slice(arguments, 0, arguments.length - 1),
id: getRandomId(),
}, arguments[arguments.length - 1]);
}
};
}
const types = {
str(arg) {
return arg.toString();
},
int(arg) {
return parseFloat(arg);
},
float(arg) {
return parseFloat(arg);
},
bool(arg) {
return (arg === true || arg == '1' || arg == 'true' || arg.toString().toLowerCase() == 'true');
},
obj(arg) {
if (typeof arg === 'string') {
return JSON.parse(arg);
}
return arg;
},
};
for (const k in apiCalls) {
const spec = apiCalls[k].split(' ');
for (let i = 0; i < spec.length; i++) {
if (types[spec[i]]) {
spec[i] = types[spec[i]];
} else {
spec[i] = types.str;
}
}
const methodName = k.toLowerCase();
constructor.prototype[k] = createRPCMethod(methodName, spec);
constructor.prototype[methodName] = constructor.prototype[k];
}
constructor.prototype.apiCalls = apiCalls;
}
function getRandomId() {
return parseInt(Math.random() * 100000);
}
generateRPCMethods(RpcClient, RpcClient.callspec, rpc);
module.exports = RpcClient;