diff --git a/PATCH_VERSION b/PATCH_VERSION deleted file mode 100644 index 4b8f7b07..00000000 --- a/PATCH_VERSION +++ /dev/null @@ -1 +0,0 @@ -v0.11.2 diff --git a/benchmarks/blockhandler.js b/benchmarks/blockhandler.js deleted file mode 100644 index 3b46e750..00000000 --- a/benchmarks/blockhandler.js +++ /dev/null @@ -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(); -}); diff --git a/bin/build b/bin/build deleted file mode 100755 index 8c0fbe64..00000000 --- a/bin/build +++ /dev/null @@ -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 diff --git a/bin/clean b/bin/clean deleted file mode 100755 index 414df579..00000000 --- a/bin/clean +++ /dev/null @@ -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/* diff --git a/bin/config_options.sh b/bin/config_options.sh deleted file mode 100644 index d51d99a0..00000000 --- a/bin/config_options.sh +++ /dev/null @@ -1,2 +0,0 @@ -./configure --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils - diff --git a/bin/config_options_debug.sh b/bin/config_options_debug.sh deleted file mode 100644 index fb5845e0..00000000 --- a/bin/config_options_debug.sh +++ /dev/null @@ -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 - diff --git a/bin/config_options_test.sh b/bin/config_options_test.sh deleted file mode 100644 index 3ee83701..00000000 --- a/bin/config_options_test.sh +++ /dev/null @@ -1,2 +0,0 @@ -./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc - diff --git a/bin/get-tarball-name.js b/bin/get-tarball-name.js deleted file mode 100644 index c7ea0ffb..00000000 --- a/bin/get-tarball-name.js +++ /dev/null @@ -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; diff --git a/bin/install b/bin/install deleted file mode 100755 index c6e3a916..00000000 --- a/bin/install +++ /dev/null @@ -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 diff --git a/bin/package.js b/bin/package.js deleted file mode 100644 index c03639bd..00000000 --- a/bin/package.js +++ /dev/null @@ -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); - } - - }); - -}); diff --git a/bin/patch-bitcoin b/bin/patch-bitcoin deleted file mode 100755 index aeaab6b1..00000000 --- a/bin/patch-bitcoin +++ /dev/null @@ -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 diff --git a/bin/start-libbitcoind.js b/bin/start-libbitcoind.js deleted file mode 100644 index e2f1c637..00000000 --- a/bin/start-libbitcoind.js +++ /dev/null @@ -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})); diff --git a/bin/start.js b/bin/start.js deleted file mode 100755 index 6fda5aff..00000000 --- a/bin/start.js +++ /dev/null @@ -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()); diff --git a/bin/upload.js b/bin/upload.js deleted file mode 100644 index 4376bb20..00000000 --- a/bin/upload.js +++ /dev/null @@ -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); - } - }); - -}); diff --git a/bin/variables.sh b/bin/variables.sh deleted file mode 100755 index 0988e0fa..00000000 --- a/bin/variables.sh +++ /dev/null @@ -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 diff --git a/binding.gyp b/binding.gyp deleted file mode 100644 index 3db1adb1..00000000 --- a/binding.gyp +++ /dev/null @@ -1,59 +0,0 @@ -{ - "targets": [ - { - "target_name": "libbitcoind", - "include_dirs" : [ - "\"$($(package)_cxxflags) $($(package)_cppflags)\" \"$($(package)_ldflags)\" \"$(boost_archiver_$(host_os))\" \"$(host_STRIP)\" \"$(host_RANLIB)\" \"$(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 -+#include -+#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 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()), 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 GetHeight; - boost::signals2::signal ProcessMessages; -+ boost::signals2::signal TxToMemPool; -+ boost::signals2::signal TxLeaveMemPool; - boost::signals2::signal SendMessages; - boost::signals2::signal InitializeNode; - boost::signals2::signal 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& 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::iterator it = mapNextTx.find(COutPoint(hash, i)); diff --git a/example/client.js b/example/client.js deleted file mode 100644 index ded2b61e..00000000 --- a/example/client.js +++ /dev/null @@ -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']); \ No newline at end of file diff --git a/index.js b/index.js index 4b2aa56a..0a210849 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,13 @@ 'use strict'; -var semver = require('semver'); -var packageData = require('./package.json'); - -function nodeVersionCheck(version, expected) { - if (!semver.satisfies(version, expected)) { - throw new Error('Node.js version ' + version + ' is expected to be ' + expected); - } -} -nodeVersionCheck(process.versions.node, packageData.engines.node); - module.exports = require('./lib'); -module.exports.nodeVersionCheck = nodeVersionCheck; module.exports.Node = require('./lib/node'); module.exports.Transaction = require('./lib/transaction'); module.exports.Service = require('./lib/service'); module.exports.errors = require('./lib/errors'); module.exports.services = {}; -module.exports.services.Address = require('./lib/services/address'); module.exports.services.Bitcoin = require('./lib/services/bitcoind'); -module.exports.services.DB = require('./lib/services/db'); module.exports.services.Web = require('./lib/services/web'); module.exports.scaffold = {}; diff --git a/lib/services/address/constants.js b/lib/services/address/constants.js deleted file mode 100644 index 3653e9cb..00000000 --- a/lib/services/address/constants.js +++ /dev/null @@ -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; - diff --git a/lib/services/address/encoding.js b/lib/services/address/encoding.js deleted file mode 100644 index 8ebce51c..00000000 --- a/lib/services/address/encoding.js +++ /dev/null @@ -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; diff --git a/lib/services/address/history.js b/lib/services/address/history.js deleted file mode 100644 index 88b5c96b..00000000 --- a/lib/services/address/history.js +++ /dev/null @@ -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; diff --git a/lib/services/address/index.js b/lib/services/address/index.js deleted file mode 100644 index eef4ed07..00000000 --- a/lib/services/address/index.js +++ /dev/null @@ -1,1611 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var BaseService = require('../../service'); -var inherits = require('util').inherits; -var async = require('async'); -var mkdirp = require('mkdirp'); -var index = require('../../'); -var log = index.log; -var errors = index.errors; -var bitcore = require('bitcore-lib'); -var Networks = bitcore.Networks; -var levelup = require('levelup'); -var leveldown = require('leveldown'); -var memdown = require('memdown'); -var $ = bitcore.util.preconditions; -var _ = bitcore.deps._; -var Hash = bitcore.crypto.Hash; -var EventEmitter = require('events').EventEmitter; -var Address = bitcore.Address; -var AddressHistory = require('./history'); -var constants = require('./constants'); -var encoding = require('./encoding'); -var InputsTransformStream = require('./streams/inputs-transform'); -var OutputsTransformStream = require('./streams/outputs-transform'); - - -/** - * The Address Service builds upon the Database Service and the Bitcoin Service to add additional - * functionality for getting information by base58check encoded addresses. This includes getting the - * balance for an address, the history for a collection of addresses, and unspent outputs for - * constructing transactions. This is typically the core functionality for building a wallet. - * @param {Object} options - * @param {Node} options.node - An instance of the node - * @param {String} options.name - An optional name of the service - */ -var AddressService = function(options) { - BaseService.call(this, options); - - this.subscriptions = {}; - this.subscriptions['address/transaction'] = {}; - this.subscriptions['address/balance'] = {}; - - this._bitcoindTransactionListener = this.transactionHandler.bind(this); - this._bitcoindTransactionLeaveListener = this.transactionLeaveHandler.bind(this); - this.node.services.bitcoind.on('tx', this._bitcoindTransactionListener); - this.node.services.bitcoind.on('txleave', this._bitcoindTransactionLeaveListener); - - this.maxInputsQueryLength = options.maxInputsQueryLength || constants.MAX_INPUTS_QUERY_LENGTH; - this.maxOutputsQueryLength = options.maxOutputsQueryLength || constants.MAX_OUTPUTS_QUERY_LENGTH; - - this._setMempoolIndexPath(); - if (options.mempoolMemoryIndex) { - this.levelupStore = memdown; - } else { - this.levelupStore = leveldown; - } - this.mempoolIndex = null; // Used for larger mempool indexes - this.mempoolSpentIndex = {}; // Used for small quick synchronous lookups - this.mempoolAddressIndex = {}; // Used to check if an address is on the spend pool -}; - -inherits(AddressService, BaseService); - -AddressService.dependencies = [ - 'bitcoind', - 'db' -]; - -AddressService.prototype.start = function(callback) { - var self = this; - - async.series([ - - function(next) { - // Flush any existing mempool index - if (fs.existsSync(self.mempoolIndexPath)) { - leveldown.destroy(self.mempoolIndexPath, next); - } else { - setImmediate(next); - } - }, - function(next) { - // Setup new mempool index - if (!fs.existsSync(self.mempoolIndexPath)) { - mkdirp(self.mempoolIndexPath, next); - } else { - setImmediate(next); - } - }, - function(next) { - self.mempoolIndex = levelup( - self.mempoolIndexPath, - { - db: self.levelupStore, - keyEncoding: 'binary', - valueEncoding: 'binary', - fillCache: false, - maxOpenFiles: 200 - }, - next - ); - } - ], callback); - -}; - -AddressService.prototype.stop = function(callback) { - // TODO Keep track of ongoing db requests before shutting down - this.node.services.bitcoind.removeListener('tx', this._bitcoindTransactionListener); - this.node.services.bitcoind.removeListener('txleave', this._bitcoindTransactionLeaveListener); - this.mempoolIndex.close(callback); -}; - -/** - * This function will set `this.mempoolIndexPath` based on `this.node.network`. - * @private - */ -AddressService.prototype._setMempoolIndexPath = function() { - this.mempoolIndexPath = this._getDBPathFor('bitcore-addressmempool.db'); -}; - -AddressService.prototype._getDBPathFor = function(dbname) { - $.checkState(this.node.datadir, 'Node is expected to have a "datadir" property'); - var path; - if (this.node.network === Networks.livenet) { - path = this.node.datadir + '/' + dbname; - } else if (this.node.network === Networks.testnet) { - if (this.node.network.regtestEnabled) { - path = this.node.datadir + '/regtest/' + dbname; - } else { - path = this.node.datadir + '/testnet3/' + dbname; - } - } else { - throw new Error('Unknown network: ' + this.network); - } - return path; -}; - -/** - * Called by the Node to get the available API methods for this service, - * that can be exposed over the JSON-RPC interface. - */ -AddressService.prototype.getAPIMethods = function() { - return [ - ['getBalance', this, this.getBalance, 2], - ['getOutputs', this, this.getOutputs, 2], - ['getUnspentOutputs', this, this.getUnspentOutputs, 2], - ['getInputForOutput', this, this.getInputForOutput, 2], - ['isSpent', this, this.isSpent, 2], - ['getAddressHistory', this, this.getAddressHistory, 2], - ['getAddressSummary', this, this.getAddressSummary, 1] - ]; -}; - -/** - * Called by the Bus to get the available events for this service. - */ -AddressService.prototype.getPublishEvents = function() { - return [ - { - name: 'address/transaction', - scope: this, - subscribe: this.subscribe.bind(this, 'address/transaction'), - unsubscribe: this.unsubscribe.bind(this, 'address/transaction') - }, - { - name: 'address/balance', - scope: this, - subscribe: this.subscribe.bind(this, 'address/balance'), - unsubscribe: this.unsubscribe.bind(this, 'address/balance') - } - ]; -}; - -/** - * Will process each output of a transaction from the daemon "tx" event, and construct - * an object with the data for the message to be relayed to any subscribers for an address. - * - * @param {Object} messages - An object to collect messages - * @param {Transaction} tx - Instance of the transaction - * @param {Number} outputIndex - The index of the output in the transaction - * @param {Boolean} rejected - If the transaction was rejected by the mempool - */ -AddressService.prototype.transactionOutputHandler = function(messages, tx, outputIndex, rejected) { - var script = tx.outputs[outputIndex].script; - - // If the script is invalid skip - if (!script) { - return; - } - - var addressInfo = encoding.extractAddressInfoFromScript(script, this.node.network); - if (!addressInfo) { - return; - } - - addressInfo.hashHex = addressInfo.hashBuffer.toString('hex'); - - // Collect data to publish to address subscribers - if (messages[addressInfo.hashHex]) { - messages[addressInfo.hashHex].outputIndexes.push(outputIndex); - } else { - messages[addressInfo.hashHex] = { - tx: tx, - outputIndexes: [outputIndex], - addressInfo: addressInfo, - rejected: rejected - }; - } -}; - -/** - * This will handle data from the daemon "txleave" that a transaction has left the mempool. - * @param {Object} txInfo - The data from the daemon.on('txleave') event - * @param {Buffer} txInfo.buffer - The transaction buffer - * @param {String} txInfo.hash - The hash of the transaction - */ -AddressService.prototype.transactionLeaveHandler = function(txInfo) { - var tx = bitcore.Transaction().fromBuffer(txInfo.buffer); - this.updateMempoolIndex(tx, false); -}; - -/** - * This will handle data from the daemon "tx" event, go through each of the outputs - * and send messages by calling `transactionEventHandler` to any subscribers for a - * particular address. - * @param {Object} txInfo - The data from the daemon.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 - * @param {Function} [callback] - Optional callback - */ -AddressService.prototype.transactionHandler = function(txInfo, callback) { - var self = this; - - if (!callback) { - callback = function(err) { - if (err) { - return log.error(err); - } - }; - } - - if (this.node.stopping) { - return callback(); - } - - // Basic transaction format is handled by the daemon - // and we can safely assume the buffer is properly formatted. - var tx = bitcore.Transaction().fromBuffer(txInfo.buffer); - - var messages = {}; - - var outputsLength = tx.outputs.length; - for (var i = 0; i < outputsLength; i++) { - this.transactionOutputHandler(messages, tx, i, !txInfo.mempool); - } - - function finish(err) { - if (err) { - return callback(err); - } - for (var key in messages) { - self.transactionEventHandler(messages[key]); - self.balanceEventHandler(null, messages[key].addressInfo); - } - callback(); - } - - if (txInfo.mempool) { - self.updateMempoolIndex(tx, true, finish); - } else { - setImmediate(finish); - } - -}; - -AddressService.prototype._updateAddressIndex = function(key, add) { - var currentValue = this.mempoolAddressIndex[key] || 0; - - if(add) { - if (currentValue > 0) { - this.mempoolAddressIndex[key] = currentValue + 1; - } else { - this.mempoolAddressIndex[key] = 1; - } - } else { - if (currentValue <= 1) { - delete this.mempoolAddressIndex[key]; - } else { - this.mempoolAddressIndex[key]--; - } - } -}; - - -/** - * This function will update the mempool address index with the necessary - * information for further lookups. - * @param {Transaction} - An instance of a Bitcore Transaction - * @param {Boolean} - Add/remove from the index - */ -AddressService.prototype.updateMempoolIndex = function(tx, add, callback) { - /* jshint maxstatements: 100 */ - - var operations = []; - var timestampBuffer = new Buffer(new Array(8)); - timestampBuffer.writeDoubleBE(new Date().getTime()); - - var action = 'put'; - if (!add) { - action = 'del'; - } - - var txid = tx.hash; - var txidBuffer = new Buffer(txid, 'hex'); - - var outputLength = tx.outputs.length; - for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) { - var output = tx.outputs[outputIndex]; - if (!output.script) { - continue; - } - var addressInfo = encoding.extractAddressInfoFromScript(output.script, this.node.network); - if (!addressInfo) { - continue; - } - - var addressIndexKey = encoding.encodeMempoolAddressIndexKey(addressInfo.hashBuffer, addressInfo.hashTypeBuffer); - - this._updateAddressIndex(addressIndexKey, add); - - // Update output index - var outputIndexBuffer = new Buffer(4); - outputIndexBuffer.writeUInt32BE(outputIndex); - - var outKey = Buffer.concat([ - constants.MEMPREFIXES.OUTPUTS, - addressInfo.hashBuffer, - addressInfo.hashTypeBuffer, - txidBuffer, - outputIndexBuffer - ]); - - var outValue = encoding.encodeOutputMempoolValue( - output.satoshis, - timestampBuffer, - output._scriptBuffer - ); - - operations.push({ - type: action, - key: outKey, - value: outValue - }); - - } - var inputLength = tx.inputs.length; - for (var inputIndex = 0; inputIndex < inputLength; inputIndex++) { - - var input = tx.inputs[inputIndex]; - - var inputOutputIndexBuffer = new Buffer(4); - inputOutputIndexBuffer.writeUInt32BE(input.outputIndex); - - // Add an additional small spent index for fast synchronous lookups - var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey( - input.prevTxId, - input.outputIndex - ); - if (add) { - this.mempoolSpentIndex[spentIndexSyncKey] = true; - } else { - delete this.mempoolSpentIndex[spentIndexSyncKey]; - } - - // Add a more detailed spent index with values - var spentIndexKey = Buffer.concat([ - constants.MEMPREFIXES.SPENTSMAP, - input.prevTxId, - inputOutputIndexBuffer - ]); - var inputIndexBuffer = new Buffer(4); - inputIndexBuffer.writeUInt32BE(inputIndex); - var inputIndexValue = Buffer.concat([ - txidBuffer, - inputIndexBuffer - ]); - operations.push({ - type: action, - key: spentIndexKey, - value: inputIndexValue - }); - - // Update input index - var inputHashBuffer; - var inputHashType; - if (input.script.isPublicKeyHashIn()) { - inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[1].buf); - inputHashType = constants.HASH_TYPES.PUBKEY; - } else if (input.script.isScriptHashIn()) { - inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf); - inputHashType = constants.HASH_TYPES.REDEEMSCRIPT; - } else { - continue; - } - var inputKey = Buffer.concat([ - constants.MEMPREFIXES.SPENTS, - inputHashBuffer, - inputHashType, - input.prevTxId, - inputOutputIndexBuffer - ]); - var inputValue = Buffer.concat([ - txidBuffer, - inputIndexBuffer, - timestampBuffer - ]); - operations.push({ - type: action, - key: inputKey, - value: inputValue - }); - - var addressIndexKey = encoding.encodeMempoolAddressIndexKey(inputHashBuffer, inputHashType); - - this._updateAddressIndex(addressIndexKey, add); - } - - if (!callback) { - callback = function(err) { - if (err) { - return log.error(err); - } - }; - } - - this.mempoolIndex.batch(operations, callback); -}; - -/** - * The Database Service will run this function when blocks are connected and - * disconnected to the chain during syncing and reorganizations. - * @param {Block} block - An instance of a Bitcore Block - * @param {Boolean} addOutput - If the block is being removed or added to the chain - * @param {Function} callback - */ -AddressService.prototype.blockHandler = function(block, addOutput, callback) { - var txs = block.transactions; - var height = block.__height; - - var action = 'put'; - if (!addOutput) { - action = 'del'; - } - - var operations = []; - - var transactionLength = txs.length; - for (var i = 0; i < transactionLength; i++) { - - var tx = txs[i]; - var txid = tx.id; - var txidBuffer = new Buffer(txid, 'hex'); - var inputs = tx.inputs; - var outputs = tx.outputs; - - // Subscription messages - var txmessages = {}; - - var outputLength = outputs.length; - for (var outputIndex = 0; outputIndex < outputLength; outputIndex++) { - var output = outputs[outputIndex]; - - var script = output.script; - - if(!script) { - log.debug('Invalid script'); - continue; - } - - var addressInfo = encoding.extractAddressInfoFromScript(script, this.node.network); - if (!addressInfo) { - continue; - } - - // We need to use the height for indexes (and not the timestamp) because the - // the timestamp has unreliable sequential ordering. The next block - // can have a time that is previous to the previous block (however not - // less than the mean of the 11 previous blocks) and not greater than 2 - // hours in the future. - var key = encoding.encodeOutputKey(addressInfo.hashBuffer, addressInfo.hashTypeBuffer, - height, txidBuffer, outputIndex); - var value = encoding.encodeOutputValue(output.satoshis, output._scriptBuffer); - operations.push({ - type: action, - key: key, - value: value - }); - - addressInfo.hashHex = addressInfo.hashBuffer.toString('hex'); - - // Collect data for subscribers - if (txmessages[addressInfo.hashHex]) { - txmessages[addressInfo.hashHex].outputIndexes.push(outputIndex); - } else { - txmessages[addressInfo.hashHex] = { - tx: tx, - height: height, - outputIndexes: [outputIndex], - addressInfo: addressInfo, - timestamp: block.header.timestamp - }; - } - - this.balanceEventHandler(block, addressInfo); - - } - - // Publish events to any subscribers for this transaction - for (var addressKey in txmessages) { - this.transactionEventHandler(txmessages[addressKey]); - } - - if(tx.isCoinbase()) { - continue; - } - - for(var inputIndex = 0; inputIndex < inputs.length; inputIndex++) { - - var input = inputs[inputIndex]; - var inputHash; - var inputHashType; - - if (input.script.isPublicKeyHashIn()) { - inputHash = Hash.sha256ripemd160(input.script.chunks[1].buf); - inputHashType = constants.HASH_TYPES.PUBKEY; - } else if (input.script.isScriptHashIn()) { - inputHash = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf); - inputHashType = constants.HASH_TYPES.REDEEMSCRIPT; - } else { - continue; - } - - var prevTxIdBuffer = new Buffer(input.prevTxId, 'hex'); - - // To be able to query inputs by address and spent height - var inputKey = encoding.encodeInputKey(inputHash, inputHashType, height, prevTxIdBuffer, input.outputIndex); - var inputValue = encoding.encodeInputValue(txidBuffer, inputIndex); - - operations.push({ - type: action, - key: inputKey, - value: inputValue - }); - - // To be able to search for an input spending an output - var inputKeyMap = encoding.encodeInputKeyMap(prevTxIdBuffer, input.outputIndex); - var inputValueMap = encoding.encodeInputValueMap(txidBuffer, inputIndex); - - operations.push({ - type: action, - key: inputKeyMap, - value: inputValueMap - }); - - } - } - - setImmediate(function() { - callback(null, operations); - }); -}; - -/** - * This function is responsible for emitting events to any subscribers to the - * `address/transaction` event. - * @param {Object} obj - * @param {Transaction} obj.tx - The transaction - * @param {Object} obj.addressInfo - * @param {String} obj.addressInfo.hashHex - The hex string of address hash for the subscription - * @param {String} obj.addressInfo.hashBuffer - The address hash buffer - * @param {String} obj.addressInfo.addressType - The address type - * @param {Array} obj.outputIndexes - Indexes of the inputs that includes the address - * @param {Array} obj.inputIndexes - Indexes of the outputs that includes the address - * @param {Date} obj.timestamp - The time of the block the transaction was included - * @param {Number} obj.height - The height of the block the transaction was included - * @param {Boolean} obj.rejected - If the transaction was not accepted in the mempool - */ -AddressService.prototype.transactionEventHandler = function(obj) { - if(this.subscriptions['address/transaction'][obj.addressInfo.hashHex]) { - var emitters = this.subscriptions['address/transaction'][obj.addressInfo.hashHex]; - var address = new Address({ - hashBuffer: obj.addressInfo.hashBuffer, - network: this.node.network, - type: obj.addressInfo.addressType - }); - for(var i = 0; i < emitters.length; i++) { - emitters[i].emit('address/transaction', { - rejected: obj.rejected, - height: obj.height, - timestamp: obj.timestamp, - inputIndexes: obj.inputIndexes, - outputIndexes: obj.outputIndexes, - address: address, - tx: obj.tx - }); - } - } -}; - -/** - * The function is responsible for emitting events to any subscribers for the - * `address/balance` event. - * @param {Block} block - * @param {Object} obj - * @param {String} obj.hashHex - * @param {Buffer} obj.hashBuffer - * @param {String} obj.addressType - */ -AddressService.prototype.balanceEventHandler = function(block, obj) { - if(this.subscriptions['address/balance'][obj.hashHex]) { - var emitters = this.subscriptions['address/balance'][obj.hashHex]; - var address = new Address({ - hashBuffer: obj.hashBuffer, - network: this.node.network, - type: obj.addressType - }); - this.getBalance(address, true, function(err, balance) { - if(err) { - return this.emit(err); - } - for(var i = 0; i < emitters.length; i++) { - emitters[i].emit('address/balance', address, balance, block); - } - }); - } -}; - -/** - * The Bus will use this function to subscribe to the available - * events for this service. For information about the available events - * please see `getPublishEvents`. - * @param {String} name - The name of the event - * @param {EventEmitter} emitter - An event emitter instance - * @param {Array} addresses - An array of addresses to subscribe - */ -AddressService.prototype.subscribe = function(name, emitter, addresses) { - $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); - $.checkArgument(Array.isArray(addresses), 'Second argument is expected to be an Array of addresses'); - - for(var i = 0; i < addresses.length; i++) { - var hashHex = bitcore.Address(addresses[i]).hashBuffer.toString('hex'); - if(!this.subscriptions[name][hashHex]) { - this.subscriptions[name][hashHex] = []; - } - this.subscriptions[name][hashHex].push(emitter); - } -}; - -/** - * The Bus will use this function to unsubscribe to the available - * events for this service. - * @param {String} name - The name of the event - * @param {EventEmitter} emitter - An event emitter instance - * @param {Array} addresses - An array of addresses to subscribe - */ -AddressService.prototype.unsubscribe = function(name, emitter, addresses) { - $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); - $.checkArgument(Array.isArray(addresses) || _.isUndefined(addresses), 'Second argument is expected to be an Array of addresses or undefined'); - - if(!addresses) { - return this.unsubscribeAll(name, emitter); - } - - for(var i = 0; i < addresses.length; i++) { - var hashHex = bitcore.Address(addresses[i]).hashBuffer.toString('hex'); - if(this.subscriptions[name][hashHex]) { - var emitters = this.subscriptions[name][hashHex]; - var index = emitters.indexOf(emitter); - if(index > -1) { - emitters.splice(index, 1); - } - } - } -}; - -/** - * A helper function for the `unsubscribe` method to unsubscribe from all addresses. - * @param {String} name - The name of the event - * @param {EventEmitter} emitter - An instance of an event emitter - */ -AddressService.prototype.unsubscribeAll = function(name, emitter) { - $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); - - for(var hashHex in this.subscriptions[name]) { - var emitters = this.subscriptions[name][hashHex]; - var index = emitters.indexOf(emitter); - if(index > -1) { - emitters.splice(index, 1); - } - } -}; - -/** - * Will sum the total of all unspent outputs to calculate the balance - * for an address. - * @param {String} address - The base58check encoded address - * @param {Boolean} queryMempool - Include mempool in the results - * @param {Function} callback - */ -AddressService.prototype.getBalance = function(address, queryMempool, callback) { - this.getUnspentOutputs(address, queryMempool, function(err, outputs) { - if(err) { - return callback(err); - } - - var satoshis = outputs.map(function(output) { - return output.satoshis; - }); - - var sum = satoshis.reduce(function(a, b) { - return a + b; - }, 0); - - return callback(null, sum); - }); -}; - -/** - * Will give the input that spends an output if it exists with: - * inputTxId - The input txid hex string - * inputIndex - A number with the spending input index - * @param {String|Buffer} txid - The transaction hash with the output - * @param {Number} outputIndex - The output index in the transaction - * @param {Object} options - * @param {Object} options.queryMempool - Include mempool in results - * @param {Function} callback - */ -AddressService.prototype.getInputForOutput = function(txid, outputIndex, options, callback) { - $.checkArgument(_.isNumber(outputIndex)); - $.checkArgument(_.isObject(options)); - $.checkArgument(_.isFunction(callback)); - var self = this; - var txidBuffer; - if (Buffer.isBuffer(txid)) { - txidBuffer = txid; - } else { - txidBuffer = new Buffer(txid, 'hex'); - } - if (options.queryMempool) { - var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(txidBuffer, outputIndex); - if (this.mempoolSpentIndex[spentIndexSyncKey]) { - return this._getSpentMempool(txidBuffer, outputIndex, callback); - } - } - var key = encoding.encodeInputKeyMap(txidBuffer, outputIndex); - var dbOptions = { - valueEncoding: 'binary', - keyEncoding: 'binary' - }; - this.node.services.db.store.get(key, dbOptions, function(err, buffer) { - if (err instanceof levelup.errors.NotFoundError) { - return callback(null, false); - } else if (err) { - return callback(err); - } - var value = encoding.decodeInputValueMap(buffer); - callback(null, { - inputTxId: value.inputTxId.toString('hex'), - inputIndex: value.inputIndex - }); - }); -}; - -/** - * A streaming equivalent to `getInputs`, and returns a transform stream with data - * emitted in the same format as `getInputs`. - * - * @param {String} addressStr - The relevant address - * @param {Object} options - Additional options for query the outputs - * @param {Number} [options.start] - The relevant start block height - * @param {Number} [options.end] - The relevant end block height - * @param {Function} callback - */ -AddressService.prototype.createInputsStream = function(addressStr, options) { - var inputStream = new InputsTransformStream({ - address: new Address(addressStr, this.node.network), - tipHeight: this.node.services.db.tip.__height - }); - - var stream = this.createInputsDBStream(addressStr, options) - .on('error', function(err) { - // Forward the error - inputStream.emit('error', err); - inputStream.end(); - }).pipe(inputStream); - - return stream; - -}; - -AddressService.prototype.createInputsDBStream = function(addressStr, options) { - var stream; - var addrObj = encoding.getAddressInfo(addressStr); - var hashBuffer = addrObj.hashBuffer; - var hashTypeBuffer = addrObj.hashTypeBuffer; - - if (options.start >= 0 && options.end >= 0) { - - var endBuffer = new Buffer(4); - endBuffer.writeUInt32BE(options.end, 0); - - var startBuffer = new Buffer(4); - // Because the key has additional data following it, we don't have an ability - // to use "gte" or "lte" we can only use "gt" and "lt", we therefore need to adjust the number - // to be one value larger to include it. - var adjustedStart = options.start + 1; - startBuffer.writeUInt32BE(adjustedStart, 0); - - stream = this.node.services.db.store.createReadStream({ - gt: Buffer.concat([ - constants.PREFIXES.SPENTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN, - endBuffer - ]), - lt: Buffer.concat([ - constants.PREFIXES.SPENTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN, - startBuffer - ]), - valueEncoding: 'binary', - keyEncoding: 'binary' - }); - } else { - var allKey = Buffer.concat([constants.PREFIXES.SPENTS, hashBuffer, hashTypeBuffer]); - stream = this.node.services.db.store.createReadStream({ - gt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MIN]), - lt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MAX]), - valueEncoding: 'binary', - keyEncoding: 'binary' - }); - } - - return stream; -}; - -/** - * Will give inputs that spend previous outputs for an address as an object with: - * address - The base58check encoded address - * hashtype - The type of the address, e.g. 'pubkeyhash' or 'scripthash' - * txid - A string of the transaction hash - * outputIndex - A number of corresponding transaction input - * height - The height of the block the transaction was included, will be -1 for mempool transactions - * confirmations - The number of confirmations, will equal 0 for mempool transactions - * - * @param {String} addressStr - The relevant address - * @param {Object} options - Additional options for query the outputs - * @param {Number} [options.start] - The relevant start block height - * @param {Number} [options.end] - The relevant end block height - * @param {Boolean} [options.queryMempool] - Include the mempool in the results - * @param {Function} callback - */ -AddressService.prototype.getInputs = function(addressStr, options, callback) { - - var self = this; - - var inputs = []; - - var addrObj = encoding.getAddressInfo(addressStr); - var hashBuffer = addrObj.hashBuffer; - var hashTypeBuffer = addrObj.hashTypeBuffer; - - var stream = this.createInputsStream(addressStr, options); - - stream.on('data', function(input) { - inputs.push(input); - if (inputs.length > self.maxInputsQueryLength) { - log.warn('Tried to query too many inputs (' + self.maxInputsQueryLength + ') for address '+ addressStr); - error = new Error('Maximum number of inputs (' + self.maxInputsQueryLength + ') per query reached'); - stream.end(); - } - }); - - var error; - - stream.on('error', function(streamError) { - if (streamError) { - error = streamError; - } - }); - - stream.on('finish', function() { - if (error) { - return callback(error); - } - - if(options.queryMempool) { - self._getInputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolInputs) { - if (err) { - return callback(err); - } - inputs = inputs.concat(mempoolInputs); - callback(null, inputs); - }); - } else { - callback(null, inputs); - } - - }); - - return stream; - -}; - -AddressService.prototype._getInputsMempool = function(addressStr, hashBuffer, hashTypeBuffer, callback) { - var self = this; - var mempoolInputs = []; - - var stream = self.mempoolIndex.createReadStream({ - gte: Buffer.concat([ - constants.MEMPREFIXES.SPENTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN - ]), - lte: Buffer.concat([ - constants.MEMPREFIXES.SPENTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MAX - ]), - valueEncoding: 'binary', - keyEncoding: 'binary' - }); - - stream.on('data', function(data) { - var txid = data.value.slice(0, 32); - var inputIndex = data.value.readUInt32BE(32); - var timestamp = data.value.readDoubleBE(36); - var input = { - address: addressStr, - hashType: constants.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')], - txid: txid.toString('hex'), //TODO use a buffer - inputIndex: inputIndex, - timestamp: timestamp, - height: -1, - confirmations: 0 - }; - mempoolInputs.push(input); - }); - - var error; - - stream.on('error', function(streamError) { - if (streamError) { - error = streamError; - } - }); - - stream.on('close', function() { - if (error) { - return callback(error); - } - callback(null, mempoolInputs); - }); - -}; - -AddressService.prototype._getSpentMempool = function(txidBuffer, outputIndex, callback) { - var outputIndexBuffer = new Buffer(4); - outputIndexBuffer.writeUInt32BE(outputIndex); - var spentIndexKey = Buffer.concat([ - constants.MEMPREFIXES.SPENTSMAP, - txidBuffer, - outputIndexBuffer - ]); - - this.mempoolIndex.get( - spentIndexKey, - function(err, mempoolValue) { - if (err) { - return callback(err); - } - var inputTxId = mempoolValue.slice(0, 32); - var inputIndex = mempoolValue.readUInt32BE(32); - callback(null, { - inputTxId: inputTxId.toString('hex'), - inputIndex: inputIndex - }); - } - ); -}; - -AddressService.prototype.createOutputsStream = function(addressStr, options) { - var outputStream = new OutputsTransformStream({ - address: new Address(addressStr, this.node.network), - tipHeight: this.node.services.db.tip.__height - }); - - var stream = this.createOutputsDBStream(addressStr, options) - .on('error', function(err) { - // Forward the error - outputStream.emit('error', err); - outputStream.end(); - }) - .pipe(outputStream); - - return stream; - -}; - -AddressService.prototype.createOutputsDBStream = function(addressStr, options) { - - var addrObj = encoding.getAddressInfo(addressStr); - var hashBuffer = addrObj.hashBuffer; - var hashTypeBuffer = addrObj.hashTypeBuffer; - var stream; - - if (options.start >= 0 && options.end >= 0) { - - var endBuffer = new Buffer(4); - endBuffer.writeUInt32BE(options.end, 0); - - var startBuffer = new Buffer(4); - // Because the key has additional data following it, we don't have an ability - // to use "gte" or "lte" we can only use "gt" and "lt", we therefore need to adjust the number - // to be one value larger to include it. - var startAdjusted = options.start + 1; - startBuffer.writeUInt32BE(startAdjusted, 0); - - stream = this.node.services.db.store.createReadStream({ - gt: Buffer.concat([ - constants.PREFIXES.OUTPUTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN, - endBuffer - ]), - lt: Buffer.concat([ - constants.PREFIXES.OUTPUTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN, - startBuffer - ]), - valueEncoding: 'binary', - keyEncoding: 'binary' - }); - } else { - var allKey = Buffer.concat([constants.PREFIXES.OUTPUTS, hashBuffer, hashTypeBuffer]); - stream = this.node.services.db.store.createReadStream({ - gt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MIN]), - lt: Buffer.concat([allKey, constants.SPACER_HEIGHT_MAX]), - valueEncoding: 'binary', - keyEncoding: 'binary' - }); - } - - return stream; - -}; - -/** - * Will give outputs for an address as an object with: - * address - The base58check encoded address - * hashtype - The type of the address, e.g. 'pubkeyhash' or 'scripthash' - * txid - A string of the transaction hash - * outputIndex - A number of corresponding transaction output - * height - The height of the block the transaction was included, will be -1 for mempool transactions - * satoshis - The satoshis value of the output - * script - The script of the output as a hex string - * confirmations - The number of confirmations, will equal 0 for mempool transactions - * - * @param {String} addressStr - The relevant address - * @param {Object} options - Additional options for query the outputs - * @param {Number} [options.start] - The relevant start block height - * @param {Number} [options.end] - The relevant end block height - * @param {Boolean} [options.queryMempool] - Include the mempool in the results - * @param {Function} callback - */ -AddressService.prototype.getOutputs = function(addressStr, options, callback) { - var self = this; - $.checkArgument(_.isObject(options), 'Second argument is expected to be an options object.'); - $.checkArgument(_.isFunction(callback), 'Third argument is expected to be a callback function.'); - - var addrObj = encoding.getAddressInfo(addressStr); - var hashBuffer = addrObj.hashBuffer; - var hashTypeBuffer = addrObj.hashTypeBuffer; - if (!hashTypeBuffer) { - return callback(new Error('Unknown address type: ' + addrObj.hashTypeReadable + ' for address: ' + addressStr)); - } - - var outputs = []; - var stream = this.createOutputsStream(addressStr, options); - - stream.on('data', function(data) { - outputs.push(data); - if (outputs.length > self.maxOutputsQueryLength) { - log.warn('Tried to query too many outputs (' + self.maxOutputsQueryLength + ') for address ' + addressStr); - error = new Error('Maximum number of outputs (' + self.maxOutputsQueryLength + ') per query reached'); - stream.end(); - } - }); - - var error; - - stream.on('error', function(streamError) { - if (streamError) { - error = streamError; - } - }); - - stream.on('finish', function() { - if (error) { - return callback(error); - } - - if(options.queryMempool) { - self._getOutputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolOutputs) { - if (err) { - return callback(err); - } - outputs = outputs.concat(mempoolOutputs); - callback(null, outputs); - }); - } else { - callback(null, outputs); - } - }); - - return stream; - -}; - -AddressService.prototype._getOutputsMempool = function(addressStr, hashBuffer, hashTypeBuffer, callback) { - var self = this; - var mempoolOutputs = []; - - var stream = self.mempoolIndex.createReadStream({ - gte: Buffer.concat([ - constants.MEMPREFIXES.OUTPUTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MIN - ]), - lte: Buffer.concat([ - constants.MEMPREFIXES.OUTPUTS, - hashBuffer, - hashTypeBuffer, - constants.SPACER_MAX - ]), - valueEncoding: 'binary', - keyEncoding: 'binary' - }); - - stream.on('data', function(data) { - // Format of data: - // prefix: 1, hashBuffer: 20, hashTypeBuffer: 1, txid: 32, outputIndex: 4 - var txid = data.key.slice(22, 54); - var outputIndex = data.key.readUInt32BE(54); - var value = encoding.decodeOutputMempoolValue(data.value); - var output = { - address: addressStr, - hashType: constants.HASH_TYPES_READABLE[hashTypeBuffer.toString('hex')], - txid: txid.toString('hex'), //TODO use a buffer - outputIndex: outputIndex, - height: -1, - timestamp: value.timestamp, - satoshis: value.satoshis, - script: value.scriptBuffer.toString('hex'), //TODO use a buffer - confirmations: 0 - }; - mempoolOutputs.push(output); - }); - - var error; - - stream.on('error', function(streamError) { - if (streamError) { - error = streamError; - } - }); - - stream.on('close', function() { - if (error) { - return callback(error); - } - callback(null, mempoolOutputs); - }); - -}; - -/** - * Will give unspent outputs for an address or an array of addresses. - * @param {Array|String} addresses - An array of addresses - * @param {Boolean} queryMempool - Include or exclude the mempool - * @param {Function} callback - */ -AddressService.prototype.getUnspentOutputs = function(addresses, queryMempool, callback) { - var self = this; - - if(!Array.isArray(addresses)) { - addresses = [addresses]; - } - - var utxos = []; - - async.eachSeries(addresses, function(address, next) { - self.getUnspentOutputsForAddress(address, queryMempool, function(err, unspents) { - if(err && err instanceof errors.NoOutputs) { - return next(); - } else if(err) { - return next(err); - } - - utxos = utxos.concat(unspents); - next(); - }); - }, function(err) { - callback(err, utxos); - }); -}; - -/** - * Will give unspent outputs for an address. - * @param {String} address - An address in base58check encoding - * @param {Boolean} queryMempool - Include or exclude the mempool - * @param {Function} callback - */ -AddressService.prototype.getUnspentOutputsForAddress = function(address, queryMempool, callback) { - - var self = this; - - this.getOutputs(address, {queryMempool: queryMempool}, function(err, outputs) { - if (err) { - return callback(err); - } else if(!outputs.length) { - return callback(new errors.NoOutputs('Address ' + address + ' has no outputs'), []); - } - - var opts = { - queryMempool: queryMempool - }; - - var isUnspent = function(output, callback) { - self.isUnspent(output, opts, callback); - }; - - async.filter(outputs, isUnspent, function(results) { - callback(null, results); - }); - }); -}; - -/** - * Will give the inverse of isSpent - * @param {Object} output - * @param {Object} options - * @param {Boolean} options.queryMempool - Include mempool in results - * @param {Function} callback - */ -AddressService.prototype.isUnspent = function(output, options, callback) { - $.checkArgument(_.isFunction(callback)); - this.isSpent(output, options, function(spent) { - callback(!spent); - }); -}; - -/** - * Will determine if an output is spent. - * @param {Object} output - An output as returned from getOutputs - * @param {Object} options - * @param {Boolean} options.queryMempool - Include mempool in results - * @param {Function} callback - */ -AddressService.prototype.isSpent = function(output, options, callback) { - $.checkArgument(_.isFunction(callback)); - var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool; - var self = this; - var txid = output.prevTxId ? output.prevTxId.toString('hex') : output.txid; - var spent = self.node.services.bitcoind.isSpent(txid, output.outputIndex); - if (!spent && queryMempool) { - var txidBuffer = new Buffer(txid, 'hex'); - var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(txidBuffer, output.outputIndex); - spent = self.mempoolSpentIndex[spentIndexSyncKey] ? true : false; - } - setImmediate(function() { - // TODO error should be the first argument? - callback(spent); - }); -}; - - -/** - * This will give the history for many addresses limited by a range of block heights (to limit - * the database lookup times) and/or paginated to limit the results length. - * - * The response format will be: - * { - * totalCount: 12 // the total number of items there are between the two heights - * items: [ - * { - * addresses: { - * '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX': { - * inputIndexes: [], - * outputIndexes: [0] - * } - * }, - * satoshis: 100, - * height: 300000, - * confirmations: 1, - * timestamp: 1442337090 // in seconds - * fees: 1000 // in satoshis - * tx: - * } - * ] - * } - * @param {Array} addresses - An array of addresses - * @param {Object} options - The options to limit the query - * @param {Number} [options.from] - The pagination "from" index - * @param {Number} [options.to] - The pagination "to" index - * @param {Number} [options.start] - The beginning block height (e.g. 1500 the most recent block height). - * @param {Number} [options.end] - The ending block height (e.g. 0 the older block height, results are inclusive). - * @param {Boolean} [options.queryMempool] - Include the mempool in the query - * @param {Function} callback - */ -AddressService.prototype.getAddressHistory = function(addresses, options, callback) { - var history = new AddressHistory({ - node: this.node, - options: options, - addresses: addresses - }); - history.get(callback); -}; - -/** - * This will give an object with: - * balance - confirmed balance - * unconfirmedBalance - unconfirmed balance - * totalReceived - satoshis received - * totalSpent - satoshis spent - * appearances - number of transactions - * unconfirmedAppearances - number of unconfirmed transactions - * txids - list of txids (unless noTxList is set) - * - * @param {String} address - * @param {Object} options - * @param {Boolean} [options.noTxList] - if set, txid array will not be included - * @param {Function} callback - */ -AddressService.prototype.getAddressSummary = function(addressArg, options, callback) { - var self = this; - - var startTime = new Date(); - var address = new Address(addressArg); - - if (_.isUndefined(options.queryMempool)) { - options.queryMempool = true; - } - - async.waterfall([ - function(next) { - self._getAddressConfirmedSummary(address, options, next); - }, - function(result, next) { - self._getAddressMempoolSummary(address, options, result, next); - }, - function(result, next) { - self._setAndSortTxidsFromAppearanceIds(result, next); - } - ], function(err, result) { - if (err) { - return callback(err); - } - - var summary = self._transformAddressSummaryFromResult(result, options); - - var timeDelta = new Date() - startTime; - if (timeDelta > 5000) { - var seconds = Math.round(timeDelta / 1000); - log.warn('Slow (' + seconds + 's) getAddressSummary request for address: ' + address.toString()); - } - - callback(null, summary); - - }); - -}; - -AddressService.prototype._getAddressConfirmedSummary = function(address, options, callback) { - var self = this; - var baseResult = { - appearanceIds: {}, - totalReceived: 0, - balance: 0, - unconfirmedAppearanceIds: {}, - unconfirmedBalance: 0 - }; - - async.waterfall([ - function(next) { - self._getAddressConfirmedInputsSummary(address, baseResult, options, next); - }, - function(result, next) { - self._getAddressConfirmedOutputsSummary(address, result, options, next); - } - ], callback); - -}; - -AddressService.prototype._getAddressConfirmedInputsSummary = function(address, result, options, callback) { - $.checkArgument(address instanceof Address); - var self = this; - var error = null; - var count = 0; - - var inputsStream = self.createInputsStream(address, options); - inputsStream.on('data', function(input) { - var txid = input.txid; - result.appearanceIds[txid] = input.height; - - count++; - - if (count > self.maxInputsQueryLength) { - log.warn('Tried to query too many inputs (' + self.maxInputsQueryLength + ') for summary of address ' + address.toString()); - error = new Error('Maximum number of inputs (' + self.maxInputsQueryLength + ') per query reached'); - inputsStream.end(); - } - - }); - - inputsStream.on('error', function(err) { - error = err; - }); - - inputsStream.on('end', function() { - if (error) { - return callback(error); - } - callback(null, result); - }); -}; - -AddressService.prototype._getAddressConfirmedOutputsSummary = function(address, result, options, callback) { - $.checkArgument(address instanceof Address); - $.checkArgument(!_.isUndefined(result) && - !_.isUndefined(result.appearanceIds) && - !_.isUndefined(result.unconfirmedAppearanceIds)); - - var self = this; - var count = 0; - - var outputStream = self.createOutputsStream(address, options); - - outputStream.on('data', function(output) { - - var txid = output.txid; - var outputIndex = output.outputIndex; - result.totalReceived += output.satoshis; - result.appearanceIds[txid] = output.height; - - if(!options.noBalance) { - - // Bitcoind's isSpent only works for confirmed transactions - var spentDB = self.node.services.bitcoind.isSpent(txid, outputIndex); - - if(!spentDB) { - result.balance += output.satoshis; - } - - if(options.queryMempool) { - // Check to see if this output is spent in the mempool and if so - // we will subtract it from the unconfirmedBalance (a.k.a unconfirmedDelta) - var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey( - new Buffer(txid, 'hex'), // TODO: get buffer directly - outputIndex - ); - var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey]; - if(spentMempool) { - result.unconfirmedBalance -= output.satoshis; - } - } - } - - count++; - - if (count > self.maxOutputsQueryLength) { - log.warn('Tried to query too many outputs (' + self.maxOutputsQueryLength + ') for summary of address ' + address.toString()); - error = new Error('Maximum number of outputs (' + self.maxOutputsQueryLength + ') per query reached'); - outputStream.end(); - } - - }); - - var error = null; - - outputStream.on('error', function(err) { - error = err; - }); - - outputStream.on('end', function() { - if (error) { - return callback(error); - } - callback(null, result); - }); - -}; - -AddressService.prototype._setAndSortTxidsFromAppearanceIds = function(result, callback) { - result.txids = Object.keys(result.appearanceIds); - result.txids.sort(function(a, b) { - return result.appearanceIds[a] - result.appearanceIds[b]; - }); - result.unconfirmedTxids = Object.keys(result.unconfirmedAppearanceIds); - result.unconfirmedTxids.sort(function(a, b) { - return result.unconfirmedAppearanceIds[a] - result.unconfirmedAppearanceIds[b]; - }); - callback(null, result); -}; - -AddressService.prototype._getAddressMempoolSummary = function(address, options, result, callback) { - var self = this; - - // Skip if the options do not want to include the mempool - if (!options.queryMempool) { - return callback(null, result); - } - - var addressStr = address.toString(); - var hashBuffer = address.hashBuffer; - var hashTypeBuffer = constants.HASH_TYPES_MAP[address.type]; - var addressIndexKey = encoding.encodeMempoolAddressIndexKey(hashBuffer, hashTypeBuffer); - - if(!this.mempoolAddressIndex[addressIndexKey]) { - return callback(null, result); - } - - async.waterfall([ - function(next) { - self._getInputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolInputs) { - if (err) { - return next(err); - } - for(var i = 0; i < mempoolInputs.length; i++) { - var input = mempoolInputs[i]; - result.unconfirmedAppearanceIds[input.txid] = input.timestamp; - } - next(null, result); - }); - - }, function(result, next) { - self._getOutputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolOutputs) { - if (err) { - return next(err); - } - for(var i = 0; i < mempoolOutputs.length; i++) { - var output = mempoolOutputs[i]; - - result.unconfirmedAppearanceIds[output.txid] = output.timestamp; - - if(!options.noBalance) { - var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey( - new Buffer(output.txid, 'hex'), // TODO: get buffer directly - output.outputIndex - ); - var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey]; - // Only add this to the balance if it's not spent in the mempool already - if(!spentMempool) { - result.unconfirmedBalance += output.satoshis; - } - } - } - next(null, result); - }); - } - ], callback); -}; - -AddressService.prototype._transformAddressSummaryFromResult = function(result, options) { - - var confirmedTxids = result.txids; - var unconfirmedTxids = result.unconfirmedTxids; - - var summary = { - totalReceived: result.totalReceived, - totalSpent: result.totalReceived - result.balance, - balance: result.balance, - appearances: confirmedTxids.length, - unconfirmedBalance: result.unconfirmedBalance, - unconfirmedAppearances: unconfirmedTxids.length - }; - - if (options.fullTxList) { - summary.appearanceIds = result.appearanceIds; - summary.unconfirmedAppearanceIds = result.unconfirmedAppearanceIds; - } else if (!options.noTxList) { - summary.txids = confirmedTxids.concat(unconfirmedTxids); - } - - return summary; - -}; - -module.exports = AddressService; diff --git a/lib/services/address/streams/inputs-transform.js b/lib/services/address/streams/inputs-transform.js deleted file mode 100644 index 8b8f71d3..00000000 --- a/lib/services/address/streams/inputs-transform.js +++ /dev/null @@ -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; diff --git a/lib/services/address/streams/outputs-transform.js b/lib/services/address/streams/outputs-transform.js deleted file mode 100644 index b9c8e8d3..00000000 --- a/lib/services/address/streams/outputs-transform.js +++ /dev/null @@ -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; diff --git a/lib/services/bitcoind.js b/lib/services/bitcoind.js index edc2dad6..d74d93ea 100644 --- a/lib/services/bitcoind.js +++ b/lib/services/bitcoind.js @@ -1,14 +1,23 @@ 'use strict'; var fs = require('fs'); +var path = require('path'); +var spawn = require('child_process').spawn; var util = require('util'); -var bindings = require('bindings')('bitcoind.node'); var mkdirp = require('mkdirp'); var bitcore = require('bitcore-lib'); +var Address = bitcore.Address; +var zmq = require('zmq'); +var async = require('async'); +var BitcoinRPC = require('bitcoind-rpc'); var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; + var index = require('../'); var log = index.log; +var errors = index.errors; var Service = require('../service'); +var Transaction = require('../transaction'); /** * Provides an interface to native bindings to [Bitcoin Core](https://github.com/bitcoin/bitcoin) @@ -31,7 +40,28 @@ util.inherits(Bitcoin, Service); Bitcoin.dependencies = []; -Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n'; +Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n' + 'addressindex=1\n' + 'server=1\n'; + +/** + * Called by Node to determine the available API methods. + */ +Bitcoin.prototype.getAPIMethods = function() { + var methods = [ + ['getBlock', this, this.getBlock, 1], + ['getBlockHeader', this, this.getBlockHeader, 1], + ['getBlockHashesByTimestamp', this, this.getBlockHashesByTimestamp, 2], + ['getTransaction', this, this.getTransaction, 2], + ['getTransactionWithBlockInfo', this, this.getTransactionWithBlockInfo, 2], + ['sendTransaction', this, this.sendTransaction, 1], + ['estimateFee', this, this.estimateFee, 1], + ['getAddressTxids', this, this.getAddressTxids, 2], + ['getAddressBalance', this, this.getAddressBalance, 2], + ['getAddressUnspentOutputs', this, this.getAddressUnspentOutputs, 2], + ['getAddressHistory', this, this.getAddressHistory, 2], + ['getAddressSummary', this, this.getAddressSummary, 1] + ]; + return methods; +}; Bitcoin.prototype._loadConfiguration = function() { /* jshint maxstatements: 25 */ @@ -44,16 +74,6 @@ Bitcoin.prototype._loadConfiguration = function() { mkdirp.sync(this.node.datadir); } - if (!fs.existsSync(configPath)) { - var defaultConfig = Bitcoin.DEFAULT_CONFIG; - if(this.node.https && this.node.httpsOptions) { - defaultConfig += 'rpcssl=1\n'; - defaultConfig += 'rpcsslprivatekeyfile=' + this.node.httpsOptions.key + '\n'; - defaultConfig += 'rpcsslcertificatechainfile=' + this.node.httpsOptions.cert + '\n'; - } - fs.writeFileSync(configPath, defaultConfig); - } - var file = fs.readFileSync(configPath); var unparsed = file.toString().split('\n'); for(var i = 0; i < unparsed.length; i++) { @@ -72,11 +92,36 @@ Bitcoin.prototype._loadConfiguration = function() { $.checkState( this.configuration.txindex && this.configuration.txindex === 1, - 'Txindex option is required in order to use most of the features of bitcore-node. ' + + '"txindex" option is required in order to use transaction query features of bitcore-node. ' + 'Please add "txindex=1" to your configuration and reindex an existing database if ' + 'necessary with reindex=1' ); + $.checkState( + this.configuration.addressindex && this.configuration.addressindex === 1, + '"addressindex" option is required in order to use address query features of bitcore-node. ' + + 'Please add "addressindex=1" to your configuration and reindex an existing database if ' + + 'necessary with reindex=1' + ); + + $.checkState( + this.configuration.server && this.configuration.server === 1, + '"server" option is required to communicate to bitcoind from bitcore. ' + + 'Please add "server=1" to your configuration and restart' + ); + + $.checkState( + this.configuration.zmqpubhashtx, + '"zmqpubhashtx" option is required to get event updates from bitcoind. ' + + 'Please add "zmqpubhashtx=tcp://127.0.0.1:" 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:" to your configuration and restart' + ); + if (this.configuration.reindex && this.configuration.reindex === 1) { log.warn('Reindex option is currently enabled. This means that bitcoind is undergoing a reindex. ' + 'The reindex flag will start the index from beginning every time the node is started, so it ' + @@ -87,42 +132,38 @@ Bitcoin.prototype._loadConfiguration = function() { }; -Bitcoin.prototype._onTipUpdate = function(result) { - if (result) { - // Emit and event that the tip was updated - this.height = result; - this.emit('tip', result); - - // TODO stopping status - if(!this.node.stopping) { - var percentage = this.syncPercentage(); - log.info('Bitcoin Height:', this.height, 'Percentage:', percentage); - } - - // Recursively wait until the next update - bindings.onTipUpdate(this._onTipUpdate.bind(this)); - } -}; - Bitcoin.prototype._registerEventHandlers = function() { var self = this; - // Set the height and emit a new tip - bindings.onTipUpdate(self._onTipUpdate.bind(this)); + this.zmqSubSocket.subscribe('hashblock'); + this.zmqSubSocket.subscribe('hashtx'); - // Register callback function to handle transactions entering the mempool - bindings.startTxMon(function(txs) { - for(var i = 0; i < txs.length; i++) { - self.emit('tx', txs[i]); + this.zmqSubSocket.on('message', function(topic, message) { + var topicString = topic.toString('utf8'); + if (topicString === 'hashtx') { + self.emit('tx', message.toString('hex')); + } else if (topicString === 'hashblock') { + self.tiphash = message.toString('hex'); + self.client.getBlock(self.tiphash, function(err, response) { + if (err) { + return log.error(err); + } + self.height = response.result.height; + $.checkState(self.height >= 0); + self.emit('tip', self.height); + }); + + if(!self.node.stopping) { + self.syncPercentage(function(err, percentage) { + if (err) { + return log.error(err); + } + log.info('Bitcoin Height:', self.height, 'Percentage:', percentage.toFixed(2)); + }); + } } }); - // Register callback function to handle transactions leaving the mempool - bindings.startTxMonLeave(function(txs) { - for(var i = 0; i < txs.length; i++) { - self.emit('txleave', txs[i]); - } - }); }; Bitcoin.prototype._onReady = function(result, callback) { @@ -130,19 +171,40 @@ Bitcoin.prototype._onReady = function(result, callback) { self._registerEventHandlers(); - var info = self.getInfo(); - self.height = info.blocks; - - self.getBlock(0, function(err, block) { + self.client.getInfo(function(err, response) { if (err) { return callback(err); } - self.genesisBuffer = block; - self.emit('ready', result); - log.info('Bitcoin Daemon Ready'); - callback(); - }); + self.height = response.result.blocks; + self.client.getBlockHash(0, function(err, response) { + if (err) { + return callback(err); + } + var blockhash = response.result; + self.getBlock(blockhash, function(err, block) { + if (err) { + return callback(err); + } + self.tiphash = block.hash; + self.genesisBuffer = block.toBuffer(); + self.emit('ready', result); + log.info('Bitcoin Daemon Ready'); + callback(); + }); + }); + }); +}; + +Bitcoin.prototype._getNetworkOption = function() { + var networkOption; + if (this.node.network === bitcore.Networks.testnet) { + if (this.node.network.regtestEnabled) { + networkOption = '--regtest'; + } + networkOption = '--testnet'; + } + return networkOption; }; /** @@ -152,58 +214,348 @@ Bitcoin.prototype._onReady = function(result, callback) { Bitcoin.prototype.start = function(callback) { var self = this; - this._loadConfiguration(); + self._loadConfiguration(); - var networkName = this.node.network.name; - if (this.node.network.regtestEnabled) { - networkName = 'regtest'; + var options = [ + '--conf=' + path.resolve(this.node.datadir, './bitcoin.conf'), + '--datadir=' + this.node.datadir, + ]; + + if (self._getNetworkOption()) { + options.push(self._getNetworkOption()); } - bindings.start({ - datadir: this.node.datadir, - network: networkName - }, function(err) { - if(err) { + self.process = spawn('bitcoind', options, {stdio: 'inherit'}); + + self.process.on('error', function(err) { + log.error(err); + }); + + async.retry({times: 60, interval: 5000}, function(done) { + if (self.node.stopping) { + return done(new Error('Stopping while trying to connect to bitcoind.')); + } + + self.client = new BitcoinRPC({ + protocol: 'http', + host: '127.0.0.1', + port: self.configuration.rpcport, + user: self.configuration.rpcuser, + pass: self.configuration.rpcpassword + }); + + self.client.getInfo(function(err) { + if (err) { + if (!(err instanceof Error)) { + log.warn(err.message); + } + return done(new Error('Could not connect to bitcoind RPC')); + } + done(); + }); + + }, function ready(err, result) { + if (err) { return callback(err); } - // Wait until the block chain is ready - bindings.onBlocksReady(function(err, result) { - if (err) { - return callback(err); - } - if (self._reindex) { - var interval = setInterval(function() { - var percentSynced = bindings.syncPercentage(); - log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced); - if (percentSynced >= 100) { + + self.zmqSubSocket = zmq.socket('sub'); + + self.zmqSubSocket.on('monitor_error', function(err) { + log.error('Error in monitoring: %s, will restart monitoring in 5 seconds', err); + setTimeout(function() { + self.zmqSubSocket.monitor(500, 0); + }, 5000); + }); + + self.zmqSubSocket.monitor(500, 0); + self.zmqSubSocket.connect(self.configuration.zmqpubhashtx); + + if (self._reindex) { + var interval = setInterval(function() { + self.syncPercentage(function(err, percentSynced) { + if (err) { + return log.error(err); + } + log.info('Bitcoin Core Daemon Reindex Percentage: ' + percentSynced.toFixed(2)); + if (Math.round(percentSynced) >= 100) { self._reindex = false; self._onReady(result, callback); clearInterval(interval); } - }, self._reindexWait); + }); + }, self._reindexWait); - } - else { - self._onReady(result, callback); - } - }); + } else { + self._onReady(result, callback); + } }); + }; /** * Helper to determine the state of the database. + * @param {Function} callback * @returns {Boolean} If the database is fully synced */ -Bitcoin.prototype.isSynced = function() { - return bindings.isSynced(); +Bitcoin.prototype.isSynced = function(callback) { + this.syncPercentage(function(err, percentage) { + if (err) { + return callback(err); + } + if (Math.round(percentage) >= 100) { + callback(null, true); + } else { + callback(null, false); + } + }); }; /** * Helper to determine the progress of the database. + * @param {Function} callback * @returns {Number} An estimated percentage of the syncronization status */ -Bitcoin.prototype.syncPercentage = function() { - return bindings.syncPercentage(); +Bitcoin.prototype.syncPercentage = function(callback) { + this.client.getBlockchainInfo(function(err, response) { + if (err) { + return callback(err); + } + var percentSynced = response.result.verificationprogress * 100; + callback(null, percentSynced); + }); +}; + +Bitcoin.prototype.getAddressBalance = function(addressArg, options, callback) { + // TODO keep a cache and update the cache by a range of block heights + var addresses = [addressArg]; + if (Array.isArray(addressArg)) { + addresses = addressArg; + } + this.client.getAddressBalance({addresses: addresses}, function(err, response) { + if (err) { + return callback(err); + } + callback(null, response.result); + }); +}; + +Bitcoin.prototype.getAddressUnspentOutputs = function() { + // TODO add this rpc method to bitcoind +}; + +Bitcoin.prototype.getAddressTxids = function(addressArg, options, callback) { + // TODO Keep a cache updated for queries + var addresses = [addressArg]; + if (Array.isArray(addressArg)) { + addresses = addressArg; + } + this.client.getAddressTxids({addresses: addresses}, function(err, response) { + if (err) { + return callback(err); + } + return callback(null, response.result); + }); +}; + +Bitcoin.prototype._getConfirmationsDetail = function(transaction) { + var confirmations = 0; + if (transaction.__height >= 0) { + confirmations = this.height - transaction.__height + 1; + } + return confirmations; +}; + +Bitcoin.prototype._getAddressDetailsForTransaction = function(transaction, addressStrings) { + var result = { + addresses: {}, + satoshis: 0 + }; + + for (var inputIndex = 0; inputIndex < transaction.inputs.length; inputIndex++) { + var input = transaction.inputs[inputIndex]; + if (!input.script) { + continue; + } + var inputAddress = input.script.toAddress(this.node.network); + if (inputAddress) { + var inputAddressString = inputAddress.toString(); + if (addressStrings.indexOf(inputAddressString) >= 0) { + if (!result.addresses[inputAddressString]) { + result.addresses[inputAddressString] = { + inputIndexes: [inputIndex], + outputIndexes: [] + }; + } else { + result.addresses[inputAddressString].inputIndexes.push(inputIndex); + } + result.satoshis -= input.output.satoshis; + } + } + } + + for (var outputIndex = 0; outputIndex < transaction.outputs.length; outputIndex++) { + var output = transaction.outputs[outputIndex]; + if (!output.script) { + continue; + } + var outputAddress = output.script.toAddress(this.node.network); + if (outputAddress) { + var outputAddressString = outputAddress.toString(); + if (addressStrings.indexOf(outputAddressString) >= 0) { + if (!result.addresses[outputAddressString]) { + result.addresses[outputAddressString] = { + inputIndexes: [], + outputIndexes: [outputIndex] + }; + } else { + result.addresses[outputAddressString].outputIndexes.push(outputIndex); + } + result.satoshis += output.satoshis; + } + } + } + + return result; + +}; + +/** + * Will expand into a detailed transaction from a txid + * @param {Object} txid - A bitcoin transaction id + * @param {Function} callback + */ +Bitcoin.prototype._getDetailedTransaction = function(txid, options, next) { + var self = this; + var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool; + + self.getTransactionWithBlockInfo( + txid, + queryMempool, + function(err, transaction) { + if (err) { + return next(err); + } + + transaction.populateInputs(self, [], function(err) { + if (err) { + return next(err); + } + + var addressDetails = self._getAddressDetailsForTransaction(transaction, options.addressStrings); + + var details = { + addresses: addressDetails.addresses, + satoshis: addressDetails.satoshis, + height: transaction.__height, + confirmations: self._getConfirmationsDetail(transaction), + timestamp: transaction.__timestamp, + // TODO bitcore-lib should return null instead of throwing error on coinbase + fees: !transaction.isCoinbase() ? transaction.getFee() : null, + tx: transaction + }; + next(null, details); + }); + } + ); +}; + +Bitcoin.prototype._getAddressStrings = function(addresses) { + var addressStrings = []; + for (var i = 0; i < addresses.length; i++) { + var address = addresses[i]; + if (address instanceof bitcore.Address) { + addressStrings.push(address.toString()); + } else if (_.isString(address)) { + addressStrings.push(address); + } else { + throw new TypeError('Addresses are expected to be strings'); + } + } + return addressStrings; +}; + +Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) { + var self = this; + var addresses = [addressArg]; + if (addresses.length > this.maxAddressesQuery) { + return callback(new TypeError('Maximum number of addresses (' + this.maxAddressesQuery + ') exceeded')); + } + + var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool; + var addressStrings = this._getAddressStrings(addresses); + + self.getAddressTxids(addresses, {}, function(err, txids) { + if (err) { + return callback(err); + } + async.mapSeries( + txids, + function(txid, next) { + self._getDetailedTransaction(txid, { + queryMempool: queryMempool, + addressStrings: addressStrings + }, next); + }, + function(err, transactions) { + if (err) { + return callback(err); + } + callback(null, { + totalCount: txids.length, + items: transactions + }); + } + ); + }); +}; + +Bitcoin.prototype.getAddressSummary = function(addressArg, options, callback) { + // TODO: optional mempool + var self = this; + var summary = {}; + + if (_.isUndefined(options.queryMempool)) { + options.queryMempool = true; + } + + function getBalance(done) { + self.getAddressBalance(addressArg, options, function(err, data) { + if (err) { + return done(err); + } + summary.totalReceived = data.received; + summary.totalSpent = data.received - data.balance; + summary.balance = data.balance; + done(); + }); + } + + function getTxList(done) { + self.getAddressTxids(addressArg, options, function(err, txids) { + if (err) { + return done(err); + } + summary.txids = txids; + summary.appearances = txids.length; + done(); + }); + } + + var tasks = []; + if (!options.noBalance) { + tasks.push(getBalance); + } + if (!options.noTxList) { + tasks.push(getTxList); + } + + async.parallel(tasks, function(err) { + if (err) { + return callback(err); + } + callback(null, summary); + }); }; /** @@ -211,41 +563,80 @@ Bitcoin.prototype.syncPercentage = function() { * @param {String|Number} block - A block hash or block height number */ Bitcoin.prototype.getBlock = function(block, callback) { - return bindings.getBlock(block, callback); + // TODO apply performance patch to the RPC method for raw data + // TODO keep a cache of results + var self = this; + + function queryHeader(blockhash) { + self.client.getBlock(blockhash, false, function(err, response) { + if (err) { + return callback(err); + } + var block = bitcore.Block.fromString(response.result); + callback(null, block); + }); + } + + if (_.isNumber(block)) { + self.client.getBlockHash(block, function(err, response) { + if (err) { + return callback(err); + } + var blockhash = response.result; + queryHeader(blockhash); + }); + } else { + queryHeader(block); + } + }; -/** - * Will return the spent status of an output (not including the mempool) - * @param {String} txid - The transaction hash - * @param {Number} outputIndex - The output index in the transaction - * @returns {Boolean} If the output has been spent - */ -Bitcoin.prototype.isSpent = function(txid, outputIndex) { - return bindings.isSpent(txid, outputIndex); +Bitcoin.prototype.getBlockHashesByTimestamp = function(high, low, callback) { + var self = this; + self.client.getBlockHashes(high, low, function(err, response) { + if (err) { + return callback(err); + } + callback(null, response.result); + }); }; /** * Will return the block index information, the output will have the format: * { - * prevHash: '7194fcf33f58c96720f88f21ab28c34ebc5638c5f88d7838517deb27313b59de', - * hash: '7c5caf0af1bf16e3467b275a3b408bc1d251bff3c25be20cb727c47b66a7b216', + * prevHash: '000000004956cc2edd1a8caa05eacfa3c69f4c490bfc9ace820257834115ab35', + * nextHash: '0000000000629d100db387f37d0f37c51118f250fb0946310a8c37316cbc4028' + * hash: ' 00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e', * chainWork: '0000000000000000000000000000000000000000000000000000000000000016', * height: 10 * } * @param {String|Number} block - A block hash or block height * @returns {Object} */ -Bitcoin.prototype.getBlockIndex = function(block) { - return bindings.getBlockIndex(block); -}; +Bitcoin.prototype.getBlockHeader = function(block, callback) { + // TODO keep a cache of queries + var self = this; -/** - * Will return if the block is a part of the main chain. - * @param {String} blockHash - * @returns {Boolean} - */ -Bitcoin.prototype.isMainChain = function(blockHash) { - return bindings.isMainChain(blockHash); + function queryHeader(blockhash) { + self.client.getBlockHeader(blockhash, function(err, response) { + if (err) { + return callback(err); + } + callback(null, response.result); + }); + } + + if (_.isNumber(block)) { + self.client.getBlockHash(block, function(err, response) { + if (err) { + return callback(err); + } + var blockhash = response.result; + queryHeader(blockhash); + }); + } else { + queryHeader(block); + } }; /** @@ -253,8 +644,13 @@ Bitcoin.prototype.isMainChain = function(blockHash) { * @param {Number} blocks - The number of blocks for the transaction to be confirmed. * @returns {Number} */ -Bitcoin.prototype.estimateFee = function(blocks) { - return bindings.estimateFee(blocks); +Bitcoin.prototype.estimateFee = function(blocks, callback) { + this.client.estimateFee(blocks, function(err, response) { + if (err) { + return callback(err); + } + callback(null, response.result); + }); }; /** @@ -263,8 +659,21 @@ Bitcoin.prototype.estimateFee = function(blocks) { * @param {String} transaction - The hex string of the transaction * @param {Boolean} allowAbsurdFees - Enable large fees */ -Bitcoin.prototype.sendTransaction = function(transaction, allowAbsurdFees) { - return bindings.sendTransaction(transaction, allowAbsurdFees); +Bitcoin.prototype.sendTransaction = function(tx, allowAbsurdFees, callback) { + var txString; + if (tx instanceof Transaction) { + txString = tx.serialize(); + } else { + txString = tx; + } + + this.client.sendTransaction(txString, allowAbsurdFees, function(err, response) { + if (err) { + return callback(err); + } + callback(null, response.result); + }); + }; /** @@ -274,7 +683,18 @@ Bitcoin.prototype.sendTransaction = function(transaction, allowAbsurdFees) { * @param {Function} callback */ Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) { - return bindings.getTransaction(txid, queryMempool, callback); + // TODO keep an LRU cache available of transactions + this.client.getRawTransaction(txid, function(err, response) { + if (err) { + return callback(err); + } + if (!response.result) { + return callback(new errors.Transaction.NotFound()); + } + var tx = Transaction(); + tx.fromString(response.result); + callback(null, tx); + }); }; /** @@ -290,41 +710,41 @@ Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) { * @param {Function} callback */ Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) { - return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback); -}; - -/** - * Will return the entire mempool as an Array of transaction Buffers. - * @returns {Array} - */ -Bitcoin.prototype.getMempoolTransactions = function() { - return bindings.getMempoolTransactions(); -}; - -/** - * Will add a transaction to the mempool without any validation. This is used - * exclusively for testing purposes. - * @param {String} transaction - The hex string for the transaction - */ -Bitcoin.prototype.addMempoolUncheckedTransaction = function(transaction) { - return bindings.addMempoolUncheckedTransaction(transaction); + // TODO keep an LRU cache available of transactions + // TODO get information from txindex as an RPC method + this.client.getRawTransaction(txid, 1, function(err, response) { + if (err) { + return callback(err); + } + if (!response.result) { + return callback(new errors.Transaction.NotFound()); + } + var tx = Transaction(); + tx.fromString(response.result.hex); + tx.__blockHash = response.result.blockhash; + tx.__height = response.result.height; + tx.__timestamp = response.result.time; + callback(null, tx); + }); }; /** * Will get the best block hash for the chain. * @returns {String} */ -Bitcoin.prototype.getBestBlockHash = function() { - return bindings.getBestBlockHash(); +Bitcoin.prototype.getBestBlockHash = function(callback) { + // TODO keep an LRU cache available of transactions + this.client.getBestBlockHash(function(err, response) { + if (err) { + return callback(err); + } + callback(null, response.result); + }); }; -/** - * Will get the next block hash for a block hash. - * @param {String} hash - The starting block hash - * @returns {String} - */ -Bitcoin.prototype.getNextBlockHash = function(hash) { - return bindings.getNextBlockHash(hash); +Bitcoin.prototype.getInputForOutput = function(txid, index, options, callback) { + // TODO + setImmediate(callback); }; /** @@ -341,8 +761,13 @@ Bitcoin.prototype.getNextBlockHash = function(hash) { * errors: '' * } */ -Bitcoin.prototype.getInfo = function() { - return bindings.getInfo(); +Bitcoin.prototype.getInfo = function(callback) { + this.client.getInfo(function(err, response) { + if (err) { + return callback(err); + } + callback(null, response.result); + }); }; /** @@ -350,14 +775,18 @@ Bitcoin.prototype.getInfo = function() { * @param {Function} callback */ Bitcoin.prototype.stop = function(callback) { - return bindings.stop(function(err, status) { - if (err) { - return callback(err); - } else { - log.info(status); - return callback(); - } - }); + if (this.process) { + this.process.once('exit', function(err, status) { + if (err) { + return callback(err); + } else { + return callback(); + } + }); + this.process.kill('SIGHUP'); + } else { + callback(); + } }; module.exports = Bitcoin; diff --git a/lib/services/db.js b/lib/services/db.js deleted file mode 100644 index 679935ca..00000000 --- a/lib/services/db.js +++ /dev/null @@ -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; diff --git a/package.json b/package.json index 8b31e793..ef7a6b69 100644 --- a/package.json +++ b/package.json @@ -31,15 +31,8 @@ "bitcore-node": "./bin/bitcore-node" }, "scripts": { - "install": "./bin/install", - "build": "./bin/build", - "clean": "./bin/clean", - "package": "node bin/package.js", - "upload": "node bin/upload.js", - "start": "node bin/start.js", "test": "NODE_ENV=test mocha -R spec --recursive", - "coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive", - "libbitcoind": "node bin/start-libbitcoind.js" + "coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive" }, "tags": [ "bitcoin", @@ -49,44 +42,28 @@ "async": "^1.3.0", "bindings": "^1.2.1", "bitcore-lib": "^0.13.13", + "bitcoind-rpc": "^0.3.0", "body-parser": "^1.13.3", "colors": "^1.1.2", "commander": "^2.8.1", "errno": "^0.1.4", "express": "^4.13.3", - "leveldown": "bitpay/leveldown#bitpay-1.4.4", - "levelup": "^1.3.1", "liftoff": "^2.2.0", "memdown": "^1.0.0", "mkdirp": "0.5.0", - "nan": "^2.0.9", "npm": "^2.14.1", "semver": "^5.0.1", "socket.io": "bitpay/socket.io#bitpay-1.3.7", - "socket.io-client": "bitpay/socket.io-client#bitpay-1.3.7" + "socket.io-client": "bitpay/socket.io-client#bitpay-1.3.7", + "zmq": "^2.14.0" }, "devDependencies": { - "aws-sdk": "~2.0.0-rc.15", "benchmark": "1.0.0", - "bitcoin": "^2.3.2", - "bitcoind-rpc": "^0.3.0", "chai": "^3.0.0", "mocha": "~1.16.2", "proxyquire": "^1.3.1", "rimraf": "^2.4.2", - "sinon": "^1.15.4", - "bitcore-p2p": "~1.0.0" + "sinon": "^1.15.4" }, - "engines": { - "node": "^0.12 || ^4.2" - }, - "os": [ - "darwin", - "linux" - ], - "cpu": [ - "x64", - "arm" - ], "license": "MIT" } diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc deleted file mode 100644 index 4627b6d3..00000000 --- a/src/libbitcoind.cc +++ /dev/null @@ -1,1614 +0,0 @@ -/** - * bitcoind.js - a binding for node.js which links to libbitcoind.so/dylib. - * Copyright (c) 2015, BitPay (MIT License) - * - * libbitcoind.cc: - * A bitcoind node.js binding. - */ - -#include "libbitcoind.h" - -using namespace std; -using namespace boost; -using namespace node; -using namespace v8; -using Nan::New; -using Nan::Null; -using Nan::Set; -using Nan::ThrowError; -using Nan::GetCurrentContext; -using Nan::GetFunction; -using v8::FunctionTemplate; - -/** - * Bitcoin Globals - */ - -// These global functions and variables are -// required to be defined/exposed here. - -extern void WaitForShutdown(boost::thread_group* threadGroup); -static termios orig_termios; -extern CTxMemPool mempool; -extern int64_t nTimeBestReceived; - -/** - * Node.js Internal Function Templates - */ - -static void -tx_notifier(uv_async_t *handle); - -static void -txleave_notifier(uv_async_t *handle); - -static void -async_tip_update(uv_work_t *req); - -static void -async_tip_update_after(uv_work_t *req); - -static void -async_start_node(uv_work_t *req); - -static void -async_start_node_after(uv_work_t *req); - -static void -async_blocks_ready(uv_work_t *req); - -static void -async_blocks_ready_after(uv_work_t *req); - -static void -async_stop_node(uv_work_t *req); - -static void -async_stop_node_after(uv_work_t *req); - -static int -start_node(void); - -static void -start_node_thread(void); - -static void -async_get_block(uv_work_t *req); - -static void -async_get_block_after(uv_work_t *req); - -static void -async_get_tx(uv_work_t *req); - -static void -async_get_tx_after(uv_work_t *req); - -static void -async_get_tx_and_info(uv_work_t *req); - -static void -async_get_tx_and_info_after(uv_work_t *req); - -static bool -queueTx(const CTransaction&); - -static bool -queueTxLeave(const CTransaction&); - -extern "C" void -init(Handle); - -/** - * Private Global Variables - * Used only by bitcoind functions. - */ -static std::vector txQueue; -static std::vector txQueueLeave; -static uv_async_t txmon_async; -static uv_async_t txmonleave_async; -static Eternal txmon_callback; -static Eternal txmonleave_callback; -static bool txmon_callback_available; -static bool txmonleave_callback_available; - -static volatile bool shutdown_complete = false; -static char *g_data_dir = NULL; -static bool g_rpc = false; -static bool g_testnet = false; -static bool g_regtest = false; -static bool g_txindex = false; - -static boost::thread_group threadGroup; - -/** - * Private Structs - * Used for async functions and necessary linked lists at points. - */ - -struct async_tip_update_data { - uv_work_t req; - size_t result; - Isolate* isolate; - Persistent callback; -}; - -/** - * async_node_data - * Where the uv async request data resides. - */ - -struct async_block_ready_data { - uv_work_t req; - std::string err_msg; - std::string result; - Isolate* isolate; - Persistent callback; -}; - -/** - * async_node_data - * Where the uv async request data resides. - */ - -struct async_node_data { - uv_work_t req; - std::string err_msg; - std::string result; - std::string datadir; - bool rpc; - bool testnet; - bool regtest; - bool txindex; - Isolate* isolate; - Persistent callback; -}; - -/** - * async_block_data - */ - -struct async_block_data { - uv_work_t req; - std::string err_msg; - uint256 hash; - int64_t height; - char* buffer; - uint32_t size; - CBlock cblock; - CBlockIndex* cblock_index; - Isolate* isolate; - Persistent callback; -}; - -/** - * async_tx_data - */ - -struct async_tx_data { - uv_work_t req; - std::string err_msg; - std::string txid; - std::string blockHash; - uint32_t nTime; - int64_t height; - bool queryMempool; - CTransaction ctx; - Isolate* isolate; - Persistent callback; -}; - -/** - * Helpers - */ - -static bool -set_cooked(void); - -/** - * SyncPercentage() - * bitcoind.syncPercentage() - * provides a float value >= indicating the progress of the blockchain sync - */ -NAN_METHOD(SyncPercentage) { - const CChainParams& chainParams = Params(); - float progress = 0; - progress = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip()); - info.GetReturnValue().Set(progress * 100); -}; - -NAN_METHOD(GetBestBlockHash) { - LOCK(cs_main); - info.GetReturnValue().Set(New(chainActive.Tip()->GetBlockHash().GetHex()).ToLocalChecked()); -} - -NAN_METHOD(GetNextBlockHash) { - - if (info.Length() < 1 || !info[0]->IsString()) { - return ThrowError("Usage: bitcoind.getNextBlockHash(blockhash)"); - } - - CBlockIndex* pblockindex; - v8::String::Utf8Value param1(info[0]->ToString()); - std::string *hash = new std::string(*param1); - uint256 shash = uint256S(*hash); - pblockindex = mapBlockIndex[shash]; - CBlockIndex* pnextblockindex = chainActive.Next(pblockindex); - if (pnextblockindex) { - uint256 nexthash = pnextblockindex->GetBlockHash(); - std::string rethash = nexthash.ToString(); - info.GetReturnValue().Set(New(rethash).ToLocalChecked()); - } else { - info.GetReturnValue().Set(Null()); - } - -} - -/** - * IsSynced() - * bitcoind.isSynced() - * returns a boolean of bitcoin is fully synced - */ -NAN_METHOD(IsSynced) { - bool isDownloading = IsInitialBlockDownload(); - info.GetReturnValue().Set(New(!isDownloading)); -}; - -NAN_METHOD(StartTxMon) { - Isolate* isolate = info.GetIsolate(); - Local callback = Local::Cast(info[0]); - Eternal cb(isolate, callback); - txmon_callback = cb; - txmon_callback_available = true; - - CNodeSignals& nodeSignals = GetNodeSignals(); - nodeSignals.TxToMemPool.connect(&queueTx); - - uv_async_init(uv_default_loop(), &txmon_async, tx_notifier); - - info.GetReturnValue().Set(Null()); -}; - -NAN_METHOD(StartTxMonLeave) { - Isolate* isolate = info.GetIsolate(); - Local callback = Local::Cast(info[0]); - Eternal cb(isolate, callback); - txmonleave_callback = cb; - txmonleave_callback_available = true; - - CNodeSignals& nodeSignals = GetNodeSignals(); - nodeSignals.TxLeaveMemPool.connect(&queueTxLeave); - - uv_async_init(uv_default_loop(), &txmonleave_async, txleave_notifier); - - info.GetReturnValue().Set(Null()); -}; - -static void -tx_notifier(uv_async_t *handle) { - Isolate* isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - - Local results = Array::New(isolate); - int arrayIndex = 0; - - LOCK(cs_main); - BOOST_FOREACH(const CTransaction& tx, txQueue) { - - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - std::string stx = ssTx.str(); - Nan::MaybeLocal txBuffer = Nan::CopyBuffer((char *)stx.c_str(), stx.size()); - - uint256 hash = tx.GetHash(); - - Local obj = New(); - - Nan::Set(obj, New("buffer").ToLocalChecked(), txBuffer.ToLocalChecked()); - Nan::Set(obj, New("hash").ToLocalChecked(), New(hash.GetHex()).ToLocalChecked()); - Nan::Set(obj, New("mempool").ToLocalChecked(), New(true)); - - results->Set(arrayIndex, obj); - arrayIndex++; - } - - const unsigned argc = 1; - Local argv[argc] = { - Local::New(isolate, results) - }; - - Local cb = txmon_callback.Get(isolate); - - cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); - - txQueue.clear(); - -} -static bool -queueTx(const CTransaction& tx) { - LOCK(cs_main); - txQueue.push_back(tx); - uv_async_send(&txmon_async); - return true; -} - -static void -txleave_notifier(uv_async_t *handle) { - Isolate* isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - - Local results = Array::New(isolate); - int arrayIndex = 0; - - LOCK(cs_main); - BOOST_FOREACH(const CTransaction& tx, txQueueLeave) { - - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - std::string stx = ssTx.str(); - Nan::MaybeLocal txBuffer = Nan::CopyBuffer((char *)stx.c_str(), stx.size()); - - uint256 hash = tx.GetHash(); - - Local obj = New(); - - Nan::Set(obj, New("buffer").ToLocalChecked(), txBuffer.ToLocalChecked()); - Nan::Set(obj, New("hash").ToLocalChecked(), New(hash.GetHex()).ToLocalChecked()); - - results->Set(arrayIndex, obj); - arrayIndex++; - } - - const unsigned argc = 1; - Local argv[argc] = { - Local::New(isolate, results) - }; - - Local cb = txmonleave_callback.Get(isolate); - - cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); - - txQueueLeave.clear(); - -} -static bool -queueTxLeave(const CTransaction& tx) { - LOCK(cs_main); - txQueueLeave.push_back(tx); - uv_async_send(&txmonleave_async); - return true; -} - -/** - * Functions - */ - -NAN_METHOD(OnTipUpdate) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - - async_tip_update_data *req = new async_tip_update_data(); - - Local callback = Local::Cast(info[0]); - req->callback.Reset(isolate, callback); - req->req.data = req; - req->isolate = isolate; - - int status = uv_queue_work(uv_default_loop(), - &req->req, async_tip_update, - (uv_after_work_cb)async_tip_update_after); - - assert(status == 0); - - info.GetReturnValue().Set(Null()); -} - -static void -async_tip_update(uv_work_t *req) { - async_tip_update_data *data = reinterpret_cast(req->data); - - size_t lastHeight = chainActive.Height(); - - while(lastHeight == (size_t)chainActive.Height() && !shutdown_complete) { - usleep(1E6); - } - - data->result = chainActive.Height(); - -} - -static void -async_tip_update_after(uv_work_t *r) { - async_tip_update_data *req = reinterpret_cast(r->data); - Isolate* isolate = req->isolate; - HandleScope scope(isolate); - Local cb = Local::New(isolate, req->callback); - - Nan::TryCatch try_catch; - Local result = Undefined(isolate); - - if (!shutdown_complete) { - result = New(req->result); - } - Local argv[1] = { - Local::New(isolate, result) - }; - cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - } - - req->callback.Reset(); -} - -NAN_METHOD(OnBlocksReady) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - - async_block_ready_data *req = new async_block_ready_data(); - req->err_msg = std::string(""); - req->result = std::string(""); - req->req.data = req; - req->isolate = isolate; - - Local callback = Local::Cast(info[0]); - req->callback.Reset(isolate, callback); - - int status = uv_queue_work(uv_default_loop(), - &req->req, async_blocks_ready, - (uv_after_work_cb)async_blocks_ready_after); - - assert(status == 0); - - info.GetReturnValue().Set(Null()); -} - -/** - * async_start_node() - * Call start_node() and start all our boost threads. - */ - -static void -async_blocks_ready(uv_work_t *req) { - async_block_ready_data *data = reinterpret_cast(req->data); - data->result = std::string(""); - - while(!chainActive.Tip()) { - usleep(1E6); - } - - CBlockIndex* tip = chainActive.Tip(); - uint256 tipHash = tip->GetBlockHash(); - - // Wait to be able to query for blocks by hash - while(mapBlockIndex.count(tipHash) == 0) { - usleep(1E6); - } - - // Wait for chainActive to be able to get the hash - // for the genesis block for querying blocks by height - while(chainActive[0] == NULL) { - usleep(1E6); - } - - //If the wallet is enabled, then we should make sure we can load it -#ifdef ENABLE_WALLET - while(pwalletMain == NULL || RPCIsInWarmup(NULL)) { - usleep(1E6); - } -#endif - - // Wait until we can get a lock on cs_main - // And therefore ready to be able to quickly - // query for transactions from the mempool. - LOCK(cs_main); - { - return; - } - -} - -static void -async_blocks_ready_after(uv_work_t *r) { - async_block_ready_data* req = reinterpret_cast(r->data); - Isolate* isolate = req->isolate; - HandleScope scope(isolate); - - Nan::TryCatch try_catch; - Local cb = Local::New(isolate, req->callback); - - if (req->err_msg != "") { - Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); - Local argv[1] = { err }; - cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); - } else { - Local argv[2] = { - v8::Null(isolate), - Local::New(isolate, New(req->result).ToLocalChecked()) - }; - cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); - } - - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - } - - req->callback.Reset(); -} - -/** - * StartBitcoind() - * bitcoind.start(callback) - * Start the bitcoind node with AppInit2() on a separate thread. - */ -NAN_METHOD(StartBitcoind) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - - Local callback; - std::string datadir = std::string(""); - bool rpc = false; - bool testnet = false; - bool regtest = false; - bool txindex = false; - - if (info.Length() >= 2 && info[0]->IsObject() && info[1]->IsFunction()) { - Local options = Local::Cast(info[0]); - if (options->Get(New("datadir").ToLocalChecked())->IsString()) { - String::Utf8Value datadir_(options->Get(New("datadir").ToLocalChecked())->ToString()); - datadir = std::string(*datadir_); - } - if (options->Get(New("rpc").ToLocalChecked())->IsBoolean()) { - rpc = options->Get(New("rpc").ToLocalChecked())->ToBoolean()->IsTrue(); - } - if (options->Get(New("network").ToLocalChecked())->IsString()) { - String::Utf8Value network_(options->Get(New("network").ToLocalChecked())->ToString()); - std::string network = std::string(*network_); - if (network == "testnet") { - testnet = true; - } else if (network == "regtest") { - regtest = true; - } - } - if (options->Get(New("txindex").ToLocalChecked())->IsBoolean()) { - txindex = options->Get(New("txindex").ToLocalChecked())->ToBoolean()->IsTrue(); - } - callback = Local::Cast(info[1]); - } else if (info.Length() >= 2 - && (info[0]->IsUndefined() || info[0]->IsNull()) - && info[1]->IsFunction()) { - callback = Local::Cast(info[1]); - } else if (info.Length() >= 1 && info[0]->IsFunction()) { - callback = Local::Cast(info[0]); - } else { - return ThrowError( - "Usage: bitcoind.start(callback)"); - } - - // - // Run bitcoind's StartNode() on a separate thread. - // - - async_node_data *req = new async_node_data(); - req->err_msg = std::string(""); - req->result = std::string(""); - req->datadir = datadir; - req->rpc = rpc; - req->testnet = testnet; - req->regtest = regtest; - req->txindex = txindex; - - req->isolate = isolate; - req->callback.Reset(isolate, callback); - req->req.data = req; - - int status = uv_queue_work(uv_default_loop(), - &req->req, async_start_node, - (uv_after_work_cb)async_start_node_after); - - assert(status == 0); - - info.GetReturnValue().Set(Null()); -} - -/** - * async_start_node() - * Call start_node() and start all our boost threads. - */ - -static void -async_start_node(uv_work_t *req) { - async_node_data *data = reinterpret_cast(req->data); - if (data->datadir != "") { - g_data_dir = (char *)data->datadir.c_str(); - } else { - g_data_dir = (char *)malloc(sizeof(char) * 512); - snprintf(g_data_dir, sizeof(char) * 512, "%s/.bitcoind.js", getenv("HOME")); - } - g_rpc = (bool)data->rpc; - g_testnet = (bool)data->testnet; - g_regtest = (bool)data->regtest; - g_txindex = (bool)data->txindex; - tcgetattr(STDIN_FILENO, &orig_termios); - start_node(); - data->result = std::string("bitcoind opened."); -} - -/** - * async_start_node_after() - * Execute our callback. - */ - -static void -async_start_node_after(uv_work_t *r) { - async_node_data *req = reinterpret_cast(r->data); - Isolate* isolate = req->isolate; - HandleScope scope(isolate); - - Nan::TryCatch try_catch; - Local cb = Local::New(isolate, req->callback); - - if (req->err_msg != "") { - Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); - Local argv[1] = { err }; - cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); - } else { - Local argv[2] = { - v8::Null(isolate), - Local::New(isolate, New(req->result).ToLocalChecked()) - }; - cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); - } - - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - } - - req->callback.Reset(); -} - -/** - * start_node(void) - * Start AppInit2() on a separate thread, wait for - * Unfortunately, we need to wait for the initialization - * to unhook the signal handlers so we can use them - * from node.js in javascript. - */ - -static int -start_node(void) { - SetupEnvironment(); - - noui_connect(); - - new boost::thread(boost::bind(&start_node_thread)); - return 0; -} - -static void -start_node_thread(void) { - CScheduler scheduler; - - // Workaround for AppInit2() arg parsing. Not ideal, but it works. - int argc = 0; - char **argv = (char **)malloc((4 + 1) * sizeof(char **)); - - argv[argc] = (char *)"bitcoind"; - argc++; - - if (g_data_dir) { - const int argl = 9 + strlen(g_data_dir) + 1; - char *arg = (char *)malloc(sizeof(char) * argl); - int w = snprintf(arg, argl, "-datadir=%s", g_data_dir); - if (w >= 10 && w <= argl) { - arg[w] = '\0'; - argv[argc] = arg; - argc++; - } else { - if (set_cooked()) { - fprintf(stderr, "bitcoind.js: Bad -datadir value.\n"); - } - } - } - - if (g_rpc) { - argv[argc] = (char *)"-server"; - argc++; - } - - if (g_testnet) { - argv[argc] = (char *)"-testnet"; - argc++; - } - - if (g_regtest) { - argv[argc] = (char *)"-regtest"; - argc++; - } - - argv[argc] = (char *)"-txindex"; - argc++; - - argv[argc] = NULL; - - bool fRet = false; - try { - ParseParameters((const int)argc, (const char **)argv); - - if (!boost::filesystem::is_directory(GetDataDir(false))) { - if (set_cooked()) { - fprintf(stderr, - "bitcoind.js: Specified data directory \"%s\" does not exist.\n", - mapArgs["-datadir"].c_str()); - } - shutdown_complete = true; - _exit(1); - return; - } - - try { - ReadConfigFile(mapArgs, mapMultiArgs); - } catch(std::exception &e) { - if (set_cooked()) { - fprintf(stderr, - "bitcoind.js: Error reading configuration file: %s\n", e.what()); - } - shutdown_complete = true; - _exit(1); - return; - } - - if (!SelectParamsFromCommandLine()) { - if (set_cooked()) { - fprintf(stderr, - "bitcoind.js: Invalid combination of -regtest and -testnet.\n"); - } - shutdown_complete = true; - _exit(1); - return; - } - - CreatePidFile(GetPidFile(), getpid()); - - fRet = AppInit2(threadGroup, scheduler); - - } catch (std::exception& e) { - if (set_cooked()) { - fprintf(stderr, "bitcoind.js: AppInit2(): std::exception\n"); - } - } catch (...) { - if (set_cooked()) { - fprintf(stderr, "bitcoind.js: AppInit2(): other exception\n"); - } - } - - if (!fRet) - { - threadGroup.interrupt_all(); - } else { - WaitForShutdown(&threadGroup); - } - Shutdown(); - shutdown_complete = true; - -} - -/** - * StopBitcoind() - * bitcoind.stop(callback) - */ - -NAN_METHOD(StopBitcoind) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - - if (info.Length() < 1 || !info[0]->IsFunction()) { - return ThrowError( - "Usage: bitcoind.stop(callback)"); - } - - Local callback = Local::Cast(info[0]); - - // - // Run bitcoind's StartShutdown() on a separate thread. - // - - async_node_data *req = new async_node_data(); - req->err_msg = std::string(""); - req->result = std::string(""); - req->callback.Reset(isolate, callback); - req->req.data = req; - req->isolate = isolate; - - int status = uv_queue_work(uv_default_loop(), - &req->req, async_stop_node, - (uv_after_work_cb)async_stop_node_after); - - assert(status == 0); - info.GetReturnValue().Set(Null()); - -} - -/** - * async_stop_node() - * Call StartShutdown() to join the boost threads, which will call Shutdown() - * and set shutdown_complete to true to notify the main node.js thread. - */ - -static void -async_stop_node(uv_work_t *req) { - async_node_data *data = reinterpret_cast(req->data); - - StartShutdown(); - - while(!shutdown_complete) { - usleep(1E6); - } - data->result = std::string("bitcoind shutdown."); -} - -/** - * async_stop_node_after() - * Execute our callback. - */ - -static void -async_stop_node_after(uv_work_t *r) { - async_node_data* req = reinterpret_cast(r->data); - Isolate* isolate = req->isolate; - HandleScope scope(isolate); - - Nan::TryCatch try_catch; - Local cb = Local::New(isolate, req->callback); - - if (req->err_msg != "") { - Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); - Local argv[1] = { err }; - cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); - } else { - Local argv[2] = { - Local::New(isolate, Null()), - Local::New(isolate, New(req->result).ToLocalChecked()) - }; - cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); - } - - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - } - req->callback.Reset(); -} - -/** - * GetBlock() - * bitcoind.getBlock([blockhash,blockheight], callback) - * Read any block from disk asynchronously. - */ - -NAN_METHOD(GetBlock) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - if (info.Length() < 2 - || (!info[0]->IsString() && !info[0]->IsNumber()) - || !info[1]->IsFunction()) { - return ThrowError( - "Usage: bitcoind.getBlock([blockhash,blockheight], callback)"); - } - - async_block_data *req = new async_block_data(); - - if (info[0]->IsNumber()) { - int64_t height = info[0]->IntegerValue(); - req->err_msg = std::string(""); - req->height = height; - } else { - std::string hash = *Nan::Utf8String(info[0]); - req->err_msg = std::string(""); - req->hash = uint256S(hash); - req->height = -1; - } - - Local callback = Local::Cast(info[1]); - req->req.data = req; - req->isolate = isolate; - req->callback.Reset(isolate, callback); - - int status = uv_queue_work(uv_default_loop(), - &req->req, async_get_block, - (uv_after_work_cb)async_get_block_after); - - assert(status == 0); - - info.GetReturnValue().Set(Null()); -} - -static void -async_get_block(uv_work_t *req) { - async_block_data* data = reinterpret_cast(req->data); - - CBlockIndex* pblockindex; - - if (data->height != -1) { - pblockindex = chainActive[data->height]; - if (pblockindex == NULL) { - data->err_msg = std::string("Block not found."); - return; - } - } else { - if (mapBlockIndex.count(data->hash) == 0) { - data->err_msg = std::string("Block not found."); - return; - } else { - pblockindex = mapBlockIndex[data->hash]; - } - } - - const CDiskBlockPos& pos = pblockindex->GetBlockPos(); - - // We can read directly from the file, and pass that, we don't need to - // deserialize the entire block only for it to then be serialized - // and then deserialized again in JavaScript - - // Open history file to read - CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION); - if (filein.IsNull()) { - data->err_msg = std::string("ReadBlockFromDisk: OpenBlockFile failed"); - return; - } - - // Get the actual file, seeked position and rewind a uint32_t - FILE* blockFile = filein.release(); - long int filePos = ftell(blockFile); - fseek(blockFile, filePos - sizeof(uint32_t), SEEK_SET); - - // Read the size of the block - uint32_t size = 0; - fread(&size, sizeof(uint32_t), 1, blockFile); - - // Read block - char* buffer = (char *)malloc(sizeof(char) * size); - fread((void *)buffer, sizeof(char), size, blockFile); - fclose(blockFile); - - data->buffer = buffer; - data->size = size; - data->cblock_index = pblockindex; - -} - -static void -async_get_block_after(uv_work_t *r) { - async_block_data* req = reinterpret_cast(r->data); - Isolate *isolate = req->isolate; - HandleScope scope(isolate); - - Nan::TryCatch try_catch; - Local cb = Local::New(isolate, req->callback); - - if (req->err_msg != "") { - Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); - Local argv[1] = { err }; - cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); - } else { - - Nan::MaybeLocal rawNodeBuffer = Nan::NewBuffer(req->buffer, req->size); - - Local argv[2] = { - Local::New(isolate, Null()), - rawNodeBuffer.ToLocalChecked() - }; - cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); - } - - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - } - - req->callback.Reset(); -} - -/** - * GetTransaction() - * bitcoind.getTransaction(txid, queryMempool, callback) - * Read any transaction from disk asynchronously. - */ - -NAN_METHOD(GetTransaction) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - if (info.Length() < 3 - || !info[0]->IsString() - || !info[1]->IsBoolean() - || !info[2]->IsFunction()) { - return ThrowError( - "Usage: daemon.getTransaction(txid, queryMempool, callback)"); - } - - std::string txid = *Nan::Utf8String(info[0]); - - bool queryMempool = info[1]->BooleanValue(); - Local callback = Local::Cast(info[2]); - - async_tx_data *req = new async_tx_data(); - - req->err_msg = std::string(""); - req->txid = txid; - req->queryMempool = queryMempool; - req->isolate = isolate; - req->req.data = req; - req->callback.Reset(isolate, callback); - - int status = uv_queue_work(uv_default_loop(), - &req->req, async_get_tx, - (uv_after_work_cb)async_get_tx_after); - - assert(status == 0); - - info.GetReturnValue().Set(Null()); -} - -static void -async_get_tx(uv_work_t *req) { - async_tx_data* data = reinterpret_cast(req->data); - - uint256 blockhash; - uint256 hash = uint256S(data->txid); - CTransaction ctx; - - if (data->queryMempool) { - LOCK(cs_main); - { - if (mempool.lookup(hash, ctx)) - { - data->ctx = ctx; - return; - } - } - } - - CDiskTxPos postx; - if (pblocktree->ReadTxIndex(hash, postx)) { - - CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); - - if (file.IsNull()) { - data->err_msg = std::string("%s: OpenBlockFile failed", __func__); - return; - } - - const int HEADER_SIZE = sizeof(int32_t) + sizeof(uint32_t) * 3 + sizeof(char) * 64; - - try { - fseek(file.Get(), postx.nTxOffset + HEADER_SIZE, SEEK_CUR); - file >> ctx; - data->ctx = ctx; - } catch (const std::exception& e) { - data->err_msg = std::string("Deserialize or I/O error - %s", __func__); - return; - } - - } - -} - -static void -async_get_tx_after(uv_work_t *r) { - async_tx_data* req = reinterpret_cast(r->data); - Isolate* isolate = req->isolate; - HandleScope scope(isolate); - - CTransaction ctx = req->ctx; - Nan::TryCatch try_catch; - Local cb = Local::New(isolate, req->callback); - - if (req->err_msg != "") { - Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); - Local argv[1] = { err }; - cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); - } else { - - if (!ctx.IsNull()) { - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << ctx; - std::string stx = ssTx.str(); - Nan::MaybeLocal result = Nan::CopyBuffer((char *)stx.c_str(), stx.size()); - Local argv[2] = { - Local::New(isolate, Null()), - result.ToLocalChecked() - }; - cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); - - } else { - Local argv[2] = { - Local::New(isolate, Null()), - Local::New(isolate, Null()) - }; - cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); - } - - } - - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - } - - req->callback.Reset(); -} - -/** - * GetTransactionWithBlockInfo() - * bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback) - * Read any transaction from disk asynchronously with block timestamp and height. - */ - -NAN_METHOD(GetTransactionWithBlockInfo) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - if (info.Length() < 3 - || !info[0]->IsString() - || !info[1]->IsBoolean() - || !info[2]->IsFunction()) { - return ThrowError( - "Usage: bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback)"); - } - - String::Utf8Value txid_(info[0]->ToString()); - bool queryMempool = info[1]->BooleanValue(); - Local callback = Local::Cast(info[2]); - - async_tx_data *req = new async_tx_data(); - - req->err_msg = std::string(""); - req->txid = std::string(""); - - std::string txid = std::string(*txid_); - - req->txid = txid; - req->queryMempool = queryMempool; - req->req.data = req; - req->isolate = isolate; - req->callback.Reset(isolate, callback); - - int status = uv_queue_work(uv_default_loop(), - &req->req, async_get_tx_and_info, - (uv_after_work_cb)async_get_tx_and_info_after); - - assert(status == 0); - - info.GetReturnValue().Set(Null()); -} - -static void -async_get_tx_and_info(uv_work_t *req) { - async_tx_data* data = reinterpret_cast(req->data); - - uint256 hash = uint256S(data->txid); - uint256 blockHash; - CTransaction ctx; - - if (data->queryMempool) { - LOCK(mempool.cs); - map::const_iterator i = mempool.mapTx.find(hash); - if (i != mempool.mapTx.end()) { - data->ctx = i->second.GetTx(); - data->nTime = i->second.GetTime(); - data->height = -1; - return; - } - } - - CDiskTxPos postx; - if (pblocktree->ReadTxIndex(hash, postx)) { - - CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); - - if (file.IsNull()) { - data->err_msg = std::string("%s: OpenBlockFile failed", __func__); - return; - } - - CBlockHeader blockHeader; - - try { - // Read header first to get block timestamp and hash - file >> blockHeader; - blockHash = blockHeader.GetHash(); - data->blockHash = blockHash.GetHex(); - data->nTime = blockHeader.nTime; - fseek(file.Get(), postx.nTxOffset, SEEK_CUR); - file >> ctx; - data->ctx = ctx; - } catch (const std::exception& e) { - data->err_msg = std::string("Deserialize or I/O error - %s", __func__); - return; - } - - // get block height - CBlockIndex* blockIndex; - - if (mapBlockIndex.count(blockHash) == 0) { - data->height = -1; - } else { - blockIndex = mapBlockIndex[blockHash]; - if (!chainActive.Contains(blockIndex)) { - data->height = -1; - } else { - data->height = blockIndex->nHeight; - } - } - - } - -} - -static void -async_get_tx_and_info_after(uv_work_t *r) { - async_tx_data* req = reinterpret_cast(r->data); - Isolate* isolate = req->isolate; - HandleScope scope(isolate); - - CTransaction ctx = req->ctx; - Nan::TryCatch try_catch; - Local cb = Local::New(isolate, req->callback); - Local obj = New(); - - if (req->err_msg != "") { - Local err = Exception::Error(New(req->err_msg).ToLocalChecked()); - Local argv[1] = { err }; - cb->Call(isolate->GetCurrentContext()->Global(), 1, argv); - } else { - - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << ctx; - std::string stx = ssTx.str(); - Nan::MaybeLocal rawNodeBuffer = Nan::CopyBuffer((char *)stx.c_str(), stx.size()); - - Nan::Set(obj, New("blockHash").ToLocalChecked(), New(req->blockHash).ToLocalChecked()); - Nan::Set(obj, New("height").ToLocalChecked(), New(req->height)); - Nan::Set(obj, New("timestamp").ToLocalChecked(), New(req->nTime)); - Nan::Set(obj, New("buffer").ToLocalChecked(), rawNodeBuffer.ToLocalChecked()); - - Local argv[2] = { - Local::New(isolate, Null()), - obj - }; - cb->Call(isolate->GetCurrentContext()->Global(), 2, argv); - } - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - } - req->callback.Reset(); -} - -/** - * IsSpent() - * bitcoind.isSpent() - * Determine if an outpoint is spent - */ -NAN_METHOD(IsSpent) { - if (info.Length() > 2) { - return ThrowError( - "Usage: bitcoind.isSpent(txid, outputIndex)"); - } - - String::Utf8Value arg(info[0]->ToString()); - std::string argStr = std::string(*arg); - const uint256 txid = uint256S(argStr); - int outputIndex = info[1]->IntegerValue(); - - { - LOCK(mempool.cs); - CCoinsView dummy; - CCoinsViewCache view(&dummy); - - CCoinsViewMemPool viewMemPool(pcoinsTip, mempool); - view.SetBackend(viewMemPool); - - if (view.HaveCoins(txid)) { - const CCoins* coins = view.AccessCoins(txid); - if (coins && coins->IsAvailable(outputIndex)) { - info.GetReturnValue().Set(New(false)); - return; - } - } - } - info.GetReturnValue().Set(New(true)); -}; - -/** - * GetBlockIndex() - * bitcoind.getBlockIndex() - * Get index information about a block by hash including: - * - the total amount of work (expected number of hashes) in the chain up to - * and including this block. - * - the previous hash of the block - */ -NAN_METHOD(GetBlockIndex) { - Isolate* isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - - CBlockIndex* blockIndex; - - if (info[0]->IsNumber()) { - int64_t height = info[0]->IntegerValue(); - blockIndex = chainActive[height]; - - if (blockIndex == NULL) { - info.GetReturnValue().Set(Null()); - return; - } - - } else { - String::Utf8Value hash_(info[0]->ToString()); - std::string hashStr = std::string(*hash_); - uint256 hash = uint256S(hashStr); - if (mapBlockIndex.count(hash) == 0) { - info.GetReturnValue().Set(Null()); - } else { - blockIndex = mapBlockIndex[hash]; - } - } - - Local obj = New(); - - arith_uint256 cw = blockIndex->nChainWork; - CBlockIndex* prevBlockIndex = blockIndex->pprev; - if (&prevBlockIndex->phashBlock != 0) { - const uint256* prevHash = prevBlockIndex->phashBlock; - Nan::Set(obj, New("prevHash").ToLocalChecked(), New(prevHash->GetHex()).ToLocalChecked()); - } else { - Nan::Set(obj, New("prevHash").ToLocalChecked(), Null()); - } - - Nan::Set(obj, New("hash").ToLocalChecked(), New(blockIndex->phashBlock->GetHex()).ToLocalChecked()); - Nan::Set(obj, New("chainWork").ToLocalChecked(), New(cw.GetHex()).ToLocalChecked()); - - Nan::Set(obj, New("height").ToLocalChecked(), New(blockIndex->nHeight)); - - info.GetReturnValue().Set(obj); -}; - - -/** - * IsMainChain() - * bitcoind.isMainChain() - * - * @param {string} - block hash - * @returns {boolean} - True if the block is in the main chain. False if it is an orphan. - */ -NAN_METHOD(IsMainChain) { - Isolate* isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - - CBlockIndex* blockIndex; - - String::Utf8Value hash_(info[0]->ToString()); - std::string hashStr = std::string(*hash_); - uint256 hash = uint256S(hashStr); - if (mapBlockIndex.count(hash) == 0) { - info.GetReturnValue().Set(Null()); - } else { - blockIndex = mapBlockIndex[hash]; - } - - if (chainActive.Contains(blockIndex)) { - info.GetReturnValue().Set(New(true)); - } else { - info.GetReturnValue().Set(New(false)); - } -} - -/** - * GetInfo() - * bitcoind.getInfo() - * Get miscellaneous information - */ - -NAN_METHOD(GetInfo) { - if (info.Length() > 0) { - return ThrowError( - "Usage: bitcoind.getInfo()"); - } - - Local obj = New(); - - proxyType proxy; - GetProxy(NET_IPV4, proxy); - - Nan::Set(obj, New("version").ToLocalChecked(), New(CLIENT_VERSION)); - Nan::Set(obj, New("protocolversion").ToLocalChecked(), New(PROTOCOL_VERSION)); - Nan::Set(obj, New("blocks").ToLocalChecked(), New((int)chainActive.Height())->ToInt32()); - Nan::Set(obj, New("timeoffset").ToLocalChecked(), New(GetTimeOffset())); - Nan::Set(obj, New("connections").ToLocalChecked(), New((int)vNodes.size())->ToInt32()); - Nan::Set(obj, New("difficulty").ToLocalChecked(), New((double)GetDifficulty())); - Nan::Set(obj, New("testnet").ToLocalChecked(), New(Params().NetworkIDString() == "test")); - Nan::Set(obj, New("network").ToLocalChecked(), New(Params().NetworkIDString()).ToLocalChecked()); - Nan::Set(obj, New("relayfee").ToLocalChecked(), New(::minRelayTxFee.GetFeePerK())); // double - Nan::Set(obj, New("errors").ToLocalChecked(), New(GetWarnings("statusbar")).ToLocalChecked()); - - info.GetReturnValue().Set(obj); -} - -/** - * Estimate Fee - * @blocks {number} - The number of blocks until confirmed - */ - -NAN_METHOD(EstimateFee) { - Isolate* isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - - int nBlocks = info[0]->NumberValue(); - if (nBlocks < 1) { - nBlocks = 1; - } - - CFeeRate feeRate = mempool.estimateFee(nBlocks); - - if (feeRate == CFeeRate(0)) { - info.GetReturnValue().Set(New(-1.0)); - return; - } - - CAmount nFee = feeRate.GetFeePerK(); - - info.GetReturnValue().Set(New(nFee)); - -} - -/** - * Send Transaction - * bitcoind.sendTransaction() - * Will add a transaction to the mempool and broadcast to connected peers. - * @param {string} - The serialized hex string of the transaction. - * @param {boolean} - Skip absurdly high fee checks - */ -NAN_METHOD(SendTransaction) { - Isolate* isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - - LOCK(cs_main); - - // Decode the transaction - v8::String::Utf8Value param1(info[0]->ToString()); - std::string *input = new std::string(*param1); - CTransaction tx; - if (!DecodeHexTx(tx, *input)) { - return ThrowError("TX decode failed"); - } - uint256 hashTx = tx.GetHash(); - - // Skip absurdly high fee check - bool allowAbsurdFees = false; - if (info.Length() > 1) { - allowAbsurdFees = info[1]->BooleanValue(); - } - - CCoinsViewCache &view = *pcoinsTip; - const CCoins* existingCoins = view.AccessCoins(hashTx); - bool fHaveMempool = mempool.exists(hashTx); - bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000; - if (!fHaveMempool && !fHaveChain) { - CValidationState state; - bool fMissingInputs; - - // Attempt to add the transaction to the mempool - if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, !allowAbsurdFees)) { - if (state.IsInvalid()) { - return ThrowError((boost::lexical_cast(state.GetRejectCode()) + ": " + state.GetRejectReason()).c_str()); - } else { - if (fMissingInputs) { - return ThrowError("Missing inputs"); - } - return ThrowError(state.GetRejectReason().c_str()); - } - } - } else if (fHaveChain) { - return ThrowError("transaction already in block chain"); - } - - // Relay the transaction connect peers - RelayTransaction(tx); - - info.GetReturnValue().Set(Local::New(isolate, New(hashTx.GetHex()).ToLocalChecked())); -} - -/** - * GetMempoolTransactions - * bitcoind.getMempoolTransactions() - * Will return an array of transaction buffers. - */ -NAN_METHOD(GetMempoolTransactions) { - Isolate* isolate = info.GetIsolate(); - HandleScope scope(isolate); - - Local transactions = Array::New(isolate); - int arrayIndex = 0; - - { - LOCK(mempool.cs); - - // Iterate through the entire mempool - std::map mapTx = mempool.mapTx; - - for(std::map::iterator it = mapTx.begin(); - it != mapTx.end(); - it++) { - CTxMemPoolEntry entry = it->second; - const CTransaction tx = entry.GetTx(); - CDataStream dataStreamTx(SER_NETWORK, PROTOCOL_VERSION); - dataStreamTx << tx; - std::string txString = dataStreamTx.str(); - Nan::MaybeLocal txBuffer = Nan::CopyBuffer((char *)txString.c_str(), txString.size()); - transactions->Set(arrayIndex, txBuffer.ToLocalChecked()); - arrayIndex++; - } - } - - info.GetReturnValue().Set(transactions); - -} - -/** - * AddMempoolUncheckedTransaction - */ -NAN_METHOD(AddMempoolUncheckedTransaction) { - v8::String::Utf8Value param1(info[0]->ToString()); - std::string *input = new std::string(*param1); - - CTransaction tx; - if (!DecodeHexTx(tx, *input)) { - return ThrowError("could not decode tx"); - } - bool added = mempool.addUnchecked(tx.GetHash(), CTxMemPoolEntry(tx, 0, 0, 0.0, 1)); - info.GetReturnValue().Set(New(added)); - -} - -/** - * Helpers - */ - -static bool -set_cooked(void) { - uv_tty_t tty; - tty.mode = 1; - tty.orig_termios = orig_termios; - - if (!uv_tty_set_mode(&tty, 0)) { - printf("\x1b[H\x1b[J"); - return true; - } - - return false; -} - -/** - * Init() - * Initialize the singleton object known as bitcoind. - */ -NAN_MODULE_INIT(init) { - Nan::Set(target, New("start").ToLocalChecked(), GetFunction(New(StartBitcoind)).ToLocalChecked()); - Nan::Set(target, New("onBlocksReady").ToLocalChecked(), GetFunction(New(OnBlocksReady)).ToLocalChecked()); - Nan::Set(target, New("onTipUpdate").ToLocalChecked(), GetFunction(New(OnTipUpdate)).ToLocalChecked()); - Nan::Set(target, New("stop").ToLocalChecked(), GetFunction(New(StopBitcoind)).ToLocalChecked()); - Nan::Set(target, New("getBlock").ToLocalChecked(), GetFunction(New(GetBlock)).ToLocalChecked()); - Nan::Set(target, New("getTransaction").ToLocalChecked(), GetFunction(New(GetTransaction)).ToLocalChecked()); - Nan::Set(target, New("getTransactionWithBlockInfo").ToLocalChecked(), GetFunction(New(GetTransactionWithBlockInfo)).ToLocalChecked()); - Nan::Set(target, New("getInfo").ToLocalChecked(), GetFunction(New(GetInfo)).ToLocalChecked()); - Nan::Set(target, New("isSpent").ToLocalChecked(), GetFunction(New(IsSpent)).ToLocalChecked()); - Nan::Set(target, New("getBlockIndex").ToLocalChecked(), GetFunction(New(GetBlockIndex)).ToLocalChecked()); - Nan::Set(target, New("isMainChain").ToLocalChecked(), GetFunction(New(IsMainChain)).ToLocalChecked()); - Nan::Set(target, New("getMempoolTransactions").ToLocalChecked(), GetFunction(New(GetMempoolTransactions)).ToLocalChecked()); - Nan::Set(target, New("addMempoolUncheckedTransaction").ToLocalChecked(), GetFunction(New(AddMempoolUncheckedTransaction)).ToLocalChecked()); - Nan::Set(target, New("sendTransaction").ToLocalChecked(), GetFunction(New(SendTransaction)).ToLocalChecked()); - Nan::Set(target, New("estimateFee").ToLocalChecked(), GetFunction(New(EstimateFee)).ToLocalChecked()); - Nan::Set(target, New("startTxMon").ToLocalChecked(), GetFunction(New(StartTxMon)).ToLocalChecked()); - Nan::Set(target, New("startTxMonLeave").ToLocalChecked(), GetFunction(New(StartTxMonLeave)).ToLocalChecked()); - Nan::Set(target, New("syncPercentage").ToLocalChecked(), GetFunction(New(SyncPercentage)).ToLocalChecked()); - Nan::Set(target, New("isSynced").ToLocalChecked(), GetFunction(New(IsSynced)).ToLocalChecked()); - Nan::Set(target, New("getBestBlockHash").ToLocalChecked(), GetFunction(New(GetBestBlockHash)).ToLocalChecked()); - Nan::Set(target, New("getNextBlockHash").ToLocalChecked(), GetFunction(New(GetNextBlockHash)).ToLocalChecked()); -} - -NODE_MODULE(libbitcoind, init); diff --git a/src/libbitcoind.h b/src/libbitcoind.h deleted file mode 100644 index 90d2ca97..00000000 --- a/src/libbitcoind.h +++ /dev/null @@ -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 -#include -#include -#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