From 4173f4b49ebb294015ab289331c83b5756cf109c Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 16 Dec 2014 10:11:58 -0300 Subject: [PATCH 1/2] include old files --- lib/transport/rpc.js | 223 ++++++++++++++++++++++++++++++++++++++++++ test/transport/rpc.js | 28 ++++++ 2 files changed, 251 insertions(+) create mode 100644 lib/transport/rpc.js create mode 100644 test/transport/rpc.js diff --git a/lib/transport/rpc.js b/lib/transport/rpc.js new file mode 100644 index 000000000..3d1c39fc3 --- /dev/null +++ b/lib/transport/rpc.js @@ -0,0 +1,223 @@ +// RpcClient.js +// MIT/X11-like license. See LICENSE.txt. +// Copyright 2013 BitPay, Inc. +// +var http = require('http'); +var https = require('https'); +var log = require('../util/log'); + +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; + this.batchedCalls = null; + this.disableAgent = opts.disableAgent || false; + this.rejectUnauthorized = opts.rejectUnauthorized || false; +} + +RpcClient.prototype.batch = function(batchCallback, resultCallback) { + this.batchedCalls = []; + batchCallback(); + rpc.call(this, this.batchedCalls, resultCallback); + this.batchedCalls = null; +} + +var callspec = { + addMultiSigAddress: '', + addNode: '', + backupWallet: '', + createMultiSig: '', + createRawTransaction: '', + decodeRawTransaction: '', + dumpPrivKey: '', + encryptWallet: '', + getAccount: '', + getAccountAddress: 'str', + getAddedNodeInfo: '', + getAddressesByAccount: '', + getBalance: 'str int', + getBestBlockHash: '', + 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: '', + importAddress: 'str str bool', + importPrivKey: 'str str bool', + keyPoolRefill: '', + 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: '', + walletPassPhrase: 'string int', + 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() { + var limit = arguments.length - 1; + if (this.batchedCalls) var limit = arguments.length; + for (var 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) + }); + } else { + rpc.call(this, { + method: methodName, + params: slice(arguments, 0, arguments.length - 1) + }, arguments[arguments.length - 1]); + } + }; + }; + + var types = { + str: function(arg) { + return arg.toString(); + }, + int: function(arg) { + return parseFloat(arg); + }, + float: function(arg) { + return parseFloat(arg); + }, + bool: function(arg) { + return (arg === true || arg == '1' || arg == 'true' || arg.toString().toLowerCase() == 'true'); + }, + }; + + for (var k in apiCalls) { + 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; + } + } + var methodName = k.toLowerCase(); + constructor.prototype[k] = createRPCMethod(methodName, spec); + constructor.prototype[methodName] = constructor.prototype[k]; + } + } +} + +function rpc(request, callback) { + 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, + rejectUnauthorized: self.rejectUnauthorized, + agent: self.disableAgent ? false : undefined, + }; + if (self.httpOptions) { + for (var k in self.httpOptions) { + options[k] = self.httpOptions[k]; + } + } + var err = null; + var req = this.protocol.request(options, function(res) { + + var buf = ''; + res.on('data', function(data) { + buf += data; + }); + res.on('end', function() { + if (res.statusCode == 401) { + callback(new Error('bitcoin JSON-RPC connection rejected: 401 unauthorized')); + return; + } + if (res.statusCode == 403) { + callback(new Error('bitcoin JSON-RPC connection rejected: 403 forbidden')); + return; + } + + if (err) { + callback(err); + return; + } + try { + var parsedBuf = JSON.parse(buf); + } catch (e) { + log.err(e.stack); + log.err(buf); + log.err('HTTP Status code:' + res.statusCode); + callback(e); + return; + } + callback(parsedBuf.error, parsedBuf); + }); + }); + req.on('error', function(e) { + var err = new Error('Could not connect to bitcoin via RPC at host: ' + self.host + ' port: ' + self.port + ' Error: ' + e.message); + log.err(err); + callback(err); + }); + + 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); + +module.exports = RpcClient; diff --git a/test/transport/rpc.js b/test/transport/rpc.js new file mode 100644 index 000000000..13ed4351c --- /dev/null +++ b/test/transport/rpc.js @@ -0,0 +1,28 @@ +'use strict'; + +var chai = chai || require('chai'); +var bitcore = bitcore || require('../bitcore'); + +var should = chai.should(); + +var RpcClientModule = bitcore.RpcClient; +var RpcClient; + RpcClient = RpcClientModule; + +describe('RpcClient', function() { + it('should initialze the main object', function() { + should.exist(RpcClientModule); + }); + it('should be able to create class', function() { + should.exist(RpcClient); + }); + it('should be able to create instance', function() { + var s = new RpcClient(); + should.exist(s); + }); +}); + + + + + From d7b8b370f1ee3b0df6d43609c7044a11c99a0571 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 17 Dec 2014 03:34:22 -0300 Subject: [PATCH 2/2] RPC: Refactor, documentation and tests --- lib/transport/index.js | 5 +- lib/transport/rpc.js | 184 +++++++++++++++++++++++------------------ test/transport/rpc.js | 74 ++++++++++++----- 3 files changed, 161 insertions(+), 102 deletions(-) diff --git a/lib/transport/index.js b/lib/transport/index.js index f1cbcbf1b..f219f81bc 100644 --- a/lib/transport/index.js +++ b/lib/transport/index.js @@ -2,7 +2,8 @@ * @namespace Transport */ module.exports = { - Peer: require('./peer'), Messages: require('./messages'), - Pool: require('./pool') + Peer: require('./peer'), + Pool: require('./pool'), + RPC: require('./rpc') }; diff --git a/lib/transport/rpc.js b/lib/transport/rpc.js index 3d1c39fc3..6fcee3f5c 100644 --- a/lib/transport/rpc.js +++ b/lib/transport/rpc.js @@ -1,30 +1,118 @@ -// RpcClient.js -// MIT/X11-like license. See LICENSE.txt. -// Copyright 2013 BitPay, Inc. -// +'use strict'; + var http = require('http'); var https = require('https'); -var log = require('../util/log'); -function RpcClient(opts) { +/** + * A JSON RPC client for bitcoind. An instances of RPC connects to a bitcoind + * server and enables simple and batch RPC calls. + * + * @example + * + * var client = new RPC('user', 'pass'); + * client.getInfo(function(err, info) { + * // do something with the info + * }); + * + * @param {String} user - username used to connect bitcoind + * @param {String} password - password used to connect bitcoind + * @param {Object} opts - Connection options: host, port, secure, disableAgent, rejectUnauthorized + * @returns {RPC} + * @constructor + */ +function RPC(user, password, opts) { + if (!(this instanceof RPC)) { + return new RPC(user, password, opts); + } + + this.user = user; + this.pass = password; + 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; + + this.secure = typeof opts.secure === 'undefined' ? true : opts.secure; + this._client = opts.secure ? https : http; + this.batchedCalls = null; this.disableAgent = opts.disableAgent || false; this.rejectUnauthorized = opts.rejectUnauthorized || false; } -RpcClient.prototype.batch = function(batchCallback, resultCallback) { +/** + * Allows to excecute RPC calls in batch. + * + * @param {Function} batchCallback - Function that makes all calls to be excecuted in bach + * @param {Function} resultCallbak - Function to be called on result + */ +RPC.prototype.batch = function(batchCallback, resultCallback) { this.batchedCalls = []; batchCallback(); - rpc.call(this, this.batchedCalls, resultCallback); + this._request(this.batchedCalls, resultCallback); this.batchedCalls = null; } +/** + * Internal function to make an RPC call + * + * @param {Object} request - Object to be serialized and sent to bitcoind + * @param {Function} callbak - Function to be called on result + */ +RPC.prototype._request = function(request, callback) { + var self = this; + + var request = JSON.stringify(request); + var auth = Buffer(self.user + ':' + self.pass).toString('base64'); + + var options = { + host: self.host, + path: '/', + method: 'POST', + port: self.port, + rejectUnauthorized: self.rejectUnauthorized, + agent: self.disableAgent ? false : undefined + }; + + var req = this._client.request(options, function(res) { + var buf = ''; + res.on('data', function(data) { + buf += data; + }); + + res.on('end', function() { + if (res.statusCode == 401) { + var error = new Error('bitcoin JSON-RPC connection rejected: 401 unauthorized'); + return callback(error); + } + + if (res.statusCode == 403) { + var error = new Error('bitcoin JSON-RPC connection rejected: 403 forbidden'); + return callback(error); + } + + try { + var parsedBuf = JSON.parse(buf); + } catch (e) { + return callback(e); + } + + callback(parsedBuf.error, parsedBuf); + }); + }); + + req.on('error', function(e) { + var err = new Error('Could not connect to bitcoin via RPC at host: ' + self.host + ' port: ' + self.port + ' Error: ' + e.message); + callback(err); + }); + + req.setHeader('Content-Length', request.length); + req.setHeader('Content-Type', 'application/json'); + req.setHeader('Authorization', 'Basic ' + auth); + req.write(request); + req.end(); +}; + var callspec = { addMultiSigAddress: '', addNode: '', @@ -94,11 +182,12 @@ var callspec = { walletPassphraseChange: '', }; + var slice = function(arr, start, end) { return Array.prototype.slice.call(arr, start, end); }; -function generateRPCMethods(constructor, apiCalls, rpc) { +function generateRPCMethods(constructor, apiCalls) { function createRPCMethod(methodName, argMap) { return function() { var limit = arguments.length - 1; @@ -113,7 +202,7 @@ function generateRPCMethods(constructor, apiCalls, rpc) { params: slice(arguments) }); } else { - rpc.call(this, { + this._request({ method: methodName, params: slice(arguments, 0, arguments.length - 1) }, arguments[arguments.length - 1]); @@ -153,71 +242,6 @@ function generateRPCMethods(constructor, apiCalls, rpc) { } } -function rpc(request, callback) { - var self = this; - var request; - request = JSON.stringify(request); - var auth = Buffer(self.user + ':' + self.pass).toString('base64'); +generateRPCMethods(RPC, callspec); - var options = { - host: self.host, - path: '/', - method: 'POST', - port: self.port, - rejectUnauthorized: self.rejectUnauthorized, - agent: self.disableAgent ? false : undefined, - }; - if (self.httpOptions) { - for (var k in self.httpOptions) { - options[k] = self.httpOptions[k]; - } - } - var err = null; - var req = this.protocol.request(options, function(res) { - - var buf = ''; - res.on('data', function(data) { - buf += data; - }); - res.on('end', function() { - if (res.statusCode == 401) { - callback(new Error('bitcoin JSON-RPC connection rejected: 401 unauthorized')); - return; - } - if (res.statusCode == 403) { - callback(new Error('bitcoin JSON-RPC connection rejected: 403 forbidden')); - return; - } - - if (err) { - callback(err); - return; - } - try { - var parsedBuf = JSON.parse(buf); - } catch (e) { - log.err(e.stack); - log.err(buf); - log.err('HTTP Status code:' + res.statusCode); - callback(e); - return; - } - callback(parsedBuf.error, parsedBuf); - }); - }); - req.on('error', function(e) { - var err = new Error('Could not connect to bitcoin via RPC at host: ' + self.host + ' port: ' + self.port + ' Error: ' + e.message); - log.err(err); - callback(err); - }); - - 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); - -module.exports = RpcClient; +module.exports = RPC; diff --git a/test/transport/rpc.js b/test/transport/rpc.js index 13ed4351c..0b292666a 100644 --- a/test/transport/rpc.js +++ b/test/transport/rpc.js @@ -1,28 +1,62 @@ 'use strict'; -var chai = chai || require('chai'); -var bitcore = bitcore || require('../bitcore'); - +var chai = require('chai'); var should = chai.should(); -var RpcClientModule = bitcore.RpcClient; -var RpcClient; - RpcClient = RpcClientModule; +var bitcore = require('../..'); +var RPC = bitcore.transport.RPC; -describe('RpcClient', function() { - it('should initialze the main object', function() { - should.exist(RpcClientModule); - }); - it('should be able to create class', function() { - should.exist(RpcClient); - }); +describe('RPC', function() { it('should be able to create instance', function() { - var s = new RpcClient(); - should.exist(s); + var client = new RPC('user', 'pass'); + should.exist(client); }); + + it('should set default config', function() { + var client = new RPC('user', 'pass'); + client.user.should.be.equal('user'); + client.pass.should.be.equal('pass'); + + client.host.should.be.equal('127.0.0.1'); + client.port.should.be.equal(8332); + client.secure.should.be.equal(true); + client.disableAgent.should.be.equal(false); + client.rejectUnauthorized.should.be.equal(false); + }); + + it('should allow setting custom host and port', function() { + var client = new RPC('user', 'pass', { + host: 'localhost', + port: 18332 + }); + + client.host.should.be.equal('localhost'); + client.port.should.be.equal(18332); + }); + + it('should honor request options', function() { + var client = new RPC('user', 'pass', { + host: 'localhost', + port: 18332, + rejectUnauthorized: true, + disableAgent: true + }); + + client._client = {}; + client._client.request = function(options, callback) { + options.host.should.be.equal('localhost'); + options.port.should.be.equal(18332); + options.rejectUnauthorized.should.be.equal(true); + options.agent.should.be.false; + return { + on: function() {}, + setHeader: function() {}, + write: function() {}, + end: function() {} + }; + }; + + client._request({}, function() {}); + }); + }); - - - - -