Compare commits
272 Commits
Author | SHA1 | Date |
---|---|---|
Simon | 53a3a2845b | |
Simon | 1803cb983e | |
Ian Munoz | 1ab4e2fd55 | |
Ian Munoz | d85bd7404f | |
Ian Munoz | f2e287d901 | |
Simon | 34a0a90e1d | |
Simon | 4f0edd4bd6 | |
Simon | 0fe30a61f8 | |
Ian Munoz | 3405d2edeb | |
Jack Grigg | 9212ef3c9e | |
Jack Grigg | f5b864f29e | |
Jack Grigg | 433a08d070 | |
Jack Grigg | 3ecbe21697 | |
Jack Grigg | 112c6d896d | |
Jack Grigg | 10b44b6392 | |
Jack Grigg | 0a9d654ca7 | |
Braydon Fuller | 639fdc941e | |
Chris Kleeschulte | b5401b0269 | |
Braydon Fuller | 75d41acdd6 | |
Braydon Fuller | f913784421 | |
Braydon Fuller | a44df8c336 | |
Matias Alejo Garcia | 5351284289 | |
Braydon Fuller | f6e0783f55 | |
Braydon Fuller | 716cee68ee | |
Braydon Fuller | fa79e694cf | |
Karel Bílek | e602eb9a48 | |
Braydon Fuller | 4dc664200b | |
Braydon Fuller | cc4d8d4c5e | |
Braydon Fuller | cfe12eb17e | |
Chris Kleeschulte | f15460a3d9 | |
Braydon Fuller | f72fe82c60 | |
Braydon Fuller | b7f888fc3e | |
Chris Kleeschulte | e5e9d60081 | |
Chris Kleeschulte | 5e5551afbf | |
Chris Kleeschulte | 4ade31ff11 | |
Chris Kleeschulte | 3713a18b2b | |
Chris Kleeschulte | 3a1034757c | |
Chris Kleeschulte | 14f21f9f68 | |
Chris Kleeschulte | ae7359cf93 | |
Chris Kleeschulte | 4a220d8e69 | |
Braydon Fuller | a2a30b81d8 | |
Chris Kleeschulte | be8a5c7db1 | |
Chris Kleeschulte | 4e78500662 | |
Braydon Fuller | b528c851ab | |
Braydon Fuller | 3dc6860cb3 | |
Braydon Fuller | ec760dc44e | |
Braydon Fuller | 8f9af8241a | |
Braydon Fuller | 6ac912545b | |
Braydon Fuller | 3715f07c84 | |
Braydon Fuller | 70fae5335c | |
Braydon Fuller | d31438b22f | |
Braydon Fuller | 3043263e3b | |
Braydon Fuller | 61caf6974a | |
Chris Kleeschulte | e87f628e7a | |
Chris Kleeschulte | a63fac32de | |
Chris Kleeschulte | 3a0ba64a43 | |
Chris Kleeschulte | 7efe271bdb | |
Braydon Fuller | cf16a23408 | |
Braydon Fuller | 32a6b25a91 | |
Braydon Fuller | 4d780a9d2d | |
Braydon Fuller | 814576953c | |
Braydon Fuller | b7560933ba | |
Chris Kleeschulte | c897f62d02 | |
Chris Kleeschulte | fd00be7e8c | |
Chris Kleeschulte | 2b2b4d821c | |
Braydon Fuller | 6df9387715 | |
Braydon Fuller | 29b59c6f7d | |
Braydon Fuller | 47e3cf7fc8 | |
Braydon Fuller | aa7f0d7c60 | |
Braydon Fuller | e8a35e2bb5 | |
Braydon Fuller | c7ec2dcc89 | |
Braydon Fuller | f38fa1324f | |
Braydon Fuller | 88c15f6844 | |
Braydon Fuller | 2dddf01bb0 | |
Braydon Fuller | f76b206178 | |
Braydon Fuller | 1d9b89f187 | |
Braydon Fuller | 0cb795d980 | |
Braydon Fuller | 52cf300858 | |
Braydon Fuller | 8d7d78a89e | |
Braydon Fuller | 0c820c5987 | |
Braydon Fuller | 86b1acd0be | |
Braydon Fuller | 4d1b853fd4 | |
Braydon Fuller | 9c90f05c73 | |
Braydon Fuller | 3fef6f5ffc | |
Braydon Fuller | 35a1b6dd04 | |
Braydon Fuller | 0a95765e51 | |
Braydon Fuller | ea3c813d51 | |
Braydon Fuller | 2a53aad34a | |
Braydon Fuller | f1a9f6d066 | |
Braydon Fuller | 85a302ee9d | |
Braydon Fuller | a4888e5354 | |
Braydon Fuller | 202971ec0c | |
Braydon Fuller | 584dd2cb98 | |
Braydon Fuller | bce64d86e3 | |
Braydon Fuller | 4001a41d2d | |
Braydon Fuller | cd9bbc8661 | |
Braydon Fuller | 73197fdc75 | |
Braydon Fuller | 83880910dc | |
Braydon Fuller | 28ff52ece6 | |
Braydon Fuller | 1800294dfe | |
Braydon Fuller | 6fbadb6c42 | |
Braydon Fuller | 522c822304 | |
Braydon Fuller | bf080422ed | |
Braydon Fuller | 57cb146ce0 | |
Braydon Fuller | 4df9b5f6cf | |
Braydon Fuller | a48bcaf900 | |
Braydon Fuller | fa6474e85f | |
Braydon Fuller | 484b707589 | |
Braydon Fuller | 8f11a33834 | |
Braydon Fuller | 64ed440729 | |
Braydon Fuller | 2cc06cc34b | |
Braydon Fuller | cd4432652d | |
Braydon Fuller | 8bddf4f0d6 | |
Braydon Fuller | 950a9d521c | |
Braydon Fuller | 17e7a7eedb | |
Braydon Fuller | 98bfd358d3 | |
Braydon Fuller | ae91ff2420 | |
Braydon Fuller | b597a05cb4 | |
Braydon Fuller | d399e9acea | |
Braydon Fuller | 4757edc570 | |
Braydon Fuller | cceb4186d4 | |
Braydon Fuller | 791047c10d | |
Braydon Fuller | 8b0d16d5a3 | |
Braydon Fuller | f6bbe54293 | |
Braydon Fuller | 26c87ea32a | |
Braydon Fuller | 85a0c16eef | |
Braydon Fuller | 75c43559d4 | |
Braydon Fuller | 0387c1a6e4 | |
Braydon Fuller | e24a9c96ae | |
Braydon Fuller | d9d50c1f0c | |
Braydon Fuller | 27112fc1d7 | |
Braydon Fuller | 36f337afb3 | |
Braydon Fuller | 7be7a7dce5 | |
Braydon Fuller | abfb07f5f8 | |
Braydon Fuller | c9154d4e0e | |
Braydon Fuller | b0290899ce | |
Braydon Fuller | 2e912af9b4 | |
Braydon Fuller | c22f6505eb | |
Braydon Fuller | d969ad7fb6 | |
Braydon Fuller | 24d1bc82e9 | |
Braydon Fuller | ea792b692f | |
Braydon Fuller | 271dcd8902 | |
Braydon Fuller | 92bae5f09a | |
Braydon Fuller | c1e9d5a3d9 | |
Braydon Fuller | d28f8567f1 | |
Braydon Fuller | d958e83f1d | |
Braydon Fuller | 9e0e9a2c89 | |
Braydon Fuller | 76eeba5999 | |
Braydon Fuller | 944c44ed74 | |
Braydon Fuller | 3f34fb6ea0 | |
Braydon Fuller | a61f43a584 | |
Braydon Fuller | c63e98f061 | |
Braydon Fuller | c6e543c2a1 | |
Braydon Fuller | 5e6600162a | |
Braydon Fuller | 7f17dd4a4c | |
Braydon Fuller | 0272b17f0e | |
Braydon Fuller | b901e10c9d | |
Braydon Fuller | d1cf9deef0 | |
Braydon Fuller | 587602d080 | |
Braydon Fuller | 2015514e78 | |
Braydon Fuller | 3e2492e6d4 | |
Braydon Fuller | 019bc2a58c | |
Braydon Fuller | c3dab07b30 | |
Braydon Fuller | 40e7b24ea9 | |
Braydon Fuller | 7d878adcf0 | |
Braydon Fuller | 458fe2f2b6 | |
Braydon Fuller | b092adcc21 | |
Braydon Fuller | 2b38f08175 | |
Braydon Fuller | 7dabd8c4ab | |
Braydon Fuller | 2975f27a8d | |
Braydon Fuller | 033a62387f | |
Braydon Fuller | feb8038da6 | |
Braydon Fuller | bf67b932de | |
Braydon Fuller | 3fed348cf7 | |
Braydon Fuller | c8ba4eaa8f | |
Braydon Fuller | afda35962b | |
Braydon Fuller | 7c37eba91e | |
Braydon Fuller | e09cc3d1fc | |
Braydon Fuller | 317fdbbdd8 | |
Braydon Fuller | 8fd405eedf | |
Braydon Fuller | a4f5a6fa82 | |
Braydon Fuller | 552abf77cf | |
Braydon Fuller | dab49aef39 | |
Braydon Fuller | bb726bac8b | |
Braydon Fuller | 24ca5ce053 | |
Braydon Fuller | 69ff5423c2 | |
Braydon Fuller | 890b38744d | |
Braydon Fuller | 848dc29777 | |
Braydon Fuller | c2eda9b3c2 | |
Braydon Fuller | 1d358a6994 | |
Braydon Fuller | 042576474f | |
Braydon Fuller | c36b0777d4 | |
Braydon Fuller | cdfe572344 | |
Braydon Fuller | 37f31fdb19 | |
Braydon Fuller | b757bd3148 | |
Braydon Fuller | 52f05f3027 | |
Braydon Fuller | d0937fea55 | |
Braydon Fuller | 019626ba15 | |
Braydon Fuller | 5bea36edc6 | |
Braydon Fuller | d7f49cc192 | |
Braydon Fuller | dbcb70f839 | |
Braydon Fuller | 8102761b55 | |
Braydon Fuller | f3f2f59615 | |
Braydon Fuller | 90e354093c | |
Braydon Fuller | 3713c6ac1e | |
Braydon Fuller | b4b560aa45 | |
Braydon Fuller | d11d0300de | |
Braydon Fuller | 1013ad3c56 | |
Braydon Fuller | fd427fa128 | |
Braydon Fuller | 9bf6941fdf | |
Braydon Fuller | 4662ca0850 | |
Braydon Fuller | 88872734de | |
Braydon Fuller | 82232c0f55 | |
Braydon Fuller | 3ead5928a7 | |
Braydon Fuller | 88a82719ca | |
Braydon Fuller | 962e7b87f8 | |
Braydon Fuller | 67b8ec2152 | |
Braydon Fuller | 7c344b5f24 | |
Braydon Fuller | 1fb552a972 | |
Braydon Fuller | c4649c9b13 | |
Braydon Fuller | 31da32ecfd | |
Braydon Fuller | c116353b8d | |
Braydon Fuller | 7c6e5cf7b1 | |
Braydon Fuller | 18310268a5 | |
Braydon Fuller | 0f24dd5f49 | |
Braydon Fuller | 5932b34a1f | |
Braydon Fuller | 9409374fbe | |
Braydon Fuller | b473b65207 | |
Braydon Fuller | 60333bcb0e | |
Braydon Fuller | ab70aa666e | |
Braydon Fuller | 7d7dfe329d | |
Braydon Fuller | af573b765b | |
Braydon Fuller | b69d848352 | |
Braydon Fuller | 7e70bbfa7d | |
Chris Kleeschulte | 07c317df80 | |
Chris Kleeschulte | 6147be5c49 | |
Braydon Fuller | 3863f5ce9b | |
Jan Pochyla | b55ecf3044 | |
Matias Alejo Garcia | 766d87192a | |
Braydon Fuller | 8c10221480 | |
Braydon Fuller | 462e4e3cdd | |
Braydon Fuller | c988fdc64d | |
Matias Alejo Garcia | e36cdb717a | |
Chris Kleeschulte | 610b9ea269 | |
Chris Kleeschulte | afce33e5ff | |
Chris Kleeschulte | 4894f1abec | |
Matias Alejo Garcia | 4d03aaa73f | |
Matias Alejo Garcia | 6e600b5def | |
Matias Alejo Garcia | 1a68ca4fae | |
Matias Alejo Garcia | 02f2234004 | |
Matias Alejo Garcia | 3bb3d82aac | |
Matias Alejo Garcia | e7895b4b34 | |
Matias Alejo Garcia | d0c2fa61d8 | |
Matias Alejo Garcia | 9f87156adc | |
Chris Kleeschulte | 53735025dd | |
Matias Alejo Garcia | dae5c9d3d5 | |
Chris Kleeschulte | 6dfb354230 | |
Matias Alejo Garcia | c65c2bad20 | |
Matias Alejo Garcia | c1d3f351f2 | |
Matias Alejo Garcia | e7e33313cf | |
Gabe Gattis | ae8221ffb3 | |
Chris Kleeschulte | 4529c3013d | |
Braydon Fuller | e56fdf457f | |
Braydon Fuller | 0d6bc98333 | |
Braydon Fuller | 17e8173d14 | |
Braydon Fuller | 83eba52657 | |
Braydon Fuller | 6e8f3ee917 | |
Chris Kleeschulte | 8afb2b8669 | |
Braydon Fuller | f473ddeddd | |
Chris Kleeschulte | 6a899e4b9c | |
Braydon Fuller | 93e5dbfc34 | |
Chris Kleeschulte | ca19994326 |
|
@ -0,0 +1 @@
|
|||
repo_token: DvrDb09a8vhPlVf6DT4cGBjcFOi6DfZN1
|
|
@ -1,12 +1,7 @@
|
|||
node_modules/
|
||||
node_modules/*
|
||||
coverage/*
|
||||
out/
|
||||
out/*
|
||||
build/
|
||||
build/*
|
||||
.lock-wscript
|
||||
Makefile.gyp
|
||||
*.swp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
|
@ -19,15 +14,16 @@ Makefile.gyp
|
|||
*.filters
|
||||
*.user
|
||||
*.project
|
||||
test.js
|
||||
**/*.dylib
|
||||
**/*.so
|
||||
**/*.old
|
||||
**/*.files
|
||||
**/*.config
|
||||
**/*.creator
|
||||
libbitcoind
|
||||
libbitcoind*
|
||||
libbitcoind.includes
|
||||
*.log
|
||||
.DS_Store
|
||||
bin/bitcoin*
|
||||
bin/SHA256SUMS.asc
|
||||
regtest/data/node1/regtest
|
||||
regtest/data/node2/regtest
|
||||
regtest/data/node3/regtest
|
||||
|
|
16
.travis.yml
16
.travis.yml
|
@ -1,7 +1,7 @@
|
|||
sudo: false
|
||||
language: node_js
|
||||
env:
|
||||
- BITCORENODE_ENV=test BITCORENODE_ASSUME_YES=true CXX=g++-4.8 CC=gcc-4.8
|
||||
- CXX=g++-4.8 CC=gcc-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
|
@ -9,14 +9,14 @@ addons:
|
|||
packages:
|
||||
- g++-4.8
|
||||
- gcc-4.8
|
||||
- libzmq3-dev
|
||||
node_js:
|
||||
- "v0.10.25"
|
||||
- "v0.12.7"
|
||||
- "v4"
|
||||
script:
|
||||
- _mocha -R spec integration/regtest.js
|
||||
- _mocha -R spec integration/regtest-node.js
|
||||
- _mocha -R spec integration/p2p.js
|
||||
- _mocha -R spec --recursive
|
||||
cache:
|
||||
directories:
|
||||
- cache
|
||||
- npm run regtest
|
||||
- npm run test
|
||||
- npm run jshint
|
||||
after_success:
|
||||
- npm run coveralls
|
|
@ -1 +0,0 @@
|
|||
v0.11.2
|
33
README.md
33
README.md
|
@ -1,7 +1,7 @@
|
|||
Bitcore Node
|
||||
============
|
||||
|
||||
A Bitcoin full node for building applications and services with Node.js. A node is extensible and can be configured to run additional services. At the minimum a node has native bindings to Bitcoin Core with the [Bitcoin Service](docs/services/bitcoind.md). Additional services can be enabled to make a node more useful such as exposing new APIs, adding new indexes for addresses with the [Address Service](docs/services/address.md), running a block explorer, wallet service, and other customizations.
|
||||
A Bitcoin full node for building applications and services with Node.js. A node is extensible and can be configured to run additional services. At the minimum a node has an interface to [Bitcoin Core with additional indexing](https://github.com/bitpay/bitcoin/tree/0.12.1-bitcore) for more advanced address queries. Additional services can be enabled to make a node more useful such as exposing new APIs, running a block explorer and wallet service.
|
||||
|
||||
## Install
|
||||
|
||||
|
@ -10,14 +10,15 @@ npm install -g bitcore-node
|
|||
bitcore-node start
|
||||
```
|
||||
|
||||
Note: For your convenience, we distribute binaries for x86_64 Linux and x86_64 Mac OS X. Upon npm install, the binaries for your platform will be downloaded. For more detailed installation instructions, or if you want to compile the project yourself, then please see the [Build & Install](docs/build.md) documentation to build the project from source.
|
||||
Note: For your convenience, we distribute bitcoind binaries for x86_64 Linux and x86_64 Mac OS X. Upon npm install, the binaries for your platform will be downloaded. For more detailed installation instructions, or if you want to compile the project yourself, then please see the Bitcore branch of [Bitcoin Core with additional indexing](https://github.com/bitpay/bitcoin/tree/0.12.1-bitcore).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js v0.12 or v4.2
|
||||
- ~100GB of disk storage
|
||||
- ~4GB of RAM
|
||||
- Mac OS X >= 10.9, Ubuntu >= 12.04 (libc >= 2.15 and libstdc++ >= 6.0.16)
|
||||
- GNU/Linux x86_32/x86_64, or OSX 64bit *(for bitcoind distributed binaries)*
|
||||
- Node.js v0.10, v0.12 or v4
|
||||
- ZeroMQ *(libzmq3-dev for Ubuntu/Debian or zeromq on OSX)*
|
||||
- ~200GB of disk storage
|
||||
- ~8GB of RAM
|
||||
|
||||
## Configuration
|
||||
|
||||
|
@ -32,33 +33,23 @@ bitcore-node install https://github.com/yourname/helloworld
|
|||
|
||||
This will create a directory with configuration files for your node and install the necessary dependencies. For more information about (and developing) services, please see the [Service Documentation](docs/services.md).
|
||||
|
||||
To start bitcore-node as a daemon:
|
||||
|
||||
```bash
|
||||
bitcore-node start --daemon
|
||||
```
|
||||
|
||||
## Add-on Services
|
||||
|
||||
There are several add-on services available to extend the functionality of Bitcore:
|
||||
|
||||
- [Insight API](https://github.com/bitpay/insight-api/tree/v0.3.0)
|
||||
- [Insight UI](https://github.com/bitpay/insight/tree/v0.3.0)
|
||||
- [Insight API](https://github.com/bitpay/insight-api)
|
||||
- [Insight UI](https://github.com/bitpay/insight-ui)
|
||||
- [Bitcore Wallet Service](https://github.com/bitpay/bitcore-wallet-service)
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Upgrade Notes](docs/upgrade.md)
|
||||
- [Services](docs/services.md)
|
||||
- [Bitcoind](docs/services/bitcoind.md) - Native bindings to Bitcoin Core
|
||||
- [Database](docs/services/db.md) - The foundation API methods for getting information about blocks and transactions.
|
||||
- [Address](docs/services/address.md) - Adds additional API methods for querying and subscribing to events with bitcoin addresses.
|
||||
- [Bitcoind](docs/services/bitcoind.md) - Interface to Bitcoin Core
|
||||
- [Web](docs/services/web.md) - Creates an express application over which services can expose their web/API content
|
||||
- [Build & Install](docs/build.md) - How to build and install from source
|
||||
- [Testing & Development](docs/testing.md) - Developer guide for testing
|
||||
- [Development Environment](docs/development.md) - Guide for setting up a development environment
|
||||
- [Node](docs/node.md) - Details on the node constructor
|
||||
- [Bus](docs/bus.md) - Overview of the event bus constructor
|
||||
- [Errors](docs/errors.md) - Reference for error handling and types
|
||||
- [Patch](docs/patch.md) - Information about the patch applied to Bitcoin Core
|
||||
- [Release Process](docs/release.md) - Information about verifying a release and the release process.
|
||||
|
||||
## Contributing
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var benchmark = require('benchmark');
|
||||
var async = require('async');
|
||||
var sinon = require('sinon');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Block = bitcore.Block;
|
||||
var AddressService = require('../lib/services/address');
|
||||
var maxTime = 20;
|
||||
|
||||
var blockData1 = require('./data/block-367238.json');
|
||||
var blockData2 = require('./data/block-367239.json');
|
||||
var blockData3 = require('./data/block-367240.json');
|
||||
|
||||
console.log('Address Service Block Handler');
|
||||
console.log('-----------------------------');
|
||||
|
||||
async.series([
|
||||
function(next) {
|
||||
|
||||
var c = 0;
|
||||
var blocks = [
|
||||
Block.fromBuffer(new Buffer(blockData1, 'hex')),
|
||||
Block.fromBuffer(new Buffer(blockData2, 'hex')),
|
||||
Block.fromBuffer(new Buffer(blockData3, 'hex'))
|
||||
];
|
||||
var blocksLength = 3;
|
||||
var node = {
|
||||
services: {
|
||||
bitcoind : {
|
||||
on: sinon.stub()
|
||||
}
|
||||
}
|
||||
};
|
||||
var addressService = new AddressService({node: node});
|
||||
|
||||
function blockHandler(deffered) {
|
||||
if (c >= blocksLength) {
|
||||
c = 0;
|
||||
}
|
||||
var block = blocks[c];
|
||||
addressService.blockHandler(block, true, function(err, operations) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
deffered.resolve();
|
||||
});
|
||||
c++;
|
||||
}
|
||||
|
||||
var suite = new benchmark.Suite();
|
||||
|
||||
suite.add('blockHandler', blockHandler, {
|
||||
defer: true,
|
||||
maxTime: maxTime
|
||||
});
|
||||
|
||||
suite
|
||||
.on('cycle', function(event) {
|
||||
console.log(String(event.target));
|
||||
})
|
||||
.on('complete', function() {
|
||||
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
|
||||
console.log('----------------------------------------------------------------------');
|
||||
next();
|
||||
})
|
||||
.run();
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log('Finished');
|
||||
process.exit();
|
||||
});
|
|
@ -5,7 +5,7 @@ var bitcoin = require('bitcoin');
|
|||
var async = require('async');
|
||||
var maxTime = 20;
|
||||
|
||||
console.log('Bitcoin Service native interface vs. Bitcoin JSON RPC interface');
|
||||
console.log('Zcash Service native interface vs. Zcash JSON RPC interface');
|
||||
console.log('----------------------------------------------------------------------');
|
||||
|
||||
// To run the benchmarks a fully synced Bitcore Core directory is needed. The RPC comands
|
||||
|
@ -28,7 +28,7 @@ var fixtureData = {
|
|||
|
||||
var bitcoind = require('../').services.Bitcoin({
|
||||
node: {
|
||||
datadir: process.env.HOME + '/.bitcoin',
|
||||
datadir: process.env.HOME + '/.zcash',
|
||||
network: {
|
||||
name: 'testnet'
|
||||
}
|
||||
|
@ -43,12 +43,12 @@ bitcoind.start(function(err) {
|
|||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log('Bitcoin Core started');
|
||||
console.log('Zcash started');
|
||||
});
|
||||
|
||||
bitcoind.on('ready', function() {
|
||||
|
||||
console.log('Bitcoin Core ready');
|
||||
console.log('Zcash ready');
|
||||
|
||||
var client = new bitcoin.Client({
|
||||
host: 'localhost',
|
||||
|
|
184
bin/build
184
bin/build
|
@ -1,184 +0,0 @@
|
|||
#!/bin/bash
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
options=`cat ${root_dir}/bin/config_options.sh`
|
||||
depends_dir=$($root_dir/bin/variables.sh depends_dir)
|
||||
host=$(${root_dir}/bin/variables.sh host)
|
||||
btc_dir="${root_dir}/libbitcoind"
|
||||
patch_sha=$($root_dir/bin/variables.sh patch_sha)
|
||||
config_lib_dir=$($root_dir/bin/variables.sh config_lib_dir)
|
||||
export CPPFLAGS="-I${depends_dir}/${host}/include/boost -I${depends_dir}/${host}/include -L${depends_dir}/${host}/lib"
|
||||
echo "Using BTC directory: ${btc_dir}"
|
||||
|
||||
cd "${root_dir}" || exit -1
|
||||
|
||||
build_dependencies () {
|
||||
if [ -d "${btc_dir}" ]; then
|
||||
pushd "${depends_dir}" || exit -1
|
||||
echo "using host for dependencies: ${host}"
|
||||
if [ "${test}" = true ]; then
|
||||
make HOST=${host} NO_QT=1 NO_UPNP=1
|
||||
else
|
||||
make HOST=${host} NO_QT=1 NO_WALLET=1 NO_UPNP=1
|
||||
fi
|
||||
if test $? -eq 0; then
|
||||
popd || exit -1
|
||||
else
|
||||
echo "Bitcoin's dependency building failed, please check the previous output for details."
|
||||
exit -1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
get_patch_file () {
|
||||
if test -e "${root_dir/PATCH_VERSION}"; then
|
||||
tag=`cat "${root_dir}/PATCH_VERSION" | xargs` || exit -1
|
||||
else
|
||||
echo "no tag file found, please create it in the root of the project as so: 'echo \"v0.10.2\" > PATCH_VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
compare_patch () {
|
||||
cd "${btc_dir}" || exit -1
|
||||
get_patch_file
|
||||
echo "running the diff command from HEAD to ${tag}"
|
||||
last_commit=$(git rev-parse HEAD)
|
||||
diff=$(git show ${last_commit})
|
||||
stripped_diff=$( echo -n "${diff}" | tail -n $( expr `echo -n "${diff}" | wc -l` - 5 ) )
|
||||
matching_patch=`echo -n "${stripped_diff}" | diff -w "${root_dir}/etc/bitcoin.patch" -`
|
||||
}
|
||||
|
||||
cache_files () {
|
||||
cache_file="${root_dir}"/cache/cache.tar
|
||||
pushd "${btc_dir}" || exit -1
|
||||
find . -type f \( -name "*.h" -or -name "*.hpp" -or -name \
|
||||
"*.ipp" -or -name "*.a" \) | tar -cf "${cache_file}" -T -
|
||||
if test $? -ne 0; then
|
||||
echo "We were trying to copy over your cached artifacts, but there was an issue."
|
||||
exit -1
|
||||
fi
|
||||
tar xf "${cache_file}" -C "${root_dir}"/cache
|
||||
if test $? -ne 0; then
|
||||
echo "We were trying to untar your cache, but there was an issue."
|
||||
exit -1
|
||||
fi
|
||||
rm -fr "${cache_file}" >/dev/null 2>&1
|
||||
popd || exit -1
|
||||
}
|
||||
|
||||
debug=
|
||||
if [ "${BITCORENODE_ENV}" == "debug" ]; then
|
||||
options=`cat ${root_dir}/bin/config_options_debug.sh` || exit -1
|
||||
fi
|
||||
|
||||
test=false
|
||||
if [ "${BITCORENODE_ENV}" == "test" ]; then
|
||||
test=true
|
||||
options=`cat ${root_dir}/bin/config_options_test.sh` || exit -1
|
||||
fi
|
||||
|
||||
if hash shasum 2>/dev/null; then
|
||||
shasum_cmd="shasum -a 256"
|
||||
else
|
||||
shasum_cmd="sha256sum"
|
||||
fi
|
||||
|
||||
patch_file_sha=$(${shasum_cmd} "${root_dir}/etc/bitcoin.patch" | awk '{print $1}')
|
||||
last_patch_file_sha=
|
||||
if [ -e "${patch_sha}" ]; then
|
||||
echo "Patch file sha exists, let's see if the patch has changed since last build..."
|
||||
last_patch_file_sha=$(cat "${patch_sha}")
|
||||
fi
|
||||
shared_file_built=false
|
||||
if [ "${last_patch_file_sha}" == "${patch_file_sha}" ]; then
|
||||
echo "Patch file contents matches the sha from the patch file itself, so no reason to rebuild the bindings unless there are no prebuilt bindings."
|
||||
shared_file_built=true
|
||||
fi
|
||||
|
||||
if [ "${shared_file_built}" = false ]; then
|
||||
echo "Looks like the patch to bitcoin changed since last build -or- this is the first build, so rebuilding libbitcoind itself..."
|
||||
mac_response=$($root_dir/bin/variables.sh mac_dependencies)
|
||||
if [ "${mac_response}" != "" ]; then
|
||||
echo "${mac_response}"
|
||||
exit -1
|
||||
fi
|
||||
only_make=false
|
||||
if [ -d "${btc_dir}" ]; then
|
||||
echo "running compare patch..."
|
||||
compare_patch
|
||||
repatch=false
|
||||
if [[ "${matching_patch}" =~ [^\s\\] ]]; then
|
||||
echo "Warning! libbitcoind is not patched with:\
|
||||
${root_dir}/etc/bitcoin.patch."
|
||||
echo -n "Would you like to remove the current patch, checkout the tag: ${tag} and \
|
||||
apply the current patch from "${root_dir}"/etc/bitcoin.patch? (y/N): "
|
||||
if [ "${BITCORENODE_ASSUME_YES}" = true ]; then
|
||||
input=y
|
||||
echo ""
|
||||
else
|
||||
read input
|
||||
fi
|
||||
if [[ "${input}" =~ ^y|^Y ]]; then
|
||||
repatch=true
|
||||
echo "Removing directory: \"${btc_dir}\" and starting over!"
|
||||
rm -fr "${btc_dir}" >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
if [ "${repatch}" = false ]; then
|
||||
echo "Running make inside libbitcoind (assuming you've previously patched and configured libbitcoind)..."
|
||||
cd "${btc_dir}" || exit -1
|
||||
only_make=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${only_make}" = false ]; then
|
||||
echo "Cloning, patching, and building libbitcoind..."
|
||||
get_patch_file
|
||||
echo "attempting to checkout tag: ${tag} of bitcoin from github..."
|
||||
cd "${root_dir}" || exit -1
|
||||
#versions of git prior to 2.x will not clone correctly with --branch
|
||||
git clone --depth 1 https://github.com/bitcoin/bitcoin.git libbitcoind
|
||||
cd "${btc_dir}" || exit -1
|
||||
git fetch --tags
|
||||
git checkout "${tag}"
|
||||
echo '../patch-bitcoin.sh' "${btc_dir}"
|
||||
../bin/patch-bitcoin "${btc_dir}"
|
||||
|
||||
if ! test -d .git; then
|
||||
echo 'Please point this script to an upstream bitcoin git repo.'
|
||||
exit -1
|
||||
fi
|
||||
|
||||
fi
|
||||
build_dependencies
|
||||
echo './autogen.sh'
|
||||
./autogen.sh || exit -1
|
||||
|
||||
full_options="${options} ${config_lib_dir}"
|
||||
echo "running the configure script with the following options:\n :::[\"${full_options}\"]:::"
|
||||
${full_options}
|
||||
|
||||
echo 'make V=1'
|
||||
make V=1 || exit -1
|
||||
|
||||
echo "Creating the sha marker for the patching in libbitcoind..."
|
||||
echo "Writing patch sha file to: \"${patch_sha}\""
|
||||
echo -n `${shasum_cmd} "${root_dir}"/etc/bitcoin.patch | awk '{print $1}'` > "${patch_sha}"
|
||||
cache_files
|
||||
echo 'Build finished successfully.'
|
||||
else
|
||||
echo 'Using existing static library.'
|
||||
fi
|
||||
|
||||
# Building the Bindings
|
||||
|
||||
set -e
|
||||
|
||||
cd "${root_dir}"
|
||||
|
||||
debug=--debug=false
|
||||
if test x"$1" = x'debug'; then
|
||||
debug=--debug
|
||||
fi
|
||||
|
||||
node-gyp ${debug} rebuild
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
cd "${root_dir}"
|
||||
pushd "${root_dir}"/libbitcoind
|
||||
make clean
|
||||
popd
|
||||
node-gyp clean
|
||||
rm -fr cache/*
|
|
@ -1,2 +0,0 @@
|
|||
./configure --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
function getTarballName() {
|
||||
var packageRoot = __dirname + '/..';
|
||||
var version = require(packageRoot + '/package.json').version;
|
||||
var platform = process.platform;
|
||||
var arch = process.arch;
|
||||
var abi = process.versions.modules;
|
||||
var tarballName = 'libbitcoind-' + version + '-node' + abi + '-' + platform + '-' + arch + '.tgz';
|
||||
return tarballName;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
process.stdout.write(getTarballName());
|
||||
}
|
||||
|
||||
module.exports = getTarballName;
|
36
bin/install
36
bin/install
|
@ -1,36 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
|
||||
cd "${root_dir}"
|
||||
|
||||
tarball_name=`node bin/get-tarball-name.js`
|
||||
bucket_name="bitcore-node"
|
||||
binary_url="https://${bucket_name}.s3.amazonaws.com/${tarball_name}"
|
||||
|
||||
echo "Downloading binary: ${binary_url}"
|
||||
|
||||
is_curl=true
|
||||
if hash curl 2>/dev/null; then
|
||||
curl --fail -I $binary_url >/dev/null 2>&1
|
||||
else
|
||||
is_curl=false
|
||||
wget --server-response --spider $binary_url >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
if test $? -eq 0; then
|
||||
if [ "${is_curl}" = true ]; then
|
||||
curl $binary_url > $tarball_name
|
||||
else
|
||||
wget $binary_url
|
||||
fi
|
||||
if test -e "${tarball_name}"; then
|
||||
echo "Unpacking binary distribution"
|
||||
tar -xvzf $tarball_name
|
||||
if test $? -eq 0; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "Prebuild binary could not be downloaded, building from source..."
|
||||
./bin/build
|
|
@ -1,58 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
var bindings = require('bindings');
|
||||
var index = require('../');
|
||||
var log = index.log;
|
||||
|
||||
var packageRoot = bindings.getRoot(bindings.getFileName());
|
||||
var binaryPath = bindings({
|
||||
path: true,
|
||||
bindings: 'bitcoind.node'
|
||||
});
|
||||
var relativeBinaryPath = binaryPath.replace(packageRoot + '/', '');
|
||||
var tarballName = require('./get-tarball-name')();
|
||||
|
||||
log.info('Signing binding binary: "' + binaryPath + '"');
|
||||
|
||||
var signCommand = 'gpg --yes --out ' + binaryPath + '.sig --detach-sig ' + binaryPath;
|
||||
|
||||
var signchild = exec(signCommand, function(error, stdout, stderr) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
log.info('GPG:', stdout);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
log.error(stderr);
|
||||
}
|
||||
|
||||
log.info('Packaging tarball: "' + tarballName + '"');
|
||||
|
||||
// Create a tarball of both the binding and the signature
|
||||
var tarCommand = 'tar -C ' +
|
||||
packageRoot + ' -cvzf ' +
|
||||
tarballName + ' ' +
|
||||
relativeBinaryPath + ' ' +
|
||||
relativeBinaryPath + '.sig';
|
||||
|
||||
var tarchild = exec(tarCommand, function (error, stdout, stderr) {
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
log.info('Tar:', stdout);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
log.error(stderr);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
#root_dir="$(readlink -f "$(dirname "$0")")/.."
|
||||
cd "$root_dir"
|
||||
dir=$(test -n "$1" && echo "$1" || echo "${HOME}/bitcoin")
|
||||
patch_file="$(pwd)/etc/bitcoin.patch"
|
||||
|
||||
cd "$dir" || exit 1
|
||||
|
||||
if ! test -d .git; then
|
||||
echo 'Please point this script to an upstream bitcoin git repo.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test $? -ne 0; then
|
||||
echo 'Unable to checkout necessary commit.'
|
||||
echo 'Please pull the latest HEAD from the upstream bitcoin repo.'
|
||||
exit 1
|
||||
fi
|
||||
git checkout -b "libbitcoind-$(date '+%Y.%m.%d')" || exit 1
|
||||
|
||||
patch -p1 < "$patch_file" || exit 1
|
||||
|
||||
git add --all || exit 1
|
||||
|
||||
[ -n "$( git config user.name )" ] \
|
||||
|| git config user.name 'Bitcore Build'
|
||||
|
||||
[ -n "$( git config user.email )" ] \
|
||||
|| git config user.email "$( id -n -u )@$( hostname -f )"
|
||||
|
||||
git commit -a -m 'allow compiling of libbitcoind.so.' || exit 1
|
||||
|
||||
echo 'Patch completed successfully.'
|
||||
exit 0
|
|
@ -1,61 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var index = require('..');
|
||||
var log = index.log;
|
||||
|
||||
process.title = 'libbitcoind';
|
||||
|
||||
/**
|
||||
* daemon
|
||||
*/
|
||||
var daemon = require('../').services.Bitcoin({
|
||||
node: {
|
||||
datadir: process.env.BITCORENODE_DIR || process.env.HOME + '/.bitcoin',
|
||||
network: {
|
||||
name: process.env.BITCORENODE_NETWORK || 'livenet'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
daemon.start(function() {
|
||||
log.info('ready');
|
||||
});
|
||||
|
||||
daemon.on('error', function(err) {
|
||||
log.info('error="%s"', err.message);
|
||||
});
|
||||
|
||||
daemon.on('open', function(status) {
|
||||
log.info('status="%s"', status);
|
||||
});
|
||||
|
||||
function exitHandler(options, err) {
|
||||
log.info('Stopping daemon');
|
||||
if (err) {
|
||||
log.error('uncaught exception:', err);
|
||||
if(err.stack) {
|
||||
console.log(err.stack);
|
||||
}
|
||||
process.exit(-1);
|
||||
}
|
||||
if (options.sigint) {
|
||||
daemon.stop(function(err) {
|
||||
if(err) {
|
||||
log.error('Failed to stop services: ' + err);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
log.info('Halted');
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//catches uncaught exceptions
|
||||
|
||||
|
||||
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
|
||||
//catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {sigint:true}));
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var start = require('../lib/scaffold/start');
|
||||
var defaultConfig = require('../lib/scaffold/default-config');
|
||||
|
||||
start(defaultConfig());
|
|
@ -1,52 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var AWS = require('aws-sdk');
|
||||
var bindings = require('bindings');
|
||||
var index = require('../');
|
||||
var log = index.log;
|
||||
|
||||
var config = require(process.env.HOME + '/.bitcore-node-upload.json');
|
||||
|
||||
AWS.config.region = config.region;
|
||||
AWS.config.update({
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey
|
||||
});
|
||||
|
||||
var packageRoot = bindings.getRoot(bindings.getFileName());
|
||||
var tarballName = require('./get-tarball-name')();
|
||||
var bucketName = 'bitcore-node';
|
||||
var url = 'https://' + bucketName + '.s3.amazonaws.com/' + tarballName;
|
||||
var localPath = packageRoot + '/' + tarballName;
|
||||
|
||||
log.info('Uploading package: ' + localPath);
|
||||
|
||||
var fileStream = fs.createReadStream(localPath);
|
||||
|
||||
fileStream.on('error', function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
fileStream.on('open', function() {
|
||||
|
||||
var s3 = new AWS.S3();
|
||||
|
||||
var params = {
|
||||
ACL: 'public-read',
|
||||
Key: tarballName,
|
||||
Body: fileStream,
|
||||
Bucket: bucketName
|
||||
};
|
||||
|
||||
s3.putObject(params, function(err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
log.info('Successfully uploaded to: ' + url);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
153
bin/variables.sh
153
bin/variables.sh
|
@ -1,153 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
exec 2> /dev/null
|
||||
root_dir="$(cd "$(dirname $0)" && pwd)/.."
|
||||
if [ "${root_dir}" == "" ]; then
|
||||
root_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.."
|
||||
fi
|
||||
bitcoin_dir="${root_dir}"/libbitcoind
|
||||
cache_dir="${root_dir}"/cache
|
||||
|
||||
host=
|
||||
compute_host () {
|
||||
platform=`uname -a | awk '{print tolower($1)}'`
|
||||
arch=`uname -m`
|
||||
if [ "${arch:0:3}" == "arm" ]; then
|
||||
host="arm-linux-gnueabihf"
|
||||
else
|
||||
host="${arch}"-"${platform}"
|
||||
fi
|
||||
}
|
||||
|
||||
compute_host
|
||||
|
||||
mac_response=
|
||||
check_mac_build_system () {
|
||||
if [ "${platform}" == "darwin" ]; then
|
||||
if [ ! -e "/usr/include/stdlib.h" ]; then
|
||||
if hash xcode-select 2>/dev/null; then
|
||||
mac_response="Please run 'xcode-select --install' from the command line because it seems that you've got Xcode, but not the Xcode command line tools that are required for compiling this project from source..."
|
||||
else
|
||||
mac_response="please use the App Store to install Xcode and Xcode command line tools. After Xcode is installed, please run: 'xcode-select --install' from the command line"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
depends_dir="${bitcoin_dir}"/depends
|
||||
thread="${cache_dir}"/depends/"${host}"/lib/libboost_thread-mt.a
|
||||
filesystem="${cache_dir}"/depends/"${host}"/lib/libboost_filesystem-mt.a
|
||||
chrono="${cache_dir}"/depends/"${host}"/lib/libboost_chrono-mt.a
|
||||
program_options="${cache_dir}"/depends/"${host}"/lib/libboost_program_options-mt.a
|
||||
system="${cache_dir}"/depends/"${host}"/lib/libboost_system-mt.a
|
||||
leveldb="${cache_dir}"/src/leveldb/libleveldb.a
|
||||
memenv="${cache_dir}"/src/leveldb/libmemenv.a
|
||||
libsecp256k1="${cache_dir}"/src/secp256k1/.libs/libsecp256k1.a
|
||||
ssl="${cache_dir}"/depends/"${host}"/lib/libssl.a
|
||||
crypto="${cache_dir}"/depends/"${host}"/lib/libcrypto.a
|
||||
|
||||
config_lib_dir=
|
||||
if [ "${platform}" == "darwin" ]; then
|
||||
config_lib_dir="--with-boost-libdir=${depends_dir}/${host}/lib"
|
||||
else
|
||||
config_lib_dir="--prefix=${depends_dir}/${host}"
|
||||
fi
|
||||
|
||||
if test x"$1" = x'anl'; then
|
||||
if [ "${platform}" != "darwin" ]; then
|
||||
echo -n "-lanl"
|
||||
fi
|
||||
fi
|
||||
|
||||
if test x"$1" = x'cache_dir'; then
|
||||
echo -n "${cache_dir}"
|
||||
fi
|
||||
|
||||
if test x"$1" = x'btcdir'; then
|
||||
echo -n "${bitcoin_dir}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'thread'; then
|
||||
echo -n "${thread}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'filesystem'; then
|
||||
echo -n "${filesystem}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'program_options'; then
|
||||
echo -n "${program_options}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'system'; then
|
||||
echo -n "${system}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'ssl'; then
|
||||
echo -n "${ssl}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'crypto'; then
|
||||
echo -n "${crypto}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'chrono'; then
|
||||
echo -n "${chrono}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'depends_dir'; then
|
||||
echo -n "${depends_dir}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'leveldb'; then
|
||||
echo -n "${leveldb}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'memenv'; then
|
||||
echo -n "${memenv}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'libsecp256k1'; then
|
||||
echo -n "${libsecp256k1}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'host'; then
|
||||
echo -n "${host}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'bdb'; then
|
||||
if [ "${BITCORENODE_ENV}" == "test" ]; then
|
||||
echo -n "${cache_dir}"/depends/"${host}"/lib/libdb_cxx.a
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'patch_sha'; then
|
||||
echo -n "${root_dir}"/cache/patch_sha.txt
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'load_archive'; then
|
||||
if [ "${os}" == "osx" ]; then
|
||||
echo -n "-Wl,-all_load -Wl,--no-undefined"
|
||||
else
|
||||
echo -n "-Wl,--whole-archive ${filesystem} ${thread} "${cache_dir}"/src/.libs/libbitcoind.a -Wl,--no-whole-archive"
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'mac_dependencies'; then
|
||||
check_mac_build_system
|
||||
echo -n "${mac_response}"
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'wallet_enabled'; then
|
||||
if [ "${BITCORENODE_ENV}" == "test" ]; then
|
||||
echo -n "-DENABLE_WALLET"
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'bitcoind'; then
|
||||
echo -n "${cache_dir}"/src/.libs/libbitcoind.a
|
||||
fi
|
||||
|
||||
if test -z "$1" -o x"$1" = x'config_lib_dir'; then
|
||||
echo -n "${config_lib_dir}"
|
||||
fi
|
59
binding.gyp
59
binding.gyp
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "libbitcoind",
|
||||
"include_dirs" : [
|
||||
"<!(node -e \"require('nan')\")",
|
||||
"<!(./bin/variables.sh cache_dir)/src",
|
||||
"<!(./bin/variables.sh cache_dir)/depends/<!(./bin/variables.sh host)/include",
|
||||
"<!(./bin/variables.sh cache_dir)/src/leveldb/include"
|
||||
],
|
||||
"sources": [
|
||||
"./src/libbitcoind.cc",
|
||||
],
|
||||
"conditions": [
|
||||
[
|
||||
"OS==\"mac\"",
|
||||
{
|
||||
"xcode_settings": {
|
||||
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
||||
"GCC_ENABLE_CPP_RTTI": "YES",
|
||||
"MACOSX_DEPLOYMENT_TARGET": "10.9",
|
||||
'OTHER_CFLAGS': [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-fpermissive",
|
||||
"<!(./bin/variables.sh wallet_enabled)",
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"cflags_cc": [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-fpermissive",
|
||||
"<!(./bin/variables.sh wallet_enabled)",
|
||||
],
|
||||
"link_settings": {
|
||||
"libraries": [
|
||||
"<!(./bin/variables.sh bitcoind)",
|
||||
"<!(./bin/variables.sh filesystem)",
|
||||
"<!(./bin/variables.sh thread)",
|
||||
"<!(./bin/variables.sh program_options)",
|
||||
"<!(./bin/variables.sh system)",
|
||||
"<!(./bin/variables.sh chrono)",
|
||||
"<!(./bin/variables.sh libsecp256k1)",
|
||||
"<!(./bin/variables.sh leveldb)",
|
||||
"<!(./bin/variables.sh memenv)",
|
||||
"<!(./bin/variables.sh bdb)",
|
||||
"<!(./bin/variables.sh anl)",
|
||||
"<!(./bin/variables.sh ssl)"
|
||||
],
|
||||
"ldflags": [
|
||||
"<!(./bin/variables.sh load_archive)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
patch_sha.txt
|
||||
src/*
|
||||
depends/*
|
|
@ -1,89 +0,0 @@
|
|||
# Build & Install
|
||||
This includes a detailed instructions for compiling. There are two main parts of the build, compiling Bitcoin Core as a static library and the Node.js bindings.
|
||||
|
||||
## Ubuntu 14.04 (Unix/Linux)
|
||||
If git is not already installed, it can be installed by running:
|
||||
|
||||
```bash
|
||||
sudo apt-get install git
|
||||
```
|
||||
|
||||
If Node.js v0.12 isn't installed, it can be installed using "nvm", it can be done by following the installation script at [https://github.com/creationix/nvm#install-script](https://github.com/creationix/nvm#install-script) and then install version v0.12
|
||||
|
||||
```bash
|
||||
nvm install v0.12
|
||||
```
|
||||
|
||||
To build Bitcoin Core and bindings development packages are needed:
|
||||
|
||||
```bash
|
||||
sudo apt-get install build-essential libtool autotools-dev automake autoconf pkg-config libssl-dev
|
||||
```
|
||||
|
||||
Clone the bitcore-node repository locally:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/bitpay/bitcore-node.git
|
||||
cd bitcore-node
|
||||
```
|
||||
|
||||
And finally run the build which will take several minutes. A script in the "bin" directory will download Bitcoin Core v0.11, apply a patch (see more info below), and compile the static library and Node.js bindings. You can start this by running:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Once everything is built, you can run bitcore-node via:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
This will then start the syncing process for Bitcoin Core and the extended capabilities as provided by the built-in Address Module (details below).
|
||||
|
||||
## Fedora
|
||||
Later versions of Fedora (>= 22) should also work with this project. The directions for Ubuntu should generally work except the installation of system utilities and libraries is a bit different. Git is already installed and ready for use without installation.
|
||||
|
||||
```bash
|
||||
yum install libtool automake autoconf pkgconfig openssl make gcc gcc-c++ kernel-devel openssl-devel.x86_64 patch
|
||||
```
|
||||
|
||||
## Mac OS X Yosemite
|
||||
If Xcode is not already installed, it can be installed via the Mac App Store (will take several minutes). XCode includes "Clang", "git" and other build tools. Once Xcode is installed, you'll then need to install "xcode-select" via running in a terminal and following the prompts:
|
||||
|
||||
```bash
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
If "Homebrew" is not yet installed, it's needed to install "autoconf" and others. You can install it using the script at [http://brew.sh](http://brew.sh) and following the directions at [https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Installation.md](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Installation.md) And then run in a terminal:
|
||||
|
||||
```bash
|
||||
brew install autoconf automake libtool openssl pkg-config
|
||||
```
|
||||
|
||||
If Node.js v0.12 and associated commands "node", "npm" and "nvm" are not already installed, you can use "nvm" by running the script at [https://github.com/creationix/nvm#install-script](https://github.com/creationix/nvm#install-script) And then run this command to install Node.js v0.12
|
||||
|
||||
```bash
|
||||
nvm install v0.12
|
||||
```
|
||||
|
||||
Clone the bitcore-node repository locally:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/bitpay/bitcore-node.git
|
||||
cd bitcore-node
|
||||
```
|
||||
|
||||
And finally run the build which will take several minutes. A script in the "bin" directory will download Bitcoin Core v0.11, apply a patch (see more info below), and compile the static library and Node.js bindings. You can start this by running:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Once everything is built, you can run bitcore-node via:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
This will then start the syncing process for Bitcoin Core and the extended capabilities as provided by the built-in Address Module (details below).
|
|
@ -20,11 +20,11 @@ bus.close();
|
|||
```javascript
|
||||
|
||||
// subscribe to all transaction events
|
||||
bus.subscribe('db/transaction');
|
||||
bus.subscribe('bitcoind/rawtransaction');
|
||||
|
||||
// only subscribe to events relevant to a bitcoin address
|
||||
bus.subscribe('address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
|
||||
// to subscribe to new block hashes
|
||||
bus.subscribe('bitcoind/hashblock');
|
||||
|
||||
// unsubscribe
|
||||
bus.unsubscribe('db/transaction');
|
||||
bus.unsubscribe('bitcoind/rawtransaction');
|
||||
```
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
# Setting up Development Environment
|
||||
|
||||
## Install Node.js
|
||||
|
||||
Install Node.js by your favorite method, or use Node Version Manager by following directions at https://github.com/creationix/nvm
|
||||
|
||||
```bash
|
||||
nvm install v4
|
||||
```
|
||||
|
||||
## Fork and Download Repositories
|
||||
|
||||
To develop bitcore-node:
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
git clone git@github.com:<yourusername>/bitcore-node.git
|
||||
git clone git@github.com:<yourusername>/bitcore-lib.git
|
||||
```
|
||||
|
||||
To develop bitcoin or to compile from source:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:<yourusername>/bitcoin.git
|
||||
git fetch origin <branchname>:<branchname>
|
||||
git checkout <branchname>
|
||||
```
|
||||
**Note**: See bitcoin documentation for building bitcoin on your platform.
|
||||
|
||||
|
||||
## Install Development Dependencies
|
||||
|
||||
For Ubuntu:
|
||||
```bash
|
||||
sudo apt-get install libzmq3-dev
|
||||
sudo apt-get install build-essential
|
||||
```
|
||||
**Note**: Make sure that libzmq-dev is not installed, it should be removed when installing libzmq3-dev.
|
||||
|
||||
|
||||
For Mac OS X:
|
||||
```bash
|
||||
brew install zeromq
|
||||
```
|
||||
|
||||
## Install and Symlink
|
||||
|
||||
```bash
|
||||
cd bitcore-lib
|
||||
npm install
|
||||
cd ../bitcore-node
|
||||
npm install
|
||||
```
|
||||
**Note**: If you get a message about not being able to download bitcoin distribution, you'll need to compile bitcoind from source, and setup your configuration to use that version.
|
||||
|
||||
|
||||
We now will setup symlinks in `bitcore-node` *(repeat this for any other modules you're planning on developing)*:
|
||||
```bash
|
||||
cd node_modules
|
||||
rm -rf bitcore-lib
|
||||
ln -s ~/bitcore-lib
|
||||
rm -rf bitcoind-rpc
|
||||
ln -s ~/bitcoind-rpc
|
||||
```
|
||||
|
||||
And if you're compiling or developing bitcoin:
|
||||
```bash
|
||||
cd ../bin
|
||||
ln -sf ~/bitcoin/src/bitcoind
|
||||
```
|
||||
|
||||
## Run Tests
|
||||
|
||||
If you do not already have mocha installed:
|
||||
```bash
|
||||
npm install mocha -g
|
||||
```
|
||||
|
||||
To run all test suites:
|
||||
```bash
|
||||
cd bitcore-node
|
||||
npm run regtest
|
||||
npm run test
|
||||
```
|
||||
|
||||
To run a specific unit test in watch mode:
|
||||
```bash
|
||||
mocha -w -R spec test/services/bitcoind.unit.js
|
||||
```
|
||||
|
||||
To run a specific regtest:
|
||||
```bash
|
||||
mocha -R spec regtest/bitcoind.js
|
||||
```
|
||||
|
||||
## Running a Development Node
|
||||
|
||||
To test running the node, you can setup a configuration that will specify development versions of all of the services:
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
mkdir devnode
|
||||
cd devnode
|
||||
mkdir node_modules
|
||||
touch bitcore-node.json
|
||||
touch package.json
|
||||
```
|
||||
|
||||
Edit `bitcore-node.json` with something similar to:
|
||||
```json
|
||||
{
|
||||
"network": "livenet",
|
||||
"port": 3001,
|
||||
"services": [
|
||||
"bitcoind",
|
||||
"web",
|
||||
"insight-api",
|
||||
"insight-ui",
|
||||
"<additional_service>"
|
||||
],
|
||||
"servicesConfig": {
|
||||
"bitcoind": {
|
||||
"spawn": {
|
||||
"datadir": "/home/<youruser>/.bitcoin",
|
||||
"exec": "/home/<youruser>/bitcoin/src/bitcoind"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: To install services [insight-api](https://github.com/bitpay/insight-api) and [insight-ui](https://github.com/bitpay/insight-ui) you'll need to clone the repositories locally.
|
||||
|
||||
Setup symlinks for all of the services and dependencies:
|
||||
|
||||
```bash
|
||||
cd node_modules
|
||||
ln -s ~/bitcore-lib
|
||||
ln -s ~/bitcore-node
|
||||
ln -s ~/insight-api
|
||||
ln -s ~/insight-ui
|
||||
```
|
||||
|
||||
Make sure that the `<datadir>/bitcoin.conf` has the necessary settings, for example:
|
||||
```
|
||||
server=1
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
zmqpubrawtx=tcp://127.0.0.1:28332
|
||||
zmqpubhashblock=tcp://127.0.0.1:28332
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
||||
```
|
||||
|
||||
From within the `devnode` directory with the configuration file, start the node:
|
||||
```bash
|
||||
../bitcore-node/bin/bitcore-node start
|
||||
```
|
|
@ -1,16 +0,0 @@
|
|||
# Errors
|
||||
Many times there are cases where an error condition can be gracefully handled depending on a particular use. To assist in better error handling, errors will have different types so that it's possible to determine the type of error and handle appropriately.
|
||||
|
||||
```js
|
||||
node.services.address.getUnspentOutputs('00000000839a8...', function(err, outputs) {
|
||||
|
||||
if (err instanceof errors.NoOutputs) {
|
||||
// the address hasn't received any transactions
|
||||
}
|
||||
|
||||
// otherwise the address has outputs (which may be unspent/spent)
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
For more information about different types of errors, please see `lib/errors.js`.
|
|
@ -1,54 +0,0 @@
|
|||
A Bitcoin full node for building applications and services with Node.js. A node is extensible and can be configured to run additional services. At the minimum a node has native bindings to Bitcoin Core with the [Bitcoin Service](services/bitcoind.md). Additional services can be enabled to make a node more useful such as exposing new APIs, adding new indexes for addresses with the [Address Service](services/address.md), running a block explorer, wallet service, and other customizations.
|
||||
|
||||
# Install
|
||||
|
||||
```bash
|
||||
npm install -g bitcore-node
|
||||
bitcore-node start
|
||||
```
|
||||
|
||||
Note: For your convenience, we distribute binaries for x86_64 Linux and x86_64 Mac OS X. Upon npm install, the binaries for your platform will be downloaded. For more detailed installation instructions, or if you want to compile the project yourself, then please see the [Build & Install](build.md) documentation to build the project from source.
|
||||
|
||||
# Prerequisites
|
||||
- Node.js v0.12 or v4.2
|
||||
- ~100GB of disk storage
|
||||
- ~4GB of RAM
|
||||
- Mac OS X >= 10.9, Ubuntu >= 12.04 (libc >= 2.15 and libstdc++ >= 6.0.16)
|
||||
|
||||
# Configuration
|
||||
Bitcore includes a Command Line Interface (CLI) for managing, configuring and interfacing with your Bitcore Node.
|
||||
|
||||
```bash
|
||||
bitcore-node create -d <bitcoin-data-dir> mynode
|
||||
cd mynode
|
||||
bitcore-node install <service>
|
||||
bitcore-node install https://github.com/yourname/helloworld
|
||||
```
|
||||
|
||||
This will create a directory with configuration files for your node and install the necessary dependencies. For more information about (and developing) services, please see the [Service Documentation](services.md).
|
||||
|
||||
To start bitcore as a daemon:
|
||||
|
||||
```bash
|
||||
bitcore start --daemon
|
||||
```
|
||||
|
||||
# Add-on Services
|
||||
There are several add-on services available to extend the functionality of Bitcore Node:
|
||||
- [Insight API](https://github.com/bitpay/insight-api/tree/v0.3.0)
|
||||
- [Insight UI](https://github.com/bitpay/insight/tree/v0.3.0)
|
||||
|
||||
# Documentation
|
||||
- [Services](services.md)
|
||||
- [Bitcoind](services/bitcoind.md) - Native bindings to Bitcoin Core
|
||||
- [Database](services/db.md) - The foundation API methods for getting information about blocks and transactions.
|
||||
- [Address](services/address.md) - Adds additional API methods for querying and subscribing to events with bitcoin addresses.
|
||||
- [Web](services/web.md) - Creates an express application over which services can expose their web/API content
|
||||
|
||||
- [Build & Install](build.md) - How to build and install from source
|
||||
- [Testing & Development](testing.md) - Developer guide for testing
|
||||
- [Node](node.md) - Details on the node constructor
|
||||
- [Bus](bus.md) - Overview of the event bus constructor
|
||||
- [Errors](errors.md) - Reference for error handling and types
|
||||
- [Patch](patch.md) - Information about the patch applied to Bitcoin Core
|
||||
- [Release Process](release.md) - Information about verifying a release and the release process.
|
|
@ -0,0 +1 @@
|
|||
../README.md
|
|
@ -1,6 +0,0 @@
|
|||
# Static Library Patch
|
||||
To provide native bindings to JavaScript _(or any other language for that matter)_, Bitcoin code, itself, must be linkable. Currently, Bitcoin Core provides a JSON RPC interface to bitcoind as well as a shared library for script validation _(and hopefully more)_ called libbitcoinconsensus. There is a node module, [node-libbitcoinconsensus](https://github.com/bitpay/node-libbitcoinconsensus), that exposes these methods. While these interfaces are useful for several use cases, there are additional use cases that are not fulfilled, and being able to implement customized interfaces is necessary. To be able to do this a few simple changes need to be made to Bitcoin Core to compile as a static library.
|
||||
|
||||
The patch is located at `etc/bitcoin.patch` and adds a configure option `--enable-daemonlib` to compile all object files with `-fPIC` (Position Independent Code - needed to create a shared object), exposes leveldb variables and objects, exposes the threadpool to the bindings, and conditionally includes the main function.
|
||||
|
||||
Every effort will be made to ensure that this patch stays up-to-date with the latest release of Bitcoin. At the very least, this project began supporting Bitcoin Core v0.11.
|
|
@ -1,55 +1,28 @@
|
|||
# Release Process
|
||||
Binaries for the C++ binding file (which includes libbitcoind statically linked in) are distributed for convenience. The binary binding file `bitcoind.node` is signed and published to S3 for later download and installation. Source files can also be built if binaries are not desired.
|
||||
|
||||
## How to Verify Signatures
|
||||
|
||||
```
|
||||
cd build/Release
|
||||
gpg --verify bitcoind.node.sig bitcoind.node
|
||||
```
|
||||
|
||||
To verify signatures, use the following PGP keys:
|
||||
- @braydonf: [https://pgp.mit.edu/pks/lookup?op=get&search=0x9BBF07CAC07A276D](https://pgp.mit.edu/pks/lookup?op=get&search=0x9BBF07CAC07A276D)
|
||||
- @kleetus: [https://pgp.mit.edu/pks/lookup?op=get&search=0x33195D27EF6BDB7F](https://pgp.mit.edu/pks/lookup?op=get&search=0x33195D27EF6BDB7F)
|
||||
- @pnagurny: [https://pgp.mit.edu/pks/lookup?op=get&search=0x0909B33F0AA53013](https://pgp.mit.edu/pks/lookup?op=get&search=0x0909B33F0AA53013)
|
||||
Binaries for bitcoind are distributed for convenience and built deterministically with Gitian, signatures for bitcoind are located at the [gitian.sigs](https://github.com/bitpay/gitian.sigs) respository.
|
||||
|
||||
## How to Release
|
||||
Ensure you've followed the instructions in the README.md for building the project from source. When building for any platform, be sure to keep in mind the minimum supported C and C++ system libraries and build from source using this library. Example, Ubuntu 12.04 has the earliest system library for Linux that we support, so it would be easiest to build the Linux artifact using this version. You will be using node-gyp to build the C++ bindings. A script will then upload the bindings to S3 for later use. You will also need credentials for BitPay's bitcore-node S3 bucket and be listed as an author for the bitcore-node's npm module.
|
||||
- Create a file `.bitcore-node-upload.json` in your home directory
|
||||
- The format of this file should be:
|
||||
|
||||
```json
|
||||
{
|
||||
"region": "us-west-2",
|
||||
"accessKeyId": "xxx",
|
||||
"secretAccessKey": "yyy"
|
||||
}
|
||||
```
|
||||
|
||||
When publishing to npm, the .gitignore file is used to exclude files from the npm publishing process. Be sure that the bitcore-node directory has only the directories and files that you would like to publish to npm. You might need to run the commands below on each platform that you intend to publish (e.g. Mac and Linux).
|
||||
|
||||
To make a release, bump the `version` and `lastBuild` of the `package.json`:
|
||||
To make a release, bump the `version` of the `package.json`:
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
git commit -a -m "Bump package version to <version>"
|
||||
npm install
|
||||
npm run package
|
||||
npm run upload
|
||||
npm publish
|
||||
```
|
||||
|
||||
And then update the `version` of the `package.json` for development (e.g. "0.3.2-dev"):
|
||||
|
||||
```bash
|
||||
git commit -a -m "Bump development version to <version>"
|
||||
npm run test
|
||||
npm run regtest
|
||||
npm run jshint
|
||||
git commit -a -m "Bump package version to <version>"
|
||||
git push upstream master
|
||||
npm publish
|
||||
```
|
||||
|
||||
Create a release tag and push it to the BitPay Github repo:
|
||||
|
||||
```bash
|
||||
git tag <version>
|
||||
git push upstream <version>
|
||||
git tag -s v<version> -m 'v<version>'
|
||||
git push upstream v<version>
|
||||
```
|
||||
|
|
169
docs/services.md
169
docs/services.md
|
@ -10,7 +10,7 @@ The `bitcore-node.json` file describes which services will load for a node:
|
|||
```json
|
||||
{
|
||||
"services": [
|
||||
"bitcoind", "db", "address", "insight-api"
|
||||
"bitcoind", "web"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
@ -37,31 +37,21 @@ If, instead, you would like to run a custom node, you can include services by in
|
|||
var bitcore = require('bitcore-node');
|
||||
|
||||
//Services
|
||||
var Address = bitcore.services.Address;
|
||||
var Bitcoin = bitcore.services.Bitcoin;
|
||||
var DB = bitcore.services.DB;
|
||||
var Web = bitcore.services.Web;
|
||||
|
||||
var myNode = new bitcore.Node({
|
||||
datadir: '/home/user/.bitcore',
|
||||
network: {
|
||||
name: 'livenet'
|
||||
},
|
||||
"services": [
|
||||
{
|
||||
name: "address",
|
||||
module: Address,
|
||||
config: {}
|
||||
},
|
||||
network: 'regtest'
|
||||
services: [
|
||||
{
|
||||
name: 'bitcoind',
|
||||
module: Bitcoin,
|
||||
config: {}
|
||||
},
|
||||
{
|
||||
name: 'db',
|
||||
module: DB,
|
||||
config: {}
|
||||
config: {
|
||||
spawn: {
|
||||
datadir: '/home/<username>/.bitcoin',
|
||||
exec: '/home/<username>/bitcore-node/bin/zcashd'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'web',
|
||||
|
@ -77,8 +67,8 @@ var myNode = new bitcore.Node({
|
|||
Now that you've loaded your services you can access them via `myNode.services.<service-name>.<method-name>`. For example if you wanted to check the balance of an address, you could access the address service like so.
|
||||
|
||||
```js
|
||||
myNode.services.address.getBalance('1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v', false, function(err, total) {
|
||||
console.log(total); //Satoshi amount of this address
|
||||
myNode.services.bitcoind.getAddressBalance('1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v', false, function(err, total) {
|
||||
console.log(total.balance); //Satoshi amount of this address
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -96,140 +86,3 @@ The `package.json` for the service module can either export the `Node.Service` d
|
|||
|
||||
Please take a look at some of the existing services for implementation specifics.
|
||||
|
||||
### Adding an index
|
||||
One quite useful feature exposed to services is the ability to index arbitrary data in the blockchain. To do so we make use of leveldb, a simple key-value store. As a service we can expose a 'blockHandler' function which is called each time a new block is added or removed from the blockchain. This gives us access to every new transaction received, allowing us to index them. Let's take a look at an example where we will index the time that a transaction was confirmed.
|
||||
|
||||
```js
|
||||
//Index prefix, so that we can determine the difference between our index
|
||||
//and the indexes provided by other services
|
||||
MyService.datePrefix = new Buffer('10', 'hex');
|
||||
|
||||
MyService.minPosition = new Buffer('00000', 'hex');
|
||||
MyService.maxPosition = new Buffer('99999', 'hex');
|
||||
|
||||
//This function is automatically called when a block is added or receieved
|
||||
MyService.prototype.prototype.blockHandler = function(block, addOutput, callback) {
|
||||
|
||||
//Determine if the block is added or removed, and therefore whether we are adding
|
||||
//or deleting indexes
|
||||
var databaseAction = 'put';
|
||||
if (!addOutput) {
|
||||
databaseAction = 'del';
|
||||
}
|
||||
|
||||
//An array of all leveldb operations we will be committing
|
||||
var operations = [];
|
||||
|
||||
//Timestamp of the current block
|
||||
var blocktime = new Buffer(4);
|
||||
blocktime.writeUInt32BE(block.header.time);
|
||||
|
||||
for (var i = 0; i < block.transactions.length; i++) {
|
||||
var transaction = block.transactions[i];
|
||||
var txid = new Buffer(transaction.id, 'hex');
|
||||
var position = new Buffer(('0000' + i).slice(-5), 'hex');
|
||||
|
||||
//To be able to query this txid by the block date we create an index, leading with the prefix we
|
||||
//defined earlier, the the current blocktime, and finally a differentiator, in this case the index
|
||||
//of this transaction in the block's transaction list
|
||||
var indexOperation = {
|
||||
type: databaseAction,
|
||||
key: Buffer.concat([this.datePrefix, blockTime, position]),
|
||||
value: txid
|
||||
};
|
||||
|
||||
//Now we push this index into our list of operations that should be performed
|
||||
operations.push(indexOperation);
|
||||
}
|
||||
|
||||
//Send the list of db operations back so they can be performed
|
||||
setImmediate(function() {
|
||||
callback(null, operations);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Retrieving data using an index
|
||||
With our block handler code every transaction in the blockchain will now be indexed. However, if we want to query this data we need to add a method to our service to expose it.
|
||||
|
||||
```js
|
||||
|
||||
MyService.prototype.getTransactionIdsByDate = function(startDateBuffer, endDateBuffer, callback) {
|
||||
|
||||
var error;
|
||||
var transactions = [];
|
||||
|
||||
//Read data from leveldb which is between our startDate and endDate
|
||||
var stream = this.node.services.db.store.createReadStream({
|
||||
gte: Buffer.concat([
|
||||
MyService.datePrefix,
|
||||
startDateBuffer,
|
||||
MyService.minPosition
|
||||
]),
|
||||
lte: Buffer.concat([
|
||||
MyService.datePrefix,
|
||||
endDateBuffer,
|
||||
MyService.maxPosition
|
||||
]),
|
||||
valueEncoding: 'binary',
|
||||
keyEncoding: 'binary'
|
||||
});
|
||||
|
||||
stream.on('data', function(data) {
|
||||
transactions.push(data.value.toString('hex'));
|
||||
});
|
||||
|
||||
stream.on('error', function(streamError) {
|
||||
if (streamError) {
|
||||
error = streamError;
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('close', function() {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
callback(null, transactions);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
If you're new to leveldb and would like to better understand how createReadStream works you can find [more information here](https://github.com/Level/levelup#dbcreatereadstreamoptions).
|
||||
|
||||
### Understanding indexes
|
||||
You may notice there are several pieces to the index itself. Let's take a look at each piece to make them easier to understand.
|
||||
|
||||
#### Prefixes
|
||||
Since leveldb is just a simple key-value store we need something to differentiate which keys are part of which index. If we had two services trying to index on the same key, say a txid, they would overwrite each other and their queries would return results from the other index. By introducing a unique prefix per index type that we can prepend our indexes with prevents these collisions.
|
||||
|
||||
```js
|
||||
//A simple example of indexing the number of inputs and ouputs given a transaction id
|
||||
|
||||
/** Wrong way **/
|
||||
var index1key = new Buffer(transaction.id, 'hex');
|
||||
var index1value = transaction.inputs.length;
|
||||
|
||||
//Since this key has the same value it would just overwrite index1 when we write to the db
|
||||
var index2key = new Buffer(transaction.id, 'hex');
|
||||
var index2value = transaction.outputs.length;
|
||||
|
||||
|
||||
/** Right way **/
|
||||
var index1prefix = new Buffer('11', 'hex');
|
||||
var index2prefix = new Buffer('12', 'hex');
|
||||
|
||||
var index1key = Buffer.concat([index1prefix, new Buffer(transaction.id, 'hex')]);
|
||||
var index1value = transaction.inputs.length;
|
||||
|
||||
//Now that the keys are different, this won't overwrite the index
|
||||
var index2key = Buffer.concat([index2prefix, new Buffer(transaction.id, 'hex')]);
|
||||
var index2value = transaction.outputs.length;
|
||||
```
|
||||
|
||||
Remember that all indexes are global, so check to make sure no other services you are using make use of the same prefix you plan to use in your service. We recommend documenting which prefixes you use and that you check for collisions with popular services if you plan to release your service for others to use.
|
||||
|
||||
#### Index Key
|
||||
The index key is the value you want to query by. This value should be deterministic so that it can be removed in the case of a [re-org](https://en.bitcoin.it/wiki/Chain_Reorganization) resulting in a block removal. The value should be unique, as no two indexes can be the same value. If you need two indexes with the same key value, consider adding a deterministic differentiator, such as a position in an array, or instead storing multiple values within the same index data.
|
||||
|
||||
#### Index Data
|
||||
This is the data which is returned when you search by the index's key. This can be whatever you would like to retrieve. Try to be efficient by not storing data that is already available elsewhere, such as storing a transaction ID instead of an entire transaction.
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
# Address Service
|
||||
The address service builds on the [Bitcoin Service](bitcoind.md) and the [Database Service](db.md) to add additional functionality for querying and subscribing to information based on bitcoin addresses. This will typically represent the core functionality for wallet applications.
|
||||
|
||||
## API Documentation
|
||||
These methods are exposed over the JSON-RPC interface and can be called directly from a node via:
|
||||
|
||||
```js
|
||||
node.services.address.<methodName>
|
||||
```
|
||||
|
||||
**Get Unspent Outputs**
|
||||
|
||||
One of the most common uses will be to retrieve unspent outputs necessary to create a transaction, here is how to get the unspent outputs for an address:
|
||||
|
||||
```js
|
||||
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
|
||||
var includeMempool = true;
|
||||
node.services.address.getUnspentOutputs(address, includeMempool, function(err, unspentOutputs) {
|
||||
// see below
|
||||
});
|
||||
```
|
||||
|
||||
The `unspentOutputs` will have the format:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW',
|
||||
txid: '9d956c5d324a1c2b12133f3242deff264a9b9f61be701311373998681b8c1769',
|
||||
outputIndex: 1,
|
||||
height: 150,
|
||||
satoshis: 1000000000,
|
||||
script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac',
|
||||
confirmations: 3
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**View Balances**
|
||||
|
||||
```js
|
||||
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
|
||||
var includeMempool = true;
|
||||
node.services.address.getBalance(address, includeMempool, function(err, balance) {
|
||||
// balance will be in satoshis
|
||||
});
|
||||
```
|
||||
|
||||
**View Address History**
|
||||
|
||||
This method will give history of an address limited by a range of block heights by using the "start" and "end" arguments. The "start" value is the more recent, and greater, block height. The "end" value is the older, and lesser, block height. This feature is most useful for synchronization as previous history can be omitted. Furthermore for large ranges of block heights, results can be paginated by using the "from" and "to" arguments.
|
||||
|
||||
```js
|
||||
var addresses = ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'];
|
||||
var options = {
|
||||
start: 345000,
|
||||
end: 344000,
|
||||
queryMempool: true
|
||||
};
|
||||
node.services.address.getAddressHistory(addresses, options, function(err, history) {
|
||||
// see below
|
||||
});
|
||||
```
|
||||
|
||||
The history format will be:
|
||||
|
||||
```js
|
||||
{
|
||||
totalCount: 1, // The total number of items within "start" and "end"
|
||||
items: [
|
||||
{
|
||||
addresses: {
|
||||
'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW': {
|
||||
inputIndexes: [],
|
||||
outputIndexes: [0]
|
||||
}
|
||||
},
|
||||
satoshis: 1000000000,
|
||||
height: 150, // the block height of the transaction
|
||||
confirmations: 3,
|
||||
timestamp: 1442948127, // in seconds
|
||||
fees: 191,
|
||||
tx: <Transaction> // the populated transaction
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**View Address Summary**
|
||||
|
||||
```js
|
||||
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
|
||||
var options = {
|
||||
noTxList: false
|
||||
};
|
||||
|
||||
node.services.address.getAddressSummary(address, options, function(err, summary) {
|
||||
// see below
|
||||
});
|
||||
```
|
||||
|
||||
The `summary` will have the format (values are in satoshis):
|
||||
|
||||
```js
|
||||
{
|
||||
totalReceived: 1000000000,
|
||||
totalSpent: 0,
|
||||
balance: 1000000000,
|
||||
unconfirmedBalance: 1000000000,
|
||||
appearances: 1, // number of transactions
|
||||
unconfirmedAppearances: 0,
|
||||
txids: [
|
||||
'3f7d13efe12e82f873f4d41f7e63bb64708fc4c942eb8c6822fa5bd7606adb00'
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Events
|
||||
For details on instantiating a bus for a node, see the [Bus Documentation](../bus.md).
|
||||
- Name: `address/transaction`, Arguments: `[address, address...]`
|
||||
- Name: `address/balance`, Arguments: `[address, address...]`
|
||||
|
||||
**Examples:**
|
||||
|
||||
```js
|
||||
bus.subscribe('address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
|
||||
bus.subscribe('address/balance', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
|
||||
|
||||
bus.on('address/transaction', function(transaction) {
|
||||
|
||||
});
|
||||
|
||||
bus.on('address/balance', function(balance) {
|
||||
|
||||
});
|
||||
```
|
|
@ -1,20 +1,120 @@
|
|||
# Bitcoin Service
|
||||
The Bitcoin Service adds a native [Node.js](https://nodejs.org) interface to [Bitcoin Core](https://github.com/bitcoin/bitcoin) for querying information about the Bitcoin blockchain. Bindings are linked to Bitcoin Core compiled as a static library.
|
||||
|
||||
The Bitcoin Service is a Node.js interface to [Bitcoin Core](https://github.com/bitcoin/bitcoin) for querying information about the bitcoin block chain. It will manage starting and stopping `bitcoind` or connect to several running `bitcoind` processes. It uses a branch of a [branch of Bitcoin Core](https://github.com/bitpay/bitcoin/tree/0.12.1-bitcore) with additional indexes for querying information about addresses and blocks. Results are cached for performance and there are several additional API methods added for common queries.
|
||||
|
||||
## Configuration
|
||||
|
||||
The default configuration will include a "spawn" configuration in "bitcoind". This defines the location of the block chain database and the location of the `bitcoind` daemon executable. The below configuration points to a local clone of `bitcoin`, and will start `bitcoind` automatically with your Node.js application.
|
||||
|
||||
```json
|
||||
"servicesConfig": {
|
||||
"bitcoind": {
|
||||
"spawn": {
|
||||
"datadir": "/home/bitcore/.bitcoin",
|
||||
"exec": "/home/bitcore/bitcoin/src/bitcoind"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It's also possible to connect to separately managed `bitcoind` processes with round-robin quering, for example:
|
||||
|
||||
```json
|
||||
"servicesConfig": {
|
||||
"bitcoind": {
|
||||
"connect": [
|
||||
{
|
||||
"rpchost": "127.0.0.1",
|
||||
"rpcport": 30521,
|
||||
"rpcuser": "bitcoin",
|
||||
"rpcpassword": "local321",
|
||||
"zmqpubrawtx": "tcp://127.0.0.1:30611"
|
||||
},
|
||||
{
|
||||
"rpchost": "127.0.0.1",
|
||||
"rpcport": 30522,
|
||||
"rpcuser": "bitcoin",
|
||||
"rpcpassword": "local321",
|
||||
"zmqpubrawtx": "tcp://127.0.0.1:30622"
|
||||
},
|
||||
{
|
||||
"rpchost": "127.0.0.1",
|
||||
"rpcport": 30523,
|
||||
"rpcuser": "bitcoin",
|
||||
"rpcpassword": "local321",
|
||||
"zmqpubrawtx": "tcp://127.0.0.1:30633"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: For detailed example configuration see [`regtest/cluster.js`](regtest/cluster.js)
|
||||
|
||||
|
||||
## API Documentation
|
||||
These methods are currently only available via directly interfacing with a node:
|
||||
Methods are available by directly interfacing with the service:
|
||||
|
||||
```js
|
||||
node.services.bitcoind.<methodName>
|
||||
```
|
||||
|
||||
### Chain
|
||||
|
||||
**Getting Latest Blocks**
|
||||
|
||||
```js
|
||||
// gives the block hashes sorted from low to high within a range of timestamps
|
||||
var high = 1460393372; // Mon Apr 11 2016 12:49:25 GMT-0400 (EDT)
|
||||
var low = 1460306965; // Mon Apr 10 2016 12:49:25 GMT-0400 (EDT)
|
||||
node.services.bitcoind.getBlockHashesByTimestamp(high, low, function(err, blockHashes) {
|
||||
//...
|
||||
});
|
||||
|
||||
// get the current tip of the chain
|
||||
node.services.bitcoind.getBestBlockHash(function(err, blockHash) {
|
||||
//...
|
||||
})
|
||||
```
|
||||
|
||||
**Getting Synchronization and Node Status**
|
||||
|
||||
```js
|
||||
// gives a boolean if the daemon is fully synced (not the initial block download)
|
||||
node.services.bitcoind.isSynced(function(err, synced) {
|
||||
//...
|
||||
})
|
||||
|
||||
// gives the current estimate of blockchain download as a percentage
|
||||
node.services.bitcoind.syncPercentage(function(err, percent) {
|
||||
//...
|
||||
});
|
||||
|
||||
// gives information about the chain including total number of blocks
|
||||
node.services.bitcoind.getInfo(function(err, info) {
|
||||
//...
|
||||
});
|
||||
```
|
||||
|
||||
**Generate Blocks**
|
||||
|
||||
```js
|
||||
// will generate a block for the "regtest" network (development purposes)
|
||||
var numberOfBlocks = 10;
|
||||
node.services.bitcoind.generateBlock(numberOfBlocks, function(err, blockHashes) {
|
||||
//...
|
||||
});
|
||||
```
|
||||
|
||||
### Blocks and Transactions
|
||||
|
||||
**Getting Block Information**
|
||||
|
||||
It's possible to query blocks by both block hash and by height. Blocks are given as Node.js buffers and can be parsed via Bitcore:
|
||||
It's possible to query blocks by both block hash and by height. Blocks are given as Node.js Buffers and can be parsed via Bitcore:
|
||||
|
||||
```js
|
||||
var blockHeight = 0;
|
||||
node.services.bitcoind.getBlock(blockHeight, function(err, blockBuffer) {
|
||||
node.services.bitcoind.getRawBlock(blockHeight, function(err, blockBuffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -22,36 +122,43 @@ node.services.bitcoind.getBlock(blockHeight, function(err, blockBuffer) {
|
|||
console.log(block);
|
||||
};
|
||||
|
||||
// check if the block is part of the main chain
|
||||
var mainChain = node.services.bitcoind.isMainChain(block.hash);
|
||||
console.log(mainChain);
|
||||
// get a bitcore object of the block (as above)
|
||||
node.services.bitcoind.getBlock(blockHash, function(err, block) {
|
||||
//...
|
||||
};
|
||||
|
||||
// get only the block index (including chain work and previous hash)
|
||||
var blockIndex = node.services.bitcoind.getBlockIndex(blockHeight);
|
||||
console.log(blockIndex);
|
||||
// get only the block header and index (including chain work, height, and previous hash)
|
||||
node.services.bitcoind.getBlockHeader(blockHeight, function(err, blockHeader) {
|
||||
//...
|
||||
});
|
||||
|
||||
// get the block with a list of txids
|
||||
node.services.bitcoind.getBlockOverview(blockHash, function(err, blockOverview) {
|
||||
//...
|
||||
};
|
||||
```
|
||||
|
||||
**Retrieving and Sending Transactions**
|
||||
|
||||
Get a transaction asynchronously by reading it from disk, with an argument to optionally not include the mempool:
|
||||
Get a transaction asynchronously by reading it from disk:
|
||||
|
||||
```js
|
||||
var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
|
||||
var queryMempool = true;
|
||||
node.services.bitcoind.getTransaction(txid, queryMempool, function(err, transactionBuffer) {
|
||||
node.services.bitcoind.getRawTransaction(txid, function(err, transactionBuffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var transaction = bitcore.Transaction().fromBuffer(transactionBuffer);
|
||||
});
|
||||
|
||||
// get a bitcore object of the transaction (as above)
|
||||
node.services.bitcoind.getTransaction(txid, function(err, transaction) {
|
||||
//...
|
||||
});
|
||||
|
||||
// also retrieve the block timestamp and height
|
||||
node.services.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, info) {
|
||||
console.log(info.blockHash);
|
||||
console.log(info.height);
|
||||
console.log(info.timestamp); // in seconds
|
||||
var transaction = bitcore.Transaction().fromBuffer(transactionBuffer);
|
||||
// retrieve the transaction with input values, fees, spent and block info
|
||||
node.services.bitcoind.getDetailedTransaction(txid, function(err, transaction) {
|
||||
//...
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -59,71 +166,164 @@ Send a transaction to the network:
|
|||
|
||||
```js
|
||||
var numberOfBlocks = 3;
|
||||
var feesPerKilobyte = node.services.bitcoind.estimateFee(numberOfBlocks); // in satoshis
|
||||
node.services.bitcoind.estimateFee(numberOfBlocks, function(err, feesPerKilobyte) {
|
||||
//...
|
||||
});
|
||||
|
||||
try {
|
||||
node.services.bitcoind.sendTransaction(transaction.serialize());
|
||||
} catch(err) {
|
||||
// handle error
|
||||
node.services.bitcoind.sendTransaction(transaction.serialize(), function(err, hash) {
|
||||
//...
|
||||
});
|
||||
```
|
||||
|
||||
### Addresses
|
||||
|
||||
**Get Unspent Outputs**
|
||||
|
||||
One of the most common uses will be to retrieve unspent outputs necessary to create a transaction, here is how to get the unspent outputs for an address:
|
||||
|
||||
```js
|
||||
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
|
||||
node.services.bitcoind.getAddressUnspentOutputs(address, options, function(err, unspentOutputs) {
|
||||
// see below
|
||||
});
|
||||
```
|
||||
|
||||
The `unspentOutputs` will have the format:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW',
|
||||
txid: '9d956c5d324a1c2b12133f3242deff264a9b9f61be701311373998681b8c1769',
|
||||
outputIndex: 1,
|
||||
height: 150,
|
||||
satoshis: 1000000000,
|
||||
script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac',
|
||||
confirmations: 3
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**View Balances**
|
||||
|
||||
```js
|
||||
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
|
||||
node.services.bitcoind.getAddressBalance(address, options, function(err, balance) {
|
||||
// balance will be in satoshis with "received" and "balance"
|
||||
});
|
||||
```
|
||||
|
||||
**View Address History**
|
||||
|
||||
This method will give history of an address limited by a range of block heights by using the "start" and "end" arguments. The "start" value is the more recent, and greater, block height. The "end" value is the older, and lesser, block height. This feature is most useful for synchronization as previous history can be omitted. Furthermore for large ranges of block heights, results can be paginated by using the "from" and "to" arguments.
|
||||
|
||||
```js
|
||||
var addresses = ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'];
|
||||
var options = {
|
||||
start: 345000,
|
||||
end: 344000,
|
||||
queryMempool: true
|
||||
};
|
||||
node.services.bitcoind.getAddressHistory(addresses, options, function(err, history) {
|
||||
// see below
|
||||
});
|
||||
```
|
||||
|
||||
The history format will be:
|
||||
|
||||
```js
|
||||
{
|
||||
totalCount: 1, // The total number of items within "start" and "end"
|
||||
items: [
|
||||
{
|
||||
addresses: {
|
||||
'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW': {
|
||||
inputIndexes: [],
|
||||
outputIndexes: [0]
|
||||
}
|
||||
},
|
||||
satoshis: 1000000000,
|
||||
tx: <detailed_transaction> // the same format as getDetailedTransaction
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Get all of the transactions in the mempool:
|
||||
**View Address Summary**
|
||||
|
||||
```js
|
||||
var mempool = node.services.bitcoind.getMempoolTransactions();
|
||||
var transactions = [];
|
||||
for (var i = 0; i < mempool.length; i++) {
|
||||
transactions.push(bitcore.Transaction().fromBuffer(transactions[i]));
|
||||
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
|
||||
var options = {
|
||||
noTxList: false
|
||||
};
|
||||
|
||||
node.services.bitcoind.getAddressSummary(address, options, function(err, summary) {
|
||||
// see below
|
||||
});
|
||||
```
|
||||
|
||||
The `summary` will have the format (values are in satoshis):
|
||||
|
||||
```js
|
||||
{
|
||||
totalReceived: 1000000000,
|
||||
totalSpent: 0,
|
||||
balance: 1000000000,
|
||||
unconfirmedBalance: 1000000000,
|
||||
appearances: 1,
|
||||
unconfirmedAppearances: 0,
|
||||
txids: [
|
||||
'3f7d13efe12e82f873f4d41f7e63bb64708fc4c942eb8c6822fa5bd7606adb00'
|
||||
]
|
||||
}
|
||||
```
|
||||
**Notes**:
|
||||
- `totalReceived` does not exclude change *(the amount of satoshis originating from the same address)*
|
||||
- `unconfirmedBalance` is the delta that the unconfirmed transactions have on the total balance *(can be both positive and negative)*
|
||||
- `unconfirmedAppearances` is the total number of unconfirmed transactions
|
||||
- `appearances` is the total confirmed transactions
|
||||
- `txids` Are sorted in block order with the most recent at the beginning. A maximum of 1000 *(default)* will be returned, the `from` and `to` options can be used to get further values.
|
||||
|
||||
Determine if an output is spent (excluding the mempool):
|
||||
|
||||
```js
|
||||
var spent = node.services.bitcoind.isSpent(txid, outputIndex);
|
||||
console.log(spent);
|
||||
```
|
||||
|
||||
**Miscellaneous**
|
||||
- `bitcoind.start(callback)` - Start the JavaScript Bitcoin node, the callback is called when the daemon is ready.
|
||||
- `bitcoind.getInfo()` - Basic information about the chain including total number of blocks.
|
||||
- `bitcoind.isSynced()` - Returns a boolean if the daemon is fully synced (not the initial block download)
|
||||
- `bitcoind.syncPercentage()` - Returns the current estimate of blockchain download as a percentage.
|
||||
- `bitcoind.stop(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.
|
||||
|
||||
## Events
|
||||
The Bitcoin Service doesn't expose any events via the Bus, however there are a few events that can be directly registered:
|
||||
The Bitcoin Service exposes two events via the Bus, and there are a few events that can be directly registered:
|
||||
|
||||
```js
|
||||
node.services.bitcoind.on('tip', function(blockHash) {
|
||||
// a new block tip has been added
|
||||
// a new block tip has been added, if there is a rapid update (with a second) this will not emit every tip update
|
||||
});
|
||||
|
||||
node.services.bitcoind.on('tx', function(txInfo) {
|
||||
node.services.bitcoind.on('tx', function(transactionBuffer) {
|
||||
// a new transaction has entered the mempool
|
||||
});
|
||||
|
||||
node.services.bitcoind.on('txleave', function(txLeaveInfo) {
|
||||
// a new transaction has left the mempool
|
||||
node.services.bitcoind.on('block', function(blockHash) {
|
||||
// a new block has been added
|
||||
});
|
||||
```
|
||||
|
||||
The `txInfo` object will have the format:
|
||||
For details on instantiating a bus for a node, see the [Bus Documentation](../bus.md).
|
||||
- Name: `bitcoind/rawtransaction`
|
||||
- Name: `bitcoind/hashblock`
|
||||
- Name: `bitcoind/addresstxid`, Arguments: [address, address...]
|
||||
|
||||
**Examples:**
|
||||
|
||||
```js
|
||||
{
|
||||
buffer: <Buffer...>,
|
||||
mempool: true, // will currently always be true
|
||||
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
|
||||
}
|
||||
```
|
||||
bus.subscribe('bitcoind/rawtransaction');
|
||||
bus.subscribe('bitcoind/hashblock');
|
||||
bus.subscribe('bitcoind/addresstxid', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
|
||||
|
||||
The `txLeaveInfo` object will have the format:
|
||||
bus.on('bitcoind/rawtransaction', function(transactionHex) {
|
||||
//...
|
||||
});
|
||||
|
||||
```js
|
||||
{
|
||||
buffer: <Buffer...>,
|
||||
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
|
||||
}
|
||||
bus.on('bitcoind/hashblock', function(blockhashHex) {
|
||||
//...
|
||||
});
|
||||
|
||||
bus.on('bitcoind/addresstxid', function(data) {
|
||||
// data.address;
|
||||
// data.txid;
|
||||
});
|
||||
```
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
# Database Service
|
||||
This service synchronizes a leveldb database with the [Bitcoin Service](bitcoind.md) block chain by connecting and disconnecting blocks to build new indexes that can be queried. Other services can extend the data that is indexed by implementing a `blockHandler` method, similar to the built-in [Address Service](address.md).
|
||||
|
||||
## How to Reindex
|
||||
|
||||
If you need to be able to recreate the database from historical transactions in blocks:
|
||||
- Shutdown your node
|
||||
- Remove the `bitcore-node.db` directory in the data directory (e.g. `~/.bitcore/bitcore-node.db`)
|
||||
- Start your node again
|
||||
|
||||
The database will then ask bitcoind for all the blocks again and recreate the database. This is sometimes required during upgrading as the format of the keys and values has changed. For "livenet" this can take half a day or more, for "testnet" this can take around an hour.
|
||||
|
||||
## Adding Indexes
|
||||
For a service to include additional block data, it can implement a `blockHandler` method that will be run to when there are new blocks added or removed.
|
||||
|
||||
```js
|
||||
CustomService.prototype.blockHandler = function(block, add, callback) {
|
||||
var transactions = block.transactions;
|
||||
var operations = [];
|
||||
operations.push({
|
||||
type: add ? 'put' : 'del',
|
||||
key: 'key',
|
||||
value: 'value'
|
||||
});
|
||||
callback(null, operations);
|
||||
};
|
||||
```
|
||||
|
||||
Take a look at the Address Service implementation for more details about how to encode the key, value for the best efficiency and ways to format the keys for streaming reads.
|
||||
|
||||
## API Documentation
|
||||
These methods are exposed over the JSON-RPC interface and can be called directly from a node via:
|
||||
|
||||
```js
|
||||
node.services.db.<methodName>
|
||||
```
|
||||
|
||||
**Query Blocks by Date**
|
||||
|
||||
One of the additional indexes created by the Database Service is querying for blocks by ranges of dates:
|
||||
|
||||
```js
|
||||
var newest = 1441914000; // Notice time is in seconds not milliseconds
|
||||
var oldest = 1441911000;
|
||||
|
||||
node.services.db.getBlockHashesByTimestamp(newest, oldest, function(err, hashes) {
|
||||
// hashes will be an array of block hashes
|
||||
});
|
||||
```
|
||||
|
||||
**Working with Blocks and Transactions as Bitcore Instances**
|
||||
|
||||
```js
|
||||
|
||||
var txid = 'c349b124b820fe6e32136c30e99f6c4f115fce4d750838edf0c46d3cb4d7281e';
|
||||
var includeMempool = true;
|
||||
node.services.db.getTransaction(txid, includeMempool, function(err, transaction) {
|
||||
console.log(transaction.toObject());
|
||||
});
|
||||
|
||||
var txid = 'c349b124b820fe6e32136c30e99f6c4f115fce4d750838edf0c46d3cb4d7281e';
|
||||
var includeMempool = true;
|
||||
node.services.db.getTransactionWithBlockInfo(txid, includeMempool, function(err, transaction) {
|
||||
console.log(transaction.toObject());
|
||||
console.log(transaction.__blockHash);
|
||||
console.log(transaction.__height);
|
||||
console.log(transaction.__timestamp);
|
||||
});
|
||||
|
||||
var blockHash = '00000000d17332a156a807b25bc5a2e041d2c730628ceb77e75841056082a2c2';
|
||||
node.services.db.getBlock(blockHash, function(err, block) {
|
||||
console.log(block.toObject());
|
||||
});
|
||||
|
||||
// contruct a transaction
|
||||
var transaction = bitcore.Transaction(<serializedString>);
|
||||
|
||||
node.services.db.sendTransaction(transaction, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// otherwise the transaction has been sent
|
||||
});
|
||||
```
|
||||
|
||||
## Events
|
||||
For details on instantiating a bus for a node, see the [Bus Documentation](../bus.md).
|
||||
- Name: `db/transaction`
|
||||
- Name: `db/block`
|
||||
|
||||
**Examples:**
|
||||
|
||||
```js
|
||||
bus.subscribe('db/transaction');
|
||||
bus.subscribe('db/block');
|
||||
|
||||
bus.on('db/block', function(blockHash) {
|
||||
// blockHash will be a hex string of the block hash
|
||||
});
|
||||
|
||||
bus.on('db/transaction', function(txInfo) {
|
||||
// see below
|
||||
});
|
||||
```
|
||||
|
||||
The `txInfo` object will have the format:
|
||||
|
||||
```js
|
||||
{
|
||||
rejected: true, // If the transaction was rejected into the mempool
|
||||
tx: <Transaction> // a Bitcore Transaction instance
|
||||
}
|
||||
```
|
|
@ -1,52 +0,0 @@
|
|||
# Development & Testing
|
||||
To run all of the JavaScript tests:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
To run tests against the bindings, as defined in `bindings.gyp` the regtest feature of Bitcoin Core is used, and to enable this feature we currently need to build with the wallet enabled _(not a part of the regular build)_. To do this, export an environment variable and recompile:
|
||||
|
||||
```bash
|
||||
export BITCORENODE_ENV=test
|
||||
npm run build
|
||||
```
|
||||
|
||||
If you do not already have mocha installed:
|
||||
|
||||
```bash
|
||||
npm install mocha -g
|
||||
```
|
||||
|
||||
To run the integration tests:
|
||||
|
||||
```bash
|
||||
mocha -R spec integration/regtest.js
|
||||
```
|
||||
|
||||
If any changes have been made to the bindings in the "src" directory, manually compile the Node.js bindings, as defined in `bindings.gyp`, you can run (-d for debug):
|
||||
|
||||
```bash
|
||||
$ node-gyp -d rebuild
|
||||
```
|
||||
|
||||
Note: `node-gyp` can be installed with `npm install node-gyp -g`
|
||||
|
||||
To be able to debug you'll need to have `gdb` and `node` compiled for debugging with gdb using `--gdb` (sometimes called node_g), and you can then run:
|
||||
|
||||
```bash
|
||||
$ gdb --args node examples/node.js
|
||||
```
|
||||
|
||||
To run mocha from within gdb (notice `_mocha` and not `mocha` so that the tests run in the same process):
|
||||
|
||||
```bash
|
||||
$ gdb --args node /path/to/_mocha -R spec integration/regtest.js
|
||||
```
|
||||
|
||||
To run the benchmarks:
|
||||
|
||||
```bash
|
||||
$ cd benchmarks
|
||||
$ node index.js
|
||||
```
|
|
@ -0,0 +1,77 @@
|
|||
# Upgrade Notes
|
||||
|
||||
## From Bitcore 3.0.0 to 4.0.0
|
||||
|
||||
`bitcore-node@2.1.1` to `bitcore-node@3.0.0`
|
||||
|
||||
This major upgrade includes changes to indexes, API methods and services. Please review below details before upgrading.
|
||||
|
||||
### Indexes
|
||||
|
||||
Indexes include *more information* and are now also *faster*. Because of this a **reindex will be necessary** when upgrading as the address and database indexes are now a part of bitcoind with three new `bitcoin.conf` options:
|
||||
- `-addressindex`
|
||||
- `-timestampindex`
|
||||
- `-spentindex`
|
||||
|
||||
To start reindexing add `reindex=1` during the **first startup only**.
|
||||
|
||||
### Configuration Options
|
||||
|
||||
- The `bitcoin.conf` file in will need to be updated to include additional indexes *(see below)*.
|
||||
- The `datadir` option is now a part of `bitcoind` spawn configuration, and there is a new option to connect to multiple bitcoind processes (Please see [Bitcoin Service Docs](services/bitcoind.md) for more details). The services `db` and `address` are now a part of the `bitcoind` service. Here is how to update `bitcore-node.json` configuration options:
|
||||
|
||||
**Before**:
|
||||
```json
|
||||
{
|
||||
"datadir": "/home/<username>/.bitcoin",
|
||||
"network": "livenet",
|
||||
"port": 3001,
|
||||
"services": [
|
||||
"address",
|
||||
"bitcoind",
|
||||
"db",
|
||||
"web"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**After**:
|
||||
```json
|
||||
{
|
||||
"network": "livenet",
|
||||
"port": 3001,
|
||||
"services": [
|
||||
"bitcoind",
|
||||
"web"
|
||||
],
|
||||
"servicesConfig": {
|
||||
"bitcoind": {
|
||||
"spawn": {
|
||||
"datadir": "/home/<username>/.bitcoin",
|
||||
"exec": "/home/<username>/bitcore-node/bin/zcashd"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It will also be necessary to update `bitcoin.conf` settings, to include these fields:
|
||||
```
|
||||
server=1
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
zmqpubrawtx=tcp://127.0.0.1:<port>
|
||||
zmqpubhashblock=tcp://127.0.0.1:<port>
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=<user>
|
||||
rpcpassword=<password>
|
||||
```
|
||||
|
||||
**Important**: Once changes have been made you'll also need to add the `reindex=1` option **only for the first startup** to regenerate the indexes. Once this is complete you should be able to remove the `bitcore-node.db` directory with the old indexes.
|
||||
|
||||
### API and Service Changes
|
||||
- Many API methods that were a part of the `db` and `address` services are now a part of the `bitcoind` service. Please see [Bitcoin Service Docs](services/bitcoind.md) for more details.
|
||||
- The `db` and `address` services are deprecated, most of the functionality still exists. Any services that were extending indexes with the `db` service, will need to manage chain state itself, or build the indexes within `bitcoind`.
|
|
@ -1,393 +0,0 @@
|
|||
diff --git a/config_me.sh b/config_me.sh
|
||||
new file mode 100644
|
||||
index 0000000..19e9a1b
|
||||
--- /dev/null
|
||||
+++ b/config_me.sh
|
||||
@@ -0,0 +1 @@
|
||||
+./configure --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --enable-debug --disable-wallet --without-utils
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index 5debd21..3bfc59e 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -119,6 +119,12 @@ AC_ARG_ENABLE([reduce-exports],
|
||||
[use_reduce_exports=$enableval],
|
||||
[use_reduce_exports=no])
|
||||
|
||||
+AC_ARG_ENABLE([daemonlib],
|
||||
+ [AS_HELP_STRING([--enable-daemonlib],
|
||||
+ [compile all of bitcoind as a library (default is no)])],
|
||||
+ [use_daemonlib=$enableval],
|
||||
+ [use_daemonlib=no])
|
||||
+
|
||||
AC_ARG_ENABLE([ccache],
|
||||
[AS_HELP_STRING([--enable-ccache],
|
||||
[use ccache for building (default is yes if ccache is found)])],
|
||||
@@ -402,6 +408,9 @@ fi
|
||||
if test x$use_hardening != xno; then
|
||||
AX_CHECK_COMPILE_FLAG([-Wstack-protector],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -Wstack-protector"])
|
||||
AX_CHECK_COMPILE_FLAG([-fstack-protector-all],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-protector-all"])
|
||||
+ if test x$use_daemonlib = xno; then
|
||||
+ AX_CHECK_COMPILE_FLAG([-fPIE],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fPIE"])
|
||||
+ fi
|
||||
|
||||
AX_CHECK_PREPROC_FLAG([-D_FORTIFY_SOURCE=2],[
|
||||
AX_CHECK_PREPROC_FLAG([-U_FORTIFY_SOURCE],[
|
||||
@@ -415,7 +424,7 @@ if test x$use_hardening != xno; then
|
||||
AX_CHECK_LINK_FLAG([[-Wl,-z,relro]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,relro"])
|
||||
AX_CHECK_LINK_FLAG([[-Wl,-z,now]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,now"])
|
||||
|
||||
- if test x$TARGET_OS != xwindows; then
|
||||
+ if test x$TARGET_OS != xwindows -a x$use_daemonlib = xno; then
|
||||
# All windows code is PIC, forcing it on just adds useless compile warnings
|
||||
AX_CHECK_COMPILE_FLAG([-fPIE],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fPIE"])
|
||||
AX_CHECK_LINK_FLAG([[-pie]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -pie"])
|
||||
@@ -433,6 +442,17 @@ if test x$use_hardening != xno; then
|
||||
OBJCXXFLAGS="$CXXFLAGS"
|
||||
fi
|
||||
|
||||
+AC_DEFINE([ENABLE_DAEMONLIB],[0],[Enable daemonlib.])
|
||||
+AM_CONDITIONAL([ENABLE_DAEMONLIB],[false])
|
||||
+if test x$use_daemonlib != xno; then
|
||||
+ AX_CHECK_COMPILE_FLAG([-fPIC],[DAEMONLIB_CXXFLAGS="$DAEMONLIB_CXXFLAGS -fPIC"])
|
||||
+ AC_DEFINE([ENABLE_DAEMONLIB],[1],[Enable daemonlib.])
|
||||
+ AM_CONDITIONAL([ENABLE_DAEMONLIB],[true])
|
||||
+ CXXFLAGS="$CXXFLAGS $DAEMONLIB_CXXFLAGS"
|
||||
+ CPPFLAGS="$CPPFLAGS $DAEMONLIB_CPPFLAGS"
|
||||
+ OBJCXXFLAGS="$CXXFLAGS"
|
||||
+fi
|
||||
+
|
||||
dnl this flag screws up non-darwin gcc even when the check fails. special-case it.
|
||||
if test x$TARGET_OS = xdarwin; then
|
||||
AX_CHECK_LINK_FLAG([[-Wl,-dead_strip]], [LDFLAGS="$LDFLAGS -Wl,-dead_strip"])
|
||||
@@ -483,11 +503,18 @@ AC_LINK_IFELSE([AC_LANG_SOURCE([
|
||||
]
|
||||
)
|
||||
|
||||
-if test x$use_reduce_exports = xyes; then
|
||||
+if test x$use_reduce_exports = xyes -a x$use_daemonlib = xno; then
|
||||
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[RE_CXXFLAGS="-fvisibility=hidden"],
|
||||
[AC_MSG_ERROR([Cannot set default symbol visibility. Use --disable-reduce-exports.])])
|
||||
fi
|
||||
|
||||
+AC_MSG_CHECKING([whether to compile as daemonlib])
|
||||
+if test x$use_daemonlib != xno; then
|
||||
+ AC_MSG_RESULT([yes])
|
||||
+else
|
||||
+ AC_MSG_RESULT([no])
|
||||
+fi
|
||||
+
|
||||
LEVELDB_CPPFLAGS=
|
||||
LIBLEVELDB=
|
||||
LIBMEMENV=
|
||||
diff --git a/depends/hosts/linux.mk b/depends/hosts/linux.mk
|
||||
index b13a0f1..0513394 100644
|
||||
--- a/depends/hosts/linux.mk
|
||||
+++ b/depends/hosts/linux.mk
|
||||
@@ -10,15 +10,15 @@ linux_debug_CXXFLAGS=$(linux_debug_CFLAGS)
|
||||
linux_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
|
||||
|
||||
ifeq (86,$(findstring 86,$(build_arch)))
|
||||
-i686_linux_CC=gcc -m32
|
||||
-i686_linux_CXX=g++ -m32
|
||||
+i686_linux_CC=${CC} -m32
|
||||
+i686_linux_CXX=${CXX} -m32
|
||||
i686_linux_AR=ar
|
||||
i686_linux_RANLIB=ranlib
|
||||
i686_linux_NM=nm
|
||||
i686_linux_STRIP=strip
|
||||
|
||||
-x86_64_linux_CC=gcc -m64
|
||||
-x86_64_linux_CXX=g++ -m64
|
||||
+x86_64_linux_CC=${CC} -m64
|
||||
+x86_64_linux_CXX=${CXX} -m64
|
||||
x86_64_linux_AR=ar
|
||||
x86_64_linux_RANLIB=ranlib
|
||||
x86_64_linux_NM=nm
|
||||
diff --git a/depends/packages/bdb.mk b/depends/packages/bdb.mk
|
||||
index 68841af..65a105b 100644
|
||||
--- a/depends/packages/bdb.mk
|
||||
+++ b/depends/packages/bdb.mk
|
||||
@@ -9,6 +9,7 @@ define $(package)_set_vars
|
||||
$(package)_config_opts=--disable-shared --enable-cxx --disable-replication
|
||||
$(package)_config_opts_mingw32=--enable-mingw
|
||||
$(package)_config_opts_linux=--with-pic
|
||||
+$(package)_cxxflags_darwin=-stdlib=libc++
|
||||
endef
|
||||
|
||||
define $(package)_preprocess_cmds
|
||||
diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk
|
||||
index e7aa48d..df0f7ae 100644
|
||||
--- a/depends/packages/boost.mk
|
||||
+++ b/depends/packages/boost.mk
|
||||
@@ -1,9 +1,8 @@
|
||||
package=boost
|
||||
-$(package)_version=1_55_0
|
||||
-$(package)_download_path=http://sourceforge.net/projects/boost/files/boost/1.55.0
|
||||
+$(package)_version=1_57_0
|
||||
+$(package)_download_path=http://sourceforge.net/projects/boost/files/boost/1.57.0
|
||||
$(package)_file_name=$(package)_$($(package)_version).tar.bz2
|
||||
-$(package)_sha256_hash=fff00023dd79486d444c8e29922f4072e1d451fc5a4d2b6075852ead7f2b7b52
|
||||
-$(package)_patches=darwin_boost_atomic-1.patch darwin_boost_atomic-2.patch gcc_5_no_cxx11.patch
|
||||
+$(package)_sha256_hash=910c8c022a33ccec7f088bd65d4f14b466588dda94ba2124e78b8c57db264967
|
||||
|
||||
define $(package)_set_vars
|
||||
$(package)_config_opts_release=variant=release
|
||||
@@ -11,7 +10,7 @@ $(package)_config_opts_debug=variant=debug
|
||||
$(package)_config_opts=--layout=tagged --build-type=complete --user-config=user-config.jam
|
||||
$(package)_config_opts+=threading=multi link=static -sNO_BZIP2=1 -sNO_ZLIB=1
|
||||
$(package)_config_opts_linux=threadapi=pthread runtime-link=shared
|
||||
-$(package)_config_opts_darwin=--toolset=darwin-4.2.1 runtime-link=shared
|
||||
+$(package)_config_opts_darwin=--toolset=clang runtime-link=shared
|
||||
$(package)_config_opts_mingw32=binary-format=pe target-os=windows threadapi=win32 runtime-link=static
|
||||
$(package)_config_opts_x86_64_mingw32=address-model=64
|
||||
$(package)_config_opts_i686_mingw32=address-model=32
|
||||
@@ -20,15 +19,14 @@ $(package)_toolset_$(host_os)=gcc
|
||||
$(package)_archiver_$(host_os)=$($(package)_ar)
|
||||
$(package)_toolset_darwin=darwin
|
||||
$(package)_archiver_darwin=$($(package)_libtool)
|
||||
-$(package)_config_libraries=chrono,filesystem,program_options,system,thread,test
|
||||
-$(package)_cxxflags=-fvisibility=hidden
|
||||
-$(package)_cxxflags_linux=-fPIC
|
||||
+$(package)_config_libraries=chrono,filesystem,program_options,system,thread
|
||||
+$(package)_cxxflags=-fvisibility=default -fPIC
|
||||
+$(package)_cxxflags_darwin=-std=c++11 -stdlib=libc++
|
||||
+$(package)_linkflags=-stdlib=libc++
|
||||
endef
|
||||
|
||||
+
|
||||
define $(package)_preprocess_cmds
|
||||
- patch -p2 < $($(package)_patch_dir)/darwin_boost_atomic-1.patch && \
|
||||
- patch -p2 < $($(package)_patch_dir)/darwin_boost_atomic-2.patch && \
|
||||
- patch -p2 < $($(package)_patch_dir)/gcc_5_no_cxx11.patch && \
|
||||
echo "using $(boost_toolset_$(host_os)) : : $($(package)_cxx) : <cxxflags>\"$($(package)_cxxflags) $($(package)_cppflags)\" <linkflags>\"$($(package)_ldflags)\" <archiver>\"$(boost_archiver_$(host_os))\" <striper>\"$(host_STRIP)\" <ranlib>\"$(host_RANLIB)\" <rc>\"$(host_WINDRES)\" : ;" > user-config.jam
|
||||
endef
|
||||
|
||||
diff --git a/src/Makefile.am b/src/Makefile.am
|
||||
index 2461f82..7be6d6e 100644
|
||||
--- a/src/Makefile.am
|
||||
+++ b/src/Makefile.am
|
||||
@@ -1,6 +1,12 @@
|
||||
DIST_SUBDIRS = secp256k1
|
||||
AM_LDFLAGS = $(PTHREAD_CFLAGS) $(LIBTOOL_LDFLAGS)
|
||||
|
||||
+noinst_LTLIBRARIES =
|
||||
+libbitcoind_la_LIBADD =
|
||||
+libbitcoind_la_LDFLAGS = -no-undefined
|
||||
+STATIC_BOOST_LIBS =
|
||||
+STATIC_BDB_LIBS =
|
||||
+STATIC_EXTRA_LIBS = $(STATIC_BOOST_LIBS) $(LIBLEVELDB) $(LIBMEMENV)
|
||||
|
||||
if EMBEDDED_LEVELDB
|
||||
LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/include
|
||||
@@ -49,16 +55,16 @@ BITCOIN_INCLUDES += $(BDB_CPPFLAGS)
|
||||
EXTRA_LIBRARIES += libbitcoin_wallet.a
|
||||
endif
|
||||
|
||||
-if BUILD_BITCOIN_LIBS
|
||||
-lib_LTLIBRARIES = libbitcoinconsensus.la
|
||||
-LIBBITCOIN_CONSENSUS=libbitcoinconsensus.la
|
||||
-else
|
||||
-LIBBITCOIN_CONSENSUS=
|
||||
-endif
|
||||
-
|
||||
+LIBBITCOIN_CONSENSUS =
|
||||
bin_PROGRAMS =
|
||||
TESTS =
|
||||
|
||||
+if BUILD_BITCOIN_LIBS
|
||||
+noinst_LTLIBRARIES += libbitcoinconsensus.la
|
||||
+LIBBITCOIN_CONSENSUS += libbitcoinconsensus.la
|
||||
+endif
|
||||
+
|
||||
+if !ENABLE_DAEMONLIB
|
||||
if BUILD_BITCOIND
|
||||
bin_PROGRAMS += bitcoind
|
||||
endif
|
||||
@@ -66,6 +72,9 @@ endif
|
||||
if BUILD_BITCOIN_UTILS
|
||||
bin_PROGRAMS += bitcoin-cli bitcoin-tx
|
||||
endif
|
||||
+else
|
||||
+noinst_LTLIBRARIES += libbitcoind.la
|
||||
+endif
|
||||
|
||||
.PHONY: FORCE
|
||||
# bitcoin core #
|
||||
@@ -170,8 +179,11 @@ obj/build.h: FORCE
|
||||
@$(MKDIR_P) $(builddir)/obj
|
||||
@$(top_srcdir)/share/genbuild.sh $(abs_top_builddir)/src/obj/build.h \
|
||||
$(abs_top_srcdir)
|
||||
-libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h
|
||||
|
||||
+ARCH_PLATFORM = $(shell ../../bin/variables.sh host)
|
||||
+
|
||||
+libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h
|
||||
+clientversion.cpp: obj/build.h
|
||||
# server: shared between bitcoind and bitcoin-qt
|
||||
libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS)
|
||||
libbitcoin_server_a_SOURCES = \
|
||||
@@ -310,9 +322,18 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h
|
||||
bitcoind_SOURCES = bitcoind.cpp
|
||||
bitcoind_CPPFLAGS = $(BITCOIN_INCLUDES)
|
||||
bitcoind_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
+libbitcoind_la_SOURCES = bitcoind.cpp
|
||||
+libbitcoind_la_SOURCES += $(libbitcoin_util_a_SOURCES)
|
||||
+libbitcoind_la_SOURCES += $(libbitcoin_univalue_a_SOURCES)
|
||||
+libbitcoind_la_SOURCES += $(libbitcoin_crypto_a_SOURCES)
|
||||
+libbitcoind_la_SOURCES += $(libbitcoin_common_a_SOURCES)
|
||||
+libbitcoind_la_SOURCES += $(libbitcoin_server_a_SOURCES)
|
||||
+libbitcoind_la_SOURCES += $(crypto_libbitcoin_crypto_a_SOURCES)
|
||||
+libbitcoind_la_SOURCES += $(univalue_libbitcoin_univalue_a_SOURCES)
|
||||
|
||||
if TARGET_WINDOWS
|
||||
bitcoind_SOURCES += bitcoind-res.rc
|
||||
+libbitcoind_la_SOURCES += bitcoind-res.rc
|
||||
endif
|
||||
|
||||
bitcoind_LDADD = \
|
||||
@@ -327,10 +348,21 @@ bitcoind_LDADD = \
|
||||
|
||||
if ENABLE_WALLET
|
||||
bitcoind_LDADD += libbitcoin_wallet.a
|
||||
+STATIC_EXTRA_LIBS += $(STATIC_BDB_LIBS)
|
||||
+libbitcoind_la_SOURCES += $(libbitcoin_wallet_a_SOURCES)
|
||||
endif
|
||||
|
||||
+STATIC_BOOST_LIBS += ../depends/$(ARCH_PLATFORM)/lib/libboost_filesystem-mt.a ../depends/$(ARCH_PLATFORM)/lib/libboost_system-mt.a ../depends/$(ARCH_PLATFORM)/lib/libboost_chrono-mt.a ../depends/$(ARCH_PLATFORM)/lib/libboost_thread-mt.a ../depends/$(ARCH_PLATFORM)/lib/libboost_program_options-mt.a
|
||||
+STATIC_BDB_LIBS += ../depends/$(ARCH_PLATFORM)/lib/libdb_cxx.a
|
||||
+
|
||||
bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS)
|
||||
-#
|
||||
+libbitcoind_la_LIBADD += $(SSL_LIBS) $(LIBSECP256K1) $(CRYPTO_LIBS) $(STATIC_EXTRA_LIBS)
|
||||
+libbitcoind_la_CPPFLAGS = $(BITCOIN_INCLUDES)
|
||||
+if TARGET_DARWIN
|
||||
+libbitcoind_la_LDFLAGS += -Wl,-all_load
|
||||
+else
|
||||
+libbitcoind_la_LDFLAGS += -Wl,--whole-archive $(STATIC_EXTRA_LIBS) -Wl,--no-whole-archive
|
||||
+endif
|
||||
|
||||
# bitcoin-cli binary #
|
||||
bitcoin_cli_SOURCES = bitcoin-cli.cpp
|
||||
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
|
||||
index 6e2758a..0352a9d 100644
|
||||
--- a/src/bitcoind.cpp
|
||||
+++ b/src/bitcoind.cpp
|
||||
@@ -33,6 +33,10 @@
|
||||
|
||||
static bool fDaemon;
|
||||
|
||||
+#if ENABLE_DAEMONLIB
|
||||
+extern void WaitForShutdown(boost::thread_group* threadGroup);
|
||||
+#endif
|
||||
+
|
||||
void WaitForShutdown(boost::thread_group* threadGroup)
|
||||
{
|
||||
bool fShutdown = ShutdownRequested();
|
||||
@@ -166,6 +170,7 @@ bool AppInit(int argc, char* argv[])
|
||||
return fRet;
|
||||
}
|
||||
|
||||
+#if !ENABLE_DAEMONLIB
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
SetupEnvironment();
|
||||
@@ -175,3 +180,4 @@ int main(int argc, char* argv[])
|
||||
|
||||
return (AppInit(argc, argv) ? 0 : 1);
|
||||
}
|
||||
+#endif
|
||||
diff --git a/src/init.cpp b/src/init.cpp
|
||||
index a04e4e0..33d0bc7 100644
|
||||
--- a/src/init.cpp
|
||||
+++ b/src/init.cpp
|
||||
@@ -638,21 +638,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
umask(077);
|
||||
}
|
||||
|
||||
- // Clean shutdown on SIGTERM
|
||||
- struct sigaction sa;
|
||||
- sa.sa_handler = HandleSIGTERM;
|
||||
- sigemptyset(&sa.sa_mask);
|
||||
- sa.sa_flags = 0;
|
||||
- sigaction(SIGTERM, &sa, NULL);
|
||||
- sigaction(SIGINT, &sa, NULL);
|
||||
-
|
||||
- // Reopen debug.log on SIGHUP
|
||||
- struct sigaction sa_hup;
|
||||
- sa_hup.sa_handler = HandleSIGHUP;
|
||||
- sigemptyset(&sa_hup.sa_mask);
|
||||
- sa_hup.sa_flags = 0;
|
||||
- sigaction(SIGHUP, &sa_hup, NULL);
|
||||
-
|
||||
#if defined (__SVR4) && defined (__sun)
|
||||
// ignore SIGPIPE on Solaris
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
diff --git a/src/init.h b/src/init.h
|
||||
index dcb2b29..5ce68ba 100644
|
||||
--- a/src/init.h
|
||||
+++ b/src/init.h
|
||||
@@ -18,6 +18,11 @@ class thread_group;
|
||||
|
||||
extern CWallet* pwalletMain;
|
||||
|
||||
+#if ENABLE_DAEMONLIB
|
||||
+#include <boost/filesystem/path.hpp>
|
||||
+#include <boost/thread/mutex.hpp>
|
||||
+#endif
|
||||
+
|
||||
void StartShutdown();
|
||||
bool ShutdownRequested();
|
||||
void Shutdown();
|
||||
diff --git a/src/main.cpp b/src/main.cpp
|
||||
index fe072ec..9f677cf 100644
|
||||
--- a/src/main.cpp
|
||||
+++ b/src/main.cpp
|
||||
@@ -1105,6 +1105,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
||||
|
||||
// Store transaction in memory
|
||||
pool.addUnchecked(hash, entry, !IsInitialBlockDownload());
|
||||
+ GetNodeSignals().TxToMemPool(tx);
|
||||
}
|
||||
|
||||
SyncWithWallets(tx, NULL);
|
||||
diff --git a/src/net.cpp b/src/net.cpp
|
||||
index e4b22f9..33fe6f9 100644
|
||||
--- a/src/net.cpp
|
||||
+++ b/src/net.cpp
|
||||
@@ -432,8 +432,10 @@ void CNode::PushVersion()
|
||||
LogPrint("net", "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nBestHeight, addrMe.ToString(), addrYou.ToString(), id);
|
||||
else
|
||||
LogPrint("net", "send version message: version %d, blocks=%d, us=%s, peer=%d\n", PROTOCOL_VERSION, nBestHeight, addrMe.ToString(), id);
|
||||
+ std::vector<std::string> bitcore;
|
||||
+ bitcore.push_back("bitcore"); //the dash character is removed from the comments section
|
||||
PushMessage("version", PROTOCOL_VERSION, nLocalServices, nTime, addrYou, addrMe,
|
||||
- nLocalHostNonce, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector<string>()), nBestHeight, true);
|
||||
+ nLocalHostNonce, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, bitcore), nBestHeight, true);
|
||||
}
|
||||
|
||||
|
||||
diff --git a/src/net.h b/src/net.h
|
||||
index 17502b9..c9ae1b2 100644
|
||||
--- a/src/net.h
|
||||
+++ b/src/net.h
|
||||
@@ -99,6 +99,8 @@ struct CNodeSignals
|
||||
{
|
||||
boost::signals2::signal<int ()> GetHeight;
|
||||
boost::signals2::signal<bool (CNode*), CombinerAll> ProcessMessages;
|
||||
+ boost::signals2::signal<bool (const CTransaction&)> TxToMemPool;
|
||||
+ boost::signals2::signal<bool (const CTransaction&)> TxLeaveMemPool;
|
||||
boost::signals2::signal<bool (CNode*, bool), CombinerAll> SendMessages;
|
||||
boost::signals2::signal<void (NodeId, const CNode*)> InitializeNode;
|
||||
boost::signals2::signal<void (NodeId)> FinalizeNode;
|
||||
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
|
||||
index c3d1b60..03e265d 100644
|
||||
--- a/src/txmempool.cpp
|
||||
+++ b/src/txmempool.cpp
|
||||
@@ -133,6 +133,7 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
|
||||
if (!mapTx.count(hash))
|
||||
continue;
|
||||
const CTransaction& tx = mapTx[hash].GetTx();
|
||||
+ GetNodeSignals().TxLeaveMemPool(tx);
|
||||
if (fRecursive) {
|
||||
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
||||
std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(hash, i));
|
|
@ -1,49 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var socket = require('socket.io-client')('http://localhost:3000');
|
||||
socket.on('connect', function(){
|
||||
console.log('connected');
|
||||
});
|
||||
|
||||
socket.on('disconnect', function(){
|
||||
console.log('disconnected');
|
||||
});
|
||||
|
||||
var message = {
|
||||
method: 'getOutputs',
|
||||
params: ['2NChMRHVCxTPq9KeyvHQUSbfLaQY55Zzzp8', true]
|
||||
};
|
||||
|
||||
socket.send(message, function(response) {
|
||||
if(response.error) {
|
||||
console.log('Error', response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(response.result);
|
||||
});
|
||||
|
||||
var message2 = {
|
||||
method: 'getTransaction',
|
||||
params: ['4f793f67fc7465f14fa3a8d3727fa7d133cdb2f298234548b94a5f08b6f4103e', true]
|
||||
};
|
||||
|
||||
socket.send(message2, function(response) {
|
||||
if(response.error) {
|
||||
console.log('Error', response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(response.result);
|
||||
});
|
||||
|
||||
socket.on('transaction', function(obj) {
|
||||
console.log(JSON.stringify(obj, null, 2));
|
||||
});
|
||||
|
||||
socket.on('address/transaction', function(obj) {
|
||||
console.log(JSON.stringify(obj, null, 2));
|
||||
});
|
||||
|
||||
socket.emit('subscribe', 'transaction');
|
||||
socket.emit('subscribe', 'address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
|
16
index.js
16
index.js
|
@ -1,26 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var semver = require('semver');
|
||||
var packageData = require('./package.json');
|
||||
|
||||
function nodeVersionCheck(version, expected) {
|
||||
if (!semver.satisfies(version, expected)) {
|
||||
throw new Error('Node.js version ' + version + ' is expected to be ' + expected);
|
||||
}
|
||||
}
|
||||
nodeVersionCheck(process.versions.node, packageData.engines.node);
|
||||
|
||||
module.exports = require('./lib');
|
||||
module.exports.nodeVersionCheck = nodeVersionCheck;
|
||||
module.exports.Node = require('./lib/node');
|
||||
module.exports.Transaction = require('./lib/transaction');
|
||||
module.exports.Service = require('./lib/service');
|
||||
module.exports.errors = require('./lib/errors');
|
||||
|
||||
module.exports.services = {};
|
||||
module.exports.services.Address = require('./lib/services/address');
|
||||
module.exports.services.Bitcoin = require('./lib/services/bitcoind');
|
||||
module.exports.services.DB = require('./lib/services/db');
|
||||
module.exports.services.Web = require('./lib/services/web');
|
||||
|
||||
module.exports.scaffold = {};
|
||||
|
@ -37,3 +23,5 @@ module.exports.cli.main = require('./lib/cli/main');
|
|||
module.exports.cli.daemon = require('./lib/cli/daemon');
|
||||
module.exports.cli.bitcore = require('./lib/cli/bitcore');
|
||||
module.exports.cli.bitcored = require('./lib/cli/bitcored');
|
||||
|
||||
module.exports.lib = require('bitcore-lib-zcash');
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
server=1
|
||||
whitelist=127.0.0.1
|
||||
rpcssl=1
|
||||
rpcsslcertificatechainfile=../bitcoind.crt
|
||||
rpcsslprivatekeyfile=../bitcoind_no_pass.key
|
||||
txindex=1
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
|
@ -1,492 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// These tests require bitcore-node Bitcoin Core bindings to be compiled with
|
||||
// the environment variable BITCORENODE_ENV=test. This enables the use of regtest
|
||||
// functionality by including the wallet in the build.
|
||||
// To run the tests: $ mocha -R spec integration/regtest.js
|
||||
|
||||
var index = require('..');
|
||||
var log = index.log;
|
||||
|
||||
if (process.env.BITCORENODE_ENV !== 'test') {
|
||||
log.info('Please set the environment variable BITCORENODE_ENV=test and make sure bindings are compiled for testing');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
var chai = require('chai');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var BN = bitcore.crypto.BN;
|
||||
var async = require('async');
|
||||
var rimraf = require('rimraf');
|
||||
var bitcoind;
|
||||
|
||||
/* jshint unused: false */
|
||||
var should = chai.should();
|
||||
var assert = chai.assert;
|
||||
var sinon = require('sinon');
|
||||
var BitcoinRPC = require('bitcoind-rpc');
|
||||
var transactionData = [];
|
||||
var blockHashes = [];
|
||||
var utxos;
|
||||
var client;
|
||||
var coinbasePrivateKey;
|
||||
var privateKey = bitcore.PrivateKey();
|
||||
var destKey = bitcore.PrivateKey();
|
||||
|
||||
describe('Daemon Binding Functionality', function() {
|
||||
|
||||
before(function(done) {
|
||||
this.timeout(30000);
|
||||
|
||||
// Add the regtest network
|
||||
bitcore.Networks.remove(bitcore.Networks.testnet);
|
||||
bitcore.Networks.add({
|
||||
name: 'regtest',
|
||||
alias: 'regtest',
|
||||
pubkeyhash: 0x6f,
|
||||
privatekey: 0xef,
|
||||
scripthash: 0xc4,
|
||||
xpubkey: 0x043587cf,
|
||||
xprivkey: 0x04358394,
|
||||
networkMagic: 0xfabfb5da,
|
||||
port: 18444,
|
||||
dnsSeeds: [ ]
|
||||
});
|
||||
|
||||
var datadir = __dirname + '/data';
|
||||
|
||||
rimraf(datadir + '/regtest', function(err) {
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
bitcoind = require('../').services.Bitcoin({
|
||||
node: {
|
||||
datadir: datadir,
|
||||
network: {
|
||||
name: 'regtest'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bitcoind.on('error', function(err) {
|
||||
log.error('error="%s"', err.message);
|
||||
});
|
||||
|
||||
log.info('Waiting for Bitcoin Core to initialize...');
|
||||
|
||||
bitcoind.start(function() {
|
||||
log.info('Bitcoind started');
|
||||
|
||||
client = new BitcoinRPC({
|
||||
protocol: 'https',
|
||||
host: '127.0.0.1',
|
||||
port: 18332,
|
||||
user: 'bitcoin',
|
||||
pass: 'local321',
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
log.info('Generating 100 blocks...');
|
||||
|
||||
// Generate enough blocks so that the initial coinbase transactions
|
||||
// can be spent.
|
||||
|
||||
setImmediate(function() {
|
||||
client.generate(150, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
blockHashes = response.result;
|
||||
|
||||
log.info('Preparing test data...');
|
||||
|
||||
// Get all of the unspent outputs
|
||||
client.listUnspent(0, 150, function(err, response) {
|
||||
utxos = response.result;
|
||||
|
||||
async.mapSeries(utxos, function(utxo, next) {
|
||||
async.series([
|
||||
function(finished) {
|
||||
// Load all of the transactions for later testing
|
||||
client.getTransaction(utxo.txid, function(err, txresponse) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// add to the list of transactions for testing later
|
||||
transactionData.push(txresponse.result.hex);
|
||||
finished();
|
||||
});
|
||||
},
|
||||
function(finished) {
|
||||
// Get the private key for each utxo
|
||||
client.dumpPrivKey(utxo.address, function(err, privresponse) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
utxo.privateKeyWIF = privresponse.result;
|
||||
finished();
|
||||
});
|
||||
}
|
||||
], next);
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
this.timeout(20000);
|
||||
bitcoind.stop(function(err, result) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('get blocks by hash', function() {
|
||||
|
||||
[0,1,2,3,5,6,7,8,9].forEach(function(i) {
|
||||
it('generated block ' + i, function(done) {
|
||||
bitcoind.getBlock(blockHashes[i], function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
should.exist(response);
|
||||
var block = bitcore.Block.fromBuffer(response);
|
||||
block.hash.should.equal(blockHashes[i]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get blocks by height', function() {
|
||||
|
||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('generated block ' + i, function(done) {
|
||||
// add the genesis block
|
||||
var height = i + 1;
|
||||
bitcoind.getBlock(i + 1, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
should.exist(response);
|
||||
var block = bitcore.Block.fromBuffer(response);
|
||||
block.hash.should.equal(blockHashes[i]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will get error with number greater than tip', function(done) {
|
||||
bitcoind.getBlock(1000000000, function(err, response) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('get transactions by hash', function() {
|
||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('for tx ' + i, function(done) {
|
||||
var txhex = transactionData[i];
|
||||
var tx = new bitcore.Transaction();
|
||||
tx.fromString(txhex);
|
||||
bitcoind.getTransaction(tx.hash, true, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
assert(response.toString('hex') === txhex, 'incorrect tx data result');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will return null if the transaction does not exist', function(done) {
|
||||
var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618';
|
||||
bitcoind.getTransaction(txid, true, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
should.not.exist(response);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('get block index', function() {
|
||||
var expectedWork = new BN(6);
|
||||
[1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('generate block ' + i, function() {
|
||||
var blockIndex = bitcoind.getBlockIndex(blockHashes[i]);
|
||||
should.exist(blockIndex);
|
||||
should.exist(blockIndex.chainWork);
|
||||
var work = new BN(blockIndex.chainWork, 'hex');
|
||||
work.cmp(expectedWork).should.equal(0);
|
||||
expectedWork = expectedWork.add(new BN(2));
|
||||
should.exist(blockIndex.prevHash);
|
||||
blockIndex.hash.should.equal(blockHashes[i]);
|
||||
blockIndex.prevHash.should.equal(blockHashes[i - 1]);
|
||||
blockIndex.height.should.equal(i + 1);
|
||||
});
|
||||
});
|
||||
it('will get null prevHash for the genesis block', function() {
|
||||
var blockIndex = bitcoind.getBlockIndex(0);
|
||||
should.exist(blockIndex);
|
||||
should.equal(blockIndex.prevHash, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get block index by height', function() {
|
||||
var expectedWork = new BN(6);
|
||||
[2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('generate block ' + i, function() {
|
||||
var blockIndex = bitcoind.getBlockIndex(i);
|
||||
should.exist(blockIndex);
|
||||
should.exist(blockIndex.chainWork);
|
||||
var work = new BN(blockIndex.chainWork, 'hex');
|
||||
work.cmp(expectedWork).should.equal(0);
|
||||
expectedWork = expectedWork.add(new BN(2));
|
||||
should.exist(blockIndex.prevHash);
|
||||
blockIndex.hash.should.equal(blockHashes[i - 1]);
|
||||
blockIndex.prevHash.should.equal(blockHashes[i - 2]);
|
||||
blockIndex.height.should.equal(i);
|
||||
});
|
||||
});
|
||||
it('will get null with number greater than tip', function(done) {
|
||||
var index = bitcoind.getBlockIndex(100000);
|
||||
should.equal(index, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isMainChain', function() {
|
||||
[1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('block ' + i + ' is on the main chain', function() {
|
||||
bitcoind.isMainChain(blockHashes[i]).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('send transaction functionality', function() {
|
||||
|
||||
it('will not error and return the transaction hash', function() {
|
||||
|
||||
// create and sign the transaction
|
||||
var tx = bitcore.Transaction();
|
||||
tx.from(utxos[0]);
|
||||
tx.change(privateKey.toAddress());
|
||||
tx.to(destKey.toAddress(), utxos[0].amount * 1e8 - 1000);
|
||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[0].privateKeyWIF));
|
||||
|
||||
// test sending the transaction
|
||||
var hash = bitcoind.sendTransaction(tx.serialize());
|
||||
hash.should.equal(tx.hash);
|
||||
});
|
||||
|
||||
it('will throw an error if an unsigned transaction is sent', function() {
|
||||
|
||||
var tx = bitcore.Transaction();
|
||||
tx.from(utxos[1]);
|
||||
tx.change(privateKey.toAddress());
|
||||
tx.to(destKey.toAddress(), utxos[1].amount * 1e8 - 1000);
|
||||
(function() {
|
||||
bitcoind.sendTransaction(tx.uncheckedSerialize());
|
||||
}).should.throw('\x10: mandatory-script-verify-flag-failed (Operation not valid with the current stack size)');
|
||||
});
|
||||
|
||||
it('will throw an error for unexpected types', function() {
|
||||
var garbage = new Buffer('abcdef', 'hex');
|
||||
(function() {
|
||||
bitcoind.sendTransaction(garbage);
|
||||
}).should.throw('TX decode failed');
|
||||
|
||||
var num = 23;
|
||||
(function() {
|
||||
bitcoind.sendTransaction(num);
|
||||
}).should.throw('TX decode failed');
|
||||
});
|
||||
|
||||
it('will emit "tx" events', function(done) {
|
||||
var tx = bitcore.Transaction();
|
||||
tx.from(utxos[2]);
|
||||
tx.change(privateKey.toAddress());
|
||||
tx.to(destKey.toAddress(), utxos[2].amount * 1e8 - 1000);
|
||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[2].privateKeyWIF));
|
||||
|
||||
var serialized = tx.serialize();
|
||||
|
||||
bitcoind.once('tx', function(result) {
|
||||
result.buffer.toString('hex').should.equal(serialized);
|
||||
result.hash.should.equal(tx.hash);
|
||||
result.mempool.should.equal(true);
|
||||
done();
|
||||
});
|
||||
bitcoind.sendTransaction(serialized);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('fee estimation', function() {
|
||||
it('will estimate fees', function() {
|
||||
var fees = bitcoind.estimateFee();
|
||||
fees.should.equal(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tip updates', function() {
|
||||
it('will get an event when the tip is new', function(done) {
|
||||
this.timeout(4000);
|
||||
bitcoind.on('tip', function(height) {
|
||||
if (height == 151) {
|
||||
height.should.equal(151);
|
||||
done();
|
||||
}
|
||||
});
|
||||
client.generate(1, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transactions leaving the mempool', function() {
|
||||
it('receive event when transaction leaves', function(done) {
|
||||
|
||||
// add transaction to build a new block
|
||||
var tx = bitcore.Transaction();
|
||||
tx.from(utxos[4]);
|
||||
tx.change(privateKey.toAddress());
|
||||
tx.to(destKey.toAddress(), utxos[4].amount * 1e8 - 1000);
|
||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[4].privateKeyWIF));
|
||||
bitcoind.sendTransaction(tx.serialize());
|
||||
|
||||
bitcoind.once('txleave', function(txInfo) {
|
||||
txInfo.hash.should.equal(tx.hash);
|
||||
done();
|
||||
});
|
||||
|
||||
client.generate(1, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mempool functionality', function() {
|
||||
|
||||
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
||||
var utxo1 = {
|
||||
address: fromAddress,
|
||||
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||
outputIndex: 0,
|
||||
script: bitcore.Script.buildPublicKeyHashOut(fromAddress).toString(),
|
||||
satoshis: 100000
|
||||
};
|
||||
var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
||||
var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up';
|
||||
var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf';
|
||||
var privateKey1 = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
||||
var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976';
|
||||
var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890';
|
||||
var tx = new bitcore.Transaction();
|
||||
tx.from(utxo1);
|
||||
tx.to(toAddress, 50000);
|
||||
tx.change(changeAddress);
|
||||
tx.sign(privateKey1);
|
||||
|
||||
var tx2;
|
||||
var tx2Key;
|
||||
|
||||
before(function() {
|
||||
tx2 = bitcore.Transaction();
|
||||
tx2.from(utxos[3]);
|
||||
tx2.change(privateKey.toAddress());
|
||||
tx2.to(destKey.toAddress(), utxos[3].amount * 1e8 - 1000);
|
||||
tx2Key = bitcore.PrivateKey.fromWIF(utxos[3].privateKeyWIF);
|
||||
tx2.sign(tx2Key);
|
||||
});
|
||||
|
||||
it('will add an unchecked transaction', function() {
|
||||
var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize());
|
||||
added.should.equal(true);
|
||||
bitcoind.getTransaction(tx.hash, true, function(err, txBuffer) {
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
var expected = tx.toBuffer().toString('hex');
|
||||
txBuffer.toString('hex').should.equal(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('get one transaction', function() {
|
||||
var transactions = bitcoind.getMempoolTransactions();
|
||||
transactions[0].toString('hex').should.equal(tx.serialize());
|
||||
});
|
||||
|
||||
it('get multiple transactions', function() {
|
||||
bitcoind.sendTransaction(tx2.serialize());
|
||||
var transactions = bitcoind.getMempoolTransactions();
|
||||
var expected = [tx.serialize(), tx2.serialize()];
|
||||
expected.should.contain(transactions[0].toString('hex'));
|
||||
expected.should.contain(transactions[1].toString('hex'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('get transaction with block info', function() {
|
||||
it('should include tx buffer, height and timestamp', function(done) {
|
||||
bitcoind.getTransactionWithBlockInfo(utxos[0].txid, true, function(err, data) {
|
||||
should.not.exist(err);
|
||||
should.exist(data.height);
|
||||
data.height.should.be.a('number');
|
||||
should.exist(data.timestamp);
|
||||
should.exist(data.buffer);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get next block hash', function() {
|
||||
it('will get next block hash', function() {
|
||||
var nextBlockHash = bitcoind.getNextBlockHash(blockHashes[0]);
|
||||
nextBlockHash.should.equal(blockHashes[1]);
|
||||
var nextnextBlockHash = bitcoind.getNextBlockHash(nextBlockHash);
|
||||
nextnextBlockHash.should.equal(blockHashes[2]);
|
||||
});
|
||||
|
||||
it('will get a null response if the tip hash is provided', function() {
|
||||
var bestBlockHash = bitcoind.getBestBlockHash();
|
||||
var nextBlockHash = bitcoind.getNextBlockHash(bestBlockHash);
|
||||
should.not.exist(nextBlockHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getInfo', function() {
|
||||
it('will get information', function() {
|
||||
var info = bitcoind.getInfo();
|
||||
should.exist(info);
|
||||
should.exist(info.version);
|
||||
should.exist(info.blocks);
|
||||
should.exist(info.timeoffset);
|
||||
should.exist(info.connections);
|
||||
should.exist(info.difficulty);
|
||||
should.exist(info.testnet);
|
||||
should.exist(info.relayfee);
|
||||
should.exist(info.errors);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -13,6 +13,7 @@ var util = require('util');
|
|||
function Bus(params) {
|
||||
events.EventEmitter.call(this);
|
||||
this.node = params.node;
|
||||
this.remoteAddress = params.remoteAddress;
|
||||
}
|
||||
|
||||
util.inherits(Bus, events.EventEmitter);
|
||||
|
|
|
@ -9,7 +9,7 @@ function main(parentServicesPath, additionalServices) {
|
|||
moduleName: 'bitcore-node',
|
||||
configName: 'bitcore-node',
|
||||
processTitle: 'bitcore'
|
||||
}).on('require', function (name, module) {
|
||||
}).on('require', function (name) {
|
||||
console.log('Loading:', name);
|
||||
}).on('requireFail', function (name, err) {
|
||||
console.log('Unable to load:', name, err);
|
||||
|
|
|
@ -9,7 +9,7 @@ function main(parentServicesPath, additionalServices) {
|
|||
moduleName: 'bitcore-node',
|
||||
configName: 'bitcore-node',
|
||||
processTitle: 'bitcored'
|
||||
}).on('require', function (name, module) {
|
||||
}).on('require', function (name) {
|
||||
console.log('Loading:', name);
|
||||
}).on('requireFail', function (name, err) {
|
||||
console.log('Unable to load:', name, err);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
var program = require('commander');
|
||||
var path = require('path');
|
||||
var bitcorenode = require('..');
|
||||
var utils = require('../utils');
|
||||
|
||||
function main(servicesPath, additionalServices) {
|
||||
/* jshint maxstatements: 100 */
|
||||
|
@ -124,7 +125,8 @@ function main(servicesPath, additionalServices) {
|
|||
program
|
||||
.command('call <method> [params...]')
|
||||
.description('Call an API method')
|
||||
.action(function(method, params) {
|
||||
.action(function(method, paramsArg) {
|
||||
var params = utils.parseParamsWithJSON(paramsArg);
|
||||
var configInfo = findConfig(process.cwd());
|
||||
if (!configInfo) {
|
||||
configInfo = defaultConfig();
|
||||
|
|
|
@ -3,23 +3,10 @@
|
|||
var createError = require('errno').create;
|
||||
|
||||
var BitcoreNodeError = createError('BitcoreNodeError');
|
||||
var NoOutputs = createError('NoOutputs', BitcoreNodeError);
|
||||
var NoOutput = createError('NoOutput', BitcoreNodeError);
|
||||
|
||||
var Wallet = createError('WalletError', BitcoreNodeError);
|
||||
Wallet.InsufficientFunds = createError('InsufficientFunds', Wallet);
|
||||
|
||||
var Consensus = createError('Consensus', BitcoreNodeError);
|
||||
Consensus.BlockExists = createError('BlockExists', Consensus);
|
||||
|
||||
var Transaction = createError('Transaction', BitcoreNodeError);
|
||||
Transaction.NotFound = createError('NotFound', Transaction);
|
||||
var RPCError = createError('RPCError', BitcoreNodeError);
|
||||
|
||||
module.exports = {
|
||||
Error: BitcoreNodeError,
|
||||
NoOutputs: NoOutputs,
|
||||
NoOutput: NoOutput,
|
||||
Wallet: Wallet,
|
||||
Consensus: Consensus,
|
||||
Transaction: Transaction
|
||||
RPCError: RPCError
|
||||
};
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var _ = bitcore.deps._;
|
||||
var colors = require('colors/safe');
|
||||
|
||||
/**
|
||||
* Wraps console.log with some special magic
|
||||
* @constructor
|
||||
*/
|
||||
function Logger() {
|
||||
function Logger(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
this.formatting = _.isUndefined(options.formatting) ? Logger.DEFAULT_FORMATTING : options.formatting;
|
||||
}
|
||||
|
||||
Logger.DEFAULT_FORMATTING = true;
|
||||
|
||||
/**
|
||||
* Prints an info message
|
||||
* #info
|
||||
|
@ -45,17 +53,18 @@ Logger.prototype.warn = function() {
|
|||
* Proxies console.log with color and arg parsing magic
|
||||
* #_log
|
||||
*/
|
||||
Logger.prototype._log = function(color, type) {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.prototype._log = function(color) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args = args.slice(1);
|
||||
var date = new Date();
|
||||
var typeString = colors[color].italic(args.shift() + ':');
|
||||
args[0] = '[' + date.toISOString() + ']' + ' ' + typeString + ' ' + args[0];
|
||||
console.log.apply(console, args);
|
||||
var level = args.shift();
|
||||
|
||||
if (this.formatting) {
|
||||
var date = new Date();
|
||||
var typeString = colors[color].italic(level + ':');
|
||||
args[0] = '[' + date.toISOString() + ']' + ' ' + typeString + ' ' + args[0];
|
||||
}
|
||||
var fn = console[level] || console.log;
|
||||
fn.apply(console, args);
|
||||
};
|
||||
|
||||
module.exports = Logger;
|
||||
|
|
108
lib/node.js
108
lib/node.js
|
@ -3,7 +3,7 @@
|
|||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var Networks = bitcore.Networks;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
|
@ -27,8 +27,8 @@ var errors = require('./errors');
|
|||
* ```
|
||||
*
|
||||
* @param {Object} config - The configuration of the node
|
||||
* @param {Array} config.formatLogs - Option to disable formatting of logs
|
||||
* @param {Array} config.services - The array of services
|
||||
* @param {String} config.datadir - The directory for data (e.g. bitcoind datadir)
|
||||
* @param {Number} config.port - The HTTP port for services
|
||||
* @param {Boolean} config.https - Enable https
|
||||
* @param {Object} config.httpsOptions - Options for https
|
||||
|
@ -41,8 +41,14 @@ function Node(config) {
|
|||
if(!(this instanceof Node)) {
|
||||
return new Node(config);
|
||||
}
|
||||
this.configPath = config.path;
|
||||
this.errors = errors;
|
||||
this.log = log;
|
||||
|
||||
if (!_.isUndefined(config.formatLogs)) {
|
||||
this.log.formatting = config.formatLogs ? true : false;
|
||||
}
|
||||
|
||||
this.network = null;
|
||||
this.services = {};
|
||||
this._unloadedServices = [];
|
||||
|
@ -52,8 +58,6 @@ function Node(config) {
|
|||
$.checkArgument(Array.isArray(config.services));
|
||||
this._unloadedServices = config.services;
|
||||
}
|
||||
$.checkState(config.datadir, 'Node config expects "datadir"');
|
||||
this.datadir = config.datadir;
|
||||
this.port = config.port;
|
||||
this.https = config.https;
|
||||
this.httpsOptions = config.httpsOptions;
|
||||
|
@ -71,19 +75,7 @@ Node.prototype._setNetwork = function(config) {
|
|||
if (config.network === 'testnet') {
|
||||
this.network = Networks.get('testnet');
|
||||
} else if (config.network === 'regtest') {
|
||||
Networks.remove(Networks.testnet);
|
||||
Networks.add({
|
||||
name: 'regtest',
|
||||
alias: 'regtest',
|
||||
pubkeyhash: 0x6f,
|
||||
privatekey: 0xef,
|
||||
scripthash: 0xc4,
|
||||
xpubkey: 0x043587cf,
|
||||
xprivkey: 0x04358394,
|
||||
networkMagic: 0xfabfb5da,
|
||||
port: 18444,
|
||||
dnsSeeds: [ ]
|
||||
});
|
||||
Networks.enableRegtest();
|
||||
this.network = Networks.get('regtest');
|
||||
} else {
|
||||
this.network = Networks.defaultNetwork;
|
||||
|
@ -95,8 +87,11 @@ Node.prototype._setNetwork = function(config) {
|
|||
* Will instantiate a new Bus for this node.
|
||||
* @returns {Bus}
|
||||
*/
|
||||
Node.prototype.openBus = function() {
|
||||
return new Bus({node: this});
|
||||
Node.prototype.openBus = function(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
return new Bus({node: this, remoteAddress: options.remoteAddress});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -107,7 +102,9 @@ Node.prototype.getAllAPIMethods = function() {
|
|||
var methods = [];
|
||||
for(var i in this.services) {
|
||||
var mod = this.services[i];
|
||||
methods = methods.concat(mod.getAPIMethods());
|
||||
if (mod.getAPIMethods) {
|
||||
methods = methods.concat(mod.getAPIMethods());
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
};
|
||||
|
@ -120,7 +117,9 @@ Node.prototype.getAllPublishEvents = function() {
|
|||
var events = [];
|
||||
for (var i in this.services) {
|
||||
var mod = this.services[i];
|
||||
events = events.concat(mod.getPublishEvents());
|
||||
if (mod.getPublishEvents) {
|
||||
events = events.concat(mod.getPublishEvents());
|
||||
}
|
||||
}
|
||||
return events;
|
||||
};
|
||||
|
@ -184,13 +183,18 @@ Node.prototype.getServiceOrder = function() {
|
|||
Node.prototype._startService = function(serviceInfo, callback) {
|
||||
var self = this;
|
||||
|
||||
$.checkState(_.isObject(serviceInfo.config));
|
||||
$.checkState(!serviceInfo.config.node);
|
||||
$.checkState(!serviceInfo.config.name);
|
||||
|
||||
log.info('Starting ' + serviceInfo.name);
|
||||
|
||||
var config = serviceInfo.config;
|
||||
var config;
|
||||
if (serviceInfo.config) {
|
||||
$.checkState(_.isObject(serviceInfo.config));
|
||||
$.checkState(!serviceInfo.config.node);
|
||||
$.checkState(!serviceInfo.config.name);
|
||||
config = serviceInfo.config;
|
||||
} else {
|
||||
config = {};
|
||||
}
|
||||
|
||||
config.node = this;
|
||||
config.name = serviceInfo.name;
|
||||
var service = new serviceInfo.module(config);
|
||||
|
@ -204,24 +208,26 @@ Node.prototype._startService = function(serviceInfo, callback) {
|
|||
}
|
||||
|
||||
// add API methods
|
||||
var methodData = service.getAPIMethods();
|
||||
var methodNameConflicts = [];
|
||||
methodData.forEach(function(data) {
|
||||
var name = data[0];
|
||||
var instance = data[1];
|
||||
var method = data[2];
|
||||
if (service.getAPIMethods) {
|
||||
var methodData = service.getAPIMethods();
|
||||
var methodNameConflicts = [];
|
||||
methodData.forEach(function(data) {
|
||||
var name = data[0];
|
||||
var instance = data[1];
|
||||
var method = data[2];
|
||||
|
||||
if (self[name]) {
|
||||
methodNameConflicts.push(name);
|
||||
} else {
|
||||
self[name] = function() {
|
||||
return method.apply(instance, arguments);
|
||||
};
|
||||
if (self[name]) {
|
||||
methodNameConflicts.push(name);
|
||||
} else {
|
||||
self[name] = function() {
|
||||
return method.apply(instance, arguments);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (methodNameConflicts.length > 0) {
|
||||
return callback(new Error('Existing API method(s) exists: ' + methodNameConflicts.join(', ')));
|
||||
}
|
||||
});
|
||||
|
||||
if (methodNameConflicts.length > 0) {
|
||||
return callback(new Error('Existing API method(s) exists: ' + methodNameConflicts.join(', ')));
|
||||
}
|
||||
|
||||
callback();
|
||||
|
@ -230,6 +236,14 @@ Node.prototype._startService = function(serviceInfo, callback) {
|
|||
|
||||
};
|
||||
|
||||
Node.prototype._logTitle = function() {
|
||||
if (this.configPath) {
|
||||
log.info('Using config:', this.configPath);
|
||||
log.info('Using network:', this.getNetworkName());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Will start all running services in the order based on the dependency chain.
|
||||
* @param {Function} callback - Called when all services are started
|
||||
|
@ -238,6 +252,8 @@ Node.prototype.start = function(callback) {
|
|||
var self = this;
|
||||
var servicesOrder = this.getServiceOrder();
|
||||
|
||||
self._logTitle();
|
||||
|
||||
async.eachSeries(
|
||||
servicesOrder,
|
||||
function(service, next) {
|
||||
|
@ -253,6 +269,14 @@ Node.prototype.start = function(callback) {
|
|||
);
|
||||
};
|
||||
|
||||
Node.prototype.getNetworkName = function() {
|
||||
var network = this.network.name;
|
||||
if (this.network.regtestEnabled) {
|
||||
network = 'regtest';
|
||||
}
|
||||
return network;
|
||||
};
|
||||
|
||||
/**
|
||||
* Will stop all running services in the reverse order that they
|
||||
* were initially started.
|
||||
|
|
|
@ -4,7 +4,8 @@ var async = require('async');
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var utils = require('../utils');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
|
@ -14,7 +15,7 @@ var _ = bitcore.deps._;
|
|||
* @param {Function} done
|
||||
*/
|
||||
function addConfig(configFilePath, service, done) {
|
||||
$.checkState(path.isAbsolute(configFilePath), 'An absolute path is expected');
|
||||
$.checkState(utils.isAbsolutePath(configFilePath), 'An absolute path is expected');
|
||||
fs.readFile(configFilePath, function(err, data) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
|
@ -39,7 +40,7 @@ function addConfig(configFilePath, service, done) {
|
|||
* @param {Function} done
|
||||
*/
|
||||
function addService(configDir, service, done) {
|
||||
$.checkState(path.isAbsolute(configDir), 'An absolute path is expected');
|
||||
$.checkState(utils.isAbsolutePath(configDir), 'An absolute path is expected');
|
||||
var npm = spawn('npm', ['install', service, '--save'], {cwd: configDir});
|
||||
|
||||
npm.stdout.on('data', function(data) {
|
||||
|
@ -69,7 +70,7 @@ function add(options, done) {
|
|||
$.checkArgument(_.isObject(options));
|
||||
$.checkArgument(_.isFunction(done));
|
||||
$.checkArgument(
|
||||
_.isString(options.path) && path.isAbsolute(options.path),
|
||||
_.isString(options.path) && utils.isAbsolutePath(options.path),
|
||||
'An absolute path is expected'
|
||||
);
|
||||
$.checkArgument(Array.isArray(options.services));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var async = require('async');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
|
@ -11,21 +11,16 @@ var mkdirp = require('mkdirp');
|
|||
var fs = require('fs');
|
||||
var defaultBaseConfig = require('./default-base-config');
|
||||
|
||||
var version;
|
||||
if (packageFile.version.match('-dev')) {
|
||||
version = '^' + packageFile.lastBuild;
|
||||
} else {
|
||||
version = '^' + packageFile.version;
|
||||
}
|
||||
var version = '^' + packageFile.version;
|
||||
|
||||
var BASE_PACKAGE = {
|
||||
description: 'A full Bitcoin node build with Bitcore',
|
||||
description: 'A full Zcash node build with Bitcore',
|
||||
repository: 'https://github.com/user/project',
|
||||
license: 'MIT',
|
||||
readme: 'README.md',
|
||||
dependencies: {
|
||||
'bitcore-lib': '^' + bitcore.version,
|
||||
'bitcore-node': version
|
||||
'bitcore-lib-zcash': 'zcash-hackworks/bitcore-lib-zcash',
|
||||
'bitcore-node-zcash': 'zcash-hackworks/bitcore-node-zcash'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ var path = require('path');
|
|||
* or default locations.
|
||||
* @param {Object} options
|
||||
* @param {String} options.network - "testnet" or "livenet"
|
||||
* @param {String} options.datadir - Absolute path to bitcoin database directory
|
||||
*/
|
||||
function getDefaultBaseConfig(options) {
|
||||
if (!options) {
|
||||
|
@ -15,10 +16,17 @@ function getDefaultBaseConfig(options) {
|
|||
return {
|
||||
path: process.cwd(),
|
||||
config: {
|
||||
datadir: options.datadir || path.resolve(process.env.HOME, '.bitcoin'),
|
||||
network: options.network || 'livenet',
|
||||
port: 3001,
|
||||
services: ['bitcoind', 'db', 'address', 'web']
|
||||
services: ['bitcoind', 'web'],
|
||||
servicesConfig: {
|
||||
bitcoind: {
|
||||
spawn: {
|
||||
datadir: options.datadir || path.resolve(process.env.HOME, '.zcash'),
|
||||
exec: path.resolve(__dirname, '../../bin/zcashd')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,17 +24,24 @@ function getDefaultConfig(options) {
|
|||
mkdirp.sync(defaultPath);
|
||||
}
|
||||
|
||||
var defaultServices = ['bitcoind', 'db', 'address', 'web'];
|
||||
var defaultServices = ['bitcoind', 'web'];
|
||||
if (options.additionalServices) {
|
||||
defaultServices = defaultServices.concat(options.additionalServices);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(defaultConfigFile)) {
|
||||
var defaultConfig = {
|
||||
datadir: path.resolve(defaultPath, './data'),
|
||||
network: 'livenet',
|
||||
port: 3001,
|
||||
services: defaultServices
|
||||
services: defaultServices,
|
||||
servicesConfig: {
|
||||
bitcoind: {
|
||||
spawn: {
|
||||
datadir: path.resolve(defaultPath, './data'),
|
||||
exec: path.resolve(__dirname, '../../bin/zcashd')
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
fs.writeFileSync(defaultConfigFile, JSON.stringify(defaultConfig, null, 2));
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Will return the path and bitcore-node configuration
|
||||
|
@ -12,7 +13,7 @@ var fs = require('fs');
|
|||
*/
|
||||
function findConfig(cwd) {
|
||||
$.checkArgument(_.isString(cwd), 'Argument should be a string');
|
||||
$.checkArgument(path.isAbsolute(cwd), 'Argument should be an absolute path');
|
||||
$.checkArgument(utils.isAbsolutePath(cwd), 'Argument should be an absolute path');
|
||||
var directory = String(cwd);
|
||||
while (!fs.existsSync(path.resolve(directory, 'bitcore-node.json'))) {
|
||||
directory = path.resolve(directory, '../');
|
||||
|
|
|
@ -5,9 +5,10 @@ var fs = require('fs');
|
|||
var npm = require('npm');
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
var utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Will remove a service from bitcore-node.json
|
||||
|
@ -16,7 +17,7 @@ var _ = bitcore.deps._;
|
|||
* @param {Function} done
|
||||
*/
|
||||
function removeConfig(configFilePath, service, done) {
|
||||
$.checkArgument(path.isAbsolute(configFilePath), 'An absolute path is expected');
|
||||
$.checkArgument(utils.isAbsolutePath(configFilePath), 'An absolute path is expected');
|
||||
fs.readFile(configFilePath, function(err, data) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
|
@ -47,7 +48,7 @@ function removeConfig(configFilePath, service, done) {
|
|||
* @param {Function} done
|
||||
*/
|
||||
function uninstallService(configDir, service, done) {
|
||||
$.checkArgument(path.isAbsolute(configDir), 'An absolute path is expected');
|
||||
$.checkArgument(utils.isAbsolutePath(configDir), 'An absolute path is expected');
|
||||
$.checkArgument(_.isString(service), 'A string is expected for the service argument');
|
||||
|
||||
var child = spawn('npm', ['uninstall', service, '--save'], {cwd: configDir});
|
||||
|
@ -76,7 +77,7 @@ function uninstallService(configDir, service, done) {
|
|||
* @param {Function} done
|
||||
*/
|
||||
function removeService(configDir, service, done) {
|
||||
$.checkArgument(path.isAbsolute(configDir), 'An absolute path is expected');
|
||||
$.checkArgument(utils.isAbsolutePath(configDir), 'An absolute path is expected');
|
||||
$.checkArgument(_.isString(service), 'A string is expected for the service argument');
|
||||
|
||||
// check if the service is installed
|
||||
|
@ -109,7 +110,7 @@ function remove(options, done) {
|
|||
$.checkArgument(_.isObject(options));
|
||||
$.checkArgument(_.isFunction(done));
|
||||
$.checkArgument(
|
||||
_.isString(options.path) && path.isAbsolute(options.path),
|
||||
_.isString(options.path) && utils.isAbsolutePath(options.path),
|
||||
'An absolute path is expected'
|
||||
);
|
||||
$.checkArgument(Array.isArray(options.services));
|
||||
|
|
|
@ -3,16 +3,152 @@
|
|||
var path = require('path');
|
||||
var BitcoreNode = require('../node');
|
||||
var index = require('../');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var _ = bitcore.deps._;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var log = index.log;
|
||||
var child_process = require('child_process');
|
||||
var fs = require('fs');
|
||||
var shuttingDown = false;
|
||||
|
||||
log.debug = function() {};
|
||||
|
||||
/**
|
||||
* Checks for configuration options from version 2. This includes an "address" and
|
||||
* "db" service, or having "datadir" at the root of the config.
|
||||
*/
|
||||
function checkConfigVersion2(fullConfig) {
|
||||
var datadirUndefined = _.isUndefined(fullConfig.datadir);
|
||||
var addressDefined = (fullConfig.services.indexOf('address') >= 0);
|
||||
var dbDefined = (fullConfig.services.indexOf('db') >= 0);
|
||||
|
||||
if (!datadirUndefined || addressDefined || dbDefined) {
|
||||
|
||||
console.warn('\nConfiguration file is not compatible with this version. \n' +
|
||||
'A reindex for zcashd is necessary for this upgrade with the "reindex=1" zcash.conf option. \n' +
|
||||
'There are changes necessary in both zcash.conf and bitcore-node.json. \n\n' +
|
||||
'To upgrade please see the details below and documentation at: \n' +
|
||||
'https://github.com/bitpay/bitcore-node/blob/bitcoind/docs/upgrade.md \n');
|
||||
|
||||
if (!datadirUndefined) {
|
||||
console.warn('Please remove "datadir" and add it to the config at ' + fullConfig.path + ' with:');
|
||||
var missingConfig = {
|
||||
servicesConfig: {
|
||||
bitcoind: {
|
||||
spawn: {
|
||||
datadir: fullConfig.datadir,
|
||||
exec: path.resolve(__dirname, '../../bin/zcashd')
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
console.warn(JSON.stringify(missingConfig, null, 2) + '\n');
|
||||
}
|
||||
|
||||
if (addressDefined || dbDefined) {
|
||||
console.warn('Please remove "address" and/or "db" from "services" in: ' + fullConfig.path + '\n');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will instantiate and start a Node, requiring the necessary service
|
||||
* modules, and registering event handlers.
|
||||
* @param {Object} options
|
||||
* @param {Object} options.servicesPath - The path to the location of service modules
|
||||
* @param {String} options.path - The absolute path of the configuration file
|
||||
* @param {Object} options.config - The parsed bitcore-node.json configuration file
|
||||
* @param {Array} options.config.services - An array of services names.
|
||||
* @param {Object} options.config.servicesConfig - Parameters to pass to each service
|
||||
* @param {String} options.config.network - 'livenet', 'testnet' or 'regtest
|
||||
* @param {Number} options.config.port - The port to use for the web service
|
||||
*/
|
||||
function start(options) {
|
||||
/* jshint maxstatements: 20 */
|
||||
|
||||
var fullConfig = _.clone(options.config);
|
||||
|
||||
var servicesPath;
|
||||
if (options.servicesPath) {
|
||||
servicesPath = options.servicesPath; // services are in a different directory than the config
|
||||
} else {
|
||||
servicesPath = options.path; // defaults to the same directory
|
||||
}
|
||||
|
||||
fullConfig.path = path.resolve(options.path, './bitcore-node.json');
|
||||
|
||||
if (checkConfigVersion2(fullConfig)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fullConfig.services = start.setupServices(require, servicesPath, options.config);
|
||||
|
||||
var node = new BitcoreNode(fullConfig);
|
||||
|
||||
// setup handlers for uncaught exceptions and ctrl+c
|
||||
start.registerExitHandlers(process, node);
|
||||
|
||||
node.on('ready', function() {
|
||||
log.info('Bitcore Node ready');
|
||||
});
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
||||
node.start(function(err) {
|
||||
if(err) {
|
||||
log.error('Failed to start services');
|
||||
if (err.stack) {
|
||||
log.error(err.stack);
|
||||
}
|
||||
start.cleanShutdown(process, node);
|
||||
}
|
||||
});
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a service for the expected methods
|
||||
* @param {Object} service
|
||||
*/
|
||||
function checkService(service) {
|
||||
// check that the service supports expected methods
|
||||
if (!service.module.prototype ||
|
||||
!service.module.dependencies ||
|
||||
!service.module.prototype.start ||
|
||||
!service.module.prototype.stop) {
|
||||
throw new Error(
|
||||
'Could not load service "' + service.name + '" as it does not support necessary methods and properties.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will require a module from local services directory first
|
||||
* and then from available node_modules
|
||||
* @param {Function} req
|
||||
* @param {Object} service
|
||||
*/
|
||||
function loadModule(req, service) {
|
||||
try {
|
||||
// first try in the built-in bitcore-node services directory
|
||||
service.module = req(path.resolve(__dirname, '../services/' + service.name));
|
||||
} catch(e) {
|
||||
|
||||
// check if the package.json specifies a specific file to use
|
||||
var servicePackage = req(service.name + '/package.json');
|
||||
var serviceModule = service.name;
|
||||
if (servicePackage.bitcoreNode) {
|
||||
serviceModule = service.name + '/' + servicePackage.bitcoreNode;
|
||||
}
|
||||
service.module = req(serviceModule);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will loop over the configuration for services and require the
|
||||
* specified modules, and assemble an array in this format:
|
||||
|
@ -42,29 +178,8 @@ function setupServices(req, servicesPath, config) {
|
|||
var hasConfig = config.servicesConfig && config.servicesConfig[service.name];
|
||||
service.config = hasConfig ? config.servicesConfig[service.name] : {};
|
||||
|
||||
try {
|
||||
// first try in the built-in bitcore-node services directory
|
||||
service.module = req(path.resolve(__dirname, '../services/' + service.name));
|
||||
} catch(e) {
|
||||
|
||||
// check if the package.json specifies a specific file to use
|
||||
var servicePackage = req(service.name + '/package.json');
|
||||
var serviceModule = service.name;
|
||||
if (servicePackage.bitcoreNode) {
|
||||
serviceModule = service.name + '/' + servicePackage.bitcoreNode;
|
||||
}
|
||||
service.module = req(serviceModule);
|
||||
}
|
||||
|
||||
// check that the service supports expected methods
|
||||
if (!service.module.prototype ||
|
||||
!service.module.dependencies ||
|
||||
!service.module.prototype.start ||
|
||||
!service.module.prototype.stop) {
|
||||
throw new Error(
|
||||
'Could not load service "' + service.name + '" as it does not support necessary methods.'
|
||||
);
|
||||
}
|
||||
loadModule(req, service);
|
||||
checkService(service);
|
||||
|
||||
services.push(service);
|
||||
}
|
||||
|
@ -72,50 +187,6 @@ function setupServices(req, servicesPath, config) {
|
|||
return services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will register event handlers to log the current db sync status.
|
||||
* @param {Node} node
|
||||
*/
|
||||
function registerSyncHandlers(node, delay) {
|
||||
|
||||
delay = delay || 10000;
|
||||
var interval = false;
|
||||
var count = 0;
|
||||
|
||||
function logSyncStatus() {
|
||||
log.info(
|
||||
'Database Sync Status: Tip:', node.services.db.tip.hash,
|
||||
'Height:', node.services.db.tip.__height,
|
||||
'Rate:', count/10, 'blocks per second'
|
||||
);
|
||||
}
|
||||
|
||||
node.on('ready', function() {
|
||||
|
||||
if (node.services.db) {
|
||||
node.on('synced', function() {
|
||||
clearInterval(interval);
|
||||
logSyncStatus();
|
||||
});
|
||||
node.services.db.on('addblock', function(block) {
|
||||
count++;
|
||||
// Initialize logging if not already instantiated
|
||||
if (!interval) {
|
||||
interval = setInterval(function() {
|
||||
logSyncStatus();
|
||||
count = 0;
|
||||
}, delay);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
node.on('stopping', function() {
|
||||
clearInterval(interval);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will shutdown a node and then the process
|
||||
* @param {Object} _process - The Node.js process object
|
||||
|
@ -132,20 +203,6 @@ function cleanShutdown(_process, node) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will register event handlers to stop the node for `process` events
|
||||
* `uncaughtException` and `SIGINT`.
|
||||
* @param {Object} _process - The Node.js process
|
||||
* @param {Node} node
|
||||
*/
|
||||
function registerExitHandlers(_process, node) {
|
||||
//catches uncaught exceptions
|
||||
_process.on('uncaughtException', exitHandler.bind(null, {exit:true}, _process, node));
|
||||
|
||||
//catches ctrl+c event
|
||||
_process.on('SIGINT', exitHandler.bind(null, {sigint:true}, _process, node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Will handle all the shutdown tasks that need to take place to ensure a safe exit
|
||||
* @param {Object} options
|
||||
|
@ -177,106 +234,22 @@ function exitHandler(options, _process, node, err) {
|
|||
}
|
||||
|
||||
/**
|
||||
* This function will instantiate and start a Node, requiring the necessary service
|
||||
* modules, and registering event handlers.
|
||||
* @param {Object} options
|
||||
* @param {Object} options.servicesPath - The path to the location of service modules
|
||||
* @param {String} options.path - The absolute path of the configuration file
|
||||
* @param {Object} options.config - The parsed bitcore-node.json configuration file
|
||||
* @param {Array} options.config.services - An array of services names.
|
||||
* @param {Object} options.config.servicesConfig - Parameters to pass to each service
|
||||
* @param {String} options.config.datadir - A relative (to options.path) or absolute path to the datadir
|
||||
* @param {String} options.config.network - 'livenet', 'testnet' or 'regtest
|
||||
* @param {Number} options.config.port - The port to use for the web service
|
||||
* Will register event handlers to stop the node for `process` events
|
||||
* `uncaughtException` and `SIGINT`.
|
||||
* @param {Object} _process - The Node.js process
|
||||
* @param {Node} node
|
||||
*/
|
||||
function start(options) {
|
||||
function registerExitHandlers(_process, node) {
|
||||
//catches uncaught exceptions
|
||||
_process.on('uncaughtException', exitHandler.bind(null, {exit:true}, _process, node));
|
||||
|
||||
var fullConfig = _.clone(options.config);
|
||||
|
||||
var servicesPath;
|
||||
if (options.servicesPath) {
|
||||
servicesPath = options.servicesPath; // services are in a different directory than the config
|
||||
} else {
|
||||
servicesPath = options.path; // defaults to the same directory
|
||||
}
|
||||
|
||||
fullConfig.services = start.setupServices(require, servicesPath, options.config);
|
||||
fullConfig.datadir = path.resolve(options.path, options.config.datadir);
|
||||
|
||||
if (fullConfig.daemon) {
|
||||
start.spawnChildProcess(fullConfig.datadir, process);
|
||||
}
|
||||
|
||||
var node = new BitcoreNode(fullConfig);
|
||||
|
||||
// set up the event handlers for logging sync information
|
||||
start.registerSyncHandlers(node);
|
||||
|
||||
// setup handlers for uncaught exceptions and ctrl+c
|
||||
start.registerExitHandlers(process, node);
|
||||
|
||||
node.on('ready', function() {
|
||||
log.info('Bitcore Node ready');
|
||||
});
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
||||
node.start(function(err) {
|
||||
if(err) {
|
||||
log.error('Failed to start services');
|
||||
if (err.stack) {
|
||||
log.error(err.stack);
|
||||
}
|
||||
start.cleanShutdown(process, node);
|
||||
}
|
||||
});
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will fork the passed in process and exit the parent process
|
||||
* in order to daemonize the process. If there is already a daemon for this pid (process),
|
||||
* then the function just returns. Stdout and stderr both append to one file, 'bitcore-node.log'
|
||||
* located in the datadir.
|
||||
* @param {String} datadir - The data directory where the bitcoin blockchain and config live.
|
||||
* @param {Object} _process - The process that needs to fork a child and then, itself, exit.
|
||||
*/
|
||||
function spawnChildProcess(datadir, _process) {
|
||||
|
||||
if (_process.env.__bitcore_node) {
|
||||
return _process.pid;
|
||||
}
|
||||
|
||||
var args = [].concat(_process.argv);
|
||||
args.shift();
|
||||
var script = args.shift();
|
||||
var env = _process.env;
|
||||
var cwd = _process.cwd();
|
||||
env.__bitcore_node = true;
|
||||
|
||||
var stderr = fs.openSync(datadir + '/bitcore-node.log', 'a+');
|
||||
var stdout = stderr;
|
||||
|
||||
var cp_opt = {
|
||||
stdio: ['ignore', stdout, stderr],
|
||||
env: env,
|
||||
cwd: cwd,
|
||||
detached: true
|
||||
};
|
||||
|
||||
var child = child_process.spawn(_process.execPath, [script].concat(args), cp_opt);
|
||||
child.unref();
|
||||
return _process.exit();
|
||||
//catches ctrl+c event
|
||||
_process.on('SIGINT', exitHandler.bind(null, {sigint:true}, _process, node));
|
||||
}
|
||||
|
||||
module.exports = start;
|
||||
module.exports.registerExitHandlers = registerExitHandlers;
|
||||
module.exports.exitHandler = exitHandler;
|
||||
module.exports.registerSyncHandlers = registerSyncHandlers;
|
||||
module.exports.setupServices = setupServices;
|
||||
module.exports.spawnChildProcess = spawnChildProcess;
|
||||
module.exports.cleanShutdown = cleanShutdown;
|
||||
module.exports.checkConfigVersion2 = checkConfigVersion2;
|
||||
|
|
|
@ -78,7 +78,7 @@ Service.prototype.stop = function(done) {
|
|||
* Setup express routes
|
||||
* @param {Express} app
|
||||
*/
|
||||
Service.prototype.setupRoutes = function(app) {
|
||||
Service.prototype.setupRoutes = function() {
|
||||
// Setup express routes here
|
||||
};
|
||||
|
||||
|
@ -86,6 +86,4 @@ Service.prototype.getRoutePrefix = function() {
|
|||
return this.name;
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = Service;
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var exports = {};
|
||||
|
||||
exports.PREFIXES = {
|
||||
OUTPUTS: new Buffer('02', 'hex'), // Query outputs by address and/or height
|
||||
SPENTS: new Buffer('03', 'hex'), // Query inputs by address and/or height
|
||||
SPENTSMAP: new Buffer('05', 'hex') // Get the input that spends an output
|
||||
};
|
||||
|
||||
exports.MEMPREFIXES = {
|
||||
OUTPUTS: new Buffer('01', 'hex'), // Query mempool outputs by address
|
||||
SPENTS: new Buffer('02', 'hex'), // Query mempool inputs by address
|
||||
SPENTSMAP: new Buffer('03', 'hex') // Query mempool for the input that spends an output
|
||||
};
|
||||
|
||||
// To save space, we're only storing the PubKeyHash or ScriptHash in our index.
|
||||
// To avoid intentional unspendable collisions, which have been seen on the blockchain,
|
||||
// we must store the hash type (PK or Script) as well.
|
||||
exports.HASH_TYPES = {
|
||||
PUBKEY: new Buffer('01', 'hex'),
|
||||
REDEEMSCRIPT: new Buffer('02', 'hex')
|
||||
};
|
||||
|
||||
// Translates from our enum type back into the hash types returned by
|
||||
// bitcore-lib/address.
|
||||
exports.HASH_TYPES_READABLE = {
|
||||
'01': 'pubkeyhash',
|
||||
'02': 'scripthash'
|
||||
};
|
||||
|
||||
exports.HASH_TYPES_MAP = {
|
||||
'pubkeyhash': exports.HASH_TYPES.PUBKEY,
|
||||
'scripthash': exports.HASH_TYPES.REDEEMSCRIPT
|
||||
};
|
||||
|
||||
exports.SPACER_MIN = new Buffer('00', 'hex');
|
||||
exports.SPACER_MAX = new Buffer('ff', 'hex');
|
||||
exports.SPACER_HEIGHT_MIN = new Buffer('0000000000', 'hex');
|
||||
exports.SPACER_HEIGHT_MAX = new Buffer('ffffffffff', 'hex');
|
||||
exports.TIMESTAMP_MIN = new Buffer('0000000000000000', 'hex');
|
||||
exports.TIMESTAMP_MAX = new Buffer('ffffffffffffffff', 'hex');
|
||||
|
||||
// The maximum number of inputs that can be queried at once
|
||||
exports.MAX_INPUTS_QUERY_LENGTH = 50000;
|
||||
// The maximum number of outputs that can be queried at once
|
||||
exports.MAX_OUTPUTS_QUERY_LENGTH = 50000;
|
||||
// The maximum number of transactions that can be queried at once
|
||||
exports.MAX_HISTORY_QUERY_LENGTH = 100;
|
||||
// The maximum number of addresses that can be queried at once
|
||||
exports.MAX_ADDRESSES_QUERY = 10000;
|
||||
|
||||
module.exports = exports;
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore-lib');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var Address = bitcore.Address;
|
||||
var PublicKey = bitcore.PublicKey;
|
||||
var constants = require('./constants');
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
var exports = {};
|
||||
|
||||
exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) {
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||
var key = Buffer.concat([
|
||||
txidBuffer,
|
||||
outputIndexBuffer
|
||||
]);
|
||||
return key.toString('binary');
|
||||
};
|
||||
|
||||
exports.encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) {
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height);
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||
var key = Buffer.concat([
|
||||
constants.PREFIXES.OUTPUTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.SPACER_MIN,
|
||||
heightBuffer,
|
||||
txidBuffer,
|
||||
outputIndexBuffer
|
||||
]);
|
||||
return key;
|
||||
};
|
||||
|
||||
exports.decodeOutputKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
var prefix = reader.read(1);
|
||||
var hashBuffer = reader.read(20);
|
||||
var hashTypeBuffer = reader.read(1);
|
||||
var spacer = reader.read(1);
|
||||
var height = reader.readUInt32BE();
|
||||
var txid = reader.read(32);
|
||||
var outputIndex = reader.readUInt32BE();
|
||||
return {
|
||||
prefix: prefix,
|
||||
hashBuffer: hashBuffer,
|
||||
hashTypeBuffer: hashTypeBuffer,
|
||||
height: height,
|
||||
txid: txid,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
};
|
||||
|
||||
exports.encodeOutputValue = function(satoshis, scriptBuffer) {
|
||||
var satoshisBuffer = new Buffer(8);
|
||||
satoshisBuffer.writeDoubleBE(satoshis);
|
||||
return Buffer.concat([satoshisBuffer, scriptBuffer]);
|
||||
};
|
||||
|
||||
exports.encodeOutputMempoolValue = function(satoshis, timestampBuffer, scriptBuffer) {
|
||||
var satoshisBuffer = new Buffer(8);
|
||||
satoshisBuffer.writeDoubleBE(satoshis);
|
||||
return Buffer.concat([satoshisBuffer, timestampBuffer, scriptBuffer]);
|
||||
};
|
||||
|
||||
exports.decodeOutputValue = function(buffer) {
|
||||
var satoshis = buffer.readDoubleBE(0);
|
||||
var scriptBuffer = buffer.slice(8, buffer.length);
|
||||
return {
|
||||
satoshis: satoshis,
|
||||
scriptBuffer: scriptBuffer
|
||||
};
|
||||
};
|
||||
|
||||
exports.decodeOutputMempoolValue = function(buffer) {
|
||||
var satoshis = buffer.readDoubleBE(0);
|
||||
var timestamp = buffer.readDoubleBE(8);
|
||||
var scriptBuffer = buffer.slice(16, buffer.length);
|
||||
return {
|
||||
satoshis: satoshis,
|
||||
timestamp: timestamp,
|
||||
scriptBuffer: scriptBuffer
|
||||
};
|
||||
};
|
||||
|
||||
exports.encodeInputKey = function(hashBuffer, hashTypeBuffer, height, prevTxIdBuffer, outputIndex) {
|
||||
var heightBuffer = new Buffer(4);
|
||||
heightBuffer.writeUInt32BE(height);
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||
return Buffer.concat([
|
||||
constants.PREFIXES.SPENTS,
|
||||
hashBuffer,
|
||||
hashTypeBuffer,
|
||||
constants.SPACER_MIN,
|
||||
heightBuffer,
|
||||
prevTxIdBuffer,
|
||||
outputIndexBuffer
|
||||
]);
|
||||
};
|
||||
|
||||
exports.decodeInputKey = function(buffer) {
|
||||
var reader = new BufferReader(buffer);
|
||||
var prefix = reader.read(1);
|
||||
var hashBuffer = reader.read(20);
|
||||
var hashTypeBuffer = reader.read(1);
|
||||
var spacer = reader.read(1);
|
||||
var height = reader.readUInt32BE();
|
||||
var prevTxId = reader.read(32);
|
||||
var outputIndex = reader.readUInt32BE();
|
||||
return {
|
||||
prefix: prefix,
|
||||
hashBuffer: hashBuffer,
|
||||
hashTypeBuffer: hashTypeBuffer,
|
||||
height: height,
|
||||
prevTxId: prevTxId,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
};
|
||||
|
||||
exports.encodeInputValue = function(txidBuffer, inputIndex) {
|
||||
var inputIndexBuffer = new Buffer(4);
|
||||
inputIndexBuffer.writeUInt32BE(inputIndex);
|
||||
return Buffer.concat([
|
||||
txidBuffer,
|
||||
inputIndexBuffer
|
||||
]);
|
||||
};
|
||||
|
||||
exports.decodeInputValue = function(buffer) {
|
||||
var txid = buffer.slice(0, 32);
|
||||
var inputIndex = buffer.readUInt32BE(32);
|
||||
return {
|
||||
txid: txid,
|
||||
inputIndex: inputIndex
|
||||
};
|
||||
};
|
||||
|
||||
exports.encodeInputKeyMap = function(outputTxIdBuffer, outputIndex) {
|
||||
var outputIndexBuffer = new Buffer(4);
|
||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||
return Buffer.concat([
|
||||
constants.PREFIXES.SPENTSMAP,
|
||||
outputTxIdBuffer,
|
||||
outputIndexBuffer
|
||||
]);
|
||||
};
|
||||
|
||||
exports.decodeInputKeyMap = function(buffer) {
|
||||
var txid = buffer.slice(1, 33);
|
||||
var outputIndex = buffer.readUInt32BE(33);
|
||||
return {
|
||||
outputTxId: txid,
|
||||
outputIndex: outputIndex
|
||||
};
|
||||
};
|
||||
|
||||
exports.encodeInputValueMap = function(inputTxIdBuffer, inputIndex) {
|
||||
var inputIndexBuffer = new Buffer(4);
|
||||
inputIndexBuffer.writeUInt32BE(inputIndex);
|
||||
return Buffer.concat([
|
||||
inputTxIdBuffer,
|
||||
inputIndexBuffer
|
||||
]);
|
||||
};
|
||||
|
||||
exports.decodeInputValueMap = function(buffer) {
|
||||
var txid = buffer.slice(0, 32);
|
||||
var inputIndex = buffer.readUInt32BE(32);
|
||||
return {
|
||||
inputTxId: txid,
|
||||
inputIndex: inputIndex
|
||||
};
|
||||
};
|
||||
|
||||
exports.encodeSummaryCacheKey = function(address) {
|
||||
return Buffer.concat([address.hashBuffer, constants.HASH_TYPES_MAP[address.type]]);
|
||||
};
|
||||
|
||||
exports.decodeSummaryCacheKey = function(buffer, network) {
|
||||
var hashBuffer = buffer.read(20);
|
||||
var type = constants.HASH_TYPES_READABLE[buffer.read(20, 2).toString('hex')];
|
||||
var address = new Address({
|
||||
hashBuffer: hashBuffer,
|
||||
type: type,
|
||||
network: network
|
||||
});
|
||||
return address;
|
||||
};
|
||||
|
||||
exports.encodeSummaryCacheValue = function(cache, tipHeight, tipHash) {
|
||||
var tipHashBuffer = new Buffer(tipHash, 'hex');
|
||||
var buffer = new Buffer(new Array(20));
|
||||
buffer.writeUInt32BE(tipHeight);
|
||||
buffer.writeDoubleBE(cache.result.totalReceived, 4);
|
||||
buffer.writeDoubleBE(cache.result.balance, 12);
|
||||
var txidBuffers = [];
|
||||
for (var i = 0; i < cache.result.txids.length; i++) {
|
||||
var buf = new Buffer(new Array(36));
|
||||
var txid = cache.result.txids[i];
|
||||
buf.write(txid, 'hex');
|
||||
buf.writeUInt32BE(cache.result.appearanceIds[txid], 32);
|
||||
txidBuffers.push(buf);
|
||||
}
|
||||
var txidsBuffer = Buffer.concat(txidBuffers);
|
||||
var value = Buffer.concat([tipHashBuffer, buffer, txidsBuffer]);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
exports.decodeSummaryCacheValue = function(buffer) {
|
||||
|
||||
var hash = buffer.slice(0, 32).toString('hex');
|
||||
var height = buffer.readUInt32BE(32);
|
||||
var totalReceived = buffer.readDoubleBE(36);
|
||||
var balance = buffer.readDoubleBE(44);
|
||||
|
||||
// read 32 byte chunks until exhausted
|
||||
var appearanceIds = {};
|
||||
var txids = [];
|
||||
var pos = 52;
|
||||
while(pos < buffer.length) {
|
||||
var txid = buffer.slice(pos, pos + 32).toString('hex');
|
||||
var txidHeight = buffer.readUInt32BE(pos + 32);
|
||||
txids.push(txid);
|
||||
appearanceIds[txid] = txidHeight;
|
||||
pos += 36;
|
||||
}
|
||||
|
||||
var cache = {
|
||||
height: height,
|
||||
hash: hash,
|
||||
result: {
|
||||
appearanceIds: appearanceIds,
|
||||
txids: txids,
|
||||
totalReceived: totalReceived,
|
||||
balance: balance,
|
||||
unconfirmedAppearanceIds: {}, // unconfirmed values are never stored in cache
|
||||
unconfirmedBalance: 0
|
||||
}
|
||||
};
|
||||
|
||||
return cache;
|
||||
};
|
||||
|
||||
exports.getAddressInfo = function(addressStr) {
|
||||
var addrObj = bitcore.Address(addressStr);
|
||||
var hashTypeBuffer = constants.HASH_TYPES_MAP[addrObj.type];
|
||||
|
||||
return {
|
||||
hashBuffer: addrObj.hashBuffer,
|
||||
hashTypeBuffer: hashTypeBuffer,
|
||||
hashTypeReadable: addrObj.type
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is optimized to return address information about an output script
|
||||
* without constructing a Bitcore Address instance.
|
||||
* @param {Script} - An instance of a Bitcore Script
|
||||
* @param {Network|String} - The network for the address
|
||||
*/
|
||||
exports.extractAddressInfoFromScript = function(script, network) {
|
||||
$.checkArgument(network, 'Second argument is expected to be a network');
|
||||
var hashBuffer;
|
||||
var addressType;
|
||||
var hashTypeBuffer;
|
||||
if (script.isPublicKeyHashOut()) {
|
||||
hashBuffer = script.chunks[2].buf;
|
||||
hashTypeBuffer = constants.HASH_TYPES.PUBKEY;
|
||||
addressType = Address.PayToPublicKeyHash;
|
||||
} else if (script.isScriptHashOut()) {
|
||||
hashBuffer = script.chunks[1].buf;
|
||||
hashTypeBuffer = constants.HASH_TYPES.REDEEMSCRIPT;
|
||||
addressType = Address.PayToScriptHash;
|
||||
} else if (script.isPublicKeyOut()) {
|
||||
var pubkey = script.chunks[0].buf;
|
||||
var address = Address.fromPublicKey(new PublicKey(pubkey), network);
|
||||
hashBuffer = address.hashBuffer;
|
||||
hashTypeBuffer = constants.HASH_TYPES.PUBKEY;
|
||||
// pay-to-publickey doesn't have an address, however for compatibility
|
||||
// purposes, we can create an address
|
||||
addressType = Address.PayToPublicKeyHash;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
hashBuffer: hashBuffer,
|
||||
hashTypeBuffer: hashTypeBuffer,
|
||||
addressType: addressType
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = exports;
|
|
@ -1,260 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore-lib');
|
||||
var async = require('async');
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var constants = require('./constants');
|
||||
|
||||
/**
|
||||
* This represents an instance that keeps track of data over a series of
|
||||
* asynchronous I/O calls to get the transaction history for a group of
|
||||
* addresses. History can be queried by start and end block heights to limit large sets
|
||||
* of results (uses leveldb key streaming).
|
||||
*/
|
||||
function AddressHistory(args) {
|
||||
this.node = args.node;
|
||||
this.options = args.options;
|
||||
|
||||
if(Array.isArray(args.addresses)) {
|
||||
this.addresses = args.addresses;
|
||||
} else {
|
||||
this.addresses = [args.addresses];
|
||||
}
|
||||
|
||||
this.maxHistoryQueryLength = args.options.maxHistoryQueryLength || constants.MAX_HISTORY_QUERY_LENGTH;
|
||||
this.maxAddressesQuery = args.options.maxAddressesQuery || constants.MAX_ADDRESSES_QUERY;
|
||||
|
||||
this.addressStrings = [];
|
||||
for (var i = 0; i < this.addresses.length; i++) {
|
||||
var address = this.addresses[i];
|
||||
if (address instanceof bitcore.Address) {
|
||||
this.addressStrings.push(address.toString());
|
||||
} else if (_.isString(address)) {
|
||||
this.addressStrings.push(address);
|
||||
} else {
|
||||
throw new TypeError('Addresses are expected to be strings');
|
||||
}
|
||||
}
|
||||
|
||||
this.detailedArray = [];
|
||||
}
|
||||
|
||||
AddressHistory.prototype._mergeAndSortTxids = function(summaries) {
|
||||
var appearanceIds = {};
|
||||
var unconfirmedAppearanceIds = {};
|
||||
for (var i = 0; i < summaries.length; i++) {
|
||||
var summary = summaries[i];
|
||||
for (var key in summary.appearanceIds) {
|
||||
appearanceIds[key] = summary.appearanceIds[key];
|
||||
delete summary.appearanceIds[key];
|
||||
}
|
||||
for (var unconfirmedKey in summary.unconfirmedAppearanceIds) {
|
||||
unconfirmedAppearanceIds[unconfirmedKey] = summary.unconfirmedAppearanceIds[unconfirmedKey];
|
||||
delete summary.unconfirmedAppearanceIds[key];
|
||||
}
|
||||
}
|
||||
var confirmedTxids = Object.keys(appearanceIds);
|
||||
confirmedTxids.sort(function(a, b) {
|
||||
// Confirmed are sorted by height
|
||||
return appearanceIds[a] - appearanceIds[b];
|
||||
});
|
||||
var unconfirmedTxids = Object.keys(unconfirmedAppearanceIds);
|
||||
unconfirmedTxids.sort(function(a, b) {
|
||||
// Unconfirmed are sorted by timestamp
|
||||
return unconfirmedAppearanceIds[a] - unconfirmedAppearanceIds[b];
|
||||
});
|
||||
return confirmedTxids.concat(unconfirmedTxids);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will give detailed history for the configured
|
||||
* addresses. See AddressService.prototype.getAddressHistory
|
||||
* for complete documentation about options and response format.
|
||||
*/
|
||||
AddressHistory.prototype.get = function(callback) {
|
||||
var self = this;
|
||||
if (this.addresses.length > this.maxAddressesQuery) {
|
||||
return callback(new TypeError('Maximum number of addresses (' + this.maxAddressesQuery + ') exceeded'));
|
||||
}
|
||||
|
||||
if (this.addresses.length === 1) {
|
||||
var address = this.addresses[0];
|
||||
self.node.services.address.getAddressSummary(address, this.options, function(err, summary) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return self._paginateWithDetails.call(self, summary.txids, callback);
|
||||
});
|
||||
} else {
|
||||
var opts = _.clone(this.options);
|
||||
opts.fullTxList = true;
|
||||
async.map(
|
||||
self.addresses,
|
||||
function(address, next) {
|
||||
self.node.services.address.getAddressSummary(address, opts, next);
|
||||
},
|
||||
function(err, summaries) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var txids = self._mergeAndSortTxids(summaries);
|
||||
return self._paginateWithDetails.call(self, txids, callback);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
AddressHistory.prototype._paginateWithDetails = function(allTxids, callback) {
|
||||
var self = this;
|
||||
var totalCount = allTxids.length;
|
||||
|
||||
// Slice the page starting with the most recent
|
||||
var txids;
|
||||
if (self.options.from >= 0 && self.options.to >= 0) {
|
||||
var fromOffset = totalCount - self.options.from;
|
||||
var toOffset = totalCount - self.options.to;
|
||||
txids = allTxids.slice(toOffset, fromOffset);
|
||||
} else {
|
||||
txids = allTxids;
|
||||
}
|
||||
|
||||
// Verify that this query isn't too long
|
||||
if (txids.length > self.maxHistoryQueryLength) {
|
||||
return callback(new Error(
|
||||
'Maximum length query (' + self.maxHistoryQueryLength + ') exceeded for address(es): ' +
|
||||
self.addresses.join(',')
|
||||
));
|
||||
}
|
||||
|
||||
// Reverse to include most recent at the top
|
||||
txids.reverse();
|
||||
|
||||
async.eachSeries(
|
||||
txids,
|
||||
function(txid, next) {
|
||||
self.getDetailedInfo(txid, next);
|
||||
},
|
||||
function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, {
|
||||
totalCount: totalCount,
|
||||
items: self.detailedArray
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will transform items from the combinedArray into
|
||||
* the detailedArray with the full transaction, satoshis and confirmation.
|
||||
* @param {Object} txInfo - An item from the `combinedArray`
|
||||
* @param {Function} next
|
||||
*/
|
||||
AddressHistory.prototype.getDetailedInfo = function(txid, next) {
|
||||
var self = this;
|
||||
var queryMempool = _.isUndefined(self.options.queryMempool) ? true : self.options.queryMempool;
|
||||
|
||||
self.node.services.db.getTransactionWithBlockInfo(
|
||||
txid,
|
||||
queryMempool,
|
||||
function(err, transaction) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
transaction.populateInputs(self.node.services.db, [], function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var addressDetails = self.getAddressDetailsForTransaction(transaction);
|
||||
|
||||
self.detailedArray.push({
|
||||
addresses: addressDetails.addresses,
|
||||
satoshis: addressDetails.satoshis,
|
||||
height: transaction.__height,
|
||||
confirmations: self.getConfirmationsDetail(transaction),
|
||||
timestamp: transaction.__timestamp,
|
||||
// TODO bitcore-lib should return null instead of throwing error on coinbase
|
||||
fees: !transaction.isCoinbase() ? transaction.getFee() : null,
|
||||
tx: transaction
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper function for `getDetailedInfo` for getting the confirmations.
|
||||
* @param {Transaction} transaction - A transaction with a populated __height value.
|
||||
*/
|
||||
AddressHistory.prototype.getConfirmationsDetail = function(transaction) {
|
||||
var confirmations = 0;
|
||||
if (transaction.__height >= 0) {
|
||||
confirmations = this.node.services.db.tip.__height - transaction.__height + 1;
|
||||
}
|
||||
return confirmations;
|
||||
};
|
||||
|
||||
AddressHistory.prototype.getAddressDetailsForTransaction = function(transaction) {
|
||||
var result = {
|
||||
addresses: {},
|
||||
satoshis: 0
|
||||
};
|
||||
|
||||
for (var inputIndex = 0; inputIndex < transaction.inputs.length; inputIndex++) {
|
||||
var input = transaction.inputs[inputIndex];
|
||||
if (!input.script) {
|
||||
continue;
|
||||
}
|
||||
var inputAddress = input.script.toAddress(this.node.network);
|
||||
if (inputAddress) {
|
||||
var inputAddressString = inputAddress.toString();
|
||||
if (this.addressStrings.indexOf(inputAddressString) >= 0) {
|
||||
if (!result.addresses[inputAddressString]) {
|
||||
result.addresses[inputAddressString] = {
|
||||
inputIndexes: [inputIndex],
|
||||
outputIndexes: []
|
||||
};
|
||||
} else {
|
||||
result.addresses[inputAddressString].inputIndexes.push(inputIndex);
|
||||
}
|
||||
result.satoshis -= input.output.satoshis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var outputIndex = 0; outputIndex < transaction.outputs.length; outputIndex++) {
|
||||
var output = transaction.outputs[outputIndex];
|
||||
if (!output.script) {
|
||||
continue;
|
||||
}
|
||||
var outputAddress = output.script.toAddress(this.node.network);
|
||||
if (outputAddress) {
|
||||
var outputAddressString = outputAddress.toString();
|
||||
if (this.addressStrings.indexOf(outputAddressString) >= 0) {
|
||||
if (!result.addresses[outputAddressString]) {
|
||||
result.addresses[outputAddressString] = {
|
||||
inputIndexes: [],
|
||||
outputIndexes: [outputIndex]
|
||||
};
|
||||
} else {
|
||||
result.addresses[outputAddressString].outputIndexes.push(outputIndex);
|
||||
}
|
||||
result.satoshis += output.satoshis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
};
|
||||
|
||||
module.exports = AddressHistory;
|
File diff suppressed because it is too large
Load Diff
|
@ -1,40 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var Transform = require('stream').Transform;
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var encodingUtil = require('../encoding');
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
function InputsTransformStream(options) {
|
||||
$.checkArgument(options.address instanceof bitcore.Address);
|
||||
Transform.call(this, {
|
||||
objectMode: true
|
||||
});
|
||||
this._address = options.address;
|
||||
this._addressStr = this._address.toString();
|
||||
this._tipHeight = options.tipHeight;
|
||||
}
|
||||
inherits(InputsTransformStream, Transform);
|
||||
|
||||
InputsTransformStream.prototype._transform = function(chunk, encoding, callback) {
|
||||
var self = this;
|
||||
|
||||
var key = encodingUtil.decodeInputKey(chunk.key);
|
||||
var value = encodingUtil.decodeInputValue(chunk.value);
|
||||
|
||||
var input = {
|
||||
address: this._addressStr,
|
||||
hashType: this._address.type,
|
||||
txid: value.txid.toString('hex'),
|
||||
inputIndex: value.inputIndex,
|
||||
height: key.height,
|
||||
confirmations: this._tipHeight - key.height + 1
|
||||
};
|
||||
|
||||
self.push(input);
|
||||
callback();
|
||||
|
||||
};
|
||||
|
||||
module.exports = InputsTransformStream;
|
|
@ -1,42 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var Transform = require('stream').Transform;
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var encodingUtil = require('../encoding');
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
function OutputsTransformStream(options) {
|
||||
Transform.call(this, {
|
||||
objectMode: true
|
||||
});
|
||||
$.checkArgument(options.address instanceof bitcore.Address);
|
||||
this._address = options.address;
|
||||
this._addressStr = this._address.toString();
|
||||
this._tipHeight = options.tipHeight;
|
||||
}
|
||||
inherits(OutputsTransformStream, Transform);
|
||||
|
||||
OutputsTransformStream.prototype._transform = function(chunk, encoding, callback) {
|
||||
var self = this;
|
||||
|
||||
var key = encodingUtil.decodeOutputKey(chunk.key);
|
||||
var value = encodingUtil.decodeOutputValue(chunk.value);
|
||||
|
||||
var output = {
|
||||
address: this._addressStr,
|
||||
hashType: this._address.type,
|
||||
txid: key.txid.toString('hex'), //TODO use a buffer
|
||||
outputIndex: key.outputIndex,
|
||||
height: key.height,
|
||||
satoshis: value.satoshis,
|
||||
script: value.scriptBuffer.toString('hex'), //TODO use a buffer
|
||||
confirmations: this._tipHeight - key.height + 1
|
||||
};
|
||||
|
||||
self.push(output);
|
||||
callback();
|
||||
|
||||
};
|
||||
|
||||
module.exports = OutputsTransformStream;
|
File diff suppressed because it is too large
Load Diff
|
@ -1,805 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var util = require('util');
|
||||
var fs = require('fs');
|
||||
var async = require('async');
|
||||
var levelup = require('levelup');
|
||||
var leveldown = require('leveldown');
|
||||
var mkdirp = require('mkdirp');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
var Networks = bitcore.Networks;
|
||||
var Block = bitcore.Block;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var index = require('../');
|
||||
var errors = index.errors;
|
||||
var log = index.log;
|
||||
var Transaction = require('../transaction');
|
||||
var Service = require('../service');
|
||||
|
||||
/**
|
||||
* This service synchronizes a leveldb database with bitcoin block chain by connecting and
|
||||
* disconnecting blocks to build new indexes that can be queried. Other services can extend
|
||||
* the data that is indexed by implementing a `blockHandler` method.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Node} options.node - A reference to the node
|
||||
* @param {Node} options.store - A levelup backend store
|
||||
*/
|
||||
function DB(options) {
|
||||
/* jshint maxstatements: 20 */
|
||||
|
||||
if (!(this instanceof DB)) {
|
||||
return new DB(options);
|
||||
}
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
Service.call(this, options);
|
||||
|
||||
// Used to keep track of the version of the indexes
|
||||
// to determine during an upgrade if a reindex is required
|
||||
this.version = 2;
|
||||
|
||||
this.tip = null;
|
||||
this.genesis = null;
|
||||
|
||||
$.checkState(this.node.network, 'Node is expected to have a "network" property');
|
||||
this.network = this.node.network;
|
||||
|
||||
this._setDataPath();
|
||||
|
||||
this.maxOpenFiles = options.maxOpenFiles || DB.DEFAULT_MAX_OPEN_FILES;
|
||||
|
||||
this.levelupStore = leveldown;
|
||||
if (options.store) {
|
||||
this.levelupStore = options.store;
|
||||
}
|
||||
|
||||
this.retryInterval = 60000;
|
||||
|
||||
this.subscriptions = {
|
||||
transaction: [],
|
||||
block: []
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(DB, Service);
|
||||
|
||||
DB.dependencies = ['bitcoind'];
|
||||
|
||||
DB.PREFIXES = {
|
||||
VERSION: new Buffer('ff', 'hex'),
|
||||
BLOCKS: new Buffer('01', 'hex'),
|
||||
TIP: new Buffer('04', 'hex')
|
||||
};
|
||||
|
||||
DB.DEFAULT_MAX_OPEN_FILES = 200;
|
||||
|
||||
/**
|
||||
* This function will set `this.dataPath` based on `this.node.network`.
|
||||
* @private
|
||||
*/
|
||||
DB.prototype._setDataPath = function() {
|
||||
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
|
||||
var regtest = Networks.get('regtest');
|
||||
if (this.node.network === Networks.livenet) {
|
||||
this.dataPath = this.node.datadir + '/bitcore-node.db';
|
||||
} else if (this.node.network === Networks.testnet) {
|
||||
this.dataPath = this.node.datadir + '/testnet3/bitcore-node.db';
|
||||
} else if (this.node.network === regtest) {
|
||||
this.dataPath = this.node.datadir + '/regtest/bitcore-node.db';
|
||||
} else {
|
||||
throw new Error('Unknown network: ' + this.network);
|
||||
}
|
||||
};
|
||||
|
||||
DB.prototype._checkVersion = function(callback) {
|
||||
var self = this;
|
||||
var options = {
|
||||
keyEncoding: 'binary',
|
||||
valueEncoding: 'binary'
|
||||
};
|
||||
self.store.get(DB.PREFIXES.TIP, options, function(err) {
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
// The database is brand new and doesn't have a tip stored
|
||||
// we can skip version checking
|
||||
return callback();
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.store.get(DB.PREFIXES.VERSION, options, function(err, buffer) {
|
||||
var version;
|
||||
if (err instanceof levelup.errors.NotFoundError) {
|
||||
// The initial version (1) of the database didn't store the version number
|
||||
version = 1;
|
||||
} else if (err) {
|
||||
return callback(err);
|
||||
} else {
|
||||
version = buffer.readUInt32BE();
|
||||
}
|
||||
if (self.version !== version) {
|
||||
var helpUrl = 'https://github.com/bitpay/bitcore-node/blob/master/docs/services/db.md#how-to-reindex';
|
||||
return callback(new Error(
|
||||
'The version of the database "' + version + '" does not match the expected version "' +
|
||||
self.version + '". A recreation of "' + self.dataPath + '" (can take several hours) is ' +
|
||||
'required or to switch versions of software to match. Please see ' + helpUrl +
|
||||
' for more information.'
|
||||
));
|
||||
}
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
DB.prototype._setVersion = function(callback) {
|
||||
var versionBuffer = new Buffer(new Array(4));
|
||||
versionBuffer.writeUInt32BE(this.version);
|
||||
this.store.put(DB.PREFIXES.VERSION, versionBuffer, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by Node to start the service.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.start = function(callback) {
|
||||
|
||||
var self = this;
|
||||
if (!fs.existsSync(this.dataPath)) {
|
||||
mkdirp.sync(this.dataPath);
|
||||
}
|
||||
|
||||
this.genesis = Block.fromBuffer(this.node.services.bitcoind.genesisBuffer);
|
||||
this.store = levelup(this.dataPath, { db: this.levelupStore, maxOpenFiles: this.maxOpenFiles });
|
||||
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
|
||||
|
||||
this.once('ready', function() {
|
||||
log.info('Bitcoin Database Ready');
|
||||
|
||||
// Notify that there is a new tip
|
||||
self.node.services.bitcoind.on('tip', function(height) {
|
||||
if(!self.node.stopping) {
|
||||
self.sync();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async.series([
|
||||
function(next) {
|
||||
self._checkVersion(next);
|
||||
},
|
||||
function(next) {
|
||||
self._setVersion(next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.loadTip(function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.sync();
|
||||
self.emit('ready');
|
||||
setImmediate(callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by Node to stop the service
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.stop = function(callback) {
|
||||
var self = this;
|
||||
|
||||
// Wait until syncing stops and all db operations are completed before closing leveldb
|
||||
async.whilst(function() {
|
||||
return self.bitcoindSyncing;
|
||||
}, function(next) {
|
||||
setTimeout(next, 10);
|
||||
}, function() {
|
||||
self.store.close(callback);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Will give information about the database from bitcoin.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.getInfo = function(callback) {
|
||||
var self = this;
|
||||
setImmediate(function() {
|
||||
var info = self.node.bitcoind.getInfo();
|
||||
callback(null, info);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the underlying store database
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.close = function(callback) {
|
||||
this.store.close(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is responsible for emitting `db/transaction` events.
|
||||
* @param {Object} txInfo - The data from the bitcoind.on('tx') event
|
||||
* @param {Buffer} txInfo.buffer - The transaction buffer
|
||||
* @param {Boolean} txInfo.mempool - If the transaction was accepted in the mempool
|
||||
* @param {String} txInfo.hash - The hash of the transaction
|
||||
*/
|
||||
DB.prototype.transactionHandler = function(txInfo) {
|
||||
var tx = Transaction().fromBuffer(txInfo.buffer);
|
||||
for (var i = 0; i < this.subscriptions.transaction.length; i++) {
|
||||
this.subscriptions.transaction[i].emit('db/transaction', {
|
||||
rejected: !txInfo.mempool,
|
||||
tx: tx
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by Node to determine the available API methods.
|
||||
*/
|
||||
DB.prototype.getAPIMethods = function() {
|
||||
var methods = [
|
||||
['getBlock', this, this.getBlock, 1],
|
||||
['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2],
|
||||
['getTransaction', this, this.getTransaction, 2],
|
||||
['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2],
|
||||
['sendTransaction', this, this.sendTransaction, 1],
|
||||
['estimateFee', this, this.estimateFee, 1]
|
||||
];
|
||||
return methods;
|
||||
};
|
||||
|
||||
DB.prototype.loadTip = function(callback) {
|
||||
var self = this;
|
||||
|
||||
var options = {
|
||||
keyEncoding: 'binary',
|
||||
valueEncoding: 'binary'
|
||||
};
|
||||
|
||||
self.store.get(DB.PREFIXES.TIP, options, function(err, tipData) {
|
||||
if(err && err instanceof levelup.errors.NotFoundError) {
|
||||
self.tip = self.genesis;
|
||||
self.tip.__height = 0;
|
||||
self.connectBlock(self.genesis, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.emit('addblock', self.genesis);
|
||||
callback();
|
||||
});
|
||||
return;
|
||||
} else if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var hash = tipData.toString('hex');
|
||||
|
||||
var times = 0;
|
||||
async.retry({times: 3, interval: self.retryInterval}, function(done) {
|
||||
self.getBlock(hash, function(err, tip) {
|
||||
if(err) {
|
||||
times++;
|
||||
log.warn('Bitcoind does not have our tip (' + hash + '). Bitcoind may have crashed and needs to catch up.');
|
||||
if(times < 3) {
|
||||
log.warn('Retrying in ' + (self.retryInterval / 1000) + ' seconds.');
|
||||
}
|
||||
return done(err);
|
||||
}
|
||||
|
||||
done(null, tip);
|
||||
});
|
||||
}, function(err, tip) {
|
||||
if(err) {
|
||||
log.warn('Giving up after 3 tries. Please report this bug to https://github.com/bitpay/bitcore-node/issues');
|
||||
log.warn('Please reindex your database.');
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.tip = tip;
|
||||
var blockIndex = self.node.services.bitcoind.getBlockIndex(self.tip.hash);
|
||||
if(!blockIndex) {
|
||||
return callback(new Error('Could not get height for tip.'));
|
||||
}
|
||||
self.tip.__height = blockIndex.height;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Will get a block from bitcoind and give a Bitcore Block
|
||||
* @param {String|Number} hash - A block hash or block height
|
||||
*/
|
||||
DB.prototype.getBlock = function(hash, callback) {
|
||||
this.node.services.bitcoind.getBlock(hash, function(err, blockBuffer) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, Block.fromBuffer(blockBuffer));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get block hashes between two timestamps
|
||||
* @param {Number} high - high timestamp, in seconds, inclusive
|
||||
* @param {Number} low - low timestamp, in seconds, inclusive
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
|
||||
var self = this;
|
||||
var hashes = [];
|
||||
var lowKey;
|
||||
var highKey;
|
||||
|
||||
try {
|
||||
lowKey = this._encodeBlockIndexKey(low);
|
||||
highKey = this._encodeBlockIndexKey(high);
|
||||
} catch(e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
var stream = this.store.createReadStream({
|
||||
gte: lowKey,
|
||||
lte: highKey,
|
||||
reverse: true,
|
||||
valueEncoding: 'binary',
|
||||
keyEncoding: 'binary'
|
||||
});
|
||||
|
||||
stream.on('data', function(data) {
|
||||
hashes.push(self._decodeBlockIndexValue(data.value));
|
||||
});
|
||||
|
||||
var error;
|
||||
|
||||
stream.on('error', function(streamError) {
|
||||
if (streamError) {
|
||||
error = streamError;
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('close', function() {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
callback(null, hashes);
|
||||
});
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* Will give a Bitcore Transaction from bitcoind by txid
|
||||
* @param {String} txid - A transaction hash
|
||||
* @param {Boolean} queryMempool - Include the mempool
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.getTransaction = function(txid, queryMempool, callback) {
|
||||
this.node.services.bitcoind.getTransaction(txid, queryMempool, function(err, txBuffer) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (!txBuffer) {
|
||||
return callback(new errors.Transaction.NotFound());
|
||||
}
|
||||
|
||||
callback(null, Transaction().fromBuffer(txBuffer));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Will give a Bitcore Transaction and populated information about the block included.
|
||||
* @param {String} txid - A transaction hash
|
||||
* @param {Boolean} queryMempool - Include the mempool
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
|
||||
this.node.services.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, obj) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var tx = Transaction().fromBuffer(obj.buffer);
|
||||
tx.__blockHash = obj.blockHash;
|
||||
tx.__height = obj.height;
|
||||
tx.__timestamp = obj.timestamp;
|
||||
|
||||
callback(null, tx);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Will send a transaction to the Bitcoin network.
|
||||
* @param {Transaction} tx - An instance of a Bitcore Transaction
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.sendTransaction = function(tx, callback) {
|
||||
var txString;
|
||||
if (tx instanceof Transaction) {
|
||||
txString = tx.serialize();
|
||||
} else {
|
||||
txString = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
var txid = this.node.services.bitcoind.sendTransaction(txString);
|
||||
return callback(null, txid);
|
||||
} catch(err) {
|
||||
return callback(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Will estimate fees for a transaction and give a result in
|
||||
* satoshis per kilobyte. Similar to the bitcoind estimateFee method.
|
||||
* @param {Number} blocks - The number of blocks for the transaction to be included.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.estimateFee = function(blocks, callback) {
|
||||
var self = this;
|
||||
setImmediate(function() {
|
||||
callback(null, self.node.services.bitcoind.estimateFee(blocks));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by the Bus to determine the available events.
|
||||
*/
|
||||
DB.prototype.getPublishEvents = function() {
|
||||
return [
|
||||
{
|
||||
name: 'db/transaction',
|
||||
scope: this,
|
||||
subscribe: this.subscribe.bind(this, 'transaction'),
|
||||
unsubscribe: this.unsubscribe.bind(this, 'transaction')
|
||||
},
|
||||
{
|
||||
name: 'db/block',
|
||||
scope: this,
|
||||
subscribe: this.subscribe.bind(this, 'block'),
|
||||
unsubscribe: this.unsubscribe.bind(this, 'block')
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
DB.prototype.subscribe = function(name, emitter) {
|
||||
this.subscriptions[name].push(emitter);
|
||||
};
|
||||
|
||||
DB.prototype.unsubscribe = function(name, emitter) {
|
||||
var index = this.subscriptions[name].indexOf(emitter);
|
||||
if (index > -1) {
|
||||
this.subscriptions[name].splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Will give the previous hash for a block.
|
||||
* @param {String} blockHash
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.getPrevHash = function(blockHash, callback) {
|
||||
var blockIndex = this.node.services.bitcoind.getBlockIndex(blockHash);
|
||||
setImmediate(function() {
|
||||
if (blockIndex) {
|
||||
callback(null, blockIndex.prevHash);
|
||||
} else {
|
||||
callback(new Error('Could not get prevHash, block not found'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Connects a block to the database and add indexes
|
||||
* @param {Block} block - The bitcore block
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.connectBlock = function(block, callback) {
|
||||
log.debug('DB handling new chain block');
|
||||
this.runAllBlockHandlers(block, true, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects a block from the database and removes indexes
|
||||
* @param {Block} block - The bitcore block
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.disconnectBlock = function(block, callback) {
|
||||
log.debug('DB removing chain block');
|
||||
this.runAllBlockHandlers(block, false, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Will collect all database operations for a block from other services that implement
|
||||
* `blockHandler` methods and then save operations to the database.
|
||||
* @param {Block} block - The bitcore block
|
||||
* @param {Boolean} add - If the block is being added/connected or removed/disconnected
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DB.prototype.runAllBlockHandlers = function(block, add, callback) {
|
||||
var self = this;
|
||||
var operations = [];
|
||||
|
||||
// Notify block subscribers
|
||||
for (var i = 0; i < this.subscriptions.block.length; i++) {
|
||||
this.subscriptions.block[i].emit('db/block', block.hash);
|
||||
}
|
||||
|
||||
// Update tip
|
||||
var tipHash = add ? new Buffer(block.hash, 'hex') : BufferUtil.reverse(block.header.prevHash);
|
||||
operations.push({
|
||||
type: 'put',
|
||||
key: DB.PREFIXES.TIP,
|
||||
value: tipHash
|
||||
});
|
||||
|
||||
// Update block index
|
||||
operations.push({
|
||||
type: add ? 'put' : 'del',
|
||||
key: this._encodeBlockIndexKey(block.header.timestamp),
|
||||
value: this._encodeBlockIndexValue(block.hash)
|
||||
});
|
||||
|
||||
async.eachSeries(
|
||||
this.node.services,
|
||||
function(mod, next) {
|
||||
if(mod.blockHandler) {
|
||||
$.checkArgument(typeof mod.blockHandler === 'function', 'blockHandler must be a function');
|
||||
|
||||
mod.blockHandler.call(mod, block, add, function(err, ops) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (ops) {
|
||||
$.checkArgument(Array.isArray(ops), 'blockHandler for ' + mod.name + ' returned non-array');
|
||||
operations = operations.concat(ops);
|
||||
}
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
setImmediate(next);
|
||||
}
|
||||
},
|
||||
function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
log.debug('Updating the database with operations', operations);
|
||||
self.store.batch(operations, callback);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
DB.prototype._encodeBlockIndexKey = function(timestamp) {
|
||||
$.checkArgument(timestamp >= 0 && timestamp <= 4294967295, 'timestamp out of bounds');
|
||||
var timestampBuffer = new Buffer(4);
|
||||
timestampBuffer.writeUInt32BE(timestamp);
|
||||
return Buffer.concat([DB.PREFIXES.BLOCKS, timestampBuffer]);
|
||||
};
|
||||
|
||||
DB.prototype._encodeBlockIndexValue = function(hash) {
|
||||
return new Buffer(hash, 'hex');
|
||||
};
|
||||
|
||||
DB.prototype._decodeBlockIndexValue = function(value) {
|
||||
return value.toString('hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will find the common ancestor between the current chain and a forked block,
|
||||
* by moving backwards on both chains until there is a meeting point.
|
||||
* @param {Block} block - The new tip that forks the current chain.
|
||||
* @param {Function} done - A callback function that is called when complete.
|
||||
*/
|
||||
DB.prototype.findCommonAncestor = function(block, done) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var mainPosition = self.tip.hash;
|
||||
var forkPosition = block.hash;
|
||||
|
||||
var mainHashesMap = {};
|
||||
var forkHashesMap = {};
|
||||
|
||||
mainHashesMap[mainPosition] = true;
|
||||
forkHashesMap[forkPosition] = true;
|
||||
|
||||
var commonAncestor = null;
|
||||
|
||||
async.whilst(
|
||||
function() {
|
||||
return !commonAncestor;
|
||||
},
|
||||
function(next) {
|
||||
|
||||
if(mainPosition) {
|
||||
var mainBlockIndex = self.node.services.bitcoind.getBlockIndex(mainPosition);
|
||||
if(mainBlockIndex && mainBlockIndex.prevHash) {
|
||||
mainHashesMap[mainBlockIndex.prevHash] = true;
|
||||
mainPosition = mainBlockIndex.prevHash;
|
||||
} else {
|
||||
mainPosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(forkPosition) {
|
||||
var forkBlockIndex = self.node.services.bitcoind.getBlockIndex(forkPosition);
|
||||
if(forkBlockIndex && forkBlockIndex.prevHash) {
|
||||
forkHashesMap[forkBlockIndex.prevHash] = true;
|
||||
forkPosition = forkBlockIndex.prevHash;
|
||||
} else {
|
||||
forkPosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(forkPosition && mainHashesMap[forkPosition]) {
|
||||
commonAncestor = forkPosition;
|
||||
}
|
||||
|
||||
if(mainPosition && forkHashesMap[mainPosition]) {
|
||||
commonAncestor = mainPosition;
|
||||
}
|
||||
|
||||
if(!mainPosition && !forkPosition) {
|
||||
return next(new Error('Unknown common ancestor'));
|
||||
}
|
||||
|
||||
setImmediate(next);
|
||||
},
|
||||
function(err) {
|
||||
done(err, commonAncestor);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will attempt to rewind the chain to the common ancestor
|
||||
* between the current chain and a forked block.
|
||||
* @param {Block} block - The new tip that forks the current chain.
|
||||
* @param {Function} done - A callback function that is called when complete.
|
||||
*/
|
||||
DB.prototype.syncRewind = function(block, done) {
|
||||
|
||||
var self = this;
|
||||
|
||||
self.findCommonAncestor(block, function(err, ancestorHash) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
log.warn('Reorg common ancestor found:', ancestorHash);
|
||||
// Rewind the chain to the common ancestor
|
||||
async.whilst(
|
||||
function() {
|
||||
// Wait until the tip equals the ancestor hash
|
||||
return self.tip.hash !== ancestorHash;
|
||||
},
|
||||
function(removeDone) {
|
||||
|
||||
var tip = self.tip;
|
||||
|
||||
// TODO: expose prevHash as a string from bitcore
|
||||
var prevHash = BufferUtil.reverse(tip.header.prevHash).toString('hex');
|
||||
|
||||
self.getBlock(prevHash, function(err, previousTip) {
|
||||
if (err) {
|
||||
removeDone(err);
|
||||
}
|
||||
|
||||
// Undo the related indexes for this block
|
||||
self.disconnectBlock(tip, function(err) {
|
||||
if (err) {
|
||||
return removeDone(err);
|
||||
}
|
||||
|
||||
// Set the new tip
|
||||
previousTip.__height = self.tip.__height - 1;
|
||||
self.tip = previousTip;
|
||||
self.emit('removeblock', tip);
|
||||
removeDone();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}, done
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will synchronize additional indexes for the chain based on
|
||||
* the current active chain in the bitcoin daemon. In the event that there is
|
||||
* a reorganization in the daemon, the chain will rewind to the last common
|
||||
* ancestor and then resume syncing.
|
||||
*/
|
||||
DB.prototype.sync = function() {
|
||||
var self = this;
|
||||
|
||||
if (self.bitcoindSyncing || self.node.stopping || !self.tip) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.bitcoindSyncing = true;
|
||||
|
||||
var height;
|
||||
|
||||
async.whilst(function() {
|
||||
height = self.tip.__height;
|
||||
return height < self.node.services.bitcoind.height && !self.node.stopping;
|
||||
}, function(done) {
|
||||
self.node.services.bitcoind.getBlock(height + 1, function(err, blockBuffer) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var block = Block.fromBuffer(blockBuffer);
|
||||
|
||||
// TODO: expose prevHash as a string from bitcore
|
||||
var prevHash = BufferUtil.reverse(block.header.prevHash).toString('hex');
|
||||
|
||||
if (prevHash === self.tip.hash) {
|
||||
|
||||
// This block appends to the current chain tip and we can
|
||||
// immediately add it to the chain and create indexes.
|
||||
|
||||
// Populate height
|
||||
block.__height = self.tip.__height + 1;
|
||||
|
||||
// Create indexes
|
||||
self.connectBlock(block, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
self.tip = block;
|
||||
log.debug('Chain added block to main chain');
|
||||
self.emit('addblock', block);
|
||||
setImmediate(done);
|
||||
});
|
||||
} else {
|
||||
// This block doesn't progress the current tip, so we'll attempt
|
||||
// to rewind the chain to the common ancestor of the block and
|
||||
// then we can resume syncing.
|
||||
log.warn('Beginning reorg! Current tip: ' + self.tip.hash + '; New tip: ' + block.hash);
|
||||
self.syncRewind(block, function(err) {
|
||||
if(err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
log.warn('Reorg complete. New tip is ' + self.tip.hash);
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
Error.captureStackTrace(err);
|
||||
return self.node.emit('error', err);
|
||||
}
|
||||
|
||||
if(self.node.stopping) {
|
||||
self.bitcoindSyncing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.node.services.bitcoind.isSynced()) {
|
||||
self.bitcoindSyncing = false;
|
||||
self.node.emit('synced');
|
||||
} else {
|
||||
self.bitcoindSyncing = false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
module.exports = DB;
|
|
@ -1,15 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
var socketio = require('socket.io');
|
||||
var BaseService = require('../service');
|
||||
var inherits = require('util').inherits;
|
||||
|
||||
var BaseService = require('../service');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var _ = bitcore.deps._;
|
||||
var index = require('../');
|
||||
var log = index.log;
|
||||
var fs = require('fs');
|
||||
|
||||
|
||||
/**
|
||||
* This service represents a hub for combining several services over a single HTTP port. Services
|
||||
|
@ -23,6 +27,7 @@ var fs = require('fs');
|
|||
* @param {Object} options.httpsOptions - Options passed into https.createServer, defaults to node settings.
|
||||
* @param {String} options.httpsOptions.key - Path to key file
|
||||
* @param {String} options.httpsOptions.cert - Path to cert file
|
||||
* @param {Boolean} options.enableSocketRPC - Option to enable/disable websocket RPC handling
|
||||
* @param {Number} options.port - The port for the service, defaults to node settings.
|
||||
*/
|
||||
var WebService = function(options) {
|
||||
|
@ -32,6 +37,13 @@ var WebService = function(options) {
|
|||
this.httpsOptions = options.httpsOptions || this.node.httpsOptions;
|
||||
this.port = options.port || this.node.port || 3456;
|
||||
|
||||
// set the maximum size of json payload, defaults to express default
|
||||
// see: https://github.com/expressjs/body-parser#limit
|
||||
this.jsonRequestLimit = options.jsonRequestLimit || '100kb';
|
||||
|
||||
this.enableSocketRPC = _.isUndefined(options.enableSocketRPC) ?
|
||||
WebService.DEFAULT_SOCKET_RPC : options.enableSocketRPC;
|
||||
|
||||
this.node.on('ready', function() {
|
||||
self.eventNames = self.getEventNames();
|
||||
self.setupAllRoutes();
|
||||
|
@ -43,6 +55,7 @@ var WebService = function(options) {
|
|||
inherits(WebService, BaseService);
|
||||
|
||||
WebService.dependencies = [];
|
||||
WebService.DEFAULT_SOCKET_RPC = true;
|
||||
|
||||
/**
|
||||
* Called by Node to start the service
|
||||
|
@ -50,7 +63,7 @@ WebService.dependencies = [];
|
|||
*/
|
||||
WebService.prototype.start = function(callback) {
|
||||
this.app = express();
|
||||
this.app.use(bodyParser.json());
|
||||
this.app.use(bodyParser.json({limit: this.jsonRequestLimit}));
|
||||
|
||||
if(this.https) {
|
||||
this.transformHttpsOptions();
|
||||
|
@ -149,21 +162,31 @@ WebService.prototype.getEventNames = function() {
|
|||
return eventNames;
|
||||
};
|
||||
|
||||
WebService.prototype._getRemoteAddress = function(socket) {
|
||||
return socket.client.request.headers['cf-connecting-ip'] || socket.conn.remoteAddress;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is responsible for managing a socket.io connection, including
|
||||
* instantiating a new Bus, subscribing/unsubscribing and handling RPC commands.
|
||||
* @param {Socket} socket - A socket.io socket instance
|
||||
*/
|
||||
WebService.prototype.socketHandler = function(socket) {
|
||||
var bus = this.node.openBus();
|
||||
var self = this;
|
||||
var remoteAddress = self._getRemoteAddress(socket);
|
||||
var bus = this.node.openBus({remoteAddress: remoteAddress});
|
||||
|
||||
socket.on('message', this.socketMessageHandler.bind(this));
|
||||
if (this.enableSocketRPC) {
|
||||
socket.on('message', this.socketMessageHandler.bind(this));
|
||||
}
|
||||
|
||||
socket.on('subscribe', function(name, params) {
|
||||
log.info(remoteAddress, 'web socket subscribe:', name);
|
||||
bus.subscribe(name, params);
|
||||
});
|
||||
|
||||
socket.on('unsubscribe', function(name, params) {
|
||||
log.info(remoteAddress, 'web socket unsubscribe:', name);
|
||||
bus.unsubscribe(name, params);
|
||||
});
|
||||
|
||||
|
@ -183,6 +206,7 @@ WebService.prototype.socketHandler = function(socket) {
|
|||
});
|
||||
|
||||
socket.on('disconnect', function() {
|
||||
log.info(remoteAddress, 'web socket disconnect');
|
||||
bus.close();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var levelup = require('levelup');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Transaction = bitcore.Transaction;
|
||||
|
||||
Transaction.prototype.populateInputs = function(db, poolTransactions, callback) {
|
||||
var self = this;
|
||||
|
||||
if(this.isCoinbase()) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
|
||||
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.getTransaction(txid, true, 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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Transaction;
|
18
lib/utils.js
18
lib/utils.js
|
@ -21,4 +21,22 @@ utils.startAtZero = function startAtZero(obj, key) {
|
|||
}
|
||||
};
|
||||
|
||||
utils.isAbsolutePath = require('path').isAbsolute;
|
||||
if (!utils.isAbsolutePath) {
|
||||
utils.isAbsolutePath = require('path-is-absolute');
|
||||
}
|
||||
|
||||
utils.parseParamsWithJSON = function parseParamsWithJSON(paramsArg) {
|
||||
var params = paramsArg.map(function(paramArg) {
|
||||
var param;
|
||||
try {
|
||||
param = JSON.parse(paramArg);
|
||||
} catch(err) {
|
||||
param = paramArg;
|
||||
}
|
||||
return param;
|
||||
});
|
||||
return params;
|
||||
};
|
||||
|
||||
module.exports = utils;
|
||||
|
|
88
package.json
88
package.json
|
@ -1,14 +1,13 @@
|
|||
{
|
||||
"name": "bitcore-node",
|
||||
"description": "Full node with extended capabilities using Bitcore and Bitcoin Core",
|
||||
"name": "bitcore-node-zcash",
|
||||
"description": "Full node with extended capabilities using Bitcore and Zcash",
|
||||
"author": "BitPay <dev@bitpay.com>",
|
||||
"version": "2.0.1",
|
||||
"lastBuild": "2.0.0",
|
||||
"version": "3.1.2",
|
||||
"main": "./index.js",
|
||||
"repository": "git://github.com/bitpay/bitcore-node.git",
|
||||
"homepage": "https://github.com/bitpay/bitcore-node",
|
||||
"repository": "git://github.com/zcash-hackworks/bitcore-node-zcash.git",
|
||||
"homepage": "https://github.com/zcash-hackworks/bitcore-node-zcash",
|
||||
"bugs": {
|
||||
"url": "https://github.com/bitpay/bitcore-node/issues"
|
||||
"url": "https://github.com/zcash-hackworks/bitcore-node-zcash/issues"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
|
@ -25,68 +24,69 @@
|
|||
{
|
||||
"name": "Patrick Nagurny",
|
||||
"email": "patrick@bitpay.com"
|
||||
},
|
||||
{
|
||||
"name": "Jack Grigg",
|
||||
"email": "jack@z.cash"
|
||||
},
|
||||
{
|
||||
"name": "Simon Liu",
|
||||
"email": "simon@z.cash"
|
||||
},
|
||||
{
|
||||
"name": "Ian Munoz",
|
||||
"email": "ian.org@gmail.com"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"bitcore-node": "./bin/bitcore-node"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "./bin/install",
|
||||
"build": "./bin/build",
|
||||
"clean": "./bin/clean",
|
||||
"package": "node bin/package.js",
|
||||
"upload": "node bin/upload.js",
|
||||
"start": "node bin/start.js",
|
||||
"test": "NODE_ENV=test mocha -R spec --recursive",
|
||||
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive",
|
||||
"libbitcoind": "node bin/start-libbitcoind.js"
|
||||
"test": "mocha -R spec --recursive",
|
||||
"regtest": "./scripts/regtest",
|
||||
"jshint": "jshint --reporter=node_modules/jshint-stylish ./lib",
|
||||
"coverage": "istanbul cover _mocha -- --recursive",
|
||||
"coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --recursive -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
|
||||
},
|
||||
"tags": [
|
||||
"bitcoin",
|
||||
"bitcoind"
|
||||
"zcash",
|
||||
"zcashd"
|
||||
],
|
||||
"dependencies": {
|
||||
"async": "^1.3.0",
|
||||
"bindings": "^1.2.1",
|
||||
"bitcore-lib": "^0.13.7",
|
||||
"bitcoind-rpc": "^0.6.0",
|
||||
"bitcore-lib-zcash": "zcash-hackworks/bitcore-lib-zcash",
|
||||
"body-parser": "^1.13.3",
|
||||
"colors": "^1.1.2",
|
||||
"commander": "^2.8.1",
|
||||
"errno": "^0.1.4",
|
||||
"express": "^4.13.3",
|
||||
"leveldown": "bitpay/leveldown#bitpay-1.4.4",
|
||||
"levelup": "^1.3.1",
|
||||
"liftoff": "^2.2.0",
|
||||
"memdown": "^1.0.0",
|
||||
"lru-cache": "^4.0.1",
|
||||
"mkdirp": "0.5.0",
|
||||
"nan": "^2.0.9",
|
||||
"npm": "^2.14.1",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"semver": "^5.0.1",
|
||||
"socket.io": "bitpay/socket.io#bitpay-1.3.7",
|
||||
"socket.io-client": "bitpay/socket.io-client#bitpay-1.3.7"
|
||||
"socket.io": "^1.4.5",
|
||||
"socket.io-client": "^1.4.5",
|
||||
"zmq": "^2.14.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "~1.2.1",
|
||||
"utf-8-validate": "~1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-sdk": "~2.0.0-rc.15",
|
||||
"benchmark": "1.0.0",
|
||||
"bitcoin": "^2.3.2",
|
||||
"bitcoind-rpc": "^0.3.0",
|
||||
"chai": "^3.0.0",
|
||||
"mocha": "~1.16.2",
|
||||
"bitcore-p2p": "^1.1.0",
|
||||
"chai": "^3.5.0",
|
||||
"coveralls": "^2.11.9",
|
||||
"istanbul": "^0.4.3",
|
||||
"jshint": "^2.9.2",
|
||||
"jshint-stylish": "^2.1.0",
|
||||
"mocha": "^2.4.5",
|
||||
"proxyquire": "^1.3.1",
|
||||
"rimraf": "^2.4.2",
|
||||
"sinon": "^1.15.4",
|
||||
"bitcore-p2p": "~1.0.0"
|
||||
"sinon": "^1.15.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^0.12 || ^4.2"
|
||||
},
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux"
|
||||
],
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,485 @@
|
|||
'use strict';
|
||||
|
||||
// To run the tests: $ mocha -R spec regtest/bitcoind.js
|
||||
|
||||
var path = require('path');
|
||||
var index = require('..');
|
||||
var log = index.log;
|
||||
|
||||
var chai = require('chai');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var BN = bitcore.crypto.BN;
|
||||
var async = require('async');
|
||||
var rimraf = require('rimraf');
|
||||
var bitcoind;
|
||||
|
||||
/* jshint unused: false */
|
||||
var should = chai.should();
|
||||
var assert = chai.assert;
|
||||
var sinon = require('sinon');
|
||||
var BitcoinRPC = require('bitcoind-rpc');
|
||||
var transactionData = [];
|
||||
var blockHashes = [];
|
||||
var utxos;
|
||||
var client;
|
||||
var coinbasePrivateKey;
|
||||
var privateKey = bitcore.PrivateKey();
|
||||
var destKey = bitcore.PrivateKey();
|
||||
|
||||
describe('Zcashd Functionality', function() {
|
||||
|
||||
before(function(done) {
|
||||
this.timeout(60000);
|
||||
|
||||
// Add the regtest network
|
||||
bitcore.Networks.enableRegtest();
|
||||
var regtestNetwork = bitcore.Networks.get('regtest');
|
||||
|
||||
var datadir = __dirname + '/data';
|
||||
|
||||
rimraf(datadir + '/regtest', function(err) {
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
bitcoind = require('../').services.Bitcoin({
|
||||
spawn: {
|
||||
datadir: datadir,
|
||||
exec: path.resolve(__dirname, '../bin/zcashd')
|
||||
},
|
||||
node: {
|
||||
network: regtestNetwork,
|
||||
getNetworkName: function() {
|
||||
return 'regtest';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bitcoind.on('error', function(err) {
|
||||
log.error('error="%s"', err.message);
|
||||
});
|
||||
|
||||
log.info('Waiting for Zcash to initialize...');
|
||||
|
||||
bitcoind.start(function() {
|
||||
log.info('Zcashd started');
|
||||
|
||||
client = new BitcoinRPC({
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 30331,
|
||||
user: 'bitcoin',
|
||||
pass: 'local321',
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
log.info('Generating 100 blocks...');
|
||||
|
||||
// Generate enough blocks so that the initial coinbase transactions
|
||||
// can be spent.
|
||||
|
||||
setImmediate(function() {
|
||||
client.generate(150, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
blockHashes = response.result;
|
||||
|
||||
log.info('Preparing test data...');
|
||||
|
||||
// Get all of the unspent outputs
|
||||
client.listUnspent(0, 150, function(err, response) {
|
||||
utxos = response.result;
|
||||
|
||||
async.mapSeries(utxos, function(utxo, next) {
|
||||
async.series([
|
||||
function(finished) {
|
||||
// Load all of the transactions for later testing
|
||||
client.getTransaction(utxo.txid, function(err, txresponse) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// add to the list of transactions for testing later
|
||||
transactionData.push(txresponse.result.hex);
|
||||
finished();
|
||||
});
|
||||
},
|
||||
function(finished) {
|
||||
// Get the private key for each utxo
|
||||
client.dumpPrivKey(utxo.address, function(err, privresponse) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
utxo.privateKeyWIF = privresponse.result;
|
||||
finished();
|
||||
});
|
||||
}
|
||||
], next);
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
this.timeout(60000);
|
||||
bitcoind.node.stopping = true;
|
||||
bitcoind.stop(function(err, result) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('get blocks by hash', function() {
|
||||
|
||||
[0,1,2,3,5,6,7,8,9].forEach(function(i) {
|
||||
it('generated block ' + i, function(done) {
|
||||
bitcoind.getBlock(blockHashes[i], function(err, block) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
should.exist(block);
|
||||
block.hash.should.equal(blockHashes[i]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get blocks as buffers', function() {
|
||||
[0,1,2,3,5,6,7,8,9].forEach(function(i) {
|
||||
it('generated block ' + i, function(done) {
|
||||
bitcoind.getRawBlock(blockHashes[i], function(err, block) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
should.exist(block);
|
||||
(block instanceof Buffer).should.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get errors as error instances', function() {
|
||||
it('will wrap an rpc into a javascript error', function(done) {
|
||||
bitcoind.client.getBlock(1000000000, function(err, response) {
|
||||
var error = bitcoind._wrapRPCError(err);
|
||||
(error instanceof Error).should.equal(true);
|
||||
error.message.should.equal(err.message);
|
||||
error.code.should.equal(err.code);
|
||||
should.exist(error.stack);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get blocks by height', function() {
|
||||
|
||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('generated block ' + i, function(done) {
|
||||
// add the genesis block
|
||||
var height = i + 1;
|
||||
bitcoind.getBlock(i + 1, function(err, block) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
should.exist(block);
|
||||
block.hash.should.equal(blockHashes[i]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will get error with number greater than tip', function(done) {
|
||||
bitcoind.getBlock(1000000000, function(err, response) {
|
||||
should.exist(err);
|
||||
err.code.should.equal(-8);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('get transactions by hash', function() {
|
||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('for tx ' + i, function(done) {
|
||||
var txhex = transactionData[i];
|
||||
var tx = new bitcore.Transaction();
|
||||
tx.fromString(txhex);
|
||||
bitcoind.getTransaction(tx.hash, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
assert(response.toString('hex') === txhex, 'incorrect tx data result');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will return error if the transaction does not exist', function(done) {
|
||||
var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618';
|
||||
bitcoind.getTransaction(txid, function(err, response) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get transactions as buffers', function() {
|
||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('for tx ' + i, function(done) {
|
||||
var txhex = transactionData[i];
|
||||
var tx = new bitcore.Transaction();
|
||||
tx.fromString(txhex);
|
||||
bitcoind.getRawTransaction(tx.hash, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
response.should.be.instanceOf(Buffer);
|
||||
assert(response.toString('hex') === txhex, 'incorrect tx data result');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will return error if the transaction does not exist', function(done) {
|
||||
var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618';
|
||||
bitcoind.getRawTransaction(txid, function(err, response) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get block header', function() {
|
||||
var expectedWork = new BN(6);
|
||||
[1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('generate block ' + i, function(done) {
|
||||
bitcoind.getBlockHeader(blockHashes[i], function(err, blockIndex) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
should.exist(blockIndex);
|
||||
should.exist(blockIndex.chainWork);
|
||||
var work = new BN(blockIndex.chainWork, 'hex');
|
||||
work.toString(16).should.equal(expectedWork.toString(16));
|
||||
expectedWork = expectedWork.add(new BN(2));
|
||||
should.exist(blockIndex.prevHash);
|
||||
blockIndex.hash.should.equal(blockHashes[i]);
|
||||
blockIndex.prevHash.should.equal(blockHashes[i - 1]);
|
||||
blockIndex.height.should.equal(i + 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('will get null prevHash for the genesis block', function(done) {
|
||||
bitcoind.getBlockHeader(0, function(err, header) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
should.exist(header);
|
||||
should.equal(header.prevHash, undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will get error for block not found', function(done) {
|
||||
bitcoind.getBlockHeader('notahash', function(err, header) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get block index by height', function() {
|
||||
var expectedWork = new BN(6);
|
||||
[2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||
it('generate block ' + i, function() {
|
||||
bitcoind.getBlockHeader(i, function(err, header) {
|
||||
should.exist(header);
|
||||
should.exist(header.chainWork);
|
||||
var work = new BN(header.chainWork, 'hex');
|
||||
work.toString(16).should.equal(expectedWork.toString(16));
|
||||
expectedWork = expectedWork.add(new BN(2));
|
||||
should.exist(header.prevHash);
|
||||
header.hash.should.equal(blockHashes[i - 1]);
|
||||
header.prevHash.should.equal(blockHashes[i - 2]);
|
||||
header.height.should.equal(i);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('will get error with number greater than tip', function(done) {
|
||||
bitcoind.getBlockHeader(100000, function(err, header) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('send transaction functionality', function() {
|
||||
|
||||
it('will not error and return the transaction hash', function(done) {
|
||||
|
||||
// create and sign the transaction
|
||||
var tx = bitcore.Transaction();
|
||||
tx.from(utxos[0]);
|
||||
tx.change(privateKey.toAddress());
|
||||
tx.to(destKey.toAddress(), utxos[0].amount * 1e8 - 1000);
|
||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[0].privateKeyWIF));
|
||||
|
||||
// test sending the transaction
|
||||
bitcoind.sendTransaction(tx.serialize(), function(err, hash) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
hash.should.equal(tx.hash);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('will throw an error if an unsigned transaction is sent', function(done) {
|
||||
var tx = bitcore.Transaction();
|
||||
tx.from(utxos[1]);
|
||||
tx.change(privateKey.toAddress());
|
||||
tx.to(destKey.toAddress(), utxos[1].amount * 1e8 - 1000);
|
||||
bitcoind.sendTransaction(tx.uncheckedSerialize(), function(err, hash) {
|
||||
should.exist(err);
|
||||
(err instanceof Error).should.equal(true);
|
||||
should.not.exist(hash);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('will throw an error for unexpected types (tx decode failed)', function(done) {
|
||||
var garbage = new Buffer('abcdef', 'hex');
|
||||
bitcoind.sendTransaction(garbage, function(err, hash) {
|
||||
should.exist(err);
|
||||
should.not.exist(hash);
|
||||
var num = 23;
|
||||
bitcoind.sendTransaction(num, function(err, hash) {
|
||||
should.exist(err);
|
||||
(err instanceof Error).should.equal(true);
|
||||
should.not.exist(hash);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will emit "tx" events', function(done) {
|
||||
var tx = bitcore.Transaction();
|
||||
tx.from(utxos[2]);
|
||||
tx.change(privateKey.toAddress());
|
||||
tx.to(destKey.toAddress(), utxos[2].amount * 1e8 - 1000);
|
||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[2].privateKeyWIF));
|
||||
|
||||
var serialized = tx.serialize();
|
||||
|
||||
bitcoind.once('tx', function(buffer) {
|
||||
buffer.toString('hex').should.equal(serialized);
|
||||
done();
|
||||
});
|
||||
bitcoind.sendTransaction(serialized, function(err, hash) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
should.exist(hash);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('fee estimation', function() {
|
||||
it('will estimate fees', function(done) {
|
||||
bitcoind.estimateFee(1, function(err, fees) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
fees.should.equal(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tip updates', function() {
|
||||
it('will get an event when the tip is new', function(done) {
|
||||
this.timeout(4000);
|
||||
bitcoind.on('tip', function(height) {
|
||||
if (height === 151) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
client.generate(1, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get detailed transaction', function() {
|
||||
it('should include details for coinbase tx', function(done) {
|
||||
bitcoind.getDetailedTransaction(utxos[0].txid, function(err, tx) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
should.exist(tx.height);
|
||||
tx.height.should.be.a('number');
|
||||
should.exist(tx.blockTimestamp);
|
||||
should.exist(tx.blockHash);
|
||||
tx.coinbase.should.equal(true);
|
||||
tx.version.should.equal(1);
|
||||
tx.hex.should.be.a('string');
|
||||
tx.locktime.should.equal(0);
|
||||
tx.feeSatoshis.should.equal(0);
|
||||
tx.outputSatoshis.should.equal(50 * 1e8);
|
||||
tx.inputSatoshis.should.equal(0);
|
||||
tx.inputs.length.should.equal(1);
|
||||
tx.outputs.length.should.equal(1);
|
||||
should.equal(tx.inputs[0].prevTxId, null);
|
||||
should.equal(tx.inputs[0].outputIndex, null);
|
||||
tx.inputs[0].script.should.be.a('string');
|
||||
should.equal(tx.inputs[0].scriptAsm, null);
|
||||
should.equal(tx.inputs[0].address, null);
|
||||
should.equal(tx.inputs[0].satoshis, null);
|
||||
tx.outputs[0].satoshis.should.equal(50 * 1e8);
|
||||
tx.outputs[0].script.should.be.a('string');
|
||||
tx.outputs[0].scriptAsm.should.be.a('string');
|
||||
tx.outputs[0].spentTxId.should.be.a('string');
|
||||
tx.outputs[0].spentIndex.should.equal(0);
|
||||
tx.outputs[0].spentHeight.should.be.a('number');
|
||||
tx.outputs[0].address.should.be.a('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getInfo', function() {
|
||||
it('will get information', function(done) {
|
||||
bitcoind.getInfo(function(err, info) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
info.network.should.equal('regtest');
|
||||
should.exist(info);
|
||||
should.exist(info.version);
|
||||
should.exist(info.blocks);
|
||||
should.exist(info.timeOffset);
|
||||
should.exist(info.connections);
|
||||
should.exist(info.difficulty);
|
||||
should.exist(info.testnet);
|
||||
should.exist(info.relayFee);
|
||||
should.exist(info.errors);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,183 @@
|
|||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var async = require('async');
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var BitcoinRPC = require('bitcoind-rpc');
|
||||
var rimraf = require('rimraf');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var chai = require('chai');
|
||||
var should = chai.should();
|
||||
|
||||
var index = require('..');
|
||||
var log = index.log;
|
||||
log.debug = function() {};
|
||||
var BitcoreNode = index.Node;
|
||||
var BitcoinService = index.services.Bitcoin;
|
||||
|
||||
describe('Bitcoin Cluster', function() {
|
||||
var node;
|
||||
var daemons = [];
|
||||
var execPath = path.resolve(__dirname, '../bin/zcashd');
|
||||
var nodesConf = [
|
||||
{
|
||||
datadir: path.resolve(__dirname, './data/node1'),
|
||||
conf: path.resolve(__dirname, './data/node1/zcash.conf'),
|
||||
rpcuser: 'bitcoin',
|
||||
rpcpassword: 'local321',
|
||||
rpcport: 30521,
|
||||
zmqpubrawtx: 'tcp://127.0.0.1:30611',
|
||||
zmqpubhashblock: 'tcp://127.0.0.1:30611'
|
||||
},
|
||||
{
|
||||
datadir: path.resolve(__dirname, './data/node2'),
|
||||
conf: path.resolve(__dirname, './data/node2/zcash.conf'),
|
||||
rpcuser: 'bitcoin',
|
||||
rpcpassword: 'local321',
|
||||
rpcport: 30522,
|
||||
zmqpubrawtx: 'tcp://127.0.0.1:30622',
|
||||
zmqpubhashblock: 'tcp://127.0.0.1:30622'
|
||||
},
|
||||
{
|
||||
datadir: path.resolve(__dirname, './data/node3'),
|
||||
conf: path.resolve(__dirname, './data/node3/zcash.conf'),
|
||||
rpcuser: 'bitcoin',
|
||||
rpcpassword: 'local321',
|
||||
rpcport: 30523,
|
||||
zmqpubrawtx: 'tcp://127.0.0.1:30633',
|
||||
zmqpubhashblock: 'tcp://127.0.0.1:30633'
|
||||
}
|
||||
];
|
||||
|
||||
before(function(done) {
|
||||
log.info('Starting 3 bitcoind daemons');
|
||||
this.timeout(60000);
|
||||
async.each(nodesConf, function(nodeConf, next) {
|
||||
var opts = [
|
||||
'--regtest',
|
||||
'--datadir=' + nodeConf.datadir,
|
||||
'--conf=' + nodeConf.conf
|
||||
];
|
||||
|
||||
rimraf(path.resolve(nodeConf.datadir, './regtest'), function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var process = spawn(execPath, opts, {stdio: 'inherit'});
|
||||
|
||||
var client = new BitcoinRPC({
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: nodeConf.rpcport,
|
||||
user: nodeConf.rpcuser,
|
||||
pass: nodeConf.rpcpassword
|
||||
});
|
||||
|
||||
daemons.push(process);
|
||||
|
||||
async.retry({times: 10, interval: 5000}, function(ready) {
|
||||
client.getInfo(ready);
|
||||
}, next);
|
||||
|
||||
});
|
||||
|
||||
}, done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
this.timeout(10000);
|
||||
setTimeout(function() {
|
||||
async.each(daemons, function(process, next) {
|
||||
process.once('exit', next);
|
||||
process.kill('SIGINT');
|
||||
}, done);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
it('step 1: will connect to three bitcoind daemons', function(done) {
|
||||
this.timeout(20000);
|
||||
var configuration = {
|
||||
network: 'regtest',
|
||||
services: [
|
||||
{
|
||||
name: 'bitcoind',
|
||||
module: BitcoinService,
|
||||
config: {
|
||||
connect: [
|
||||
{
|
||||
rpchost: '127.0.0.1',
|
||||
rpcport: 30521,
|
||||
rpcuser: 'bitcoin',
|
||||
rpcpassword: 'local321',
|
||||
zmqpubrawtx: 'tcp://127.0.0.1:30611'
|
||||
},
|
||||
{
|
||||
rpchost: '127.0.0.1',
|
||||
rpcport: 30522,
|
||||
rpcuser: 'bitcoin',
|
||||
rpcpassword: 'local321',
|
||||
zmqpubrawtx: 'tcp://127.0.0.1:30622'
|
||||
},
|
||||
{
|
||||
rpchost: '127.0.0.1',
|
||||
rpcport: 30523,
|
||||
rpcuser: 'bitcoin',
|
||||
rpcpassword: 'local321',
|
||||
zmqpubrawtx: 'tcp://127.0.0.1:30633'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var regtest = bitcore.Networks.get('regtest');
|
||||
should.exist(regtest);
|
||||
|
||||
node = new BitcoreNode(configuration);
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
||||
node.on('ready', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
node.start(function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('step 2: receive block events', function(done) {
|
||||
this.timeout(10000);
|
||||
node.services.bitcoind.once('tip', function(height) {
|
||||
height.should.equal(1);
|
||||
done();
|
||||
});
|
||||
node.generateBlock(1, function(err, hashes) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
should.exist(hashes);
|
||||
});
|
||||
});
|
||||
|
||||
it('step 3: get blocks', function(done) {
|
||||
async.times(3, function(n, next) {
|
||||
node.getBlock(1, function(err, block) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
should.exist(block);
|
||||
next();
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
server=1
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
zmqpubrawtx=tcp://127.0.0.1:30332
|
||||
zmqpubhashblock=tcp://127.0.0.1:30332
|
||||
rpcallowip=127.0.0.1
|
||||
rpcport=30331
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
|
@ -0,0 +1,16 @@
|
|||
server=1
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
addnode=127.0.0.1:30432
|
||||
addnode=127.0.0.1:30433
|
||||
port=30431
|
||||
rpcport=30521
|
||||
zmqpubrawtx=tcp://127.0.0.1:30611
|
||||
zmqpubhashblock=tcp://127.0.0.1:30611
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
||||
keypool=3
|
|
@ -0,0 +1,16 @@
|
|||
server=1
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
addnode=127.0.0.1:30431
|
||||
addnode=127.0.0.1:30433
|
||||
port=30432
|
||||
rpcport=30522
|
||||
zmqpubrawtx=tcp://127.0.0.1:30622
|
||||
zmqpubhashblock=tcp://127.0.0.1:30622
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
||||
keypool=3
|
|
@ -0,0 +1,16 @@
|
|||
server=1
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
addnode=127.0.0.1:30431
|
||||
addnode=127.0.0.1:30432
|
||||
port=30433
|
||||
rpcport=30523
|
||||
zmqpubrawtx=tcp://127.0.0.1:30633
|
||||
zmqpubhashblock=tcp://127.0.0.1:30633
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
||||
keypool=3
|
|
@ -1,22 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
// These tests require bitcore-node Bitcoin Core bindings to be compiled with
|
||||
// the environment variable BITCORENODE_ENV=test. This enables the use of regtest
|
||||
// functionality by including the wallet in the build.
|
||||
// To run the tests: $ mocha -R spec integration/regtest-node.js
|
||||
// To run the tests: $ mocha -R spec regtest/node.js
|
||||
|
||||
var path = require('path');
|
||||
var index = require('..');
|
||||
var async = require('async');
|
||||
var log = index.log;
|
||||
log.debug = function() {};
|
||||
|
||||
if (process.env.BITCORENODE_ENV !== 'test') {
|
||||
log.info('Please set the environment variable BITCORENODE_ENV=test and make sure bindings are compiled for testing');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
var chai = require('chai');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var rimraf = require('rimraf');
|
||||
var node;
|
||||
|
||||
|
@ -24,12 +17,9 @@ var should = chai.should();
|
|||
|
||||
var BitcoinRPC = require('bitcoind-rpc');
|
||||
var index = require('..');
|
||||
var Transaction = index.Transaction;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BitcoreNode = index.Node;
|
||||
var AddressService = index.services.Address;
|
||||
var BitcoinService = index.services.Bitcoin;
|
||||
var encoding = require('../lib/services/address/encoding');
|
||||
var DBService = index.services.DB;
|
||||
var testWIF = 'cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG';
|
||||
var testKey;
|
||||
var client;
|
||||
|
@ -42,7 +32,7 @@ describe('Node Functionality', function() {
|
|||
var regtest;
|
||||
|
||||
before(function(done) {
|
||||
this.timeout(30000);
|
||||
this.timeout(20000);
|
||||
|
||||
var datadir = __dirname + '/data';
|
||||
|
||||
|
@ -55,23 +45,17 @@ describe('Node Functionality', function() {
|
|||
}
|
||||
|
||||
var configuration = {
|
||||
datadir: datadir,
|
||||
network: 'regtest',
|
||||
services: [
|
||||
{
|
||||
name: 'db',
|
||||
module: DBService,
|
||||
config: {}
|
||||
},
|
||||
{
|
||||
name: 'bitcoind',
|
||||
module: BitcoinService,
|
||||
config: {}
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
module: AddressService,
|
||||
config: {}
|
||||
config: {
|
||||
spawn: {
|
||||
datadir: datadir,
|
||||
exec: path.resolve(__dirname, '../bin/zcashd')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -85,37 +69,35 @@ describe('Node Functionality', function() {
|
|||
log.error(err);
|
||||
});
|
||||
|
||||
node.on('ready', function() {
|
||||
node.start(function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
client = new BitcoinRPC({
|
||||
protocol: 'https',
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 18332,
|
||||
port: 30331,
|
||||
user: 'bitcoin',
|
||||
pass: 'local321',
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
var syncedHandler = function() {
|
||||
if (node.services.db.tip.__height === 150) {
|
||||
node.removeListener('synced', syncedHandler);
|
||||
if (node.services.bitcoind.height === 150) {
|
||||
node.services.bitcoind.removeListener('synced', syncedHandler);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
node.on('synced', syncedHandler);
|
||||
node.services.bitcoind.on('synced', syncedHandler);
|
||||
|
||||
client.generate(150, function(err, response) {
|
||||
client.generate(150, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
node.start(function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -132,104 +114,31 @@ describe('Node Functionality', function() {
|
|||
});
|
||||
});
|
||||
|
||||
var invalidatedBlockHash;
|
||||
|
||||
it('will handle a reorganization', function(done) {
|
||||
|
||||
var count;
|
||||
var blockHash;
|
||||
|
||||
async.series([
|
||||
function(next) {
|
||||
client.getBlockCount(function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
count = response.result;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.getBlockHash(count, function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
invalidatedBlockHash = response.result;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.invalidateBlock(invalidatedBlockHash, next);
|
||||
},
|
||||
function(next) {
|
||||
client.getBlockCount(function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
response.result.should.equal(count - 1);
|
||||
next();
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var blocksRemoved = 0;
|
||||
var blocksAdded = 0;
|
||||
|
||||
var removeBlock = function() {
|
||||
blocksRemoved++;
|
||||
};
|
||||
|
||||
node.services.db.on('removeblock', removeBlock);
|
||||
|
||||
var addBlock = function() {
|
||||
blocksAdded++;
|
||||
if (blocksAdded === 2 && blocksRemoved === 1) {
|
||||
node.services.db.removeListener('addblock', addBlock);
|
||||
node.services.db.removeListener('removeblock', removeBlock);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
node.services.db.on('addblock', addBlock);
|
||||
|
||||
// We need to add a transaction to the mempool so that the next block will
|
||||
// have a different hash as the hash has been invalidated.
|
||||
client.sendToAddress(testKey.toAddress(regtest).toString(), 10, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
client.generate(2, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('isMainChain() will return false for stale/orphan block', function(done) {
|
||||
node.services.bitcoind.isMainChain(invalidatedBlockHash).should.equal(false);
|
||||
setImmediate(done);
|
||||
});
|
||||
|
||||
describe('Bus Functionality', function() {
|
||||
it('subscribes and unsubscribes to an event on the bus', function(done) {
|
||||
var bus = node.openBus();
|
||||
var block;
|
||||
bus.subscribe('db/block');
|
||||
bus.on('db/block', function(data) {
|
||||
bus.unsubscribe('db/block');
|
||||
data.should.be.equal(block);
|
||||
done();
|
||||
var blockExpected;
|
||||
var blockReceived;
|
||||
bus.subscribe('bitcoind/hashblock');
|
||||
bus.on('bitcoind/hashblock', function(data) {
|
||||
bus.unsubscribe('bitcoind/hashblock');
|
||||
if (blockExpected) {
|
||||
data.should.be.equal(blockExpected);
|
||||
done();
|
||||
} else {
|
||||
blockReceived = data;
|
||||
}
|
||||
});
|
||||
client.generate(1, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
block = response.result[0];
|
||||
if (blockReceived) {
|
||||
blockReceived.should.be.equal(response.result[0]);
|
||||
done();
|
||||
} else {
|
||||
blockExpected = response.result[0];
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -237,20 +146,37 @@ describe('Node Functionality', function() {
|
|||
describe('Address Functionality', function() {
|
||||
var address;
|
||||
var unspentOutput;
|
||||
before(function() {
|
||||
before(function(done) {
|
||||
this.timeout(10000);
|
||||
address = testKey.toAddress(regtest).toString();
|
||||
});
|
||||
it('should be able to get the balance of the test address', function(done) {
|
||||
node.services.address.getBalance(address, false, function(err, balance) {
|
||||
var startHeight = node.services.bitcoind.height;
|
||||
node.services.bitcoind.on('tip', function(height) {
|
||||
if (height === startHeight + 3) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
client.sendToAddress(testKey.toAddress(regtest).toString(), 10, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
balance.should.equal(10 * 1e8);
|
||||
client.generate(3, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should be able to get the balance of the test address', function(done) {
|
||||
node.getAddressBalance(address, false, function(err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
data.balance.should.equal(10 * 1e8);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('can get unspent outputs for address', function(done) {
|
||||
node.services.address.getUnspentOutputs(address, false, function(err, results) {
|
||||
node.getAddressUnspentOutputs(address, false, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -265,7 +191,7 @@ describe('Node Functionality', function() {
|
|||
to: 10,
|
||||
queryMempool: false
|
||||
};
|
||||
node.services.address.getAddressHistory(address, options, function(err, results) {
|
||||
node.getAddressHistory(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -278,9 +204,8 @@ describe('Node Functionality', function() {
|
|||
info.addresses[address].inputIndexes.should.deep.equal([]);
|
||||
info.satoshis.should.equal(10 * 1e8);
|
||||
info.confirmations.should.equal(3);
|
||||
info.timestamp.should.be.a('number');
|
||||
info.fees.should.be.within(950, 970);
|
||||
info.tx.should.be.an.instanceof(Transaction);
|
||||
info.tx.blockTimestamp.should.be.a('number');
|
||||
info.tx.feeSatoshis.should.be.within(950, 4000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -288,16 +213,16 @@ describe('Node Functionality', function() {
|
|||
var options = {
|
||||
queryMempool: false
|
||||
};
|
||||
node.services.address.getAddressSummary(address, options, function(err, results) {
|
||||
node.getAddressSummary(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
results.totalReceived.should.equal(1000000000);
|
||||
results.totalSpent.should.equal(0);
|
||||
results.balance.should.equal(1000000000);
|
||||
results.unconfirmedBalance.should.equal(0);
|
||||
should.not.exist(results.unconfirmedBalance);
|
||||
results.appearances.should.equal(1);
|
||||
results.unconfirmedAppearances.should.equal(0);
|
||||
should.not.exist(results.unconfirmedAppearances);
|
||||
results.txids.length.should.equal(1);
|
||||
done();
|
||||
});
|
||||
|
@ -316,10 +241,20 @@ describe('Node Functionality', function() {
|
|||
var address5;
|
||||
var testKey6;
|
||||
var address6;
|
||||
var tx2Amount;
|
||||
var tx2Hash;
|
||||
|
||||
before(function(done) {
|
||||
/* jshint maxstatements: 50 */
|
||||
|
||||
// Finished once all blocks have been mined
|
||||
var startHeight = node.services.bitcoind.height;
|
||||
node.services.bitcoind.on('tip', function(height) {
|
||||
if (height === startHeight + 5) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
testKey2 = bitcore.PrivateKey.fromWIF('cNfF4jXiLHQnFRsxaJyr2YSGcmtNYvxQYSakNhuDGxpkSzAwn95x');
|
||||
address2 = testKey2.toAddress(regtest).toString();
|
||||
|
||||
|
@ -347,8 +282,6 @@ describe('Node Functionality', function() {
|
|||
|
||||
unspentOutputSpentTxId = tx.id;
|
||||
|
||||
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||
|
||||
function mineBlock(next) {
|
||||
client.generate(1, function(err, response) {
|
||||
if (err) {
|
||||
|
@ -359,13 +292,18 @@ describe('Node Functionality', function() {
|
|||
});
|
||||
}
|
||||
|
||||
client.generate(1, function(err, response) {
|
||||
node.sendTransaction(tx.serialize(), function(err, hash) {
|
||||
if (err) {
|
||||
throw err;
|
||||
return done(err);
|
||||
}
|
||||
should.exist(response);
|
||||
node.once('synced', function() {
|
||||
node.services.address.getUnspentOutputs(address, false, function(err, results) {
|
||||
|
||||
client.generate(1, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
should.exist(response);
|
||||
|
||||
node.getAddressUnspentOutputs(address, false, function(err, results) {
|
||||
/* jshint maxstatements: 50 */
|
||||
if (err) {
|
||||
throw err;
|
||||
|
@ -375,28 +313,42 @@ describe('Node Functionality', function() {
|
|||
async.series([
|
||||
function(next) {
|
||||
var tx2 = new Transaction();
|
||||
tx2Amount = results[0].satoshis - 10000;
|
||||
tx2.from(results[0]);
|
||||
tx2.to(address2, results[0].satoshis - 10000);
|
||||
tx2.to(address2, tx2Amount);
|
||||
tx2.change(address);
|
||||
tx2.sign(testKey);
|
||||
node.services.bitcoind.sendTransaction(tx2.serialize());
|
||||
mineBlock(next);
|
||||
tx2Hash = tx2.hash;
|
||||
node.sendTransaction(tx2.serialize(), function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
mineBlock(next);
|
||||
});
|
||||
}, function(next) {
|
||||
var tx3 = new Transaction();
|
||||
tx3.from(results[1]);
|
||||
tx3.to(address3, results[1].satoshis - 10000);
|
||||
tx3.change(address);
|
||||
tx3.sign(testKey);
|
||||
node.services.bitcoind.sendTransaction(tx3.serialize());
|
||||
mineBlock(next);
|
||||
node.sendTransaction(tx3.serialize(), function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
mineBlock(next);
|
||||
});
|
||||
}, function(next) {
|
||||
var tx4 = new Transaction();
|
||||
tx4.from(results[2]);
|
||||
tx4.to(address4, results[2].satoshis - 10000);
|
||||
tx4.change(address);
|
||||
tx4.sign(testKey);
|
||||
node.services.bitcoind.sendTransaction(tx4.serialize());
|
||||
mineBlock(next);
|
||||
node.sendTransaction(tx4.serialize(), function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
mineBlock(next);
|
||||
});
|
||||
}, function(next) {
|
||||
var tx5 = new Transaction();
|
||||
tx5.from(results[3]);
|
||||
|
@ -405,19 +357,22 @@ describe('Node Functionality', function() {
|
|||
tx5.to(address6, results[4].satoshis - 10000);
|
||||
tx5.change(address);
|
||||
tx5.sign(testKey);
|
||||
node.services.bitcoind.sendTransaction(tx5.serialize());
|
||||
mineBlock(next);
|
||||
node.sendTransaction(tx5.serialize(), function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
mineBlock(next);
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
node.once('synced', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -431,22 +386,23 @@ describe('Node Functionality', function() {
|
|||
address6
|
||||
];
|
||||
var options = {};
|
||||
node.services.address.getAddressHistory(addresses, options, function(err, results) {
|
||||
node.getAddressHistory(addresses, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
results.totalCount.should.equal(4);
|
||||
var history = results.items;
|
||||
history.length.should.equal(4);
|
||||
history[0].height.should.equal(157);
|
||||
history[0].tx.height.should.equal(159);
|
||||
history[0].confirmations.should.equal(1);
|
||||
history[1].height.should.equal(156);
|
||||
history[1].tx.height.should.equal(158);
|
||||
should.exist(history[1].addresses[address4]);
|
||||
history[2].height.should.equal(155);
|
||||
history[2].tx.height.should.equal(157);
|
||||
should.exist(history[2].addresses[address3]);
|
||||
history[3].height.should.equal(154);
|
||||
history[3].tx.height.should.equal(156);
|
||||
should.exist(history[3].addresses[address2]);
|
||||
history[3].satoshis.should.equal(99990000);
|
||||
history[3].satoshis.should.equal(tx2Amount);
|
||||
history[3].tx.hash.should.equal(tx2Hash);
|
||||
history[3].confirmations.should.equal(4);
|
||||
done();
|
||||
});
|
||||
|
@ -461,20 +417,20 @@ describe('Node Functionality', function() {
|
|||
address6
|
||||
];
|
||||
var options = {
|
||||
start: 157,
|
||||
end: 156
|
||||
start: 158,
|
||||
end: 157
|
||||
};
|
||||
node.services.address.getAddressHistory(addresses, options, function(err, results) {
|
||||
node.getAddressHistory(addresses, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
results.totalCount.should.equal(2);
|
||||
var history = results.items;
|
||||
history.length.should.equal(2);
|
||||
history[0].height.should.equal(157);
|
||||
history[0].confirmations.should.equal(1);
|
||||
history[1].height.should.equal(156);
|
||||
should.exist(history[1].addresses[address4]);
|
||||
history[0].tx.height.should.equal(158);
|
||||
history[0].confirmations.should.equal(2);
|
||||
history[1].tx.height.should.equal(157);
|
||||
should.exist(history[1].addresses[address3]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -488,18 +444,18 @@ describe('Node Functionality', function() {
|
|||
address6
|
||||
];
|
||||
var options = {
|
||||
start: 155,
|
||||
end: 154
|
||||
start: 157,
|
||||
end: 156
|
||||
};
|
||||
node.services.address.getAddressHistory(addresses, options, function(err, results) {
|
||||
node.getAddressHistory(addresses, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
results.totalCount.should.equal(2);
|
||||
var history = results.items;
|
||||
history.length.should.equal(2);
|
||||
history[0].height.should.equal(155);
|
||||
history[1].height.should.equal(154);
|
||||
history[0].tx.height.should.equal(157);
|
||||
history[1].tx.height.should.equal(156);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -516,16 +472,16 @@ describe('Node Functionality', function() {
|
|||
from: 0,
|
||||
to: 3
|
||||
};
|
||||
node.services.address.getAddressHistory(addresses, options, function(err, results) {
|
||||
node.getAddressHistory(addresses, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
results.totalCount.should.equal(4);
|
||||
var history = results.items;
|
||||
history.length.should.equal(3);
|
||||
history[0].height.should.equal(157);
|
||||
history[0].tx.height.should.equal(159);
|
||||
history[0].confirmations.should.equal(1);
|
||||
history[1].height.should.equal(156);
|
||||
history[1].tx.height.should.equal(158);
|
||||
should.exist(history[1].addresses[address4]);
|
||||
done();
|
||||
});
|
||||
|
@ -536,32 +492,32 @@ describe('Node Functionality', function() {
|
|||
address
|
||||
];
|
||||
var options = {};
|
||||
node.services.address.getAddressHistory(addresses, options, function(err, results) {
|
||||
node.getAddressHistory(addresses, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
results.totalCount.should.equal(6);
|
||||
var history = results.items;
|
||||
history.length.should.equal(6);
|
||||
history[0].height.should.equal(157);
|
||||
history[0].tx.height.should.equal(159);
|
||||
history[0].addresses[address].inputIndexes.should.deep.equal([0, 1]);
|
||||
history[0].addresses[address].outputIndexes.should.deep.equal([2]);
|
||||
history[0].confirmations.should.equal(1);
|
||||
history[1].height.should.equal(156);
|
||||
history[2].height.should.equal(155);
|
||||
history[3].height.should.equal(154);
|
||||
history[4].height.should.equal(153);
|
||||
history[1].tx.height.should.equal(158);
|
||||
history[2].tx.height.should.equal(157);
|
||||
history[3].tx.height.should.equal(156);
|
||||
history[4].tx.height.should.equal(155);
|
||||
history[4].satoshis.should.equal(-10000);
|
||||
history[4].addresses[address].outputIndexes.should.deep.equal([0, 1, 2, 3, 4]);
|
||||
history[4].addresses[address].inputIndexes.should.deep.equal([0]);
|
||||
history[5].height.should.equal(150);
|
||||
history[5].tx.height.should.equal(152);
|
||||
history[5].satoshis.should.equal(10 * 1e8);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('summary for an address (sending and receiving)', function(done) {
|
||||
node.services.address.getAddressSummary(address, {}, function(err, results) {
|
||||
node.getAddressSummary(address, {}, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -582,7 +538,7 @@ describe('Node Functionality', function() {
|
|||
address
|
||||
];
|
||||
var options = {};
|
||||
node.services.address.getAddressHistory(addresses, options, function(err, results) {
|
||||
node.getAddressHistory(addresses, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -597,13 +553,13 @@ describe('Node Functionality', function() {
|
|||
from: 0,
|
||||
to: 1
|
||||
};
|
||||
node.services.address.getAddressHistory(address, options, function(err, results) {
|
||||
node.getAddressHistory(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var history = results.items;
|
||||
history.length.should.equal(1);
|
||||
history[0].height.should.equal(157);
|
||||
history[0].tx.height.should.equal(159);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -612,13 +568,13 @@ describe('Node Functionality', function() {
|
|||
from: 1,
|
||||
to: 2
|
||||
};
|
||||
node.services.address.getAddressHistory(address, options, function(err, results) {
|
||||
node.getAddressHistory(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var history = results.items;
|
||||
history.length.should.equal(1);
|
||||
history[0].height.should.equal(156);
|
||||
history[0].tx.height.should.equal(158);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -627,13 +583,13 @@ describe('Node Functionality', function() {
|
|||
from: 2,
|
||||
to: 3
|
||||
};
|
||||
node.services.address.getAddressHistory(address, options, function(err, results) {
|
||||
node.getAddressHistory(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var history = results.items;
|
||||
history.length.should.equal(1);
|
||||
history[0].height.should.equal(155);
|
||||
history[0].tx.height.should.equal(157);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -642,13 +598,13 @@ describe('Node Functionality', function() {
|
|||
from: 3,
|
||||
to: 4
|
||||
};
|
||||
node.services.address.getAddressHistory(address, options, function(err, results) {
|
||||
node.getAddressHistory(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var history = results.items;
|
||||
history.length.should.equal(1);
|
||||
history[0].height.should.equal(154);
|
||||
history[0].tx.height.should.equal(156);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -657,13 +613,13 @@ describe('Node Functionality', function() {
|
|||
from: 4,
|
||||
to: 5
|
||||
};
|
||||
node.services.address.getAddressHistory(address, options, function(err, results) {
|
||||
node.getAddressHistory(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var history = results.items;
|
||||
history.length.should.equal(1);
|
||||
history[0].height.should.equal(153);
|
||||
history[0].tx.height.should.equal(155);
|
||||
history[0].satoshis.should.equal(-10000);
|
||||
history[0].addresses[address].outputIndexes.should.deep.equal([0, 1, 2, 3, 4]);
|
||||
history[0].addresses[address].inputIndexes.should.deep.equal([0]);
|
||||
|
@ -675,13 +631,13 @@ describe('Node Functionality', function() {
|
|||
from: 5,
|
||||
to: 6
|
||||
};
|
||||
node.services.address.getAddressHistory(address, options, function(err, results) {
|
||||
node.getAddressHistory(address, options, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var history = results.items;
|
||||
history.length.should.equal(1);
|
||||
history[0].height.should.equal(150);
|
||||
history[0].tx.height.should.equal(152);
|
||||
history[0].satoshis.should.equal(10 * 1e8);
|
||||
done();
|
||||
});
|
||||
|
@ -693,7 +649,7 @@ describe('Node Functionality', function() {
|
|||
describe('Mempool Index', function() {
|
||||
var unspentOutput;
|
||||
before(function(done) {
|
||||
node.services.address.getUnspentOutputs(address, false, function(err, results) {
|
||||
node.getAddressUnspentOutputs(address, false, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -704,23 +660,20 @@ describe('Node Functionality', function() {
|
|||
});
|
||||
|
||||
it('will update the mempool index after new tx', function(done) {
|
||||
|
||||
var memAddress = bitcore.PrivateKey().toAddress(node.network).toString();
|
||||
var tx = new Transaction();
|
||||
tx.from(unspentOutput);
|
||||
tx.to(address, unspentOutput.satoshis - 1000);
|
||||
tx.to(memAddress, unspentOutput.satoshis - 1000);
|
||||
tx.fee(1000);
|
||||
tx.sign(testKey);
|
||||
|
||||
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||
|
||||
setImmediate(function() {
|
||||
var addrObj = encoding.getAddressInfo(address);
|
||||
node.services.address._getOutputsMempool(address, addrObj.hashBuffer,
|
||||
addrObj.hashTypeBuffer, function(err, outs) {
|
||||
node.services.bitcoind.sendTransaction(tx.serialize(), function(err, hash) {
|
||||
node.getAddressTxids(memAddress, {}, function(err, txids) {
|
||||
if (err) {
|
||||
throw err;
|
||||
return done(err);
|
||||
}
|
||||
outs.length.should.equal(1);
|
||||
txids.length.should.equal(1);
|
||||
txids[0].should.equal(hash);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -728,72 +681,78 @@ describe('Node Functionality', function() {
|
|||
|
||||
});
|
||||
|
||||
describe('#getInputForOutput(db)', function() {
|
||||
it('will get the input txid and input index', function(done) {
|
||||
var txid = outputForIsSpentTest1.txid;
|
||||
var outputIndex = outputForIsSpentTest1.outputIndex;
|
||||
var options = {
|
||||
queryMempool: true
|
||||
};
|
||||
node.services.address.getInputForOutput(txid, outputIndex, options, function(err, result) {
|
||||
result.inputTxId.should.equal(unspentOutputSpentTxId);
|
||||
result.inputIndex.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isSpent and #getInputForOutput(mempool)', function() {
|
||||
var spentOutput;
|
||||
var spentOutputInputTxId;
|
||||
it('will return true if an input is spent in a confirmed transaction', function(done) {
|
||||
var txid = outputForIsSpentTest1.txid;
|
||||
var outputIndex = outputForIsSpentTest1.outputIndex;
|
||||
var result = node.services.bitcoind.isSpent(txid, outputIndex);
|
||||
result.should.equal(true);
|
||||
done();
|
||||
});
|
||||
//CCoinsViewMemPool only checks for spent outputs that are not the mempool
|
||||
it('will correctly return false for an input that is spent in an unconfirmed transaction', function(done) {
|
||||
node.services.address.getUnspentOutputs(address, false, function(err, results) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var unspentOutput = results[0];
|
||||
|
||||
var tx = new Transaction();
|
||||
tx.from(unspentOutput);
|
||||
tx.to(address, unspentOutput.satoshis - 1000);
|
||||
tx.fee(1000);
|
||||
tx.sign(testKey);
|
||||
|
||||
node.services.bitcoind.sendTransaction(tx.serialize());
|
||||
spentOutput = unspentOutput;
|
||||
spentOutputInputTxId = tx.hash;
|
||||
|
||||
setImmediate(function() {
|
||||
var result = node.services.bitcoind.isSpent(unspentOutput.txid, unspentOutput.outputIndex);
|
||||
result.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will get the input txid and input index (mempool)', function(done) {
|
||||
var txid = spentOutput.txid;
|
||||
var outputIndex = spentOutput.outputIndex;
|
||||
var options = {
|
||||
queryMempool: true
|
||||
};
|
||||
node.services.address.getInputForOutput(txid, outputIndex, options, function(err, result) {
|
||||
result.inputTxId.should.equal(spentOutputInputTxId);
|
||||
result.inputIndex.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Orphaned Transactions', function() {
|
||||
this.timeout(8000);
|
||||
var orphanedTransaction;
|
||||
|
||||
before(function(done) {
|
||||
var count;
|
||||
var invalidatedBlockHash;
|
||||
|
||||
async.series([
|
||||
function(next) {
|
||||
client.sendToAddress(testKey.toAddress(regtest).toString(), 10, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
client.generate(1, next);
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.getBlockCount(function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
count = response.result;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.getBlockHash(count, function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
invalidatedBlockHash = response.result;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.getBlock(invalidatedBlockHash, function(err, response) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
orphanedTransaction = response.result.tx[1];
|
||||
should.exist(orphanedTransaction);
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
client.invalidateBlock(invalidatedBlockHash, next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('will not show confirmation count for orphaned transaction', function(done) {
|
||||
// This test verifies that in the situation that the transaction is not in the mempool and
|
||||
// is included in an orphaned block transaction index that the confirmation count will be unconfirmed.
|
||||
node.getDetailedTransaction(orphanedTransaction, function(err, data) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
should.exist(data);
|
||||
should.exist(data.height);
|
||||
data.height.should.equal(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,17 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
// To run the tests: $ mocha -R spec regtest/p2p.js
|
||||
|
||||
var path = require('path');
|
||||
var index = require('..');
|
||||
var log = index.log;
|
||||
|
||||
if (process.env.BITCORENODE_ENV !== 'test') {
|
||||
log.info('Please set the environment variable BITCORENODE_ENV=test and make sure bindings are compiled for testing');
|
||||
process.exit();
|
||||
}
|
||||
var p2p = require('bitcore-p2p');
|
||||
var Peer = p2p.Peer;
|
||||
var Messages = p2p.Messages;
|
||||
var chai = require('chai');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BN = bitcore.crypto.BN;
|
||||
var async = require('async');
|
||||
|
@ -40,36 +39,23 @@ describe('P2P Functionality', function() {
|
|||
before(function(done) {
|
||||
this.timeout(100000);
|
||||
|
||||
// Add the regtest network
|
||||
bitcore.Networks.remove(bitcore.Networks.testnet);
|
||||
bitcore.Networks.add({
|
||||
name: 'regtest',
|
||||
alias: 'regtest',
|
||||
pubkeyhash: 0x6f,
|
||||
privatekey: 0xef,
|
||||
scripthash: 0xc4,
|
||||
xpubkey: 0x043587cf,
|
||||
xprivkey: 0x04358394,
|
||||
networkMagic: 0xfabfb5da,
|
||||
port: 18444,
|
||||
dnsSeeds: [ ]
|
||||
});
|
||||
|
||||
// enable regtest
|
||||
bitcore.Networks.enableRegtest();
|
||||
var regtestNetwork = bitcore.Networks.get('regtest');
|
||||
var datadir = __dirname + '/data';
|
||||
|
||||
rimraf(datadir + '/regtest', function(err) {;
|
||||
|
||||
rimraf(datadir + '/regtest', function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
bitcoind = require('../').services.Bitcoin({
|
||||
node: {
|
||||
spawn: {
|
||||
datadir: datadir,
|
||||
network: {
|
||||
name: 'regtest'
|
||||
}
|
||||
exec: path.resolve(__dirname, '../bin/zcashd')
|
||||
},
|
||||
node: {
|
||||
network: bitcore.Networks.testnet
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -77,15 +63,18 @@ describe('P2P Functionality', function() {
|
|||
log.error('error="%s"', err.message);
|
||||
});
|
||||
|
||||
log.info('Waiting for Bitcoin Core to initialize...');
|
||||
log.info('Waiting for Zcash to initialize...');
|
||||
|
||||
bitcoind.start(function() {
|
||||
log.info('Bitcoind started');
|
||||
bitcoind.start(function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
log.info('Zcashd started');
|
||||
|
||||
client = new BitcoinRPC({
|
||||
protocol: 'https',
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 18332,
|
||||
port: 30331,
|
||||
user: 'bitcoin',
|
||||
pass: 'local321',
|
||||
rejectUnauthorized: false
|
||||
|
@ -174,6 +163,7 @@ describe('P2P Functionality', function() {
|
|||
this.timeout(20000);
|
||||
peer.on('disconnect', function() {
|
||||
log.info('Peer disconnected');
|
||||
bitcoind.node.stopping = true;
|
||||
bitcoind.stop(function(err, result) {
|
||||
done();
|
||||
});
|
||||
|
@ -186,13 +176,11 @@ describe('P2P Functionality', function() {
|
|||
|
||||
var usedTxs = {};
|
||||
|
||||
bitcoind.on('tx', function(result) {
|
||||
var txFromResult = new Transaction().fromBuffer(result.buffer);
|
||||
bitcoind.on('tx', function(buffer) {
|
||||
var txFromResult = new Transaction().fromBuffer(buffer);
|
||||
var tx = usedTxs[txFromResult.id];
|
||||
should.exist(tx);
|
||||
result.buffer.toString('hex').should.equal(tx.serialize());
|
||||
result.hash.should.equal(tx.hash);
|
||||
result.mempool.should.equal(true);
|
||||
buffer.toString('hex').should.equal(tx.serialize());
|
||||
delete usedTxs[tx.id];
|
||||
if (Object.keys(usedTxs).length === 0) {
|
||||
done();
|
|
@ -0,0 +1,119 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
platform=`uname -a | awk '{print tolower($1)}'`
|
||||
arch=`uname -m`
|
||||
version="0.12.1"
|
||||
url="https://github.com/bitpay/bitcoin/releases/download"
|
||||
tag="v0.12.1-bitcore-2"
|
||||
|
||||
if [ "${platform}" == "linux" ]; then
|
||||
if [ "${arch}" == "x86_64" ]; then
|
||||
tarball_name="bitcoin-${version}-linux64.tar.gz"
|
||||
elif [ "${arch}" == "x86_32" ]; then
|
||||
tarball_name="bitcoin-${version}-linux32.tar.gz"
|
||||
fi
|
||||
elif [ "${platform}" == "darwin" ]; then
|
||||
tarball_name="bitcoin-${version}-osx64.tar.gz"
|
||||
else
|
||||
echo "Bitcoin binary distribution not available for platform and architecture"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
binary_url="${url}/${tag}/${tarball_name}"
|
||||
shasums_url="${url}/${tag}/SHA256SUMS.asc"
|
||||
|
||||
download_bitcoind() {
|
||||
|
||||
cd "${root_dir}/bin"
|
||||
|
||||
echo "Downloading bitcoin: ${binary_url}"
|
||||
|
||||
is_curl=true
|
||||
if hash curl 2>/dev/null; then
|
||||
curl --fail -I $binary_url >/dev/null 2>&1
|
||||
else
|
||||
is_curl=false
|
||||
wget --server-response --spider $binary_url >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
if test $? -eq 0; then
|
||||
if [ "${is_curl}" = true ]; then
|
||||
curl -L $binary_url > $tarball_name
|
||||
curl -L $shasums_url > SHA256SUMS.asc
|
||||
else
|
||||
wget $binary_url
|
||||
wget $shasums_url
|
||||
fi
|
||||
if test -e "${tarball_name}"; then
|
||||
echo "Unpacking bitcoin distribution"
|
||||
tar -xvzf $tarball_name
|
||||
if test $? -eq 0; then
|
||||
ln -sf "bitcoin-${version}/bin/bitcoind"
|
||||
return;
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "Bitcoin binary distribution could not be downloaded"
|
||||
exit -1
|
||||
}
|
||||
|
||||
verify_download() {
|
||||
echo "Verifying signatures of bitcoin download"
|
||||
gpg --verify "${root_dir}/bin/SHA256SUMS.asc"
|
||||
|
||||
if hash shasum 2>/dev/null; then
|
||||
shasum_cmd="shasum -a 256"
|
||||
else
|
||||
shasum_cmd="sha256sum"
|
||||
fi
|
||||
|
||||
download_sha=$(${shasum_cmd} "${root_dir}/bin/${tarball_name}" | awk '{print $1}')
|
||||
expected_sha=$(cat "${root_dir}/bin/SHA256SUMS.asc" | grep "${tarball_name}" | awk '{print $1}')
|
||||
echo "Checksum (download): ${download_sha}"
|
||||
echo "Checksum (verified): ${expected_sha}"
|
||||
if [ "${download_sha}" != "${expected_sha}" ]; then
|
||||
echo -e "\033[1;31mChecksums did NOT match!\033[0m\n"
|
||||
exit 1
|
||||
else
|
||||
echo -e "\033[1;32mChecksums matched!\033[0m\n"
|
||||
fi
|
||||
}
|
||||
|
||||
download=1
|
||||
verify=0
|
||||
|
||||
if [ "${SKIP_BITCOIN_DOWNLOAD}" = 1 ]; then
|
||||
download=0;
|
||||
fi
|
||||
|
||||
if [ "${VERIFY_BITCOIN_DOWNLOAD}" = 1 ]; then
|
||||
verify=1;
|
||||
fi
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
param="$1"
|
||||
value="$2"
|
||||
|
||||
case $param in
|
||||
--skip-bitcoin-download)
|
||||
download=0
|
||||
;;
|
||||
--verify-bitcoin-download)
|
||||
verify=1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "${download}" = 1 ]; then
|
||||
download_bitcoind
|
||||
fi
|
||||
|
||||
if [ "${verify}" = 1 ]; then
|
||||
verify_download
|
||||
fi
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
_mocha -R spec regtest/p2p.js
|
||||
_mocha -R spec regtest/bitcoind.js
|
||||
_mocha -R spec regtest/cluster.js
|
||||
_mocha -R spec regtest/node.js
|
1609
src/libbitcoind.cc
1609
src/libbitcoind.cc
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +0,0 @@
|
|||
#include "main.h"
|
||||
#include "addrman.h"
|
||||
#include "alert.h"
|
||||
#include "base58.h"
|
||||
#include "init.h"
|
||||
#include "noui.h"
|
||||
#include "rpcserver.h"
|
||||
#include "txdb.h"
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "nan.h"
|
||||
#include "scheduler.h"
|
||||
#include "core_io.h"
|
||||
#include "script/bitcoinconsensus.h"
|
||||
#include "consensus/validation.h"
|
||||
#ifdef ENABLE_WALLET
|
||||
#include "wallet/wallet.h"
|
||||
#endif
|
|
@ -1,17 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var path = require('path');
|
||||
var getTarballName = require('../../bin/get-tarball-name');
|
||||
|
||||
describe('#getTarballName', function() {
|
||||
it('will return the expected tarball name', function() {
|
||||
var name = getTarballName();
|
||||
var version = require(path.resolve(__dirname + '../../../package.json')).version;
|
||||
var platform = process.platform;
|
||||
var arch = process.arch;
|
||||
var abi = process.versions.modules;
|
||||
var expected = 'libbitcoind-' + version + '-node' + abi + '-' + platform + '-' + arch + '.tgz';
|
||||
name.should.equal(expected);
|
||||
});
|
||||
});
|
|
@ -1,12 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
var sinon = require('sinon');
|
||||
var Service = require('../lib/service');
|
||||
var BitcoreNode = require('../lib/node');
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var should = require('chai').should();
|
||||
var index = require('../lib');
|
||||
var log = index.log;
|
||||
|
||||
var TestService = function(options) {
|
||||
this.node = options.node;
|
||||
}
|
||||
};
|
||||
util.inherits(TestService, Service);
|
||||
TestService.dependencies = [];
|
||||
|
||||
|
@ -40,6 +44,14 @@ TestService.prototype.unsubscribe = function(name, emitter) {
|
|||
|
||||
|
||||
describe('Bus Functionality', function() {
|
||||
var sandbox = sinon.sandbox.create();
|
||||
beforeEach(function() {
|
||||
sandbox.stub(log, 'info');
|
||||
});
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should subscribe to testEvent', function(done) {
|
||||
var node = new BitcoreNode({
|
||||
datadir: './',
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
#testnet=1
|
||||
#irc=0
|
||||
#upnp=0
|
||||
upnp=0
|
||||
server=1
|
||||
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
dbcache=8192
|
||||
checkblocks=144
|
||||
maxuploadtarget=1024
|
||||
zmqpubrawtx=tcp://127.0.0.1:28332
|
||||
zmqpubhashblock=tcp://127.0.0.1:28332
|
||||
|
||||
# listen on different ports
|
||||
port=20000
|
||||
rpcport=50001
|
||||
|
||||
rpcallowip=127.0.0.1
|
||||
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
server=1
|
||||
whitelist=127.0.0.1
|
||||
txindex=1
|
||||
addressindex=1
|
||||
timestampindex=1
|
||||
spentindex=1
|
||||
zmqpubrawtx=tcp://127.0.0.1:28332
|
||||
zmqpubhashblock=tcp://127.0.0.1:28332
|
||||
rpcallowip=127.0.0.1
|
||||
rpcuser=bitcoin
|
||||
rpcpassword=local321
|
||||
uacomment=bitcore
|
|
@ -1,19 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var index = require('..');
|
||||
|
||||
describe('Index', function() {
|
||||
describe('#nodeVersionCheck', function() {
|
||||
it('will throw informative error message with incompatible Node.js version 4.1.2', function() {
|
||||
(function() {
|
||||
index.nodeVersionCheck('4.1.2', '>=0.12.0 <1');
|
||||
}).should.throw('Node.js version');
|
||||
});
|
||||
it('will throw informative error message with incompatible Node.js version 0.10.40', function() {
|
||||
(function() {
|
||||
index.nodeVersionCheck('4.1.2', '>=0.12.0 <1');
|
||||
}).should.throw('Node.js version');
|
||||
});
|
||||
describe('Index Exports', function() {
|
||||
it('will export bitcore-lib', function() {
|
||||
var bitcore = require('../');
|
||||
should.exist(bitcore.lib);
|
||||
should.exist(bitcore.lib.Transaction);
|
||||
should.exist(bitcore.lib.Block);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
|
||||
var sinon = require('sinon');
|
||||
var chai = require('chai');
|
||||
var should = chai.should();
|
||||
var Logger = require('../lib/logger');
|
||||
|
||||
describe('Logger', function() {
|
||||
var sandbox = sinon.sandbox.create();
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('will instatiate without options', function() {
|
||||
var logger = new Logger();
|
||||
should.exist(logger);
|
||||
logger.formatting.should.equal(true);
|
||||
});
|
||||
|
||||
it('will instatiate with formatting option', function() {
|
||||
var logger = new Logger({
|
||||
formatting: false
|
||||
});
|
||||
logger.formatting.should.equal(false);
|
||||
var logger2 = new Logger({
|
||||
formatting: true
|
||||
});
|
||||
logger2.formatting.should.equal(true);
|
||||
});
|
||||
|
||||
it('will log with formatting', function() {
|
||||
var logger = new Logger({formatting: true});
|
||||
|
||||
sandbox.stub(console, 'info');
|
||||
logger.info('Test info log');
|
||||
console.info.callCount.should.equal(1);
|
||||
console.info.restore();
|
||||
|
||||
sandbox.stub(console, 'error');
|
||||
logger.error(new Error('Test error log'));
|
||||
console.error.callCount.should.equal(1);
|
||||
console.error.restore();
|
||||
|
||||
sandbox.stub(console, 'log');
|
||||
logger.debug('Test debug log');
|
||||
console.log.callCount.should.equal(1);
|
||||
console.log.restore();
|
||||
|
||||
sandbox.stub(console, 'warn');
|
||||
logger.warn('Test warn log');
|
||||
console.warn.callCount.should.equal(1);
|
||||
console.warn.restore();
|
||||
});
|
||||
|
||||
it('will log without formatting', function() {
|
||||
var logger = new Logger({formatting: false});
|
||||
|
||||
sandbox.stub(console, 'info');
|
||||
logger.info('Test info log');
|
||||
console.info.callCount.should.equal(1);
|
||||
should.not.exist(console.info.args[0][0].match(/^\[/));
|
||||
console.info.restore();
|
||||
|
||||
sandbox.stub(console, 'error');
|
||||
logger.error(new Error('Test error log'));
|
||||
console.error.callCount.should.equal(1);
|
||||
console.error.args[0][0].should.be.instanceof(Error);
|
||||
console.error.restore();
|
||||
|
||||
sandbox.stub(console, 'log');
|
||||
logger.debug('Test debug log');
|
||||
console.log.callCount.should.equal(1);
|
||||
should.equal(console.log.args[0][0].match(/^\[/), null);
|
||||
console.log.restore();
|
||||
|
||||
sandbox.stub(console, 'warn');
|
||||
logger.warn('Test warn log');
|
||||
console.warn.callCount.should.equal(1);
|
||||
should.equal(console.warn.args[0][0].match(/^\[/), null);
|
||||
console.warn.restore();
|
||||
});
|
||||
|
||||
});
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var bitcore = require('bitcore-lib-zcash');
|
||||
var Networks = bitcore.Networks;
|
||||
var proxyquire = require('proxyquire');
|
||||
var util = require('util');
|
||||
var BaseService = require('../lib/service');
|
||||
var index = require('../lib');
|
||||
var log = index.log;
|
||||
|
||||
describe('Bitcore Node', function() {
|
||||
|
||||
var baseConfig = {
|
||||
datadir: 'testdir'
|
||||
};
|
||||
var baseConfig = {};
|
||||
|
||||
var Node;
|
||||
|
||||
|
@ -21,29 +21,9 @@ describe('Bitcore Node', function() {
|
|||
Node.prototype._loadConfiguration = sinon.spy();
|
||||
Node.prototype._initialize = sinon.spy();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
var regtest = Networks.get('regtest');
|
||||
if (regtest) {
|
||||
Networks.remove(regtest);
|
||||
}
|
||||
// restore testnet
|
||||
Networks.add({
|
||||
name: 'testnet',
|
||||
alias: 'testnet',
|
||||
pubkeyhash: 0x6f,
|
||||
privatekey: 0xef,
|
||||
scripthash: 0xc4,
|
||||
xpubkey: 0x043587cf,
|
||||
xprivkey: 0x04358394,
|
||||
networkMagic: 0x0b110907,
|
||||
port: 18333,
|
||||
dnsSeeds: [
|
||||
'testnet-seed.bitcoin.petertodd.org',
|
||||
'testnet-seed.bluematt.me',
|
||||
'testnet-seed.alexykot.me',
|
||||
'testnet-seed.bitcoin.schildbach.de'
|
||||
],
|
||||
});
|
||||
Networks.disableRegtest();
|
||||
});
|
||||
|
||||
describe('@constructor', function() {
|
||||
|
@ -54,7 +34,6 @@ describe('Bitcore Node', function() {
|
|||
});
|
||||
it('will set properties', function() {
|
||||
var config = {
|
||||
datadir: 'testdir',
|
||||
services: [
|
||||
{
|
||||
name: 'test1',
|
||||
|
@ -69,11 +48,15 @@ describe('Bitcore Node', function() {
|
|||
node._unloadedServices[0].name.should.equal('test1');
|
||||
node._unloadedServices[0].module.should.equal(TestService);
|
||||
node.network.should.equal(Networks.defaultNetwork);
|
||||
var node2 = TestNode(config);
|
||||
node2._unloadedServices.length.should.equal(1);
|
||||
node2._unloadedServices[0].name.should.equal('test1');
|
||||
node2._unloadedServices[0].module.should.equal(TestService);
|
||||
node2.network.should.equal(Networks.defaultNetwork);
|
||||
});
|
||||
it('will set network to testnet', function() {
|
||||
var config = {
|
||||
network: 'testnet',
|
||||
datadir: 'testdir',
|
||||
services: [
|
||||
{
|
||||
name: 'test1',
|
||||
|
@ -89,7 +72,6 @@ describe('Bitcore Node', function() {
|
|||
it('will set network to regtest', function() {
|
||||
var config = {
|
||||
network: 'regtest',
|
||||
datadir: 'testdir',
|
||||
services: [
|
||||
{
|
||||
name: 'test1',
|
||||
|
@ -104,6 +86,26 @@ describe('Bitcore Node', function() {
|
|||
should.exist(regtest);
|
||||
node.network.should.equal(regtest);
|
||||
});
|
||||
it('will be able to disable log formatting', function() {
|
||||
var config = {
|
||||
network: 'regtest',
|
||||
services: [
|
||||
{
|
||||
name: 'test1',
|
||||
module: TestService
|
||||
}
|
||||
],
|
||||
formatLogs: false
|
||||
};
|
||||
var TestNode = proxyquire('../lib/node', {});
|
||||
var node = new TestNode(config);
|
||||
node.log.formatting.should.equal(false);
|
||||
|
||||
var TestNode = proxyquire('../lib/node', {});
|
||||
config.formatLogs = true;
|
||||
var node2 = new TestNode(config);
|
||||
node2.log.formatting.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#openBus', function() {
|
||||
|
@ -112,6 +114,11 @@ describe('Bitcore Node', function() {
|
|||
var bus = node.openBus();
|
||||
bus.node.should.equal(node);
|
||||
});
|
||||
it('will use remoteAddress config option', function() {
|
||||
var node = new Node(baseConfig);
|
||||
var bus = node.openBus({remoteAddress: '127.0.0.1'});
|
||||
bus.remoteAddress.should.equal('127.0.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAllAPIMethods', function() {
|
||||
|
@ -132,6 +139,21 @@ describe('Bitcore Node', function() {
|
|||
var methods = node.getAllAPIMethods();
|
||||
methods.should.deep.equal(['db1', 'db2', 'mda1', 'mda2', 'mdb1', 'mdb2']);
|
||||
});
|
||||
it('will handle service without getAPIMethods defined', function() {
|
||||
var node = new Node(baseConfig);
|
||||
node.services = {
|
||||
db: {
|
||||
getAPIMethods: sinon.stub().returns(['db1', 'db2']),
|
||||
},
|
||||
service1: {},
|
||||
service2: {
|
||||
getAPIMethods: sinon.stub().returns(['mdb1', 'mdb2'])
|
||||
}
|
||||
};
|
||||
|
||||
var methods = node.getAllAPIMethods();
|
||||
methods.should.deep.equal(['db1', 'db2', 'mdb1', 'mdb2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAllPublishEvents', function() {
|
||||
|
@ -151,6 +173,20 @@ describe('Bitcore Node', function() {
|
|||
var events = node.getAllPublishEvents();
|
||||
events.should.deep.equal(['db1', 'db2', 'mda1', 'mda2', 'mdb1', 'mdb2']);
|
||||
});
|
||||
it('will handle service without getPublishEvents defined', function() {
|
||||
var node = new Node(baseConfig);
|
||||
node.services = {
|
||||
db: {
|
||||
getPublishEvents: sinon.stub().returns(['db1', 'db2']),
|
||||
},
|
||||
service1: {},
|
||||
service2: {
|
||||
getPublishEvents: sinon.stub().returns(['mdb1', 'mdb2'])
|
||||
}
|
||||
};
|
||||
var events = node.getAllPublishEvents();
|
||||
events.should.deep.equal(['db1', 'db2', 'mdb1', 'mdb2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getServiceOrder', function() {
|
||||
|
@ -191,12 +227,20 @@ describe('Bitcore Node', function() {
|
|||
});
|
||||
|
||||
describe('#_startService', function() {
|
||||
var sandbox = sinon.sandbox.create();
|
||||
beforeEach(function() {
|
||||
sandbox.stub(log, 'info');
|
||||
});
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
it('will instantiate an instance and load api methods', function() {
|
||||
var node = new Node(baseConfig);
|
||||
function TestService() {}
|
||||
util.inherits(TestService, BaseService);
|
||||
TestService.prototype.start = sinon.stub().callsArg(0);
|
||||
TestService.prototype.getData = function() {};
|
||||
var getData = sinon.stub();
|
||||
TestService.prototype.getData = getData;
|
||||
TestService.prototype.getAPIMethods = function() {
|
||||
return [
|
||||
['getData', this, this.getData, 1]
|
||||
|
@ -214,6 +258,35 @@ describe('Bitcore Node', function() {
|
|||
TestService.prototype.start.callCount.should.equal(1);
|
||||
should.exist(node.services.testservice);
|
||||
should.exist(node.getData);
|
||||
node.getData();
|
||||
getData.callCount.should.equal(1);
|
||||
});
|
||||
});
|
||||
it('will handle config not being set', function() {
|
||||
var node = new Node(baseConfig);
|
||||
function TestService() {}
|
||||
util.inherits(TestService, BaseService);
|
||||
TestService.prototype.start = sinon.stub().callsArg(0);
|
||||
var getData = sinon.stub();
|
||||
TestService.prototype.getData = getData;
|
||||
TestService.prototype.getAPIMethods = function() {
|
||||
return [
|
||||
['getData', this, this.getData, 1]
|
||||
];
|
||||
};
|
||||
var service = {
|
||||
name: 'testservice',
|
||||
module: TestService,
|
||||
};
|
||||
node._startService(service, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
TestService.prototype.start.callCount.should.equal(1);
|
||||
should.exist(node.services.testservice);
|
||||
should.exist(node.getData);
|
||||
node.getData();
|
||||
getData.callCount.should.equal(1);
|
||||
});
|
||||
});
|
||||
it('will give an error from start', function() {
|
||||
|
@ -233,6 +306,13 @@ describe('Bitcore Node', function() {
|
|||
});
|
||||
|
||||
describe('#start', function() {
|
||||
var sandbox = sinon.sandbox.create();
|
||||
beforeEach(function() {
|
||||
sandbox.stub(log, 'info');
|
||||
});
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
it('will call start for each service', function(done) {
|
||||
var node = new Node(baseConfig);
|
||||
|
||||
|
@ -319,9 +399,62 @@ describe('Bitcore Node', function() {
|
|||
});
|
||||
|
||||
});
|
||||
it('will handle service with getAPIMethods undefined', function(done) {
|
||||
var node = new Node(baseConfig);
|
||||
|
||||
function TestService() {}
|
||||
util.inherits(TestService, BaseService);
|
||||
TestService.prototype.start = sinon.stub().callsArg(0);
|
||||
TestService.prototype.getData = function() {};
|
||||
|
||||
node.getServiceOrder = sinon.stub().returns([
|
||||
{
|
||||
name: 'test',
|
||||
module: TestService,
|
||||
config: {}
|
||||
},
|
||||
]);
|
||||
|
||||
node.start(function() {
|
||||
TestService.prototype.start.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getNetworkName', function() {
|
||||
afterEach(function() {
|
||||
bitcore.Networks.disableRegtest();
|
||||
});
|
||||
it('it will return the network name for livenet', function() {
|
||||
var node = new Node(baseConfig);
|
||||
node.getNetworkName().should.equal('livenet');
|
||||
});
|
||||
it('it will return the network name for testnet', function() {
|
||||
var baseConfig = {
|
||||
network: 'testnet'
|
||||
};
|
||||
var node = new Node(baseConfig);
|
||||
node.getNetworkName().should.equal('testnet');
|
||||
});
|
||||
it('it will return the network for regtest', function() {
|
||||
var baseConfig = {
|
||||
network: 'regtest'
|
||||
};
|
||||
var node = new Node(baseConfig);
|
||||
node.getNetworkName().should.equal('regtest');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#stop', function() {
|
||||
var sandbox = sinon.sandbox.create();
|
||||
beforeEach(function() {
|
||||
sandbox.stub(log, 'info');
|
||||
});
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
it('will call stop for each service', function(done) {
|
||||
var node = new Node(baseConfig);
|
||||
function TestService() {}
|
||||
|
|
|
@ -94,7 +94,7 @@ describe('#add', function() {
|
|||
var callCount = 0;
|
||||
var oldPackage = {
|
||||
dependencies: {
|
||||
'bitcore-lib': '^v0.13.7',
|
||||
'bitcore-lib-zcash': '^v0.13.7',
|
||||
'bitcore-node': '^v0.2.0'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('#create', function() {
|
|||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
mkdirp(testDir + '/.bitcoin', function(err) {
|
||||
mkdirp(testDir + '/.zcash', function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ describe('#create', function() {
|
|||
dirname: 'mynode3',
|
||||
name: 'My Node 3',
|
||||
isGlobal: true,
|
||||
datadir: '../.bitcoin'
|
||||
datadir: '../.zcash'
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
|
@ -139,7 +139,7 @@ describe('#create', function() {
|
|||
dirname: 'mynode4',
|
||||
name: 'My Node 4',
|
||||
isGlobal: false,
|
||||
datadir: '../.bitcoin'
|
||||
datadir: '../.zcash'
|
||||
}, function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('There was an error installing dependencies.');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var path = require('path');
|
||||
var defaultBaseConfig = require('../../lib/scaffold/default-base-config');
|
||||
|
||||
describe('#defaultBaseConfig', function() {
|
||||
|
@ -9,29 +10,19 @@ describe('#defaultBaseConfig', function() {
|
|||
var home = process.env.HOME;
|
||||
var info = defaultBaseConfig();
|
||||
info.path.should.equal(cwd);
|
||||
info.config.datadir.should.equal(home + '/.bitcoin');
|
||||
info.config.network.should.equal('livenet');
|
||||
info.config.port.should.equal(3001);
|
||||
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
|
||||
info.config.services.should.deep.equal(['bitcoind', 'web']);
|
||||
var bitcoind = info.config.servicesConfig.bitcoind;
|
||||
bitcoind.spawn.datadir.should.equal(home + '/.zcash');
|
||||
bitcoind.spawn.exec.should.equal(path.resolve(__dirname, '../../bin/zcashd'));
|
||||
});
|
||||
it('be able to specify a network', function() {
|
||||
var cwd = process.cwd();
|
||||
var home = process.env.HOME;
|
||||
var info = defaultBaseConfig({network: 'testnet'});
|
||||
info.path.should.equal(cwd);
|
||||
info.config.datadir.should.equal(home + '/.bitcoin');
|
||||
info.config.network.should.equal('testnet');
|
||||
info.config.port.should.equal(3001);
|
||||
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
|
||||
});
|
||||
it('be able to specify a datadir', function() {
|
||||
var cwd = process.cwd();
|
||||
var home = process.env.HOME;
|
||||
var info = defaultBaseConfig({datadir: './data2', network: 'testnet'});
|
||||
info.path.should.equal(cwd);
|
||||
info.config.datadir.should.equal('./data2');
|
||||
info.config.network.should.equal('testnet');
|
||||
info.config.port.should.equal(3001);
|
||||
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
|
||||
info.config.servicesConfig.bitcoind.spawn.datadir.should.equal('./data2');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var proxyquire = require('proxyquire');
|
||||
|
||||
describe('#defaultConfig', function() {
|
||||
var expectedExecPath = path.resolve(__dirname, '../../bin/zcashd');
|
||||
|
||||
it('will return expected configuration', function() {
|
||||
var config = JSON.stringify({
|
||||
datadir: process.env.HOME + '/.bitcore/data',
|
||||
network: 'livenet',
|
||||
port: 3001,
|
||||
services: [
|
||||
'bitcoind',
|
||||
'db',
|
||||
'address',
|
||||
'web'
|
||||
]
|
||||
],
|
||||
servicesConfig: {
|
||||
bitcoind: {
|
||||
spawn: {
|
||||
datadir: process.env.HOME + '/.bitcore/data',
|
||||
exec: expectedExecPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}, null, 2);
|
||||
var defaultConfig = proxyquire('../../lib/scaffold/default-config', {
|
||||
fs: {
|
||||
|
@ -32,28 +40,35 @@ describe('#defaultConfig', function() {
|
|||
sync: sinon.stub()
|
||||
}
|
||||
});
|
||||
var cwd = process.cwd();
|
||||
var home = process.env.HOME;
|
||||
var info = defaultConfig();
|
||||
info.path.should.equal(home + '/.bitcore');
|
||||
info.config.datadir.should.equal(home + '/.bitcore/data');
|
||||
info.config.network.should.equal('livenet');
|
||||
info.config.port.should.equal(3001);
|
||||
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
|
||||
info.config.services.should.deep.equal(['bitcoind', 'web']);
|
||||
var bitcoind = info.config.servicesConfig.bitcoind;
|
||||
should.exist(bitcoind);
|
||||
bitcoind.spawn.datadir.should.equal(home + '/.bitcore/data');
|
||||
bitcoind.spawn.exec.should.equal(expectedExecPath);
|
||||
});
|
||||
it('will include additional services', function() {
|
||||
var config = JSON.stringify({
|
||||
datadir: process.env.HOME + '/.bitcore/data',
|
||||
network: 'livenet',
|
||||
port: 3001,
|
||||
services: [
|
||||
'bitcoind',
|
||||
'db',
|
||||
'address',
|
||||
'web',
|
||||
'insight-api',
|
||||
'insight-ui'
|
||||
]
|
||||
],
|
||||
servicesConfig: {
|
||||
bitcoind: {
|
||||
spawn: {
|
||||
datadir: process.env.HOME + '/.bitcore/data',
|
||||
exec: expectedExecPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}, null, 2);
|
||||
var defaultConfig = proxyquire('../../lib/scaffold/default-config', {
|
||||
fs: {
|
||||
|
@ -75,16 +90,17 @@ describe('#defaultConfig', function() {
|
|||
additionalServices: ['insight-api', 'insight-ui']
|
||||
});
|
||||
info.path.should.equal(home + '/.bitcore');
|
||||
info.config.datadir.should.equal(home + '/.bitcore/data');
|
||||
info.config.network.should.equal('livenet');
|
||||
info.config.port.should.equal(3001);
|
||||
info.config.services.should.deep.equal([
|
||||
'bitcoind',
|
||||
'db',
|
||||
'address',
|
||||
'web',
|
||||
'insight-api',
|
||||
'insight-ui'
|
||||
]);
|
||||
var bitcoind = info.config.servicesConfig.bitcoind;
|
||||
should.exist(bitcoind);
|
||||
bitcoind.spawn.datadir.should.equal(home + '/.bitcore/data');
|
||||
bitcoind.spawn.exec.should.equal(expectedExecPath);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,19 +3,33 @@
|
|||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var proxyquire = require('proxyquire');
|
||||
var AddressService = require('../../lib/services/address');
|
||||
var BitcoinService = require('../../lib/services/bitcoind');
|
||||
var index = require('../../lib');
|
||||
var log = index.log;
|
||||
|
||||
describe('#start', function() {
|
||||
|
||||
var sandbox = sinon.sandbox.create();
|
||||
beforeEach(function() {
|
||||
sandbox.stub(log, 'error');
|
||||
});
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('will dynamically create a node from a configuration', function() {
|
||||
|
||||
it('require each bitcore-node service with default config', function(done) {
|
||||
var node;
|
||||
var TestNode = function(options) {
|
||||
options.services[0].should.deep.equal({
|
||||
name: 'address',
|
||||
module: AddressService,
|
||||
config: {}
|
||||
name: 'bitcoind',
|
||||
module: BitcoinService,
|
||||
config: {
|
||||
spawn: {
|
||||
datadir: './data'
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
TestNode.prototype.start = sinon.stub().callsArg(0);
|
||||
|
@ -28,13 +42,21 @@ describe('#start', function() {
|
|||
'../node': TestNode
|
||||
});
|
||||
|
||||
starttest.registerExitHandlers = sinon.stub();
|
||||
|
||||
node = starttest({
|
||||
path: __dirname,
|
||||
config: {
|
||||
services: [
|
||||
'address'
|
||||
'bitcoind'
|
||||
],
|
||||
datadir: './data'
|
||||
servicesConfig: {
|
||||
bitcoind: {
|
||||
spawn: {
|
||||
datadir: './data'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
node.should.be.instanceof(TestNode);
|
||||
|
@ -51,11 +73,13 @@ describe('#start', function() {
|
|||
'../node': TestNode
|
||||
});
|
||||
starttest.cleanShutdown = sinon.stub();
|
||||
starttest.registerExitHandlers = sinon.stub();
|
||||
|
||||
starttest({
|
||||
path: __dirname,
|
||||
config: {
|
||||
services: [],
|
||||
datadir: './testdir'
|
||||
servicesConfig: {}
|
||||
}
|
||||
});
|
||||
setImmediate(function() {
|
||||
|
@ -67,10 +91,13 @@ describe('#start', function() {
|
|||
var node;
|
||||
var TestNode = function(options) {
|
||||
options.services[0].should.deep.equal({
|
||||
name: 'address',
|
||||
module: AddressService,
|
||||
name: 'bitcoind',
|
||||
module: BitcoinService,
|
||||
config: {
|
||||
param: 'test'
|
||||
param: 'test',
|
||||
spawn: {
|
||||
datadir: './data'
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -83,19 +110,23 @@ describe('#start', function() {
|
|||
var starttest = proxyquire('../../lib/scaffold/start', {
|
||||
'../node': TestNode
|
||||
});
|
||||
starttest.registerExitHandlers = sinon.stub();
|
||||
|
||||
node = starttest({
|
||||
path: __dirname,
|
||||
config: {
|
||||
services: [
|
||||
'address'
|
||||
'bitcoind'
|
||||
],
|
||||
servicesConfig: {
|
||||
'address': {
|
||||
param: 'test'
|
||||
'bitcoind': {
|
||||
param: 'test',
|
||||
spawn: {
|
||||
datadir: './data'
|
||||
}
|
||||
}
|
||||
},
|
||||
datadir: './data'
|
||||
|
||||
}
|
||||
});
|
||||
node.should.be.instanceof(TestNode);
|
||||
|
|
|
@ -8,6 +8,35 @@ var proxyquire = require('proxyquire');
|
|||
var start = require('../../lib/scaffold/start');
|
||||
|
||||
describe('#start', function() {
|
||||
describe('#checkConfigVersion2', function() {
|
||||
var sandbox = sinon.sandbox.create();
|
||||
beforeEach(function() {
|
||||
sandbox.stub(console, 'warn');
|
||||
});
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
it('will give true with "datadir" at root', function() {
|
||||
var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2;
|
||||
var v2 = checkConfigVersion2({datadir: '/home/user/.bitcore/data', services: []});
|
||||
v2.should.equal(true);
|
||||
});
|
||||
it('will give true with "address" service enabled', function() {
|
||||
var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2;
|
||||
var v2 = checkConfigVersion2({services: ['address']});
|
||||
v2.should.equal(true);
|
||||
});
|
||||
it('will give true with "db" service enabled', function() {
|
||||
var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2;
|
||||
var v2 = checkConfigVersion2({services: ['db']});
|
||||
v2.should.equal(true);
|
||||
});
|
||||
it('will give false without "datadir" at root and "address", "db" services disabled', function() {
|
||||
var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2;
|
||||
var v2 = checkConfigVersion2({services: []});
|
||||
v2.should.equal(false);
|
||||
});
|
||||
});
|
||||
describe('#setupServices', function() {
|
||||
var cwd = process.cwd();
|
||||
var setupServices = proxyquire('../../lib/scaffold/start', {}).setupServices;
|
||||
|
@ -96,34 +125,6 @@ describe('#start', function() {
|
|||
}).should.throw('Could not load service');
|
||||
});
|
||||
});
|
||||
describe('#registerSyncHandlers', function() {
|
||||
it('will log the sync status at an interval', function(done) {
|
||||
var log = {
|
||||
info: sinon.stub()
|
||||
};
|
||||
var registerSyncHandlers = proxyquire('../../lib/scaffold/start', {
|
||||
'../': {
|
||||
log: log
|
||||
}
|
||||
}).registerSyncHandlers;
|
||||
var node = new EventEmitter();
|
||||
node.services = {
|
||||
db: new EventEmitter()
|
||||
};
|
||||
node.services.db.tip = {
|
||||
hash: 'hash',
|
||||
__height: 10
|
||||
};
|
||||
registerSyncHandlers(node, 10);
|
||||
node.emit('ready');
|
||||
node.services.db.emit('addblock');
|
||||
setTimeout(function() {
|
||||
node.emit('synced');
|
||||
log.info.callCount.should.be.within(3, 4);
|
||||
done();
|
||||
}, 35);
|
||||
});
|
||||
});
|
||||
describe('#cleanShutdown', function() {
|
||||
it('will call node stop and process exit', function() {
|
||||
var log = {
|
||||
|
@ -212,110 +213,6 @@ describe('#start', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('#spawnChildProcess', function() {
|
||||
|
||||
it('should build the appropriate arguments to spawn a child process', function() {
|
||||
var child = {
|
||||
unref: function() {}
|
||||
};
|
||||
var _process = {
|
||||
exit: function() {},
|
||||
env: {
|
||||
__bitcore_node: false
|
||||
},
|
||||
argv: [
|
||||
'node',
|
||||
'bitcore-node'
|
||||
],
|
||||
cwd: function(){return ''},
|
||||
pid: 999,
|
||||
execPath: '/tmp'
|
||||
};
|
||||
var fd = {};
|
||||
var spawn = sinon.stub().returns(child);
|
||||
var openSync = sinon.stub().returns(fd);
|
||||
var spawnChildProcess = proxyquire('../../lib/scaffold/start', {
|
||||
fs: {
|
||||
openSync: openSync
|
||||
},
|
||||
child_process: {
|
||||
spawn: spawn
|
||||
}
|
||||
}).spawnChildProcess;
|
||||
|
||||
spawnChildProcess('/tmp', _process);
|
||||
|
||||
spawn.callCount.should.equal(1);
|
||||
spawn.args[0][0].should.equal(_process.execPath);
|
||||
var expected = [].concat(_process.argv);
|
||||
expected.shift();
|
||||
spawn.args[0][1].should.deep.equal(expected);
|
||||
var cp_opt = {
|
||||
stdio: ['ignore', fd, fd],
|
||||
env: _process.env,
|
||||
cwd: '',
|
||||
detached: true
|
||||
};
|
||||
spawn.args[0][2].should.deep.equal(cp_opt);
|
||||
openSync.callCount.should.equal(1);
|
||||
openSync.args[0][0].should.equal('/tmp/bitcore-node.log');
|
||||
openSync.args[0][1].should.equal('a+');
|
||||
});
|
||||
it('should not spawn a new child process if there is already a daemon running', function() {
|
||||
var _process = {
|
||||
exit: function() {},
|
||||
env: {
|
||||
__bitcore_node: true
|
||||
},
|
||||
argv: [
|
||||
'node',
|
||||
'bitcore-node'
|
||||
],
|
||||
cwd: 'cwd',
|
||||
pid: 999,
|
||||
execPath: '/tmp'
|
||||
};
|
||||
var spawnChildProcess = proxyquire('../../lib/scaffold/start', {}).spawnChildProcess;
|
||||
spawnChildProcess('/tmp', _process).should.equal(999);
|
||||
});
|
||||
});
|
||||
describe('daemon', function() {
|
||||
var sandbox;
|
||||
var spawn;
|
||||
var setup;
|
||||
var registerSync;
|
||||
var registerExit;
|
||||
var start = require('../../lib/scaffold/start');
|
||||
var options = {
|
||||
config: {
|
||||
datadir: '/tmp',
|
||||
daemon: true
|
||||
}
|
||||
}
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
spawn = sandbox.stub(start, 'spawnChildProcess', function() {});
|
||||
setup = sandbox.stub(start, 'setupServices', function() {});
|
||||
registerSync = sandbox.stub(start, 'registerSyncHandlers', function() {});
|
||||
registerExit = sandbox.stub(start, 'registerExitHandlers', function() {});
|
||||
});
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
it('call spawnChildProcess if there is a config option to do so', function() {
|
||||
start(options);
|
||||
registerSync.callCount.should.equal(1);
|
||||
registerExit.callCount.should.equal(1);
|
||||
spawn.callCount.should.equal(1);
|
||||
});
|
||||
it('not call spawnChildProcess if there is not an option to do so', function() {
|
||||
options.config.daemon = false;
|
||||
start(options);
|
||||
registerSync.callCount.should.equal(1);
|
||||
registerExit.callCount.should.equal(1);
|
||||
spawn.callCount.should.equal(0);
|
||||
});
|
||||
});
|
||||
describe('#registerExitHandlers', function() {
|
||||
var stub;
|
||||
var registerExitHandlers = require('../../lib/scaffold/start').registerExitHandlers;
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var chai = require('chai');
|
||||
var should = chai.should();
|
||||
var sinon = require('sinon');
|
||||
var bitcorenode = require('../../../');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Address = bitcore.Address;
|
||||
var Script = bitcore.Script;
|
||||
var AddressService = bitcorenode.services.Address;
|
||||
var Networks = bitcore.Networks;
|
||||
var encoding = require('../../../lib/services/address/encoding');
|
||||
|
||||
var mockdb = {
|
||||
};
|
||||
|
||||
var mocknode = {
|
||||
network: Networks.testnet,
|
||||
datadir: 'testdir',
|
||||
db: mockdb,
|
||||
services: {
|
||||
bitcoind: {
|
||||
on: sinon.stub()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('Address Service Encoding', function() {
|
||||
|
||||
describe('#encodeSpentIndexSyncKey', function() {
|
||||
it('will encode to 36 bytes (string)', function() {
|
||||
var txidBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
|
||||
var key = encoding.encodeSpentIndexSyncKey(txidBuffer, 12);
|
||||
key.length.should.equal(36);
|
||||
});
|
||||
it('will be able to decode encoded value', function() {
|
||||
var txid = '3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7';
|
||||
var txidBuffer = new Buffer(txid, 'hex');
|
||||
var key = encoding.encodeSpentIndexSyncKey(txidBuffer, 12);
|
||||
var keyBuffer = new Buffer(key, 'binary');
|
||||
keyBuffer.slice(0, 32).toString('hex').should.equal(txid);
|
||||
var outputIndex = keyBuffer.readUInt32BE(32);
|
||||
outputIndex.should.equal(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_encodeInputKeyMap/#_decodeInputKeyMap roundtrip', function() {
|
||||
var encoded;
|
||||
var outputTxIdBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
|
||||
it('encode key', function() {
|
||||
encoded = encoding.encodeInputKeyMap(outputTxIdBuffer, 13);
|
||||
});
|
||||
it('decode key', function() {
|
||||
var key = encoding.decodeInputKeyMap(encoded);
|
||||
key.outputTxId.toString('hex').should.equal(outputTxIdBuffer.toString('hex'));
|
||||
key.outputIndex.should.equal(13);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_encodeInputValueMap/#_decodeInputValueMap roundtrip', function() {
|
||||
var encoded;
|
||||
var inputTxIdBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
|
||||
it('encode key', function() {
|
||||
encoded = encoding.encodeInputValueMap(inputTxIdBuffer, 7);
|
||||
});
|
||||
it('decode key', function() {
|
||||
var key = encoding.decodeInputValueMap(encoded);
|
||||
key.inputTxId.toString('hex').should.equal(inputTxIdBuffer.toString('hex'));
|
||||
key.inputIndex.should.equal(7);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#extractAddressInfoFromScript', function() {
|
||||
it('pay-to-publickey', function() {
|
||||
var pubkey = new bitcore.PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da');
|
||||
var script = Script.buildPublicKeyOut(pubkey);
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.addressType.should.equal(Address.PayToPublicKeyHash);
|
||||
info.hashBuffer.toString('hex').should.equal('9674af7395592ec5d91573aa8d6557de55f60147');
|
||||
});
|
||||
it('pay-to-publickeyhash', function() {
|
||||
var script = Script('OP_DUP OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG');
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.addressType.should.equal(Address.PayToPublicKeyHash);
|
||||
info.hashBuffer.toString('hex').should.equal('0000000000000000000000000000000000000000');
|
||||
});
|
||||
it('pay-to-scripthash', function() {
|
||||
var script = Script('OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUAL');
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.addressType.should.equal(Address.PayToScriptHash);
|
||||
info.hashBuffer.toString('hex').should.equal('0000000000000000000000000000000000000000');
|
||||
});
|
||||
it('non-address script type', function() {
|
||||
var buf = new Buffer(40);
|
||||
buf.fill(0);
|
||||
var script = Script('OP_RETURN 40 0x' + buf.toString('hex'));
|
||||
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
|
||||
info.should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -1,541 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var bitcore = require('bitcore-lib');
|
||||
var Transaction = require('../../../lib/transaction');
|
||||
var AddressHistory = require('../../../lib/services/address/history');
|
||||
|
||||
describe('Address Service History', function() {
|
||||
|
||||
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
|
||||
|
||||
describe('@constructor', function() {
|
||||
it('will construct a new instance', function() {
|
||||
var node = {};
|
||||
var options = {};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
history.should.be.instanceof(AddressHistory);
|
||||
history.node.should.equal(node);
|
||||
history.options.should.equal(options);
|
||||
history.addresses.should.equal(addresses);
|
||||
history.detailedArray.should.deep.equal([]);
|
||||
});
|
||||
it('will set addresses an array if only sent a string', function() {
|
||||
var history = new AddressHistory({
|
||||
node: {},
|
||||
options: {},
|
||||
addresses: address
|
||||
});
|
||||
history.addresses.should.deep.equal([address]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get', function() {
|
||||
it('will give an error if length of addresses is too long', function(done) {
|
||||
var node = {};
|
||||
var options = {};
|
||||
var addresses = [];
|
||||
for (var i = 0; i < 101; i++) {
|
||||
addresses.push(address);
|
||||
}
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
history.maxAddressesQuery = 100;
|
||||
history.get(function(err) {
|
||||
should.exist(err);
|
||||
err.message.match(/Maximum/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('give error from getAddressSummary with one address', function(done) {
|
||||
var node = {
|
||||
services: {
|
||||
address: {
|
||||
getAddressSummary: sinon.stub().callsArgWith(2, new Error('test'))
|
||||
}
|
||||
}
|
||||
};
|
||||
var options = {};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
history.get(function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('give error from getAddressSummary with multiple addresses', function(done) {
|
||||
var node = {
|
||||
services: {
|
||||
address: {
|
||||
getAddressSummary: sinon.stub().callsArgWith(2, new Error('test2'))
|
||||
}
|
||||
}
|
||||
};
|
||||
var options = {};
|
||||
var addresses = [address, address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
history.get(function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will query get address summary directly with one address', function(done) {
|
||||
var txids = [];
|
||||
var summary = {
|
||||
txids: txids
|
||||
};
|
||||
var node = {
|
||||
services: {
|
||||
address: {
|
||||
getAddressSummary: sinon.stub().callsArgWith(2, null, summary)
|
||||
}
|
||||
}
|
||||
};
|
||||
var options = {};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
history._mergeAndSortTxids = sinon.stub();
|
||||
history._paginateWithDetails = sinon.stub().callsArg(1);
|
||||
history.get(function() {
|
||||
history.node.services.address.getAddressSummary.callCount.should.equal(1);
|
||||
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
|
||||
history.node.services.address.getAddressSummary.args[0][1].should.equal(options);
|
||||
history._paginateWithDetails.callCount.should.equal(1);
|
||||
history._paginateWithDetails.args[0][0].should.equal(txids);
|
||||
history._mergeAndSortTxids.callCount.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will merge multiple summaries with multiple addresses', function(done) {
|
||||
var txids = [];
|
||||
var summary = {
|
||||
txids: txids
|
||||
};
|
||||
var node = {
|
||||
services: {
|
||||
address: {
|
||||
getAddressSummary: sinon.stub().callsArgWith(2, null, summary)
|
||||
}
|
||||
}
|
||||
};
|
||||
var options = {};
|
||||
var addresses = [address, address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
history._mergeAndSortTxids = sinon.stub().returns(txids);
|
||||
history._paginateWithDetails = sinon.stub().callsArg(1);
|
||||
history.get(function() {
|
||||
history.node.services.address.getAddressSummary.callCount.should.equal(2);
|
||||
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
|
||||
history.node.services.address.getAddressSummary.args[0][1].should.deep.equal({
|
||||
fullTxList: true
|
||||
});
|
||||
history._paginateWithDetails.callCount.should.equal(1);
|
||||
history._paginateWithDetails.args[0][0].should.equal(txids);
|
||||
history._mergeAndSortTxids.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_paginateWithDetails', function() {
|
||||
it('slice txids based on "from" and "to" (3 to 30)', function() {
|
||||
var node = {};
|
||||
var options = {
|
||||
from: 3,
|
||||
to: 30
|
||||
};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
|
||||
this.detailedArray.push(txid);
|
||||
next();
|
||||
});
|
||||
history._paginateWithDetails(txids, function(err, result) {
|
||||
result.totalCount.should.equal(11);
|
||||
result.items.should.deep.equal([7, 6, 5, 4, 3, 2, 1, 0]);
|
||||
});
|
||||
});
|
||||
it('slice txids based on "from" and "to" (0 to 3)', function() {
|
||||
var node = {};
|
||||
var options = {
|
||||
from: 0,
|
||||
to: 3
|
||||
};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
|
||||
this.detailedArray.push(txid);
|
||||
next();
|
||||
});
|
||||
history._paginateWithDetails(txids, function(err, result) {
|
||||
result.totalCount.should.equal(11);
|
||||
result.items.should.deep.equal([10, 9, 8]);
|
||||
});
|
||||
});
|
||||
it('will given an error if the full details is too long', function() {
|
||||
var node = {};
|
||||
var options = {
|
||||
from: 0,
|
||||
to: 3
|
||||
};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
|
||||
this.detailedArray.push(txid);
|
||||
next();
|
||||
});
|
||||
history.maxHistoryQueryLength = 1;
|
||||
history._paginateWithDetails(txids, function(err) {
|
||||
should.exist(err);
|
||||
err.message.match(/Maximum/);
|
||||
});
|
||||
});
|
||||
it('will give full result without pagination options', function() {
|
||||
var node = {};
|
||||
var options = {};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
|
||||
this.detailedArray.push(txid);
|
||||
next();
|
||||
});
|
||||
history._paginateWithDetails(txids, function(err, result) {
|
||||
result.totalCount.should.equal(11);
|
||||
result.items.should.deep.equal([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_mergeAndSortTxids', function() {
|
||||
it('will merge and sort multiple summaries', function() {
|
||||
var summaries = [
|
||||
{
|
||||
totalReceived: 10000000,
|
||||
totalSpent: 0,
|
||||
balance: 10000000,
|
||||
appearances: 2,
|
||||
unconfirmedBalance: 20000000,
|
||||
unconfirmedAppearances: 2,
|
||||
appearanceIds: {
|
||||
'56fafeb01961831b926558d040c246b97709fd700adcaa916541270583e8e579': 154,
|
||||
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce': 120
|
||||
},
|
||||
unconfirmedAppearanceIds: {
|
||||
'ec94d845c603f292a93b7c829811ac624b76e52b351617ca5a758e9d61a11681': 1452898347406,
|
||||
'ed11a08e3102f9610bda44c80c46781d97936a4290691d87244b1b345b39a693': 1452898331964
|
||||
}
|
||||
},
|
||||
{
|
||||
totalReceived: 59990000,
|
||||
totalSpent: 0,
|
||||
balance: 49990000,
|
||||
appearances: 3,
|
||||
unconfirmedBalance: 1000000,
|
||||
unconfirmedAppearances: 3,
|
||||
appearanceIds: {
|
||||
'bc992ad772eb02864db07ef248d31fb3c6826d25f1153ebf8c79df9b7f70fcf2': 156,
|
||||
'f3c1ba3ef86a0420d6102e40e2cfc8682632ab95d09d86a27f5d466b9fa9da47': 152,
|
||||
'f637384e9f81f18767ea50e00bce58fc9848b6588a1130529eebba22a410155f': 151
|
||||
},
|
||||
unconfirmedAppearanceIds: {
|
||||
'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345': 1452897902377,
|
||||
'edc080f2084eed362aa488ccc873a24c378dc0979aa29b05767517b70569414a': 1452897971363,
|
||||
'f35e7e2a2334e845946f3eaca76890d9a68f4393ccc9fe37a0c2fb035f66d2e9': 1452897923107
|
||||
}
|
||||
}
|
||||
];
|
||||
var node = {};
|
||||
var options = {};
|
||||
var addresses = [address];
|
||||
var history = new AddressHistory({
|
||||
node: node,
|
||||
options: options,
|
||||
addresses: addresses
|
||||
});
|
||||
var txids = history._mergeAndSortTxids(summaries);
|
||||
txids.should.deep.equal([
|
||||
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
|
||||
'f637384e9f81f18767ea50e00bce58fc9848b6588a1130529eebba22a410155f',
|
||||
'f3c1ba3ef86a0420d6102e40e2cfc8682632ab95d09d86a27f5d466b9fa9da47',
|
||||
'56fafeb01961831b926558d040c246b97709fd700adcaa916541270583e8e579',
|
||||
'bc992ad772eb02864db07ef248d31fb3c6826d25f1153ebf8c79df9b7f70fcf2',
|
||||
'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345',
|
||||
'f35e7e2a2334e845946f3eaca76890d9a68f4393ccc9fe37a0c2fb035f66d2e9',
|
||||
'edc080f2084eed362aa488ccc873a24c378dc0979aa29b05767517b70569414a',
|
||||
'ed11a08e3102f9610bda44c80c46781d97936a4290691d87244b1b345b39a693',
|
||||
'ec94d845c603f292a93b7c829811ac624b76e52b351617ca5a758e9d61a11681'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getDetailedInfo', function() {
|
||||
it('will add additional information to existing this.transactions', function(done) {
|
||||
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
|
||||
var tx = {
|
||||
populateInputs: sinon.stub().callsArg(2),
|
||||
__height: 20,
|
||||
__timestamp: 1453134151,
|
||||
isCoinbase: sinon.stub().returns(false),
|
||||
getFee: sinon.stub().returns(1000)
|
||||
};
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
db: {
|
||||
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, tx),
|
||||
tip: {
|
||||
__height: 300
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
history.getAddressDetailsForTransaction = sinon.stub().returns({
|
||||
addresses: {},
|
||||
satoshis: 1000,
|
||||
});
|
||||
history.getDetailedInfo(txid, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
history.node.services.db.getTransactionWithBlockInfo.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will handle error from getTransactionFromBlock', function(done) {
|
||||
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
db: {
|
||||
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, new Error('test')),
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
history.getDetailedInfo(txid, function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will handle error from populateInputs', function(done) {
|
||||
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
db: {
|
||||
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, {
|
||||
populateInputs: sinon.stub().callsArgWith(2, new Error('test'))
|
||||
}),
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
history.getDetailedInfo(txid, function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will set this.transactions with correct information', function(done) {
|
||||
// block #314159
|
||||
// txid 30169e8bf78bc27c4014a7aba3862c60e2e3cce19e52f1909c8255e4b7b3174e
|
||||
// outputIndex 1
|
||||
var txAddress = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
|
||||
var txString = '0100000001a08ee59fcd5d86fa170abb6d925d62d5c5c476359681b70877c04f270c4ef246000000008a47304402203fb9b476bb0c37c9b9ed5784ebd67ae589492be11d4ae1612be29887e3e4ce750220741ef83781d1b3a5df8c66fa1957ad0398c733005310d7d9b1d8c2310ef4f74c0141046516ad02713e51ecf23ac9378f1069f9ae98e7de2f2edbf46b7836096e5dce95a05455cc87eaa1db64f39b0c63c0a23a3b8df1453dbd1c8317f967c65223cdf8ffffffff02b0a75fac000000001976a91484b45b9bf3add8f7a0f3daad305fdaf6b73441ea88ac20badc02000000001976a914809dc14496f99b6deb722cf46d89d22f4beb8efd88ac00000000';
|
||||
var previousTxString = '010000000155532fad2869bb951b0bd646a546887f6ee668c4c0ee13bf3f1c4bce6d6e3ed9000000008c4930460221008540795f4ef79b1d2549c400c61155ca5abbf3089c84ad280e1ba6db2a31abce022100d7d162175483d51174d40bba722e721542c924202a0c2970b07e680b51f3a0670141046516ad02713e51ecf23ac9378f1069f9ae98e7de2f2edbf46b7836096e5dce95a05455cc87eaa1db64f39b0c63c0a23a3b8df1453dbd1c8317f967c65223cdf8ffffffff02f0af3caf000000001976a91484b45b9bf3add8f7a0f3daad305fdaf6b73441ea88ac80969800000000001976a91421277e65777760d1f3c7c982ba14ed8f934f005888ac00000000';
|
||||
var transaction = new Transaction();
|
||||
var previousTransaction = new Transaction();
|
||||
previousTransaction.fromString(previousTxString);
|
||||
var previousTransactionTxid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
|
||||
transaction.fromString(txString);
|
||||
var txid = transaction.hash;
|
||||
transaction.__blockHash = '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45';
|
||||
transaction.__height = 314159;
|
||||
transaction.__timestamp = 1407292005;
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
db: {
|
||||
tip: {
|
||||
__height: 314159
|
||||
},
|
||||
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, transaction),
|
||||
getTransaction: function(prevTxid, queryMempool, callback) {
|
||||
prevTxid.should.equal(previousTransactionTxid);
|
||||
setImmediate(function() {
|
||||
callback(null, previousTransaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: [txAddress]
|
||||
});
|
||||
var transactionInfo = {
|
||||
addresses: {},
|
||||
txid: txid,
|
||||
timestamp: 1407292005,
|
||||
satoshis: 48020000,
|
||||
address: txAddress
|
||||
};
|
||||
transactionInfo.addresses[txAddress] = {};
|
||||
transactionInfo.addresses[txAddress].outputIndexes = [1];
|
||||
transactionInfo.addresses[txAddress].inputIndexes = [];
|
||||
history.getDetailedInfo(txid, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var info = history.detailedArray[0];
|
||||
info.addresses[txAddress].should.deep.equal({
|
||||
outputIndexes: [1],
|
||||
inputIndexes: []
|
||||
});
|
||||
info.satoshis.should.equal(48020000);
|
||||
info.height.should.equal(314159);
|
||||
info.confirmations.should.equal(1);
|
||||
info.timestamp.should.equal(1407292005);
|
||||
info.fees.should.equal(20000);
|
||||
info.tx.should.equal(transaction);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAddressDetailsForTransaction', function() {
|
||||
it('will calculate details for the transaction', function(done) {
|
||||
/* jshint sub:true */
|
||||
var tx = bitcore.Transaction({
|
||||
'hash': 'b12b3ae8489c5a566b629a3c62ce4c51c3870af550fb5dc77d715b669a91343c',
|
||||
'version': 1,
|
||||
'inputs': [
|
||||
{
|
||||
'prevTxId': 'a2b7ea824a92f4a4944686e67ec1001bc8785348b8c111c226f782084077b543',
|
||||
'outputIndex': 0,
|
||||
'sequenceNumber': 4294967295,
|
||||
'script': '47304402201b81c933297241960a57ae1b2952863b965ac8c9ec7466ff0b715712d27548d50220576e115b63864f003889443525f47c7cf0bc1e2b5108398da085b221f267ba2301210229766f1afa25ca499a51f8e01c292b0255a21a41bb6685564a1607a811ffe924',
|
||||
'scriptString': '71 0x304402201b81c933297241960a57ae1b2952863b965ac8c9ec7466ff0b715712d27548d50220576e115b63864f003889443525f47c7cf0bc1e2b5108398da085b221f267ba2301 33 0x0229766f1afa25ca499a51f8e01c292b0255a21a41bb6685564a1607a811ffe924',
|
||||
'output': {
|
||||
'satoshis': 1000000000,
|
||||
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
|
||||
}
|
||||
}
|
||||
],
|
||||
'outputs': [
|
||||
{
|
||||
'satoshis': 100000000,
|
||||
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
|
||||
},
|
||||
{
|
||||
'satoshis': 200000000,
|
||||
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
|
||||
},
|
||||
{
|
||||
'satoshis': 50000000,
|
||||
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
|
||||
},
|
||||
{
|
||||
'satoshis': 300000000,
|
||||
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
|
||||
},
|
||||
{
|
||||
'satoshis': 349990000,
|
||||
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
|
||||
}
|
||||
],
|
||||
'nLockTime': 0
|
||||
});
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
network: bitcore.Networks.testnet
|
||||
},
|
||||
options: {},
|
||||
addresses: ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW']
|
||||
});
|
||||
var details = history.getAddressDetailsForTransaction(tx);
|
||||
should.exist(details.addresses['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW']);
|
||||
details.addresses['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'].inputIndexes.should.deep.equal([0]);
|
||||
details.addresses['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'].outputIndexes.should.deep.equal([
|
||||
0, 1, 2, 3, 4
|
||||
]);
|
||||
details.satoshis.should.equal(-10000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getConfirmationsDetail', function() {
|
||||
it('the correct confirmations when included in the tip', function(done) {
|
||||
var history = new AddressHistory({
|
||||
node: {
|
||||
services: {
|
||||
db: {
|
||||
tip: {
|
||||
__height: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
addresses: []
|
||||
});
|
||||
var transaction = {
|
||||
__height: 100
|
||||
};
|
||||
history.getConfirmationsDetail(transaction).should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue