Merge pull request #26 from pnagurny/feature/chainlib-bitcoin

Incorporate chainlib-bitcoin into bitcoind.js
This commit is contained in:
Braydon Fuller 2015-07-20 17:01:39 -04:00
commit 5c1737067b
29 changed files with 5087 additions and 311 deletions

View File

@ -1,26 +1,86 @@
# bitcoind.js
bitcoind.js
=======
[![Build Status](https://img.shields.io/travis/bitpay/bitcoind.js.svg?branch=master&style=flat-square)](https://travis-ci.org/bitpay/bitcoind.js)
[![Coverage Status](https://img.shields.io/coveralls/bitpay/bitcoind.js.svg?style=flat-square)](https://coveralls.io/r/bitpay/bitcoind.js)
A Node.js module that adds a native interface to Bitcoin Core for querying information about the Bitcoin blockchain. Bindings are linked to Bitcore Core compiled as a shared library.
## Install
```bash
git clone git@github.com:bitpay/bitcoind.js.git
cd bitcoind.js
npm install
```
## Example Usage
``` js
var bitcoind = require('bitcoind.js')({
```js
var BitcoinNode = require('bitcoind.js');
var configuration = {
directory: '~/.bitcoin',
testnet: true
};
var node = new BitcoinNode(configuration);
node.chain.on('addblock', function(block) {
console.log('New Best Tip:', block.hash);
});
bitcoind.on('ready', function() {
```
bitcoind.getBlock(blockHash, function(err, block) {
// block is a node buffer
}
## API Documentation
bitcoind.close(function(err, result) {
// bitcoind is stopped
});
Get Unspent Outputs
```js
var address = '15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2';
var includeMempool = true;
node.getUnspentOutputs(address, includeMempool, function(err, unspentOutputs) {
//...
});
```
View Balances
```js
var address = '15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2';
var includeMempool = true;
node.getBalance(address, includeMempool, function(err, balance) {
//...
});
```
Get Outputs
```js
var address = '15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2';
var includeMempool = true;
node.getOutputs(address, includeMempool, function(err, outputs) {
//...
});
```
Get Transaction
```js
var txid = 'c349b124b820fe6e32136c30e99f6c4f115fce4d750838edf0c46d3cb4d7281e';
var includeMempool = true;
node.getTransaction(txid, includeMempool, function(err, transaction) {
//...
});
```
Get Block
```js
var blockHash = '00000000d17332a156a807b25bc5a2e041d2c730628ceb77e75841056082a2c2';
node.getBlock(blockHash, function(err, block) {
//...
});
```
You can log output from the daemon using:
@ -31,14 +91,14 @@ $ tail -f ~/.bitcoin/debug.log
^C (SIGINT) will call `StartShutdown()` in bitcoind on the node thread pool.
## Documentation
## Daemon Documentation
- `bitcoind.start([options], [callback])` - Start the JavaScript Bitcoin node.
- `bitcoind.getBlock(blockHash|blockHeight, callback)` - Get any block asynchronously by block hash or height as a node buffer.
- `bitcoind.getTransaction(txid, blockhash, callback)` - Get any tx asynchronously by reading it from disk.
- `bitcoind.log(message), bitcoind.info(message)` - Log to standard output.
- `bitcoind.error(message)` - Log to stderr.
- `bitcoind.close([callback])` - Stop the JavaScript bitcoin node safely, the callback will be called when bitcoind is closed. This will also be done automatically on `process.exit`. It also takes the bitcoind node off the libuv event loop. If the bitcoind object is the only thing on the event loop. Node will simply close.
- `daemon.start([options], [callback])` - Start the JavaScript Bitcoin node.
- `daemon.getBlock(blockHash|blockHeight, callback)` - Get any block asynchronously by block hash or height as a node buffer.
- `daemon.getTransaction(txid, blockhash, callback)` - Get any tx asynchronously by reading it from disk.
- `daemon.log(message), daemon.info(message)` - Log to standard output.
- `daemon.error(message)` - Log to stderr.
- `daemon.close([callback])` - Stop the JavaScript bitcoin node safely, the callback will be called when bitcoind is closed. This will also be done automatically on `process.exit`. It also takes the bitcoind node off the libuv event loop. If the daemon object is the only thing on the event loop. Node will simply close.
## Building
@ -147,3 +207,4 @@ Copyright 2013-2015 BitPay, Inc.
- bitcoin: Copyright (c) 2009-2015 Bitcoin Core Developers (MIT License)
- bcoin (some code borrowed temporarily): Copyright Fedor Indutny, 2014.

View File

@ -26,7 +26,7 @@ var fixtureData = {
]
};
var bitcoind = require('../')({
var bitcoind = require('../').daemon({
directory: '~/.bitcoin',
testnet: true
});

View File

@ -7,21 +7,21 @@
process.title = 'bitcoind.js';
/**
* bitcoind
* daemon
*/
var bitcoind = require('../index.js')({
directory: '~/.bitcoin'
var daemon = require('../index.js').daemon({
directory: process.env.BITCOINDJS_DIR || '~/.bitcoin'
});
bitcoind.on('error', function(err) {
bitcoind.log('error="%s"', err.message);
daemon.on('error', function(err) {
daemon.log('error="%s"', err.message);
});
bitcoind.on('ready', function(err, result) {
daemon.on('ready', function(err, result) {
console.log('Ready!');
bitcoind.getBlock('000000000000000082ccf8f1557c5d40b21edabb18d2d691cfbf87118bac7254', function(err, block) {
daemon.getBlock('000000000000000082ccf8f1557c5d40b21edabb18d2d691cfbf87118bac7254', function(err, block) {
if (err) {
console.log(err);
}
@ -30,6 +30,6 @@ bitcoind.on('ready', function(err, result) {
});
bitcoind.on('open', function(status) {
bitcoind.log('status="%s"', status);
daemon.on('open', function(status) {
daemon.log('status="%s"', status);
});

View File

@ -9,17 +9,17 @@
process.title = 'bitcoind.js';
/**
* bitcoind
* daemon
*/
var bitcoind = require('../')({
var daemon = require('../').daemon({
directory: process.env.BITCOINDJS_DIR || '~/.bitcoin'
});
bitcoind.on('error', function(err) {
bitcoind.log('error="%s"', err.message);
daemon.on('error', function(err) {
daemon.log('error="%s"', err.message);
});
bitcoind.on('open', function(status) {
bitcoind.log('status="%s"', status);
daemon.on('open', function(status) {
daemon.log('status="%s"', status);
});

View File

@ -7,17 +7,17 @@
process.title = 'bitcoind_stripped.js';
/**
* bitcoind
* daemon
*/
var bitcoind = require('../index_stripped.js')({
var daemon = require('../index_stripped.js')({
directory: '~/.libbitcoind-example'
});
bitcoind.on('error', function(err) {
bitcoind.log('error="%s"', err.message);
daemon.on('error', function(err) {
daemon.log('error="%s"', err.message);
});
bitcoind.on('open', function(status) {
bitcoind.log('status="%s"', status);
daemon.on('open', function(status) {
daemon.log('status="%s"', status);
});

55
example/node.js Normal file
View File

@ -0,0 +1,55 @@
'use strict';
var BitcoindJS = require('..');
var BitcoinNode = BitcoindJS.Node;
var chainlib = require('chainlib');
var log = chainlib.log;
//log.debug = function() {};
var privkey = 'tprv8ZgxMBicQKsPdj1QowoT9z1tY5Et38qaMjCHZVoPdPFb6narfmYkqTygEVHfUmY78k3HcaEpkyNCAQDANaXtwNe1HLFvcA7nqYj1B7wTSTo';
var configuration = {
db: {
xprivkey: privkey,
path: './bitcoind.db'
},
p2p: {
addrs: [
{
ip: {
v4: '127.0.0.1'
},
port: 8333
}
],
dnsSeed: false
},
testnet: false
};
var node = new BitcoinNode(configuration);
var startHeight;
var count = 100;
var times = Array(count);
node.on('ready', function() {
times[node.chain.tip.__height % count] = Date.now();
startHeight = node.chain.tip.__height;
});
node.on('error', function(err) {
log.error(err);
});
node.chain.on('addblock', function(block) {
console.log('New Best Tip:', block.hash);
var startTime = times[node.chain.tip.__height % count];
if(startTime) {
var timeElapsed = (Date.now() - startTime) / 1000;
console.log(Math.round(count / timeElapsed) + ' blocks per second');
}
times[node.chain.tip.__height % count] = Date.now();
});

View File

@ -1 +1,13 @@
module.exports = require('./lib/bitcoind.js');
'use strict';
module.exports = {};
module.exports.daemon = require('./lib/daemon');
module.exports.Node = require('./lib/node');
module.exports.Block = require('./lib/block');
module.exports.Chain = require('./lib/chain');
module.exports.DB = require('./lib/db');
module.exports.Transaction = require('./lib/transaction');
module.exports.errors = require('./lib/errors');
module.exports.deps = {};
module.exports.deps.chainlib = require('chainlib');

View File

@ -22,7 +22,7 @@ describe('Basic Functionality', function() {
before(function(done) {
this.timeout(30000);
bitcoind = require('../')({
bitcoind = require('../').daemon({
directory: process.env.BITCOINDJS_DIR || '~/.bitcoin',
});

106
lib/block.js Normal file
View File

@ -0,0 +1,106 @@
'use strict';
var util = require('util');
var chainlib = require('chainlib');
var BaseBlock = chainlib.Block;
var bitcore = require('bitcore');
var BufferReader = bitcore.encoding.BufferReader;
var BN = bitcore.crypto.BN;
function Block(obj) {
if (!obj) {
obj = {};
}
BaseBlock.call(this, obj);
this.bits = obj.bits;
this.nonce = obj.nonce || 0;
}
util.inherits(Block, BaseBlock);
Block.prototype.validate = function(chain, callback) {
// bitcoind does all validation
setImmediate(callback);
};
Block.fromBuffer = function(buffer) {
var br = new BufferReader(buffer);
return Block.fromBufferReader(br);
};
Block.fromBufferReader = function(br) {
var obj = {};
obj.version = br.readUInt32LE();
obj.prevHash = BufferReader(br.read(32)).readReverse().toString('hex');
var nullHash = new Buffer(Array(32)).toString('hex');
if (obj.prevHash === nullHash) {
obj.prevHash = null;
}
obj.merkleRoot = BufferReader(br.read(32)).readReverse().toString('hex');
var timestamp = br.readUInt32LE();
obj.timestamp = new Date(timestamp * 1000);
obj.bits = br.readUInt32LE();
obj.nonce = br.readUInt32LE();
obj.data = br.readAll();
return new Block(obj);
};
Block.prototype.toObject = function() {
return {
version: this.version,
prevHash: this.prevHash,
merkleRoot: this.merkleRoot,
timestamp: this.timestamp.toISOString(),
bits: this.bits,
nonce: this.nonce,
data: this.data.toString('hex')
};
};
Block.prototype.headerToBufferWriter = function(bw) {
/* jshint maxstatements: 20 */
// version
bw.writeUInt32LE(this.version);
// prevhash
if (!this.prevHash) {
bw.write(new Buffer(Array(32)));
} else {
var prevHashBuffer = new Buffer(this.prevHash, 'hex');
prevHashBuffer = BufferReader(prevHashBuffer).readReverse();
if (prevHashBuffer.length !== 32) {
throw new Error('"prevHash" is expected to be 32 bytes');
}
bw.write(prevHashBuffer);
}
// merkleroot
if (!this.merkleRoot) {
bw.write(new Buffer(Array(32)));
} else {
var merkleRoot = new Buffer(this.merkleRoot, 'hex');
merkleRoot = BufferReader(merkleRoot).readReverse();
if (merkleRoot.length !== 32) {
throw new Error('"merkleRoot" is expected to be 32 bytes');
}
bw.write(merkleRoot);
}
// timestamp
bw.writeUInt32LE(Math.floor(this.timestamp.getTime() / 1000));
// bits
bw.writeUInt32LE(this.bits);
// nonce
bw.writeUInt32LE(this.nonce);
return bw;
};
module.exports = Block;

98
lib/chain.js Normal file
View File

@ -0,0 +1,98 @@
'use strict';
var util = require('util');
var bitcore = require('bitcore');
var chainlib = require('chainlib');
var BaseChain = chainlib.Chain;
var BN = bitcore.crypto.BN;
var Block = require('./block');
Chain.DEFAULTS = {
MAX_HASHES: new BN('10000000000000000000000000000000000000000000000000000000000000000', 'hex'),
TARGET_TIMESPAN: 14 * 24 * 60 * 60 * 1000, // two weeks
TARGET_SPACING: 10 * 60 * 1000, // ten minutes
MAX_BITS: 0x1d00ffff,
MIN_BITS: 0x03000000
};
/**
* Will instantiate a new Chain instance
* @param {Object} options - The options for the chain
* @param {Number} options.minBits - The minimum number of bits
* @param {Number} options.maxBits - The maximum number of bits
* @param {BN|Number} options.targetTimespan - The number of milliseconds for difficulty retargeting
* @param {BN|Number} options.targetSpacing - The number of milliseconds between blocks
* @returns {Chain}
* @extends BaseChain
* @constructor
*/
function Chain(options) {
/* jshint maxstatements: 20 */
/* jshint maxcomplexity: 12 */
if (!(this instanceof Chain)) {
return new Chain(options);
}
if (!options) {
options = {};
}
BaseChain.call(this, options);
this.minBits = options.minBits || Chain.DEFAULTS.MIN_BITS;
this.maxBits = options.maxBits || Chain.DEFAULTS.MAX_BITS;
this.maxHashes = options.maxHashes || Chain.DEFAULTS.MAX_HASHES;
this.targetTimespan = options.targetTimespan || Chain.DEFAULTS.TARGET_TIMESPAN;
this.targetSpacing = options.targetSpacing || Chain.DEFAULTS.TARGET_SPACING;
return this;
}
util.inherits(Chain, BaseChain);
Chain.prototype._writeBlock = function(block, callback) {
// Update hashes
this.cache.hashes[block.hash] = block.prevHash;
// call db.putBlock to update prevHash index, but it won't write the block to disk
this.db.putBlock(block, callback);
};
Chain.prototype._validateBlock = function(block, callback) {
// All validation is done by bitcoind
setImmediate(callback);
};
Chain.prototype.startBuilder = function() {
// Unused in bitcoind.js
};
Chain.prototype.buildGenesisBlock = function buildGenesisBlock(options) {
if (!options) {
options = {};
}
var genesis = new Block({
prevHash: null,
height: 0,
timestamp: options.timestamp || new Date(),
nonce: options.nonce || 0,
bits: options.bits || this.maxBits
});
var data = this.db.buildGenesisData();
genesis.merkleRoot = data.merkleRoot;
genesis.data = data.buffer;
return genesis;
};
Chain.prototype.getWeight = function getWeight(blockHash, callback) {
var self = this;
setImmediate(function() {
var weight = self.db.bitcoind.getChainWork(blockHash);
if(weight === undefined) {
return callback(new Error('Weight not found for ' + blockHash));
}
callback(null, new BN(weight, 'hex'));
});
};
module.exports = Chain;

View File

@ -16,16 +16,16 @@ var tiny = require('tiny').json;
var setImmediate = global.setImmediate || process.nextTick.bind(process);
/**
* Bitcoin
* Daemon
*/
var bitcoin = Bitcoin;
var daemon = Daemon;
function Bitcoin(options) {
function Daemon(options) {
var self = this;
if (!(this instanceof Bitcoin)) {
return new Bitcoin(options);
if (!(this instanceof Daemon)) {
return new Daemon(options);
}
if (Object.keys(this.instances).length) {
@ -54,7 +54,7 @@ function Bitcoin(options) {
this.datadir = this.options.datadir;
this.config = this.datadir + '/bitcoin.conf';
this.network = Bitcoin[this.options.testnet ? 'testnet' : 'livenet'];
this.network = Daemon[this.options.testnet ? 'testnet' : 'livenet'];
if (!fs.existsSync(this.datadir)) {
mkdirp.sync(this.datadir);
@ -104,16 +104,16 @@ function Bitcoin(options) {
});
}
Bitcoin.prototype.__proto__ = EventEmitter.prototype;
Daemon.prototype.__proto__ = EventEmitter.prototype;
Bitcoin.livenet = {
Daemon.livenet = {
name: 'livenet',
peers: [
// hardcoded peers
]
};
Bitcoin.testnet = {
Daemon.testnet = {
name: 'testnet',
peers: [
// hardcoded peers
@ -121,30 +121,30 @@ Bitcoin.testnet = {
};
// Make sure signal handlers are not overwritten
Bitcoin._signalQueue = [];
Bitcoin._processOn = process.on;
Daemon._signalQueue = [];
Daemon._processOn = process.on;
process.addListener =
process.on = function(name, listener) {
if (~['SIGINT', 'SIGHUP', 'SIGQUIT'].indexOf(name.toUpperCase())) {
if (!Bitcoin.global || !Bitcoin.global._started) {
Bitcoin._signalQueue.push([name, listener]);
if (!Daemon.global || !Daemon.global._started) {
Daemon._signalQueue.push([name, listener]);
return;
}
}
return Bitcoin._processOn.apply(this, arguments);
return Daemon._processOn.apply(this, arguments);
};
Bitcoin.instances = {};
Bitcoin.prototype.instances = Bitcoin.instances;
Daemon.instances = {};
Daemon.prototype.instances = Daemon.instances;
Bitcoin.__defineGetter__('global', function() {
if (bitcoin.stopping) return [];
return Bitcoin.instances[Object.keys(Bitcoin.instances)[0]];
Daemon.__defineGetter__('global', function() {
if (daemon.stopping) return [];
return Daemon.instances[Object.keys(Daemon.instances)[0]];
});
Bitcoin.prototype.__defineGetter__('global', function() {
if (bitcoin.stopping) return [];
return Bitcoin.global;
Daemon.prototype.__defineGetter__('global', function() {
if (daemon.stopping) return [];
return Daemon.global;
});
tiny.debug = function() {};
@ -152,13 +152,13 @@ tiny.prototype.debug = function() {};
tiny.error = function() {};
tiny.prototype.error = function() {};
Bitcoin.db = tiny({
Daemon.db = tiny({
file: process.env.HOME + '/.bitcoindjs.db',
saveIndex: false,
initialCache: false
});
Bitcoin.prototype.start = function(options, callback) {
Daemon.prototype.start = function(options, callback) {
var self = this;
if (!callback) {
@ -212,8 +212,8 @@ Bitcoin.prototype.start = function(options, callback) {
});
// Finally set signal handlers
process.on = process.addListener = Bitcoin._processOn;
Bitcoin._signalQueue.forEach(function(event) {
process.on = process.addListener = Daemon._processOn;
Daemon._signalQueue.forEach(function(event) {
process.on(event[0], event[1]);
});
@ -307,36 +307,36 @@ Bitcoin.prototype.start = function(options, callback) {
}, 1000);
};
Bitcoin.prototype.getBlock = function(blockhash, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBlock = function(blockhash, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getBlock(blockhash, function(err, block) {
if (err) return callback(err);
return callback(null, block);
});
};
Bitcoin.prototype.getBlockHeight = function(height, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBlockHeight = function(height, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getBlock(+height, function(err, block) {
if (err) return callback(err);
return callback(null, bitcoin.block(block));
return callback(null, daemon.block(block));
});
};
Bitcoin.prototype.isSpent = function(txid, outputIndex) {
Daemon.prototype.isSpent = function(txid, outputIndex) {
return bitcoindjs.isSpent(txid, outputIndex);
};
Bitcoin.prototype.getChainWork = function(blockHash) {
Daemon.prototype.getChainWork = function(blockHash) {
return bitcoindjs.getChainWork(blockHash);
};
Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
Daemon.prototype.getTransaction = function(txid, queryMempool, callback) {
return bitcoindjs.getTransaction(txid, queryMempool, callback);
};
Bitcoin.prototype.getTransactionWithBlock = function(txid, blockhash, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getTransactionWithBlock = function(txid, blockhash, callback) {
if (daemon.stopping) return [];
var self = this;
var slow = true;
@ -377,57 +377,57 @@ Bitcoin.prototype.getTransactionWithBlock = function(txid, blockhash, callback)
return bitcoindjs.getBlock(tx.blockhash, function(err, block) {
if (err) return callback(err);
return callback(null, bitcoin.tx(tx), bitcoin.block(block));
return callback(null, daemon.tx(tx), daemon.block(block));
});
});
};
Bitcoin.prototype.getMempoolOutputs = function(address) {
Daemon.prototype.getMempoolOutputs = function(address) {
return bitcoindjs.getMempoolOutputs(address);
};
Bitcoin.prototype.addMempoolUncheckedTransaction = function(txBuffer) {
Daemon.prototype.addMempoolUncheckedTransaction = function(txBuffer) {
return bitcoindjs.addMempoolUncheckedTransaction(txBuffer);
};
Bitcoin.prototype.getInfo = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getInfo = function() {
if (daemon.stopping) return [];
return bitcoindjs.getInfo();
};
Bitcoin.prototype.getPeerInfo = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getPeerInfo = function() {
if (daemon.stopping) return [];
return bitcoindjs.getPeerInfo();
};
Bitcoin.prototype.getAddresses = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getAddresses = function() {
if (daemon.stopping) return [];
return bitcoindjs.getAddresses();
};
Bitcoin.prototype.getProgress = function(callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getProgress = function(callback) {
if (daemon.stopping) return [];
return bitcoindjs.getProgress(callback);
};
Bitcoin.prototype.setGenerate = function(options) {
if (bitcoin.stopping) return [];
Daemon.prototype.setGenerate = function(options) {
if (daemon.stopping) return [];
return bitcoindjs.setGenerate(options || {});
};
Bitcoin.prototype.getGenerate = function(options) {
if (bitcoin.stopping) return [];
Daemon.prototype.getGenerate = function(options) {
if (daemon.stopping) return [];
return bitcoindjs.getGenerate(options || {});
};
Bitcoin.prototype.getMiningInfo = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getMiningInfo = function() {
if (daemon.stopping) return [];
return bitcoindjs.getMiningInfo();
};
Bitcoin.prototype.getAddrTransactions = function(address, callback) {
if (bitcoin.stopping) return [];
return bitcoin.db.get('addr-tx/' + address, function(err, records) {
Daemon.prototype.getAddrTransactions = function(address, callback) {
if (daemon.stopping) return [];
return daemon.db.get('addr-tx/' + address, function(err, records) {
var options = {
address: address,
blockheight: (records || []).reduce(function(out, record) {
@ -443,15 +443,15 @@ Bitcoin.prototype.getAddrTransactions = function(address, callback) {
};
return bitcoindjs.getAddrTransactions(options, function(err, addr) {
if (err) return callback(err);
addr = bitcoin.addr(addr);
addr = daemon.addr(addr);
if (addr.tx[0] && !addr.tx[0].vout[0]) {
return bitcoin.db.set('addr-tx/' + address, [{
return daemon.db.set('addr-tx/' + address, [{
txid: null,
blockhash: null,
blockheight: null,
blocktime: null
}], function() {
return callback(null, bitcoin.addr({
return callback(null, daemon.addr({
address: addr.address,
tx: []
}));
@ -469,33 +469,33 @@ Bitcoin.prototype.getAddrTransactions = function(address, callback) {
blocktime: tx.blocktime
});
});
return bitcoin.db.set('addr-tx/' + address, set, function() {
return daemon.db.set('addr-tx/' + address, set, function() {
return callback(null, addr);
});
});
});
};
Bitcoin.prototype.getBestBlock = function(callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBestBlock = function(callback) {
if (daemon.stopping) return [];
var hash = bitcoindjs.getBestBlock();
return bitcoindjs.getBlock(hash, callback);
};
Bitcoin.prototype.getChainHeight = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getChainHeight = function() {
if (daemon.stopping) return [];
return bitcoindjs.getChainHeight();
};
Bitcoin.prototype.__defineGetter__('chainHeight', function() {
if (bitcoin.stopping) return [];
Daemon.prototype.__defineGetter__('chainHeight', function() {
if (daemon.stopping) return [];
return this.getChainHeight();
});
Bitcoin.prototype.getBlockByTxid =
Bitcoin.prototype.getBlockByTx = function(txid, callback) {
if (bitcoin.stopping) return [];
return bitcoin.db.get('block-tx/' + txid, function(err, block) {
Daemon.prototype.getBlockByTxid =
Daemon.prototype.getBlockByTx = function(txid, callback) {
if (daemon.stopping) return [];
return daemon.db.get('block-tx/' + txid, function(err, block) {
if (block) {
return self.getBlock(block.hash, function(err, block) {
if (err) return callback(err);
@ -507,41 +507,41 @@ Bitcoin.prototype.getBlockByTx = function(txid, callback) {
}
return bitcoindjs.getBlockByTx(txid, function(err, block, tx_) {
if (err) return callback(err);
bitcoin.db.set('block-tx/' + txid, { hash: block.hash }, utils.NOOP);
return callback(null, bitcoin.block(block), bitcoin.tx(tx_));
daemon.db.set('block-tx/' + txid, { hash: block.hash }, utils.NOOP);
return callback(null, daemon.block(block), daemon.tx(tx_));
});
});
};
Bitcoin.prototype.getBlocksByDate =
Bitcoin.prototype.getBlocksByTime = function(options, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBlocksByDate =
Daemon.prototype.getBlocksByTime = function(options, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getBlocksByTime(options, function(err, blocks) {
if (err) return callback(err);
return callback(null, blocks.map(function(block) {
return bitcoin.block(block);
return daemon.block(block);
}));
});
};
Bitcoin.prototype.getFromTx = function(txid, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getFromTx = function(txid, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getFromTx(txid, function(err, txs) {
if (err) return callback(err);
return callback(null, txs.map(function(tx) {
return bitcoin.tx(tx)
return daemon.tx(tx)
}));
});
};
Bitcoin.prototype.getLastFileIndex = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getLastFileIndex = function() {
if (daemon.stopping) return [];
return bitcoindjs.getLastFileIndex();
};
Bitcoin.prototype.log =
Bitcoin.prototype.info = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.log =
Daemon.prototype.info = function() {
if (daemon.stopping) return [];
if (this.options.silent) return;
if (typeof arguments[0] !== 'string') {
var out = util.inspect(arguments[0], null, 20, true);
@ -551,8 +551,8 @@ Bitcoin.prototype.info = function() {
return process.stdout.write('bitcoind.js: ' + out + '\n');
};
Bitcoin.prototype.error = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.error = function() {
if (daemon.stopping) return [];
if (this.options.silent) return;
if (typeof arguments[0] !== 'string') {
var out = util.inspect(arguments[0], null, 20, true);
@ -562,9 +562,9 @@ Bitcoin.prototype.error = function() {
return process.stderr.write('bitcoind.js: ' + out + '\n');
};
Bitcoin.prototype.stop =
Bitcoin.prototype.close = function(callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.stop =
Daemon.prototype.close = function(callback) {
if (daemon.stopping) return [];
var self = this;
return bitcoindjs.stop(function(err, status) {
if (err) {
@ -577,19 +577,19 @@ Bitcoin.prototype.close = function(callback) {
});
};
Bitcoin.prototype.__defineGetter__('stopping', function() {
Daemon.prototype.__defineGetter__('stopping', function() {
return bitcoindjs.stopping() || bitcoindjs.stopped();
});
Bitcoin.prototype.__defineGetter__('stopped', function() {
Daemon.prototype.__defineGetter__('stopped', function() {
return bitcoindjs.stopped();
});
Bitcoin.__defineGetter__('stopping', function() {
Daemon.__defineGetter__('stopping', function() {
return bitcoindjs.stopping() || bitcoindjs.stopped();
});
Bitcoin.__defineGetter__('stopped', function() {
Daemon.__defineGetter__('stopped', function() {
return bitcoindjs.stopped();
});
@ -610,7 +610,7 @@ function Block(data) {
return data;
}
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var self = this;
@ -621,7 +621,7 @@ function Block(data) {
});
this.tx = this.tx.map(function(tx) {
return bitcoin.tx(tx);
return daemon.tx(tx);
});
if (!this.hex) {
@ -638,17 +638,17 @@ Object.defineProperty(Block.prototype, '_blockFlag', {
});
Block.isBlock = function(block) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return block._blockFlag === Block.prototype._blockFlag;
};
Block.fromHex = function(hex) {
if (bitcoin.stopping) return [];
return bitcoin.block(bitcoindjs.blockFromHex(hex));
if (daemon.stopping) return [];
return daemon.block(bitcoindjs.blockFromHex(hex));
};
Block.prototype.getHash = function(enc) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getBlockHex(this);
if (!this.hash || this.hash !== data.hash) {
this.hash = data.hash;
@ -660,12 +660,12 @@ Block.prototype.getHash = function(enc) {
};
Block.prototype.verify = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return this.verified = this.verified || bitcoindjs.verifyBlock(this);
};
Block.prototype.toHex = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var hex = Block.toHex(this);
if (!this.hex || this.hex !== hex) {
this.hex = hex;
@ -674,18 +674,18 @@ Block.prototype.toHex = function() {
};
Block.toHex = function(block) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getBlockHex(block);
return data.hex;
};
Block.prototype.toBinary = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return Block.toBinary(this);
};
Block.toBinary = function(block) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getBlockHex(block);
return new Buffer(data.hex, 'hex');
};
@ -707,7 +707,7 @@ function Transaction(data) {
return data;
}
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var self = this;
@ -732,34 +732,34 @@ Object.defineProperty(Transaction.prototype, '_txFlag', {
Transaction.isTransaction =
Transaction.isTx = function(tx) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return tx._txFlag === Transaction.prototype._txFlag;
};
Transaction.fromHex = function(hex) {
if (bitcoin.stopping) return [];
return bitcoin.tx(bitcoindjs.txFromHex(hex));
if (daemon.stopping) return [];
return daemon.tx(bitcoindjs.txFromHex(hex));
};
Transaction.prototype.verify = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return this.verified = this.verified || bitcoindjs.verifyTransaction(this);
};
Transaction.prototype.sign =
Transaction.prototype.fill = function(options) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return Transaction.fill(this, options);
};
Transaction.sign =
Transaction.fill = function(tx, options) {
if (bitcoin.stopping) return [];
var isTx = bitcoin.tx.isTx(tx)
if (daemon.stopping) return [];
var isTx = daemon.tx.isTx(tx)
, newTx;
if (!isTx) {
tx = bitcoin.tx(tx);
tx = daemon.tx(tx);
}
try {
@ -776,7 +776,7 @@ Transaction.fill = function(tx, options) {
};
Transaction.prototype.getHash = function(enc) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getTxHex(this);
if (!this.txid || this.txid !== data.hash) {
this.txid = data.hash;
@ -788,12 +788,12 @@ Transaction.prototype.getHash = function(enc) {
};
Transaction.prototype.isCoinbase = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return this.vin.length === 1 && this.vin[0].coinbase;
};
Transaction.prototype.toHex = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var hex = Transaction.toHex(this);
if (!this.hex || hex !== this.hex) {
this.hex = hex;
@ -802,24 +802,24 @@ Transaction.prototype.toHex = function() {
};
Transaction.toHex = function(tx) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getTxHex(tx);
return data.hex;
};
Transaction.prototype.toBinary = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return Transaction.toBinary(this);
};
Transaction.toBinary = function(tx) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getTxHex(tx);
return new Buffer(data.hex, 'hex');
};
Transaction.broadcast = function(tx, options, callback) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
if (typeof tx === 'string') {
tx = { hex: tx };
}
@ -840,25 +840,25 @@ Transaction.broadcast = function(tx, options, callback) {
callback = utils.NOOP;
}
if (!bitcoin.isTx(tx)) {
tx = bitcoin.tx(tx);
if (!daemon.isTx(tx)) {
tx = daemon.tx(tx);
}
return bitcoindjs.broadcastTx(tx, fee, own, function(err, hash, tx) {
if (err) {
if (callback === utils.NOOP) {
bitcoin.global.emit('error', err);
daemon.global.emit('error', err);
}
return callback(err);
}
tx = bitcoin.tx(tx);
bitcoin.global.emit('broadcast', tx);
tx = daemon.tx(tx);
daemon.global.emit('broadcast', tx);
return callback(null, hash, tx);
});
};
Transaction.prototype.broadcast = function(options, callback) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
if (!callback) {
callback = options;
options = null;
@ -879,7 +879,7 @@ function Addresses(data) {
return data;
}
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var self = this;
@ -900,7 +900,7 @@ Object.defineProperty(Transaction.prototype, '_addrFlag', {
Addresses.isAddresses =
Addresses.isAddr = function(addr) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return addr._txFlag === Addresses.prototype._addrFlag;
};
@ -911,7 +911,7 @@ Addresses.isAddr = function(addr) {
var utils = {};
utils.forEach = function(obj, iter, done) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var pending = obj.length;
if (!pending) return done();
var next = function() {
@ -928,11 +928,11 @@ utils.NOOP = function() {};
* Expose
*/
module.exports = exports = bitcoin;
module.exports = exports = daemon;
exports.Bitcoin = bitcoin;
exports.bitcoin = bitcoin;
exports.bitcoind = bitcoin;
exports.Daemon = daemon;
exports.daemon = daemon;
exports.bitcoind = daemon;
exports.native = bitcoindjs;
exports.bitcoindjs = bitcoindjs;

View File

@ -16,16 +16,16 @@ var tiny = require('tiny').json;
var setImmediate = global.setImmediate || process.nextTick.bind(process);
/**
* Bitcoin
* Daemon
*/
var bitcoin = Bitcoin;
var daemon = Daemon;
function Bitcoin(options) {
function Daemon(options) {
var self = this;
if (!(this instanceof Bitcoin)) {
return new Bitcoin(options);
if (!(this instanceof Daemon)) {
return new Daemon(options);
}
if (Object.keys(this.instances).length) {
@ -54,7 +54,7 @@ function Bitcoin(options) {
this.datadir = this.options.datadir;
this.config = this.datadir + '/bitcoin.conf';
this.network = Bitcoin[this.options.testnet ? 'testnet' : 'livenet'];
this.network = Daemon[this.options.testnet ? 'testnet' : 'livenet'];
if (!fs.existsSync(this.datadir)) {
mkdirp.sync(this.datadir);
@ -104,16 +104,16 @@ function Bitcoin(options) {
});
}
Bitcoin.prototype.__proto__ = EventEmitter.prototype;
Daemon.prototype.__proto__ = EventEmitter.prototype;
Bitcoin.livenet = {
Daemon.livenet = {
name: 'livenet',
peers: [
// hardcoded peers
]
};
Bitcoin.testnet = {
Daemon.testnet = {
name: 'testnet',
peers: [
// hardcoded peers
@ -121,30 +121,30 @@ Bitcoin.testnet = {
};
// Make sure signal handlers are not overwritten
Bitcoin._signalQueue = [];
Bitcoin._processOn = process.on;
Daemon._signalQueue = [];
Daemon._processOn = process.on;
process.addListener =
process.on = function(name, listener) {
if (~['SIGINT', 'SIGHUP', 'SIGQUIT'].indexOf(name.toUpperCase())) {
if (!Bitcoin.global || !Bitcoin.global._started) {
Bitcoin._signalQueue.push([name, listener]);
if (!Daemon.global || !Daemon.global._started) {
Daemon._signalQueue.push([name, listener]);
return;
}
}
return Bitcoin._processOn.apply(this, arguments);
return Daemon._processOn.apply(this, arguments);
};
Bitcoin.instances = {};
Bitcoin.prototype.instances = Bitcoin.instances;
Daemon.instances = {};
Daemon.prototype.instances = Daemon.instances;
Bitcoin.__defineGetter__('global', function() {
if (bitcoin.stopping) return [];
return Bitcoin.instances[Object.keys(Bitcoin.instances)[0]];
Daemon.__defineGetter__('global', function() {
if (daemon.stopping) return [];
return Daemon.instances[Object.keys(Daemon.instances)[0]];
});
Bitcoin.prototype.__defineGetter__('global', function() {
if (bitcoin.stopping) return [];
return Bitcoin.global;
Daemon.prototype.__defineGetter__('global', function() {
if (daemon.stopping) return [];
return Daemon.global;
});
tiny.debug = function() {};
@ -152,13 +152,13 @@ tiny.prototype.debug = function() {};
tiny.error = function() {};
tiny.prototype.error = function() {};
Bitcoin.db = tiny({
Daemon.db = tiny({
file: process.env.HOME + '/.bitcoindjs.db',
saveIndex: false,
initialCache: false
});
Bitcoin.prototype.start = function(options, callback) {
Daemon.prototype.start = function(options, callback) {
var self = this;
if (!callback) {
@ -212,8 +212,8 @@ Bitcoin.prototype.start = function(options, callback) {
});
// Finally set signal handlers
process.on = process.addListener = Bitcoin._processOn;
Bitcoin._signalQueue.forEach(function(event) {
process.on = process.addListener = Daemon._processOn;
Daemon._signalQueue.forEach(function(event) {
process.on(event[0], event[1]);
});
@ -303,25 +303,25 @@ Bitcoin.prototype.start = function(options, callback) {
}, 1000);
};
Bitcoin.prototype.getBlock = function(blockhash, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBlock = function(blockhash, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getBlock(blockhash, function(err, block) {
if (err) return callback(err);
return callback(null, bitcoin.block(block));
return callback(null, daemon.block(block));
});
};
Bitcoin.prototype.getBlockHeight = function(height, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBlockHeight = function(height, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getBlock(+height, function(err, block) {
if (err) return callback(err);
return callback(null, bitcoin.block(block));
return callback(null, daemon.block(block));
});
};
Bitcoin.prototype.getTransaction =
Bitcoin.prototype.getTx = function(txid, blockhash, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getTransaction =
Daemon.prototype.getTx = function(txid, blockhash, callback) {
if (daemon.stopping) return [];
if (typeof txid === 'object' && txid) {
var options = txid;
callback = blockhash;
@ -347,12 +347,12 @@ Bitcoin.prototype.getTx = function(txid, blockhash, callback) {
return bitcoindjs.getTransaction(txid, blockhash, function(err, tx) {
if (err) return callback(err);
return callback(null, bitcoin.tx(tx));
return callback(null, daemon.tx(tx));
});
};
Bitcoin.prototype.getTransactionWithBlock = function(txid, blockhash, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getTransactionWithBlock = function(txid, blockhash, callback) {
if (daemon.stopping) return [];
var self = this;
var slow = true;
@ -393,49 +393,49 @@ Bitcoin.prototype.getTransactionWithBlock = function(txid, blockhash, callback)
return bitcoindjs.getBlock(tx.blockhash, function(err, block) {
if (err) return callback(err);
return callback(null, bitcoin.tx(tx), bitcoin.block(block));
return callback(null, daemon.tx(tx), daemon.block(block));
});
});
};
Bitcoin.prototype.getInfo = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getInfo = function() {
if (daemon.stopping) return [];
return bitcoindjs.getInfo();
};
Bitcoin.prototype.getPeerInfo = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getPeerInfo = function() {
if (daemon.stopping) return [];
return bitcoindjs.getPeerInfo();
};
Bitcoin.prototype.getAddresses = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getAddresses = function() {
if (daemon.stopping) return [];
return bitcoindjs.getAddresses();
};
Bitcoin.prototype.getProgress = function(callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getProgress = function(callback) {
if (daemon.stopping) return [];
return bitcoindjs.getProgress(callback);
};
Bitcoin.prototype.setGenerate = function(options) {
if (bitcoin.stopping) return [];
Daemon.prototype.setGenerate = function(options) {
if (daemon.stopping) return [];
return bitcoindjs.setGenerate(options || {});
};
Bitcoin.prototype.getGenerate = function(options) {
if (bitcoin.stopping) return [];
Daemon.prototype.getGenerate = function(options) {
if (daemon.stopping) return [];
return bitcoindjs.getGenerate(options || {});
};
Bitcoin.prototype.getMiningInfo = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getMiningInfo = function() {
if (daemon.stopping) return [];
return bitcoindjs.getMiningInfo();
};
Bitcoin.prototype.getAddrTransactions = function(address, callback) {
if (bitcoin.stopping) return [];
return bitcoin.db.get('addr-tx/' + address, function(err, records) {
Daemon.prototype.getAddrTransactions = function(address, callback) {
if (daemon.stopping) return [];
return daemon.db.get('addr-tx/' + address, function(err, records) {
var options = {
address: address,
blockheight: (records || []).reduce(function(out, record) {
@ -451,15 +451,15 @@ Bitcoin.prototype.getAddrTransactions = function(address, callback) {
};
return bitcoindjs.getAddrTransactions(options, function(err, addr) {
if (err) return callback(err);
addr = bitcoin.addr(addr);
addr = daemon.addr(addr);
if (addr.tx[0] && !addr.tx[0].vout[0]) {
return bitcoin.db.set('addr-tx/' + address, [{
return daemon.db.set('addr-tx/' + address, [{
txid: null,
blockhash: null,
blockheight: null,
blocktime: null
}], function() {
return callback(null, bitcoin.addr({
return callback(null, daemon.addr({
address: addr.address,
tx: []
}));
@ -477,33 +477,33 @@ Bitcoin.prototype.getAddrTransactions = function(address, callback) {
blocktime: tx.blocktime
});
});
return bitcoin.db.set('addr-tx/' + address, set, function() {
return daemon.db.set('addr-tx/' + address, set, function() {
return callback(null, addr);
});
});
});
};
Bitcoin.prototype.getBestBlock = function(callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBestBlock = function(callback) {
if (daemon.stopping) return [];
var hash = bitcoindjs.getBestBlock();
return bitcoindjs.getBlock(hash, callback);
};
Bitcoin.prototype.getChainHeight = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getChainHeight = function() {
if (daemon.stopping) return [];
return bitcoindjs.getChainHeight();
};
Bitcoin.prototype.__defineGetter__('chainHeight', function() {
if (bitcoin.stopping) return [];
Daemon.prototype.__defineGetter__('chainHeight', function() {
if (daemon.stopping) return [];
return this.getChainHeight();
});
Bitcoin.prototype.getBlockByTxid =
Bitcoin.prototype.getBlockByTx = function(txid, callback) {
if (bitcoin.stopping) return [];
return bitcoin.db.get('block-tx/' + txid, function(err, block) {
Daemon.prototype.getBlockByTxid =
Daemon.prototype.getBlockByTx = function(txid, callback) {
if (daemon.stopping) return [];
return daemon.db.get('block-tx/' + txid, function(err, block) {
if (block) {
return self.getBlock(block.hash, function(err, block) {
if (err) return callback(err);
@ -515,41 +515,41 @@ Bitcoin.prototype.getBlockByTx = function(txid, callback) {
}
return bitcoindjs.getBlockByTx(txid, function(err, block, tx_) {
if (err) return callback(err);
bitcoin.db.set('block-tx/' + txid, { hash: block.hash }, utils.NOOP);
return callback(null, bitcoin.block(block), bitcoin.tx(tx_));
daemon.db.set('block-tx/' + txid, { hash: block.hash }, utils.NOOP);
return callback(null, daemon.block(block), daemon.tx(tx_));
});
});
};
Bitcoin.prototype.getBlocksByDate =
Bitcoin.prototype.getBlocksByTime = function(options, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getBlocksByDate =
Daemon.prototype.getBlocksByTime = function(options, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getBlocksByTime(options, function(err, blocks) {
if (err) return callback(err);
return callback(null, blocks.map(function(block) {
return bitcoin.block(block)
return daemon.block(block)
}));
});
};
Bitcoin.prototype.getFromTx = function(txid, callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.getFromTx = function(txid, callback) {
if (daemon.stopping) return [];
return bitcoindjs.getFromTx(txid, function(err, txs) {
if (err) return callback(err);
return callback(null, txs.map(function(tx) {
return bitcoin.tx(tx)
return daemon.tx(tx)
}));
});
};
Bitcoin.prototype.getLastFileIndex = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.getLastFileIndex = function() {
if (daemon.stopping) return [];
return bitcoindjs.getLastFileIndex();
};
Bitcoin.prototype.log =
Bitcoin.prototype.info = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.log =
Daemon.prototype.info = function() {
if (daemon.stopping) return [];
if (this.options.silent) return;
if (typeof arguments[0] !== 'string') {
var out = util.inspect(arguments[0], null, 20, true);
@ -559,8 +559,8 @@ Bitcoin.prototype.info = function() {
return process.stdout.write('bitcoind.js: ' + out + '\n');
};
Bitcoin.prototype.error = function() {
if (bitcoin.stopping) return [];
Daemon.prototype.error = function() {
if (daemon.stopping) return [];
if (this.options.silent) return;
if (typeof arguments[0] !== 'string') {
var out = util.inspect(arguments[0], null, 20, true);
@ -570,9 +570,9 @@ Bitcoin.prototype.error = function() {
return process.stderr.write('bitcoind.js: ' + out + '\n');
};
Bitcoin.prototype.stop =
Bitcoin.prototype.close = function(callback) {
if (bitcoin.stopping) return [];
Daemon.prototype.stop =
Daemon.prototype.close = function(callback) {
if (daemon.stopping) return [];
var self = this;
return bitcoindjs.stop(function(err, status) {
if (err) {
@ -585,19 +585,19 @@ Bitcoin.prototype.close = function(callback) {
});
};
Bitcoin.prototype.__defineGetter__('stopping', function() {
Daemon.prototype.__defineGetter__('stopping', function() {
return bitcoindjs.stopping() || bitcoindjs.stopped();
});
Bitcoin.prototype.__defineGetter__('stopped', function() {
Daemon.prototype.__defineGetter__('stopped', function() {
return bitcoindjs.stopped();
});
Bitcoin.__defineGetter__('stopping', function() {
Daemon.__defineGetter__('stopping', function() {
return bitcoindjs.stopping() || bitcoindjs.stopped();
});
Bitcoin.__defineGetter__('stopped', function() {
Daemon.__defineGetter__('stopped', function() {
return bitcoindjs.stopped();
});
@ -618,7 +618,7 @@ function Block(data) {
return data;
}
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var self = this;
@ -629,7 +629,7 @@ function Block(data) {
});
this.tx = this.tx.map(function(tx) {
return bitcoin.tx(tx);
return daemon.tx(tx);
});
if (!this.hex) {
@ -646,17 +646,17 @@ Object.defineProperty(Block.prototype, '_blockFlag', {
});
Block.isBlock = function(block) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return block._blockFlag === Block.prototype._blockFlag;
};
Block.fromHex = function(hex) {
if (bitcoin.stopping) return [];
return bitcoin.block(bitcoindjs.blockFromHex(hex));
if (daemon.stopping) return [];
return daemon.block(bitcoindjs.blockFromHex(hex));
};
Block.prototype.getHash = function(enc) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getBlockHex(this);
if (!this.hash || this.hash !== data.hash) {
this.hash = data.hash;
@ -668,12 +668,12 @@ Block.prototype.getHash = function(enc) {
};
Block.prototype.verify = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return this.verified = this.verified || bitcoindjs.verifyBlock(this);
};
Block.prototype.toHex = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var hex = Block.toHex(this);
if (!this.hex || this.hex !== hex) {
this.hex = hex;
@ -682,18 +682,18 @@ Block.prototype.toHex = function() {
};
Block.toHex = function(block) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getBlockHex(block);
return data.hex;
};
Block.prototype.toBinary = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return Block.toBinary(this);
};
Block.toBinary = function(block) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getBlockHex(block);
return new Buffer(data.hex, 'hex');
};
@ -715,7 +715,7 @@ function Transaction(data) {
return data;
}
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var self = this;
@ -740,34 +740,34 @@ Object.defineProperty(Transaction.prototype, '_txFlag', {
Transaction.isTransaction =
Transaction.isTx = function(tx) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return tx._txFlag === Transaction.prototype._txFlag;
};
Transaction.fromHex = function(hex) {
if (bitcoin.stopping) return [];
return bitcoin.tx(bitcoindjs.txFromHex(hex));
if (daemon.stopping) return [];
return daemon.tx(bitcoindjs.txFromHex(hex));
};
Transaction.prototype.verify = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return this.verified = this.verified || bitcoindjs.verifyTransaction(this);
};
Transaction.prototype.sign =
Transaction.prototype.fill = function(options) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return Transaction.fill(this, options);
};
Transaction.sign =
Transaction.fill = function(tx, options) {
if (bitcoin.stopping) return [];
var isTx = bitcoin.tx.isTx(tx)
if (daemon.stopping) return [];
var isTx = daemon.tx.isTx(tx)
, newTx;
if (!isTx) {
tx = bitcoin.tx(tx);
tx = daemon.tx(tx);
}
try {
@ -784,7 +784,7 @@ Transaction.fill = function(tx, options) {
};
Transaction.prototype.getHash = function(enc) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getTxHex(this);
if (!this.txid || this.txid !== data.hash) {
this.txid = data.hash;
@ -796,12 +796,12 @@ Transaction.prototype.getHash = function(enc) {
};
Transaction.prototype.isCoinbase = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return this.vin.length === 1 && this.vin[0].coinbase;
};
Transaction.prototype.toHex = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var hex = Transaction.toHex(this);
if (!this.hex || hex !== this.hex) {
this.hex = hex;
@ -810,24 +810,24 @@ Transaction.prototype.toHex = function() {
};
Transaction.toHex = function(tx) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getTxHex(tx);
return data.hex;
};
Transaction.prototype.toBinary = function() {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return Transaction.toBinary(this);
};
Transaction.toBinary = function(tx) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var data = bitcoindjs.getTxHex(tx);
return new Buffer(data.hex, 'hex');
};
Transaction.broadcast = function(tx, options, callback) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
if (typeof tx === 'string') {
tx = { hex: tx };
}
@ -848,25 +848,25 @@ Transaction.broadcast = function(tx, options, callback) {
callback = utils.NOOP;
}
if (!bitcoin.isTx(tx)) {
tx = bitcoin.tx(tx);
if (!daemon.isTx(tx)) {
tx = daemon.tx(tx);
}
return bitcoindjs.broadcastTx(tx, fee, own, function(err, hash, tx) {
if (err) {
if (callback === utils.NOOP) {
bitcoin.global.emit('error', err);
daemon.global.emit('error', err);
}
return callback(err);
}
tx = bitcoin.tx(tx);
bitcoin.global.emit('broadcast', tx);
tx = daemon.tx(tx);
daemon.global.emit('broadcast', tx);
return callback(null, hash, tx);
});
};
Transaction.prototype.broadcast = function(options, callback) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
if (!callback) {
callback = options;
options = null;
@ -887,7 +887,7 @@ function Addresses(data) {
return data;
}
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var self = this;
@ -908,7 +908,7 @@ Object.defineProperty(Transaction.prototype, '_addrFlag', {
Addresses.isAddresses =
Addresses.isAddr = function(addr) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
return addr._txFlag === Addresses.prototype._addrFlag;
};
@ -919,7 +919,7 @@ Addresses.isAddr = function(addr) {
var utils = {};
utils.forEach = function(obj, iter, done) {
if (bitcoin.stopping) return [];
if (daemon.stopping) return [];
var pending = obj.length;
if (!pending) return done();
var next = function() {
@ -936,11 +936,11 @@ utils.NOOP = function() {};
* Expose
*/
module.exports = exports = bitcoin;
module.exports = exports = daemon;
exports.Bitcoin = bitcoin;
exports.bitcoin = bitcoin;
exports.bitcoind = bitcoin;
exports.Daemon = daemon;
exports.daemon = daemon;
exports.bitcoind = daemon;
exports.native = bitcoindjs;
exports.bitcoindjs = bitcoindjs;

384
lib/db.js Normal file
View File

@ -0,0 +1,384 @@
'use strict';
var util = require('util');
var chainlib = require('chainlib');
var BaseDB = chainlib.DB;
var Transaction = require('./transaction');
var async = require('async');
var bitcore = require('bitcore');
var BufferWriter = bitcore.encoding.BufferWriter;
var errors = require('./errors');
var levelup = chainlib.deps.levelup;
var log = chainlib.log;
var PublicKey = bitcore.PublicKey;
var Address = bitcore.Address;
function DB(options) {
if(!options) {
options = {};
}
BaseDB.call(this, options);
this.coinbaseAddress = options.coinbaseAddress;
this.coinbaseAmount = options.coinbaseAmount || 50 * 1e8;
this.Transaction = Transaction;
this.network = bitcore.Networks.get(options.network) || bitcore.Networks.testnet;
}
util.inherits(DB, BaseDB);
DB.PREFIXES = {
OUTPUTS: 'outs'
};
DB.CONCURRENCY = 10;
DB.prototype.getBlock = function(hash, callback) {
var self = this;
// get block from bitcoind
this.bitcoind.getBlock(hash, function(err, blockData) {
if(err) {
return callback(err);
}
callback(null, self.Block.fromBuffer(blockData));
});
};
DB.prototype.putBlock = function(block, callback) {
// block is already stored in bitcoind, but we need to update
// our prevhash index still
this._updatePrevHashIndex(block, callback);
};
DB.prototype.getTransaction = function(txid, queryMempool, callback) {
this.bitcoind.getTransaction(txid, queryMempool, function(err, txBuffer) {
if(err) {
return callback(err);
}
callback(null, Transaction().fromBuffer(txBuffer));
});
};
DB.prototype.validateBlockData = function(block, callback) {
// bitcoind does the validation
setImmediate(callback);
};
DB.prototype._updateWeight = function(hash, weight, callback) {
// bitcoind has all work for each block
setImmediate(callback);
};
DB.prototype.buildGenesisData = function() {
var coinbaseTx = this.buildCoinbaseTransaction();
var bw = new BufferWriter();
bw.writeVarintNum(1);
bw.write(coinbaseTx.toBuffer());
var merkleRoot = this.getMerkleRoot([coinbaseTx]);
var buffer = bw.concat();
return {
merkleRoot: merkleRoot,
buffer: buffer
};
};
DB.prototype.buildCoinbaseTransaction = function(transactions, data) {
if(!this.coinbaseAddress) {
throw new Error('coinbaseAddress required to build coinbase');
}
if(!data) {
data = bitcore.crypto.Random.getRandomBuffer(40);
}
var fees = 0;
if(transactions && transactions.length) {
fees = this.getInputTotal(transactions) - this.getOutputTotal(transactions, true);
}
var coinbaseTx = new this.Transaction();
coinbaseTx.to(this.coinbaseAddress, this.coinbaseAmount + fees);
var script = bitcore.Script.buildDataOut(data);
var input = new bitcore.Transaction.Input({
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
outputIndex: 0xffffffff,
sequenceNumber: 4294967295,
script: script
});
coinbaseTx.inputs = [input];
return coinbaseTx;
};
DB.prototype.getOutputTotal = function(transactions, excludeCoinbase) {
var totals = transactions.map(function(tx) {
if(tx.isCoinbase() && excludeCoinbase) {
return 0;
} else {
return tx._getOutputAmount();
}
});
var grandTotal = totals.reduce(function(previousValue, currentValue) {
return previousValue + currentValue;
});
return grandTotal;
};
DB.prototype.getInputTotal = function(transactions) {
var totals = transactions.map(function(tx) {
if(tx.isCoinbase()) {
return 0;
} else {
return tx._getInputAmount();
}
});
var grandTotal = totals.reduce(function(previousValue, currentValue) {
return previousValue + currentValue;
});
return grandTotal;
};
DB.prototype._updateOutputs = function(block, addOutput, callback) {
var txs = this.getTransactionsFromBlock(block);
log.debug('Processing transactions', txs);
log.debug('Updating outputs');
var action = 'put';
if (!addOutput) {
action = 'del';
}
var operations = [];
for (var i = 0; i < txs.length; i++) {
var tx = txs[i];
var txid = tx.id;
var inputs = tx.inputs;
var outputs = tx.outputs;
for (var j = 0; j < outputs.length; j++) {
var output = outputs[j];
var script = output.script;
if(!script) {
log.debug('Invalid script');
continue;
}
if (!script.isPublicKeyHashOut() && !script.isScriptHashOut() && !script.isPublicKeyOut()) {
// ignore for now
log.debug('script was not pubkeyhashout, scripthashout, or pubkeyout');
continue;
}
var address;
if(script.isPublicKeyOut()) {
var pubkey = script.chunks[0].buf;
address = Address.fromPublicKey(new PublicKey(pubkey), this.network);
} else {
address = output.script.toAddress(this.network);
}
var outputIndex = j;
var timestamp = block.timestamp.getTime();
var height = block.height;
operations.push({
type: action,
key: [DB.PREFIXES.OUTPUTS, address, timestamp, txid, outputIndex].join('-'),
value: [output.satoshis, script, height].join(':')
});
}
if(tx.isCoinbase()) {
continue;
}
}
setImmediate(function() {
callback(null, operations);
});
};
DB.prototype._onChainAddBlock = function(block, callback) {
var self = this;
log.debug('DB handling new chain block');
// Remove block from mempool
self.mempool.removeBlock(block.hash);
async.series([
this._updateOutputs.bind(this, block, true), // add outputs
], function(err, results) {
if (err) {
return callback(err);
}
var operations = [];
for (var i = 0; i < results.length; i++) {
operations = operations.concat(results[i]);
}
log.debug('Updating the database with operations', operations);
self.store.batch(operations, callback);
});
};
DB.prototype._onChainRemoveBlock = function(block, callback) {
var self = this;
async.series([
this._updateOutputs.bind(this, block, false), // remove outputs
], function(err, results) {
if (err) {
return callback(err);
}
var operations = [];
for (var i = 0; i < results.length; i++) {
operations = operations.concat(results[i]);
}
self.store.batch(operations, callback);
});
};
DB.prototype.getAPIMethods = function() {
return [
['getTransaction', this, this.getTransaction, 2],
['getBalance', this, this.getBalance, 2],
['getOutputs', this, this.getOutputs, 2],
['getUnspentOutputs', this, this.getUnspentOutputs, 2],
['isSpent', this, this.isSpent, 2]
];
};
DB.prototype.getBalance = function(address, queryMempool, callback) {
this.getUnspentOutputs(address, queryMempool, function(err, outputs) {
if(err) {
return callback(err);
}
var satoshis = outputs.map(function(output) {
return output.satoshis;
});
var sum = satoshis.reduce(function(a, b) {
return a + b;
}, 0);
return callback(null, sum);
});
};
DB.prototype.getOutputs = function(address, queryMempool, callback) {
var self = this;
var outputs = [];
var key = [DB.PREFIXES.OUTPUTS, address].join('-');
var stream = this.store.createReadStream({
start: key,
end: key + '~'
});
stream.on('data', function(data) {
var key = data.key.split('-');
var value = data.value.split(':');
var output = {
address: key[1],
txid: key[3],
outputIndex: Number(key[4]),
satoshis: Number(value[0]),
script: value[1],
blockHeight: Number(value[2])
};
outputs.push(output);
});
var error;
stream.on('error', function(streamError) {
if (streamError) {
error = streamError;
}
});
stream.on('close', function() {
if (error) {
return callback(error);
}
if(queryMempool) {
outputs = outputs.concat(self.bitcoind.getMempoolOutputs(address));
}
callback(null, outputs);
});
return stream;
};
DB.prototype.getUnspentOutputs = function(address, queryMempool, callback) {
var self = this;
this.getOutputs(address, queryMempool, function(err, outputs) {
if (err) {
return callback(err);
} else if(!outputs.length) {
return callback(new errors.NoOutputs('Address ' + address + ' has no outputs'), []);
}
var isUnspent = function(output, callback) {
self.isUnspent(output, queryMempool, callback);
};
async.filter(outputs, isUnspent, function(results) {
callback(null, results);
});
});
};
DB.prototype.isUnspent = function(output, queryMempool, callback) {
this.isSpent(output, queryMempool, function(spent) {
callback(!spent);
});
};
DB.prototype.isSpent = function(output, queryMempool, callback) {
var self = this;
var txid = output.prevTxId ? output.prevTxId.toString('hex') : output.txid;
setImmediate(function() {
callback(self.bitcoind.isSpent(txid, output.outputIndex));
});
};
module.exports = DB;

8
lib/errors.js Normal file
View File

@ -0,0 +1,8 @@
'use strict';
var createError = require('errno').create;
var chainlib = require('chainlib');
var errors = chainlib.errors;
module.exports = errors;

4
lib/genesis.json Normal file
View File

@ -0,0 +1,4 @@
{
"livenet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000",
"testnet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
}

260
lib/node.js Normal file
View File

@ -0,0 +1,260 @@
'use strict';
var async = require('async');
var Chain = require('./chain');
var Block = require('./block');
var DB = require('./db');
var chainlib = require('chainlib');
var P2P = chainlib.P2P;
var BaseNode = chainlib.Node;
var util = require('util');
var log = chainlib.log;
var bitcore = require('bitcore');
var Networks = bitcore.Networks;
var _ = bitcore.deps._;
var genesis = require('./genesis.json');
var daemon = require('./daemon');
function Node(config) {
BaseNode.call(this, config);
this.testnet = config.testnet;
}
util.inherits(Node, BaseNode);
Node.prototype._loadConfiguration = function(config) {
var self = this;
this._loadBitcoind(config);
Node.super_.prototype._loadConfiguration.call(self, config);
};
Node.SYNC_STRATEGIES = {
P2P: 'p2p',
BITCOIND: 'bitcoind'
};
Node.prototype.setSyncStrategy = function(strategy) {
this.syncStrategy = strategy;
if (this.syncStrategy === Node.SYNC_STRATEGIES.P2P) {
this.p2p.startSync();
} else if (this.syncStrategy === Node.SYNC_STRATEGIES.BITCOIND) {
this.p2p.disableSync = true;
this._syncBitcoind();
} else {
throw new Error('Strategy "' + strategy + '" is unknown.');
}
};
Node.prototype._loadBitcoind = function(config) {
var bitcoindConfig = {};
if (config.testnet) {
bitcoindConfig.directory = '~/.bitcoin/testnet3';
} else {
bitcoindConfig.directory = '~/.bitcoin';
}
// start the bitcoind daemon
this.bitcoind = daemon(bitcoindConfig);
};
Node.prototype._syncBitcoind = function() {
var self = this;
log.info('Starting Bitcoind Sync');
var info = self.bitcoind.getInfo();
var height;
async.whilst(function() {
if (self.syncStrategy !== Node.SYNC_STRATEGIES.BITCOIND) {
log.info('Stopping Bitcoind Sync');
return false;
}
height = self.chain.tip.__height;
return height < info.blocks;
}, function(next) {
self.bitcoind.getBlock(height + 1, function(err, blockBuffer) {
if (err) {
return next(err);
}
self.chain.addBlock(self.Block.fromBuffer(blockBuffer), next);
});
}, function(err) {
if (err) {
Error.captureStackTrace(err);
return self.emit('error', err);
}
// we're done resume syncing via p2p to handle forks
self.p2p.synced = true;
self.setSyncStrategy(Node.SYNC_STRATEGIES.P2P);
self.emit('synced');
});
};
Node.prototype._loadNetwork = function(config) {
if (config.network) {
Networks.add(config.network);
this.network = Networks.get(config.network.name);
} else if (config.testnet) {
this.network = Networks.get('testnet');
} else {
this.network = Networks.get('livenet');
}
};
Node.prototype._loadDB = function(config) {
if (config.DB) {
// Other modules can inherit from our DB and replace it with their own
DB = config.DB;
}
if(!config.db) {
config.db = {};
}
config.db.network = this.network;
this.db = new DB(config.db);
};
Node.prototype._loadP2P = function(config) {
if (!config.p2p) {
config.p2p = {};
}
config.p2p.noListen = true;
config.p2p.network = this.network;
config.p2p.Transaction = this.db.Transaction;
config.p2p.Block = this.Block;
config.p2p.disableSync = true; // Disable p2p syncing and instead use bitcoind sync
this.p2p = new P2P(config.p2p);
};
Node.prototype._loadConsensus = function(config) {
if (!config.consensus) {
config.consensus = {};
}
this.Block = Block;
var genesisBlock;
if (config.genesis) {
genesisBlock = config.genesis;
} else if (config.testnet) {
genesisBlock = genesis.testnet;
} else {
genesisBlock = genesis.livenet;
}
if (_.isString(genesisBlock)) {
genesisBlock = this.Block.fromBuffer(new Buffer(genesisBlock, 'hex'));
}
// pass genesis to chain
config.consensus.genesis = genesisBlock;
this.chain = new Chain(config.consensus);
};
Node.prototype._initializeBitcoind = function() {
var self = this;
// Bitcoind
this.bitcoind.on('ready', function(status) {
log.info('Bitcoin Daemon Ready');
self.db.initialize();
});
this.bitcoind.on('open', function(status) {
log.info('Bitcoin Core Daemon Status:', status);
});
this.bitcoind.on('error', function(err) {
Error.captureStackTrace(err);
self.emit('error', err);
});
};
Node.prototype._initializeDatabase = function() {
var self = this;
// Database
this.db.on('ready', function() {
log.info('Bitcoin Database Ready');
self.chain.initialize();
});
this.db.on('error', function(err) {
Error.captureStackTrace(err);
self.emit('error', err);
});
};
Node.prototype._initializeChain = function() {
var self = this;
// Chain
this.chain.on('ready', function() {
log.info('Bitcoin Chain Ready');
self.p2p.initialize();
});
this.chain.on('error', function(err) {
Error.captureStackTrace(err);
self.emit('error', err);
});
};
Node.prototype._initializeP2P = function() {
var self = this;
// Peer-to-Peer
this.p2p.on('ready', function() {
log.info('Bitcoin P2P Ready');
self.emit('ready');
});
this.p2p.on('synced', function() {
log.info('Bitcoin P2P Synced');
self.emit('synced');
});
this.p2p.on('error', function(err) {
Error.captureStackTrace(err);
self.emit('error', err);
});
};
Node.prototype._initialize = function() {
var self = this;
// DB References
this.db.chain = this.chain;
this.db.Block = this.Block;
this.db.bitcoind = this.bitcoind;
// Chain References
this.chain.db = this.db;
this.chain.p2p = this.p2p;
// P2P References
this.p2p.db = this.db;
this.p2p.chain = this.chain;
// Setup Chain of Events
this._initializeBitcoind();
this._initializeDatabase();
this._initializeChain();
this._initializeP2P();
this.on('ready', function() {
self.setSyncStrategy(Node.SYNC_STRATEGIES.BITCOIND);
});
};
module.exports = Node;

132
lib/transaction.js Normal file
View File

@ -0,0 +1,132 @@
'use strict';
var async = require('async');
var bitcore = require('bitcore');
var Transaction = bitcore.Transaction;
var chainlib = require('chainlib');
var BaseTransaction = chainlib.Transaction;
var BaseDatabase = chainlib.DB;
var levelup = chainlib.deps.levelup;
Transaction.prototype.validate = function(db, poolTransactions, callback) {
var self = this;
if (!(db instanceof BaseDatabase)) {
throw new Error('First argument is expected to be an instance of Database');
}
// coinbase is valid
if (this.isCoinbase()) {
return callback();
}
var verified = this.verify();
if(verified !== true) {
return callback(new Error(verified));
}
async.series(
[
self._validateInputs.bind(self, db, poolTransactions),
self._validateOutputs.bind(self),
self._checkSufficientInputs.bind(self)
],
callback
);
};
Transaction.prototype._validateInputs = function(db, poolTransactions, callback) {
var self = this;
// Verify inputs are unspent
async.each(self.inputs, function(input, next) {
async.series(
[
self._populateInput.bind(self, db, input, poolTransactions),
self._checkSpent.bind(self, db, input, poolTransactions),
self._checkScript.bind(self, db, input, self.inputs.indexOf(input))
],
next
);
}, callback);
};
Transaction.prototype.populateInputs = function(db, poolTransactions, callback) {
var self = this;
async.each(
this.inputs,
function(input, next) {
self._populateInput(db, input, poolTransactions, next);
},
callback
);
};
Transaction.prototype._populateInput = function(db, input, poolTransactions, callback) {
if (!input.prevTxId || !Buffer.isBuffer(input.prevTxId)) {
return callback(new Error('Input is expected to have prevTxId as a buffer'));
}
var txid = input.prevTxId.toString('hex');
db.getTransactionFromDB(txid, function(err, prevTx) {
if(err instanceof levelup.errors.NotFoundError) {
// Check the pool for transaction
for(var i = 0; i < poolTransactions.length; i++) {
if(txid === poolTransactions[i].hash) {
input.output = poolTransactions[i].outputs[input.outputIndex];
return callback();
}
}
return callback(new Error('Previous tx ' + input.prevTxId.toString('hex') + ' not found'));
} else if(err) {
callback(err);
} else {
input.output = prevTx.outputs[input.outputIndex];
callback();
}
});
};
Transaction.prototype._checkSpent = function(db, input, poolTransactions, callback) {
// TODO check and see if another transaction in the pool spent the output
db.isSpentDB(input, function(spent) {
if(spent) {
return callback(new Error('Input already spent'));
} else {
callback();
}
});
};
Transaction.prototype._checkScript = function(db, input, index, callback) {
if (input.output.script) {
var scriptPubkey = input.output._scriptBuffer;
var txTo = this.toBuffer();
var valid = db.bitcoind.verifyScript(scriptPubkey, txTo, index);
if(valid) {
return callback();
}
}
return callback(new Error('Script does not validate'));
};
Transaction.prototype._validateOutputs = function(callback) {
setImmediate(callback);
};
Transaction.prototype._checkSufficientInputs = function(callback) {
var inputTotal = this._getInputAmount();
var outputTotal = this._getOutputAmount();
if(inputTotal < outputTotal) {
return callback(new Error('Insufficient inputs'));
} else {
return callback();
}
};
Transaction.manyToBuffer = function(transactions) {
return BaseTransaction.manyToBuffer(transactions);
};
module.exports = Transaction;

View File

@ -20,13 +20,19 @@
{
"name": "Chris Kleeschulte",
"email": "chrisk@bitpay.com"
},
{
"name": "Patrick Nagurny",
"email": "patrick@bitpay.com"
}
],
"scripts": {
"preinstall": "./bin/build-libbitcoind",
"install": "./bin/build-bindings",
"start": "export LD_LIBRARY_PATH=`./platform/os.sh osdir` && node example",
"debug_install": "./bin/build-libbitcoind debug && ./bin/build-bindings debug"
"debug_install": "./bin/build-libbitcoind debug && ./bin/build-bindings debug",
"test": "NODE_ENV=test mocha --recursive",
"coverage": "istanbul cover _mocha -- --recursive"
},
"tags": [
"bitcoin",
@ -36,15 +42,19 @@
"bindings": "^1.2.1",
"mkdirp": "0.5.0",
"nan": "1.3.0",
"tiny": "0.0.10"
"tiny": "0.0.10",
"chainlib": "^0.1.1",
"errno": "^0.1.2",
"async": "1.3.0",
"memdown": "^1.0.0"
},
"devDependencies": {
"async": "1.2.1",
"benchmark": "1.0.0",
"bitcoin": "^2.3.2",
"bitcore": "^0.12.12",
"chai": "^3.0.0",
"mocha": "~1.16.2",
"sinon": "^1.15.4"
"sinon": "^1.15.4",
"proxyquire": "^1.3.1"
}
}

View File

@ -137,6 +137,43 @@ struct async_tx_data {
Eternal<Function> callback;
};
/**
* Verify Scripts
*/
NAN_METHOD(VerifyScript) {
NanScope();
if (!node::Buffer::HasInstance(args[0])) {
return NanThrowTypeError("First argument should be a Buffer.");
}
if (!node::Buffer::HasInstance(args[1])) {
return NanThrowTypeError("Second argument should be a Buffer.");
}
unsigned char *scriptPubKey = (unsigned char *) node::Buffer::Data(args[0]);
unsigned int scriptPubKeyLen = (unsigned int) node::Buffer::Length(args[0]);
const unsigned char *txTo = (unsigned char *) node::Buffer::Data(args[1]);
unsigned int txToLen = (unsigned int)node::Buffer::Length(args[1]);
unsigned int nIn = args[2]->NumberValue();
unsigned int flags = args[3]->NumberValue();
bitcoinconsensus_error* err;
err = 0;
int valid = bitcoinconsensus_verify_script(scriptPubKey, scriptPubKeyLen, txTo, txToLen, nIn, flags, err);
if (!valid && err) {
NanThrowError("The transaction was not valid");
}
NanReturnValue(NanNew<Number>(valid));
}
/**
* Helpers
@ -1120,6 +1157,8 @@ init(Handle<Object> target) {
NODE_SET_METHOD(target, "getChainWork", GetChainWork);
NODE_SET_METHOD(target, "getMempoolOutputs", GetMempoolOutputs);
NODE_SET_METHOD(target, "addMempoolUncheckedTransaction", AddMempoolUncheckedTransaction);
NODE_SET_METHOD(target, "verifyScript", VerifyScript);
}
NODE_MODULE(bitcoindjs, init)

View File

@ -11,6 +11,7 @@
#include "nan.h"
#include "scheduler.h"
#include "core_io.h"
#include "script/bitcoinconsensus.h"
NAN_METHOD(StartBitcoind);
NAN_METHOD(OnBlocksReady);
@ -24,3 +25,4 @@ NAN_METHOD(IsSpent);
NAN_METHOD(GetChainWork);
NAN_METHOD(GetMempoolOutputs);
NAN_METHOD(AddMempoolUncheckedTransaction);
NAN_METHOD(VerifyScript);

83
test/block.unit.js Normal file
View File

@ -0,0 +1,83 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var sinon = require('sinon');
var bitcore = require('bitcore');
var BN = bitcore.crypto.BN;
var BufferWriter = bitcore.encoding.BufferWriter;
var BufferReader = bitcore.encoding.BufferReader;
var bitcoindjs = require('../');
var Block = bitcoindjs.Block;
var chainData = require('./data/pow-chain.json');
describe('Bitcoin Block', function() {
describe('@constructor', function() {
it('set bits and nonce', function() {
var block = new Block(chainData[1]);
should.exist(block.bits);
block.bits.should.equal(chainData[1].bits);
should.exist(block.nonce);
block.nonce.should.equal(chainData[1].nonce);
});
});
describe('#fromBuffer', function() {
var buffer = new Buffer('010000004404c1ff5f300e5ed830b45ec9f68fbe9a0c51c4b4eaa4ce09a03ac4ddde01750000000000000000000000000000000000000000000000000000000000000000b134de547fcc071f4a020000abcdef', 'hex');
it('deserializes correctly', function() {
var block = Block.fromBuffer(buffer);
block.version.should.equal(1);
block.prevHash.should.equal('7501deddc43aa009cea4eab4c4510c9abe8ff6c95eb430d85e0e305fffc10444');
block.merkleRoot.should.equal(new Buffer(Array(32)).toString('hex'));
block.timestamp.should.be.instanceof(Date);
block.timestamp.toISOString().should.equal('2015-02-13T17:30:25.000Z');
block.bits.should.equal(520604799);
block.nonce.should.equal(586);
block.data.should.deep.equal(new Buffer('abcdef', 'hex'));
});
it('roundtrip serialization', function() {
var actual = Block.fromBuffer(buffer).toBuffer();
actual.should.deep.equal(buffer);
});
it('set null prevHash if null hash buffer', function() {
var blockBuffer = new Buffer('0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d4d5fd834b0100007fcc071f4a020000abcdef', 'hex');
var block = Block.fromBuffer(blockBuffer);
block.hasOwnProperty('prevHash').should.equal(true);
should.equal(block.prevHash, null);
});
});
describe('#headerToBufferWriter', function() {
it('serializes correctly', function() {
var block = new Block(chainData[1]);
var bw = new BufferWriter();
block.headerToBufferWriter(bw);
bw.bufs[0].toString('hex').should.equal('01000000'); // version
BufferReader(bw.bufs[1]).readReverse().toString('hex').should.equal(chainData[1].prevHash); // prevhash
Number(bw.bufs[2].toString('hex')).should.equal(0); // merkle root
should.exist(bw.bufs[3]); // time
bw.bufs[3].length.should.equal(4);
should.exist(bw.bufs[4]); // bits
bw.bufs[4].length.should.equal(4);
should.exist(bw.bufs[5]); // nonce
bw.bufs[5].length.should.equal(4);
});
});
describe('Bitcoin Block', function() {
it('should load and serialize the Bitcoin testnet genesis block correctly', function() {
var blockBuffer = new Buffer('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000', 'hex');
var block = Block.fromBuffer(blockBuffer);
block.hash.should.equal('000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943');
});
it('should load and serialize Bitcoin testnet #1 block correctly', function() {
var blockBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex');
var block = Block.fromBuffer(blockBuffer);
block.hash.should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206');
block.toBuffer().should.deep.equal(blockBuffer);
});
});
});

116
test/chain.unit.js Normal file
View File

@ -0,0 +1,116 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var sinon = require('sinon');
var async = require('async');
var proxyquire = require('proxyquire');
var memdown = require('memdown');
var bitcoindjs = require('../');
var DB = bitcoindjs.DB;
var Chain = bitcoindjs.Chain;
var Block = bitcoindjs.Block;
var chainData = require('./data/testnet-blocks.json');
describe('Bitcoin Chain', function() {
describe('@constructor', function() {
it('can create a new instance with and without `new`', function() {
var chain = new Chain();
chain = Chain();
});
});
describe('#_writeBlock', function() {
it('should update hashes and call putBlock', function(done) {
var chain = new Chain();
chain.db = {
putBlock: sinon.stub().callsArg(1)
};
chain._writeBlock({hash: 'hash', prevHash: 'prevhash'}, function(err) {
should.not.exist(err);
chain.db.putBlock.callCount.should.equal(1);
chain.cache.hashes.hash.should.equal('prevhash');
done();
});
});
});
describe('#_validateBlock', function() {
it('should call the callback', function(done) {
var chain = new Chain();
chain._validateBlock('block', function(err) {
should.not.exist(err);
done();
});
});
});
describe('#buildGenesisBlock', function() {
it('can handle no options', function() {
var db = {
buildGenesisData: sinon.stub().returns({})
};
var chain = new Chain({db: db});
var block = chain.buildGenesisBlock();
should.exist(block);
block.should.be.instanceof(Block);
db.buildGenesisData.calledOnce.should.equal(true);
});
it('set timestamp, nonce, bits, merkleRoot and data of the genesis', function() {
var db = {
buildGenesisData: sinon.stub().returns({
merkleRoot: 'merkleRoot',
buffer: new Buffer('abcdef', 'hex')
})
};
var chain = new Chain({db: db});
var timestamp = '2015-03-20T14:46:01.118Z';
var block = chain.buildGenesisBlock({
timestamp: timestamp,
nonce: 1,
bits: 520617984
});
should.exist(block);
block.should.be.instanceof(Block);
block.timestamp.toISOString().should.equal(timestamp);
block.nonce.should.equal(1);
block.bits.should.equal(520617984);
block.merkleRoot.should.equal('merkleRoot');
block.data.should.deep.equal(new Buffer('abcdef', 'hex'));
db.buildGenesisData.calledOnce.should.equal(true);
});
});
describe('#getWeight', function() {
var work = '000000000000000000000000000000000000000000005a7b3c42ea8b844374e9';
var chain = new Chain();
chain.db = {
bitcoind: {
getChainWork: sinon.stub().returns(work)
}
};
it('should give the weight as a BN', function(done) {
chain.getWeight('hash', function(err, weight) {
should.not.exist(err);
weight.toString(16).should.equal('5a7b3c42ea8b844374e9');
done();
});
});
it('should give an error if the weight is undefined', function(done) {
chain.db.bitcoind.getChainWork = sinon.stub().returns(undefined);
chain.getWeight('hash2', function(err, weight) {
should.exist(err);
done();
});
});
});
});

View File

@ -0,0 +1,14 @@
[
{
"comment": "sends to tx[1]",
"hex":"0100000001dee8f4266e83072e0ad258125cc5a42ac25d2d2c73e6e2e873413b3939af1605000000006b483045022100ae987d056f81d2c982b71b0406f2374c1958b24bd289d77371347e275d2a62c002205148b17173be18af4e1e73ce2b0fd600734ea77087754bdba5dc7d645b01880a01210226ab3b46f85bf32f63778c680e16ef8b3fcb51d638a7980d651bfaeae6c17752ffffffff0170820300000000001976a9142baf68e3681df183375a4f4c10306de9a5c6cc7788ac00000000"
},
{
"comment": "spends from tx[0] (valid)",
"hex":"0100000001f77c71cf8c272d22471f054cae7fb48561ebcf004b8ec8f9f65cd87af82a2944000000006a47304402203c2bc91a170facdc5ef4b5b94c413bc7a10f65e09b326d205f070b17aa94d67102205b684111af2a20171eb65db73e6c73f9e77e6e6f739e050bc052ed6ecc9feb4a01210365d8756a4f3fc738105cfab8d80a85189bdb4db5af83374e645b79e2aadd976effffffff01605b0300000000001976a9149e84d1295471958e5ffccd8d36a57bd5d220f8ed88ac00000000"
},
{
"comment": "spends from tx[0] (missing signature)",
"hex":"0100000001f77c71cf8c272d22471f054cae7fb48561ebcf004b8ec8f9f65cd87af82a29440000000000ffffffff01605b0300000000001976a9149e84d1295471958e5ffccd8d36a57bd5d220f8ed88ac00000000"
}
]

File diff suppressed because one or more lines are too long

58
test/data/pow-chain.json Normal file
View File

@ -0,0 +1,58 @@
[
{
"version": 1,
"prevHash": null,
"timestamp": "2015-04-16T20:02:24.777Z",
"bits": 520617984,
"nonce": 0,
"data": ""
},
{
"version": 1,
"prevHash": "bab3003201bdf327ac03735e70a5f02968bc1e8cf74cc9045ae960c074139386",
"timestamp": "2015-04-16T20:02:26.650Z",
"bits": 520617984,
"nonce": 6256,
"data": ""
},
{
"version": 1,
"prevHash": "0002cbf2807997765971f14bdd7c748e93c315c2d3af35b85c6604126c788fa8",
"timestamp": "2015-04-16T20:02:27.885Z",
"bits": 520617984,
"nonce": 12232,
"data": ""
},
{
"version": 1,
"prevHash": "0007b3fec55496a3741caa992aac55395921a965e8cec6192659f266eec39f62",
"timestamp": "2015-04-16T20:02:28.559Z",
"bits": 520355840,
"nonce": 6492,
"data": ""
},
{
"version": 1,
"prevHash": "000002de06d8fdd4d7036fc99ddc8c9b432bfa910e0968756b988e25f7f43d8e",
"timestamp": "2015-04-16T20:02:29.593Z",
"bits": 520355840,
"nonce": 9502,
"data": ""
},
{
"version": 1,
"prevHash": "00016a5c727ef18b406b16c188af017c101f5a32c2d881e92dd7aa1746b185ea",
"timestamp": "2015-04-16T20:02:30.713Z",
"bits": 520355840,
"nonce": 10586,
"data": ""
},
{
"version": 1,
"prevHash": "00023ac90a33ce9faf07f407b525f186180c902fbb2e71c48cd73ffb25a8dc5b",
"timestamp": "22015-04-16T20:02:31.440Z",
"bits": 520181077,
"nonce": 6983,
"data": ""
}
]

File diff suppressed because one or more lines are too long

592
test/db.unit.js Normal file
View File

@ -0,0 +1,592 @@
'use strict';
var should = require('chai').should();
var sinon = require('sinon');
var chainlib = require('chainlib');
var levelup = chainlib.deps.levelup;
var bitcoindjs = require('../');
var DB = bitcoindjs.DB;
var blockData = require('./data/livenet-345003.json');
var bitcore = require('bitcore');
var EventEmitter = require('events').EventEmitter;
var errors = bitcoindjs.errors;
var memdown = require('memdown');
describe('Bitcoin DB', function() {
var coinbaseAmount = 50 * 1e8;
describe('#getBlock', function() {
var db = new DB({store: memdown});
db.bitcoind = {
getBlock: sinon.stub().callsArgWith(1, null, new Buffer(blockData, 'hex'))
};
db.Block = {
fromBuffer: sinon.stub().returns('block')
};
it('should get the block from bitcoind.js', function(done) {
db.getBlock('00000000000000000593b60d8b4f40fd1ec080bdb0817d475dae47b5f5b1f735', function(err, block) {
should.not.exist(err);
block.should.equal('block');
done();
});
});
it('should give an error when bitcoind.js gives an error', function(done) {
db.bitcoind.getBlock = sinon.stub().callsArgWith(1, new Error('error'));
db.getBlock('00000000000000000593b60d8b4f40fd1ec080bdb0817d475dae47b5f5b1f735', function(err, block) {
should.exist(err);
err.message.should.equal('error');
done();
});
});
});
describe('#putBlock', function() {
it('should call _updatePrevHashIndex', function(done) {
var db = new DB({store: memdown});
db._updatePrevHashIndex = sinon.stub().callsArg(1);
db.putBlock('block', function(err) {
should.not.exist(err);
db._updatePrevHashIndex.called.should.equal(true);
done();
});
});
});
describe('#buildGenesisData', function() {
it('build genisis data', function() {
var db = new DB({path: 'path', store: memdown});
db.buildCoinbaseTransaction = sinon.stub().returns({
toBuffer: sinon.stub().returns(new Buffer('abcdef', 'hex'))
});
db.getMerkleRoot = sinon.stub().returns('merkleRoot');
var data = db.buildGenesisData();
data.buffer.should.deep.equal(new Buffer('01abcdef', 'hex'));
data.merkleRoot.should.equal('merkleRoot');
});
});
describe('#buildCoinbaseTransaction', function() {
it('should correctly build a coinbase transaction with no fees', function() {
var db = new DB({path: 'path', store: memdown});
db.coinbaseAddress = 'mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L';
db.coinbaseAmount = coinbaseAmount;
var coinbaseTx = db.buildCoinbaseTransaction();
coinbaseTx.inputs.length.should.equal(1);
var input = coinbaseTx.inputs[0];
var expectedTxId = '0000000000000000000000000000000000000000000000000000000000000000';
input.prevTxId.toString('hex').should.equal(expectedTxId);
should.exist(input.outputIndex);
should.exist(input.sequenceNumber);
should.exist(input._script); // coinbase input script returns null
coinbaseTx.outputs.length.should.equal(1);
var output = coinbaseTx.outputs[0];
output.satoshis.should.equal(coinbaseAmount);
});
it('should correctly build a coinbase transaction with fees', function() {
var db = new DB({path: 'path', store: memdown});
db.coinbaseAddress = 'mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L';
db.coinbaseAmount = coinbaseAmount;
var transactions = [
{
_getInputAmount: sinon.stub().returns(5000),
_getOutputAmount: sinon.stub().returns(4000),
isCoinbase: sinon.stub().returns(false)
},
{
_getInputAmount: sinon.stub().returns(8000),
_getOutputAmount: sinon.stub().returns(7000),
isCoinbase: sinon.stub().returns(false)
}
];
var coinbaseTx = db.buildCoinbaseTransaction(transactions);
coinbaseTx.inputs.length.should.equal(1);
var input = coinbaseTx.inputs[0];
var expectedTxId = '0000000000000000000000000000000000000000000000000000000000000000';
input.prevTxId.toString('hex').should.equal(expectedTxId);
should.exist(input.outputIndex);
should.exist(input.sequenceNumber);
should.exist(input._script); // coinbase input returns null
coinbaseTx.outputs.length.should.equal(1);
var output = coinbaseTx.outputs[0];
output.satoshis.should.equal(coinbaseAmount + 2000);
});
it('should throw an error if coinbaseAddress not included', function() {
var db = new DB({path: 'path', store: memdown});
(function() {
db.buildCoinbaseTransaction();
}).should.throw('coinbaseAddress required to build coinbase');
});
it('will build a coinbase database with different data', function() {
var db = new DB({path: 'path', store: memdown});
db.coinbaseAddress = 'mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L';
var tx1 = db.buildCoinbaseTransaction().uncheckedSerialize();
var tx2 = db.buildCoinbaseTransaction().uncheckedSerialize();
tx1.should.not.equal(tx2);
});
it('can pass in custom data', function() {
var db = new DB({path: 'path', store: memdown});
db.coinbaseAddress = 'mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L';
var tx1 = db.buildCoinbaseTransaction(null, new Buffer('abcdef', 'hex'));
var data = tx1.inputs[0]._script.getData();
data.should.deep.equal(new Buffer('abcdef', 'hex'));
});
});
describe('#getOutputTotal', function() {
it('should return the correct value including the coinbase', function() {
var totals = [10, 20, 30];
var db = new DB({path: 'path', store: memdown});
var transactions = totals.map(function(total) {
return {
_getOutputAmount: function() {
return total;
},
isCoinbase: function() {
return total === 10 ? true : false;
}
};
});
var grandTotal = db.getOutputTotal(transactions);
grandTotal.should.equal(60);
});
it('should return the correct value excluding the coinbase', function() {
var totals = [10, 20, 30];
var db = new DB({path: 'path', store: memdown});
var transactions = totals.map(function(total) {
return {
_getOutputAmount: function() {
return total;
},
isCoinbase: function() {
return total === 10 ? true : false;
}
};
});
var grandTotal = db.getOutputTotal(transactions, true);
grandTotal.should.equal(50)
});
});
describe('#getInputTotal', function() {
it('should return the correct value', function() {
var totals = [10, 20, 30];
var db = new DB({path: 'path', store: memdown});
var transactions = totals.map(function(total) {
return {
_getInputAmount: function() {
return total;
},
isCoinbase: sinon.stub().returns(false)
};
});
var grandTotal = db.getInputTotal(transactions);
grandTotal.should.equal(60);
});
it('should return 0 if the tx is a coinbase', function() {
var db = new DB({store: memdown});
var tx = {
isCoinbase: sinon.stub().returns(true)
};
var total = db.getInputTotal([tx]);
total.should.equal(0);
});
});
describe('#_updateOutputs', function() {
var block = bitcore.Block.fromString(blockData);
var db = new DB({path: 'path', store: memdown, network: 'livenet'});
db.getTransactionsFromBlock = function() {
return block.transactions.slice(0, 8);
};
var data = [
{
key: {
address: '1F1MAvhTKg2VG29w8cXsiSN2PJ8gSsrJw',
timestamp: 1424836934000,
txid: 'fdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e16923',
outputIndex: 0
},
value: {
satoshis: 2502227470,
script: 'OP_DUP OP_HASH160 20 0x02a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b OP_EQUALVERIFY OP_CHECKSIG',
blockHeight: 345003
}
},
{
key: {
prevTxId: '3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a9',
prevOutputIndex: 32
},
value: {
txid: '5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca',
inputIndex: 0,
timestamp: 1424836934000
}
},
{
key: {
address: '1Ep5LA4T6Y7zaBPiwruUJurjGFvCJHzJhm',
timestamp: 1424836934000,
txid: 'e66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d',
outputIndex: 1
},
value: {
satoshis: 3100000,
script: 'OP_DUP OP_HASH160 20 0x9780ccd5356e2acc0ee439ee04e0fe69426c7528 OP_EQUALVERIFY OP_CHECKSIG',
blockHeight: 345003
}
}
];
var key0 = data[0].key;
var value0 = data[0].value;
var key3 = data[1].key;
var value3 = data[1].value;
var key64 = data[2].key;
var value64 = data[2].value;
it('should create the correct operations when updating/adding outputs', function(done) {
db._updateOutputs({height: 345003, timestamp: new Date(1424836934000)}, true, function(err, operations) {
should.not.exist(err);
operations.length.should.equal(11);
operations[0].type.should.equal('put');
var expected0 = ['outs', key0.address, key0.timestamp, key0.txid, key0.outputIndex].join('-');
operations[0].key.should.equal(expected0);
operations[0].value.should.equal([value0.satoshis, value0.script, value0.blockHeight].join(':'));
done();
});
});
it('should create the correct operations when removing outputs', function(done) {
db._updateOutputs({height: 345003, timestamp: new Date(1424836934000)}, false, function(err, operations) {
should.not.exist(err);
operations.length.should.equal(11);
operations[0].type.should.equal('del');
operations[0].key.should.equal(['outs', key0.address, key0.timestamp, key0.txid, key0.outputIndex].join('-'));
operations[0].value.should.equal([value0.satoshis, value0.script, value0.blockHeight].join(':'));
done();
});
});
it('should continue if output script is null', function(done) {
var db = new DB({path: 'path', store: memdown, network: 'livenet'});
var transactions = [
{
inputs: [],
outputs: [
{
script: null,
satoshis: 1000,
}
],
isCoinbase: sinon.stub().returns(false)
}
];
db.getTransactionsFromBlock = function() {
return transactions;
};
db._updateOutputs({height: 345003, timestamp: new Date(1424836934000)}, false, function(err, operations) {
should.not.exist(err);
operations.length.should.equal(0);
done();
});
});
});
describe('#_onChainAddBlock', function() {
var db = new DB({path: 'path', store: memdown});
db._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
db.store = {
batch: sinon.stub().callsArg(1)
};
it('should give error when there is a failure to write', function() {
var errordb = new DB({path: 'path', store: memdown});
errordb._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
errordb.store = {
batch: sinon.stub().callsArgWith(1, new Error('error'))
};
errordb._onChainAddBlock('block', function(err) {
should.exist(err);
});
});
it('should call block processing functions and write to database', function(done) {
db._onChainAddBlock('block', function(err) {
should.not.exist(err);
db._updateOutputs.calledOnce.should.equal(true);
db._updateOutputs.calledWith('block', true).should.equal(true);
db.store.batch.args[0][0].should.deep.equal(['1a', '1b']);
done();
});
});
it('should halt on an error and not write to database', function(done) {
db._updateOutputs.reset();
db.store.batch.reset();
db._updateOutputs = sinon.stub().callsArgWith(2, new Error('error'));
db._onChainAddBlock('block', function(err) {
should.exist(err);
err.message.should.equal('error');
db._updateOutputs.calledOnce.should.equal(true);
db._updateOutputs.calledWith('block', true).should.equal(true);
db.store.batch.called.should.equal(false);
done();
});
});
});
describe('#_onChainRemoveBlock', function() {
var db = new DB({path: 'path', store: memdown});
db._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
db.store = {
batch: sinon.stub().callsArg(1)
};
it('should give error when there is a failure to write', function() {
var errordb = new DB({path: 'path', store: memdown});
errordb._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
errordb.store = {
batch: sinon.stub().callsArgWith(1, new Error('error'))
};
errordb._onChainRemoveBlock('block', function(err) {
should.exist(err);
});
});
it('should call block processing functions and write to database', function(done) {
db._onChainRemoveBlock('block', function(err) {
should.not.exist(err);
db._updateOutputs.calledOnce.should.equal(true);
db._updateOutputs.calledWith('block', false).should.equal(true);
db.store.batch.args[0][0].should.deep.equal(['1a', '1b']);
done();
});
});
it('should halt on an error and not write to database', function(done) {
db._updateOutputs.reset();
db.store.batch.reset();
db._updateOutputs = sinon.stub().callsArgWith(2, new Error('error'));
db._onChainRemoveBlock('block', function(err) {
should.exist(err);
err.message.should.equal('error');
db._updateOutputs.calledOnce.should.equal(true);
db._updateOutputs.calledWith('block', false).should.equal(true);
db.store.batch.called.should.equal(false);
done();
});
});
});
describe('#getAPIMethods', function() {
it('should return the correct methods', function() {
var db = new DB({path: 'path', store: memdown});
var methods = db.getAPIMethods();
methods.length.should.equal(5);
});
});
describe('#getBalance', function() {
it('should sum up the unspent outputs', function(done) {
var db = new DB({path: 'path', store: memdown});
var outputs = [
{satoshis: 1000}, {satoshis: 2000}, {satoshis: 3000}
];
db.getUnspentOutputs = sinon.stub().callsArgWith(2, null, outputs);
db.getBalance('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N', false, function(err, balance) {
should.not.exist(err);
balance.should.equal(6000);
done();
});
});
it('will handle error from unspent outputs', function(done) {
var db = new DB({path: 'path', store: memdown});
db.getUnspentOutputs = sinon.stub().callsArgWith(2, new Error('error'));
db.getBalance('someaddress', false, function(err) {
should.exist(err);
err.message.should.equal('error');
done();
});
});
});
describe('#getOutputs', function() {
var db = new DB({path: 'path', store: memdown});
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
it('should get outputs for an address', function(done) {
var readStream1 = new EventEmitter();
db.store = {
createReadStream: sinon.stub().returns(readStream1)
};
var mempoolOutputs = [
{
address: '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W',
txid: 'aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371',
satoshis: 307627737,
script: 'OP_DUP OP_HASH160 f6db95c81dea3d10f0ff8d890927751bf7b203c1 OP_EQUALVERIFY OP_CHECKSIG',
blockHeight: 352532
}
];
db.bitcoind = {
getMempoolOutputs: sinon.stub().returns(mempoolOutputs)
};
db.getOutputs(address, true, function(err, outputs) {
should.not.exist(err);
outputs.length.should.equal(3);
outputs[0].address.should.equal(address);
outputs[0].txid.should.equal('125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87');
outputs[0].outputIndex.should.equal(1);
outputs[0].satoshis.should.equal(4527773864);
outputs[0].script.should.equal('OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG');
outputs[0].blockHeight.should.equal(345000);
outputs[1].address.should.equal(address);
outputs[1].txid.should.equal('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7');
outputs[1].outputIndex.should.equal(2);
outputs[1].satoshis.should.equal(10000);
outputs[1].script.should.equal('OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG');
outputs[1].blockHeight.should.equal(345004);
outputs[2].address.should.equal(address);
outputs[2].txid.should.equal('aa2db23f670596e96ed94c405fd11848c8f236d266ee96da37ecd919e53b4371');
outputs[2].script.should.equal('OP_DUP OP_HASH160 f6db95c81dea3d10f0ff8d890927751bf7b203c1 OP_EQUALVERIFY OP_CHECKSIG');
outputs[2].blockHeight.should.equal(352532);
done();
});
var data1 = {
key: ['outs', address, '1424835319000', '125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87', '1'].join('-'),
value: ['4527773864', 'OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG', '345000'].join(':')
};
var data2 = {
key: ['outs', address, '1424837300000', '3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', '2'].join('-'),
value: ['10000', 'OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG', '345004'].join(':')
};
readStream1.emit('data', data1);
readStream1.emit('data', data2);
readStream1.emit('close');
});
it('should give an error if the readstream has an error', function(done) {
var readStream2 = new EventEmitter();
db.store = {
createReadStream: sinon.stub().returns(readStream2)
};
db.getOutputs(address, true, function(err, outputs) {
should.exist(err);
err.message.should.equal('readstreamerror');
done();
});
readStream2.emit('error', new Error('readstreamerror'));
process.nextTick(function() {
readStream2.emit('close');
});
});
});
describe('#getUnspentOutputs', function() {
it('should filter out spent outputs', function(done) {
var outputs = [
{
satoshis: 1000,
spent: false,
},
{
satoshis: 2000,
spent: true
},
{
satoshis: 3000,
spent: false
}
];
var i = 0;
var db = new DB({path: 'path', store: memdown});
db.getOutputs = sinon.stub().callsArgWith(2, null, outputs);
db.isUnspent = function(output, queryMempool, callback) {
callback(!outputs[i].spent);
i++;
};
db.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
should.not.exist(err);
outputs.length.should.equal(2);
outputs[0].satoshis.should.equal(1000);
outputs[1].satoshis.should.equal(3000);
done();
});
});
it('should handle an error from getOutputs', function(done) {
var db = new DB({path: 'path', store: memdown});
db.getOutputs = sinon.stub().callsArgWith(2, new Error('error'));
db.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
should.exist(err);
err.message.should.equal('error');
done();
});
});
it('should handle when there are no outputs', function(done) {
var db = new DB({path: 'path', store: memdown});
db.getOutputs = sinon.stub().callsArgWith(2, null, []);
db.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
should.exist(err);
err.should.be.instanceof(errors.NoOutputs);
outputs.length.should.equal(0);
done();
});
});
});
describe('#isUnspent', function() {
var db = new DB({path: 'path', store: memdown});
it('should give true when isSpent() gives false', function(done) {
db.isSpent = sinon.stub().callsArgWith(2, false);
db.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
unspent.should.equal(true);
done();
});
});
it('should give false when isSpent() gives true', function(done) {
db.isSpent = sinon.stub().callsArgWith(2, true);
db.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
unspent.should.equal(false);
done();
});
});
it('should give false when isSpent() returns an error', function(done) {
db.isSpent = sinon.stub().callsArgWith(2, new Error('error'));
db.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
unspent.should.equal(false);
done();
});
});
});
describe('#isSpent', function() {
var db = new DB({path: 'path', store: memdown});
db.bitcoind = {
isSpent: sinon.stub().returns(true)
};
it('should give true if bitcoind.isSpent gives true', function(done) {
db.isSpent('output', true, function(spent) {
spent.should.equal(true);
done();
});
});
});
});

412
test/node.unit.js Normal file
View File

@ -0,0 +1,412 @@
'use strict';
var should = require('chai').should();
var sinon = require('sinon');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var bitcore = require('bitcore');
var Networks = bitcore.Networks;
var blockData = require('./data/livenet-345003.json');
var Block = require('../lib/block');
var proxyquire = require('proxyquire');
var chainlib = require('chainlib');
var OriginalNode = chainlib.Node;
var BaseNode = function() {};
util.inherits(BaseNode, EventEmitter);
BaseNode.log = chainlib.log;
BaseNode.prototype._loadConfiguration = sinon.spy();
BaseNode.prototype._initialize = sinon.spy();
chainlib.Node = BaseNode;
var Node = proxyquire('../lib/node', {chainlib: chainlib});
chainlib.Node = OriginalNode;
describe('Bitcoind Node', function() {
describe('#_loadConfiguration', function() {
it('should call the necessary methods', function() {
var node = new Node({});
node._loadBitcoind = sinon.spy();
node._loadConfiguration({});
node._loadBitcoind.called.should.equal(true);
BaseNode.prototype._loadConfiguration.called.should.equal(true);
});
});
describe('#setSyncStrategy', function() {
it('will call p2p.startSync', function() {
var node = new Node({});
node.p2p = {
startSync: sinon.spy()
};
node.setSyncStrategy(Node.SYNC_STRATEGIES.P2P);
node.p2p.startSync.callCount.should.equal(1);
});
it('will call this._syncBitcoind and disable p2p sync', function() {
var node = new Node({});
node.p2p = {};
node._syncBitcoind = sinon.spy();
node.setSyncStrategy(Node.SYNC_STRATEGIES.BITCOIND);
node._syncBitcoind.callCount.should.equal(1);
node.p2p.disableSync.should.equal(true);
});
it('will error with an unknown strategy', function() {
var node = new Node({});
(function(){
node.setSyncStrategy('unknown');
}).should.throw('Strategy "unknown" is unknown');
});
});
describe('#_loadBitcoind', function() {
it('should initialize', function() {
var node = new Node({});
node._loadBitcoind({});
should.exist(node.bitcoind);
});
it('should initialize with testnet', function() {
var node = new Node({});
node._loadBitcoind({testnet: true});
should.exist(node.bitcoind);
});
});
describe('#_syncBitcoind', function() {
it('will get and add block up to the tip height', function(done) {
var node = new Node({});
node.p2p = {
synced: false
};
node.Block = Block;
node.syncStrategy = Node.SYNC_STRATEGIES.BITCOIND;
node.setSyncStrategy = sinon.stub();
node.bitcoind = {
getInfo: sinon.stub().returns({blocks: 2}),
getBlock: sinon.stub().callsArgWith(1, null, new Buffer(blockData))
};
node.chain = {
tip: {
__height: 0
},
addBlock: function(block, callback) {
node.chain.tip.__height += 1;
callback();
}
};
node.on('synced', function() {
node.p2p.synced.should.equal(true);
node.setSyncStrategy.callCount.should.equal(1);
done();
});
node._syncBitcoind();
});
it('will exit and emit error with error from bitcoind.getBlock', function(done) {
var node = new Node({});
node.p2p = {
synced: false
};
node.syncStrategy = Node.SYNC_STRATEGIES.BITCOIND;
node.setSyncStrategy = sinon.stub();
node.bitcoind = {
getInfo: sinon.stub().returns({blocks: 2}),
getBlock: sinon.stub().callsArgWith(1, new Error('test error'))
};
node.chain = {
tip: {
__height: 0
}
};
node.on('error', function(err) {
err.message.should.equal('test error');
done();
});
node._syncBitcoind();
});
it('will exit if sync strategy is changed to bitcoind', function(done) {
var node = new Node({});
node.p2p = {
synced: false
};
node.syncStrategy = Node.SYNC_STRATEGIES.P2P;
node.setSyncStrategy = sinon.stub();
node.bitcoind = {
getInfo: sinon.stub().returns({blocks: 2})
};
node.chain = {
tip: {
__height: 0
}
};
node.on('synced', function() {
node.p2p.synced.should.equal(true);
node.setSyncStrategy.callCount.should.equal(1);
done();
});
node._syncBitcoind();
});
});
describe('#_loadNetwork', function() {
it('should add the network that was listed in the config', function() {
var config = {
network: {
name: 'chainlib',
alias: 'chainlib',
pubkeyhash: 0x1c,
privatekey: 0x1e,
scripthash: 0x28,
xpubkey: 0x02e8de8f,
xprivkey: 0x02e8da54,
networkMagic: 0x0c110907,
port: 9333
}
};
var node = new Node(config);
node._loadNetwork(config);
var network = Networks.get('chainlib');
should.exist(network);
node.network.name.should.equal('chainlib');
});
it('should use the testnet network if testnet is specified', function() {
var config = {
testnet: true
};
var node = new Node(config);
node._loadNetwork(config);
node.network.name.should.equal('testnet');
});
it('should use the livenet network if nothing is specified', function() {
var config = {};
var node = new Node(config);
node._loadNetwork(config);
node.network.name.should.equal('livenet');
});
});
describe('#_loadDB', function() {
it('should load the db', function() {
var DB = function() {};
var config = {
DB: DB
};
var node = new Node(config);
node._loadDB(config);
node.db.should.be.instanceof(DB);
});
});
describe('#_loadP2P', function() {
it('should load p2p', function() {
var config = {};
var node = new Node(config);
node.db = {
Transaction: bitcore.Transaction
};
node.network = Networks.get('testnet');
node._loadP2P(config);
should.exist(node.p2p);
node.p2p.noListen.should.equal(true);
node.p2p.pool.network.should.deep.equal(node.network);
node.db.Transaction.should.equal(bitcore.Transaction);
});
});
describe('#_loadConsensus', function() {
var node = new Node({});
it('should use the genesis specified in the config', function() {
var config = {
genesis: '0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000'
};
node._loadConsensus(config);
should.exist(node.chain);
node.chain.genesis.hash.should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206');
});
it('should use the testnet genesis if testnet is specified', function() {
var config = {
testnet: true
};
node._loadConsensus(config);
should.exist(node.chain);
node.chain.genesis.hash.should.equal('000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943');
});
it('should use the livenet genesis if nothing is specified', function() {
var config = {};
node._loadConsensus(config);
should.exist(node.chain);
node.chain.genesis.hash.should.equal('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f');
});
});
describe('#_initializeBitcoind', function() {
it('will call db.initialize() on ready event', function(done) {
var node = new Node({});
node.bitcoind = new EventEmitter();
node.db = {
initialize: sinon.spy()
};
sinon.stub(chainlib.log, 'info');
node.bitcoind.on('ready', function() {
setImmediate(function() {
chainlib.log.info.callCount.should.equal(1);
chainlib.log.info.restore();
node.db.initialize.callCount.should.equal(1);
done();
});
});
node._initializeBitcoind();
node.bitcoind.emit('ready');
});
it('will call emit an error from bitcoind.js', function(done) {
var node = new Node({});
node.bitcoind = new EventEmitter();
node.on('error', function(err) {
should.exist(err);
err.message.should.equal('test error');
done();
});
node._initializeBitcoind();
node.bitcoind.emit('error', new Error('test error'));
});
});
describe('#_initializeDatabase', function() {
it('will call chain.initialize() on ready event', function(done) {
var node = new Node({});
node.db = new EventEmitter();
node.chain = {
initialize: sinon.spy()
};
sinon.stub(chainlib.log, 'info');
node.db.on('ready', function() {
setImmediate(function() {
chainlib.log.info.callCount.should.equal(1);
chainlib.log.info.restore();
node.chain.initialize.callCount.should.equal(1);
done();
});
});
node._initializeDatabase();
node.db.emit('ready');
});
it('will call emit an error from db', function(done) {
var node = new Node({});
node.db = new EventEmitter();
node.on('error', function(err) {
should.exist(err);
err.message.should.equal('test error');
done();
});
node._initializeDatabase();
node.db.emit('error', new Error('test error'));
});
});
describe('#_initializeChain', function() {
it('will call p2p.initialize() on ready event', function(done) {
var node = new Node({});
node.chain = new EventEmitter();
node.p2p = {
initialize: sinon.spy()
};
sinon.stub(chainlib.log, 'info');
node.chain.on('ready', function() {
setImmediate(function() {
chainlib.log.info.callCount.should.equal(1);
chainlib.log.info.restore();
node.p2p.initialize.callCount.should.equal(1);
done();
});
});
node._initializeChain();
node.chain.emit('ready');
});
it('will call emit an error from chain', function(done) {
var node = new Node({});
node.chain = new EventEmitter();
node.on('error', function(err) {
should.exist(err);
err.message.should.equal('test error');
done();
});
node._initializeChain();
node.chain.emit('error', new Error('test error'));
});
});
describe('#_initializeP2P', function() {
it('will emit node "ready" when p2p is ready', function(done) {
var node = new Node({});
node.p2p = new EventEmitter();
sinon.stub(chainlib.log, 'info');
node.on('ready', function() {
chainlib.log.info.callCount.should.equal(1);
chainlib.log.info.restore();
done();
});
node._initializeP2P();
node.p2p.emit('ready');
});
it('will call emit an error from p2p', function(done) {
var node = new Node({});
node.p2p = new EventEmitter();
node.on('error', function(err) {
should.exist(err);
err.message.should.equal('test error');
done();
});
node._initializeP2P();
node.p2p.emit('error', new Error('test error'));
});
it('will relay synced event from p2p to node', function(done) {
var node = new Node({});
node.p2p = new EventEmitter();
node.on('synced', function() {
done();
});
node._initializeP2P();
node.p2p.emit('synced');
});
});
describe('#_initialize', function() {
it('should initialize', function(done) {
var node = new Node({});
node.chain = {};
node.Block = 'Block';
node.bitcoind = 'bitcoind';
node.p2p = {};
node.db = {};
node._initializeBitcoind = sinon.spy();
node._initializeDatabase = sinon.spy();
node._initializeChain = sinon.spy();
node._initializeP2P = sinon.spy();
node._initialize();
// references
node.db.chain.should.equal(node.chain);
node.db.Block.should.equal(node.Block);
node.db.bitcoind.should.equal(node.bitcoind);
node.chain.db.should.equal(node.db);
node.chain.p2p.should.equal(node.p2p);
node.chain.db.should.equal(node.db);
node.p2p.db.should.equal(node.db);
node.p2p.chain.should.equal(node.chain);
// events
node._initializeBitcoind.callCount.should.equal(1);
node._initializeDatabase.callCount.should.equal(1);
node._initializeChain.callCount.should.equal(1);
node._initializeP2P.callCount.should.equal(1);
// start syncing
node.setSyncStrategy = sinon.spy();
node.on('ready', function() {
node.setSyncStrategy.callCount.should.equal(1);
done();
});
node.emit('ready');
});
});
});

310
test/transaction.unit.js Normal file
View File

@ -0,0 +1,310 @@
'use strict';
var should = require('chai').should();
var sinon = require('sinon');
var bitcoinlib = require('../');
var Transaction = bitcoinlib.Transaction;
var transactionData = require('./data/bitcoin-transactions.json');
var memdown = require('memdown');
var DB = bitcoinlib.DB;
var db = new DB({store: memdown});
var chainlib = require('chainlib');
var levelup = chainlib.deps.levelup;
describe('Bitcoin Transaction', function() {
describe('#validate', function() {
it('should give an error if verify() fails', function(done) {
var tx = new Transaction();
tx.verify = sinon.stub().returns('invalid tx');
tx.validate(db, [], function(err) {
should.exist(err);
err.message.should.equal('invalid tx');
done();
});
});
it('should give an error if one if the async series functions fails', function(done) {
var tx = new Transaction();
tx._validateInputs = sinon.stub().callsArg(2);
tx._validateOutputs = sinon.stub().callsArgWith(0, new Error('output validation error'));
tx._checkSufficientInputs = sinon.stub().callsArg(0);
tx.verify = sinon.stub().returns(true);
tx.validate(db, [], function(err) {
should.exist(err);
err.message.should.equal('output validation error');
tx._validateInputs.calledOnce.should.equal(true);
tx._validateOutputs.calledOnce.should.equal(true);
tx._checkSufficientInputs.called.should.equal(false);
done();
});
});
it('should call all the functions if there is no error', function(done) {
var tx = new Transaction();
tx._validateInputs = sinon.stub().callsArg(2);
tx._validateOutputs = sinon.stub().callsArg(0);
tx._checkSufficientInputs = sinon.stub().callsArg(0);
tx.verify = sinon.stub().returns(true);
tx.validate(db, [], function(err) {
should.not.exist(err);
tx._validateInputs.calledOnce.should.equal(true);
tx._validateOutputs.calledOnce.should.equal(true);
tx._checkSufficientInputs.calledOnce.should.equal(true);
done();
});
});
});
describe('#_validateInputs', function() {
it('should call all the functions and complete when no errors', function(done) {
var tx = new Transaction();
tx.inputs = ['input'];
sinon.stub(tx, '_populateInput', function(db, input, poolTransactions, callback) {
return callback(null, input, 'populateInput');
});
sinon.stub(tx, '_checkSpent', function(db, input, poolTransactions, callback) {
return callback();
});
sinon.stub(tx, '_checkScript', function(db, input, index, callback) {
return callback();
});
tx._validateInputs('db', [], function(err) {
should.not.exist(err);
tx._populateInput.calledOnce.should.equal(true);
tx._populateInput.calledWith('db', 'input');
tx._checkSpent.calledOnce.should.equal(true);
tx._populateInput.calledWith('db', 'input');
tx._checkScript.calledOnce.should.equal(true);
tx._populateInput.calledWith('input');
done();
});
});
it('should halt on an error', function(done) {
var tx = new Transaction();
tx.inputs = ['input'];
sinon.stub(tx, '_populateInput', function(db, input, poolTransactions, callback) {
return callback();
});
sinon.stub(tx, '_checkSpent', function(db, input, poolTransactions, callback) {
return callback(new Error('error'));
});
sinon.stub(tx, '_checkScript', function(input, callback) {
return callback();
});
tx._validateInputs('db', [], function(err) {
should.exist(err);
err.message.should.equal('error');
tx._populateInput.calledOnce.should.equal(true);
tx._populateInput.calledWith('input');
tx._checkSpent.calledOnce.should.equal(true);
tx._populateInput.calledWith('input');
tx._checkScript.called.should.equal(false);
done();
});
});
});
describe('#populateInputs', function() {
it('will call _populateInput with transactions', function() {
var tx = new Transaction();
tx._populateInput = sinon.stub().callsArg(3);
tx.inputs = ['input'];
var transactions = [];
var db = {};
tx.populateInputs(db, transactions, function(err) {
tx._populateInput.callCount.should.equal(1);
tx._populateInput.args[0][0].should.equal(db);
tx._populateInput.args[0][1].should.equal('input');
tx._populateInput.args[0][2].should.equal(transactions);
});
});
});
describe('#_populateInput', function() {
var input = {
prevTxId: new Buffer('d6cffbb343a6a41eeaa199478c985493843bfe6a59d674a5c188787416cbcda3', 'hex'),
outputIndex: 0
};
it('should give an error if the input does not have a valid prevTxId', function(done) {
var badInput = {
prevTxId: 'bad'
};
var tx = new Transaction();
tx._populateInput({}, badInput, [], function(err) {
should.exist(err);
err.message.should.equal('Input is expected to have prevTxId as a buffer');
done();
});
});
it('if an error happened it should pass it along', function(done) {
var tx = new Transaction();
var db = {
getTransactionFromDB: sinon.stub().callsArgWith(1, new Error('error'))
};
tx._populateInput(db, input, [], function(err) {
should.exist(err);
err.message.should.equal('error');
done();
});
});
it('should return an error if the transaction for the input does not exist', function(done) {
var tx = new Transaction();
var db = {
getTransactionFromDB: sinon.stub().callsArgWith(1, new levelup.errors.NotFoundError())
};
tx._populateInput(db, input, [], function(err) {
should.exist(err);
err.message.should.equal('Previous tx ' + input.prevTxId.toString('hex') + ' not found');
done();
});
});
it('should look through poolTransactions if database does not have transaction', function(done) {
var tx = new Transaction();
var db = {
getTransactionFromDB: sinon.stub().callsArgWith(1, new levelup.errors.NotFoundError())
};
var transactions = [
{
hash: 'd6cffbb343a6a41eeaa199478c985493843bfe6a59d674a5c188787416cbcda3',
outputs: ['output']
}
];
tx._populateInput(db, input, transactions, function(err) {
should.not.exist(err);
input.output.should.equal('output');
done();
});
});
it('should not return an error if an error did not occur', function(done) {
var prevTx = new Transaction();
prevTx.outputs = ['output'];
var tx = new Transaction();
var db = {
getTransactionFromDB: sinon.stub().callsArgWith(1, null, prevTx)
};
tx._populateInput(db, input, [], function(err) {
should.not.exist(err);
input.output.should.equal('output');
done();
});
});
});
describe('#_checkSpent', function() {
it('should return an error if input was spent', function(done) {
var tx = new Transaction();
var db = {
isSpentDB: sinon.stub().callsArgWith(1, true)
};
tx._checkSpent(db, [], 'input', function(err) {
should.exist(err);
err.message.should.equal('Input already spent');
done();
});
});
it('should not return an error if input was unspent', function(done) {
var tx = new Transaction();
var db = {
isSpentDB: sinon.stub().callsArgWith(1, false)
};
tx._checkSpent(db, [], 'input', function(err) {
should.not.exist(err);
done();
});
});
});
describe('#_checkScript', function() {
it('should not have an error with a valid script', function(done) {
var prevTx = new Transaction();
prevTx.fromString(transactionData[0].hex);
var tx = new Transaction();
tx.fromString(transactionData[1].hex);
var input = tx.inputs[0];
input.output = prevTx.outputs[0];
var db = {
bitcoind: {
verifyScript: sinon.stub().returns(true)
}
};
tx._checkScript(db, input, 0, function(err) {
should.not.exist(err);
done();
});
});
it('should have an error when signature is missing', function(done) {
var prevTx = new Transaction();
prevTx.fromString(transactionData[0].hex);
var tx = new Transaction();
tx.fromString(transactionData[2].hex);
var input = tx.inputs[0];
input.output = prevTx.outputs[0];
var db = {
bitcoind: {
verifyScript: sinon.stub().returns(false)
}
};
tx._checkScript(db, input, 0, function(err) {
should.exist(err);
done();
});
});
});
describe('#_checkSufficientInputs', function() {
var inputs = [
{
outputIndex: 0,
output: {
satoshis: 1000
}
},
{
outputIndex: 0,
output: {
satoshis: 2000
}
},
{
outputIndex: 1,
output: {
satoshis: 3000
}
},
];
var outputs = [
{
satoshis: 4000
},
{
satoshis: 3000
}
];
it('should give an error if inputs are less than outputs', function(done) {
var tx = new Transaction();
tx.inputs = inputs;
tx.outputs = outputs;
tx._checkSufficientInputs(function(err) {
should.exist(err);
err.message.should.equal('Insufficient inputs');
done();
});
});
it('should not give an error if inputs are greater than or equal to outputs', function(done) {
inputs[2].output = {
satoshis: 8000
};
var tx = new Transaction();
tx.inputs = inputs;
tx.outputs = outputs;
tx._checkSufficientInputs(function(err) {
should.not.exist(err);
done();
});
});
});
});