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';
|
'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 = require('./lib');
|
||||||
module.exports.nodeVersionCheck = nodeVersionCheck;
|
|
||||||
module.exports.Node = require('./lib/node');
|
module.exports.Node = require('./lib/node');
|
||||||
module.exports.Transaction = require('./lib/transaction');
|
module.exports.Transaction = require('./lib/transaction');
|
||||||
module.exports.Service = require('./lib/service');
|
module.exports.Service = require('./lib/service');
|
||||||
module.exports.errors = require('./lib/errors');
|
module.exports.errors = require('./lib/errors');
|
||||||
|
|
||||||
module.exports.services = {};
|
module.exports.services = {};
|
||||||
module.exports.services.Address = require('./lib/services/address');
|
|
||||||
module.exports.services.Bitcoin = require('./lib/services/bitcoind');
|
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.services.Web = require('./lib/services/web');
|
||||||
|
|
||||||
module.exports.scaffold = {};
|
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';
|
'use strict';
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var bindings = require('bindings')('bitcoind.node');
|
|
||||||
var mkdirp = require('mkdirp');
|
var mkdirp = require('mkdirp');
|
||||||
var bitcore = require('bitcore-lib');
|
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.util.preconditions;
|
||||||
|
var _ = bitcore.deps._;
|
||||||
|
|
||||||
var index = require('../');
|
var index = require('../');
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
|
var errors = index.errors;
|
||||||
var Service = require('../service');
|
var Service = require('../service');
|
||||||
|
var Transaction = require('../transaction');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an interface to native bindings to [Bitcoin Core](https://github.com/bitcoin/bitcoin)
|
* 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.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() {
|
Bitcoin.prototype._loadConfiguration = function() {
|
||||||
/* jshint maxstatements: 25 */
|
/* jshint maxstatements: 25 */
|
||||||
|
@ -44,16 +74,6 @@ Bitcoin.prototype._loadConfiguration = function() {
|
||||||
mkdirp.sync(this.node.datadir);
|
mkdirp.sync(this.node.datadir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(configPath)) {
|
|
||||||
var defaultConfig = Bitcoin.DEFAULT_CONFIG;
|
|
||||||
if(this.node.https && this.node.httpsOptions) {
|
|
||||||
defaultConfig += 'rpcssl=1\n';
|
|
||||||
defaultConfig += 'rpcsslprivatekeyfile=' + this.node.httpsOptions.key + '\n';
|
|
||||||
defaultConfig += 'rpcsslcertificatechainfile=' + this.node.httpsOptions.cert + '\n';
|
|
||||||
}
|
|
||||||
fs.writeFileSync(configPath, defaultConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = fs.readFileSync(configPath);
|
var file = fs.readFileSync(configPath);
|
||||||
var unparsed = file.toString().split('\n');
|
var unparsed = file.toString().split('\n');
|
||||||
for(var i = 0; i < unparsed.length; i++) {
|
for(var i = 0; i < unparsed.length; i++) {
|
||||||
|
@ -72,11 +92,36 @@ Bitcoin.prototype._loadConfiguration = function() {
|
||||||
|
|
||||||
$.checkState(
|
$.checkState(
|
||||||
this.configuration.txindex && this.configuration.txindex === 1,
|
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 ' +
|
'Please add "txindex=1" to your configuration and reindex an existing database if ' +
|
||||||
'necessary with reindex=1'
|
'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) {
|
if (this.configuration.reindex && this.configuration.reindex === 1) {
|
||||||
log.warn('Reindex option is currently enabled. This means that bitcoind is undergoing a reindex. ' +
|
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 ' +
|
'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() {
|
Bitcoin.prototype._registerEventHandlers = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Set the height and emit a new tip
|
this.zmqSubSocket.subscribe('hashblock');
|
||||||
bindings.onTipUpdate(self._onTipUpdate.bind(this));
|
this.zmqSubSocket.subscribe('hashtx');
|
||||||
|
|
||||||
// Register callback function to handle transactions entering the mempool
|
this.zmqSubSocket.on('message', function(topic, message) {
|
||||||
bindings.startTxMon(function(txs) {
|
var topicString = topic.toString('utf8');
|
||||||
for(var i = 0; i < txs.length; i++) {
|
if (topicString === 'hashtx') {
|
||||||
self.emit('tx', txs[i]);
|
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) {
|
Bitcoin.prototype._onReady = function(result, callback) {
|
||||||
|
@ -130,19 +171,40 @@ Bitcoin.prototype._onReady = function(result, callback) {
|
||||||
|
|
||||||
self._registerEventHandlers();
|
self._registerEventHandlers();
|
||||||
|
|
||||||
var info = self.getInfo();
|
self.client.getInfo(function(err, response) {
|
||||||
self.height = info.blocks;
|
|
||||||
|
|
||||||
self.getBlock(0, function(err, block) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
self.genesisBuffer = block;
|
self.height = response.result.blocks;
|
||||||
self.emit('ready', result);
|
|
||||||
log.info('Bitcoin Daemon Ready');
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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) {
|
Bitcoin.prototype.start = function(callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._loadConfiguration();
|
self._loadConfiguration();
|
||||||
|
|
||||||
var networkName = this.node.network.name;
|
var options = [
|
||||||
if (this.node.network.regtestEnabled) {
|
'--conf=' + path.resolve(this.node.datadir, './bitcoin.conf'),
|
||||||
networkName = 'regtest';
|
'--datadir=' + this.node.datadir,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (self._getNetworkOption()) {
|
||||||
|
options.push(self._getNetworkOption());
|
||||||
}
|
}
|
||||||
|
|
||||||
bindings.start({
|
self.process = spawn('bitcoind', options, {stdio: 'inherit'});
|
||||||
datadir: this.node.datadir,
|
|
||||||
network: networkName
|
self.process.on('error', function(err) {
|
||||||
}, function(err) {
|
log.error(err);
|
||||||
if(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);
|
return callback(err);
|
||||||
}
|
}
|
||||||
// Wait until the block chain is ready
|
|
||||||
bindings.onBlocksReady(function(err, result) {
|
self.zmqSubSocket = zmq.socket('sub');
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
self.zmqSubSocket.on('monitor_error', function(err) {
|
||||||
}
|
log.error('Error in monitoring: %s, will restart monitoring in 5 seconds', err);
|
||||||
if (self._reindex) {
|
setTimeout(function() {
|
||||||
var interval = setInterval(function() {
|
self.zmqSubSocket.monitor(500, 0);
|
||||||
var percentSynced = bindings.syncPercentage();
|
}, 5000);
|
||||||
log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced);
|
});
|
||||||
if (percentSynced >= 100) {
|
|
||||||
|
self.zmqSubSocket.monitor(500, 0);
|
||||||
|
self.zmqSubSocket.connect(self.configuration.zmqpubhashtx);
|
||||||
|
|
||||||
|
if (self._reindex) {
|
||||||
|
var interval = setInterval(function() {
|
||||||
|
self.syncPercentage(function(err, percentSynced) {
|
||||||
|
if (err) {
|
||||||
|
return log.error(err);
|
||||||
|
}
|
||||||
|
log.info('Bitcoin Core Daemon Reindex Percentage: ' + percentSynced.toFixed(2));
|
||||||
|
if (Math.round(percentSynced) >= 100) {
|
||||||
self._reindex = false;
|
self._reindex = false;
|
||||||
self._onReady(result, callback);
|
self._onReady(result, callback);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
}, self._reindexWait);
|
});
|
||||||
|
}, self._reindexWait);
|
||||||
|
|
||||||
}
|
} else {
|
||||||
else {
|
self._onReady(result, callback);
|
||||||
self._onReady(result, callback);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to determine the state of the database.
|
* Helper to determine the state of the database.
|
||||||
|
* @param {Function} callback
|
||||||
* @returns {Boolean} If the database is fully synced
|
* @returns {Boolean} If the database is fully synced
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.isSynced = function() {
|
Bitcoin.prototype.isSynced = function(callback) {
|
||||||
return bindings.isSynced();
|
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.
|
* Helper to determine the progress of the database.
|
||||||
|
* @param {Function} callback
|
||||||
* @returns {Number} An estimated percentage of the syncronization status
|
* @returns {Number} An estimated percentage of the syncronization status
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.syncPercentage = function() {
|
Bitcoin.prototype.syncPercentage = function(callback) {
|
||||||
return bindings.syncPercentage();
|
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
|
* @param {String|Number} block - A block hash or block height number
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getBlock = function(block, callback) {
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
Bitcoin.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
|
||||||
* Will return the spent status of an output (not including the mempool)
|
var self = this;
|
||||||
* @param {String} txid - The transaction hash
|
self.client.getBlockHashes(high, low, function(err, response) {
|
||||||
* @param {Number} outputIndex - The output index in the transaction
|
if (err) {
|
||||||
* @returns {Boolean} If the output has been spent
|
return callback(err);
|
||||||
*/
|
}
|
||||||
Bitcoin.prototype.isSpent = function(txid, outputIndex) {
|
callback(null, response.result);
|
||||||
return bindings.isSpent(txid, outputIndex);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will return the block index information, the output will have the format:
|
* Will return the block index information, the output will have the format:
|
||||||
* {
|
* {
|
||||||
* prevHash: '7194fcf33f58c96720f88f21ab28c34ebc5638c5f88d7838517deb27313b59de',
|
* prevHash: '000000004956cc2edd1a8caa05eacfa3c69f4c490bfc9ace820257834115ab35',
|
||||||
* hash: '7c5caf0af1bf16e3467b275a3b408bc1d251bff3c25be20cb727c47b66a7b216',
|
* nextHash: '0000000000629d100db387f37d0f37c51118f250fb0946310a8c37316cbc4028'
|
||||||
|
* hash: ' 00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e',
|
||||||
* chainWork: '0000000000000000000000000000000000000000000000000000000000000016',
|
* chainWork: '0000000000000000000000000000000000000000000000000000000000000016',
|
||||||
* height: 10
|
* height: 10
|
||||||
* }
|
* }
|
||||||
* @param {String|Number} block - A block hash or block height
|
* @param {String|Number} block - A block hash or block height
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getBlockIndex = function(block) {
|
Bitcoin.prototype.getBlockHeader = function(block, callback) {
|
||||||
return bindings.getBlockIndex(block);
|
// TODO keep a cache of queries
|
||||||
};
|
var self = this;
|
||||||
|
|
||||||
/**
|
function queryHeader(blockhash) {
|
||||||
* Will return if the block is a part of the main chain.
|
self.client.getBlockHeader(blockhash, function(err, response) {
|
||||||
* @param {String} blockHash
|
if (err) {
|
||||||
* @returns {Boolean}
|
return callback(err);
|
||||||
*/
|
}
|
||||||
Bitcoin.prototype.isMainChain = function(blockHash) {
|
callback(null, response.result);
|
||||||
return bindings.isMainChain(blockHash);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
* @param {Number} blocks - The number of blocks for the transaction to be confirmed.
|
||||||
* @returns {Number}
|
* @returns {Number}
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.estimateFee = function(blocks) {
|
Bitcoin.prototype.estimateFee = function(blocks, callback) {
|
||||||
return bindings.estimateFee(blocks);
|
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 {String} transaction - The hex string of the transaction
|
||||||
* @param {Boolean} allowAbsurdFees - Enable large fees
|
* @param {Boolean} allowAbsurdFees - Enable large fees
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.sendTransaction = function(transaction, allowAbsurdFees) {
|
Bitcoin.prototype.sendTransaction = function(tx, allowAbsurdFees, callback) {
|
||||||
return bindings.sendTransaction(transaction, allowAbsurdFees);
|
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
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getTransaction = function(txid, queryMempool, 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
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
|
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
|
||||||
return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback);
|
// 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) {
|
||||||
* Will return the entire mempool as an Array of transaction Buffers.
|
return callback(err);
|
||||||
* @returns {Array}
|
}
|
||||||
*/
|
if (!response.result) {
|
||||||
Bitcoin.prototype.getMempoolTransactions = function() {
|
return callback(new errors.Transaction.NotFound());
|
||||||
return bindings.getMempoolTransactions();
|
}
|
||||||
};
|
var tx = Transaction();
|
||||||
|
tx.fromString(response.result.hex);
|
||||||
/**
|
tx.__blockHash = response.result.blockhash;
|
||||||
* Will add a transaction to the mempool without any validation. This is used
|
tx.__height = response.result.height;
|
||||||
* exclusively for testing purposes.
|
tx.__timestamp = response.result.time;
|
||||||
* @param {String} transaction - The hex string for the transaction
|
callback(null, tx);
|
||||||
*/
|
});
|
||||||
Bitcoin.prototype.addMempoolUncheckedTransaction = function(transaction) {
|
|
||||||
return bindings.addMempoolUncheckedTransaction(transaction);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will get the best block hash for the chain.
|
* Will get the best block hash for the chain.
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getBestBlockHash = function() {
|
Bitcoin.prototype.getBestBlockHash = function(callback) {
|
||||||
return bindings.getBestBlockHash();
|
// TODO keep an LRU cache available of transactions
|
||||||
|
this.client.getBestBlockHash(function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, response.result);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
Bitcoin.prototype.getInputForOutput = function(txid, index, options, callback) {
|
||||||
* Will get the next block hash for a block hash.
|
// TODO
|
||||||
* @param {String} hash - The starting block hash
|
setImmediate(callback);
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
Bitcoin.prototype.getNextBlockHash = function(hash) {
|
|
||||||
return bindings.getNextBlockHash(hash);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -341,8 +761,13 @@ Bitcoin.prototype.getNextBlockHash = function(hash) {
|
||||||
* errors: ''
|
* errors: ''
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getInfo = function() {
|
Bitcoin.prototype.getInfo = function(callback) {
|
||||||
return bindings.getInfo();
|
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
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.stop = function(callback) {
|
Bitcoin.prototype.stop = function(callback) {
|
||||||
return bindings.stop(function(err, status) {
|
if (this.process) {
|
||||||
if (err) {
|
this.process.once('exit', function(err, status) {
|
||||||
return callback(err);
|
if (err) {
|
||||||
} else {
|
return callback(err);
|
||||||
log.info(status);
|
} else {
|
||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.process.kill('SIGHUP');
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Bitcoin;
|
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"
|
"bitcore-node": "./bin/bitcore-node"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"test": "NODE_ENV=test mocha -R spec --recursive",
|
||||||
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive",
|
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive"
|
||||||
"libbitcoind": "node bin/start-libbitcoind.js"
|
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
|
@ -49,44 +42,28 @@
|
||||||
"async": "^1.3.0",
|
"async": "^1.3.0",
|
||||||
"bindings": "^1.2.1",
|
"bindings": "^1.2.1",
|
||||||
"bitcore-lib": "^0.13.13",
|
"bitcore-lib": "^0.13.13",
|
||||||
|
"bitcoind-rpc": "^0.3.0",
|
||||||
"body-parser": "^1.13.3",
|
"body-parser": "^1.13.3",
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"commander": "^2.8.1",
|
"commander": "^2.8.1",
|
||||||
"errno": "^0.1.4",
|
"errno": "^0.1.4",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"leveldown": "bitpay/leveldown#bitpay-1.4.4",
|
|
||||||
"levelup": "^1.3.1",
|
|
||||||
"liftoff": "^2.2.0",
|
"liftoff": "^2.2.0",
|
||||||
"memdown": "^1.0.0",
|
"memdown": "^1.0.0",
|
||||||
"mkdirp": "0.5.0",
|
"mkdirp": "0.5.0",
|
||||||
"nan": "^2.0.9",
|
|
||||||
"npm": "^2.14.1",
|
"npm": "^2.14.1",
|
||||||
"semver": "^5.0.1",
|
"semver": "^5.0.1",
|
||||||
"socket.io": "bitpay/socket.io#bitpay-1.3.7",
|
"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": {
|
"devDependencies": {
|
||||||
"aws-sdk": "~2.0.0-rc.15",
|
|
||||||
"benchmark": "1.0.0",
|
"benchmark": "1.0.0",
|
||||||
"bitcoin": "^2.3.2",
|
|
||||||
"bitcoind-rpc": "^0.3.0",
|
|
||||||
"chai": "^3.0.0",
|
"chai": "^3.0.0",
|
||||||
"mocha": "~1.16.2",
|
"mocha": "~1.16.2",
|
||||||
"proxyquire": "^1.3.1",
|
"proxyquire": "^1.3.1",
|
||||||
"rimraf": "^2.4.2",
|
"rimraf": "^2.4.2",
|
||||||
"sinon": "^1.15.4",
|
"sinon": "^1.15.4"
|
||||||
"bitcore-p2p": "~1.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
|
||||||
"node": "^0.12 || ^4.2"
|
|
||||||
},
|
|
||||||
"os": [
|
|
||||||
"darwin",
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64",
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
"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