Merge pull request #26 from pnagurny/feature/chainlib-bitcoin
Incorporate chainlib-bitcoin into bitcoind.js
This commit is contained in:
commit
5c1737067b
95
README.md
95
README.md
|
@ -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.
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ var fixtureData = {
|
|||
]
|
||||
};
|
||||
|
||||
var bitcoind = require('../')({
|
||||
var bitcoind = require('../').daemon({
|
||||
directory: '~/.bitcoin',
|
||||
testnet: true
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
14
index.js
14
index.js
|
@ -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');
|
|
@ -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',
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
var createError = require('errno').create;
|
||||
var chainlib = require('chainlib');
|
||||
|
||||
var errors = chainlib.errors;
|
||||
|
||||
module.exports = errors;
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"livenet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000",
|
||||
"testnet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
18
package.json
18
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
@ -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
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue