bitcoind: bitcoind service using rpc and zmq with address index
This commit is contained in:
parent
07c317df80
commit
7e70bbfa7d
|
@ -1 +0,0 @@
|
|||
v0.11.2
|
|
@ -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
187
bin/build
|
@ -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
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
cd "${root_dir}"
|
||||
pushd "${root_dir}"/libbitcoind
|
||||
make clean
|
||||
popd
|
||||
node-gyp clean
|
||||
rm -fr cache/*
|
|
@ -1,2 +0,0 @@
|
|||
./configure --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc
|
||||
|
|
@ -1,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;
|
36
bin/install
36
bin/install
|
@ -1,36 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
|
||||
cd "${root_dir}"
|
||||
|
||||
tarball_name=`node bin/get-tarball-name.js`
|
||||
bucket_name="bitcore-node"
|
||||
binary_url="https://${bucket_name}.s3.amazonaws.com/${tarball_name}"
|
||||
|
||||
echo "Downloading binary: ${binary_url}"
|
||||
|
||||
is_curl=true
|
||||
if hash curl 2>/dev/null; then
|
||||
curl --fail -I $binary_url >/dev/null 2>&1
|
||||
else
|
||||
is_curl=false
|
||||
wget --server-response --spider $binary_url >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
if test $? -eq 0; then
|
||||
if [ "${is_curl}" = true ]; then
|
||||
curl $binary_url > $tarball_name
|
||||
else
|
||||
wget $binary_url
|
||||
fi
|
||||
if test -e "${tarball_name}"; then
|
||||
echo "Unpacking binary distribution"
|
||||
tar -xvzf $tarball_name
|
||||
if test $? -eq 0; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "Prebuild binary could not be downloaded, building from source..."
|
||||
./bin/build
|
|
@ -1,58 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
var bindings = require('bindings');
|
||||
var index = require('../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);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||
#root_dir="$(readlink -f "$(dirname "$0")")/.."
|
||||
cd "$root_dir"
|
||||
dir=$(test -n "$1" && echo "$1" || echo "${HOME}/bitcoin")
|
||||
patch_file="$(pwd)/etc/bitcoin.patch"
|
||||
|
||||
cd "$dir" || exit 1
|
||||
|
||||
if ! test -d .git; then
|
||||
echo 'Please point this script to an upstream bitcoin git repo.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test $? -ne 0; then
|
||||
echo 'Unable to checkout necessary commit.'
|
||||
echo 'Please pull the latest HEAD from the upstream bitcoin repo.'
|
||||
exit 1
|
||||
fi
|
||||
git checkout -b "libbitcoind-$(date '+%Y.%m.%d')" || exit 1
|
||||
|
||||
patch -p1 < "$patch_file" || exit 1
|
||||
|
||||
git add --all || exit 1
|
||||
|
||||
[ -n "$( git config user.name )" ] \
|
||||
|| git config user.name 'Bitcore Build'
|
||||
|
||||
[ -n "$( git config user.email )" ] \
|
||||
|| git config user.email "$( id -n -u )@$( hostname -f )"
|
||||
|
||||
git commit -a -m 'allow compiling of libbitcoind.so.' || exit 1
|
||||
|
||||
echo 'Patch completed successfully.'
|
||||
exit 0
|
|
@ -1,61 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var index = require('..');
|
||||
var log = index.log;
|
||||
|
||||
process.title = 'libbitcoind';
|
||||
|
||||
/**
|
||||
* daemon
|
||||
*/
|
||||
var daemon = require('../').services.Bitcoin({
|
||||
node: {
|
||||
datadir: process.env.BITCORENODE_DIR || process.env.HOME + '/.bitcoin',
|
||||
network: {
|
||||
name: process.env.BITCORENODE_NETWORK || 'livenet'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
daemon.start(function() {
|
||||
log.info('ready');
|
||||
});
|
||||
|
||||
daemon.on('error', function(err) {
|
||||
log.info('error="%s"', err.message);
|
||||
});
|
||||
|
||||
daemon.on('open', function(status) {
|
||||
log.info('status="%s"', status);
|
||||
});
|
||||
|
||||
function exitHandler(options, err) {
|
||||
log.info('Stopping daemon');
|
||||
if (err) {
|
||||
log.error('uncaught exception:', err);
|
||||
if(err.stack) {
|
||||
console.log(err.stack);
|
||||
}
|
||||
process.exit(-1);
|
||||
}
|
||||
if (options.sigint) {
|
||||
daemon.stop(function(err) {
|
||||
if(err) {
|
||||
log.error('Failed to stop services: ' + err);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
log.info('Halted');
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//catches uncaught exceptions
|
||||
|
||||
|
||||
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
|
||||
//catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {sigint:true}));
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var start = require('../lib/scaffold/start');
|
||||
var defaultConfig = require('../lib/scaffold/default-config');
|
||||
|
||||
start(defaultConfig());
|
|
@ -1,52 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var AWS = require('aws-sdk');
|
||||
var bindings = require('bindings');
|
||||
var index = require('../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);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
182
bin/variables.sh
182
bin/variables.sh
|
@ -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
|
59
binding.gyp
59
binding.gyp
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "libbitcoind",
|
||||
"include_dirs" : [
|
||||
"<!(node -e \"require('nan')\")",
|
||||
"<!(./bin/variables.sh cache_dir)/src",
|
||||
"<!(./bin/variables.sh cache_dir)/depends/<!(./bin/variables.sh host)/include",
|
||||
"<!(./bin/variables.sh cache_dir)/src/leveldb/include"
|
||||
],
|
||||
"sources": [
|
||||
"./src/libbitcoind.cc",
|
||||
],
|
||||
"conditions": [
|
||||
[
|
||||
"OS==\"mac\"",
|
||||
{
|
||||
"xcode_settings": {
|
||||
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
||||
"GCC_ENABLE_CPP_RTTI": "YES",
|
||||
"MACOSX_DEPLOYMENT_TARGET": "10.9",
|
||||
'OTHER_CFLAGS': [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-fpermissive",
|
||||
"<!(./bin/variables.sh wallet_enabled)",
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"cflags_cc": [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
"-fpermissive",
|
||||
"<!(./bin/variables.sh wallet_enabled)",
|
||||
],
|
||||
"link_settings": {
|
||||
"libraries": [
|
||||
"<!(./bin/variables.sh bitcoind)",
|
||||
"<!(./bin/variables.sh filesystem)",
|
||||
"<!(./bin/variables.sh thread)",
|
||||
"<!(./bin/variables.sh program_options)",
|
||||
"<!(./bin/variables.sh system)",
|
||||
"<!(./bin/variables.sh chrono)",
|
||||
"<!(./bin/variables.sh libsecp256k1)",
|
||||
"<!(./bin/variables.sh leveldb)",
|
||||
"<!(./bin/variables.sh memenv)",
|
||||
"<!(./bin/variables.sh bdb)",
|
||||
"<!(./bin/variables.sh anl)",
|
||||
"<!(./bin/variables.sh ssl)"
|
||||
],
|
||||
"ldflags": [
|
||||
"<!(./bin/variables.sh load_archive)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
patch_sha.txt
|
||||
src/*
|
||||
depends/*
|
|
@ -1,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));
|
|
@ -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']);
|
13
index.js
13
index.js
|
@ -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 = {};
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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;
|
|
@ -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
|
@ -1,40 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var Transform = require('stream').Transform;
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var encodingUtil = require('../encoding');
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
function InputsTransformStream(options) {
|
||||
$.checkArgument(options.address instanceof bitcore.Address);
|
||||
Transform.call(this, {
|
||||
objectMode: true
|
||||
});
|
||||
this._address = options.address;
|
||||
this._addressStr = this._address.toString();
|
||||
this._tipHeight = options.tipHeight;
|
||||
}
|
||||
inherits(InputsTransformStream, Transform);
|
||||
|
||||
InputsTransformStream.prototype._transform = function(chunk, encoding, callback) {
|
||||
var self = this;
|
||||
|
||||
var key = encodingUtil.decodeInputKey(chunk.key);
|
||||
var value = encodingUtil.decodeInputValue(chunk.value);
|
||||
|
||||
var input = {
|
||||
address: this._addressStr,
|
||||
hashType: this._address.type,
|
||||
txid: value.txid.toString('hex'),
|
||||
inputIndex: value.inputIndex,
|
||||
height: key.height,
|
||||
confirmations: this._tipHeight - key.height + 1
|
||||
};
|
||||
|
||||
self.push(input);
|
||||
callback();
|
||||
|
||||
};
|
||||
|
||||
module.exports = InputsTransformStream;
|
|
@ -1,42 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var Transform = require('stream').Transform;
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore-lib');
|
||||
var encodingUtil = require('../encoding');
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
function OutputsTransformStream(options) {
|
||||
Transform.call(this, {
|
||||
objectMode: true
|
||||
});
|
||||
$.checkArgument(options.address instanceof bitcore.Address);
|
||||
this._address = options.address;
|
||||
this._addressStr = this._address.toString();
|
||||
this._tipHeight = options.tipHeight;
|
||||
}
|
||||
inherits(OutputsTransformStream, Transform);
|
||||
|
||||
OutputsTransformStream.prototype._transform = function(chunk, encoding, callback) {
|
||||
var self = this;
|
||||
|
||||
var key = encodingUtil.decodeOutputKey(chunk.key);
|
||||
var value = encodingUtil.decodeOutputValue(chunk.value);
|
||||
|
||||
var output = {
|
||||
address: this._addressStr,
|
||||
hashType: this._address.type,
|
||||
txid: key.txid.toString('hex'), //TODO use a buffer
|
||||
outputIndex: key.outputIndex,
|
||||
height: key.height,
|
||||
satoshis: value.satoshis,
|
||||
script: value.scriptBuffer.toString('hex'), //TODO use a buffer
|
||||
confirmations: this._tipHeight - key.height + 1
|
||||
};
|
||||
|
||||
self.push(output);
|
||||
callback();
|
||||
|
||||
};
|
||||
|
||||
module.exports = OutputsTransformStream;
|
|
@ -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.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) {
|
||||
return callback(err);
|
||||
}
|
||||
// Wait until the block chain is ready
|
||||
bindings.onBlocksReady(function(err, result) {
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
var percentSynced = bindings.syncPercentage();
|
||||
log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced);
|
||||
if (percentSynced >= 100) {
|
||||
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);
|
||||
|
||||
}
|
||||
else {
|
||||
} 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 (this.process) {
|
||||
this.process.once('exit', function(err, status) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
} else {
|
||||
log.info(status);
|
||||
return callback();
|
||||
}
|
||||
});
|
||||
this.process.kill('SIGHUP');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Bitcoin;
|
||||
|
|
|
@ -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;
|
33
package.json
33
package.json
|
@ -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"
|
||||
}
|
||||
|
|
1614
src/libbitcoind.cc
1614
src/libbitcoind.cc
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +0,0 @@
|
|||
#include "main.h"
|
||||
#include "addrman.h"
|
||||
#include "alert.h"
|
||||
#include "base58.h"
|
||||
#include "init.h"
|
||||
#include "noui.h"
|
||||
#include "rpcserver.h"
|
||||
#include "txdb.h"
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "nan.h"
|
||||
#include "scheduler.h"
|
||||
#include "core_io.h"
|
||||
#include "script/bitcoinconsensus.h"
|
||||
#include "consensus/validation.h"
|
||||
#ifdef ENABLE_WALLET
|
||||
#include "wallet/wallet.h"
|
||||
#endif
|
Loading…
Reference in New Issue