bitcoind: bitcoind service using rpc and zmq with address index

This commit is contained in:
Braydon Fuller 2016-03-16 19:35:24 -04:00
parent 07c317df80
commit 7e70bbfa7d
31 changed files with 577 additions and 6179 deletions

View File

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

View File

@ -1,75 +0,0 @@
'use strict';
var benchmark = require('benchmark');
var async = require('async');
var sinon = require('sinon');
var bitcore = require('bitcore-lib');
var Block = bitcore.Block;
var AddressService = require('../lib/services/address');
var maxTime = 20;
var blockData1 = require('./data/block-367238.json');
var blockData2 = require('./data/block-367239.json');
var blockData3 = require('./data/block-367240.json');
console.log('Address Service Block Handler');
console.log('-----------------------------');
async.series([
function(next) {
var c = 0;
var blocks = [
Block.fromBuffer(new Buffer(blockData1, 'hex')),
Block.fromBuffer(new Buffer(blockData2, 'hex')),
Block.fromBuffer(new Buffer(blockData3, 'hex'))
];
var blocksLength = 3;
var node = {
services: {
bitcoind : {
on: sinon.stub()
}
}
};
var addressService = new AddressService({node: node});
function blockHandler(deffered) {
if (c >= blocksLength) {
c = 0;
}
var block = blocks[c];
addressService.blockHandler(block, true, function(err, operations) {
if (err) {
throw err;
}
deffered.resolve();
});
c++;
}
var suite = new benchmark.Suite();
suite.add('blockHandler', blockHandler, {
defer: true,
maxTime: maxTime
});
suite
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
console.log('----------------------------------------------------------------------');
next();
})
.run();
}
], function(err) {
if (err) {
throw err;
}
console.log('Finished');
process.exit();
});

187
bin/build
View File

@ -1,187 +0,0 @@
#!/bin/bash
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
options=`cat ${root_dir}/bin/config_options.sh`
host=$(${root_dir}/bin/variables.sh host) || exit -1
depends_dir=$($root_dir/bin/variables.sh depends_dir)
btc_dir="${root_dir}/libbitcoind"
sys=$($root_dir/bin/variables.sh sys)
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 src depends/${host} -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
config_host="--host ${host}"
full_options="${options} ${config_host} ${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
echo "running::: 'node-gyp ${sys} ${debug} rebuild'"
node-gyp ${sys} ${debug} rebuild

View File

@ -1,9 +0,0 @@
#!/bin/bash
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
cd "${root_dir}"
pushd "${root_dir}"/libbitcoind
make clean
popd
node-gyp clean
rm -fr cache/*

View File

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

View File

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

View File

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

View File

@ -1,19 +0,0 @@
'use strict';
var execSync = require('child_process').execSync;
function getTarballName() {
var packageRoot = __dirname + '/..';
var version = require(packageRoot + '/package.json').version;
var platform = process.platform;
var arch = execSync(packageRoot + '/bin/variables.sh arch').toString();
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;

View File

@ -1,36 +0,0 @@
#!/bin/bash
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
cd "${root_dir}"
tarball_name=`node bin/get-tarball-name.js`
bucket_name="bitcore-node"
binary_url="https://${bucket_name}.s3.amazonaws.com/${tarball_name}"
echo "Downloading binary: ${binary_url}"
is_curl=true
if hash curl 2>/dev/null; then
curl --fail -I $binary_url >/dev/null 2>&1
else
is_curl=false
wget --server-response --spider $binary_url >/dev/null 2>&1
fi
if test $? -eq 0; then
if [ "${is_curl}" = true ]; then
curl $binary_url > $tarball_name
else
wget $binary_url
fi
if test -e "${tarball_name}"; then
echo "Unpacking binary distribution"
tar -xvzf $tarball_name
if test $? -eq 0; then
exit 0
fi
fi
fi
echo "Prebuild binary could not be downloaded, building from source..."
./bin/build

View File

@ -1,58 +0,0 @@
'use strict';
var exec = require('child_process').exec;
var bindings = require('bindings');
var index = require('../lib');
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);
}
});
});

View File

@ -1,36 +0,0 @@
#!/bin/bash
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
#root_dir="$(readlink -f "$(dirname "$0")")/.."
cd "$root_dir"
dir=$(test -n "$1" && echo "$1" || echo "${HOME}/bitcoin")
patch_file="$(pwd)/etc/bitcoin.patch"
cd "$dir" || exit 1
if ! test -d .git; then
echo 'Please point this script to an upstream bitcoin git repo.'
exit 1
fi
if test $? -ne 0; then
echo 'Unable to checkout necessary commit.'
echo 'Please pull the latest HEAD from the upstream bitcoin repo.'
exit 1
fi
git checkout -b "libbitcoind-$(date '+%Y.%m.%d')" || exit 1
patch -p1 < "$patch_file" || exit 1
git add --all || exit 1
[ -n "$( git config user.name )" ] \
|| git config user.name 'Bitcore Build'
[ -n "$( git config user.email )" ] \
|| git config user.email "$( id -n -u )@$( hostname -f )"
git commit -a -m 'allow compiling of libbitcoind.so.' || exit 1
echo 'Patch completed successfully.'
exit 0

View File

@ -1,61 +0,0 @@
#!/usr/bin/env node
'use strict';
var index = require('..');
var log = index.log;
process.title = 'libbitcoind';
/**
* daemon
*/
var daemon = require('../').services.Bitcoin({
node: {
datadir: process.env.BITCORENODE_DIR || process.env.HOME + '/.bitcoin',
network: {
name: process.env.BITCORENODE_NETWORK || 'livenet'
}
}
});
daemon.start(function() {
log.info('ready');
});
daemon.on('error', function(err) {
log.info('error="%s"', err.message);
});
daemon.on('open', function(status) {
log.info('status="%s"', status);
});
function exitHandler(options, err) {
log.info('Stopping daemon');
if (err) {
log.error('uncaught exception:', err);
if(err.stack) {
console.log(err.stack);
}
process.exit(-1);
}
if (options.sigint) {
daemon.stop(function(err) {
if(err) {
log.error('Failed to stop services: ' + err);
return process.exit(1);
}
log.info('Halted');
process.exit(0);
});
}
}
//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, {sigint:true}));

View File

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

View File

@ -1,52 +0,0 @@
'use strict';
var fs = require('fs');
var AWS = require('aws-sdk');
var bindings = require('bindings');
var index = require('../lib');
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);
}
});
});

View File

@ -1,182 +0,0 @@
#!/bin/bash
exec 2> /dev/null
root_dir="$(cd "$(dirname $0)" && pwd)/.."
if [ "${root_dir}" == "" ]; then
root_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.."
fi
bitcoin_dir="${root_dir}"/libbitcoind
cache_dir="${root_dir}"/cache
get_host_and_platform () {
platform=`uname -a | awk '{print tolower($1)}'`
arch=`uname -m`
if [ "${arch:0:3}" == "arm" ]; then
platform="linux-gnueabihf"
arch="arm"
fi
if [ -n "${CXX}" ] && [ -n "${CC}" ]; then
cc_target=$("${CC}" -v 2>&1 | awk '/Target:/ {print $2}')
cxx_target=$("${CXX}" -v 2>&1 | awk '/Target:/ {print $2}')
IFS='-' read -ra SYS <<< "${cc_target}"
if [ "${SYS[0]}" != "${arch}" ]; then
if [ -n "${SYS[1]}" ] && [ -n "${SYS[2]}" ] && hash "${CXX}" && hash "${CC}" && [ -n "${cc_target}" ] && [ -n "${cxx_target}" ]; then
#try and see if we've got a cross compiler, if not then auto detect
arch="${SYS[0]}"
platform="${SYS[1]}"-"${SYS[2]}"
else
error_message="You've specified a cross compiler, but we could not compute the host-platform-triplet for cross compilation. Please set CC and CXX environment variables with host-platform-triplet-*. Also ensure the cross compiler exists on your system and is available on your path. Example: CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++"
return_error_message
fi
fi
fi
}
return_error_message () {
echo "${error_message}"
exit -1
}
get_host_and_platform
host="${arch}"-"${platform}"
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'arch'; then
echo -n "${arch}"
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'sys'; then
if [ -n "${SYS}" ]; then
echo -n "--arch=${SYS[0]}"
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

View File

@ -1,59 +0,0 @@
{
"targets": [
{
"target_name": "libbitcoind",
"include_dirs" : [
"<!(node -e \"require('nan')\")",
"<!(./bin/variables.sh cache_dir)/src",
"<!(./bin/variables.sh cache_dir)/depends/<!(./bin/variables.sh host)/include",
"<!(./bin/variables.sh cache_dir)/src/leveldb/include"
],
"sources": [
"./src/libbitcoind.cc",
],
"conditions": [
[
"OS==\"mac\"",
{
"xcode_settings": {
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
"GCC_ENABLE_CPP_RTTI": "YES",
"MACOSX_DEPLOYMENT_TARGET": "10.9",
'OTHER_CFLAGS': [
"-fexceptions",
"-frtti",
"-fpermissive",
"<!(./bin/variables.sh wallet_enabled)",
]
}
}
]
],
"cflags_cc": [
"-fexceptions",
"-frtti",
"-fpermissive",
"<!(./bin/variables.sh wallet_enabled)",
],
"link_settings": {
"libraries": [
"<!(./bin/variables.sh bitcoind)",
"<!(./bin/variables.sh filesystem)",
"<!(./bin/variables.sh thread)",
"<!(./bin/variables.sh program_options)",
"<!(./bin/variables.sh system)",
"<!(./bin/variables.sh chrono)",
"<!(./bin/variables.sh libsecp256k1)",
"<!(./bin/variables.sh leveldb)",
"<!(./bin/variables.sh memenv)",
"<!(./bin/variables.sh bdb)",
"<!(./bin/variables.sh anl)",
"<!(./bin/variables.sh ssl)"
],
"ldflags": [
"<!(./bin/variables.sh load_archive)"
]
}
}
]
}

3
cache/.gitignore vendored
View File

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

View File

@ -1,387 +0,0 @@
diff --git a/config_me.sh b/config_me.sh
new file mode 100644
index 0000000..19e9a1b
--- /dev/null
+++ b/config_me.sh
@@ -0,0 +1 @@
+./configure --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --enable-debug --disable-wallet --without-utils
diff --git a/configure.ac b/configure.ac
index 5debd21..3bfc59e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -119,6 +119,12 @@ AC_ARG_ENABLE([reduce-exports],
[use_reduce_exports=$enableval],
[use_reduce_exports=no])
+AC_ARG_ENABLE([daemonlib],
+ [AS_HELP_STRING([--enable-daemonlib],
+ [compile all of bitcoind as a library (default is no)])],
+ [use_daemonlib=$enableval],
+ [use_daemonlib=no])
+
AC_ARG_ENABLE([ccache],
[AS_HELP_STRING([--enable-ccache],
[use ccache for building (default is yes if ccache is found)])],
@@ -402,6 +408,9 @@ fi
if test x$use_hardening != xno; then
AX_CHECK_COMPILE_FLAG([-Wstack-protector],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -Wstack-protector"])
AX_CHECK_COMPILE_FLAG([-fstack-protector-all],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-protector-all"])
+ if test x$use_daemonlib = xno; then
+ AX_CHECK_COMPILE_FLAG([-fPIE],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fPIE"])
+ fi
AX_CHECK_PREPROC_FLAG([-D_FORTIFY_SOURCE=2],[
AX_CHECK_PREPROC_FLAG([-U_FORTIFY_SOURCE],[
@@ -415,7 +424,7 @@ if test x$use_hardening != xno; then
AX_CHECK_LINK_FLAG([[-Wl,-z,relro]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,relro"])
AX_CHECK_LINK_FLAG([[-Wl,-z,now]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,now"])
- if test x$TARGET_OS != xwindows; then
+ if test x$TARGET_OS != xwindows -a x$use_daemonlib = xno; then
# All windows code is PIC, forcing it on just adds useless compile warnings
AX_CHECK_COMPILE_FLAG([-fPIE],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fPIE"])
AX_CHECK_LINK_FLAG([[-pie]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -pie"])
@@ -433,6 +442,17 @@ if test x$use_hardening != xno; then
OBJCXXFLAGS="$CXXFLAGS"
fi
+AC_DEFINE([ENABLE_DAEMONLIB],[0],[Enable daemonlib.])
+AM_CONDITIONAL([ENABLE_DAEMONLIB],[false])
+if test x$use_daemonlib != xno; then
+ AX_CHECK_COMPILE_FLAG([-fPIC],[DAEMONLIB_CXXFLAGS="$DAEMONLIB_CXXFLAGS -fPIC"])
+ AC_DEFINE([ENABLE_DAEMONLIB],[1],[Enable daemonlib.])
+ AM_CONDITIONAL([ENABLE_DAEMONLIB],[true])
+ CXXFLAGS="$CXXFLAGS $DAEMONLIB_CXXFLAGS"
+ CPPFLAGS="$CPPFLAGS $DAEMONLIB_CPPFLAGS"
+ OBJCXXFLAGS="$CXXFLAGS"
+fi
+
dnl this flag screws up non-darwin gcc even when the check fails. special-case it.
if test x$TARGET_OS = xdarwin; then
AX_CHECK_LINK_FLAG([[-Wl,-dead_strip]], [LDFLAGS="$LDFLAGS -Wl,-dead_strip"])
@@ -483,11 +503,18 @@ AC_LINK_IFELSE([AC_LANG_SOURCE([
]
)
-if test x$use_reduce_exports = xyes; then
+if test x$use_reduce_exports = xyes -a x$use_daemonlib = xno; then
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[RE_CXXFLAGS="-fvisibility=hidden"],
[AC_MSG_ERROR([Cannot set default symbol visibility. Use --disable-reduce-exports.])])
fi
+AC_MSG_CHECKING([whether to compile as daemonlib])
+if test x$use_daemonlib != xno; then
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
+
LEVELDB_CPPFLAGS=
LIBLEVELDB=
LIBMEMENV=
diff --git a/depends/hosts/linux.mk b/depends/hosts/linux.mk
index b13a0f1..0513394 100644
--- a/depends/hosts/linux.mk
+++ b/depends/hosts/linux.mk
@@ -10,15 +10,15 @@ linux_debug_CXXFLAGS=$(linux_debug_CFLAGS)
linux_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
ifeq (86,$(findstring 86,$(build_arch)))
-i686_linux_CC=gcc -m32
-i686_linux_CXX=g++ -m32
+i686_linux_CC=${CC} -m32
+i686_linux_CXX=${CXX} -m32
i686_linux_AR=ar
i686_linux_RANLIB=ranlib
i686_linux_NM=nm
i686_linux_STRIP=strip
-x86_64_linux_CC=gcc -m64
-x86_64_linux_CXX=g++ -m64
+x86_64_linux_CC=${CC} -m64
+x86_64_linux_CXX=${CXX} -m64
x86_64_linux_AR=ar
x86_64_linux_RANLIB=ranlib
x86_64_linux_NM=nm
diff --git a/depends/packages/bdb.mk b/depends/packages/bdb.mk
index 68841af..65a105b 100644
--- a/depends/packages/bdb.mk
+++ b/depends/packages/bdb.mk
@@ -9,6 +9,7 @@ define $(package)_set_vars
$(package)_config_opts=--disable-shared --enable-cxx --disable-replication
$(package)_config_opts_mingw32=--enable-mingw
$(package)_config_opts_linux=--with-pic
+$(package)_cxxflags_darwin=-stdlib=libc++
endef
define $(package)_preprocess_cmds
diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk
index e7aa48d..df0f7ae 100644
--- a/depends/packages/boost.mk
+++ b/depends/packages/boost.mk
@@ -1,9 +1,8 @@
package=boost
-$(package)_version=1_55_0
-$(package)_download_path=http://sourceforge.net/projects/boost/files/boost/1.55.0
+$(package)_version=1_57_0
+$(package)_download_path=http://sourceforge.net/projects/boost/files/boost/1.57.0
$(package)_file_name=$(package)_$($(package)_version).tar.bz2
-$(package)_sha256_hash=fff00023dd79486d444c8e29922f4072e1d451fc5a4d2b6075852ead7f2b7b52
-$(package)_patches=darwin_boost_atomic-1.patch darwin_boost_atomic-2.patch gcc_5_no_cxx11.patch
+$(package)_sha256_hash=910c8c022a33ccec7f088bd65d4f14b466588dda94ba2124e78b8c57db264967
define $(package)_set_vars
$(package)_config_opts_release=variant=release
@@ -11,7 +10,7 @@ $(package)_config_opts_debug=variant=debug
$(package)_config_opts=--layout=tagged --build-type=complete --user-config=user-config.jam
$(package)_config_opts+=threading=multi link=static -sNO_BZIP2=1 -sNO_ZLIB=1
$(package)_config_opts_linux=threadapi=pthread runtime-link=shared
-$(package)_config_opts_darwin=--toolset=darwin-4.2.1 runtime-link=shared
+$(package)_config_opts_darwin=--toolset=clang runtime-link=shared
$(package)_config_opts_mingw32=binary-format=pe target-os=windows threadapi=win32 runtime-link=static
$(package)_config_opts_x86_64_mingw32=address-model=64
$(package)_config_opts_i686_mingw32=address-model=32
@@ -20,15 +19,14 @@ $(package)_toolset_$(host_os)=gcc
$(package)_archiver_$(host_os)=$($(package)_ar)
$(package)_toolset_darwin=darwin
$(package)_archiver_darwin=$($(package)_libtool)
-$(package)_config_libraries=chrono,filesystem,program_options,system,thread,test
-$(package)_cxxflags=-fvisibility=hidden
-$(package)_cxxflags_linux=-fPIC
+$(package)_config_libraries=chrono,filesystem,program_options,system,thread
+$(package)_cxxflags=-fvisibility=default -fPIC
+$(package)_cxxflags_darwin=-std=c++11 -stdlib=libc++
+$(package)_linkflags=-stdlib=libc++
endef
+
define $(package)_preprocess_cmds
- patch -p2 < $($(package)_patch_dir)/darwin_boost_atomic-1.patch && \
- patch -p2 < $($(package)_patch_dir)/darwin_boost_atomic-2.patch && \
- patch -p2 < $($(package)_patch_dir)/gcc_5_no_cxx11.patch && \
echo "using $(boost_toolset_$(host_os)) : : $($(package)_cxx) : <cxxflags>\"$($(package)_cxxflags) $($(package)_cppflags)\" <linkflags>\"$($(package)_ldflags)\" <archiver>\"$(boost_archiver_$(host_os))\" <striper>\"$(host_STRIP)\" <ranlib>\"$(host_RANLIB)\" <rc>\"$(host_WINDRES)\" : ;" > user-config.jam
endef
diff --git a/src/Makefile.am b/src/Makefile.am
index 2461f82..e7e9ecf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,10 @@
DIST_SUBDIRS = secp256k1
AM_LDFLAGS = $(PTHREAD_CFLAGS) $(LIBTOOL_LDFLAGS)
+noinst_LTLIBRARIES =
+libbitcoind_la_LIBADD =
+libbitcoind_la_LDFLAGS = -no-undefined
+STATIC_EXTRA_LIBS = $(LIBLEVELDB) $(LIBMEMENV)
if EMBEDDED_LEVELDB
LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/include
@@ -49,16 +53,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 +70,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 +177,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 +320,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 +346,17 @@ bitcoind_LDADD = \
if ENABLE_WALLET
bitcoind_LDADD += libbitcoin_wallet.a
+libbitcoind_la_SOURCES += $(libbitcoin_wallet_a_SOURCES)
endif
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));

View File

@ -1,49 +0,0 @@
'use strict';
var socket = require('socket.io-client')('http://localhost:3000');
socket.on('connect', function(){
console.log('connected');
});
socket.on('disconnect', function(){
console.log('disconnected');
});
var message = {
method: 'getOutputs',
params: ['2NChMRHVCxTPq9KeyvHQUSbfLaQY55Zzzp8', true]
};
socket.send(message, function(response) {
if(response.error) {
console.log('Error', response.error);
return;
}
console.log(response.result);
});
var message2 = {
method: 'getTransaction',
params: ['4f793f67fc7465f14fa3a8d3727fa7d133cdb2f298234548b94a5f08b6f4103e', true]
};
socket.send(message2, function(response) {
if(response.error) {
console.log('Error', response.error);
return;
}
console.log(response.result);
});
socket.on('transaction', function(obj) {
console.log(JSON.stringify(obj, null, 2));
});
socket.on('address/transaction', function(obj) {
console.log(JSON.stringify(obj, null, 2));
});
socket.emit('subscribe', 'transaction');
socket.emit('subscribe', 'address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);

View File

@ -1,26 +1,13 @@
'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 = {};

View File

@ -1,56 +0,0 @@
'use strict';
var exports = {};
exports.PREFIXES = {
OUTPUTS: new Buffer('02', 'hex'), // Query outputs by address and/or height
SPENTS: new Buffer('03', 'hex'), // Query inputs by address and/or height
SPENTSMAP: new Buffer('05', 'hex') // Get the input that spends an output
};
exports.MEMPREFIXES = {
OUTPUTS: new Buffer('01', 'hex'), // Query mempool outputs by address
SPENTS: new Buffer('02', 'hex'), // Query mempool inputs by address
SPENTSMAP: new Buffer('03', 'hex') // Query mempool for the input that spends an output
};
// To save space, we're only storing the PubKeyHash or ScriptHash in our index.
// To avoid intentional unspendable collisions, which have been seen on the blockchain,
// we must store the hash type (PK or Script) as well.
exports.HASH_TYPES = {
PUBKEY: new Buffer('01', 'hex'),
REDEEMSCRIPT: new Buffer('02', 'hex')
};
// Translates from our enum type back into the hash types returned by
// bitcore-lib/address.
exports.HASH_TYPES_READABLE = {
'01': 'pubkeyhash',
'02': 'scripthash'
};
exports.HASH_TYPES_MAP = {
'pubkeyhash': exports.HASH_TYPES.PUBKEY,
'scripthash': exports.HASH_TYPES.REDEEMSCRIPT
};
exports.SPACER_MIN = new Buffer('00', 'hex');
exports.SPACER_MAX = new Buffer('ff', 'hex');
exports.SPACER_HEIGHT_MIN = new Buffer('0000000000', 'hex');
exports.SPACER_HEIGHT_MAX = new Buffer('ffffffffff', 'hex');
exports.TIMESTAMP_MIN = new Buffer('0000000000000000', 'hex');
exports.TIMESTAMP_MAX = new Buffer('ffffffffffffffff', 'hex');
// The maximum number of inputs that can be queried at once
exports.MAX_INPUTS_QUERY_LENGTH = 50000;
// The maximum number of outputs that can be queried at once
exports.MAX_OUTPUTS_QUERY_LENGTH = 50000;
// The maximum number of transactions that can be queried at once
exports.MAX_HISTORY_QUERY_LENGTH = 100;
// The maximum number of addresses that can be queried at once
exports.MAX_ADDRESSES_QUERY = 10000;
// The maximum number of simultaneous requests
exports.MAX_ADDRESSES_LIMIT = 5;
module.exports = exports;

View File

@ -1,307 +0,0 @@
'use strict';
var bitcore = require('bitcore-lib');
var BufferReader = bitcore.encoding.BufferReader;
var Address = bitcore.Address;
var PublicKey = bitcore.PublicKey;
var constants = require('./constants');
var $ = bitcore.util.preconditions;
var exports = {};
exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) {
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex);
var key = Buffer.concat([
txidBuffer,
outputIndexBuffer
]);
return key.toString('binary');
};
exports.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 {
prefix: prefix,
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
height: height,
txid: txid,
outputIndex: outputIndex
};
};
exports.encodeOutputValue = function(satoshis, scriptBuffer) {
var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis);
return Buffer.concat([satoshisBuffer, scriptBuffer]);
};
exports.encodeOutputMempoolValue = function(satoshis, timestampBuffer, scriptBuffer) {
var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis);
return Buffer.concat([satoshisBuffer, timestampBuffer, scriptBuffer]);
};
exports.decodeOutputValue = function(buffer) {
var satoshis = buffer.readDoubleBE(0);
var scriptBuffer = buffer.slice(8, buffer.length);
return {
satoshis: satoshis,
scriptBuffer: scriptBuffer
};
};
exports.decodeOutputMempoolValue = function(buffer) {
var satoshis = buffer.readDoubleBE(0);
var timestamp = buffer.readDoubleBE(8);
var scriptBuffer = buffer.slice(16, buffer.length);
return {
satoshis: satoshis,
timestamp: timestamp,
scriptBuffer: scriptBuffer
};
};
exports.encodeInputKey = function(hashBuffer, hashTypeBuffer, height, prevTxIdBuffer, outputIndex) {
var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height);
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex);
return Buffer.concat([
constants.PREFIXES.SPENTS,
hashBuffer,
hashTypeBuffer,
constants.SPACER_MIN,
heightBuffer,
prevTxIdBuffer,
outputIndexBuffer
]);
};
exports.decodeInputKey = function(buffer) {
var reader = new BufferReader(buffer);
var prefix = reader.read(1);
var hashBuffer = reader.read(20);
var hashTypeBuffer = reader.read(1);
var spacer = reader.read(1);
var height = reader.readUInt32BE();
var prevTxId = reader.read(32);
var outputIndex = reader.readUInt32BE();
return {
prefix: prefix,
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
height: height,
prevTxId: prevTxId,
outputIndex: outputIndex
};
};
exports.encodeInputValue = function(txidBuffer, inputIndex) {
var inputIndexBuffer = new Buffer(4);
inputIndexBuffer.writeUInt32BE(inputIndex);
return Buffer.concat([
txidBuffer,
inputIndexBuffer
]);
};
exports.decodeInputValue = function(buffer) {
var txid = buffer.slice(0, 32);
var inputIndex = buffer.readUInt32BE(32);
return {
txid: txid,
inputIndex: inputIndex
};
};
exports.encodeInputKeyMap = function(outputTxIdBuffer, outputIndex) {
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex);
return Buffer.concat([
constants.PREFIXES.SPENTSMAP,
outputTxIdBuffer,
outputIndexBuffer
]);
};
exports.decodeInputKeyMap = function(buffer) {
var txid = buffer.slice(1, 33);
var outputIndex = buffer.readUInt32BE(33);
return {
outputTxId: txid,
outputIndex: outputIndex
};
};
exports.encodeInputValueMap = function(inputTxIdBuffer, inputIndex) {
var inputIndexBuffer = new Buffer(4);
inputIndexBuffer.writeUInt32BE(inputIndex);
return Buffer.concat([
inputTxIdBuffer,
inputIndexBuffer
]);
};
exports.decodeInputValueMap = function(buffer) {
var txid = buffer.slice(0, 32);
var inputIndex = buffer.readUInt32BE(32);
return {
inputTxId: txid,
inputIndex: inputIndex
};
};
exports.encodeSummaryCacheKey = function(address) {
return Buffer.concat([address.hashBuffer, constants.HASH_TYPES_MAP[address.type]]);
};
exports.decodeSummaryCacheKey = function(buffer, network) {
var hashBuffer = buffer.read(20);
var type = constants.HASH_TYPES_READABLE[buffer.read(20, 2).toString('hex')];
var address = new Address({
hashBuffer: hashBuffer,
type: type,
network: network
});
return address;
};
exports.encodeSummaryCacheValue = function(cache, tipHeight, tipHash) {
var tipHashBuffer = new Buffer(tipHash, 'hex');
var buffer = new Buffer(new Array(20));
buffer.writeUInt32BE(tipHeight);
buffer.writeDoubleBE(cache.result.totalReceived, 4);
buffer.writeDoubleBE(cache.result.balance, 12);
var txidBuffers = [];
for (var i = 0; i < cache.result.txids.length; i++) {
var buf = new Buffer(new Array(36));
var txid = cache.result.txids[i];
buf.write(txid, 'hex');
buf.writeUInt32BE(cache.result.appearanceIds[txid], 32);
txidBuffers.push(buf);
}
var txidsBuffer = Buffer.concat(txidBuffers);
var value = Buffer.concat([tipHashBuffer, buffer, txidsBuffer]);
return value;
};
exports.decodeSummaryCacheValue = function(buffer) {
var hash = buffer.slice(0, 32).toString('hex');
var height = buffer.readUInt32BE(32);
var totalReceived = buffer.readDoubleBE(36);
var balance = buffer.readDoubleBE(44);
// read 32 byte chunks until exhausted
var appearanceIds = {};
var txids = [];
var pos = 52;
while(pos < buffer.length) {
var txid = buffer.slice(pos, pos + 32).toString('hex');
var txidHeight = buffer.readUInt32BE(pos + 32);
txids.push(txid);
appearanceIds[txid] = txidHeight;
pos += 36;
}
var cache = {
height: height,
hash: hash,
result: {
appearanceIds: appearanceIds,
txids: txids,
totalReceived: totalReceived,
balance: balance,
unconfirmedAppearanceIds: {}, // unconfirmed values are never stored in cache
unconfirmedBalance: 0
}
};
return cache;
};
exports.getAddressInfo = function(addressStr) {
var addrObj = bitcore.Address(addressStr);
var hashTypeBuffer = constants.HASH_TYPES_MAP[addrObj.type];
return {
hashBuffer: addrObj.hashBuffer,
hashTypeBuffer: hashTypeBuffer,
hashTypeReadable: addrObj.type
};
};
/**
* This function is optimized to return address information about an output script
* without constructing a Bitcore Address instance.
* @param {Script} - An instance of a Bitcore Script
* @param {Network|String} - The network for the address
*/
exports.extractAddressInfoFromScript = function(script, network) {
$.checkArgument(network, 'Second argument is expected to be a network');
var hashBuffer;
var addressType;
var hashTypeBuffer;
if (script.isPublicKeyHashOut()) {
hashBuffer = script.chunks[2].buf;
hashTypeBuffer = constants.HASH_TYPES.PUBKEY;
addressType = Address.PayToPublicKeyHash;
} else if (script.isScriptHashOut()) {
hashBuffer = script.chunks[1].buf;
hashTypeBuffer = constants.HASH_TYPES.REDEEMSCRIPT;
addressType = Address.PayToScriptHash;
} else if (script.isPublicKeyOut()) {
var pubkey = script.chunks[0].buf;
var address = Address.fromPublicKey(new PublicKey(pubkey), network);
hashBuffer = address.hashBuffer;
hashTypeBuffer = constants.HASH_TYPES.PUBKEY;
// pay-to-publickey doesn't have an address, however for compatibility
// purposes, we can create an address
addressType = Address.PayToPublicKeyHash;
} else {
return false;
}
return {
hashBuffer: hashBuffer,
hashTypeBuffer: hashTypeBuffer,
addressType: addressType
};
};
module.exports = exports;

View File

@ -1,266 +0,0 @@
'use strict';
var bitcore = require('bitcore-lib');
var async = require('async');
var _ = bitcore.deps._;
var constants = require('./constants');
/**
* This represents an instance that keeps track of data over a series of
* asynchronous I/O calls to get the transaction history for a group of
* addresses. History can be queried by start and end block heights to limit large sets
* of results (uses leveldb key streaming).
*/
function AddressHistory(args) {
this.node = args.node;
this.options = args.options;
if(Array.isArray(args.addresses)) {
this.addresses = args.addresses;
} else {
this.addresses = [args.addresses];
}
this.maxHistoryQueryLength = args.options.maxHistoryQueryLength || constants.MAX_HISTORY_QUERY_LENGTH;
this.maxAddressesQuery = args.options.maxAddressesQuery || constants.MAX_ADDRESSES_QUERY;
this.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 = Math.max(0, totalCount - self.options.from);
var toOffset = Math.max(0, 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

@ -1,40 +0,0 @@
'use strict';
var Transform = require('stream').Transform;
var inherits = require('util').inherits;
var bitcore = require('bitcore-lib');
var encodingUtil = require('../encoding');
var $ = bitcore.util.preconditions;
function InputsTransformStream(options) {
$.checkArgument(options.address instanceof bitcore.Address);
Transform.call(this, {
objectMode: true
});
this._address = options.address;
this._addressStr = this._address.toString();
this._tipHeight = options.tipHeight;
}
inherits(InputsTransformStream, Transform);
InputsTransformStream.prototype._transform = function(chunk, encoding, callback) {
var self = this;
var key = encodingUtil.decodeInputKey(chunk.key);
var value = encodingUtil.decodeInputValue(chunk.value);
var input = {
address: this._addressStr,
hashType: this._address.type,
txid: value.txid.toString('hex'),
inputIndex: value.inputIndex,
height: key.height,
confirmations: this._tipHeight - key.height + 1
};
self.push(input);
callback();
};
module.exports = InputsTransformStream;

View File

@ -1,42 +0,0 @@
'use strict';
var Transform = require('stream').Transform;
var inherits = require('util').inherits;
var bitcore = require('bitcore-lib');
var encodingUtil = require('../encoding');
var $ = bitcore.util.preconditions;
function OutputsTransformStream(options) {
Transform.call(this, {
objectMode: true
});
$.checkArgument(options.address instanceof bitcore.Address);
this._address = options.address;
this._addressStr = this._address.toString();
this._tipHeight = options.tipHeight;
}
inherits(OutputsTransformStream, Transform);
OutputsTransformStream.prototype._transform = function(chunk, encoding, callback) {
var self = this;
var key = encodingUtil.decodeOutputKey(chunk.key);
var value = encodingUtil.decodeOutputValue(chunk.value);
var output = {
address: this._addressStr,
hashType: this._address.type,
txid: key.txid.toString('hex'), //TODO use a buffer
outputIndex: key.outputIndex,
height: key.height,
satoshis: value.satoshis,
script: value.scriptBuffer.toString('hex'), //TODO use a buffer
confirmations: this._tipHeight - key.height + 1
};
self.push(output);
callback();
};
module.exports = OutputsTransformStream;

View File

@ -1,14 +1,23 @@
'use strict';
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var util = require('util');
var bindings = require('bindings')('bitcoind.node');
var mkdirp = require('mkdirp');
var bitcore = require('bitcore-lib');
var Address = bitcore.Address;
var zmq = require('zmq');
var async = require('async');
var BitcoinRPC = require('bitcoind-rpc');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var index = require('../');
var log = index.log;
var errors = index.errors;
var Service = require('../service');
var Transaction = require('../transaction');
/**
* Provides an interface to native bindings to [Bitcoin Core](https://github.com/bitcoin/bitcoin)
@ -31,7 +40,28 @@ util.inherits(Bitcoin, Service);
Bitcoin.dependencies = [];
Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n';
Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n' + 'addressindex=1\n' + 'server=1\n';
/**
* Called by Node to determine the available API methods.
*/
Bitcoin.prototype.getAPIMethods = function() {
var methods = [
['getBlock', this, this.getBlock, 1],
['getBlockHeader', this, this.getBlockHeader, 1],
['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2],
['getTransaction', this, this.getTransaction, 2],
['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2],
['sendTransaction', this, this.sendTransaction, 1],
['estimateFee', this, this.estimateFee, 1],
['getAddressTxids', this, this.getAddressTxids, 2],
['getAddressBalance', this, this.getAddressBalance, 2],
['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 2],
['getAddressHistory', this, this.getAddressHistory, 2],
['getAddressSummary', this, this.getAddressSummary, 1]
];
return methods;
};
Bitcoin.prototype._loadConfiguration = function() {
/* jshint maxstatements: 25 */
@ -44,16 +74,6 @@ Bitcoin.prototype._loadConfiguration = function() {
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++) {
@ -72,11 +92,36 @@ Bitcoin.prototype._loadConfiguration = function() {
$.checkState(
this.configuration.txindex && this.configuration.txindex === 1,
'Txindex option is required in order to use most of the features of bitcore-node. ' +
'"txindex" option is required in order to use transaction query features of bitcore-node. ' +
'Please add "txindex=1" to your configuration and reindex an existing database if ' +
'necessary with reindex=1'
);
$.checkState(
this.configuration.addressindex && this.configuration.addressindex === 1,
'"addressindex" option is required in order to use address query features of bitcore-node. ' +
'Please add "addressindex=1" to your configuration and reindex an existing database if ' +
'necessary with reindex=1'
);
$.checkState(
this.configuration.server && this.configuration.server === 1,
'"server" option is required to communicate to bitcoind from bitcore. ' +
'Please add "server=1" to your configuration and restart'
);
$.checkState(
this.configuration.zmqpubhashtx,
'"zmqpubhashtx" option is required to get event updates from bitcoind. ' +
'Please add "zmqpubhashtx=tcp://127.0.0.1:<port>" to your configuration and restart'
);
$.checkState(
this.configuration.zmqpubhashtx,
'"zmqpubhashblock" option is required to get event updates from bitcoind. ' +
'Please add "zmqpubhashblock=tcp://127.0.0.1:<port>" to your configuration and restart'
);
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 ' +
@ -87,42 +132,38 @@ Bitcoin.prototype._loadConfiguration = function() {
};
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));
this.zmqSubSocket.subscribe('hashblock');
this.zmqSubSocket.subscribe('hashtx');
// 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]);
this.zmqSubSocket.on('message', function(topic, message) {
var topicString = topic.toString('utf8');
if (topicString === 'hashtx') {
self.emit('tx', message.toString('hex'));
} else if (topicString === 'hashblock') {
self.tiphash = message.toString('hex');
self.client.getBlock(self.tiphash, function(err, response) {
if (err) {
return log.error(err);
}
self.height = response.result.height;
$.checkState(self.height >= 0);
self.emit('tip', self.height);
});
if(!self.node.stopping) {
self.syncPercentage(function(err, percentage) {
if (err) {
return log.error(err);
}
log.info('Bitcoin Height:', self.height, 'Percentage:', percentage.toFixed(2));
});
}
}
});
// 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) {
@ -130,19 +171,40 @@ Bitcoin.prototype._onReady = function(result, callback) {
self._registerEventHandlers();
var info = self.getInfo();
self.height = info.blocks;
self.getBlock(0, function(err, block) {
self.client.getInfo(function(err, response) {
if (err) {
return callback(err);
}
self.genesisBuffer = block;
self.emit('ready', result);
log.info('Bitcoin Daemon Ready');
callback();
});
self.height = response.result.blocks;
self.client.getBlockHash(0, function(err, response) {
if (err) {
return callback(err);
}
var blockhash = response.result;
self.getBlock(blockhash, function(err, block) {
if (err) {
return callback(err);
}
self.tiphash = block.hash;
self.genesisBuffer = block.toBuffer();
self.emit('ready', result);
log.info('Bitcoin Daemon Ready');
callback();
});
});
});
};
Bitcoin.prototype._getNetworkOption = function() {
var networkOption;
if (this.node.network === bitcore.Networks.testnet) {
if (this.node.network.regtestEnabled) {
networkOption = '--regtest';
}
networkOption = '--testnet';
}
return networkOption;
};
/**
@ -152,58 +214,348 @@ Bitcoin.prototype._onReady = function(result, callback) {
Bitcoin.prototype.start = function(callback) {
var self = this;
this._loadConfiguration();
self._loadConfiguration();
var networkName = this.node.network.name;
if (this.node.network.regtestEnabled) {
networkName = 'regtest';
var options = [
'--conf=' + path.resolve(this.node.datadir, './bitcoin.conf'),
'--datadir=' + this.node.datadir,
];
if (self._getNetworkOption()) {
options.push(self._getNetworkOption());
}
bindings.start({
datadir: this.node.datadir,
network: networkName
}, function(err) {
if(err) {
self.process = spawn('bitcoind', options, {stdio: 'inherit'});
self.process.on('error', function(err) {
log.error(err);
});
async.retry({times: 60, interval: 5000}, function(done) {
if (self.node.stopping) {
return done(new Error('Stopping while trying to connect to bitcoind.'));
}
self.client = new BitcoinRPC({
protocol: 'http',
host: '127.0.0.1',
port: self.configuration.rpcport,
user: self.configuration.rpcuser,
pass: self.configuration.rpcpassword
});
self.client.getInfo(function(err) {
if (err) {
if (!(err instanceof Error)) {
log.warn(err.message);
}
return done(new Error('Could not connect to bitcoind RPC'));
}
done();
});
}, function ready(err, result) {
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.zmqSubSocket = zmq.socket('sub');
self.zmqSubSocket.on('monitor_error', function(err) {
log.error('Error in monitoring: %s, will restart monitoring in 5 seconds', err);
setTimeout(function() {
self.zmqSubSocket.monitor(500, 0);
}, 5000);
});
self.zmqSubSocket.monitor(500, 0);
self.zmqSubSocket.connect(self.configuration.zmqpubhashtx);
if (self._reindex) {
var interval = setInterval(function() {
self.syncPercentage(function(err, percentSynced) {
if (err) {
return log.error(err);
}
log.info('Bitcoin Core Daemon Reindex Percentage: ' + percentSynced.toFixed(2));
if (Math.round(percentSynced) >= 100) {
self._reindex = false;
self._onReady(result, callback);
clearInterval(interval);
}
}, self._reindexWait);
});
}, self._reindexWait);
}
else {
self._onReady(result, callback);
}
});
} else {
self._onReady(result, callback);
}
});
};
/**
* Helper to determine the state of the database.
* @param {Function} callback
* @returns {Boolean} If the database is fully synced
*/
Bitcoin.prototype.isSynced = function() {
return bindings.isSynced();
Bitcoin.prototype.isSynced = function(callback) {
this.syncPercentage(function(err, percentage) {
if (err) {
return callback(err);
}
if (Math.round(percentage) >= 100) {
callback(null, true);
} else {
callback(null, false);
}
});
};
/**
* Helper to determine the progress of the database.
* @param {Function} callback
* @returns {Number} An estimated percentage of the syncronization status
*/
Bitcoin.prototype.syncPercentage = function() {
return bindings.syncPercentage();
Bitcoin.prototype.syncPercentage = function(callback) {
this.client.getBlockchainInfo(function(err, response) {
if (err) {
return callback(err);
}
var percentSynced = response.result.verificationprogress * 100;
callback(null, percentSynced);
});
};
Bitcoin.prototype.getAddressBalance = function(addressArg, options, callback) {
// TODO keep a cache and update the cache by a range of block heights
var addresses = [addressArg];
if (Array.isArray(addressArg)) {
addresses = addressArg;
}
this.client.getAddressBalance({addresses: addresses}, function(err, response) {
if (err) {
return callback(err);
}
callback(null, response.result);
});
};
Bitcoin.prototype.getAddressUnspentOutputs = function() {
// TODO add this rpc method to bitcoind
};
Bitcoin.prototype.getAddressTxids = function(addressArg, options, callback) {
// TODO Keep a cache updated for queries
var addresses = [addressArg];
if (Array.isArray(addressArg)) {
addresses = addressArg;
}
this.client.getAddressTxids({addresses: addresses}, function(err, response) {
if (err) {
return callback(err);
}
return callback(null, response.result);
});
};
Bitcoin.prototype._getConfirmationsDetail = function(transaction) {
var confirmations = 0;
if (transaction.__height >= 0) {
confirmations = this.height - transaction.__height + 1;
}
return confirmations;
};
Bitcoin.prototype._getAddressDetailsForTransaction = function(transaction, addressStrings) {
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 (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 (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;
};
/**
* Will expand into a detailed transaction from a txid
* @param {Object} txid - A bitcoin transaction id
* @param {Function} callback
*/
Bitcoin.prototype._getDetailedTransaction = function(txid, options, next) {
var self = this;
var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool;
self.getTransactionWithBlockInfo(
txid,
queryMempool,
function(err, transaction) {
if (err) {
return next(err);
}
transaction.populateInputs(self, [], function(err) {
if (err) {
return next(err);
}
var addressDetails = self._getAddressDetailsForTransaction(transaction, options.addressStrings);
var details = {
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(null, details);
});
}
);
};
Bitcoin.prototype._getAddressStrings = function(addresses) {
var addressStrings = [];
for (var i = 0; i < addresses.length; i++) {
var address = addresses[i];
if (address instanceof bitcore.Address) {
addressStrings.push(address.toString());
} else if (_.isString(address)) {
addressStrings.push(address);
} else {
throw new TypeError('Addresses are expected to be strings');
}
}
return addressStrings;
};
Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) {
var self = this;
var addresses = [addressArg];
if (addresses.length > this.maxAddressesQuery) {
return callback(new TypeError('Maximum number of addresses (' + this.maxAddressesQuery + ') exceeded'));
}
var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool;
var addressStrings = this._getAddressStrings(addresses);
self.getAddressTxids(addresses, {}, function(err, txids) {
if (err) {
return callback(err);
}
async.mapSeries(
txids,
function(txid, next) {
self._getDetailedTransaction(txid, {
queryMempool: queryMempool,
addressStrings: addressStrings
}, next);
},
function(err, transactions) {
if (err) {
return callback(err);
}
callback(null, {
totalCount: txids.length,
items: transactions
});
}
);
});
};
Bitcoin.prototype.getAddressSummary = function(addressArg, options, callback) {
// TODO: optional mempool
var self = this;
var summary = {};
if (_.isUndefined(options.queryMempool)) {
options.queryMempool = true;
}
function getBalance(done) {
self.getAddressBalance(addressArg, options, function(err, data) {
if (err) {
return done(err);
}
summary.totalReceived = data.received;
summary.totalSpent = data.received - data.balance;
summary.balance = data.balance;
done();
});
}
function getTxList(done) {
self.getAddressTxids(addressArg, options, function(err, txids) {
if (err) {
return done(err);
}
summary.txids = txids;
summary.appearances = txids.length;
done();
});
}
var tasks = [];
if (!options.noBalance) {
tasks.push(getBalance);
}
if (!options.noTxList) {
tasks.push(getTxList);
}
async.parallel(tasks, function(err) {
if (err) {
return callback(err);
}
callback(null, summary);
});
};
/**
@ -211,41 +563,80 @@ Bitcoin.prototype.syncPercentage = function() {
* @param {String|Number} block - A block hash or block height number
*/
Bitcoin.prototype.getBlock = function(block, callback) {
return bindings.getBlock(block, callback);
// TODO apply performance patch to the RPC method for raw data
// TODO keep a cache of results
var self = this;
function queryHeader(blockhash) {
self.client.getBlock(blockhash, false, function(err, response) {
if (err) {
return callback(err);
}
var block = bitcore.Block.fromString(response.result);
callback(null, block);
});
}
if (_.isNumber(block)) {
self.client.getBlockHash(block, function(err, response) {
if (err) {
return callback(err);
}
var blockhash = response.result;
queryHeader(blockhash);
});
} else {
queryHeader(block);
}
};
/**
* 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);
Bitcoin.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
var self = this;
self.client.getBlockHashes(high, low, function(err, response) {
if (err) {
return callback(err);
}
callback(null, response.result);
});
};
/**
* Will return the block index information, the output will have the format:
* {
* prevHash: '7194fcf33f58c96720f88f21ab28c34ebc5638c5f88d7838517deb27313b59de',
* hash: '7c5caf0af1bf16e3467b275a3b408bc1d251bff3c25be20cb727c47b66a7b216',
* prevHash: '000000004956cc2edd1a8caa05eacfa3c69f4c490bfc9ace820257834115ab35',
* nextHash: '0000000000629d100db387f37d0f37c51118f250fb0946310a8c37316cbc4028'
* hash: ' 00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e',
* 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);
};
Bitcoin.prototype.getBlockHeader = function(block, callback) {
// TODO keep a cache of queries
var self = this;
/**
* 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);
function queryHeader(blockhash) {
self.client.getBlockHeader(blockhash, function(err, response) {
if (err) {
return callback(err);
}
callback(null, response.result);
});
}
if (_.isNumber(block)) {
self.client.getBlockHash(block, function(err, response) {
if (err) {
return callback(err);
}
var blockhash = response.result;
queryHeader(blockhash);
});
} else {
queryHeader(block);
}
};
/**
@ -253,8 +644,13 @@ Bitcoin.prototype.isMainChain = function(blockHash) {
* @param {Number} blocks - The number of blocks for the transaction to be confirmed.
* @returns {Number}
*/
Bitcoin.prototype.estimateFee = function(blocks) {
return bindings.estimateFee(blocks);
Bitcoin.prototype.estimateFee = function(blocks, callback) {
this.client.estimateFee(blocks, function(err, response) {
if (err) {
return callback(err);
}
callback(null, response.result);
});
};
/**
@ -263,8 +659,21 @@ Bitcoin.prototype.estimateFee = function(blocks) {
* @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);
Bitcoin.prototype.sendTransaction = function(tx, allowAbsurdFees, callback) {
var txString;
if (tx instanceof Transaction) {
txString = tx.serialize();
} else {
txString = tx;
}
this.client.sendTransaction(txString, allowAbsurdFees, function(err, response) {
if (err) {
return callback(err);
}
callback(null, response.result);
});
};
/**
@ -274,7 +683,18 @@ Bitcoin.prototype.sendTransaction = function(transaction, allowAbsurdFees) {
* @param {Function} callback
*/
Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
return bindings.getTransaction(txid, queryMempool, callback);
// TODO keep an LRU cache available of transactions
this.client.getRawTransaction(txid, function(err, response) {
if (err) {
return callback(err);
}
if (!response.result) {
return callback(new errors.Transaction.NotFound());
}
var tx = Transaction();
tx.fromString(response.result);
callback(null, tx);
});
};
/**
@ -290,41 +710,41 @@ Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
* @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);
// TODO keep an LRU cache available of transactions
// TODO get information from txindex as an RPC method
this.client.getRawTransaction(txid, 1, function(err, response) {
if (err) {
return callback(err);
}
if (!response.result) {
return callback(new errors.Transaction.NotFound());
}
var tx = Transaction();
tx.fromString(response.result.hex);
tx.__blockHash = response.result.blockhash;
tx.__height = response.result.height;
tx.__timestamp = response.result.time;
callback(null, tx);
});
};
/**
* Will get the best block hash for the chain.
* @returns {String}
*/
Bitcoin.prototype.getBestBlockHash = function() {
return bindings.getBestBlockHash();
Bitcoin.prototype.getBestBlockHash = function(callback) {
// TODO keep an LRU cache available of transactions
this.client.getBestBlockHash(function(err, response) {
if (err) {
return callback(err);
}
callback(null, response.result);
});
};
/**
* 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);
Bitcoin.prototype.getInputForOutput = function(txid, index, options, callback) {
// TODO
setImmediate(callback);
};
/**
@ -341,8 +761,13 @@ Bitcoin.prototype.getNextBlockHash = function(hash) {
* errors: ''
* }
*/
Bitcoin.prototype.getInfo = function() {
return bindings.getInfo();
Bitcoin.prototype.getInfo = function(callback) {
this.client.getInfo(function(err, response) {
if (err) {
return callback(err);
}
callback(null, response.result);
});
};
/**
@ -350,14 +775,18 @@ Bitcoin.prototype.getInfo = function() {
* @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();
}
});
if (this.process) {
this.process.once('exit', function(err, status) {
if (err) {
return callback(err);
} else {
return callback();
}
});
this.process.kill('SIGHUP');
} else {
callback();
}
};
module.exports = Bitcoin;

View File

@ -1,812 +0,0 @@
'use strict';
var util = require('util');
var fs = require('fs');
var async = require('async');
var levelup = require('levelup');
var leveldown = require('leveldown');
var mkdirp = require('mkdirp');
var bitcore = require('bitcore-lib');
var BufferUtil = bitcore.util.buffer;
var Networks = bitcore.Networks;
var Block = bitcore.Block;
var $ = bitcore.util.preconditions;
var index = require('../');
var errors = index.errors;
var log = index.log;
var Transaction = require('../transaction');
var Service = require('../service');
/**
* This service synchronizes a leveldb database with bitcoin block chain by connecting and
* disconnecting blocks to build new indexes that can be queried. Other services can extend
* the data that is indexed by implementing a `blockHandler` method.
*
* @param {Object} options
* @param {Node} options.node - A reference to the node
* @param {Node} options.store - A levelup backend store
*/
function DB(options) {
/* jshint maxstatements: 20 */
if (!(this instanceof DB)) {
return new DB(options);
}
if (!options) {
options = {};
}
Service.call(this, options);
// Used to keep track of the version of the indexes
// to determine during an upgrade if a reindex is required
this.version = 2;
this.tip = null;
this.genesis = null;
$.checkState(this.node.network, 'Node is expected to have a "network" property');
this.network = this.node.network;
this._setDataPath();
this.maxOpenFiles = options.maxOpenFiles || DB.DEFAULT_MAX_OPEN_FILES;
this.maxTransactionLimit = options.maxTransactionLimit || DB.MAX_TRANSACTION_LIMIT;
this.levelupStore = leveldown;
if (options.store) {
this.levelupStore = options.store;
}
this.retryInterval = 60000;
this.subscriptions = {
transaction: [],
block: []
};
}
util.inherits(DB, Service);
DB.dependencies = ['bitcoind'];
DB.PREFIXES = {
VERSION: new Buffer('ff', 'hex'),
BLOCKS: new Buffer('01', 'hex'),
TIP: new Buffer('04', 'hex')
};
// The maximum number of transactions to query at once
// Used for populating previous inputs
DB.MAX_TRANSACTION_LIMIT = 5;
// The default maxiumum number of files open for leveldb
DB.DEFAULT_MAX_OPEN_FILES = 200;
/**
* This function will set `this.dataPath` based on `this.node.network`.
* @private
*/
DB.prototype._setDataPath = function() {
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
if (this.node.network === Networks.livenet) {
this.dataPath = this.node.datadir + '/bitcore-node.db';
} else if (this.node.network === Networks.testnet) {
if (this.node.network.regtestEnabled) {
this.dataPath = this.node.datadir + '/regtest/bitcore-node.db';
} else {
this.dataPath = this.node.datadir + '/testnet3/bitcore-node.db';
}
} else {
throw new Error('Unknown network: ' + this.network);
}
};
DB.prototype._checkVersion = function(callback) {
var self = this;
var options = {
keyEncoding: 'binary',
valueEncoding: 'binary'
};
self.store.get(DB.PREFIXES.TIP, options, function(err) {
if (err instanceof levelup.errors.NotFoundError) {
// The database is brand new and doesn't have a tip stored
// we can skip version checking
return callback();
} else if (err) {
return callback(err);
}
self.store.get(DB.PREFIXES.VERSION, options, function(err, buffer) {
var version;
if (err instanceof levelup.errors.NotFoundError) {
// The initial version (1) of the database didn't store the version number
version = 1;
} else if (err) {
return callback(err);
} else {
version = buffer.readUInt32BE();
}
if (self.version !== version) {
var helpUrl = 'https://github.com/bitpay/bitcore-node/blob/master/docs/services/db.md#how-to-reindex';
return callback(new Error(
'The version of the database "' + version + '" does not match the expected version "' +
self.version + '". A recreation of "' + self.dataPath + '" (can take several hours) is ' +
'required or to switch versions of software to match. Please see ' + helpUrl +
' for more information.'
));
}
callback();
});
});
};
DB.prototype._setVersion = function(callback) {
var versionBuffer = new Buffer(new Array(4));
versionBuffer.writeUInt32BE(this.version);
this.store.put(DB.PREFIXES.VERSION, versionBuffer, callback);
};
/**
* Called by Node to start the service.
* @param {Function} callback
*/
DB.prototype.start = function(callback) {
var self = this;
if (!fs.existsSync(this.dataPath)) {
mkdirp.sync(this.dataPath);
}
this.genesis = Block.fromBuffer(this.node.services.bitcoind.genesisBuffer);
this.store = levelup(this.dataPath, { db: this.levelupStore, maxOpenFiles: this.maxOpenFiles });
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
this.once('ready', function() {
log.info('Bitcoin Database Ready');
// Notify that there is a new tip
self.node.services.bitcoind.on('tip', function(height) {
if(!self.node.stopping) {
self.sync();
}
});
});
async.series([
function(next) {
self._checkVersion(next);
},
function(next) {
self._setVersion(next);
}
], function(err) {
if (err) {
return callback(err);
}
self.loadTip(function(err) {
if (err) {
return callback(err);
}
self.sync();
self.emit('ready');
setImmediate(callback);
});
});
};
/**
* Called by Node to stop the service
* @param {Function} callback
*/
DB.prototype.stop = function(callback) {
var self = this;
// Wait until syncing stops and all db operations are completed before closing leveldb
async.whilst(function() {
return self.bitcoindSyncing;
}, function(next) {
setTimeout(next, 10);
}, function() {
self.store.close(callback);
});
};
/**
* Will give information about the database from bitcoin.
* @param {Function} callback
*/
DB.prototype.getInfo = function(callback) {
var self = this;
setImmediate(function() {
var info = self.node.bitcoind.getInfo();
callback(null, info);
});
};
/**
* Closes the underlying store database
* @param {Function} callback
*/
DB.prototype.close = function(callback) {
this.store.close(callback);
};
/**
* This function is responsible for emitting `db/transaction` events.
* @param {Object} txInfo - The data from the bitcoind.on('tx') event
* @param {Buffer} txInfo.buffer - The transaction buffer
* @param {Boolean} txInfo.mempool - If the transaction was accepted in the mempool
* @param {String} txInfo.hash - The hash of the transaction
*/
DB.prototype.transactionHandler = function(txInfo) {
var tx = Transaction().fromBuffer(txInfo.buffer);
for (var i = 0; i < this.subscriptions.transaction.length; i++) {
this.subscriptions.transaction[i].emit('db/transaction', {
rejected: !txInfo.mempool,
tx: tx
});
}
};
/**
* Called by Node to determine the available API methods.
*/
DB.prototype.getAPIMethods = function() {
var methods = [
['getBlock', this, this.getBlock, 1],
['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2],
['getTransaction', this, this.getTransaction, 2],
['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2],
['sendTransaction', this, this.sendTransaction, 1],
['estimateFee', this, this.estimateFee, 1]
];
return methods;
};
DB.prototype.loadTip = function(callback) {
var self = this;
var options = {
keyEncoding: 'binary',
valueEncoding: 'binary'
};
self.store.get(DB.PREFIXES.TIP, options, function(err, tipData) {
if(err && err instanceof levelup.errors.NotFoundError) {
self.tip = self.genesis;
self.tip.__height = 0;
self.connectBlock(self.genesis, function(err) {
if(err) {
return callback(err);
}
self.emit('addblock', self.genesis);
callback();
});
return;
} else if(err) {
return callback(err);
}
var hash = tipData.toString('hex');
var times = 0;
async.retry({times: 3, interval: self.retryInterval}, function(done) {
self.getBlock(hash, function(err, tip) {
if(err) {
times++;
log.warn('Bitcoind does not have our tip (' + hash + '). Bitcoind may have crashed and needs to catch up.');
if(times < 3) {
log.warn('Retrying in ' + (self.retryInterval / 1000) + ' seconds.');
}
return done(err);
}
done(null, tip);
});
}, function(err, tip) {
if(err) {
log.warn('Giving up after 3 tries. Please report this bug to https://github.com/bitpay/bitcore-node/issues');
log.warn('Please reindex your database.');
return callback(err);
}
self.tip = tip;
var blockIndex = self.node.services.bitcoind.getBlockIndex(self.tip.hash);
if(!blockIndex) {
return callback(new Error('Could not get height for tip.'));
}
self.tip.__height = blockIndex.height;
callback();
});
});
};
/**
* Will get a block from bitcoind and give a Bitcore Block
* @param {String|Number} hash - A block hash or block height
*/
DB.prototype.getBlock = function(hash, callback) {
this.node.services.bitcoind.getBlock(hash, function(err, blockBuffer) {
if (err) {
return callback(err);
}
callback(null, Block.fromBuffer(blockBuffer));
});
};
/**
* Get block hashes between two timestamps
* @param {Number} high - high timestamp, in seconds, inclusive
* @param {Number} low - low timestamp, in seconds, inclusive
* @param {Function} callback
*/
DB.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
var self = this;
var hashes = [];
var lowKey;
var highKey;
try {
lowKey = this._encodeBlockIndexKey(low);
highKey = this._encodeBlockIndexKey(high);
} catch(e) {
return callback(e);
}
var stream = this.store.createReadStream({
gte: lowKey,
lte: highKey,
reverse: true,
valueEncoding: 'binary',
keyEncoding: 'binary'
});
stream.on('data', function(data) {
hashes.push(self._decodeBlockIndexValue(data.value));
});
var error;
stream.on('error', function(streamError) {
if (streamError) {
error = streamError;
}
});
stream.on('close', function() {
if (error) {
return callback(error);
}
callback(null, hashes);
});
return stream;
};
/**
* Will give a Bitcore Transaction from bitcoind by txid
* @param {String} txid - A transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
DB.prototype.getTransaction = function(txid, queryMempool, callback) {
this.node.services.bitcoind.getTransaction(txid, queryMempool, function(err, txBuffer) {
if (err) {
return callback(err);
}
if (!txBuffer) {
return callback(new errors.Transaction.NotFound());
}
callback(null, Transaction().fromBuffer(txBuffer));
});
};
/**
* Will give a Bitcore Transaction and populated information about the block included.
* @param {String} txid - A transaction hash
* @param {Boolean} queryMempool - Include the mempool
* @param {Function} callback
*/
DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
this.node.services.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, obj) {
if (err) {
return callback(err);
}
var tx = Transaction().fromBuffer(obj.buffer);
tx.__blockHash = obj.blockHash;
tx.__height = obj.height;
tx.__timestamp = obj.timestamp;
callback(null, tx);
});
};
/**
* Will send a transaction to the Bitcoin network.
* @param {Transaction} tx - An instance of a Bitcore Transaction
* @param {Function} callback
*/
DB.prototype.sendTransaction = function(tx, callback) {
var txString;
if (tx instanceof Transaction) {
txString = tx.serialize();
} else {
txString = tx;
}
try {
var txid = this.node.services.bitcoind.sendTransaction(txString);
return callback(null, txid);
} catch(err) {
return callback(err);
}
};
/**
* Will estimate fees for a transaction and give a result in
* satoshis per kilobyte. Similar to the bitcoind estimateFee method.
* @param {Number} blocks - The number of blocks for the transaction to be included.
* @param {Function} callback
*/
DB.prototype.estimateFee = function(blocks, callback) {
var self = this;
setImmediate(function() {
callback(null, self.node.services.bitcoind.estimateFee(blocks));
});
};
/**
* Called by the Bus to determine the available events.
*/
DB.prototype.getPublishEvents = function() {
return [
{
name: 'db/transaction',
scope: this,
subscribe: this.subscribe.bind(this, 'transaction'),
unsubscribe: this.unsubscribe.bind(this, 'transaction')
},
{
name: 'db/block',
scope: this,
subscribe: this.subscribe.bind(this, 'block'),
unsubscribe: this.unsubscribe.bind(this, 'block')
}
];
};
DB.prototype.subscribe = function(name, emitter) {
this.subscriptions[name].push(emitter);
};
DB.prototype.unsubscribe = function(name, emitter) {
var index = this.subscriptions[name].indexOf(emitter);
if (index > -1) {
this.subscriptions[name].splice(index, 1);
}
};
/**
* Will give the previous hash for a block.
* @param {String} blockHash
* @param {Function} callback
*/
DB.prototype.getPrevHash = function(blockHash, callback) {
var blockIndex = this.node.services.bitcoind.getBlockIndex(blockHash);
setImmediate(function() {
if (blockIndex) {
callback(null, blockIndex.prevHash);
} else {
callback(new Error('Could not get prevHash, block not found'));
}
});
};
/**
* Connects a block to the database and add indexes
* @param {Block} block - The bitcore block
* @param {Function} callback
*/
DB.prototype.connectBlock = function(block, callback) {
log.debug('DB handling new chain block');
this.runAllBlockHandlers(block, true, callback);
};
/**
* Disconnects a block from the database and removes indexes
* @param {Block} block - The bitcore block
* @param {Function} callback
*/
DB.prototype.disconnectBlock = function(block, callback) {
log.debug('DB removing chain block');
this.runAllBlockHandlers(block, false, callback);
};
/**
* Will collect all database operations for a block from other services that implement
* `blockHandler` methods and then save operations to the database.
* @param {Block} block - The bitcore block
* @param {Boolean} add - If the block is being added/connected or removed/disconnected
* @param {Function} callback
*/
DB.prototype.runAllBlockHandlers = function(block, add, callback) {
var self = this;
var operations = [];
// Notify block subscribers
for (var i = 0; i < this.subscriptions.block.length; i++) {
this.subscriptions.block[i].emit('db/block', block.hash);
}
// Update tip
var tipHash = add ? new Buffer(block.hash, 'hex') : BufferUtil.reverse(block.header.prevHash);
operations.push({
type: 'put',
key: DB.PREFIXES.TIP,
value: tipHash
});
// Update block index
operations.push({
type: add ? 'put' : 'del',
key: this._encodeBlockIndexKey(block.header.timestamp),
value: this._encodeBlockIndexValue(block.hash)
});
async.eachSeries(
this.node.services,
function(mod, next) {
if(mod.blockHandler) {
$.checkArgument(typeof mod.blockHandler === 'function', 'blockHandler must be a function');
mod.blockHandler.call(mod, block, add, function(err, ops) {
if (err) {
return next(err);
}
if (ops) {
$.checkArgument(Array.isArray(ops), 'blockHandler for ' + mod.name + ' returned non-array');
operations = operations.concat(ops);
}
next();
});
} else {
setImmediate(next);
}
},
function(err) {
if (err) {
return callback(err);
}
log.debug('Updating the database with operations', operations);
self.store.batch(operations, callback);
}
);
};
DB.prototype._encodeBlockIndexKey = function(timestamp) {
$.checkArgument(timestamp >= 0 && timestamp <= 4294967295, 'timestamp out of bounds');
var timestampBuffer = new Buffer(4);
timestampBuffer.writeUInt32BE(timestamp);
return Buffer.concat([DB.PREFIXES.BLOCKS, timestampBuffer]);
};
DB.prototype._encodeBlockIndexValue = function(hash) {
return new Buffer(hash, 'hex');
};
DB.prototype._decodeBlockIndexValue = function(value) {
return value.toString('hex');
};
/**
* This function will find the common ancestor between the current chain and a forked block,
* by moving backwards on both chains until there is a meeting point.
* @param {Block} block - The new tip that forks the current chain.
* @param {Function} done - A callback function that is called when complete.
*/
DB.prototype.findCommonAncestor = function(block, done) {
var self = this;
var mainPosition = self.tip.hash;
var forkPosition = block.hash;
var mainHashesMap = {};
var forkHashesMap = {};
mainHashesMap[mainPosition] = true;
forkHashesMap[forkPosition] = true;
var commonAncestor = null;
async.whilst(
function() {
return !commonAncestor;
},
function(next) {
if(mainPosition) {
var mainBlockIndex = self.node.services.bitcoind.getBlockIndex(mainPosition);
if(mainBlockIndex && mainBlockIndex.prevHash) {
mainHashesMap[mainBlockIndex.prevHash] = true;
mainPosition = mainBlockIndex.prevHash;
} else {
mainPosition = null;
}
}
if(forkPosition) {
var forkBlockIndex = self.node.services.bitcoind.getBlockIndex(forkPosition);
if(forkBlockIndex && forkBlockIndex.prevHash) {
forkHashesMap[forkBlockIndex.prevHash] = true;
forkPosition = forkBlockIndex.prevHash;
} else {
forkPosition = null;
}
}
if(forkPosition && mainHashesMap[forkPosition]) {
commonAncestor = forkPosition;
}
if(mainPosition && forkHashesMap[mainPosition]) {
commonAncestor = mainPosition;
}
if(!mainPosition && !forkPosition) {
return next(new Error('Unknown common ancestor'));
}
setImmediate(next);
},
function(err) {
done(err, commonAncestor);
}
);
};
/**
* This function will attempt to rewind the chain to the common ancestor
* between the current chain and a forked block.
* @param {Block} block - The new tip that forks the current chain.
* @param {Function} done - A callback function that is called when complete.
*/
DB.prototype.syncRewind = function(block, done) {
var self = this;
self.findCommonAncestor(block, function(err, ancestorHash) {
if (err) {
return done(err);
}
log.warn('Reorg common ancestor found:', ancestorHash);
// Rewind the chain to the common ancestor
async.whilst(
function() {
// Wait until the tip equals the ancestor hash
return self.tip.hash !== ancestorHash;
},
function(removeDone) {
var tip = self.tip;
// TODO: expose prevHash as a string from bitcore
var prevHash = BufferUtil.reverse(tip.header.prevHash).toString('hex');
self.getBlock(prevHash, function(err, previousTip) {
if (err) {
removeDone(err);
}
// Undo the related indexes for this block
self.disconnectBlock(tip, function(err) {
if (err) {
return removeDone(err);
}
// Set the new tip
previousTip.__height = self.tip.__height - 1;
self.tip = previousTip;
self.emit('removeblock', tip);
removeDone();
});
});
}, done
);
});
};
/**
* This function will synchronize additional indexes for the chain based on
* the current active chain in the bitcoin daemon. In the event that there is
* a reorganization in the daemon, the chain will rewind to the last common
* ancestor and then resume syncing.
*/
DB.prototype.sync = function() {
var self = this;
if (self.bitcoindSyncing || self.node.stopping || !self.tip) {
return;
}
self.bitcoindSyncing = true;
var height;
async.whilst(function() {
height = self.tip.__height;
return height < self.node.services.bitcoind.height && !self.node.stopping;
}, function(done) {
self.node.services.bitcoind.getBlock(height + 1, function(err, blockBuffer) {
if (err) {
return done(err);
}
var block = Block.fromBuffer(blockBuffer);
// TODO: expose prevHash as a string from bitcore
var prevHash = BufferUtil.reverse(block.header.prevHash).toString('hex');
if (prevHash === self.tip.hash) {
// This block appends to the current chain tip and we can
// immediately add it to the chain and create indexes.
// Populate height
block.__height = self.tip.__height + 1;
// Create indexes
self.connectBlock(block, function(err) {
if (err) {
return done(err);
}
self.tip = block;
log.debug('Chain added block to main chain');
self.emit('addblock', block);
setImmediate(done);
});
} else {
// This block doesn't progress the current tip, so we'll attempt
// to rewind the chain to the common ancestor of the block and
// then we can resume syncing.
log.warn('Beginning reorg! Current tip: ' + self.tip.hash + '; New tip: ' + block.hash);
self.syncRewind(block, function(err) {
if(err) {
return done(err);
}
log.warn('Reorg complete. New tip is ' + self.tip.hash);
done();
});
}
});
}, function(err) {
if (err) {
Error.captureStackTrace(err);
return self.node.emit('error', err);
}
if(self.node.stopping) {
self.bitcoindSyncing = false;
return;
}
if (self.node.services.bitcoind.isSynced()) {
self.bitcoindSyncing = false;
self.node.emit('synced');
} else {
self.bitcoindSyncing = false;
}
});
};
module.exports = DB;

View File

@ -31,15 +31,8 @@
"bitcore-node": "./bin/bitcore-node"
},
"scripts": {
"install": "./bin/install",
"build": "./bin/build",
"clean": "./bin/clean",
"package": "node bin/package.js",
"upload": "node bin/upload.js",
"start": "node bin/start.js",
"test": "NODE_ENV=test mocha -R spec --recursive",
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive",
"libbitcoind": "node bin/start-libbitcoind.js"
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive"
},
"tags": [
"bitcoin",
@ -49,44 +42,28 @@
"async": "^1.3.0",
"bindings": "^1.2.1",
"bitcore-lib": "^0.13.13",
"bitcoind-rpc": "^0.3.0",
"body-parser": "^1.13.3",
"colors": "^1.1.2",
"commander": "^2.8.1",
"errno": "^0.1.4",
"express": "^4.13.3",
"leveldown": "bitpay/leveldown#bitpay-1.4.4",
"levelup": "^1.3.1",
"liftoff": "^2.2.0",
"memdown": "^1.0.0",
"mkdirp": "0.5.0",
"nan": "^2.0.9",
"npm": "^2.14.1",
"semver": "^5.0.1",
"socket.io": "bitpay/socket.io#bitpay-1.3.7",
"socket.io-client": "bitpay/socket.io-client#bitpay-1.3.7"
"socket.io-client": "bitpay/socket.io-client#bitpay-1.3.7",
"zmq": "^2.14.0"
},
"devDependencies": {
"aws-sdk": "~2.0.0-rc.15",
"benchmark": "1.0.0",
"bitcoin": "^2.3.2",
"bitcoind-rpc": "^0.3.0",
"chai": "^3.0.0",
"mocha": "~1.16.2",
"proxyquire": "^1.3.1",
"rimraf": "^2.4.2",
"sinon": "^1.15.4",
"bitcore-p2p": "~1.0.0"
"sinon": "^1.15.4"
},
"engines": {
"node": "^0.12 || ^4.2"
},
"os": [
"darwin",
"linux"
],
"cpu": [
"x64",
"arm"
],
"license": "MIT"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
#include "main.h"
#include "addrman.h"
#include "alert.h"
#include "base58.h"
#include "init.h"
#include "noui.h"
#include "rpcserver.h"
#include "txdb.h"
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include "nan.h"
#include "scheduler.h"
#include "core_io.h"
#include "script/bitcoinconsensus.h"
#include "consensus/validation.h"
#ifdef ENABLE_WALLET
#include "wallet/wallet.h"
#endif