2013-07-04 13:02:18 -07:00
|
|
|
// RpcClient.js
|
|
|
|
// MIT/X11-like license. See LICENSE.txt.
|
|
|
|
// Copyright 2013 BitPay, Inc.
|
|
|
|
require('classtool');
|
|
|
|
|
|
|
|
function ClassSpec(b) {
|
|
|
|
var http = b.http || require('http');
|
|
|
|
var https = b.https || require('https');
|
2013-10-31 09:40:06 -07:00
|
|
|
var log = b.log || require('./util/log');
|
2013-07-04 13:02:18 -07:00
|
|
|
|
|
|
|
function RpcClient(opts) {
|
|
|
|
opts = opts || {};
|
|
|
|
this.host = opts.host || '127.0.0.1';
|
|
|
|
this.port = opts.port || 8332;
|
|
|
|
this.user = opts.user || 'user';
|
|
|
|
this.pass = opts.pass || 'pass';
|
|
|
|
this.protocol = (opts.protocol == 'http') ? http : https;
|
2013-07-18 13:21:48 -07:00
|
|
|
this.batchedCalls = null;
|
2014-01-15 16:51:51 -08:00
|
|
|
this.disableAgent = opts.disableAgent || false;
|
2013-07-18 13:21:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
RpcClient.prototype.batch = function(batchCallback, resultCallback) {
|
|
|
|
this.batchedCalls = [];
|
|
|
|
batchCallback();
|
|
|
|
rpc.call(this, this.batchedCalls, resultCallback);
|
|
|
|
this.batchedCalls = null;
|
2013-07-04 13:02:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
var callspec = {
|
|
|
|
addMultiSigAddress: '',
|
|
|
|
addNode: '',
|
|
|
|
backupWallet: '',
|
|
|
|
createMultiSig: '',
|
|
|
|
createRawTransaction: '',
|
|
|
|
decodeRawTransaction: '',
|
|
|
|
dumpPrivKey: '',
|
|
|
|
encryptWallet: '',
|
|
|
|
getAccount: '',
|
2013-09-14 19:58:23 -07:00
|
|
|
getAccountAddress: 'str',
|
2013-07-04 13:02:18 -07:00
|
|
|
getAddedNodeInfo: '',
|
|
|
|
getAddressesByAccount: '',
|
2013-07-17 11:09:59 -07:00
|
|
|
getBalance: 'str int',
|
2014-01-15 13:23:50 -08:00
|
|
|
getBestBlockHash: '',
|
2013-07-04 13:02:18 -07:00
|
|
|
getBlock: '',
|
|
|
|
getBlockCount: '',
|
|
|
|
getBlockHash: 'int',
|
|
|
|
getBlockNumber: '',
|
|
|
|
getBlockTemplate: '',
|
|
|
|
getConnectionCount: '',
|
|
|
|
getDifficulty: '',
|
|
|
|
getGenerate: '',
|
|
|
|
getHashesPerSec: '',
|
|
|
|
getInfo: '',
|
|
|
|
getMemoryPool: '',
|
|
|
|
getMiningInfo: '',
|
|
|
|
getNewAddress: '',
|
|
|
|
getPeerInfo: '',
|
|
|
|
getRawMemPool: '',
|
|
|
|
getRawTransaction: 'str int',
|
|
|
|
getReceivedByAccount: 'str int',
|
|
|
|
getReceivedByAddress: 'str int',
|
|
|
|
getTransaction: '',
|
|
|
|
getTxOut: 'str int bool',
|
|
|
|
getTxOutSetInfo: '',
|
|
|
|
getWork: '',
|
|
|
|
help: '',
|
2013-07-18 14:16:54 -07:00
|
|
|
importAddress: 'str str bool',
|
2013-07-04 13:02:18 -07:00
|
|
|
importPrivKey: 'str str bool',
|
2013-10-31 09:40:06 -07:00
|
|
|
keyPoolRefill: '',
|
2013-07-04 13:02:18 -07:00
|
|
|
listAccounts: 'int',
|
|
|
|
listAddressGroupings: '',
|
|
|
|
listReceivedByAccount: 'int bool',
|
|
|
|
listReceivedByAddress: 'int bool',
|
|
|
|
listSinceBlock: 'str int',
|
|
|
|
listTransactions: 'str int int',
|
|
|
|
listUnspent: 'int int',
|
|
|
|
listLockUnspent: 'bool',
|
|
|
|
lockUnspent: '',
|
|
|
|
move: 'str str float int str',
|
|
|
|
sendFrom: 'str str float int str str',
|
|
|
|
sendMany: 'str str int str', //not sure this is will work
|
|
|
|
sendRawTransaction: '',
|
|
|
|
sendToAddress: 'str float str str',
|
|
|
|
setAccount: '',
|
|
|
|
setGenerate: 'bool int',
|
|
|
|
setTxFee: 'float',
|
|
|
|
signMessage: '',
|
|
|
|
signRawTransaction: '',
|
|
|
|
stop: '',
|
|
|
|
submitBlock: '',
|
|
|
|
validateAddress: '',
|
|
|
|
verifyMessage: '',
|
|
|
|
walletLock: '',
|
2013-08-12 08:18:38 -07:00
|
|
|
walletPassPhrase: 'string int',
|
2013-07-04 13:02:18 -07:00
|
|
|
walletPassphraseChange: '',
|
|
|
|
};
|
|
|
|
|
|
|
|
var slice = function(arr, start, end) {
|
|
|
|
return Array.prototype.slice.call(arr, start, end);
|
|
|
|
};
|
|
|
|
|
|
|
|
function generateRPCMethods(constructor, apiCalls, rpc) {
|
|
|
|
function createRPCMethod(methodName, argMap) {
|
|
|
|
return function() {
|
2013-07-18 13:21:48 -07:00
|
|
|
var limit = arguments.length - 1;
|
|
|
|
if(this.batchedCalls) var limit = arguments.length;
|
|
|
|
for (var i=0; i<limit; i++) {
|
2013-07-04 13:02:18 -07:00
|
|
|
if(argMap[i]) arguments[i] = argMap[i](arguments[i]);
|
|
|
|
};
|
2013-07-18 13:21:48 -07:00
|
|
|
if(this.batchedCalls) {
|
|
|
|
this.batchedCalls.push({jsonrpc: '2.0', method: methodName, params: slice(arguments)});
|
|
|
|
} else {
|
|
|
|
rpc.call(this, {method: methodName, params: slice(arguments, 0, arguments.length - 1)}, arguments[arguments.length - 1]);
|
|
|
|
}
|
2013-07-04 13:02:18 -07:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
var types = {
|
|
|
|
str: function(arg) {return arg.toString();},
|
|
|
|
int: function(arg) {return parseFloat(arg);},
|
|
|
|
float: function(arg) {return parseFloat(arg);},
|
2013-07-18 18:24:27 -07:00
|
|
|
bool: function(arg) {return (arg === true || arg == '1' || arg == 'true' || arg.toString().toLowerCase() == 'true');},
|
2013-07-04 13:02:18 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
for(var k in apiCalls) {
|
2014-02-19 11:07:50 -08:00
|
|
|
if (apiCalls.hasOwnProperty(k)) {
|
|
|
|
var spec = apiCalls[k].split(' ');
|
|
|
|
for (var i = 0; i < spec.length; i++) {
|
|
|
|
if(types[spec[i]]) {
|
|
|
|
spec[i] = types[spec[i]];
|
|
|
|
} else {
|
|
|
|
spec[i] = types.string;
|
|
|
|
}
|
2013-07-04 13:02:18 -07:00
|
|
|
}
|
2014-02-19 11:07:50 -08:00
|
|
|
var methodName = k.toLowerCase();
|
|
|
|
constructor.prototype[k] = createRPCMethod(methodName, spec);
|
|
|
|
constructor.prototype[methodName] = constructor.prototype[k];
|
|
|
|
}
|
2013-07-04 13:02:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-18 13:21:48 -07:00
|
|
|
function rpc(request, callback) {
|
2013-07-04 13:02:18 -07:00
|
|
|
var self = this;
|
|
|
|
var request;
|
|
|
|
request = JSON.stringify(request);
|
|
|
|
var auth = Buffer(self.user + ':' + self.pass).toString('base64');
|
|
|
|
|
|
|
|
var options = {
|
|
|
|
host: self.host,
|
|
|
|
path: '/',
|
|
|
|
method: 'POST',
|
|
|
|
port: self.port,
|
2014-01-15 16:51:51 -08:00
|
|
|
agent: self.disableAgent ? false : undefined,
|
2013-07-04 13:02:18 -07:00
|
|
|
};
|
|
|
|
if(self.httpOptions) {
|
|
|
|
for(var k in self.httpOptions) {
|
|
|
|
options[k] = self.httpOptions[k];
|
|
|
|
}
|
|
|
|
}
|
2013-10-31 09:40:06 -07:00
|
|
|
var err = null;
|
2013-07-04 13:02:18 -07:00
|
|
|
var req = this.protocol.request(options, function(res) {
|
2013-10-31 09:40:06 -07:00
|
|
|
|
2013-07-04 13:02:18 -07:00
|
|
|
var buf = '';
|
|
|
|
res.on('data', function(data) {
|
|
|
|
buf += data;
|
|
|
|
});
|
|
|
|
res.on('end', function() {
|
2013-10-31 09:40:06 -07:00
|
|
|
if(res.statusCode == 401) {
|
2014-01-28 15:10:24 -08:00
|
|
|
callback(new Error('bitcoin JSON-RPC connection rejected: 401 unauthorized'));
|
2013-10-31 09:40:06 -07:00
|
|
|
return;
|
|
|
|
}
|
2014-01-28 15:10:24 -08:00
|
|
|
if(res.statusCode == 403) {
|
|
|
|
callback(new Error('bitcoin JSON-RPC connection rejected: 403 forbidden'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-31 09:40:06 -07:00
|
|
|
if(err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-07-04 13:02:18 -07:00
|
|
|
try {
|
|
|
|
var parsedBuf = JSON.parse(buf);
|
|
|
|
} catch(e) {
|
|
|
|
log.err(e.stack);
|
|
|
|
log.err(buf);
|
2014-01-28 15:10:24 -08:00
|
|
|
log.err('HTTP Status code:' + res.statusCode);
|
2013-07-04 13:02:18 -07:00
|
|
|
callback(e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
callback(parsedBuf.error, parsedBuf);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.on('error', function(e) {
|
2014-01-14 11:00:05 -08:00
|
|
|
var err = new Error('Could not connect to bitcoin via RPC: '+e.message);
|
|
|
|
log.err(err);
|
|
|
|
callback(err);
|
2013-07-04 13:02:18 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
req.setHeader('Content-Length', request.length);
|
|
|
|
req.setHeader('Content-Type', 'application/json');
|
|
|
|
req.setHeader('Authorization', 'Basic ' + auth);
|
|
|
|
req.write(request);
|
|
|
|
req.end();
|
|
|
|
};
|
|
|
|
|
|
|
|
generateRPCMethods(RpcClient, callspec, rpc);
|
|
|
|
return RpcClient;
|
|
|
|
};
|
|
|
|
module.defineClass(ClassSpec);
|
|
|
|
|