Compare commits

..

No commits in common. "master" and "v1.1.2" have entirely different histories.

24 changed files with 535 additions and 1110 deletions

5
.gitignore vendored
View File

@ -1,4 +1 @@
node_modules/
test/config.js
.project
node_modules

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "test/testnet-box"]
path = test/testnet-box
url = https://github.com/freewil/bitcoin-testnet-box

View File

@ -1,6 +0,0 @@
node_modules/
test/
.project
.gitmodules
.travis.yml
Dockerfile

View File

@ -1,18 +0,0 @@
# need sudo for docker
sudo: required
language: node_js
# skip install step (npm install)
# See http://docs.travis-ci.com/user/customizing-the-build/#Skipping-the-Installation-Step
install: true
services:
- docker
before_script:
- git submodule update --init
- docker build -t node-bitcoin .
script:
- docker run node-bitcoin

View File

@ -1,144 +0,0 @@
# node-bitcoin changelog
## v3.0.1 (2015/10/25)
* fix redefinition of already defined variable (does not actually affect behavior)
## v3.0.0 (2015/10/18)
* make public domain license explicit
* remove commands dropped in bitcoind v0.11
* `getHashesPerSecond`
* `getHashesPerSec`
* add missing commands for bitcoind v0.11
* `generate`
* `verifyTxOutProof`
## v2.4.0 (2015/07/16)
* don't lazy-load http/https modules
* add command for bitcoind v0.11.0: `getTxOutProof`
## v2.3.2 (2015/06/26)
* fix bug in test suite that was supposed to detect missing commands
* add missing commands
* `prioritiseTransaction`
* `importWallet`
## v2.3.1 (2015/06/24)
* add missing `getMempoolInfo` for bitcoind v0.10
## v2.3.0 (2015/02/04)
* drop node v0.8.x support
* update testnet-box
* update devDependencies
* add commands for bitcoind v0.10
* `estimateFee`
* `estimatePriority`
* `getChainTips`
* `importAddress`
## v2.2.0 (2014/08/29)
* add commands for bitcoind v0.9.x
* `decodeScript`
* `dumpWallet`
* `getBestBlockHash`
* `getBlockchainInfo`
* `getNetTotals`
* `getNetworkInfo`
* `getNetworkHashPs`
* `getRawChangeAddress`
* `getUnconfirmedBalance`
* `getWalletInfo`
* `ping`
* `verifyChain`
## v2.1.2 (2014/04/16)
* lazy load `http`/`https` module
## v2.1.1 (2014/03/25)
* change default request timeout from `5000`ms to `30000`ms
## v2.1.0 (2014/03/12)
* remove `deprecate` dependency
* add request timeout option (defaults to 5000ms)
* add 3rd parameter to callbacks: response headers
## v2.0.1 (2014/01/08)
* default `host` to 'localhost'; `port` to '8332'
## v2.0.0 (2013/10/14)
* remove deprecated commands
* `getMemoryPool`
* `getMemorypool`
* remove deprecated functionality
* creating `bitcoin.Client` with more than one argument
## v1.7.0 (2013/05/05)
* add missing commands from bitcoind v0.7.0
* `createMultiSig`
* `getBlockTemplate`
* `getTxOut`
* `getTxOutSetInfo`
* `listAddressGroupings`
* `submitBlock`
* deprecate commands
* `getMemoryPool`
* `getMemorypool`
## v1.6.2 (2013/03/21)
* shrink package size via .npmignore
## v1.6.1 (2013/03/13)
* add node v0.10.x support (rejectUnauthorized defaults to true in 0.10.x)
## v1.6.0 (2013/03/08)
* drop node v0.6.x support
* change test runner from `vows` to `mocha`
* upgrade testnet-box
* add commands for bitcoind v0.8.0
* `addNode`
* `getAddedNodeInfo`
* `listLockUnspent`
* `lockUnspent`
* deprecate creating `bitcoin.Client` with more than one argument
* add SSL support
## v1.5.0 (2012/10/22)
* remove `getBlockNumber` test
* upgrade testnet-box
* add RPC call batching (multiple RPC calls within one HTTP request)
## v1.4.0 (2012/09/09)
* add commands for bitcoind v0.7.0
* `createRawTransaction`
* `decodeRawTransaction`
* `getPeerInfo`
* `getRawMemPool`
* `getRawTransaction`
* `listUnspent`
* `sendRawTransaction`
* `signRawTransaction`
* remove deprecated `getBlockNumber`
## v1.3.1 (2012/08/19)
Remove `underscore` dependency
## v1.3.0 (2012/07/03)
Change use of http.createClient() (deprecated in node v0.8.x) to http.request()
## v1.2.2 (2012/04/26)
Fix callback being called twice when a client and request error
occur on the same command call.
## v1.2.1 (2012/04/26)
* add missing `getBlock` command
## v1.2.0 (2012/04/25)
* submodule testnet-box for running tests
* err objects should all now be an instance of Error
## v1.1.6 (2012/04/11)
* add commands for bitcoind v0.6.0
* `addMultiSigAddress` (only available in testnet)
* `dumpPrivKey`
* `getBlockHash`
* `getMiningInfo`
* `importPrivKey`

View File

@ -1,21 +0,0 @@
# Dockerfile for running node-bitcoin tests
FROM freewil/bitcoin-testnet-box
MAINTAINER Sean Lavine <lavis88@gmail.com>
# install node.js
USER root
RUN apt-get install --yes curl
RUN curl --silent --location https://deb.nodesource.com/setup_0.12 | bash -
RUN apt-get install --yes nodejs
# set permissions for tester user on project
ADD . /home/tester/node-bitcoin
RUN chown --recursive tester:tester /home/tester/node-bitcoin
# install module dependencies
USER tester
WORKDIR /home/tester/node-bitcoin
RUN npm install
# run test suite
CMD ["npm", "test"]

24
LICENSE
View File

@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

View File

@ -1,44 +0,0 @@
MOCHA=./node_modules/.bin/mocha
BOX=test/testnet-box
test:
$(MAKE) test-ssl-no
sleep 20
$(MAKE) clean
$(MAKE) test-ssl
test-ssl-no:
$(MAKE) start
sleep 20
$(MAKE) run-test
$(MAKE) stop
test-ssl:
$(MAKE) start-ssl
sleep 20
$(MAKE) run-test-ssl
$(MAKE) stop-ssl
start:
$(MAKE) -C $(BOX) start
start-ssl:
$(MAKE) -C $(BOX) start B1_FLAGS=-rpcssl=1 B2_FLAGS=-rpcssl=1
stop:
$(MAKE) -C $(BOX) stop
@while ps -C bitcoind > /dev/null; do sleep 1; done
stop-ssl:
$(MAKE) -C $(BOX) stop B1_FLAGS=-rpcssl=1 B2_FLAGS=-rpcssl=1
run-test:
$(MOCHA) --invert --grep SSL
run-test-ssl:
$(MOCHA) --grep SSL
clean:
$(MAKE) -C $(BOX) clean
.PHONY: test

40
README.md Normal file
View File

@ -0,0 +1,40 @@
# node-bitcoin
node-bitcoin is a simple wrapper for the Bitcoin client's JSON-RPC API.
The API is equivalent to the API document [here](https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list).
The methods are exposed as lower camelcase methods on the `bitcoin.Client`
object.
## Install
`npm install bitcoin`
## Setup
1. Traverse to `~/.bitcoin` or `~/Library/Application Support/Bitcoin` and add a
file called `bitcoin.conf` if it doesn't already exist.
2. Add these lines to the file:
rpcuser=username
rpcpassword=password
You will use these to login to the server.
3. Start your Bitcoin client with the `-server` argument or run `bitcoind`
4. You should now be able to communicate with Bitcoin JSON-RPC API using the
node-bitcoin library, try it out!
## Example
```js
var bitcoin = require('bitcoin');
var client = new bitcoin.Client('localhost', 8332, 'username', 'password');
client.getBalance(function(err, balance) {
if (err) return console.log(err);
console.log("Balance:", balance);
});
```

101
Readme.md
View File

@ -1,101 +0,0 @@
# node-bitcoin
[![travis][travis-image]][travis-url]
[![npm][npm-image]][npm-url]
[![downloads][downloads-image]][downloads-url]
[![js-standard-style][standard-image]][standard-url]
[travis-image]: https://travis-ci.org/freewil/node-bitcoin.svg?branch=master
[travis-url]: https://travis-ci.org/freewil/node-bitcoin
[npm-image]: https://img.shields.io/npm/v/bitcoin.svg?style=flat
[npm-url]: https://npmjs.org/package/bitcoin
[downloads-image]: https://img.shields.io/npm/dm/bitcoin.svg?style=flat
[downloads-url]: https://npmjs.org/package/bitcoin
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat
[standard-url]: http://standardjs.com
node-bitcoin is a simple wrapper for the Bitcoin client's JSON-RPC API.
**Unmaintained, please see [bitcoin-core](https://github.com/ruimarinho/bitcoin-core)**
The API is equivalent to the API document [here](https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list).
The methods are exposed as lower camelcase methods on the `bitcoin.Client`
object, or you may call the API directly using the `cmd` method.
## Install
`npm install bitcoin`
## Examples
### Create client
```js
// all config options are optional
var client = new bitcoin.Client({
host: 'localhost',
port: 8332,
user: 'username',
pass: 'password',
timeout: 30000
});
```
### Get balance across all accounts with minimum confirmations of 6
```js
client.getBalance('*', 6, function(err, balance, resHeaders) {
if (err) return console.log(err);
console.log('Balance:', balance);
});
```
### Getting the balance directly using `cmd`
```js
client.cmd('getbalance', '*', 6, function(err, balance, resHeaders){
if (err) return console.log(err);
console.log('Balance:', balance);
});
```
### Batch multiple RPC calls into single HTTP request
```js
var batch = [];
for (var i = 0; i < 10; ++i) {
batch.push({
method: 'getnewaddress',
params: ['myaccount']
});
}
client.cmd(batch, function(err, address, resHeaders) {
if (err) return console.log(err);
console.log('Address:', address);
});
```
## SSL
See [Enabling SSL on original client](https://en.bitcoin.it/wiki/Enabling_SSL_on_original_client_daemon).
If you're using this to connect to bitcoind across a network it is highly
recommended to enable `ssl`, otherwise an attacker may intercept your RPC credentials
resulting in theft of your bitcoins.
When enabling `ssl` by setting the configuration option to `true`, the `sslStrict`
option (verifies the server certificate) will also be enabled by default. It is
highly recommended to specify the `sslCa` as well, even if your bitcoind has
a certificate signed by an actual CA, to ensure you are connecting
to your own bitcoind.
```js
var client = new bitcoin.Client({
host: 'localhost',
port: 8332,
user: 'username',
pass: 'password',
ssl: true,
sslStrict: true,
sslCa: fs.readFileSync(__dirname + '/myca.cert')
});
```

112
lib/bitcoin/client.js Normal file
View File

@ -0,0 +1,112 @@
var rpc = require('../jsonrpc')
, _ = require('underscore')._;
//===----------------------------------------------------------------------===//
// jsonrpc wrappers
//===----------------------------------------------------------------------===//
var bitcoinAPI = {
backupWallet: 'backupwallet',
encryptWallet: 'encryptwallet',
getAccount: 'getaccount',
getAccountAddress: 'getaccountaddress',
getAddressesByAccount: 'getaddressesbyaccount',
getBalance: 'getbalance',
getBlockCount: 'getblockcount',
getBlockNumber: 'getblocknumber',
getConnectionCount: 'getconnectioncount',
getDifficulty: 'getdifficulty',
getGenerate: 'getgenerate',
getHashesPerSecond: 'gethashespersec',
getHashesPerSec: 'gethashespersec',
getInfo: 'getinfo',
getNewAddress: 'getnewaddress',
getReceivedByAccount: 'getreceivedbyaccount',
getReceivedByAddress: 'getreceivedbyaddress',
getTransaction: 'gettransaction',
getWork: 'getwork',
help: 'help',
keypoolRefill: 'keypoolrefill',
listAccounts: 'listaccounts',
listReceivedByAccount: 'listreceivedbyaccount',
listReceivedByAddress: 'listreceivedbyaddress',
listTransactions: 'listtransactions',
move: 'move',
sendFrom: 'sendfrom',
sendMany: 'sendmany',
sendToAddress: 'sendtoaddress',
setAccount: 'setaccount',
setGenerate: 'setgenerate',
setTxFee: 'settxfee',
stop: 'stop',
validateAddress: 'validateaddress',
walletLock: 'walletlock',
walletPassphrase: 'walletpassphrase',
walletPassphraseChange: 'walletpassphrasechange',
};
//===----------------------------------------------------------------------===//
// Client
// Either pass in 4 arguments, or a single object with host, port, user, pass
//===----------------------------------------------------------------------===//
function Client() {
var args = [].slice.call(arguments);
this.host = args[0].host || args[0];
this.port = args[0].port || args[1];
this.user = args[0].user || args[2];
this.pass = args[0].pass || args[3];
this.rpc = new rpc.Client(this.port, this.host, this.user, this.pass);
}
//===----------------------------------------------------------------------===//
// cmd
// Call custom jsonrpc commands
//===----------------------------------------------------------------------===//
Client.prototype.cmd = function() {
var args = [].slice.call(arguments);
var cmd = args.shift();
callRpc(cmd, args, this.rpc);
}
//===----------------------------------------------------------------------===//
// callRpc
//===----------------------------------------------------------------------===//
function callRpc(cmd, args, rpc) {
var fn = args[args.length-1];
// If the last function is a callback, pop it from the args list
if(_.isFunction(fn)) {
args.pop();
} else {
fn = function () {};
}
rpc.call(cmd, args, function(){
var args = [].slice.call(arguments);
args.unshift(null);
fn.apply(this, args);
}, function(err){
fn(err);
});
}
//===----------------------------------------------------------------------===//
// Initialize wrappers
//===----------------------------------------------------------------------===//
(function() {
_.each(bitcoinAPI, function(jsonFn, protoFn) {
Client.prototype[protoFn] = function() {
var args = [].slice.call(arguments);
callRpc(jsonFn, args, this.rpc);
}
});
})();
// Export!
module.exports = Client;

2
lib/bitcoin/index.js Normal file
View File

@ -0,0 +1,2 @@
module.exports.Client = require('./client');

View File

@ -1,92 +0,0 @@
module.exports = {
addMultiSigAddress: 'addmultisigaddress',
addNode: 'addnode', // bitcoind v0.8.0+
backupWallet: 'backupwallet',
createMultiSig: 'createmultisig',
createRawTransaction: 'createrawtransaction', // bitcoind v0.7.0+
decodeRawTransaction: 'decoderawtransaction', // bitcoind v0.7.0+
decodeScript: 'decodescript',
dumpPrivKey: 'dumpprivkey',
dumpWallet: 'dumpwallet', // bitcoind v0.9.0+
encryptWallet: 'encryptwallet',
estimateFee: 'estimatefee', // bitcoind v0.10.0x
estimatePriority: 'estimatepriority', // bitcoind v0.10.0+
generate: 'generate', // bitcoind v0.11.0+
getAccount: 'getaccount',
getAccountAddress: 'getaccountaddress',
getAddedNodeInfo: 'getaddednodeinfo', // bitcoind v0.8.0+
getAddressesByAccount: 'getaddressesbyaccount',
getBalance: 'getbalance',
getBestBlockHash: 'getbestblockhash', // bitcoind v0.9.0+
getBlock: 'getblock',
getBlockStats: 'getblockstats',
getBlockFilter: 'getblockfilter',
getBlockchainInfo: 'getblockchaininfo', // bitcoind v0.9.2+
getBlockCount: 'getblockcount',
getBlockHash: 'getblockhash',
getBlockHeader: 'getblockheader',
getBlockTemplate: 'getblocktemplate', // bitcoind v0.7.0+
getChainTips: 'getchaintips', // bitcoind v0.10.0+
getChainTxStats: 'getchaintxstats',
getConnectionCount: 'getconnectioncount',
getDifficulty: 'getdifficulty',
getGenerate: 'getgenerate',
getInfo: 'getinfo',
getMempoolAncestors: 'getmempoolancestors',
getMempoolDescendants: 'getmempooldescendants',
getMempoolEntry: 'getmempoolentry',
getMempoolInfo: 'getmempoolinfo', // bitcoind v0.10+
getMiningInfo: 'getmininginfo',
getNetTotals: 'getnettotals',
getNetworkInfo: 'getnetworkinfo', // bitcoind v0.9.2+
getNetworkHashPs: 'getnetworkhashps', // bitcoind v0.9.0+
getNewAddress: 'getnewaddress',
getPeerInfo: 'getpeerinfo', // bitcoind v0.7.0+
getRawChangeAddress: 'getrawchangeaddress', // bitcoin v0.9+
getRawMemPool: 'getrawmempool', // bitcoind v0.7.0+
getRawTransaction: 'getrawtransaction', // bitcoind v0.7.0+
getReceivedByAccount: 'getreceivedbyaccount',
getReceivedByAddress: 'getreceivedbyaddress',
getTransaction: 'gettransaction',
getTxOut: 'gettxout', // bitcoind v0.7.0+
getTxOutProof: 'gettxoutproof', // bitcoind v0.11.0+
getTxOutSetInfo: 'gettxoutsetinfo', // bitcoind v0.7.0+
getUnconfirmedBalance: 'getunconfirmedbalance', // bitcoind v0.9.0+
getWalletInfo: 'getwalletinfo', // bitcoind v0.9.2+
help: 'help',
importAddress: 'importaddress', // bitcoind v0.10.0+
importPrivKey: 'importprivkey',
importWallet: 'importwallet', // bitcoind v0.9.0+
keypoolRefill: 'keypoolrefill',
keyPoolRefill: 'keypoolrefill',
listAccounts: 'listaccounts',
listAddressGroupings: 'listaddressgroupings', // bitcoind v0.7.0+
listLockUnspent: 'listlockunspent', // bitcoind v0.8.0+
listReceivedByAccount: 'listreceivedbyaccount',
listReceivedByAddress: 'listreceivedbyaddress',
listSinceBlock: 'listsinceblock',
listTransactions: 'listtransactions',
listUnspent: 'listunspent', // bitcoind v0.7.0+
lockUnspent: 'lockunspent', // bitcoind v0.8.0+
move: 'move',
ping: 'ping', // bitcoind v0.9.0+
prioritiseTransaction: 'prioritisetransaction', // bitcoind v0.10.0+
sendFrom: 'sendfrom',
sendMany: 'sendmany',
sendRawTransaction: 'sendrawtransaction', // bitcoind v0.7.0+
sendToAddress: 'sendtoaddress',
setAccount: 'setaccount',
setGenerate: 'setgenerate',
setTxFee: 'settxfee',
signMessage: 'signmessage',
signRawTransaction: 'signrawtransaction', // bitcoind v0.7.0+
stop: 'stop',
submitBlock: 'submitblock', // bitcoind v0.7.0+
validateAddress: 'validateaddress',
verifyChain: 'verifychain', // bitcoind v0.9.0+
verifyMessage: 'verifymessage',
verifyTxOutProof: 'verifytxoutproof', // bitcoind v0.11.0+
walletLock: 'walletlock',
walletPassphrase: 'walletpassphrase',
walletPassphraseChange: 'walletpassphrasechange'
}

View File

@ -1,58 +0,0 @@
var commands = require('./commands')
var rpc = require('./jsonrpc')
// ===----------------------------------------------------------------------===//
// Client
// ===----------------------------------------------------------------------===//
function Client (opts) {
this.rpc = new rpc.Client(opts)
}
// ===----------------------------------------------------------------------===//
// cmd
// ===----------------------------------------------------------------------===//
Client.prototype.cmd = function () {
var args = [].slice.call(arguments)
var cmd = args.shift()
callRpc(cmd, args, this.rpc)
}
// ===----------------------------------------------------------------------===//
// callRpc
// ===----------------------------------------------------------------------===//
function callRpc (cmd, args, rpc) {
var fn = args[args.length - 1]
// If the last argument is a callback, pop it from the args list
if (typeof fn === 'function') {
args.pop()
} else {
fn = function () {}
}
return rpc.call(cmd, args, function () {
var args = [].slice.call(arguments)
args.unshift(null)
fn.apply(this, args)
}, function (err) {
fn(err)
})
}
// ===----------------------------------------------------------------------===//
// Initialize wrappers
// ===----------------------------------------------------------------------===//
(function () {
for (var protoFn in commands) {
(function (protoFn) {
Client.prototype[protoFn] = function () {
var args = [].slice.call(arguments)
return callRpc(commands[protoFn], args, this.rpc)
}
})(protoFn)
}
})()
// Export!
module.exports.Client = Client

View File

@ -1,155 +1,247 @@
var http = require('http')
var https = require('https')
var sys = require('sys');
var http = require('http');
var METHOD_NOT_ALLOWED = "Method Not Allowed\n";
var INVALID_REQUEST = "Invalid Request\n";
var Client = function (opts) {
this.opts = opts || {}
this.http = this.opts.ssl ? https : http
}
Client.prototype.call = function (method, params) {
return new Promise((resolve, reject) => {
var time = Date.now()
var requestJSON
//===----------------------------------------------------------------------===//
// Server Client
//===----------------------------------------------------------------------===//
var Client = function(port, host, user, password) {
this.port = port;
this.host = host;
this.user = user;
this.password = password;
if (Array.isArray(method)) {
// multiple rpc batch call
requestJSON = []
method.forEach(function (batchCall, i) {
requestJSON.push({
id: time + '-' + i,
method: batchCall.method,
params: batchCall.params
})
})
} else {
// single rpc call
requestJSON = {
id: time,
method: method,
params: params
}
}
this.call = function(method, params, callback, errback, path) {
var client = http.createClient(port, host);
// First we encode the request into JSON
requestJSON = JSON.stringify(requestJSON)
var requestJSON = JSON.stringify({
'id': '' + (new Date()).getTime(),
'method': method,
'params': params
});
// prepare request options
var requestOptions = {
host: this.opts.host || 'localhost',
port: this.opts.port || 8332,
method: 'POST',
path: '/',
headers: {
'Host': this.opts.host || 'localhost',
'Content-Length': requestJSON.length
},
agent: false,
rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false
var headers = {};
if (user && password) {
var buff = new Buffer(this.user + ":" + this.password)
.toString('base64');
var auth = 'Basic ' + buff;
headers['Authorization'] = auth;
}
if (this.opts.ssl && this.opts.sslCa) {
requestOptions.ca = this.opts.sslCa
}
// use HTTP auth if user and password set
if (this.opts.user && this.opts.pass) {
requestOptions.auth = this.opts.user + ':' + this.opts.pass
}
// Then we build some basic headers.
headers['Host'] = host;
headers['Content-Length'] = requestJSON.length;
// Now we'll make a request to the server
var cbCalled = false
var request = this.http.request(requestOptions)
// start request timeout timer
var reqTimeout = setTimeout(function () {
if (cbCalled) return
cbCalled = true
request.abort()
var err = new Error('ETIMEDOUT')
err.code = 'ETIMEDOUT'
reject(err)
}, this.opts.timeout || 30000)
var request = client.request('POST', path || '/', headers);
request.write(requestJSON);
// set additional timeout on socket in case of remote freeze after sending headers
request.setTimeout(this.opts.timeout || 30000, function () {
if (cbCalled) return
cbCalled = true
request.abort()
var err = new Error('ESOCKETTIMEDOUT')
err.code = 'ESOCKETTIMEDOUT'
reject(err)
})
client.on('error', function(e){
errback(e);
});
request.on('error', function (err) {
if (cbCalled) return
cbCalled = true
clearTimeout(reqTimeout)
reject(err)
})
request.on('response', function (response) {
clearTimeout(reqTimeout)
request.on('error', function(e){
errback(e);
});
request.on('response', function(response) {
// We need to buffer the response chunks in a nonblocking way.
var buffer = ''
response.on('data', function (chunk) {
buffer = buffer + chunk
})
var buffer = '';
response.on('data', function(chunk) {
buffer = buffer + chunk;
});
// When all the responses are finished, we decode the JSON and
// depending on whether it's got a result or an error, we call
// emitSuccess or emitError on the promise.
response.on('end', function () {
var err
if (cbCalled) return
cbCalled = true
try {
var decoded = JSON.parse(buffer)
} catch (e) {
if (response.statusCode !== 200) {
err = new Error('Invalid params, response status code: ' + response.statusCode)
err.code = -32602
reject(err)
} else {
err = new Error('Problem parsing JSON response from server')
err.code = -32603
reject(err)
}
return
response.on('end', function() {
var decoded = JSON.parse(buffer);
if(decoded.hasOwnProperty('error') && decoded.error != null) {
if (errback)
errback(decoded.error)
} else if(decoded.hasOwnProperty('result')) {
if (callback)
callback(decoded.result);
}
if (!Array.isArray(decoded)) {
decoded = [decoded]
else {
if (errback)
errback(decoded.error);
}
});
});
};
}
// iterate over each response, normally there will be just one
// unless a batch rpc call response is being processed
decoded.forEach(function (decodedResponse, i) {
if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) {
if (reject) {
err = new Error(decodedResponse.error.message || '')
if (decodedResponse.error.code) {
err.code = decodedResponse.error.code
}
reject(err)
}
} else if (decodedResponse.hasOwnProperty('result')) {
resolve(decodedResponse.result, response.headers)
} else {
if (reject) {
err = new Error(decodedResponse.error.message || '')
if (decodedResponse.error.code) {
err.code = decodedResponse.error.code
}
reject(err)
}
}
})
})
})
request.end(requestJSON);
//===----------------------------------------------------------------------===//
// Server
//===----------------------------------------------------------------------===//
function Server() {
var self = this;
this.functions = {};
this.server = http.createServer(function(req, res) {
Server.trace('<--', 'accepted request');
if(req.method === 'POST') {
self.handlePOST(req, res);
}
else {
Server.handleNonPOST(req, res);
}
});
}
module.exports.Client = Client
//===----------------------------------------------------------------------===//
// exposeModule
//===----------------------------------------------------------------------===//
Server.prototype.exposeModule = function(mod, object) {
var funcs = [];
for(var funcName in object) {
var funcObj = object[funcName];
if(typeof(funcObj) == 'function') {
this.functions[mod + '.' + funcName] = funcObj;
funcs.push(funcName);
}
}
Server.trace('***', 'exposing module: ' + mod + ' [funs: ' + funcs.join(', ')
+ ']');
return object;
}
//===----------------------------------------------------------------------===//
// expose
//===----------------------------------------------------------------------===//
Server.prototype.expose = function(name, func) {
Server.trace('***', 'exposing: ' + name);
this.functions[name] = func;
}
//===----------------------------------------------------------------------===//
// trace
//===----------------------------------------------------------------------===//
Server.trace = function(direction, message) {
sys.puts(' ' + direction + ' ' + message);
}
//===----------------------------------------------------------------------===//
// listen
//===----------------------------------------------------------------------===//
Server.prototype.listen = function(port, host) {
this.server.listen(port, host);
Server.trace('***', 'Server listening on http://' + (host || '127.0.0.1') +
':' + port + '/');
}
//===----------------------------------------------------------------------===//
// handleInvalidRequest
//===----------------------------------------------------------------------===//
Server.handleInvalidRequest = function(req, res) {
res.writeHead(400, {'Content-Type': 'text/plain',
'Content-Length': INVALID_REQUEST.length});
res.write(INVALID_REQUEST);
res.end();
}
//===----------------------------------------------------------------------===//
// handlePOST
//===----------------------------------------------------------------------===//
Server.prototype.handlePOST = function(req, res) {
var buffer = '';
var self = this;
var handle = function (buf) {
var decoded = JSON.parse(buf);
console.log(decoded);
// Check for the required fields, and if they aren't there, then
// dispatch to the handleInvalidRequest function.
if(!(decoded.method && decoded.params)) {
return Server.handleInvalidRequest(req, res);
}
if(!self.functions.hasOwnProperty(decoded.method)) {
return Server.handleInvalidRequest(req, res);
}
// Build our success handler
var onSuccess = function(funcResp) {
Server.trace('-->', 'response (id ' + decoded.id + '): ' +
JSON.stringify(funcResp));
var encoded = JSON.stringify({
'result': funcResp,
'error': null,
'id': decoded.id
});
res.writeHead(200, {'Content-Type': 'application/json',
'Content-Length': encoded.length});
res.write(encoded);
res.end();
};
// Build our failure handler (note that error must not be null)
var onFailure = function(failure) {
Server.trace('-->', 'failure: ' + JSON.stringify(failure));
var encoded = JSON.stringify({
'result': null,
'error': failure || 'Unspecified Failure',
'id': decoded.id
});
res.writeHead(200, {'Content-Type': 'application/json',
'Content-Length': encoded.length});
res.write(encoded);
res.end();
};
Server.trace('<--', 'request (id ' + decoded.id + '): ' +
decoded.method + '(' + decoded.params.join(', ') + ')');
// Try to call the method, but intercept errors and call our
// onFailure handler.
var method = self.functions[decoded.method];
var args = decoded.params.unshift(function(resp) {
onSuccess(resp);
});
try {
method.apply(null, decoded.params);
}
catch(err) {
return onFailure(err);
}
} // function handle(buf)
req.addListener('data', function(chunk) {
buffer = buffer + chunk;
});
req.addListener('end', function() {
handle(buffer);
});
}
//===----------------------------------------------------------------------===//
// handleNonPOST
//===----------------------------------------------------------------------===//
Server.handleNonPOST = function(req, res) {
res.writeHead(405, {'Content-Type': 'text/plain',
'Content-Length': METHOD_NOT_ALLOWED.length,
'Allow': 'POST'});
res.write(METHOD_NOT_ALLOWED);
res.end();
}
module.exports.Server = Server;
module.exports.Client = Client;

View File

@ -1,40 +1,27 @@
{
"name": "@mempool/bitcoin",
"name": "bitcoin",
"description": "Communicate with bitcoind via JSON-RPC",
"version": "3.0.3",
"main": "lib/index.js",
"version": "1.1.2",
"main": "./lib/bitcoin",
"keywords": [
"bitcoin",
"rpc"
],
"author": "Bill Casarin <bill@casarin.ca> (jb55.com)",
"contributors": [
"Sean Lavine <sean@eternalrise.com>"
],
"dependencies": {},
"devDependencies": {
"clone": "^1.0.2",
"mocha": "^2.3.3",
"standard": "^5.3.1"
"dependencies": {
"underscore": ">= 1.0.3"
},
"optionalDependencies": {},
"repository": {
"type": "git",
"url": "git://github.com/mempool/node-bitcoin.git"
"url": "git://github.com/jb55/node-bitcoin.git"
},
"devDependencies": {
"vows": "*"
},
"engines": {
"node": ">= 0.10.0"
"node": "*"
},
"scripts": {
"pretest": "standard --verbose",
"test": "make test"
},
"bugs": {
"url": "https://github.com/mempool/node-bitcoin/issues"
},
"homepage": "https://github.com/mempool/node-bitcoin#readme",
"directories": {
"test": "test"
},
"license": "Unlicense"
}
"test": "node test/api.js"
}
}

14
test.js Normal file
View File

@ -0,0 +1,14 @@
var bitcoin = require('./lib/bitcoin');
var client = new bitcoin.Client('localhost', 8332, 'jb55', 'thisisthepassword');
function doCmd(cmd) {
client[cmd](function(err, data) {
console.log(cmd);
console.log(data);
console.log("err: ", err);
console.log('');
});
}
doCmd('getWork');

130
test/api.js Normal file
View File

@ -0,0 +1,130 @@
var path = require('path');
require.paths.unshift(path.join(__dirname, '..'));
// test variables
var test = {
account: "test"
};
var config = {
host: 'localhost',
port: 8332,
user: 'jb55',
pass: 'thisisthepassword'
};
// end test variables
var vows = require('vows'),
assert = require('assert');
var bitcoin = require('lib/bitcoin');
function makeClient() {
return new bitcoin.Client(config.host, config.port, config.user, config.pass);
}
function notEmpty(data) {
if (data === 0)
return;
assert.ok(data);
}
vows.describe('api').addBatch({
'': {
topic: makeClient,
'an account address': {
topic: function(client){
client.getAccountAddress(test.account, this.callback);
},
'is valid': function(address){
assert.ok(address);
},
'after getting the account name again': {
topic: function(address, client) {
client.getAccount(address, this.callback);
},
'should be the same as the original': function(account) {
assert.equal(account, test.account);
}
},
},
'listTransactions with specific amount': {
topic: function(client){
client.listTransactions(test.account, 15, this.callback);
},
'should not be empty': function(txs){ assert.ok(txs); },
'is an array': function(txs) { assert.isTrue(txs instanceof Array); }
},
'listTransactions without specific amount': {
topic: function(client){
client.listTransactions(test.account, this.callback);
},
'should not be empty': function(txs){ assert.ok(txs); },
'is an array': function(txs) { assert.isTrue(txs instanceof Array); }
},
'account addresses': {
topic: function(client){
client.getAddressesByAccount(test.account, this.callback);
},
'is not empty': function(addresses) {
assert.isTrue(addresses && addresses.length > 0);
}
},
'getDifficulty': {
topic: function(client) { client.getDifficulty(this.callback); },
'should not be empty': notEmpty,
'is a number': function (data) {
assert.isNumber(data);
},
'is greater than 0': function (data) { assert.isTrue(data > 0); }
},
'getInfo': {
topic: function(client) { client.getInfo(this.callback); },
'should not be empty': notEmpty,
'info.errors should be empty': function (info) {
assert.isEmpty(info.errors);
},
},
'getHashesPerSec': {
topic: function(client) { client.getHashesPerSec(this.callback); },
'should not be empty': notEmpty,
'is a number': function (data) { assert.isNumber(data) },
},
'help': {
topic: function(client) { client.help(this.callback); },
'should not be empty': notEmpty,
},
'getWork': {
topic: function(client) { client.getWork(this.callback); },
'should not be empty': notEmpty,
},
'getTransaction': {
topic: "TODO: get valid transaction",
'should not be empty': notEmpty,
},
'client creation with single object': {
topic: function(client){
var client2 = new bitcoin.Client(config);
var self = this;
client2.getWork(function(err, work) {
self.callback(err, work, client2, client);
});
},
'should have same params': function(err, work, client2, client) {
assert.isNull(err);
assert.equal(client2.host, client.host);
assert.equal(client2.port, client.port);
assert.equal(client2.user, client.user);
assert.equal(client2.pass, client.pass);
},
'getWork should be an object': function(work) {
assert.isObject(work);
}
},
},
}).export(module);

View File

@ -1,6 +0,0 @@
module.exports = {
host: 'localhost',
port: 19001,
user: 'admin1',
pass: '123'
};

View File

@ -1,292 +0,0 @@
/* global describe, it */
var assert = require('assert')
var clone = require('clone')
var http = require('http')
var bitcoin = require('../')
var config = require('./config')
var test = {
account: 'test'
}
var makeClient = function makeClient () {
return new bitcoin.Client(config)
}
var notEmpty = function notEmpty (data) {
if (data === 0) return
assert.ok(data)
}
var makeServer = function (port) {
var server = http.createServer()
server.listen(port)
return server
}
describe('Client', function () {
describe('getAccountAddress()', function () {
it('should be able to get an account address', function (done) {
var client = makeClient()
client.getAccountAddress(test.account, function (err, address) {
assert.ifError(err)
assert.ok(address)
client.getAccount(address, function (err, account) {
assert.ifError(err)
assert.equal(account, test.account)
done()
})
})
})
})
describe('listTransactions()', function () {
it('should be able to listTransactions with specific count', function (done) {
var client = makeClient()
client.listTransactions(test.account, 15, function (err, txs) {
assert.ifError(err)
assert.ok(txs)
assert.ok(Array.isArray(txs))
done()
})
})
it('should be able to listTransactions without specific count', function (done) {
var client = makeClient()
client.listTransactions(test.account, function (err, txs) {
assert.ifError(err)
assert.ok(txs)
assert.ok(Array.isArray(txs))
done()
})
})
})
describe('getNewAddress()', function () {
it('should be able to get new address', function (done) {
var client = makeClient()
client.getNewAddress(test.account, function (err, address) {
assert.ifError(err)
client.getAddressesByAccount(test.account, function (err, addresses) {
assert.ifError(err)
assert.ok(addresses && addresses.length > 0)
done()
})
})
})
})
describe('getBalance()', function () {
it('should return balance without any args', function (done) {
var client = makeClient()
client.getBalance(function (err, balance) {
assert.ifError(err)
assert.ok(typeof balance === 'number')
done()
})
})
})
describe('getDifficulty()', function () {
it('should get difficulty', function (done) {
var client = makeClient()
client.getDifficulty(function (err, difficulty) {
assert.ifError(err)
assert.ok(typeof difficulty === 'number')
done()
})
})
})
describe('getInfo()', function () {
it('should get info', function (done) {
var client = makeClient()
client.getInfo(function (err, info, headers) {
assert.ifError(err)
notEmpty(info)
assert.ok(info.errors === '')
done()
})
})
})
describe('help()', function () {
it('should return help', function (done) {
var client = makeClient()
client.help(function (err, help) {
assert.ifError(err)
notEmpty(help)
done()
})
})
})
it('bitcoin related error should be an Error object', function (done) {
var client = makeClient()
client.cmd('nomethod', function (err, expectedValue) {
assert.ok(err instanceof Error)
assert.equal(err.message, 'Method not found')
assert.equal(err.code, -32601)
assert.equal(expectedValue, undefined)
done()
})
})
it('running batch of rpc calls', function (done) {
this.timeout(5000)
var batch = []
for (var i = 0; i < 10; ++i) {
batch.push({
method: 'getnewaddress',
params: [test.account]
})
}
var client = makeClient()
var batchCallbackCount = 0
client.cmd(batch, function (err, address) {
assert.ifError(err)
assert.ok(++batchCallbackCount <= 10)
assert.ok(address)
if (batchCallbackCount === 10) done()
})
})
describe('invalid credentials', function () {
var badCredentials = clone(config)
badCredentials.user = 'baduser'
badCredentials.pass = 'badpwd'
var client = new bitcoin.Client(badCredentials)
it('should still return client object', function (done) {
assert.ok(client instanceof bitcoin.Client)
done()
})
it('should return status 401 with html', function (done) {
client.getDifficulty(function (err, difficulty) {
assert.ok(err instanceof Error)
assert.equal(err.message, 'Invalid params, response status code: 401')
assert.equal(err.code, -32602)
assert.equal(difficulty, undefined)
done()
})
})
})
describe('creating client on non-listening port', function () {
var badPort = clone(config)
badPort.port = 9897
badPort.user = 'baduser'
badPort.pass = 'badpwd'
var client = new bitcoin.Client(badPort)
it('will return client object', function (done) {
assert.ok(client instanceof bitcoin.Client)
done()
})
it('should not call callback more than once', function (done) {
client.listSinceBlock(function (err, result) {
assert.ok(err instanceof Error)
done()
})
})
})
describe('request timeouts', function () {
it('should occur by default after 30000ms', function (done) {
this.timeout(31000)
var server = makeServer(19998)
var request // eslint-disable-line no-unused-vars
var response
server.on('request', function (req, res) {
request = req
response = res
})
var client = new bitcoin.Client({
host: 'localhost',
port: 19998,
user: 'admin1',
pass: '123'
})
var start = Date.now()
client.getInfo(function (err, info) {
var delta = Date.now() - start
assert.ok(err instanceof Error)
assert.ok(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT')
assert.ok(delta >= 30000, 'delta should be >= 30000: ' + delta)
response.end()
server.close()
done()
})
})
it('should be customizable', function (done) {
this.timeout(4500)
var server = makeServer(19999)
var request // eslint-disable-line no-unused-vars
var response
server.on('request', function (req, res) {
request = req
response = res
})
var client = new bitcoin.Client({
host: 'localhost',
port: 19999,
user: 'admin1',
pass: '123',
timeout: 2500
})
var start = Date.now()
client.getInfo(function (err, info) {
var delta = Date.now() - start
assert.ok(err instanceof Error)
assert.ok(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT')
assert.ok(delta >= 2500, 'delta should be >= 2500:' + delta)
response.end()
server.close()
done()
})
})
})
describe('response headers', function () {
var assertResHeaders = function (resHeaders) {
assert.ok(resHeaders)
assert.ok(resHeaders.server)
assert.ok(/bitcoin/.test(resHeaders.server))
}
it('should be returned for no parameter calls', function (done) {
var client = makeClient()
client.getInfo(function (err, info, resHeaders) {
assert.ifError(err)
notEmpty(info)
assert.ok(info.errors === '')
assertResHeaders(resHeaders)
done()
})
})
it('should be returned for 1-parameter call', function (done) {
var client = makeClient()
client.getNewAddress(test.account, function (err, address, resHeaders) {
assert.ifError(err)
assert.ok(address)
assertResHeaders(resHeaders)
done()
})
it('should be returned for 2-parameter call', function (done) {
var client = makeClient()
client.listTransactions(test.account, 15, function (err, txs, resHeaders) {
assert.ifError(err)
assert.ok(txs)
assert.ok(Array.isArray(txs))
assertResHeaders(resHeaders)
done()
})
})
})
})
})

View File

@ -1,75 +0,0 @@
/* global describe, it */
var assert = require('assert')
var bitcoin = require('../')
var config = require('./config')
var commands = require('../lib/commands')
var getHelpCommands = function (client, cb) {
var commandRegex = /^([a-z]+)/
client.cmd('help', function (err, commandList) {
if (err) return cb(err)
var helpCommands = []
// split up the command list by newlines
var commandListLines = commandList.split('\n')
var result
for (var i in commandListLines) {
result = commandRegex.exec(commandListLines[i])
if (!result) continue
helpCommands.push(result[1])
}
cb(null, helpCommands)
})
}
describe('Client Commands', function () {
it('should have all the commands listed by `help`', function (done) {
var client = new bitcoin.Client(config)
getHelpCommands(client, function (err, helpCommands) {
assert.ifError(err)
for (var i in helpCommands) {
var found = false
for (var j in commands) {
if (commands[j] === helpCommands[i]) {
found = true
break
}
}
assert.ok(found, 'missing command found in `help`: ' + helpCommands[i])
}
done()
})
})
it('should not have any commands not listed by `help`', function (done) {
var client = new bitcoin.Client(config)
getHelpCommands(client, function (err, helpCommands) {
assert.ifError(err)
for (var i in commands) {
var found = false
for (var j in helpCommands) {
if (commands[i] === helpCommands[j]) {
found = true
break
}
}
// ignore commands not found in help because they are hidden
// if the wallet isn't encrypted
var ignore = ['walletlock', 'walletpassphrase', 'walletpassphrasechange']
if (~ignore.indexOf(commands[i])) {
assert.ok(!found, 'command found in `help`: ' + commands[i])
} else {
assert.ok(found, 'command not found in `help`: ' + commands[i])
}
}
done()
})
})
})

View File

@ -1 +0,0 @@
--reporter spec

View File

@ -1,63 +0,0 @@
/* global describe, it */
var assert = require('assert')
var fs = require('fs')
var clone = require('clone')
var bitcoin = require('../')
var config = require('./config')
var getInfo = function (opts, cb) {
var client = new bitcoin.Client(opts)
client.getInfo(cb)
}
describe('Client SSL', function () {
it('use sslStrict by default', function (done) {
var opts = clone(config)
opts.ssl = true
getInfo(opts, function (err, info) {
assert.ok(err instanceof Error)
// node v0.11 adds `code` param to this error
// and uses a user-friendly `message`
// continue using err.message for v0.8 and v0.10
assert.equal(err.code || err.message, 'DEPTH_ZERO_SELF_SIGNED_CERT')
done()
})
})
it('strictSSL should fail with self-signed certificate', function (done) {
var opts = clone(config)
opts.ssl = true
opts.sslStrict = true
getInfo(opts, function (err, info) {
assert.ok(err instanceof Error)
// node v0.11 adds `code` param to this error
// and uses a user-friendly `message`
// continue using err.message for v0.8 and v0.10
assert.equal(err.code || err.message, 'DEPTH_ZERO_SELF_SIGNED_CERT')
done()
})
})
it('self-signed certificate with sslStrict false', function (done) {
var opts = clone(config)
opts.ssl = true
opts.sslStrict = false
getInfo(opts, function (err, info) {
assert.ifError(err)
assert.ok(info)
done()
})
})
it('self-signed certificate with sslStrict and CA specified', function (done) {
var opts = clone(config)
opts.ssl = true
opts.sslCa = fs.readFileSync(__dirname + '/testnet-box/1/regtest/server.cert')
getInfo(opts, function (err, info) {
assert.ifError(err)
assert.ok(info)
done()
})
})
})

@ -1 +0,0 @@
Subproject commit 24f5214acfddfec498cca21dd96dac480e9b9013