Manuel Aráoz
10 years ago
4 changed files with 1 additions and 357 deletions
@ -1,44 +0,0 @@ |
|||||
title: JSON-RPC |
|
||||
description: A simple interface to connect and make RPC calls to bitcoind. |
|
||||
--- |
|
||||
# JSON-RPC |
|
||||
|
|
||||
## Description |
|
||||
|
|
||||
Bitcoind provides a direct interface to the bitcoin network and it also exposes a `JSON-RPC` API. This class will connect to a local instance of a bitcoind server and make simple or batch RPC calls to it. |
|
||||
|
|
||||
## Connection to bitcoind |
|
||||
|
|
||||
First you will need a running instance of bitcoind, setting up a username and password to connect with it. For more information about running bitcoind please refer to the [official documentation](https://en.bitcoin.it/wiki/Running_Bitcoin). |
|
||||
|
|
||||
The code for creating and configuring an instance of the RPC client looks like this: |
|
||||
|
|
||||
``` |
|
||||
var bitcore = require('bitcore'); |
|
||||
var RPC = bitcore.transport.RPC; |
|
||||
|
|
||||
var client = new RPC('username', 'password', { |
|
||||
host: 'localhost', |
|
||||
port: 18332, |
|
||||
secure: false, |
|
||||
disableAgent: true, |
|
||||
rejectUnauthorized: true |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
|
|
||||
## Examples |
|
||||
|
|
||||
For more information please refer to the [API reference](https://en.bitcoin.it/wiki/API_reference_%28JSON-RPC%29). |
|
||||
|
|
||||
``` |
|
||||
var bitcore = require('bitcore'); |
|
||||
var blockHash = '0000000000000000045d581af7fa3b6110266ece8131424d95bf490af828be1c'; |
|
||||
|
|
||||
var client = new bitcore.transport.RPC('username', 'password'); |
|
||||
|
|
||||
client.getBlock(blockHash, function(err, block) { |
|
||||
// do something with the block |
|
||||
}); |
|
||||
|
|
||||
``` |
|
@ -1,249 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var http = require('http'); |
|
||||
var https = require('https'); |
|
||||
|
|
||||
/** |
|
||||
* A JSON RPC client for bitcoind. An instances of RPC connects to a bitcoind |
|
||||
* server and enables simple and batch RPC calls. |
|
||||
* |
|
||||
* @example |
|
||||
* ```javascript
|
|
||||
* |
|
||||
* 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.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; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 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(); |
|
||||
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: '', |
|
||||
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) { |
|
||||
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 { |
|
||||
this._request({ |
|
||||
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]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
generateRPCMethods(RPC, callspec); |
|
||||
|
|
||||
module.exports = RPC; |
|
@ -1,62 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var chai = require('chai'); |
|
||||
var should = chai.should(); |
|
||||
|
|
||||
var bitcore = require('../..'); |
|
||||
var RPC = bitcore.transport.RPC; |
|
||||
|
|
||||
describe('RPC', function() { |
|
||||
it('should be able to create instance', function() { |
|
||||
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() {}); |
|
||||
}); |
|
||||
|
|
||||
}); |
|
Loading…
Reference in new issue