smart-rpc
Version:
Block and Transaction Real-Time Broadcasting in JavaScript.
185 lines (143 loc) • 7.38 kB
JavaScript
'use strict';
var axios = require('axios'),
util = require('util'),
log = require('loglevel'),
zmq = require('zeromq'),
LRU = require("lru-cache"),
Hash = require('mix-hash'),
EventEmitter = require('events').EventEmitter;
function RPCClient(option) {
EventEmitter.call(this);
log.setLevel("info");
this.opts = option;
this.id = option.id || option._id;
this.apis = ['abandonTransaction', 'addMultiSigAddress', 'addNode', 'backupWallet', 'clearBanned', 'createMultiSig', 'createRawTransaction', 'debug', 'decodeRawTransaction', 'decodeScript', 'disconnectNode', 'dumpPrivKey', 'dumpWallet', 'encryptWallet', 'estimateFee', 'estimatePriority', 'estimateSmartFee', 'estimateSmartPriority', 'fundRawTransaction', 'generate', 'getAccount', 'getAccountAddress', 'getAddressMempool', 'getAddressUtxos', 'getAddressBalance', 'getAddressDeltas', 'getAddressTxids', 'getAddressesByAccount', 'getAddedNodeInfo', 'getBalance', 'getBestBlockHash', 'getBlock', 'getBlockchainInfo', 'getBlockCount', 'getBlockHashes', 'getBlockHash', 'getBlockHeader', 'getBlockHeaders', 'getBlockTemplate', 'getConnectionCount', 'getChainTips', 'getDifficulty', 'getGenerate', 'getGovernanceInfo', 'getGovernanceInfo', 'getInfo', 'getMemPoolInfo', 'getMiningInfo', 'getNewAddress', 'getNetTotals', 'getNetworkInfo', 'getNetworkHashps', 'getPeerInfo', 'getPoolInfo', 'getRawMemPool', 'getRawChangeAddress', 'getRawTransaction', 'getReceivedByAccount', 'getReceivedByAddress', 'getSpentInfo', 'getSuperBlockBudget', 'getTransaction', 'getTxOut', 'getTxOutProof', 'getTxOutSetInfo', 'getWalletInfo', 'help', 'importAddress', 'instantSendToAddress', 'gobject', 'invalidateBlock', 'importPrivKey', 'importPubKey', 'importElectrumWallet', 'importWallet', 'keyPoolRefill', 'listAccounts', 'listAddressGroupings', 'listBanned', 'listReceivedByAccount', 'listReceivedByAddress', 'listSinceBlock', 'listTransactions', 'listUnspent', 'listLockUnspent', 'lockUnspent', 'masternode', 'masternodeBroadcast', 'masternodeList', 'mnsync', 'move', 'ping', 'prioritiseTransaction', 'privateSend', 'reconsiderBlock', 'resendWalletTransactions', 'sendFrom', 'sendMany', 'sendRawTransaction', 'sendToAddress', 'sentinelPing', 'setAccount', 'setBan', 'setGenerate', 'setTxFee', 'setMockTime', 'spork', 'signMessage', 'signRawTransaction', 'stop', 'submitBlock', 'validateAddress', 'verifyMessage', 'verifyChain', 'verifyTxOutProof', 'voteRaw', 'walletLock', 'walletPassPhrase', 'walletPassphraseChange'];
this.transactions = LRU(5000);
this.transactionLocks = LRU(5000);
this.blocks = LRU(50);
this.retry = 0;
this.axios = axios.create(option.rpc);
this.axios.interceptors.response.use(function (response) {
// Do something with response data
//console.log(response);
return response && response.data ? response.data.result : null;
}, function (error) {
// Do something with response error
// console.log(util.inspect(error));
return Promise.reject(error && error.response ? error.response.data : error);
});
this.init();
if (option.ready) {
this.ready();
}
if (option.listen && option.socket) {
this.listen();
}
}
RPCClient.prototype = {
init: function () {
// var own = this;
for (let i = 0; i < this.apis.length; i++) {
const a = this.apis[i];
this[a] = function () {
var params = [].slice.call(arguments);
return this.axios.request({
data: {
jsonrpc: '2.0',
method: a.toLowerCase(),
params: params,
id: new Date().getTime()
},
maxContentLength: 50 * 1000 * 1000,
timeout: 60000
});
}
};
},
ready: function () {
var self = this;
self.retry += 1;
log.info('Check %s Daemon Ready', self.id, new Date());
this.getBestBlockHash().then((hash) => {
self.getBlock(hash).then((data) => {
self.emit('ready');
log.info('%s Daemon Ready', self.id);
}).catch(err => {
setTimeout(function () {
self.ready();
}, self.retry * 5000);
});
}).catch(err => {
setTimeout(function () {
self.ready();
}, self.retry * 5000);
});
},
listen: function () {
var self = this;
this.socket = zmq.socket('sub');
this.socket.on('connect', function (fd, endPoint) {
self.retry = 0;
log.info('%s ZMQ connected to:', self.id, endPoint);
});
this.socket.on('connect_delay', function (fd, endPoint) {
log.warn('%s ZMQ connection delay:', self.id, endPoint);
});
this.socket.on('disconnect', function (fd, endPoint) {
log.warn('%s ZMQ disconnect:', self.id, endPoint);
});
this.socket.on('monitor_error', function (err) {
self.retry += 1;
log.error('Error in monitoring: %s, will restart monitoring in 5 seconds', err);
setTimeout(function () {
self.socket.monitor(500, 0);
}, self.retry * 5000);
});
// subscribe events
this.socket.subscribe('hashblock');
this.socket.subscribe('rawtx');
this.socket.subscribe('rawtxlock');
this.socket.on('message', function (topic, message) {
var cmd = topic.toString('utf8');
switch (cmd) {
case 'rawtxlock':
var hash = message.toString('hex');
var id = Hash.md5(hash);
if (!self.transactionLocks.get(id)) {
self.transactionLocks.set(id, true);
self.decodeRawTransaction(hash).then(data => {
self.emit('txlock', data);
});
}
break;
case 'rawtx':
var hash = message.toString('hex');
var id = Hash.md5(hash);
if (!self.transactions.get(id)) {
self.transactions.set(id, true);
self.emit('rawtx', message);
self.decodeRawTransaction(hash).then(data => {
self.emit('tx', data);
});
}
break;
case 'hashblock':
// Notify block subscribers
var hash = message.toString('hex');
if (!self.blocks.get(hash)) {
self.blocks.set(hash, true);
self.emit('hashblock', hash);
self.getBlock(hash).then(data => {
self.emit('block', data);
});
}
break;
}
});
log.info('Start monitoring...');
this.socket.monitor(500, 0);
this.socket.connect(this.opts.socket);
}
};
util.inherits(RPCClient, EventEmitter);
module.exports = RPCClient;