Compare commits

..

No commits in common. "master" and "opt/multiple-address-history" have entirely different histories.

164 changed files with 17541 additions and 16087 deletions

View File

@ -1 +0,0 @@
repo_token: DvrDb09a8vhPlVf6DT4cGBjcFOi6DfZN1

21
.gitignore vendored
View File

@ -1,7 +1,12 @@
node_modules/
node_modules/*
coverage/*
out/
out/*
build/
build/*
.lock-wscript
Makefile.gyp
*.swp
*.Makefile
*.target.gyp.mk
@ -14,23 +19,15 @@ coverage/*
*.filters
*.user
*.project
test.js
**/*.dylib
**/*.so
**/*.old
**/*.files
**/*.config
**/*.creator
libbitcoind
libbitcoind*
libbitcoind.includes
*.log
*.tmp
*.tmp.*
.DS_Store
bin/florincoin*
bin/SHA256SUMS.asc
regtest/data/node1/regtest
regtest/data/node2/regtest
regtest/data/node3/regtest
flocore-node.json*
*.bak
*.orig
lib/services/insight-api
testnet/*

View File

@ -22,11 +22,11 @@
"trailing": true,
"undef": true,
"unused": true,
"maxparams": 6,
"maxstatements": 25,
"maxparams": 4,
"maxstatements": 15,
"maxcomplexity": 10,
"maxdepth": 4,
"maxlen": 140,
"maxdepth": 3,
"maxlen": 120,
"multistr": true,
"predef": [
"after",
@ -39,4 +39,4 @@
"module",
"require"
]
}
}

View File

@ -1,33 +0,0 @@
node_modules/
node_modules/*
coverage/*
.lock-wscript
*.swp
*.Makefile
*.target.gyp.mk
*.node
*.sln
*.sdf
*.vcxproj
*.suo
*.opensdf
*.filters
*.user
*.project
**/*.dylib
**/*.so
**/*.old
**/*.files
**/*.config
**/*.creator
*.log
.DS_Store
bin/florincoin*
bin/SHA256SUMS.asc
regtest/data/node1/regtest
regtest/data/node2/regtest
regtest/data/node3/regtest
flocore-node.json*
*.bak
*.orig
lib/services/insight-api

View File

@ -1,5 +1,22 @@
dist: trusty
sudo: false
language: node_js
env:
- BITCORENODE_ENV=test BITCORENODE_ASSUME_YES=true CXX=g++-4.8 CC=gcc-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- gcc-4.8
node_js:
- 8
- "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

View File

@ -1,7 +1,7 @@
Copyright (c) 2014-2015 BitPay, Inc.
Parts of this software are based on Florincoin Core
Copyright (c) 2009-2015 The Florincoin Core developers
Parts of this software are based on Bitcoin Core
Copyright (c) 2009-2015 The Bitcoin Core developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

1
PATCH_VERSION Normal file
View File

@ -0,0 +1 @@
v0.11.2

View File

@ -1,71 +1,74 @@
Flocore Node
Bitcore Node
============
A Florincoin blockchain indexing and query service. Intended to be used with as a Florincoin full node or in conjunction with a Florincoin full node.
## Upgrading from previous versions of Flocore Node
There is no upgrade path from previous versions of Flocore Node due to the removal of the included Florincoin Core software. By installing this version, you must resynchronize the indexes from scratch.
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.
## Install
```bash
npm install
./bin/flocore-node start
npm install -g bitcore-node
bitcore-node start
```
Note: A default configuration file is placed in the flocore user's home directory (~/.flocore/flocore-node.json). Or, alternatively, you can copy the provided "flocore-node.json.sample" file to the project's root directory as flocore-node.json and edit it for your preferences. If you don't have a preferred block source (trusted peer), [Bcoin](https://github.com/bcoin-org/bcoin) will be started automatically and synchronized with the mainnet chain.
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.
## Prerequisites
- Node.js v8.2.0+
- ~500GB of disk storage
- 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
The main configuration file is called "flocore-node.json". This file instructs flocore-node for the following options:
Bitcore includes a Command Line Interface (CLI) for managing, configuring and interfacing with your Bitcore Node.
- location of database files (datadir)
- tcp port for web services, if configured (port)
- florincoin network type (e.g. mainnet, testnet3, regtest), (network)
- what services to include (services)
- the services' configuration (servicesConfig)
```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](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 Flocore:
There are several add-on services available to extend the functionality of Bitcore:
- [Insight API](https://github.com/bitpay/insight-api)
- [Insight UI](https://github.com/bitpay/insight-ui)
- [Flocore Wallet Service](https://github.com/bitpay/flocore-wallet-service)
- [Insight API](https://github.com/bitpay/insight-api/tree/v0.3.0)
- [Insight UI](https://github.com/bitpay/insight/tree/v0.3.0)
- [Bitcore Wallet Service](https://github.com/bitpay/bitcore-wallet-service)
## Documentation
- [Services](docs/services.md)
- [Fee](docs/services/fee.md) - Creates a service to handle fee queries
- [Header](docs/services/header.md) - Creates a service to handle block headers
- [Block](docs/services/block.md) - Creates a service to handle blocks
- [Transaction](docs/services/transaction.md) - Creates a service to handle transactions
- [Address](docs/services/address.md) - Creates a service to handle addresses
- [Mempool](docs/services/mempool.md) - Creates a service to handle mempool
- [Timestamp](docs/services/timestamp.md) - Creates a service to handle timestamp
- [Db](docs/services/db.md) - Creates a service to handle the database
- [p2p](docs/services/p2p.md) - Creates a service to handle the peer-to-peer network
- [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.
- [Web](docs/services/web.md) - Creates an express application over which services can expose their web/API content
- [Development Environment](docs/development.md) - Guide for setting up a development environment
- [Build & Install](docs/build.md) - How to build and install from source
- [Testing & Development](docs/testing.md) - Developer guide for testing
- [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
Please send pull requests for bug fixes, code optimization, and ideas for improvement. For more information on how to contribute, please refer to our [CONTRIBUTING](https://github.com/bitpay/flocore/blob/master/CONTRIBUTING.md) file.
Please send pull requests for bug fixes, code optimization, and ideas for improvement. For more information on how to contribute, please refer to our [CONTRIBUTING](https://github.com/bitpay/bitcore/blob/master/CONTRIBUTING.md) file.
## License
Code released under [the MIT license](https://github.com/bitpay/flocore-node/blob/master/LICENSE).
Code released under [the MIT license](https://github.com/bitpay/bitcore-node/blob/master/LICENSE).
Copyright 2013-2017 BitPay, Inc.
Copyright 2013-2015 BitPay, Inc.
- florincoin: Copyright (c) 2009-2015 Florincoin Core Developers (MIT License)
- bitcoin: Copyright (c) 2009-2015 Bitcoin Core Developers (MIT License)

View File

@ -0,0 +1,75 @@
'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();
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

169
benchmarks/index.js Normal file
View File

@ -0,0 +1,169 @@
'use strict';
var benchmark = require('benchmark');
var bitcoin = require('bitcoin');
var async = require('async');
var maxTime = 20;
console.log('Bitcoin Service native interface vs. Bitcoin JSON RPC interface');
console.log('----------------------------------------------------------------------');
// To run the benchmarks a fully synced Bitcore Core directory is needed. The RPC comands
// can be modified to match the settings in bitcoin.conf.
var fixtureData = {
blockHashes: [
'00000000fa7a4acea40e5d0591d64faf48fd862fa3561d111d967fc3a6a94177',
'000000000017e9e0afc4bc55339f60ffffb9cbe883f7348a9fbc198a486d5488',
'000000000019ddb889b534c5d85fca2c91a73feef6fd775cd228dea45353bae1',
'0000000000977ac3d9f5261efc88a3c2d25af92a91350750d00ad67744fa8d03'
],
txHashes: [
'5523b432c1bd6c101bee704ad6c560fd09aefc483f8a4998df6741feaa74e6eb',
'ff48393e7731507c789cfa9cbfae045b10e023ce34ace699a63cdad88c8b43f8',
'5d35c5eebf704877badd0a131b0a86588041997d40dbee8ccff21ca5b7e5e333',
'88842f2cf9d8659c3434f6bc0c515e22d87f33e864e504d2d7117163a572a3aa',
]
};
var bitcoind = require('../').services.Bitcoin({
node: {
datadir: process.env.HOME + '/.bitcoin',
network: {
name: 'testnet'
}
}
});
bitcoind.on('error', function(err) {
console.error(err.message);
});
bitcoind.start(function(err) {
if (err) {
throw err;
}
console.log('Bitcoin Core started');
});
bitcoind.on('ready', function() {
console.log('Bitcoin Core ready');
var client = new bitcoin.Client({
host: 'localhost',
port: 18332,
user: 'bitcoin',
pass: 'local321'
});
async.series([
function(next) {
var c = 0;
var hashesLength = fixtureData.blockHashes.length;
var txLength = fixtureData.txHashes.length;
function bitcoindGetBlockNative(deffered) {
if (c >= hashesLength) {
c = 0;
}
var hash = fixtureData.blockHashes[c];
bitcoind.getBlock(hash, function(err, block) {
if (err) {
throw err;
}
deffered.resolve();
});
c++;
}
function bitcoindGetBlockJsonRpc(deffered) {
if (c >= hashesLength) {
c = 0;
}
var hash = fixtureData.blockHashes[c];
client.getBlock(hash, false, function(err, block) {
if (err) {
throw err;
}
deffered.resolve();
});
c++;
}
function bitcoinGetTransactionNative(deffered) {
if (c >= txLength) {
c = 0;
}
var hash = fixtureData.txHashes[c];
bitcoind.getTransaction(hash, true, function(err, tx) {
if (err) {
throw err;
}
deffered.resolve();
});
c++;
}
function bitcoinGetTransactionJsonRpc(deffered) {
if (c >= txLength) {
c = 0;
}
var hash = fixtureData.txHashes[c];
client.getRawTransaction(hash, function(err, tx) {
if (err) {
throw err;
}
deffered.resolve();
});
c++;
}
var suite = new benchmark.Suite();
suite.add('bitcoind getblock (native)', bitcoindGetBlockNative, {
defer: true,
maxTime: maxTime
});
suite.add('bitcoind getblock (json rpc)', bitcoindGetBlockJsonRpc, {
defer: true,
maxTime: maxTime
});
suite.add('bitcoind gettransaction (native)', bitcoinGetTransactionNative, {
defer: true,
maxTime: maxTime
});
suite.add('bitcoind gettransaction (json rpc)', bitcoinGetTransactionJsonRpc, {
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');
bitcoind.stop(function(err) {
if (err) {
console.error('Fail to stop services: ' + err);
process.exit(1);
}
process.exit(0);
});
});
});

4
bin/bitcore-node Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env node
var bitcore = require('../lib/cli/bitcore');
bitcore();

184
bin/build Executable file
View File

@ -0,0 +1,184 @@
#!/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

9
bin/clean Executable file
View File

@ -0,0 +1,9 @@
#!/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/*

2
bin/config_options.sh Normal file
View File

@ -0,0 +1,2 @@
./configure --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils

View File

@ -0,0 +1,2 @@
./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils

View File

@ -0,0 +1,2 @@
./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc

View File

@ -1,4 +0,0 @@
#!/usr/bin/env node
var flocore = require('../lib/cli/flocore');
flocore();

17
bin/get-tarball-name.js Normal file
View File

@ -0,0 +1,17 @@
'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 Executable file
View File

@ -0,0 +1,36 @@
#!/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

58
bin/package.js Normal file
View File

@ -0,0 +1,58 @@
'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);
}
});
});

36
bin/patch-bitcoin Executable file
View File

@ -0,0 +1,36 @@
#!/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

61
bin/start-libbitcoind.js Normal file
View File

@ -0,0 +1,61 @@
#!/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}));

8
bin/start.js Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env node
'use strict';
var start = require('../lib/scaffold/start');
var defaultConfig = require('../lib/scaffold/default-config');
start(defaultConfig());

52
bin/upload.js Normal file
View File

@ -0,0 +1,52 @@
'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 Executable file
View File

@ -0,0 +1,153 @@
#!/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 Normal file
View File

@ -0,0 +1,59 @@
{
"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)"
]
}
}
]
}

3
cache/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
patch_sha.txt
src/*
depends/*

1
contrib/.gitignore vendored
View File

@ -1 +0,0 @@
*.json

View File

@ -1,32 +0,0 @@
'use strict';
var request = require('request');
var config = require('./config.json');
// each of those addresses has a large number of utxos
// we are going to act like this group of addresses is our wallet, this ought to be fun!
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
var url = config.txs.new;
if (process.argv[2] === 'old') {
url = config.txs.old;
}
console.log(url);
var options = {
url: url,
method: 'POST',
qs: { from: 0, to: 5, noAsm: 1, noScriptSig: 1, noSpent: 1 },
json: { addrs: config.addrs }
};
request(options, function(err, response, body) {
console.log(body);
});

View File

@ -1,63 +0,0 @@
'use strict';
// pulls some rando utxos that can be used for testing
var levelup = require('levelup');
var leveldown = require('leveldown');
var Encoding = require('../lib/services/address/encoding');
var fs = require('fs');
var outputFile = '/tmp/large_amounts_utxos.json';
var addresses = [];
var dbLocation = process.argv[2];
console.log('Using db location: ', dbLocation);
var addressPrefix = new Buffer('0006', 'hex');
var startAddress = new Array(35).join('0');
var endAddress = new Array(35).join('f');
var store = levelup(leveldown(dbLocation), {
keyEncoding: 'binary',
valueEncoding: 'binary'
});
var encoding = new Encoding(addressPrefix);
var start = encoding.encodeUtxoIndexKey(startAddress);
var end = encoding.encodeUtxoIndexKey(endAddress);
var res = {};
var limit = 18000000;
var count = 0;
var stream = store.createReadStream({
gte: start,
lte: end
});
stream.on('data', function(data) {
count++;
limit--;
if (limit <= 0) {
stream.emit('end');
}
var key = encoding.decodeUtxoIndexKey(data.key);
if (res[key.address] >= 1) {
res[key.address]++;
} else {
res[key.address] = 1;
}
});
stream.on('end', function() {
Object.keys(res).map(function(key) {
if (res[key] > 1000) {
addresses.push(key);
}
});
fs.writeFileSync(outputFile, JSON.stringify(addresses));
console.log('total utxo count: ', count);
console.log('done');
});

View File

@ -1,48 +0,0 @@
'use strict';
var levelup = require('levelup');
var leveldown = require('leveldown');
var Encoding = require('../lib/services/address/encoding');
var dbPath = '/Users/chrisk/.bwdb/flocore-node.db';
var flocore = require('flocore-lib');
var db = levelup(dbPath, {keyEncoding: 'binary', valueEncoding: 'binary'});
var prefix = new Buffer('0002', 'hex');
var encoding = new Encoding(prefix);
var address = '1MfDRRVVKXUe5KNVZzu8CBzUZDHTTYZM94';
var addressLength = new Buffer(1);
addressLength.writeUInt8(address.length);
//var startBuffer = prefix;
//var endBuffer = Buffer.concat([prefix, new Buffer('ff', 'hex')]);
//var startBuffer = Buffer.concat([prefix, addressLength, new Buffer(address, 'utf8'), new Buffer('00', 'hex')]);
//var endBuffer = Buffer.concat([prefix, addressLength, new Buffer(address, 'utf8'), new Buffer('01', 'hex')]);
var start = Buffer.concat([prefix, new Buffer('0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9', 'hex')]);
var end = Buffer.concat([prefix, new Buffer('0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9', 'hex'), new Buffer('01', 'hex')]);
var stream = db.createReadStream({
gte: start,
lt: end
});
stream.on('data', function(data) {
var txkey = data.key.slice(2).toString('hex');
var height = data.value.readUInt32BE();
var timestamp = data.value.readDoubleBE(4);
var inputValues = [];
var inputValuesLength = data.value.readUInt16BE(12);
for(var i = 0; i < inputValuesLength / 8; i++) {
inputValues.push(buffer.readDoubleBE(i * 8 + 14));
}
var transaction = new flocore.Transaction(data.value.slice(inputValues.length * 8 + 14));
transaction.__height = height;
transaction.__inputValues = inputValues;
transaction.__timestamp = timestamp;
//console.log(txkey, transaction.toObject());
console.log(data.value);
console.log(transaction.__height, transaction.__inputValues, transaction.__timestamp);
//console.log(data.key.toString('hex'), data.value.toString('hex'));
});
stream.on('end', function() {
console.log('end');
});

View File

@ -1,11 +0,0 @@
#!/bin/bash
# helper script to run bwdb and/or restart it
# execute thie script and then simply tail /tmp/bwdb-out
# e.g. ./contrib/restart_bwdb.sh && tail -f /tmp/bwdb-out
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
pkill -2 -x flocore
wait
exec $DIR/../bin/flocore-node start >> /tmp/bwdb-out 2>&1 &

View File

@ -1,149 +0,0 @@
'use strict'
var Readable = require('stream').Readable;
var Writable = require('stream').Writable;
var Transform = require('stream').Transform;
var inherits = require('util').inherits;
var async = require('async');
function main() {
var blockStream = new BlockStream();
var processConcurrent = new ProcessConcurrent();
var processSerial = new ProcessSerial();
var writeStreamFast = new WriteStreamFast();
var writeStreamSlow = new WriteStreamSlow();
var start = Date.now();
writeStreamFast.on('finish', function() {
var end = Date.now();
console.log('Total time: ', (end - start) + ' ms');
console.log('Concurrent write time: ', writeStreamSlow.writeTime + ' ms');
console.log('Serial write time: ', writeStreamFast.writeTime + ' ms');
});
blockStream
.pipe(processConcurrent)
.pipe(writeStreamSlow);
blockStream
.pipe(processSerial)
.pipe(writeStreamFast);
}
function BlockStream() {
Readable.call(this, {objectMode: true, highWaterMark: 10});
this.height = 0;
}
inherits(BlockStream, Readable);
BlockStream.prototype._read = function() {
var self = this;
console.log('_read');
setTimeout(function() {
self.height++;
if(self.height > 40) {
self.push(null);
return;
}
console.log('ReadStream block ', self.height);
console.log(self.push({height: self.height}));
}, 500);
};
function ProcessSerial() {
Transform.call(this, {objectMode: true, highWaterMark: 10});
}
inherits(ProcessSerial, Transform);
ProcessSerial.prototype._transform = function(block, enc, callback) {
var operations = [{index1: block.height}, {index2: block.height}];
setTimeout(function() {
var obj = {
tipHeight: block.height,
operations: operations
};
callback(null, obj);
}, 100);
};
function ProcessConcurrent() {
Transform.call(this, {objectMode: true, highWaterMark: 10});
this.operations = [];
this.lastHeight = 0;
};
inherits(ProcessConcurrent, Transform);
ProcessConcurrent.prototype._transform = function(block, enc, callback) {
var self = this;
self.lastHeight = block.height;
setTimeout(function() {
self.operations = self.operations.concat([{index3: block.height}, {index4: block.height}]);
console.log(self.operations.length);
if(self.operations.length >= 10) {
var obj = {
concurrentTipHeight: self.lastHeight,
operations: self.operations
};
self.operations = [];
return callback(null, obj);
}
callback();
}, 100);
};
ProcessConcurrent.prototype._flush = function(callback) {
if(this.operations.length) {
var obj = {
concurrentTipHeight: this.lastHeight,
operations: this.operations
};
this.operations = [];
return callback(null, operations);
}
};
function WriteStreamSlow() {
Writable.call(this, {objectMode: true, highWaterMark: 10});
this.writeTime = 0;
}
inherits(WriteStreamSlow, Writable);
WriteStreamSlow.prototype._write = function(operations, enc, callback) {
var self = this;
setTimeout(function() {
console.log('WriteStreamSlow block ', operations.concurrentTipHeight);
self.writeTime += 2000;
callback();
}, 2000);
};
function WriteStreamFast() {
Writable.call(this, {objectMode: true, highWaterMark: 1});
this.writeTime = 0;
}
inherits(WriteStreamFast, Writable);
WriteStreamFast.prototype._write = function(operations, enc, callback) {
var self = this;
setTimeout(function() {
console.log('WriteStreamFast block ', operations.tipHeight);
self.writeTime += 1000;
callback();
}, 1000);
};
main();

View File

@ -1,20 +0,0 @@
[Unit]
Description=BWDB
Requires=network.target
[Service]
Type=simple
WorkingDirectory=/usr/opt/flocore
ExecStart=/usr/bin/bwdb
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=15
User=flocore
ExecStartPre=/bin/mkdir -p /run/bwdb
ExecStartPre=/bin/chown flocore:flocore /run/bwdb
ExecStartPre=/bin/chmod 755 /run/bwdb
PermissionsStartOnly=true
TimeoutStopSec=300
[Install]
WantedBy=multi-user.target

File diff suppressed because one or more lines are too long

89
docs/build.md Normal file
View File

@ -0,0 +1,89 @@
# 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).

View File

@ -1,5 +1,5 @@
# Bus
The bus provides a way to subscribe to events from any of the services running. It's implemented abstract from transport specific implementation. The primary use of the bus in Flocore Node is for subscribing to events via a web socket.
The bus provides a way to subscribe to events from any of the services running. It's implemented abstract from transport specific implementation. The primary use of the bus in Bitcore Node is for subscribing to events via a web socket.
## Opening/Closing
@ -20,11 +20,11 @@ bus.close();
```javascript
// subscribe to all transaction events
bus.subscribe('florincoind/rawtransaction');
bus.subscribe('db/transaction');
// to subscribe to new block hashes
bus.subscribe('florincoind/hashblock');
// only subscribe to events relevant to a bitcoin address
bus.subscribe('address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);
// unsubscribe
bus.unsubscribe('florincoind/rawtransaction');
bus.unsubscribe('db/transaction');
```

View File

@ -1,162 +0,0 @@
# 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 flocore-node:
```bash
cd ~
git clone git@github.com:<yourusername>/flocore-node.git
git clone git@github.com:<yourusername>/flocore-lib.git
```
To develop florincoin or to compile from source:
```bash
git clone git@github.com:<yourusername>/florincoin.git
git fetch origin <branchname>:<branchname>
git checkout <branchname>
```
**Note**: See florincoin documentation for building florincoin 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 flocore-lib
npm install
cd ../flocore-node
npm install
```
**Note**: If you get a message about not being able to download florincoin distribution, you'll need to compile florincoind from source, and setup your configuration to use that version.
We now will setup symlinks in `flocore-node` *(repeat this for any other modules you're planning on developing)*:
```bash
cd node_modules
rm -rf flocore-lib
ln -s ~/flocore-lib
rm -rf florincoind-rpc
ln -s ~/florincoind-rpc
```
And if you're compiling or developing florincoin:
```bash
cd ../bin
ln -sf ~/florincoin/src/florincoind
```
## Run Tests
If you do not already have mocha installed:
```bash
npm install mocha -g
```
To run all test suites:
```bash
cd flocore-node
npm run regtest
npm run test
```
To run a specific unit test in watch mode:
```bash
mocha -w -R spec test/services/florincoind.unit.js
```
To run a specific regtest:
```bash
mocha -R spec regtest/florincoind.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 flocore-node.json
touch package.json
```
Edit `flocore-node.json` with something similar to:
```json
{
"network": "livenet",
"port": 3001,
"services": [
"florincoind",
"web",
"insight-api",
"insight-ui",
"<additional_service>"
],
"servicesConfig": {
"florincoind": {
"spawn": {
"datadir": "/home/<youruser>/.florincoin",
"exec": "/home/<youruser>/florincoin/src/florincoind"
}
}
}
}
```
**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 ~/flocore-lib
ln -s ~/flocore-node
ln -s ~/insight-api
ln -s ~/insight-ui
```
Make sure that the `<datadir>/florincoin.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=florincoin
rpcpassword=local321
```
From within the `devnode` directory with the configuration file, start the node:
```bash
../flocore-node/bin/flocore-node start
```

16
docs/errors.md Normal file
View File

@ -0,0 +1,16 @@
# 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`.

View File

@ -1 +0,0 @@
../README.md

54
docs/index.md Normal file
View File

@ -0,0 +1,54 @@
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.

View File

@ -14,17 +14,17 @@ A node represents a collection of services that are loaded together. For more in
```js
var index = require('flocore-node');
var Florincoin = index.services.Florincoin;
var index = require('bitcore-node');
var Bitcoin = index.services.Bitcoin;
var Node = index.Node;
var configuration = {
datadir: '/home/user/.florincoin',
datadir: '/home/user/.bitcoin',
network: 'testnet',
services: [
{
name: 'florincoind',
module: Florincoin,
name: 'bitcoind',
module: Bitcoin,
config: {}
}
]
@ -37,7 +37,7 @@ node.start(function() {
});
node.on('ready', function() {
console.log('Florincoin Node Ready');
console.log('Bitcoin Node Ready');
});
node.on('error', function(err) {

6
docs/patch.md Normal file
View File

@ -0,0 +1,6 @@
# 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.

View File

@ -1,28 +1,55 @@
# 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.
Binaries for florincoind are distributed for convenience and built deterministically with Gitian, signatures for florincoind are located at the [gitian.sigs](https://github.com/bitpay/gitian.sigs) respository.
## 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)
## 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:
When publishing to npm, the .gitignore file is used to exclude files from the npm publishing process. Be sure that the flocore-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).
```json
{
"region": "us-west-2",
"accessKeyId": "xxx",
"secretAccessKey": "yyy"
}
```
To make a release, bump the `version` of the `package.json`:
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`:
```bash
git checkout master
git pull upstream master
npm install
npm run test
npm run regtest
npm run jshint
git commit -a -m "Bump package version to <version>"
git push upstream master
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>"
git push upstream master
```
Create a release tag and push it to the BitPay Github repo:
```bash
git tag -s v<version> -m 'v<version>'
git push upstream v<version>
git tag <version>
git push upstream <version>
```

View File

@ -1,20 +1,20 @@
# Scaffold
A collection of functions for creating, managing, starting, stopping and interacting with a Flocore node.
A collection of functions for creating, managing, starting, stopping and interacting with a Bitcore node.
## Install
This function will add a service to a node by installing the necessary dependencies and modifying the `flocore-node.json` configuration.
This function will add a service to a node by installing the necessary dependencies and modifying the `bitcore-node.json` configuration.
## Start
This function will load a configuration file `flocore-node.json` and instantiate and start a node based on the configuration.
This function will load a configuration file `bitcore-node.json` and instantiate and start a node based on the configuration.
## Find Config
This function will recursively find a configuration `flocore-node.json` file in parent directories and return the result.
This function will recursively find a configuration `bitcore-node.json` file in parent directories and return the result.
## Default Config
This function will return a default configuration with the default services based on environment variables, and will default to using the standard `/home/user/.florincoin` data directory.
This function will return a default configuration with the default services based on environment variables, and will default to using the standard `/home/user/.bitcoin` data directory.
## Uninstall
This function will remove a service from a node by uninstalling the necessary dependencies and modifying the `flocore-node.json` configuration.
This function will remove a service from a node by uninstalling the necessary dependencies and modifying the `bitcore-node.json` configuration.
## Call Method
This function will call an API method on a node via the JSON-RPC interface.

View File

@ -1,16 +1,16 @@
# Services
Flocore Node has a service module system that can start up additional services that can include additional:
Bitcore Node has a service module system that can start up additional services that can include additional:
- Blockchain indexes (e.g. querying balances for addresses)
- API methods
- HTTP routes
- Event types to publish and subscribe
The `flocore-node.json` file describes which services will load for a node:
The `bitcore-node.json` file describes which services will load for a node:
```json
{
"services": [
"florincoind", "web"
"bitcoind", "db", "address", "insight-api"
]
}
```
@ -20,38 +20,48 @@ Services correspond with a Node.js module as described in 'package.json', for ex
```json
{
"dependencies": {
"flocore-lib": "^0.13.7",
"flocore-node": "^0.2.0",
"bitcore-lib": "^0.13.7",
"bitcore-node": "^0.2.0",
"insight-api": "^3.0.0"
}
}
```
_Note:_ If you already have a flocore-node database, and you want to query data from previous blocks in the blockchain, you will need to reindex. Reindexing right now means deleting your flocore-node database and resyncing.
_Note:_ If you already have a bitcore-node database, and you want to query data from previous blocks in the blockchain, you will need to reindex. Reindexing right now means deleting your bitcore-node database and resyncing.
## Using Services Programmatically
If, instead, you would like to run a custom node, you can include services by including them in your configuration object when initializing a new node.
```js
//Require flocore
var flocore = require('flocore-node');
//Require bitcore
var bitcore = require('bitcore-node');
//Services
var Florincoin = flocore.services.Florincoin;
var Web = flocore.services.Web;
var Address = bitcore.services.Address;
var Bitcoin = bitcore.services.Bitcoin;
var DB = bitcore.services.DB;
var Web = bitcore.services.Web;
var myNode = new flocore.Node({
network: 'regtest'
services: [
var myNode = new bitcore.Node({
datadir: '/home/user/.bitcore',
network: {
name: 'livenet'
},
"services": [
{
name: 'florincoind',
module: Florincoin,
config: {
spawn: {
datadir: '/home/<username>/.florincoin',
exec: '/home/<username>/flocore-node/bin/florincoind'
}
}
name: "address",
module: Address,
config: {}
},
{
name: 'bitcoind',
module: Bitcoin,
config: {}
},
{
name: 'db',
module: DB,
config: {}
},
{
name: 'web',
@ -67,8 +77,8 @@ var myNode = new flocore.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.florincoind.getAddressBalance('1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v', false, function(err, total) {
console.log(total.balance); //Satoshi amount of this address
myNode.services.address.getBalance('1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v', false, function(err, total) {
console.log(total); //Satoshi amount of this address
});
```
@ -82,7 +92,144 @@ A new service can be created by inheriting from `Node.Service` and implementing
- `Service.prototype.getPublishEvents()` - Describes which events can be subscribed to for this service, useful to subscribe to events over the included web socket API.
- `Service.prototype.setupRoutes()` - A service can extend HTTP routes on an express application by implementing this method.
The `package.json` for the service module can either export the `Node.Service` directly, or specify a specific module to load by including `"flocoreNode": "lib/flocore-node.js"`.
The `package.json` for the service module can either export the `Node.Service` directly, or specify a specific module to load by including `"bitcoreNode": "lib/bitcore-node.js"`.
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.

View File

@ -1,23 +1,136 @@
# 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.
The address service provides an address index for the Florincoin blockchain. Specifically, it builds and maintains the following information about every address ever used on the Florincoin network:
## API Documentation
These methods are exposed over the JSON-RPC interface and can be called directly from a node via:
- block heights the address appeared in
- transaction ids and the index in the transaction
- whether the address appeared in an input or output
- the timestamp for the block
```js
node.services.address.<methodName>
```
Additionally, the address index also maintains the unspent transaction output index for the Florincoin blockchain. Example queries for this type of data is provided by 'getAddressUnspentOutputs', 'getAddressSummary', and 'getAddressHistory'.
**Get Unspent Outputs**
This service is generally used to support other services and is not used externally.
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:
## Service Configuration
```js
var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW';
var includeMempool = true;
node.services.address.getUnspentOutputs(address, includeMempool, function(err, unspentOutputs) {
// see below
});
```
none
The `unspentOutputs` will have the format:
## Other services this service Depends on
```js
[
{
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW',
txid: '9d956c5d324a1c2b12133f3242deff264a9b9f61be701311373998681b8c1769',
outputIndex: 1,
height: 150,
satoshis: 1000000000,
script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac',
confirmations: 3
}
]
```
- db
- header
- transaction
- timestamp
**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) {
});
```

129
docs/services/bitcoind.md Normal file
View File

@ -0,0 +1,129 @@
# 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.
## API Documentation
These methods are currently only available via directly interfacing with a node:
```js
node.services.bitcoind.<methodName>
```
**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:
```js
var blockHeight = 0;
node.services.bitcoind.getBlock(blockHeight, function(err, blockBuffer) {
if (err) {
throw err;
}
var block = bitcore.Block.fromBuffer(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 only the block index (including chain work and previous hash)
var blockIndex = node.services.bitcoind.getBlockIndex(blockHeight);
console.log(blockIndex);
```
**Retrieving and Sending Transactions**
Get a transaction asynchronously by reading it from disk, with an argument to optionally not include the mempool:
```js
var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
var queryMempool = true;
node.services.bitcoind.getTransaction(txid, queryMempool, function(err, transactionBuffer) {
if (err) {
throw err;
}
var transaction = bitcore.Transaction().fromBuffer(transactionBuffer);
});
// 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);
});
```
Send a transaction to the network:
```js
var numberOfBlocks = 3;
var feesPerKilobyte = node.services.bitcoind.estimateFee(numberOfBlocks); // in satoshis
try {
node.services.bitcoind.sendTransaction(transaction.serialize());
} catch(err) {
// handle error
}
```
Get all of the transactions in the mempool:
```js
var mempool = node.services.bitcoind.getMempoolTransactions();
var transactions = [];
for (var i = 0; i < mempool.length; i++) {
transactions.push(bitcore.Transaction().fromBuffer(transactions[i]));
}
```
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:
```js
node.services.bitcoind.on('tip', function(blockHash) {
// a new block tip has been added
});
node.services.bitcoind.on('tx', function(txInfo) {
// a new transaction has entered the mempool
});
node.services.bitcoind.on('txleave', function(txLeaveInfo) {
// a new transaction has left the mempool
});
```
The `txInfo` object will have the format:
```js
{
buffer: <Buffer...>,
mempool: true, // will currently always be true
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
}
```
The `txLeaveInfo` object will have the format:
```js
{
buffer: <Buffer...>,
hash: '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'
}
```

View File

@ -1,20 +0,0 @@
# Block Service
The block service provides a block index for the Florincoin blockchain. Specifically, there are two data points this service tracks:
- block hash
- raw block
This service is generally used to support other services and is not used externally.
## Service Configuration
none
## Other services this service Depends on
- header
- timestamp
- p2p
- db

View File

@ -1,13 +1,113 @@
# Db Service
# 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).
The db service provides an abstraction over the underlying database used to store the indexes in flocore-node.
## How to Reindex
## Service Configuration
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
None
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.
## Other services this service Depends on
## 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.
None
```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
}
```

View File

@ -1,22 +0,0 @@
# Fee Service
The fee service is a requirement of the insight-api service (not a flocore-node built-in service). Its primary purpose is to query a florincoin full node for the most up-to-date miner fees for transactions. A florincoin full node such as [BTC1](https://github.com/btc1/florincoin) or [bcoin](https://github.com/bcoin-org/bcoin) with an available RPC interface is required.
## Service Configuration
```json
"fee": {
"rpc": {
"user": "user",
"pass": "pass",
"host": "localhost",
"protocol": "http",
"port": 8332
}
}
```
## Usage Example
```bash
curl http://localhost:3001/insight-api/estimateFee
```

View File

@ -1,19 +0,0 @@
# Header Service
The header service provides a header index for the Florincoin blockchain. Specifically, it builds and maintains the following information about every florincoin block header:
- block hash
- block height
- block header
This service is generally used to support other services and is not used externally.
## Service Configuration
none
## Other services this service Depends on
- db
- p2p

View File

@ -1,16 +0,0 @@
# Mempool Service
The mempool service provides a mempool transaction index for the Florincoin blockchain. Specifically, it maintains a larger index of mempool transactions than a typical full node can manage on its own.
- transaction id
- transaction
This service is generally used to support other services and is not used externally.
## Service Configuration
none
## Other services this service Depends on
- db

View File

@ -1,21 +0,0 @@
# P2P Service
The p2p service provides a peer-to-peer interface for the Florincoin blockchain. This service abstracts the connection and commnuication interface between the Florincoin and the rest of flocore node.
This service also provides the publisher interface on flocore-node bus architecture. The P2P service will publish header, block and transaction events.
## Service Configuration
```json
"p2p": {
"peers": [
{ "ip": { "v4": "127.0.0.1" }, "port": 8333 }
]
}
```
## Other services this service Depends on
None

View File

@ -1,18 +0,0 @@
# Timestamp Service
The timestamp service provides a block timestamp index for the Florincoin blockchain. The only reason this index needs to exist is to ensure that block timestamps are always strictly greater than all the previous block timestamps. In the native block timestamps, this is not always the case. Without this index, accounting systems that are based on time spans (pretty much all of them), there will be issues accounting for transactions accurately.
- block timestamp
- block hash
This service is generally used to support other services and is not used externally.
## Service Configuration
none
## Other services this service Depends on
- db

View File

@ -1,21 +0,0 @@
# Transaction Service
The transaction service provides a transaction index for the Florincoin blockchain. Specifically, it builds and maintains the following information about every transaction on the Florincoin network:
- transaction ids and transactions
- input values for every transaction
- the timestamp for the block that the transaction appears in
- the block height for the block that the transaction appears in
This service is generally used to support other services and is not used externally.
## Service Configuration
none
## Other services this service Depends on
- p2p
- db
- timestamp
- mempool

View File

@ -1,5 +1,5 @@
# Web Service
The web service creates an express app which can be used by services for setting up web routes for API's, static content, web applications, etc. This allows users to interact with various flocore node services over one http or https port.
The web service creates an express app which can be used by services for setting up web routes for API's, static content, web applications, etc. This allows users to interact with various bitcore node services over one http or https port.
In order for your service to add routes, it must implement the `setupRoutes()` and `getRoutePrefix()` methods.
@ -22,7 +22,7 @@ MyService.prototype.getRoutePrefix = function() {
```
## Configuring Web Service for HTTPS
You can run the web service over https by editing your flocore node config, setting https to true and adding httpsOptions:
You can run the web service over https by editing your bitcore node config, setting https to true and adding httpsOptions:
```json
{

52
docs/testing.md Normal file
View File

@ -0,0 +1,52 @@
# 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
```

View File

@ -1,77 +0,0 @@
# Upgrade Notes
## From Flocore 3.0.0 to 4.0.0
`flocore-node@2.1.1` to `flocore-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 florincoind with three new `florincoin.conf` options:
- `-addressindex`
- `-timestampindex`
- `-spentindex`
To start reindexing add `reindex=1` during the **first startup only**.
### Configuration Options
- The `florincoin.conf` file in will need to be updated to include additional indexes *(see below)*.
- The `datadir` option is now a part of `florincoind` spawn configuration, and there is a new option to connect to multiple florincoind processes (Please see [Florincoin Service Docs](services/florincoind.md) for more details). The services `db` and `address` are now a part of the `florincoind` service. Here is how to update `flocore-node.json` configuration options:
**Before**:
```json
{
"datadir": "/home/<username>/.florincoin",
"network": "livenet",
"port": 3001,
"services": [
"address",
"florincoind",
"db",
"web"
]
}
```
**After**:
```json
{
"network": "livenet",
"port": 3001,
"services": [
"florincoind",
"web"
],
"servicesConfig": {
"florincoind": {
"spawn": {
"datadir": "/home/<username>/.florincoin",
"exec": "/home/<username>/flocore-node/bin/florincoind"
}
}
}
}
```
It will also be necessary to update `florincoin.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 `flocore-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 `florincoind` service. Please see [Florincoin Service Docs](services/florincoind.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 `florincoind`.

393
etc/bitcoin.patch Normal file
View File

@ -0,0 +1,393 @@
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));

49
example/client.js Normal file
View File

@ -0,0 +1,49 @@
'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']);

View File

@ -1,34 +0,0 @@
{
"network": "testnet",
"port": 3001,
"datadir": "/tmp",
"services": [
"p2p",
"db",
"header",
"block",
"mempool",
"address",
"transaction",
"timestamp",
"fee",
"insight-api",
"web"
],
"servicesConfig": {
"insight-api": {
"routePrefix": "api",
"disableRateLimiter": true,
"enableCache": true
},
"fee": {
"rpc": {
"user": "local",
"pass": "local",
"host": "localhost",
"protocol": "http",
"port": 18332
}
}
}
}

View File

@ -1,11 +1,26 @@
'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 = {};
@ -20,7 +35,5 @@ module.exports.scaffold.defaultConfig = require('./lib/scaffold/default-config')
module.exports.cli = {};
module.exports.cli.main = require('./lib/cli/main');
module.exports.cli.daemon = require('./lib/cli/daemon');
module.exports.cli.flocore = require('./lib/cli/flocore');
module.exports.cli.flocored = require('./lib/cli/flocored');
module.exports.lib = require('flocore-lib');
module.exports.cli.bitcore = require('./lib/cli/bitcore');
module.exports.cli.bitcored = require('./lib/cli/bitcored');

3
integration/data/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.lock
blocks
regtest

View File

@ -0,0 +1,9 @@
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

View File

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICDTCCAXYCCQCsGf/7CM97gDANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJV
UzELMAkGA1UECBMCR0ExDDAKBgNVBAcTA2ZvbzEhMB8GA1UEChMYSW50ZXJuZXQg
V2lkZ2l0cyBQdHkgTHRkMB4XDTE1MDgyNjE3NTAwOFoXDTE1MDkyNTE3NTAwOFow
SzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkdBMQwwCgYDVQQHEwNmb28xITAfBgNV
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOB
jQAwgYkCgYEA7SXnPpKk+0qXTTa42jzvu/C/gdGby0arY50bE2A+epI7FlI5YqKd
hQWoNRpuehF3jH6Ij3mbeeImtTA7TaUYlgHKpn63xfJ0cRj55+6vqq09nDxf0Lm9
IpTbgllu1l+SHtSuzFBVtGuNRSqObf8gD5XCD5lWK1vXHQ6PFSnAakMCAwEAATAN
BgkqhkiG9w0BAQUFAAOBgQBNARLDgsw7NCBVkn57AEgwZptxeyvFWlGZCd0BmYIX
ZFk7T1OQDwn7GlHry2IBswI0QRi076RQ4oJq+fg2O3XdFvEYV0cyypW7AxrnYTHP
m1h2xr6Y5vhxFKP8DxpAxST27DHbR18YvTD+IGtp2UjLj646587N0MWxt8vmaU3c
og==
-----END CERTIFICATE-----

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDtJec+kqT7SpdNNrjaPO+78L+B0ZvLRqtjnRsTYD56kjsWUjli
op2FBag1Gm56EXeMfoiPeZt54ia1MDtNpRiWAcqmfrfF8nRxGPnn7q+qrT2cPF/Q
ub0ilNuCWW7WX5Ie1K7MUFW0a41FKo5t/yAPlcIPmVYrW9cdDo8VKcBqQwIDAQAB
AoGBAOzYFxyCPu2OMI/4ICQuCcwtBEa2Ph+Fo/Rn2ru+OogV9Zc0ZYWiHSnWXYkz
rbSSL1CMqvyIGoRfHgOFeSTxxxtVyRo5LayI6Ce6V04yUFua16uo6hMX5bfGKZ9d
uq/HCDmdQvgifxUFpTpoSencuxwVCSYstMqjGfpobc5nxN2RAkEA+23BWNyA2qcq
yqSplgTQO0laXox9ksr1k2mJB2HtG3GNs1kapP+Z8AVtn/Mf9Va0hgbSnqpF9fll
1xpBqfidSQJBAPF1rizAm7xP7AeRRET36YKjZCVayA/g4rp7kecrNJTTupJIuxHr
JUlOTtXEWjIVcutSM7bP7bytPv4SAaApBysCQQDEZhqnCC+bHQO/IVrbNc1W0ljG
DGY22VV1HfYND0CAtHXkx9CZXJPpusPEMs0e/uiq3P9/MzDNEFCt8vOiCvMJAkEA
65oDIKGzk/R7/wpMjetEyva5AgXpjizFrmZigCjVPp61voT/G8XQ9Q1WuRjFVXc+
UcU8tpV+iIqXG3vgYDGITwJBANb0NFFF8QsygbENtad1tw1C/hNabHk8n9hu+Z8+
OSzEMlP7SsvddPaqusGydhTUxazoG3s4kEh5WmCWKgZGKO0=
-----END RSA PRIVATE KEY-----

229
integration/p2p.js Normal file
View File

@ -0,0 +1,229 @@
'use strict';
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 Transaction = bitcore.Transaction;
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 txs = [];
var client;
var messages;
var peer;
var coinbasePrivateKey;
var privateKey = bitcore.PrivateKey();
var destKey = bitcore.PrivateKey();
var BufferUtil = bitcore.util.buffer;
var blocks;
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: [ ]
});
var regtestNetwork = bitcore.Networks.get('regtest');
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
});
peer = new Peer({
host: '127.0.0.1',
port: '18444',
network: regtestNetwork
});
messages = new Messages({
network: regtestNetwork
});
blocks = 500;
log.info('Generating ' + blocks + ' blocks...');
// Generate enough blocks so that the initial coinbase transactions
// can be spent.
setImmediate(function() {
client.generate(blocks, function(err, response) {
if (err) {
throw err;
}
blockHashes = response.result;
log.info('Preparing test data...');
// Get all of the unspent outputs
client.listUnspent(0, blocks, function(err, response) {
var 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;
var tx = bitcore.Transaction();
tx.from(utxo);
tx.change(privateKey.toAddress());
tx.to(destKey.toAddress(), utxo.amount * 1e8 - 1000);
tx.sign(bitcore.PrivateKey.fromWIF(utxo.privateKeyWIF));
txs.push(tx);
finished();
});
}
], next);
}, function(err) {
if (err) {
throw err;
}
peer.on('ready', function() {
log.info('Peer ready');
done();
});
log.info('Connecting to peer');
peer.connect();
});
});
});
});
});
});
});
after(function(done) {
this.timeout(20000);
peer.on('disconnect', function() {
log.info('Peer disconnected');
bitcoind.stop(function(err, result) {
done();
});
});
peer.disconnect();
});
it('will be able to handle many inventory messages and be able to send getdata messages and received the txs', function(done) {
this.timeout(100000);
var usedTxs = {};
bitcoind.on('tx', function(result) {
var txFromResult = new Transaction().fromBuffer(result.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);
delete usedTxs[tx.id];
if (Object.keys(usedTxs).length === 0) {
done();
}
});
peer.on('getdata', function(message) {
var hash = message.inventory[0].hash;
var reversedHash = BufferUtil.reverse(hash).toString('hex');
var tx = usedTxs[reversedHash];
if (reversedHash === tx.id) {
var txMessage = messages.Transaction(tx);
peer.sendMessage(txMessage);
}
});
async.whilst(
function() {
return txs.length > 0;
},
function(callback) {
var tx = txs.pop();
usedTxs[tx.id] = tx;
var message = messages.Inventory.forTransaction(tx.hash);
peer.sendMessage(message);
callback();
},
function(err) {
if (err) {
throw err;
}
});
});
});

799
integration/regtest-node.js Normal file
View File

@ -0,0 +1,799 @@
'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
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 rimraf = require('rimraf');
var node;
var should = chai.should();
var BitcoinRPC = require('bitcoind-rpc');
var index = require('..');
var Transaction = index.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;
var outputForIsSpentTest1;
var unspentOutputSpentTxId;
describe('Node Functionality', function() {
var regtest;
before(function(done) {
this.timeout(30000);
var datadir = __dirname + '/data';
testKey = bitcore.PrivateKey(testWIF);
rimraf(datadir + '/regtest', function(err) {
if (err) {
throw err;
}
var configuration = {
datadir: datadir,
network: 'regtest',
services: [
{
name: 'db',
module: DBService,
config: {}
},
{
name: 'bitcoind',
module: BitcoinService,
config: {}
},
{
name: 'address',
module: AddressService,
config: {}
}
]
};
node = new BitcoreNode(configuration);
regtest = bitcore.Networks.get('regtest');
should.exist(regtest);
node.on('error', function(err) {
log.error(err);
});
node.on('ready', function() {
client = new BitcoinRPC({
protocol: 'https',
host: '127.0.0.1',
port: 18332,
user: 'bitcoin',
pass: 'local321',
rejectUnauthorized: false
});
var syncedHandler = function() {
if (node.services.db.tip.__height === 150) {
node.removeListener('synced', syncedHandler);
done();
}
};
node.on('synced', syncedHandler);
client.generate(150, function(err, response) {
if (err) {
throw err;
}
});
});
node.start(function(err) {
if (err) {
throw err;
}
});
});
});
after(function(done) {
this.timeout(20000);
node.stop(function(err, result) {
if(err) {
throw err;
}
done();
});
});
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();
});
client.generate(1, function(err, response) {
if (err) {
throw err;
}
block = response.result[0];
});
});
});
describe('Address Functionality', function() {
var address;
var unspentOutput;
before(function() {
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) {
if (err) {
throw err;
}
balance.should.equal(10 * 1e8);
done();
});
});
it('can get unspent outputs for address', function(done) {
node.services.address.getUnspentOutputs(address, false, function(err, results) {
if (err) {
throw err;
}
results.length.should.equal(1);
unspentOutput = outputForIsSpentTest1 = results[0];
done();
});
});
it('correctly give the history for the address', function(done) {
var options = {
from: 0,
to: 10,
queryMempool: false
};
node.services.address.getAddressHistory(address, options, function(err, results) {
if (err) {
throw err;
}
var items = results.items;
items.length.should.equal(1);
var info = items[0];
should.exist(info.addresses[address]);
info.addresses[address].outputIndexes.length.should.equal(1);
info.addresses[address].outputIndexes[0].should.be.within(0, 1);
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);
done();
});
});
it('correctly give the summary for the address', function(done) {
var options = {
queryMempool: false
};
node.services.address.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);
results.appearances.should.equal(1);
results.unconfirmedAppearances.should.equal(0);
results.txids.length.should.equal(1);
done();
});
});
describe('History', function() {
this.timeout(20000);
var testKey2;
var address2;
var testKey3;
var address3;
var testKey4;
var address4;
var testKey5;
var address5;
var testKey6;
var address6;
before(function(done) {
/* jshint maxstatements: 50 */
testKey2 = bitcore.PrivateKey.fromWIF('cNfF4jXiLHQnFRsxaJyr2YSGcmtNYvxQYSakNhuDGxpkSzAwn95x');
address2 = testKey2.toAddress(regtest).toString();
testKey3 = bitcore.PrivateKey.fromWIF('cVTYQbaFNetiZcvxzXcVMin89uMLC43pEBMy2etgZHbPPxH5obYt');
address3 = testKey3.toAddress(regtest).toString();
testKey4 = bitcore.PrivateKey.fromWIF('cPNQmfE31H2oCUFqaHpfSqjDibkt7XoT2vydLJLDHNTvcddCesGw');
address4 = testKey4.toAddress(regtest).toString();
testKey5 = bitcore.PrivateKey.fromWIF('cVrzm9gCmnzwEVMGeCxY6xLVPdG3XWW97kwkFH3H3v722nb99QBF');
address5 = testKey5.toAddress(regtest).toString();
testKey6 = bitcore.PrivateKey.fromWIF('cPfMesNR2gsQEK69a6xe7qE44CZEZavgMUak5hQ74XDgsRmmGBYF');
address6 = testKey6.toAddress(regtest).toString();
var tx = new Transaction();
tx.from(unspentOutput);
tx.to(address, 1 * 1e8);
tx.to(address, 2 * 1e8);
tx.to(address, 0.5 * 1e8);
tx.to(address, 3 * 1e8);
tx.fee(10000);
tx.change(address);
tx.sign(testKey);
unspentOutputSpentTxId = tx.id;
node.services.bitcoind.sendTransaction(tx.serialize());
function mineBlock(next) {
client.generate(1, function(err, response) {
if (err) {
throw err;
}
should.exist(response);
next();
});
}
client.generate(1, function(err, response) {
if (err) {
throw err;
}
should.exist(response);
node.once('synced', function() {
node.services.address.getUnspentOutputs(address, false, function(err, results) {
/* jshint maxstatements: 50 */
if (err) {
throw err;
}
results.length.should.equal(5);
async.series([
function(next) {
var tx2 = new Transaction();
tx2.from(results[0]);
tx2.to(address2, results[0].satoshis - 10000);
tx2.change(address);
tx2.sign(testKey);
node.services.bitcoind.sendTransaction(tx2.serialize());
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);
}, 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);
}, function(next) {
var tx5 = new Transaction();
tx5.from(results[3]);
tx5.from(results[4]);
tx5.to(address5, results[3].satoshis - 10000);
tx5.to(address6, results[4].satoshis - 10000);
tx5.change(address);
tx5.sign(testKey);
node.services.bitcoind.sendTransaction(tx5.serialize());
mineBlock(next);
}
], function(err) {
if (err) {
throw err;
}
node.once('synced', function() {
done();
});
});
});
});
});
});
it('five addresses', function(done) {
var addresses = [
address2,
address3,
address4,
address5,
address6
];
var options = {};
node.services.address.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].confirmations.should.equal(1);
history[1].height.should.equal(156);
should.exist(history[1].addresses[address4]);
history[2].height.should.equal(155);
should.exist(history[2].addresses[address3]);
history[3].height.should.equal(154);
should.exist(history[3].addresses[address2]);
history[3].satoshis.should.equal(99990000);
history[3].confirmations.should.equal(4);
done();
});
});
it('five addresses (limited by height)', function(done) {
var addresses = [
address2,
address3,
address4,
address5,
address6
];
var options = {
start: 157,
end: 156
};
node.services.address.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]);
done();
});
});
it('five addresses (limited by height 155 to 154)', function(done) {
var addresses = [
address2,
address3,
address4,
address5,
address6
];
var options = {
start: 155,
end: 154
};
node.services.address.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);
done();
});
});
it('five addresses (paginated by index)', function(done) {
var addresses = [
address2,
address3,
address4,
address5,
address6
];
var options = {
from: 0,
to: 3
};
node.services.address.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].confirmations.should.equal(1);
history[1].height.should.equal(156);
should.exist(history[1].addresses[address4]);
done();
});
});
it('one address with sending and receiving', function(done) {
var addresses = [
address
];
var options = {};
node.services.address.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].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[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].satoshis.should.equal(10 * 1e8);
done();
});
});
it('summary for an address (sending and receiving)', function(done) {
node.services.address.getAddressSummary(address, {}, function(err, results) {
if (err) {
throw err;
}
results.totalReceived.should.equal(2000000000);
results.totalSpent.should.equal(1999990000);
results.balance.should.equal(10000);
results.unconfirmedBalance.should.equal(0);
results.appearances.should.equal(6);
results.unconfirmedAppearances.should.equal(0);
results.txids.length.should.equal(6);
done();
});
});
it('total transaction count (sending and receiving)', function(done) {
var addresses = [
address
];
var options = {};
node.services.address.getAddressHistory(addresses, options, function(err, results) {
if (err) {
throw err;
}
results.totalCount.should.equal(6);
done();
});
});
describe('Pagination', function() {
it('from 0 to 1', function(done) {
var options = {
from: 0,
to: 1
};
node.services.address.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);
done();
});
});
it('from 1 to 2', function(done) {
var options = {
from: 1,
to: 2
};
node.services.address.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);
done();
});
});
it('from 2 to 3', function(done) {
var options = {
from: 2,
to: 3
};
node.services.address.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);
done();
});
});
it('from 3 to 4', function(done) {
var options = {
from: 3,
to: 4
};
node.services.address.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);
done();
});
});
it('from 4 to 5', function(done) {
var options = {
from: 4,
to: 5
};
node.services.address.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].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]);
done();
});
});
it('from 5 to 6', function(done) {
var options = {
from: 5,
to: 6
};
node.services.address.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].satoshis.should.equal(10 * 1e8);
done();
});
});
});
});
describe('Mempool Index', function() {
var unspentOutput;
before(function(done) {
node.services.address.getUnspentOutputs(address, false, function(err, results) {
if (err) {
throw err;
}
results.length.should.equal(1);
unspentOutput = results[0];
done();
});
});
it('will update the mempool index after new tx', function(done) {
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());
setImmediate(function() {
var addrObj = encoding.getAddressInfo(address);
node.services.address._getOutputsMempool(address, addrObj.hashBuffer,
addrObj.hashTypeBuffer, function(err, outs) {
if (err) {
throw err;
}
outs.length.should.equal(1);
done();
});
});
});
});
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();
});
});
});
});
});

492
integration/regtest.js Normal file
View File

@ -0,0 +1,492 @@
'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);
});
});
});

View File

@ -13,7 +13,6 @@ var util = require('util');
function Bus(params) {
events.EventEmitter.call(this);
this.node = params.node;
this.remoteAddress = params.remoteAddress;
}
util.inherits(Bus, events.EventEmitter);

View File

@ -5,11 +5,11 @@ var Liftoff = require('liftoff');
function main(parentServicesPath, additionalServices) {
var liftoff = new Liftoff({
name: 'flocore',
moduleName: 'flocore-node',
configName: 'flocore-node',
processTitle: 'flocore'
}).on('require', function (name) {
name: 'bitcore',
moduleName: 'bitcore-node',
configName: 'bitcore-node',
processTitle: 'bitcore'
}).on('require', function (name, module) {
console.log('Loading:', name);
}).on('requireFail', function (name, err) {
console.log('Unable to load:', name, err);
@ -31,15 +31,6 @@ function main(parentServicesPath, additionalServices) {
node.cli.main(parentServicesPath, additionalServices);
}
// Gracefully Shut Down
process.on('SIGTERM', function () {
console.log("Shutting down flocore-node")
node.stop(function() {
console.log("flocore-node successfully stopped!")
process.exit(0)
})
})
});
}

View File

@ -5,11 +5,11 @@ var Liftoff = require('liftoff');
function main(parentServicesPath, additionalServices) {
var liftoff = new Liftoff({
name: 'flocored',
moduleName: 'flocore-node',
configName: 'flocore-node',
processTitle: 'flocored'
}).on('require', function (name) {
name: 'bitcored',
moduleName: 'bitcore-node',
configName: 'bitcore-node',
processTitle: 'bitcored'
}).on('require', function (name, module) {
console.log('Loading:', name);
}).on('requireFail', function (name, err) {
console.log('Unable to load:', name, err);

View File

@ -2,20 +2,21 @@
var program = require('commander');
var path = require('path');
var flocore = require('..');
var bitcore = require('..');
function main(servicesPath, additionalServices) {
/* jshint maxstatements: 100 */
var version = flocore.version;
var start = flocore.scaffold.start;
var findConfig = flocore.scaffold.findConfig;
var defaultConfig = flocore.scaffold.defaultConfig;
var version = bitcore.version;
var start = bitcore.scaffold.start;
var findConfig = bitcore.scaffold.findConfig;
var defaultConfig = bitcore.scaffold.defaultConfig;
program
.version(version)
.description('Start the current node')
.option('-c, --config <dir>', 'Specify the directory with Flocore Node configuration');
.option('-c, --config <dir>', 'Specify the directory with Bitcore Node configuration')
.option('-d, --daemon', 'Make bitcore a daemon (running in the background)');
program.parse(process.argv);
@ -28,6 +29,9 @@ function main(servicesPath, additionalServices) {
additionalServices: additionalServices
});
}
if(program.daemon) {
configInfo.config.daemon = true;
}
if (servicesPath) {
configInfo.servicesPath = servicesPath;
}

View File

@ -2,20 +2,19 @@
var program = require('commander');
var path = require('path');
var flocorenode = require('..');
var utils = require('../utils');
var bitcorenode = require('..');
function main(servicesPath, additionalServices) {
/* jshint maxstatements: 100 */
var version = flocorenode.version;
var create = flocorenode.scaffold.create;
var add = flocorenode.scaffold.add;
var start = flocorenode.scaffold.start;
var remove = flocorenode.scaffold.remove;
var callMethod = flocorenode.scaffold.callMethod;
var findConfig = flocorenode.scaffold.findConfig;
var defaultConfig = flocorenode.scaffold.defaultConfig;
var version = bitcorenode.version;
var create = bitcorenode.scaffold.create;
var add = bitcorenode.scaffold.add;
var start = bitcorenode.scaffold.start;
var remove = bitcorenode.scaffold.remove;
var callMethod = bitcorenode.scaffold.callMethod;
var findConfig = bitcorenode.scaffold.findConfig;
var defaultConfig = bitcorenode.scaffold.defaultConfig;
program
.version(version);
@ -23,7 +22,7 @@ function main(servicesPath, additionalServices) {
program
.command('create <directory>')
.description('Create a new node')
.option('-d, --datadir <dir>', 'Specify the florincoin database directory')
.option('-d, --datadir <dir>', 'Specify the bitcoin database directory')
.option('-t, --testnet', 'Enable testnet as the network')
.action(function(dirname, cmd){
if (cmd.datadir) {
@ -49,7 +48,8 @@ function main(servicesPath, additionalServices) {
program
.command('start')
.description('Start the current node')
.option('-c, --config <dir>', 'Specify the directory with Flocore Node configuration')
.option('-c, --config <dir>', 'Specify the directory with Bitcore Node configuration')
.option('-d, --daemon', 'Make bitcore-node a daemon (running in the background)')
.action(function(cmd){
if (cmd.config) {
cmd.config = path.resolve(process.cwd(), cmd.config);
@ -60,6 +60,9 @@ function main(servicesPath, additionalServices) {
additionalServices: additionalServices
});
}
if(cmd.daemon) {
configInfo.config.daemon = true;
}
if (servicesPath) {
configInfo.servicesPath = servicesPath;
}
@ -72,7 +75,7 @@ function main(servicesPath, additionalServices) {
.action(function(services){
var configInfo = findConfig(process.cwd());
if (!configInfo) {
throw new Error('Could not find configuration, see `flocore-node create --help`');
throw new Error('Could not find configuration, see `bitcore-node create --help`');
}
var opts = {
path: configInfo.path,
@ -87,8 +90,8 @@ function main(servicesPath, additionalServices) {
}).on('--help', function() {
console.log(' Examples:');
console.log();
console.log(' $ flocore-node add wallet-service');
console.log(' $ flocore-node add insight-api');
console.log(' $ bitcore-node add wallet-service');
console.log(' $ bitcore-node add insight-api');
console.log();
});
@ -98,7 +101,7 @@ function main(servicesPath, additionalServices) {
.action(function(services){
var configInfo = findConfig(process.cwd());
if (!configInfo) {
throw new Error('Could not find configuration, see `flocore-node create --help`');
throw new Error('Could not find configuration, see `bitcore-node create --help`');
}
var opts = {
path: configInfo.path,
@ -113,16 +116,15 @@ function main(servicesPath, additionalServices) {
}).on('--help', function() {
console.log(' Examples:');
console.log();
console.log(' $ flocore-node remove wallet-service');
console.log(' $ flocore-node remove insight-api');
console.log(' $ bitcore-node remove wallet-service');
console.log(' $ bitcore-node remove insight-api');
console.log();
});
program
.command('call <method> [params...]')
.description('Call an API method')
.action(function(method, paramsArg) {
var params = utils.parseParamsWithJSON(paramsArg);
.action(function(method, params) {
var configInfo = findConfig(process.cwd());
if (!configInfo) {
configInfo = defaultConfig();

View File

@ -1,13 +0,0 @@
'use strict';
module.exports = {
BITCOIN_GENESIS_HASH: {
livenet: '09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea',
regtest: 'ec42fa26ca6dcb1103b59a1d24b161935ea4566f8d5736db8917d5b9a8dee0d7',
testnet: '9b7bc86236c34b5e3a39367c036b7fe8807a966c22a7a1f0da2a198a27e03731', //this is testnet3
testnet5: '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943' //this is testnet5
},
DB_PREFIX: new Buffer('ffff', 'hex')
};

View File

@ -2,11 +2,24 @@
var createError = require('errno').create;
var FlocoreNodeError = createError('FlocoreNodeError');
var BitcoreNodeError = createError('BitcoreNodeError');
var NoOutputs = createError('NoOutputs', BitcoreNodeError);
var NoOutput = createError('NoOutput', BitcoreNodeError);
var RPCError = createError('RPCError', FlocoreNodeError);
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);
module.exports = {
Error: FlocoreNodeError,
RPCError: RPCError
Error: BitcoreNodeError,
NoOutputs: NoOutputs,
NoOutput: NoOutput,
Wallet: Wallet,
Consensus: Consensus,
Transaction: Transaction
};

View File

@ -1,22 +1,14 @@
'use strict';
var flocore = require('flocore-lib');
var _ = flocore.deps._;
var colors = require('colors/safe');
/**
* Wraps console.log with some special magic
* @constructor
*/
function Logger(options) {
if (!options) {
options = {};
}
this.formatting = _.isUndefined(options.formatting) ? Logger.DEFAULT_FORMATTING : options.formatting;
function Logger() {
}
Logger.DEFAULT_FORMATTING = true;
/**
* Prints an info message
* #info
@ -38,9 +30,7 @@ Logger.prototype.error = function() {
* #debug
*/
Logger.prototype.debug = function() {
if (process.env.BITCORE_ENV === 'debug') {
this._log.apply(this, ['green', 'debug'].concat(Array.prototype.slice.call(arguments)));
}
this._log.apply(this, ['magenta', 'debug'].concat(Array.prototype.slice.call(arguments)));
};
/**
@ -55,21 +45,17 @@ Logger.prototype.warn = function() {
* Proxies console.log with color and arg parsing magic
* #_log
*/
Logger.prototype._log = function(color) {
Logger.prototype._log = function(color, type) {
if (process.env.NODE_ENV === 'test') {
return;
}
var args = Array.prototype.slice.call(arguments);
args = args.slice(1);
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.log;
if (level === 'error') {
fn = console.error;
}
fn.apply(console, args);
var date = new Date();
var typeString = colors[color].italic(args.shift() + ':');
args[0] = '[' + date.toISOString() + ']' + ' ' + typeString + ' ' + args[0];
console.log.apply(console, args);
};
module.exports = Logger;

View File

@ -3,84 +3,138 @@
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var async = require('async');
var assert = require('assert');
var flocore = require('flocore-lib');
var _ = flocore.deps._;
var bitcore = require('bitcore-lib');
var Networks = bitcore.Networks;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var index = require('./');
var log = index.log;
var Bus = require('./bus');
var errors = require('./errors');
/**
* A node is a hub of services, and will manage starting and stopping the services in
* the correct order based the the dependency chain. The node also holds common configuration
* properties that can be shared across services, such as network settings.
*
* The array of services should have the format:
* ```js
* {
* name: 'bitcoind',
* config: {}, // options to pass into constructor
* module: ServiceConstructor
* }
* ```
*
* @param {Object} config - The configuration of the node
* @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
* @param {String} config.httpsOptions.key - Path to key file
* @param {String} config.httpsOptions.cert - Path to cert file
* @param {}
*/
function Node(config) {
/* jshint maxstatements: 20 */
if(!(this instanceof Node)) {
return new Node(config);
}
this._init(config);
if (!_.isUndefined(config.formatLogs)) {
this.log.formatting = config.formatLogs ? true : false;
}
if (config.services) {
this._unloadedServices = config.services;
}
}
util.inherits(Node, EventEmitter);
Node.prototype._init = function(config) {
this.configPath = config.path;
this.errors = errors;
this.log = log;
this.datadir = config.datadir;
this.network = null;
this.services = {};
this._unloadedServices = [];
// TODO type check the arguments of config.services
if (config.services) {
$.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;
this._setNetwork(config);
};
}
util.inherits(Node, EventEmitter);
/**
* Will set the this.network based on a network string.
* @param {Object} config
* @param {String} config.network - Possible options "testnet", "regtest" or "livenet"
*/
Node.prototype._setNetwork = function(config) {
this.network = config.network;
};
Node.prototype.openBus = function(options) {
if (!options) {
options = {};
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: [ ]
});
this.network = Networks.get('regtest');
} else {
this.network = Networks.defaultNetwork;
}
return new Bus({node: this, remoteAddress: options.remoteAddress});
$.checkState(this.network, 'Unrecognized network');
};
/**
* Will instantiate a new Bus for this node.
* @returns {Bus}
*/
Node.prototype.openBus = function() {
return new Bus({node: this});
};
/**
* Will get an array of API method descriptions from all of the available services.
* @returns {Array}
*/
Node.prototype.getAllAPIMethods = function() {
var methods = [];
for(var i in this.services) {
var mod = this.services[i];
if (mod.getAPIMethods) {
methods = methods.concat(mod.getAPIMethods());
}
methods = methods.concat(mod.getAPIMethods());
}
return methods;
};
/**
* Will get an array of events from all of the available services.
* @returns {Array}
*/
Node.prototype.getAllPublishEvents = function() {
var events = [];
for (var i in this.services) {
var mod = this.services[i];
if (mod.getPublishEvents) {
events = events.concat(mod.getPublishEvents());
}
events = events.concat(mod.getPublishEvents());
}
return events;
};
Node.prototype._getServiceOrder = function(services) {
/**
* Will organize services into the order that they should be started
* based on the service's dependencies.
* @returns {Array}
*/
Node.prototype.getServiceOrder = function() {
var services = this._unloadedServices;
// organize data for sorting
var names = [];
var servicesByName = {};
for (var i = 0; i < services.length; i++) {
@ -97,10 +151,12 @@ Node.prototype._getServiceOrder = function(services) {
var name = names[i];
var service = servicesByName[name];
assert(service, 'Required dependency "' + name + '" not available.');
$.checkState(service, 'Required dependency "' + name + '" not available.');
// first add the dependencies
addToStack(service.module.dependencies);
// add to the stack if it hasn't been added
if(!stackNames[name]) {
stack.push(service);
stackNames[name] = true;
@ -114,25 +170,32 @@ Node.prototype._getServiceOrder = function(services) {
return stack;
};
/**
* Will instantiate an instance of the service module, add it to the node
* services, start the service and add available API methods to the node and
* checking for any conflicts.
* @param {Object} serviceInfo
* @param {String} serviceInfo.name - The name of the service
* @param {Object} serviceInfo.module - The service module constructor
* @param {Object} serviceInfo.config - Options to pass into the constructor
* @param {Function} callback - Called when the service is started
* @private
*/
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;
if (serviceInfo.config) {
assert(_.isObject(serviceInfo.config));
assert(!serviceInfo.config.node);
assert(!serviceInfo.config.name);
config = serviceInfo.config;
} else {
config = {};
}
var config = serviceInfo.config;
config.node = this;
config.name = serviceInfo.name;
var service = new serviceInfo.module(config);
// include in loaded services
self.services[serviceInfo.name] = service;
service.start(function(err) {
@ -140,26 +203,25 @@ Node.prototype._startService = function(serviceInfo, callback) {
return callback(err);
}
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];
// 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 (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 (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(', ')));
}
callback();
@ -168,21 +230,13 @@ Node.prototype._startService = function(serviceInfo, callback) {
};
Node.prototype._logTitle = function() {
if (this.configPath) {
log.info('Using config:', this.configPath);
log.info('Using network:', this.network);
}
};
/**
* Will start all running services in the order based on the dependency chain.
* @param {Function} callback - Called when all services are started
*/
Node.prototype.start = function(callback) {
var self = this;
var services = this._unloadedServices;
var servicesOrder = this._getServiceOrder(services);
self._logTitle();
var servicesOrder = this.getServiceOrder();
async.eachSeries(
servicesOrder,
@ -190,43 +244,41 @@ Node.prototype.start = function(callback) {
self._startService(service, next);
},
function(err) {
if (err) {
return callback(err);
}
self.emit('ready');
callback();
}
);
};
/**
* Will stop all running services in the reverse order that they
* were initially started.
* @param {Function} callback - Called when all services are stopped
*/
Node.prototype.stop = function(callback) {
log.info('Beginning shutdown');
var self = this;
var services = this._getServiceOrder(this._unloadedServices).reverse();
var services = this.getServiceOrder().reverse();
this.stopping = true;
this.emit('stopping');
async.eachSeries(
services,
function(service, next) {
if (self.services[service.name]) {
log.info('Stopping ' + service.name);
self.services[service.name].stop(next);
} else {
log.info('Stopping ' + service.name + ' (not started)');
setImmediate(next);
}
},
function() {
if (callback) {
callback();
}
});
services,
function(service, next) {
if (self.services[service.name]) {
log.info('Stopping ' + service.name);
self.services[service.name].stop(next);
} else {
log.info('Stopping ' + service.name + ' (not started)');
setImmediate(next);
}
},
callback
);
};
module.exports = Node;

View File

@ -4,10 +4,9 @@ var async = require('async');
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var flocore = require('flocore-lib');
var utils = require('../utils');
var $ = flocore.util.preconditions;
var _ = flocore.deps._;
var bitcore = require('bitcore-lib');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* @param {String} configFilePath - The absolute path to the configuration file
@ -15,7 +14,7 @@ var _ = flocore.deps._;
* @param {Function} done
*/
function addConfig(configFilePath, service, done) {
$.checkState(utils.isAbsolutePath(configFilePath), 'An absolute path is expected');
$.checkState(path.isAbsolute(configFilePath), 'An absolute path is expected');
fs.readFile(configFilePath, function(err, data) {
if (err) {
return done(err);
@ -40,7 +39,7 @@ function addConfig(configFilePath, service, done) {
* @param {Function} done
*/
function addService(configDir, service, done) {
$.checkState(utils.isAbsolutePath(configDir), 'An absolute path is expected');
$.checkState(path.isAbsolute(configDir), 'An absolute path is expected');
var npm = spawn('npm', ['install', service, '--save'], {cwd: configDir});
npm.stdout.on('data', function(data) {
@ -62,7 +61,7 @@ function addService(configDir, service, done) {
/**
* @param {String} options.cwd - The current working directory
* @param {String} options.dirname - The flocore-node configuration directory
* @param {String} options.dirname - The bitcore-node configuration directory
* @param {Array} options.services - An array of strings of service names
* @param {Function} done - A callback function called when finished
*/
@ -70,7 +69,7 @@ function add(options, done) {
$.checkArgument(_.isObject(options));
$.checkArgument(_.isFunction(done));
$.checkArgument(
_.isString(options.path) && utils.isAbsolutePath(options.path),
_.isString(options.path) && path.isAbsolute(options.path),
'An absolute path is expected'
);
$.checkArgument(Array.isArray(options.services));
@ -78,12 +77,12 @@ function add(options, done) {
var configPath = options.path;
var services = options.services;
var flocoreConfigPath = path.resolve(configPath, 'flocore-node.json');
var bitcoreConfigPath = path.resolve(configPath, 'bitcore-node.json');
var packagePath = path.resolve(configPath, 'package.json');
if (!fs.existsSync(flocoreConfigPath) || !fs.existsSync(packagePath)) {
if (!fs.existsSync(bitcoreConfigPath) || !fs.existsSync(packagePath)) {
return done(
new Error('Directory does not have a flocore-node.json and/or package.json file.')
new Error('Directory does not have a bitcore-node.json and/or package.json file.')
);
}
@ -108,8 +107,8 @@ function add(options, done) {
oldPackage = updatedPackage;
var serviceName = newDependencies[0];
// add service to flocore-node.json
addConfig(flocoreConfigPath, serviceName, next);
// add service to bitcore-node.json
addConfig(bitcoreConfigPath, serviceName, next);
});
}, done
);

View File

@ -1,35 +1,40 @@
'use strict';
var spawn = require('child_process').spawn;
var flocore = require('flocore-lib');
var bitcore = require('bitcore-lib');
var async = require('async');
var $ = flocore.util.preconditions;
var _ = flocore.deps._;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var path = require('path');
var packageFile = require('../../package.json');
var mkdirp = require('mkdirp');
var fs = require('fs');
var defaultConfig = require('./default-config');
var defaultBaseConfig = require('./default-base-config');
var version = '^' + packageFile.version;
var version;
if (packageFile.version.match('-dev')) {
version = '^' + packageFile.lastBuild;
} else {
version = '^' + packageFile.version;
}
var BASE_PACKAGE = {
description: 'A full Florincoin node build with Flocore',
description: 'A full Bitcoin node build with Bitcore',
repository: 'https://github.com/user/project',
license: 'MIT',
readme: 'README.md',
dependencies: {
'flocore-lib': '^' + flocore.version,
'flocore-node': version
'bitcore-lib': '^' + bitcore.version,
'bitcore-node': version
}
};
/**
* Will create a directory and florincoin.conf file for Florincoin.
* Will create a directory and bitcoin.conf file for Bitcoin.
* @param {String} dataDir - The absolute path
* @param {Function} done - The callback function called when finished
*/
function createFlorincoinDirectory(datadir, done) {
function createBitcoinDirectory(datadir, done) {
mkdirp(datadir, function(err) {
if (err) {
throw err;
@ -42,10 +47,10 @@ function createFlorincoinDirectory(datadir, done) {
}
/**
* Will create a base Flocore Node configuration directory and files.
* Will create a base Bitcore Node configuration directory and files.
* @param {Object} options
* @param {String} options.network - "testnet" or "livenet"
* @param {String} options.datadir - The florincoin database directory
* @param {String} options.datadir - The bitcoin database directory
* @param {String} configDir - The absolute path
* @param {Boolean} isGlobal - If the configuration depends on globally installed node services.
* @param {Function} done - The callback function called when finished
@ -55,13 +60,13 @@ function createConfigDirectory(options, configDir, isGlobal, done) {
if (err) {
throw err;
}
var configInfo = defaultConfig(options);
var configInfo = defaultBaseConfig(options);
var config = configInfo.config;
var configJSON = JSON.stringify(config, null, 2);
var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2);
try {
fs.writeFileSync(configDir + '/flocore-node.json', configJSON);
fs.writeFileSync(configDir + '/bitcore-node.json', configJSON);
if (!isGlobal) {
fs.writeFileSync(configDir + '/package.json', packageJSON);
}
@ -74,13 +79,13 @@ function createConfigDirectory(options, configDir, isGlobal, done) {
}
/**
* Will setup a directory with a Flocore Node directory, configuration file,
* florincoin configuration, and will install all necessary dependencies.
* Will setup a directory with a Bitcore Node directory, configuration file,
* bitcoin configuration, and will install all necessary dependencies.
*
* @param {Object} options
* @param {String} options.cwd - The current working directory
* @param {String} options.dirname - The name of the flocore node configuration directory
* @param {String} options.datadir - The path to the florincoin datadir
* @param {String} options.dirname - The name of the bitcore node configuration directory
* @param {String} options.datadir - The path to the bitcoin datadir
* @param {Function} done - A callback function called when finished
*/
function create(options, done) {
@ -103,7 +108,7 @@ function create(options, done) {
async.series([
function(next) {
// Setup the the flocore-node directory and configuration
// Setup the the bitcore-node directory and configuration
if (!fs.existsSync(absConfigDir)) {
var createOptions = {
network: options.network,
@ -115,9 +120,9 @@ function create(options, done) {
}
},
function(next) {
// Setup the florincoin directory and configuration
// Setup the bitcoin directory and configuration
if (!fs.existsSync(absDataDir)) {
createFlorincoinDirectory(absDataDir, next);
createBitcoinDirectory(absDataDir, next);
} else {
next();
}

View File

@ -0,0 +1,26 @@
'use strict';
var path = require('path');
/**
* Will return the path and default bitcore-node configuration on environment variables
* or default locations.
* @param {Object} options
* @param {String} options.network - "testnet" or "livenet"
*/
function getDefaultBaseConfig(options) {
if (!options) {
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']
}
};
}
module.exports = getDefaultBaseConfig;

View File

@ -3,15 +3,10 @@
var path = require('path');
var mkdirp = require('mkdirp');
var fs = require('fs');
var packageJson = require('../../package');
function getMajorVersion(versionString) {
return parseInt(versionString.split('.')[0]);
}
/**
* Will return the path and default flocore-node configuration. It will search for the
* configuration file in the "~/.flocore" directory, and if it doesn't exist, it will create one
* Will return the path and default bitcore-node configuration. It will search for the
* configuration file in the "~/.bitcore" directory, and if it doesn't exist, it will create one
* based on default settings.
* @param {Object} [options]
* @param {Array} [options.additionalServices] - An optional array of services.
@ -22,48 +17,27 @@ function getDefaultConfig(options) {
options = {};
}
var defaultPath = path.resolve(process.env.HOME, './.flocore');
var defaultConfigFile = path.resolve(defaultPath, './flocore-node.json');
var defaultPath = path.resolve(process.env.HOME, './.bitcore');
var defaultConfigFile = path.resolve(defaultPath, './bitcore-node.json');
if (!fs.existsSync(defaultPath)) {
mkdirp.sync(defaultPath);
}
if (fs.existsSync(defaultConfigFile)) {
var currentConfig = require(defaultConfigFile);
// config must have a `version` field with major equal to package major version
if(currentConfig.version && getMajorVersion(packageJson.version) === getMajorVersion(currentConfig.version)) {
return {
path: defaultPath,
config: currentConfig
};
}
console.log(`The configuration file at '${defaultConfigFile}' is incompatible with this version of Flocore.`);
var now = new Date();
// flocore-node.YYYY-MM-DD.UnixTimestamp.json
var backupFileName = `flocore-node.${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}.${now.getTime()}.json`;
var backupFile = path.resolve(defaultPath, backupFileName);
fs.renameSync(defaultConfigFile, backupFile);
console.log(`The previous configuration file has been moved to: ${backupFile}.`);
var defaultServices = ['bitcoind', 'db', 'address', 'web'];
if (options.additionalServices) {
defaultServices = defaultServices.concat(options.additionalServices);
}
console.log(`Creating a new configuration file at: ${defaultConfigFile}.`);
var defaultServices = [
'address',
'block',
'db',
'fee',
'header',
'mempool',
'p2p',
'timestamp',
'transaction',
'web'
];
if (!fs.existsSync(defaultConfigFile)) {
var defaultConfig = {
datadir: path.resolve(defaultPath, './data'),
network: 'livenet',
port: 3001,
services: defaultServices
};
fs.writeFileSync(defaultConfigFile, JSON.stringify(defaultConfig, null, 2));
}
var defaultDataDir = path.resolve(defaultPath, './data');
@ -71,23 +45,6 @@ function getDefaultConfig(options) {
mkdirp.sync(defaultDataDir);
}
var defaultConfig = {
version: packageJson.version,
network: 'livenet',
port: 3001,
services: options.additionalServices ? defaultServices.concat(options.additionalServices) : defaultServices,
datadir: defaultDataDir,
servicesConfig: {
'flosight-api': {
cwdRequirePath: 'node_modules/flosight-api'
},
'flosight-ui': {
cwdRequirePath: 'node_modules/flosight-ui'
}
}
};
fs.writeFileSync(defaultConfigFile, JSON.stringify(defaultConfig, null, 2));
var config = JSON.parse(fs.readFileSync(defaultConfigFile, 'utf-8'));
return {

View File

@ -1,21 +1,20 @@
'use strict';
var flocore = require('flocore-lib');
var $ = flocore.util.preconditions;
var _ = flocore.deps._;
var bitcore = require('bitcore-lib');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var path = require('path');
var fs = require('fs');
var utils = require('../utils');
/**
* Will return the path and flocore-node configuration
* Will return the path and bitcore-node configuration
* @param {String} cwd - The absolute path to the current working directory
*/
function findConfig(cwd) {
$.checkArgument(_.isString(cwd), 'Argument should be a string');
$.checkArgument(utils.isAbsolutePath(cwd), 'Argument should be an absolute path');
$.checkArgument(path.isAbsolute(cwd), 'Argument should be an absolute path');
var directory = String(cwd);
while (!fs.existsSync(path.resolve(directory, 'flocore-node.json'))) {
while (!fs.existsSync(path.resolve(directory, 'bitcore-node.json'))) {
directory = path.resolve(directory, '../');
if (directory === '/') {
return false;
@ -23,7 +22,7 @@ function findConfig(cwd) {
}
return {
path: directory,
config: require(path.resolve(directory, 'flocore-node.json'))
config: require(path.resolve(directory, 'bitcore-node.json'))
};
}

View File

@ -2,21 +2,21 @@
var async = require('async');
var fs = require('fs');
var npm = require('npm');
var path = require('path');
var spawn = require('child_process').spawn;
var flocore = require('flocore-lib');
var $ = flocore.util.preconditions;
var _ = flocore.deps._;
var utils = require('../utils');
var bitcore = require('bitcore-lib');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* Will remove a service from flocore-node.json
* Will remove a service from bitcore-node.json
* @param {String} configFilePath - The absolute path to the configuration file
* @param {String} service - The name of the module
* @param {Function} done
*/
function removeConfig(configFilePath, service, done) {
$.checkArgument(utils.isAbsolutePath(configFilePath), 'An absolute path is expected');
$.checkArgument(path.isAbsolute(configFilePath), 'An absolute path is expected');
fs.readFile(configFilePath, function(err, data) {
if (err) {
return done(err);
@ -47,7 +47,7 @@ function removeConfig(configFilePath, service, done) {
* @param {Function} done
*/
function uninstallService(configDir, service, done) {
$.checkArgument(utils.isAbsolutePath(configDir), 'An absolute path is expected');
$.checkArgument(path.isAbsolute(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,15 +76,32 @@ function uninstallService(configDir, service, done) {
* @param {Function} done
*/
function removeService(configDir, service, done) {
$.checkArgument(utils.isAbsolutePath(configDir), 'An absolute path is expected');
$.checkArgument(path.isAbsolute(configDir), 'An absolute path is expected');
$.checkArgument(_.isString(service), 'A string is expected for the service argument');
uninstallService(configDir, service, done);
// check if the service is installed
npm.load(function(err) {
if (err) {
return done(err);
}
npm.commands.ls([service], true /*silent*/, function(err, data, lite) {
if (err) {
return done(err);
}
if (lite.dependencies) {
uninstallService(configDir, service, done);
} else {
done();
}
});
});
}
/**
* Will remove the Node.js service and from the flocore-node configuration.
* Will remove the Node.js service and from the bitcore-node configuration.
* @param {String} options.cwd - The current working directory
* @param {String} options.dirname - The flocore-node configuration directory
* @param {String} options.dirname - The bitcore-node configuration directory
* @param {Array} options.services - An array of strings of service names
* @param {Function} done - A callback function called when finished
*/
@ -92,7 +109,7 @@ function remove(options, done) {
$.checkArgument(_.isObject(options));
$.checkArgument(_.isFunction(done));
$.checkArgument(
_.isString(options.path) && utils.isAbsolutePath(options.path),
_.isString(options.path) && path.isAbsolute(options.path),
'An absolute path is expected'
);
$.checkArgument(Array.isArray(options.services));
@ -100,12 +117,12 @@ function remove(options, done) {
var configPath = options.path;
var services = options.services;
var flocoreConfigPath = path.resolve(configPath, 'flocore-node.json');
var bitcoreConfigPath = path.resolve(configPath, 'bitcore-node.json');
var packagePath = path.resolve(configPath, 'package.json');
if (!fs.existsSync(flocoreConfigPath) || !fs.existsSync(packagePath)) {
if (!fs.existsSync(bitcoreConfigPath) || !fs.existsSync(packagePath)) {
return done(
new Error('Directory does not have a flocore-node.json and/or package.json file.')
new Error('Directory does not have a bitcore-node.json and/or package.json file.')
);
}
@ -117,8 +134,8 @@ function remove(options, done) {
if (err) {
return next(err);
}
// remove service to flocore-node.json
removeConfig(flocoreConfigPath, service, next);
// remove service to bitcore-node.json
removeConfig(bitcoreConfigPath, service, next);
});
}, done
);

View File

@ -1,36 +1,222 @@
'use strict';
var path = require('path');
var FlocoreNode = require('../node');
var BitcoreNode = require('../node');
var index = require('../');
var flocore = require('flocore-lib');
var _ = flocore.deps._;
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var log = index.log;
var shuttingDown = false;
var child_process = require('child_process');
var fs = require('fs');
var shuttingDown = false;
log.debug = function() {};
/**
* This function will loop over the configuration for services and require the
* specified modules, and assemble an array in this format:
* [
* {
* name: 'bitcoind',
* config: {},
* module: BitcoinService
* }
* ]
* @param {Function} req - The require function to use
* @param {Array} servicesPath - The local path (for requiring services)
* @param {Object} config
* @param {Array} config.services - An array of strings of service names.
* @returns {Array}
*/
function setupServices(req, servicesPath, config) {
module.paths.push(path.resolve(servicesPath, './node_modules'));
var services = [];
if (config.services) {
for (var i = 0; i < config.services.length; i++) {
var service = {};
service.name = config.services[i];
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.'
);
}
services.push(service);
}
}
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
* @param {Node} node - The Bitcore Node instance
*/
function cleanShutdown(_process, node) {
node.stop(function(err) {
if(err) {
log.error('Failed to stop services: ' + err);
return _process.exit(1);
}
log.info('Halted');
_process.exit(0);
});
}
/**
* 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
* @param {String} options.sigint - The signal given was a SIGINT
* @param {Array} options.exit - The signal given was an uncaughtException
* @param {Object} _process - The Node.js process
* @param {Node} node
* @param {Error} error
*/
function exitHandler(options, _process, node, err) {
if (err) {
log.error('uncaught exception:', err);
if(err.stack) {
log.error(err.stack);
}
node.stop(function(err) {
if(err) {
log.error('Failed to stop services: ' + err);
}
_process.exit(-1);
});
}
if (options.sigint) {
if (!shuttingDown) {
shuttingDown = true;
start.cleanShutdown(_process, node);
}
}
}
/**
* 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
*/
function start(options) {
var fullConfig = _.clone(options.config);
var servicesPath;
if (options.servicesPath) {
servicesPath = options.servicesPath;
servicesPath = options.servicesPath; // services are in a different directory than the config
} else {
servicesPath = options.path;
servicesPath = options.path; // defaults to the same directory
}
fullConfig.path = path.resolve(options.path, './flocore-node.json');
fullConfig.services = start.setupServices(require, servicesPath, options.config);
fullConfig.datadir = path.resolve(options.path, options.config.datadir);
var node = new FlocoreNode(fullConfig);
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('Flocore Node ready');
log.info('Bitcore Node ready');
});
node.on('error', function(err) {
@ -52,190 +238,45 @@ function start(options) {
}
/**
* Checks a service for the expected methods
* @param {Object} service
* 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 checkService(service) {
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.');
}
}
function spawnChildProcess(datadir, _process) {
function lookInRequirePathConfig(req, service) {
if (!service.config.requirePath) {
return;
if (_process.env.__bitcore_node) {
return _process.pid;
}
try {
if (fs.statSync(service.config.requirePath).isDirectory()) {
return req(service.config.requirePath);
}
var serviceFile = service.config.requirePath.replace(/.js$/, '');
return req(serviceFile);
} catch(e) {
log.info('Checked the service\'s requirePath value, ' +
'but could not find the service, checking elsewhere. ' +
'Error caught: ' + e.message);
}
}
var args = [].concat(_process.argv);
args.shift();
var script = args.shift();
var env = _process.env;
var cwd = _process.cwd();
env.__bitcore_node = true;
function lookInCwd(req, service) {
var location = service.config.cwdRequirePath ? service.config.cwdRequirePath : service.name;
try {
return req(process.cwd() + '/' + location);
} catch(e) {
if(e.code !== 'MODULE_NOT_FOUND') {
log.error(e);
}
log.info('Checked the current working directory for service: ' + location);
}
}
var stderr = fs.openSync(datadir + '/bitcore-node.log', 'a+');
var stdout = stderr;
function lookInBuiltInPath(req, service) {
try {
var serviceFile = path.resolve(__dirname, '../services/' + service.name);
return req(serviceFile);
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
log.error(e);
}
log.info('Checked the built-in path: lib/services, for service: ' + service.name);
}
}
var cp_opt = {
stdio: ['ignore', stdout, stderr],
env: env,
cwd: cwd,
detached: true
};
function lookInModuleManifest(req, service) {
try {
var servicePackage = req(service.name + '/package.json');
var serviceModule = service.name;
if (servicePackage.flocoreNode) {
serviceModule = serviceModule + '/' + servicePackage.flocoreNode;
return req(serviceModule);
}
} catch(e) {
log.info('Checked the module\'s package.json for service: ' + service.name);
}
}
function loadModule(req, service) {
var serviceCode;
//first, if we have explicitly set the require path for our service:
serviceCode = lookInRequirePathConfig(req, service);
//second, look in the current working directory (of the controlling terminal, if there is one) for the service code
if(!serviceCode) {
serviceCode = lookInCwd(req, service);
}
//third, try the built-in services
if(!serviceCode) {
serviceCode = lookInBuiltInPath(req, service);
}
//fourth, see if there is directory in our module search path that has a
//package.json file, if so, then see if there is a flocoreNode field, if so
//use this as the path to the service module
if(!serviceCode) {
serviceCode = lookInModuleManifest(req, service);
}
if (!serviceCode) {
throw new Error('Attempted to load the ' + service.name + ' service from: ' +
'the requirePath in the services\' config, then "' +
process.cwd() + '" then from: "' + __dirname + '/../lib/services' + '" finally from: "' +
process.cwd() + '/package.json" - flocoreNode field. All paths failed to find valid nodeJS code.');
}
service.module = serviceCode;
}
/**
* This function will loop over the configuration for services and require the
* specified modules, and assemble an array in this format:
* [
* {
* name: 'florincoind',
* config: {},
* module: FlorincoinService
* }
* ]
* @param {Function} req - The require function to use
* @param {Array} servicesPath - The local path (for requiring services)
* @param {Object} config
* @param {Array} config.services - An array of strings of service names.
* @returns {Array}
*/
function setupServices(req, servicesPath, config) {
module.paths.push(path.resolve(servicesPath, './node_modules'));
var services = [];
if (config.services) {
for (var i = 0; i < config.services.length; i++) {
var service = {};
service.name = config.services[i];
var hasConfig = config.servicesConfig && config.servicesConfig[service.name];
service.config = hasConfig ? config.servicesConfig[service.name] : {};
loadModule(req, service);
checkService(service);
services.push(service);
}
}
return services;
}
function cleanShutdown(_process, node) {
node.stop(function(err) {
if(err) {
log.error('Failed to stop services: ' + err);
return _process.exit(1);
}
log.info('Halted');
process.exit(0);
});
}
function exitHandler(options, _process, node, err) {
// Handle and log errors other than SIGINT shutdown
if (err && err !== "SIGINT") {
log.error('uncaught exception:', err);
if(err.stack) {
log.error(err.stack);
}
if(options.exit)
node.stop(function(err) {
if(err) {
log.error('Failed to stop services: ' + err);
}
_process.exit(-1);
});
}
// Handle SIGINT (Ctrl+C)
if (options.sigint) {
if (!shuttingDown) {
shuttingDown = true;
start.cleanShutdown(_process, node);
}
}
}
function registerExitHandlers(_process, node) {
_process.on('uncaughtException', exitHandler.bind(null, {exit:false}, _process, node));
_process.on('SIGINT', exitHandler.bind(null, {sigint:true}, _process, node));
var child = child_process.spawn(_process.execPath, [script].concat(args), cp_opt);
child.unref();
return _process.exit();
}
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;

View File

@ -1,11 +1,7 @@
'use strict';
/* exported LRU, assert, constants */
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var LRU = require('lru-cache');
var assert = require('assert');
var constants = require('./constants');
var Service = function(options) {
EventEmitter.call(this);
@ -72,7 +68,7 @@ Service.prototype.start = function(done) {
};
/**
* Function to be called when flocore-node is stopped
* Function to be called when bitcore-node is stopped
*/
Service.prototype.stop = function(done) {
setImmediate(done);
@ -82,7 +78,7 @@ Service.prototype.stop = function(done) {
* Setup express routes
* @param {Express} app
*/
Service.prototype.setupRoutes = function() {
Service.prototype.setupRoutes = function(app) {
// Setup express routes here
};
@ -90,4 +86,6 @@ Service.prototype.getRoutePrefix = function() {
return this.name;
};
module.exports = Service;

View File

@ -0,0 +1,56 @@
'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;
// The maximum number of simultaneous requests
exports.MAX_ADDRESSES_LIMIT = 50;
module.exports = exports;

View File

@ -1,167 +1,307 @@
'use strict';
function Encoding(servicePrefix) {
this.servicePrefix = servicePrefix;
this.addressIndex = new Buffer('00', 'hex');
this.utxoIndex = new Buffer('01', 'hex');
this.addressCache = new Buffer('fe', 'hex');
}
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;
Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input, timestamp) {
var buffers = [this.servicePrefix, this.addressIndex];
var addressSizeBuffer = new Buffer(1);
addressSizeBuffer.writeUInt8(address.length);
var addressBuffer = new Buffer(address, 'utf8');
buffers.push(addressSizeBuffer);
buffers.push(addressBuffer);
var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height || 0);
buffers.push(heightBuffer);
var txidBuffer = new Buffer(txid || Array(65).join('0'), 'hex');
buffers.push(txidBuffer);
var indexBuffer = new Buffer(4);
indexBuffer.writeUInt32BE(index || 0);
buffers.push(indexBuffer);
// this is whether the address appears in an input (1) or output (0)
var inputBuffer = new Buffer(1);
inputBuffer.writeUInt8(input || 0);
buffers.push(inputBuffer);
var timestampBuffer = new Buffer(4);
timestampBuffer.writeUInt32BE(timestamp || 0);
buffers.push(timestampBuffer);
return Buffer.concat(buffers);
};
Encoding.prototype.decodeAddressIndexKey = function(buffer) {
var addressSize = buffer.readUInt8(3);
var address = buffer.slice(4, addressSize + 4).toString('utf8');
var height = buffer.readUInt32BE(addressSize + 4);
var txid = buffer.slice(addressSize + 8, addressSize + 40).toString('hex');
var index = buffer.readUInt32BE(addressSize + 40);
var input = buffer.readUInt8(addressSize + 44);
var timestamp = buffer.readUInt32BE(addressSize + 45);
return {
address: address,
height: height,
txid: txid,
index: index,
input: input,
timestamp: timestamp
};
};
Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) {
var buffers = [this.servicePrefix, this.utxoIndex];
var addressSizeBuffer = new Buffer(1);
addressSizeBuffer.writeUInt8(address.length);
var addressBuffer = new Buffer(address, 'utf8');
buffers.push(addressSizeBuffer);
buffers.push(addressBuffer);
var txidBuffer = new Buffer(txid || new Array(65).join('0'), 'hex');
buffers.push(txidBuffer);
var exports = {};
exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) {
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex || 0);
buffers.push(outputIndexBuffer);
return Buffer.concat(buffers);
outputIndexBuffer.writeUInt32BE(outputIndex);
var key = Buffer.concat([
txidBuffer,
outputIndexBuffer
]);
return key.toString('binary');
};
Encoding.prototype.decodeUtxoIndexKey = function(buffer) {
var addressSize = buffer.readUInt8(3);
var address = buffer.slice(4, addressSize + 4).toString('utf8');
var txid = buffer.slice(addressSize + 4, addressSize + 36).toString('hex');
var outputIndex = buffer.readUInt32BE(addressSize + 36);
exports.encodeMempoolAddressIndexKey = function(hashBuffer, hashTypeBuffer) {
var key = Buffer.concat([
hashBuffer,
hashTypeBuffer,
]);
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 {
address: address,
prefix: prefix,
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
height: height,
txid: txid,
outputIndex: outputIndex
};
};
Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, timestamp, scriptBuffer) {
var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height);
exports.encodeOutputValue = function(satoshis, scriptBuffer) {
var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis);
var timestampBuffer = new Buffer(4);
timestampBuffer.writeUInt32BE(timestamp || 0);
return Buffer.concat([heightBuffer, satoshisBuffer, timestampBuffer, scriptBuffer]);
return Buffer.concat([satoshisBuffer, scriptBuffer]);
};
Encoding.prototype.decodeUtxoIndexValue = function(buffer) {
var height = buffer.readUInt32BE();
var satoshis = buffer.readDoubleBE(4);
var timestamp = buffer.readUInt32BE(12);
var scriptBuffer = buffer.slice(16);
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 {
height: height,
satoshis: satoshis,
timestamp: timestamp,
script: scriptBuffer
scriptBuffer: scriptBuffer
};
};
Encoding.prototype.encodeAddressCacheKey = function(address) {
return Buffer.concat([this.servicePrefix, this.addressCache, new Buffer(address, 'utf8')]);
}
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
};
};
Encoding.prototype.decodeAddressCacheKey = function(buffer) {
return buffer.slice(3).toString('utf8');
}
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
]);
};
Encoding.prototype.encodeAddressCacheValue = function(lastTx, lastBlock, balance, received, sent, txApperances) {
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];
var buffer = [];
return {
hashBuffer: addrObj.hashBuffer,
hashTypeBuffer: hashTypeBuffer,
hashTypeReadable: addrObj.type
};
};
var balanceBuffer = new Buffer(8);
balanceBuffer.writeBigUInt64BE(BigInt(balance));
buffer.push(balanceBuffer);
var receivedBuffer = new Buffer(8);
receivedBuffer.writeBigUInt64BE(BigInt(received));
buffer.push(receivedBuffer);
var sentBuffer = new Buffer(8);
sentBuffer.writeBigUInt64BE(BigInt(sent));
buffer.push(sentBuffer);
var txApperancesBuffer = new Buffer(4);
txApperancesBuffer.writeUInt32BE(txApperances);
buffer.push(txApperancesBuffer);
var txidBuffer = new Buffer(lastTx, 'hex');
buffer.push(txidBuffer);
var blkBuffer = new Buffer(lastBlock, 'hex');
buffer.push(blkBuffer);
return Buffer.concat(buffer);
}
Encoding.prototype.decodeAddressCacheValue = function(buffer) {
var balance = parseInt(buffer.readBigUInt64BE(0));
var received = parseInt(buffer.readBigUInt64BE(8));
var sent = parseInt(buffer.readBigUInt64BE(16));
var txApperances = buffer.readUInt32BE(24);
var lastTx = buffer.slice(28, 60).toString('hex'); //28 + 32 (tx hash buffer length) = 60
var lastBlock = buffer.slice(60).toString('hex');
return { lastTx, lastBlock, balance, received, sent, txApperances };
}
module.exports = Encoding;
/**
* 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;

View File

@ -0,0 +1,266 @@
'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.maxAddressesLimit = args.options.maxAddressesLimit || constants.MAX_ADDRESSES_LIMIT;
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'));
}
var opts = _.clone(this.options);
opts.noBalance = true;
if (this.addresses.length === 1) {
var address = this.addresses[0];
self.node.services.address.getAddressSummary(address, opts, function(err, summary) {
if (err) {
return callback(err);
}
return self._paginateWithDetails.call(self, summary.txids, callback);
});
} else {
opts.fullTxList = true;
async.mapLimit(
self.addresses,
self.maxAddressesLimit,
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

View File

@ -0,0 +1,40 @@
'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;

View File

@ -0,0 +1,42 @@
'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;

358
lib/services/bitcoind.js Normal file
View File

@ -0,0 +1,358 @@
'use strict';
var fs = require('fs');
var util = require('util');
var bindings = require('bindings')('bitcoind.node');
var mkdirp = require('mkdirp');
var bitcore = require('bitcore-lib');
var $ = bitcore.util.preconditions;
var index = require('../');
var log = index.log;
var Service = require('../service');
/**
* Provides an interface to native bindings to [Bitcoin Core](https://github.com/bitcoin/bitcoin)
* compiled as a static library. The C++ bindings can be found at `src/libbitcoind.cc`
* @param {Object} options
* @param {Node} options.node - A reference to the node
*/
function Bitcoin(options) {
if (!(this instanceof Bitcoin)) {
return new Bitcoin(options);
}
this._reindex = false;
this._reindexWait = 1000;
Service.call(this, options);
$.checkState(this.node.datadir, 'Node is missing datadir property');
}
util.inherits(Bitcoin, Service);
Bitcoin.dependencies = [];
Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n';
Bitcoin.prototype._loadConfiguration = function() {
/* jshint maxstatements: 25 */
$.checkArgument(this.node.datadir, 'Please specify "datadir" in configuration options');
var configPath = this.node.datadir + '/bitcoin.conf';
this.configuration = {};
if (!fs.existsSync(this.node.datadir)) {
mkdirp.sync(this.node.datadir);
}
if (!fs.existsSync(configPath)) {
var defaultConfig = Bitcoin.DEFAULT_CONFIG;
if(this.node.https && this.node.httpsOptions) {
defaultConfig += 'rpcssl=1\n';
defaultConfig += 'rpcsslprivatekeyfile=' + this.node.httpsOptions.key + '\n';
defaultConfig += 'rpcsslcertificatechainfile=' + this.node.httpsOptions.cert + '\n';
}
fs.writeFileSync(configPath, defaultConfig);
}
var file = fs.readFileSync(configPath);
var unparsed = file.toString().split('\n');
for(var i = 0; i < unparsed.length; i++) {
var line = unparsed[i];
if (!line.match(/^\#/) && line.match(/\=/)) {
var option = line.split('=');
var value;
if (!Number.isNaN(Number(option[1]))) {
value = Number(option[1]);
} else {
value = option[1];
}
this.configuration[option[0]] = value;
}
}
$.checkState(
this.configuration.txindex && this.configuration.txindex === 1,
'Txindex option is required in order to use most of the features of bitcore-node. ' +
'Please add "txindex=1" to your configuration and reindex an existing database if ' +
'necessary with reindex=1'
);
if (this.configuration.reindex && this.configuration.reindex === 1) {
log.warn('Reindex option is currently enabled. This means that bitcoind is undergoing a reindex. ' +
'The reindex flag will start the index from beginning every time the node is started, so it ' +
'should be removed after the reindex has been initiated. Once the reindex is complete, the rest ' +
'of bitcore-node services will start.');
this._reindex = true;
}
};
Bitcoin.prototype._onTipUpdate = function(result) {
if (result) {
// Emit and event that the tip was updated
this.height = result;
this.emit('tip', result);
// TODO stopping status
if(!this.node.stopping) {
var percentage = this.syncPercentage();
log.info('Bitcoin Height:', this.height, 'Percentage:', percentage);
}
// Recursively wait until the next update
bindings.onTipUpdate(this._onTipUpdate.bind(this));
}
};
Bitcoin.prototype._registerEventHandlers = function() {
var self = this;
// Set the height and emit a new tip
bindings.onTipUpdate(self._onTipUpdate.bind(this));
// Register callback function to handle transactions entering the mempool
bindings.startTxMon(function(txs) {
for(var i = 0; i < txs.length; i++) {
self.emit('tx', txs[i]);
}
});
// Register callback function to handle transactions leaving the mempool
bindings.startTxMonLeave(function(txs) {
for(var i = 0; i < txs.length; i++) {
self.emit('txleave', txs[i]);
}
});
};
Bitcoin.prototype._onReady = function(result, callback) {
var self = this;
self._registerEventHandlers();
var info = self.getInfo();
self.height = info.blocks;
self.getBlock(0, function(err, block) {
if (err) {
return callback(err);
}
self.genesisBuffer = block;
self.emit('ready', result);
log.info('Bitcoin Daemon Ready');
callback();
});
};
/**
* Called by Node to start the service
* @param {Function} callback
*/
Bitcoin.prototype.start = function(callback) {
var self = this;
this._loadConfiguration();
bindings.start({
datadir: this.node.datadir,
network: this.node.network.name
}, function(err) {
if(err) {
return callback(err);
}
// Wait until the block chain is ready
bindings.onBlocksReady(function(err, result) {
if (err) {
return callback(err);
}
if (self._reindex) {
var interval = setInterval(function() {
var percentSynced = bindings.syncPercentage();
log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced);
if (percentSynced >= 100) {
self._reindex = false;
self._onReady(result, callback);
clearInterval(interval);
}
}, self._reindexWait);
}
else {
self._onReady(result, callback);
}
});
});
};
/**
* Helper to determine the state of the database.
* @returns {Boolean} If the database is fully synced
*/
Bitcoin.prototype.isSynced = function() {
return bindings.isSynced();
};
/**
* Helper to determine the progress of the database.
* @returns {Number} An estimated percentage of the syncronization status
*/
Bitcoin.prototype.syncPercentage = function() {
return bindings.syncPercentage();
};
/**
* Will retrieve a block as a Node.js Buffer from disk.
* @param {String|Number} block - A block hash or block height number
*/
Bitcoin.prototype.getBlock = function(block, callback) {
return bindings.getBlock(block, callback);
};
/**
* Will return the spent status of an output (not including the mempool)
* @param {String} txid - The transaction hash
* @param {Number} outputIndex - The output index in the transaction
* @returns {Boolean} If the output has been spent
*/
Bitcoin.prototype.isSpent = function(txid, outputIndex) {
return bindings.isSpent(txid, outputIndex);
};
/**
* Will return the block index information, the output will have the format:
* {
* prevHash: '7194fcf33f58c96720f88f21ab28c34ebc5638c5f88d7838517deb27313b59de',
* hash: '7c5caf0af1bf16e3467b275a3b408bc1d251bff3c25be20cb727c47b66a7b216',
* chainWork: '0000000000000000000000000000000000000000000000000000000000000016',
* height: 10
* }
* @param {String|Number} block - A block hash or block height
* @returns {Object}
*/
Bitcoin.prototype.getBlockIndex = function(block) {
return bindings.getBlockIndex(block);
};
/**
* Will return if the block is a part of the main chain.
* @param {String} blockHash
* @returns {Boolean}
*/
Bitcoin.prototype.isMainChain = function(blockHash) {
return bindings.isMainChain(blockHash);
};
/**
* Will estimate the fee per kilobyte.
* @param {Number} blocks - The number of blocks for the transaction to be confirmed.
* @returns {Number}
*/
Bitcoin.prototype.estimateFee = function(blocks) {
return bindings.estimateFee(blocks);
};
/**
* Will add a transaction to the mempool and relay to connected peers, the function
* will throw an error if there were validation problems.
* @param {String} transaction - The hex string of the transaction
* @param {Boolean} allowAbsurdFees - Enable large fees
*/
Bitcoin.prototype.sendTransaction = function(transaction, allowAbsurdFees) {
return bindings.sendTransaction(transaction, allowAbsurdFees);
};
/**
* Will get a transaction as a Node.js Buffer from disk and the mempool.
* @param {String} txid - The transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
return bindings.getTransaction(txid, queryMempool, callback);
};
/**
* Will get a transaction with additional information about the block, in the format:
* {
* blockHash: '2725743288feae6bdaa976590af7cb12d7b535b5a242787de6d2789c73682ed1',
* height: 48,
* timestamp: 1442951110, // in seconds
* buffer: <Buffer...> // transaction buffer
* }
* @param {String} txid - The transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback);
};
/**
* Will return the entire mempool as an Array of transaction Buffers.
* @returns {Array}
*/
Bitcoin.prototype.getMempoolTransactions = function() {
return bindings.getMempoolTransactions();
};
/**
* Will add a transaction to the mempool without any validation. This is used
* exclusively for testing purposes.
* @param {String} transaction - The hex string for the transaction
*/
Bitcoin.prototype.addMempoolUncheckedTransaction = function(transaction) {
return bindings.addMempoolUncheckedTransaction(transaction);
};
/**
* Will get the best block hash for the chain.
* @returns {String}
*/
Bitcoin.prototype.getBestBlockHash = function() {
return bindings.getBestBlockHash();
};
/**
* Will get the next block hash for a block hash.
* @param {String} hash - The starting block hash
* @returns {String}
*/
Bitcoin.prototype.getNextBlockHash = function(hash) {
return bindings.getNextBlockHash(hash);
};
/**
* This will return information about the database in the format:
* {
* version: 110000,
* protocolversion: 70002,
* blocks: 151,
* timeoffset: 0,
* connections: 0,
* difficulty: 4.6565423739069247e-10,
* testnet: false,
* relayfee: 1000,
* errors: ''
* }
*/
Bitcoin.prototype.getInfo = function() {
return bindings.getInfo();
};
/**
* Called by Node to stop the service.
* @param {Function} callback
*/
Bitcoin.prototype.stop = function(callback) {
return bindings.stop(function(err, status) {
if (err) {
return callback(err);
} else {
log.info(status);
return callback();
}
});
};
module.exports = Bitcoin;

View File

@ -1,375 +0,0 @@
'use strict';
var Readable = require('stream').Readable;
var Writable = require('stream').Writable;
var Transform = require('stream').Transform;
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var async = require('async');
var index = require('../../index');
var log = index.log;
var _ = require('lodash');
function BlockStream(highWaterMark, sync) {
Readable.call(this, {objectMode: true, highWaterMark: highWaterMark});
this.sync = sync;
this.block = this.sync.block;
this.dbTip = this.block.tip;
this.lastReadHeight = this._getTipHeight();
this.lastEmittedHash = this.dbTip.hash;
this.queue = [];
this.processing = false;
var self = this;
self.block.on('reorg', function() {
self.push(null);
});
}
inherits(BlockStream, Readable);
function ProcessConcurrent(highWaterMark, sync) {
Transform.call(this, {objectMode: true, highWaterMark: highWaterMark});
this.block = sync.block;
this.db = sync.db;
this.operations = [];
this.lastBlock = 0;
this.blockCount = 0;
}
inherits(ProcessConcurrent, Transform);
function ProcessSerial(highWaterMark, sync) {
Writable.call(this, {objectMode: true, highWaterMark: highWaterMark});
this.block = sync.block;
this.db = sync.db;
this.block = sync.block;
this.tip = sync.block.tip;
this.processBlockStartTime = [];
this._lastReportedTime = Date.now();
}
inherits(ProcessSerial, Writable);
function ProcessBoth(highWaterMark, sync) {
Writable.call(this, {objectMode: true, highWaterMark: highWaterMark});
this.db = sync.db;
}
inherits(ProcessBoth, Writable);
function WriteStream(highWaterMark, sync) {
Writable.call(this, {objectMode: true, highWaterMark: highWaterMark});
this.db = sync.db;
this.writeTime = 0;
this.lastConcurrentOutputHeight = 0;
this.block = sync.block;
}
inherits(WriteStream, Writable);
function BlockHandler(node, block) {
this.node = node;
this.db = this.node.services.db;
this.block = block;
this.syncing = false;
this.paused = false;
this.blockQueue = [];
this.highWaterMark = 10;
}
inherits(BlockHandler, EventEmitter);
BlockHandler.prototype.sync = function(block) {
var self = this;
if (block) {
self.blockQueue.push(block);
}
if(this.syncing || this.paused) {
log.debug('Sync lock held, not able to sync at the moment');
return;
}
self.syncing = true;
self._setupStreams();
};
BlockHandler.prototype._setupStreams = function() {
var self = this;
var blockStream = new BlockStream(self.highWaterMark, self);
var processConcurrent = new ProcessConcurrent(self.highWaterMark, self);
var writeStream = new WriteStream(self.highWaterMark, self);
var processSerial = new ProcessSerial(self.highWaterMark, self);
self._handleErrors(blockStream);
self._handleErrors(processConcurrent);
self._handleErrors(processSerial);
self._handleErrors(writeStream);
blockStream
.pipe(processConcurrent)
.pipe(writeStream);
blockStream
.pipe(processSerial);
processSerial.on('finish', self._onFinish.bind(self));
};
BlockHandler.prototype._onFinish = function() {
var self = this;
self.syncing = false;
self.emit('synced');
};
BlockHandler.prototype._handleErrors = function(stream) {
var self = this;
stream.on('error', function(err) {
self.syncing = false;
self.emit('error', err);
});
};
BlockStream.prototype._read = function() {
if (this.lastEmittedHash === this.block.getNetworkTipHash()) {
return this.push(null);
}
if (this.sync.blockQueue.length === 0) {
this.queue.push(++this.lastReadHeight);
} else {
var block = this.sync.blockQueue.shift();
if (block) {
this.lastReadHeight = block.__height;
this.queue.push(block);
}
}
this._process();
};
BlockStream.prototype._process = function() {
var self = this;
if(self.processing) {
return;
}
this.processing = true;
async.whilst(
function() {
return self.queue.length;
}, function(next) {
var blockArgs = self.queue.slice(0, Math.min(5, self.queue.length));
self.queue = self.queue.slice(blockArgs.length);
if (_.isNumber(blockArgs[0])) {
self.block.getBlocks(blockArgs, function(err, blocks) {
if(err) {
return next(err);
}
self._pushBlocks(blocks);
next();
});
} else {
self._pushBlocks(blockArgs);
next();
}
}, function(err) {
if(err) {
return self.emit('error', err);
}
self.processing = false;
}
);
};
BlockStream.prototype._pushBlocks = function(blocks) {
var self = this;
for(var i = 0; i < blocks.length; i++) {
self.lastEmittedHash = blocks[i].hash;
self.push(blocks[i]);
}
};
BlockStream.prototype._getTipHeight = function() {
if (this.dbTip.__height === 0) {
return -1;
}
return this.dbTip.__height;
};
ProcessSerial.prototype._reportStatus = function() {
if ((Date.now() - this._lastReportedTime) > 1000) {
this._lastReportedTime = Date.now();
log.info('Sync: current height is: ' + this.block.tip.__height);
}
};
ProcessSerial.prototype._write = function(block, enc, callback) {
var self = this;
function check() {
return self.block.concurrentTip.__height >= block.__height;
}
if(check()) {
return self._process(block, callback);
}
self.block.once('concurrentaddblock', function() {
if(!check()) {
var err = 'Concurrent block ' + self.block.concurrentTip.__height + ' is less than ' + block.__height;
return self.emit('error', err);
}
self._process(block, callback);
});
};
ProcessSerial.prototype._process = function(block, callback) {
var self = this;
self.block.getBlockOperations(block, true, 'serial', function(err, operations) {
if(err) {
return callback(err);
}
operations.push(self.block.getTipOperation(block, true));
var obj = {
tip: block,
operations: operations
};
self.tip = block;
self.db.batch(obj.operations, function(err) {
if(err) {
return callback(err);
}
self.block.tip = block;
self._reportStatus();
self.block.emit('addblock');
callback();
});
});
};
ProcessConcurrent.prototype._transform = function(block, enc, callback) {
var self = this;
this.lastBlock = block;
self.block.getBlockOperations(block, true, 'concurrent', function(err, operations) {
if(err) {
return callback(err);
}
self.blockCount++;
self.operations = self.operations.concat(operations);
if(self.blockCount >= 1) {
self.operations.push(self.block.getTipOperation(block, true, 'concurrentTip'));
var obj = {
concurrentTip: block,
operations: self.operations
};
self.operations = [];
self.blockCount = 0;
return callback(null, obj);
}
callback();
});
};
ProcessConcurrent.prototype._flush = function(callback) {
if(this.operations.length) {
this.operations.push(this.block.getTipOperation(this.lastBlock, true));
this.operations = [];
return callback(null, this.operations);
}
};
WriteStream.prototype._write = function(obj, enc, callback) {
var self = this;
if (self.db.node.stopping) {
return setImmediate(callback);
}
self.db.batch(obj.operations, function(err) {
if(err) {
return callback(err);
}
self.block.concurrentTip = obj.concurrentTip;
self.block.emit('concurrentaddblock');
self.lastConcurrentOutputHeight = self.block.concurrentTip.__height;
callback();
});
};
ProcessBoth.prototype._write = function(block, encoding, callback) {
var self = this;
async.parallel([function(next) {
self.block.getBlockOperations(block, true, 'concurrent', function(err, operations) {
if(err) {
return callback(err);
}
operations.push(self.block.getTipOperation(block, true, 'concurrentTip'));
next(null, operations);
});
}, function(next) {
self.block.getBlockOperations(block, true, 'serial', function(err, operations) {
if(err) {
return callback(err);
}
operations.push(self.block.getTipOperation(block, true));
next(null, operations);
});
}], function(err, results) {
if(err) {
return callback(err);
}
var operations = results[0].concat(results[1]);
self.db.batch(operations, function(err) {
if(err) {
return callback(err);
}
self.block.tip = block;
self.block.concurrentTip = block;
callback();
});
});
};
module.exports = BlockHandler;

View File

@ -1,28 +0,0 @@
'use strict';
var Block = require('fcoin').Block;
// stores -- block header as key, block itself as value (optionally)
function Encoding(servicePrefix) {
this._servicePrefix = servicePrefix;
}
// ---- hash --> rawblock
Encoding.prototype.encodeBlockKey = function(hash) {
return Buffer.concat([ this._servicePrefix, new Buffer(hash, 'hex') ]);
};
Encoding.prototype.decodeBlockKey = function(buffer) {
return buffer.slice(2).toString('hex');
};
Encoding.prototype.encodeBlockValue = function(block) {
return block.toRaw();
};
Encoding.prototype.decodeBlockValue = function(buffer) {
return Block.fromRaw(buffer);
};
module.exports = Encoding;

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