diff --git a/Dockerfile b/Dockerfile index 92b0b779c..7a4308f7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get -y update && \ apt-get -y upgrade && \ apt-get -y install build-essential pkg-config libc6-dev m4 g++-multilib autoconf libtool ncurses-dev \ unzip python zlib1g-dev wget bsdmainutils automake libssl-dev libprotobuf-dev \ - protobuf-compiler libqrencode-dev libdb++-dev software-properties-common libcurl4-openssl-dev && \ + protobuf-compiler libqrencode-dev libdb++-dev software-properties-common libcurl4-openssl-dev curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/Makefile.am b/Makefile.am index 143b2cc68..d0c289ee1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,7 @@ endif BITCOIND_BIN=$(top_builddir)/src/zcashd$(EXEEXT) BITCOIN_CLI_BIN=$(top_builddir)/src/zcash-cli$(EXEEXT) +WALLET_UTILITY_BIN=$(top_builddir)/src/wallet-utility$(EXEEXT) BITCOIN_WIN_INSTALLER=$(PACKAGE)-$(PACKAGE_VERSION)-win$(WINDOWS_BITS)-setup$(EXEEXT) if TARGET_DARWIN @@ -32,26 +33,26 @@ endif DIST_DOCS = $(wildcard doc/*.md) $(wildcard doc/release-notes/*.md) BIN_CHECKS=$(top_srcdir)/contrib/devtools/symbol-check.py \ - $(top_srcdir)/contrib/devtools/security-check.py + $(top_srcdir)/contrib/devtools/security-check.py WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/bitcoin.ico \ - $(top_srcdir)/share/pixmaps/nsis-header.bmp \ - $(top_srcdir)/share/pixmaps/nsis-wizard.bmp + $(top_srcdir)/share/pixmaps/nsis-header.bmp \ + $(top_srcdir)/share/pixmaps/nsis-wizard.bmp if TARGET_DARWIN OSX_PACKAGING = $(OSX_DEPLOY_SCRIPT) $(OSX_FANCY_PLIST) $(OSX_INSTALLER_ICONS) $(OSX_BASE_LPROJ_DIR) \ - $(top_srcdir)/contrib/macdeploy/$(OSX_BACKGROUND_IMAGE) \ - $(top_srcdir)/contrib/macdeploy/DS_Store \ - $(top_srcdir)/contrib/macdeploy/detached-sig-apply.sh \ - $(top_srcdir)/contrib/macdeploy/detached-sig-create.sh + $(top_srcdir)/contrib/macdeploy/$(OSX_BACKGROUND_IMAGE) \ + $(top_srcdir)/contrib/macdeploy/DS_Store \ + $(top_srcdir)/contrib/macdeploy/detached-sig-apply.sh \ + $(top_srcdir)/contrib/macdeploy/detached-sig-create.sh endif if TARGET_DARWIN COVERAGE_INFO = baseline_filtered_combined.info baseline.info block_test.info \ - leveldb_baseline.info test_bitcoin_filtered.info total_coverage.info \ - baseline_filtered.info block_test_filtered.info \ - leveldb_baseline_filtered.info test_bitcoin_coverage.info test_bitcoin.info + leveldb_baseline.info test_bitcoin_filtered.info total_coverage.info \ + baseline_filtered.info block_test_filtered.info \ + leveldb_baseline_filtered.info test_bitcoin_coverage.info test_bitcoin.info # zcash-gtest.info zcash-gtest_filtered.info zcash-gtest_coverage.info else COVERAGE_INFO = baseline_filtered_combined.info baseline.info block_test.info \ @@ -104,9 +105,9 @@ $(OSX_APP)/Contents/Resources/Base.lproj/InfoPlist.strings: $(OSX_BASE_LPROJ_DIR $(INSTALL_DATA) $< $@ OSX_APP_BUILT=$(OSX_APP)/Contents/PkgInfo $(OSX_APP)/Contents/Resources/empty.lproj \ - $(OSX_APP)/Contents/Resources/bitcoin.icns $(OSX_APP)/Contents/Info.plist \ - $(OSX_APP)/Contents/MacOS/Bitcoin-Qt $(OSX_APP)/Contents/Resources/Base.lproj/InfoPlist.strings - + $(OSX_APP)/Contents/Resources/bitcoin.icns $(OSX_APP)/Contents/Info.plist \ + $(OSX_APP)/Contents/MacOS/Bitcoin-Qt $(OSX_APP)/Contents/Resources/Base.lproj/InfoPlist.strings + endif if BUILD_DARWIN @@ -155,6 +156,9 @@ $(BITCOIND_BIN): FORCE $(BITCOIN_CLI_BIN): FORCE $(MAKE) -C src $(@F) +$(WALLET_UTILITY_BIN): FORCE + $(MAKE) -C src $(@F) + if USE_LCOV baseline.info: @@ -163,27 +167,27 @@ baseline.info: if BUILD_DARWIN baseline_filtered.info: baseline.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ - "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ + "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ else baseline_filtered.info: baseline.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ endif leveldb_baseline.info: baseline_filtered.info @@ -192,27 +196,27 @@ leveldb_baseline.info: baseline_filtered.info if BUILD_DARWIN leveldb_baseline_filtered.info: leveldb_baseline.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ - "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ + "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ else leveldb_baseline_filtered.info: leveldb_baseline.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ endif baseline_filtered_combined.info: leveldb_baseline_filtered.info baseline_filtered.info @@ -227,27 +231,27 @@ test_bitcoin.info: baseline_filtered_combined.info if BUILD_DARWIN test_bitcoin_filtered.info: test_bitcoin.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ - "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ + "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ else test_bitcoin_filtered.info: test_bitcoin.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ endif @@ -261,32 +265,32 @@ block_test.info: test_bitcoin_filtered.info if BUILD_DARWIN block_test_filtered.info: block_test.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ - "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ - "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/$(BUILD)/include/*.h" \ + "$(abs_builddir)/depends/$(BUILD)/include/boost/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gmock/*" \ + "$(abs_builddir)/depends/$(BUILD)/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ else block_test_filtered.info: block_test.info $(LCOV) -r $< "/usr/include/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ - "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ - "$(abs_builddir)/src/gtest/*" \ - "$(abs_builddir)/src/test/*" \ - "$(abs_builddir)/src/wallet/gtest/*" \ - "$(abs_builddir)/src/wallet/test/*" \ - -o $@ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/*.h" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/boost/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gmock/*" \ + "$(abs_builddir)/depends/x86_64-unknown-linux-gnu/include/gtest/*" \ + "$(abs_builddir)/src/gtest/*" \ + "$(abs_builddir)/src/test/*" \ + "$(abs_builddir)/src/wallet/gtest/*" \ + "$(abs_builddir)/src/wallet/test/*" \ + -o $@ endif test_bitcoin_coverage.info: baseline_filtered_combined.info test_bitcoin_filtered.info - $(LCOV) -a baseline_filtered.info -a leveldb_baseline_filtered.info -a test_bitcoin_filtered.info -o $@ + $(LCOV) -a baseline_filtered.info -a leveldb_baseline_filtered.info -a test_bitcoin_filtered.info -o $@ if ! BUILD_DARWIN zcash-gtest_coverage.info: baseline_filtered_combined.info zcash-gtest_filtered.info @@ -295,7 +299,7 @@ endif if BUILD_DARWIN total_coverage.info: baseline_filtered_combined.info test_bitcoin_filtered.info zcash-gtest_filtered.info block_test_filtered.info - $(LCOV) -a baseline_filtered.info -a leveldb_baseline_filtered.info -a test_bitcoin_filtered.info -a zcash-gtest_filtered.info -a block_test_filtered.info -o $@ | $(GREP) "\%" | $(AWK) '{ print substr($$3,2,50) "/" $$5 }' > coverage_percent.txt + $(LCOV) -a baseline_filtered.info -a leveldb_baseline_filtered.info -a test_bitcoin_filtered.info -a zcash-gtest_filtered.info -a block_test_filtered.info -o $@ | $(GREP) "\%" | $(AWK) '{ print substr($$3,2,50) "/" $$5 }' > coverage_percent.txt else total_coverage.info: baseline_filtered_combined.info test_bitcoin_filtered.info zcash-gtest_filtered.info block_test_filtered.info $(LCOV) -a baseline_filtered.info -a leveldb_baseline_filtered.info -a test_bitcoin_filtered.info -a zcash-gtest_filtered.info -a block_test_filtered.info -o $@ | $(GREP) "\%" | $(AWK) '{ print substr($$3,2,50) "/" $$5 }' > coverage_percent.txt @@ -303,19 +307,19 @@ endif test_bitcoin.coverage/.dirstamp: test_bitcoin_coverage.info - $(GENHTML) -s $< -o $(@D) - @touch $@ + $(GENHTML) -s $< -o $(@D) + @touch $@ if TARGET_DARWIN zcash-gtest.coverage/.dirstamp: zcash-gtest_coverage.info - $(GENHTML) -s $< -o $(@D) - @touch $@ + $(GENHTML) -s $< -o $(@D) + @touch $@ cov-zcash: zcash-gtest.coverage/.dirstamp endif total.coverage/.dirstamp: total_coverage.info - $(GENHTML) -s $< -o $(@D) - @touch $@ + $(GENHTML) -s $< -o $(@D) + @touch $@ if BUILD_DARWIN cov: test_bitcoin.coverage/.dirstamp cov-zcash total.coverage/.dirstamp diff --git a/README.md b/README.md index 25c573317..e766870a5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Komodo 1.0.15 - +## Komodo with Bitcore +This version of Komodo contains Bitcore support for komodo and all its assetchains. ## Komodod This software is Komodo client, generally you will use this if you want to mine KMD or setup a full node. It downloads and stores the entire history of Komodo transactions; depending on the speed of your computer and network connection, the synchronization process could take a day or more once the blockchain has reached a significant size. @@ -34,7 +34,7 @@ Dependencies ``` #The following packages are needed: -sudo apt-get install build-essential pkg-config libc6-dev m4 g++-multilib autoconf libtool ncurses-dev unzip git python python-zmq zlib1g-dev wget libcurl3-gnutls-dev bsdmainutils automake +sudo apt-get install build-essential pkg-config libc6-dev m4 g++-multilib autoconf libtool ncurses-dev unzip git python python-zmq zlib1g-dev wget libcurl4-openssl-dev bsdmainutils automake curl ``` Komodo diff --git a/configure.ac b/configure.ac index decf2b2e6..7de40ff26 100644 --- a/configure.ac +++ b/configure.ac @@ -232,7 +232,7 @@ CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], - [build zcash-cli zcash-tx (default=yes)])], + [build zcash-cli zcash-tx wallet-utility (default=yes)])], [build_bitcoin_utils=$withval], [build_bitcoin_utils=yes]) @@ -805,7 +805,7 @@ AC_MSG_CHECKING([whether to build bitcoind]) AM_CONDITIONAL([BUILD_BITCOIND], [test x$build_bitcoind = xyes]) AC_MSG_RESULT($build_bitcoind) -AC_MSG_CHECKING([whether to build utils (zcash-cli zcash-tx)]) +AC_MSG_CHECKING([whether to build utils (zcash-cli zcash-tx wallet-utility)]) AM_CONDITIONAL([BUILD_BITCOIN_UTILS], [test x$build_bitcoin_utils = xyes]) AC_MSG_RESULT($build_bitcoin_utils) diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 0ed693951..50de39f64 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -44,6 +44,9 @@ testScripts=( 'key_import_export.py' 'nodehandling.py' 'reindex.py' + 'addressindex.py' + 'timestampindex.py' + 'spentindex.py' 'decodescript.py' 'disablewallet.py' 'zcjoinsplit.py' diff --git a/qa/rpc-tests/addressindex.py b/qa/rpc-tests/addressindex.py new file mode 100644 index 000000000..11fa7ffdb --- /dev/null +++ b/qa/rpc-tests/addressindex.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test addressindex generation and fetching +# + +import time +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.script import * +from test_framework.mininode import * +import binascii + +class AddressIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-relaypriority=0"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-addressindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-addressindex", "-relaypriority=0"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-addressindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining blocks..." + self.nodes[0].generate(105) + self.sync_all() + + chain_height = self.nodes[1].getblockcount() + assert_equal(chain_height, 105) + assert_equal(self.nodes[1].getbalance(), 0) + assert_equal(self.nodes[2].getbalance(), 0) + + # Check that balances are correct + balance0 = self.nodes[1].getaddressbalance("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(balance0["balance"], 0) + + # Check p2pkh and p2sh address indexes + print "Testing p2pkh and p2sh address index..." + + txid0 = self.nodes[0].sendtoaddress("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 10) + self.nodes[0].generate(1) + + txidb0 = self.nodes[0].sendtoaddress("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 10) + self.nodes[0].generate(1) + + txid1 = self.nodes[0].sendtoaddress("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 15) + self.nodes[0].generate(1) + + txidb1 = self.nodes[0].sendtoaddress("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 15) + self.nodes[0].generate(1) + + txid2 = self.nodes[0].sendtoaddress("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 20) + self.nodes[0].generate(1) + + txidb2 = self.nodes[0].sendtoaddress("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 20) + self.nodes[0].generate(1) + + self.sync_all() + + txids = self.nodes[1].getaddresstxids("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs") + assert_equal(len(txids), 3) + assert_equal(txids[0], txid0) + assert_equal(txids[1], txid1) + assert_equal(txids[2], txid2) + + txidsb = self.nodes[1].getaddresstxids("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(len(txidsb), 3) + assert_equal(txidsb[0], txidb0) + assert_equal(txidsb[1], txidb1) + assert_equal(txidsb[2], txidb2) + + # Check that limiting by height works + print "Testing querying txids by range of block heights.." + height_txids = self.nodes[1].getaddresstxids({ + "addresses": ["2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br"], + "start": 105, + "end": 110 + }) + assert_equal(len(height_txids), 2) + assert_equal(height_txids[0], txidb0) + assert_equal(height_txids[1], txidb1) + + # Check that multiple addresses works + multitxids = self.nodes[1].getaddresstxids({"addresses": ["2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs"]}) + assert_equal(len(multitxids), 6) + assert_equal(multitxids[0], txid0) + assert_equal(multitxids[1], txidb0) + assert_equal(multitxids[2], txid1) + assert_equal(multitxids[3], txidb1) + assert_equal(multitxids[4], txid2) + assert_equal(multitxids[5], txidb2) + + # Check that balances are correct + balance0 = self.nodes[1].getaddressbalance("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(balance0["balance"], 45 * 100000000) + + # Check that outputs with the same address will only return one txid + print "Testing for txid uniqueness..." + addressHash = "6349a418fc4578d10a372b54b45c280cc8c4382f".decode("hex") + scriptPubKey = CScript([OP_HASH160, addressHash, OP_EQUAL]) + unspent = self.nodes[0].listunspent() + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + tx.vout = [CTxOut(10, scriptPubKey), CTxOut(11, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + + self.nodes[0].generate(1) + self.sync_all() + + txidsmany = self.nodes[1].getaddresstxids("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(len(txidsmany), 4) + assert_equal(txidsmany[3], sent_txid) + + # Check that balances are correct + print "Testing balances..." + balance0 = self.nodes[1].getaddressbalance("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(balance0["balance"], 45 * 100000000 + 21) + + # Check that balances are correct after spending + print "Testing balances after spending..." + privkey2 = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address2 = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash2 = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey2 = CScript([OP_DUP, OP_HASH160, addressHash2, OP_EQUALVERIFY, OP_CHECKSIG]) + self.nodes[0].importprivkey(privkey2) + + unspent = self.nodes[0].listunspent() + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + amount = unspent[0]["amount"] * 100000000 + tx.vout = [CTxOut(amount, scriptPubKey2)] + tx.rehash() + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + spending_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + balance1 = self.nodes[1].getaddressbalance(address2) + assert_equal(balance1["balance"], amount) + + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(spending_txid, 16), 0))] + send_amount = 1 * 100000000 + 12840 + change_amount = amount - send_amount - 10000 + tx.vout = [CTxOut(change_amount, scriptPubKey2), CTxOut(send_amount, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + + balance2 = self.nodes[1].getaddressbalance(address2) + assert_equal(balance2["balance"], change_amount) + + # Check that deltas are returned correctly + deltas = self.nodes[1].getaddressdeltas({"addresses": [address2], "start": 1, "end": 200}) + balance3 = 0 + for delta in deltas: + balance3 += delta["satoshis"] + assert_equal(balance3, change_amount) + assert_equal(deltas[0]["address"], address2) + assert_equal(deltas[0]["blockindex"], 1) + + # Check that entire range will be queried + deltasAll = self.nodes[1].getaddressdeltas({"addresses": [address2]}) + assert_equal(len(deltasAll), len(deltas)) + + # Check that deltas can be returned from range of block heights + deltas = self.nodes[1].getaddressdeltas({"addresses": [address2], "start": 113, "end": 113}) + assert_equal(len(deltas), 1) + + # Check that unspent outputs can be queried + print "Testing utxos..." + utxos = self.nodes[1].getaddressutxos({"addresses": [address2]}) + assert_equal(len(utxos), 1) + assert_equal(utxos[0]["satoshis"], change_amount) + + # Check that indexes will be updated with a reorg + print "Testing reorg..." + + best_hash = self.nodes[0].getbestblockhash() + self.nodes[0].invalidateblock(best_hash) + self.nodes[1].invalidateblock(best_hash) + self.nodes[2].invalidateblock(best_hash) + self.nodes[3].invalidateblock(best_hash) + self.sync_all() + + balance4 = self.nodes[1].getaddressbalance(address2) + assert_equal(balance4, balance1) + + utxos2 = self.nodes[1].getaddressutxos({"addresses": [address2]}) + assert_equal(len(utxos2), 1) + assert_equal(utxos2[0]["satoshis"], amount) + + # Check sorting of utxos + self.nodes[2].generate(150) + + txidsort1 = self.nodes[2].sendtoaddress(address2, 50) + self.nodes[2].generate(1) + txidsort2 = self.nodes[2].sendtoaddress(address2, 50) + self.nodes[2].generate(1) + self.sync_all() + + utxos3 = self.nodes[1].getaddressutxos({"addresses": [address2]}) + assert_equal(len(utxos3), 3) + assert_equal(utxos3[0]["height"], 114) + assert_equal(utxos3[1]["height"], 264) + assert_equal(utxos3[2]["height"], 265) + + # Check mempool indexing + print "Testing mempool indexing..." + + privKey3 = "cVfUn53hAbRrDEuMexyfgDpZPhF7KqXpS8UZevsyTDaugB7HZ3CD" + address3 = "mw4ynwhS7MmrQ27hr82kgqu7zryNDK26JB" + addressHash3 = "aa9872b5bbcdb511d89e0e11aa27da73fd2c3f50".decode("hex") + scriptPubKey3 = CScript([OP_DUP, OP_HASH160, addressHash3, OP_EQUALVERIFY, OP_CHECKSIG]) + address4 = "2N8oFVB2vThAKury4vnLquW2zVjsYjjAkYQ" + scriptPubKey4 = CScript([OP_HASH160, addressHash3, OP_EQUAL]) + unspent = self.nodes[2].listunspent() + + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + amount = unspent[0]["amount"] * 100000000 + tx.vout = [CTxOut(amount, scriptPubKey3)] + tx.rehash() + signed_tx = self.nodes[2].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + memtxid1 = self.nodes[2].sendrawtransaction(signed_tx["hex"], True) + time.sleep(2) + + tx2 = CTransaction() + tx2.vin = [CTxIn(COutPoint(int(unspent[1]["txid"], 16), unspent[1]["vout"]))] + amount = unspent[1]["amount"] * 100000000 + tx2.vout = [ + CTxOut(amount / 4, scriptPubKey3), + CTxOut(amount / 4, scriptPubKey3), + CTxOut(amount / 4, scriptPubKey4), + CTxOut(amount / 4, scriptPubKey4) + ] + tx2.rehash() + signed_tx2 = self.nodes[2].signrawtransaction(binascii.hexlify(tx2.serialize()).decode("utf-8")) + memtxid2 = self.nodes[2].sendrawtransaction(signed_tx2["hex"], True) + time.sleep(2) + + mempool = self.nodes[2].getaddressmempool({"addresses": [address3]}) + assert_equal(len(mempool), 3) + assert_equal(mempool[0]["txid"], memtxid1) + assert_equal(mempool[0]["address"], address3) + assert_equal(mempool[0]["index"], 0) + assert_equal(mempool[1]["txid"], memtxid2) + assert_equal(mempool[1]["index"], 0) + assert_equal(mempool[2]["txid"], memtxid2) + assert_equal(mempool[2]["index"], 1) + + self.nodes[2].generate(1); + self.sync_all(); + mempool2 = self.nodes[2].getaddressmempool({"addresses": [address3]}) + assert_equal(len(mempool2), 0) + + tx = CTransaction() + tx.vin = [ + CTxIn(COutPoint(int(memtxid2, 16), 0)), + CTxIn(COutPoint(int(memtxid2, 16), 1)) + ] + tx.vout = [CTxOut(amount / 2 - 10000, scriptPubKey2)] + tx.rehash() + self.nodes[2].importprivkey(privKey3) + signed_tx3 = self.nodes[2].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + memtxid3 = self.nodes[2].sendrawtransaction(signed_tx3["hex"], True) + time.sleep(2) + + mempool3 = self.nodes[2].getaddressmempool({"addresses": [address3]}) + assert_equal(len(mempool3), 2) + assert_equal(mempool3[0]["prevtxid"], memtxid2) + assert_equal(mempool3[0]["prevout"], 0) + assert_equal(mempool3[1]["prevtxid"], memtxid2) + assert_equal(mempool3[1]["prevout"], 1) + + # sending and receiving to the same address + privkey1 = "cQY2s58LhzUCmEXN8jtAp1Etnijx78YRZ466w4ikX1V4UpTpbsf8" + address1 = "myAUWSHnwsQrhuMWv4Br6QsCnpB41vFwHn" + address1hash = "c192bff751af8efec15135d42bfeedf91a6f3e34".decode("hex") + address1script = CScript([OP_DUP, OP_HASH160, address1hash, OP_EQUALVERIFY, OP_CHECKSIG]) + + self.nodes[0].sendtoaddress(address1, 10) + self.nodes[0].generate(1) + self.sync_all() + + utxos = self.nodes[1].getaddressutxos({"addresses": [address1]}) + assert_equal(len(utxos), 1) + + tx = CTransaction() + tx.vin = [ + CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["outputIndex"])) + ] + amount = utxos[0]["satoshis"] - 1000 + tx.vout = [CTxOut(amount, address1script)] + tx.rehash() + self.nodes[0].importprivkey(privkey1) + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + mem_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + + self.sync_all() + mempool_deltas = self.nodes[2].getaddressmempool({"addresses": [address1]}) + assert_equal(len(mempool_deltas), 2) + + # Include chaininfo in results + print "Testing results with chain info..." + + deltas_with_info = self.nodes[1].getaddressdeltas({ + "addresses": [address2], + "start": 1, + "end": 200, + "chainInfo": True + }) + start_block_hash = self.nodes[1].getblockhash(1); + end_block_hash = self.nodes[1].getblockhash(200); + assert_equal(deltas_with_info["start"]["height"], 1) + assert_equal(deltas_with_info["start"]["hash"], start_block_hash) + assert_equal(deltas_with_info["end"]["height"], 200) + assert_equal(deltas_with_info["end"]["hash"], end_block_hash) + + utxos_with_info = self.nodes[1].getaddressutxos({"addresses": [address2], "chainInfo": True}) + expected_tip_block_hash = self.nodes[1].getblockhash(267); + assert_equal(utxos_with_info["height"], 267) + assert_equal(utxos_with_info["hash"], expected_tip_block_hash) + + print "Passed\n" + + +if __name__ == '__main__': + AddressIndexTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/spentindex.py b/qa/rpc-tests/spentindex.py new file mode 100644 index 000000000..ebe2b8cc2 --- /dev/null +++ b/qa/rpc-tests/spentindex.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test addressindex generation and fetching +# + +import time +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.script import * +from test_framework.mininode import * +import binascii + +class SpentIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-spentindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-spentindex"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-spentindex", "-txindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining blocks..." + self.nodes[0].generate(105) + self.sync_all() + + chain_height = self.nodes[1].getblockcount() + assert_equal(chain_height, 105) + + # Check that + print "Testing spent index..." + + privkey = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey = CScript([OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG]) + unspent = self.nodes[0].listunspent() + tx = CTransaction() + amount = unspent[0]["amount"] * 100000000 + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + tx.vout = [CTxOut(amount, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + + print "Testing getspentinfo method..." + + # Check that the spentinfo works standalone + info = self.nodes[1].getspentinfo({"txid": unspent[0]["txid"], "index": unspent[0]["vout"]}) + assert_equal(info["txid"], txid) + assert_equal(info["index"], 0) + assert_equal(info["height"], 106) + + print "Testing getrawtransaction method..." + + # Check that verbose raw transaction includes spent info + txVerbose = self.nodes[3].getrawtransaction(unspent[0]["txid"], 1) + assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentTxId"], txid) + assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentIndex"], 0) + assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentHeight"], 106) + + # Check that verbose raw transaction includes input values + txVerbose2 = self.nodes[3].getrawtransaction(txid, 1) + assert_equal(txVerbose2["vin"][0]["value"], Decimal(unspent[0]["amount"])) + assert_equal(txVerbose2["vin"][0]["valueSat"], amount) + + # Check that verbose raw transaction includes address values and input values + privkey2 = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address2 = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash2 = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey2 = CScript([OP_DUP, OP_HASH160, addressHash2, OP_EQUALVERIFY, OP_CHECKSIG]) + tx2 = CTransaction() + tx2.vin = [CTxIn(COutPoint(int(txid, 16), 0))] + tx2.vout = [CTxOut(amount, scriptPubKey2)] + tx.rehash() + self.nodes[0].importprivkey(privkey) + signed_tx2 = self.nodes[0].signrawtransaction(binascii.hexlify(tx2.serialize()).decode("utf-8")) + txid2 = self.nodes[0].sendrawtransaction(signed_tx2["hex"], True) + + # Check the mempool index + self.sync_all() + txVerbose3 = self.nodes[1].getrawtransaction(txid2, 1) + assert_equal(txVerbose3["vin"][0]["address"], address2) + assert_equal(txVerbose3["vin"][0]["value"], Decimal(unspent[0]["amount"])) + assert_equal(txVerbose3["vin"][0]["valueSat"], amount) + + # Check the database index + block_hash = self.nodes[0].generate(1) + self.sync_all() + + txVerbose4 = self.nodes[3].getrawtransaction(txid2, 1) + assert_equal(txVerbose4["vin"][0]["address"], address2) + assert_equal(txVerbose4["vin"][0]["value"], Decimal(unspent[0]["amount"])) + assert_equal(txVerbose4["vin"][0]["valueSat"], amount) + + + # Check block deltas + print "Testing getblockdeltas..." + + block = self.nodes[3].getblockdeltas(block_hash[0]) + assert_equal(len(block["deltas"]), 2) + assert_equal(block["deltas"][0]["index"], 0) + assert_equal(len(block["deltas"][0]["inputs"]), 0) + assert_equal(len(block["deltas"][0]["outputs"]), 0) + assert_equal(block["deltas"][1]["index"], 1) + assert_equal(block["deltas"][1]["txid"], txid2) + assert_equal(block["deltas"][1]["inputs"][0]["index"], 0) + assert_equal(block["deltas"][1]["inputs"][0]["address"], "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW") + assert_equal(block["deltas"][1]["inputs"][0]["satoshis"], amount * -1) + assert_equal(block["deltas"][1]["inputs"][0]["prevtxid"], txid) + assert_equal(block["deltas"][1]["inputs"][0]["prevout"], 0) + assert_equal(block["deltas"][1]["outputs"][0]["index"], 0) + assert_equal(block["deltas"][1]["outputs"][0]["address"], "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW") + assert_equal(block["deltas"][1]["outputs"][0]["satoshis"], amount) + + print "Passed\n" + + +if __name__ == '__main__': + SpentIndexTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/timestampindex.py b/qa/rpc-tests/timestampindex.py new file mode 100644 index 000000000..23244159f --- /dev/null +++ b/qa/rpc-tests/timestampindex.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test timestampindex generation and fetching +# + +import time + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + + +class TimestampIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-timestampindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-timestampindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining 25 blocks..." + blockhashes = self.nodes[0].generate(25) + time.sleep(3) + print "Mining 25 blocks..." + blockhashes.extend(self.nodes[0].generate(25)) + time.sleep(3) + print "Mining 25 blocks..." + blockhashes.extend(self.nodes[0].generate(25)) + self.sync_all() + low = self.nodes[1].getblock(blockhashes[0])["time"] + high = low + 76 + + print "Checking timestamp index..." + hashes = self.nodes[1].getblockhashes(high, low) + + assert_equal(len(hashes), len(blockhashes)) + + assert_equal(hashes, blockhashes) + + print "Passed\n" + + +if __name__ == '__main__': + TimestampIndexTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/txindex.py b/qa/rpc-tests/txindex.py new file mode 100644 index 000000000..e59b6d7d9 --- /dev/null +++ b/qa/rpc-tests/txindex.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test txindex generation and fetching +# + +import time +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.script import * +from test_framework.mininode import * +import binascii + +class TxIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-txindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-txindex"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-txindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining blocks..." + self.nodes[0].generate(105) + self.sync_all() + + chain_height = self.nodes[1].getblockcount() + assert_equal(chain_height, 105) + + print "Testing transaction index..." + + privkey = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey = CScript([OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG]) + unspent = self.nodes[0].listunspent() + tx = CTransaction() + amount = unspent[0]["amount"] * 100000000 + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + tx.vout = [CTxOut(amount, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + + # Check verbose raw transaction results + verbose = self.nodes[3].getrawtransaction(unspent[0]["txid"], 1) + assert_equal(verbose["vout"][0]["valueSat"], 5000000000); + assert_equal(verbose["vout"][0]["value"], 50); + + print "Passed\n" + + +if __name__ == '__main__': + TxIndexTest().main() \ No newline at end of file diff --git a/qa/zcash/create_wallet_200k_utxos.py b/qa/zcash/create_wallet_200k_utxos.py old mode 100755 new mode 100644 diff --git a/qa/zcash/full-test-suite.sh b/qa/zcash/full-test-suite.sh new file mode 100755 index 000000000..af4fb2791 --- /dev/null +++ b/qa/zcash/full-test-suite.sh @@ -0,0 +1,201 @@ +#!/usr/bin/env python2 +# +# Execute all of the automated tests related to Zcash. +# + +import argparse +import os +import re +import subprocess +import sys + +REPOROOT = os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.abspath(__file__) + ) + ) +) + +def repofile(filename): + return os.path.join(REPOROOT, filename) + + +# +# Custom test runners +# + +RE_RPATH_RUNPATH = re.compile('No RPATH.*No RUNPATH') +RE_FORTIFY_AVAILABLE = re.compile('FORTIFY_SOURCE support available.*Yes') +RE_FORTIFY_USED = re.compile('Binary compiled with FORTIFY_SOURCE support.*Yes') + +def test_rpath_runpath(filename): + output = subprocess.check_output( + [repofile('qa/zcash/checksec.sh'), '--file', repofile(filename)] + ) + if RE_RPATH_RUNPATH.search(output): + print('PASS: %s has no RPATH or RUNPATH.' % filename) + return True + else: + print('FAIL: %s has an RPATH or a RUNPATH.' % filename) + print(output) + return False + +def test_fortify_source(filename): + proc = subprocess.Popen( + [repofile('qa/zcash/checksec.sh'), '--fortify-file', repofile(filename)], + stdout=subprocess.PIPE, + ) + line1 = proc.stdout.readline() + line2 = proc.stdout.readline() + proc.terminate() + if RE_FORTIFY_AVAILABLE.search(line1) and RE_FORTIFY_USED.search(line2): + print('PASS: %s has FORTIFY_SOURCE.' % filename) + return True + else: + print('FAIL: %s is missing FORTIFY_SOURCE.' % filename) + return False + +def check_security_hardening(): + ret = True + + # PIE, RELRO, Canary, and NX are tested by make check-security. + ret &= subprocess.call(['make', '-C', repofile('src'), 'check-security']) == 0 + + ret &= test_rpath_runpath('src/zcashd') + ret &= test_rpath_runpath('src/zcash-cli') + ret &= test_rpath_runpath('src/zcash-gtest') + ret &= test_rpath_runpath('src/zcash-tx') + ret &= test_rpath_runpath('src/test/test_bitcoin') + ret &= test_rpath_runpath('src/zcash/GenerateParams') + + # NOTE: checksec.sh does not reliably determine whether FORTIFY_SOURCE + # is enabled for the entire binary. See issue #915. + ret &= test_fortify_source('src/zcashd') + ret &= test_fortify_source('src/zcash-cli') + ret &= test_fortify_source('src/zcash-gtest') + ret &= test_fortify_source('src/zcash-tx') + ret &= test_fortify_source('src/test/test_bitcoin') + ret &= test_fortify_source('src/zcash/GenerateParams') + + return ret + +def ensure_no_dot_so_in_depends(): + arch_dir = os.path.join( + REPOROOT, + 'depends', + 'x86_64-unknown-linux-gnu', + ) + + exit_code = 0 + + if os.path.isdir(arch_dir): + lib_dir = os.path.join(arch_dir, 'lib') + libraries = os.listdir(lib_dir) + + for lib in libraries: + if lib.find(".so") != -1: + print lib + exit_code = 1 + else: + exit_code = 2 + print "arch-specific build dir not present: {}".format(arch_dir) + print "Did you build the ./depends tree?" + print "Are you on a currently unsupported architecture?" + + if exit_code == 0: + print "PASS." + else: + print "FAIL." + + return exit_code == 0 + +def util_test(): + return subprocess.call( + [repofile('src/test/bitcoin-util-test.py')], + cwd=repofile('src'), + env={'PYTHONPATH': repofile('src/test'), 'srcdir': repofile('src')} + ) == 0 + + +# +# Tests +# + +STAGES = [ + 'btest', + 'gtest', + 'sec-hard', + 'no-dot-so', + 'util-test', + 'secp256k1', + 'libsnark', + 'univalue', + 'rpc', +] + +STAGE_COMMANDS = { + 'btest': [repofile('src/test/test_bitcoin'), '-p'], + 'gtest': [repofile('src/zcash-gtest')], + 'sec-hard': check_security_hardening, + 'no-dot-so': ensure_no_dot_so_in_depends, + 'util-test': util_test, + 'secp256k1': ['make', '-C', repofile('src/secp256k1'), 'check'], + 'libsnark': ['make', '-C', repofile('src'), 'libsnark-tests'], + 'univalue': ['make', '-C', repofile('src/univalue'), 'check'], + 'rpc': [repofile('qa/pull-tester/rpc-tests.sh')], +} + + +# +# Test driver +# + +def run_stage(stage): + print('Running stage %s' % stage) + print('=' * (len(stage) + 14)) + print + + cmd = STAGE_COMMANDS[stage] + if type(cmd) == type([]): + ret = subprocess.call(cmd) == 0 + else: + ret = cmd() + + print + print('-' * (len(stage) + 15)) + print('Finished stage %s' % stage) + print + + return ret + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--list-stages', dest='list', action='store_true') + parser.add_argument('stage', nargs='*', default=STAGES, + help='One of %s'%STAGES) + args = parser.parse_args() + + # Check for list + if args.list: + for s in STAGES: + print(s) + sys.exit(0) + + # Check validity of stages + for s in args.stage: + if s not in STAGES: + print("Invalid stage '%s' (choose from %s)" % (s, STAGES)) + sys.exit(1) + + # Run the stages + passed = True + for s in args.stage: + passed &= run_stage(s) + + if not passed: + print("!!! One or more test stages failed !!!") + sys.exit(1) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/qa/zcash/performance-measurements.sh b/qa/zcash/performance-measurements.sh index 15f3077b5..3370bd20f 100755 --- a/qa/zcash/performance-measurements.sh +++ b/qa/zcash/performance-measurements.sh @@ -338,4 +338,4 @@ case "$1" in esac # Cleanup -rm -rf "$DATADIR" +rm -rf "$DATADIR" \ No newline at end of file diff --git a/qa/zcash/test-depends-sources-mirror.py b/qa/zcash/test-depends-sources-mirror.py index e09c2c238..6b612d715 100755 --- a/qa/zcash/test-depends-sources-mirror.py +++ b/qa/zcash/test-depends-sources-mirror.py @@ -40,4 +40,4 @@ for filename in get_depends_sources_list(): sys.exit(1) print "PASS." -sys.exit(0) +sys.exit(0) \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index 00acd5e05..c51489565 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -72,12 +72,12 @@ $(LIBCRYPTOCONDITIONS): $(wildcard cryptoconditions/src/*) $(wildcard cryptocond # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: EXTRA_LIBRARIES = \ - crypto/libbitcoin_crypto.a \ - libbitcoin_util.a \ - libbitcoin_common.a \ - libbitcoin_server.a \ - libbitcoin_cli.a \ - libzcash.a + crypto/libbitcoin_crypto.a \ + libbitcoin_util.a \ + libbitcoin_common.a \ + libbitcoin_server.a \ + libbitcoin_cli.a \ + libzcash.a if ENABLE_WALLET BITCOIN_INCLUDES += $(BDB_CPPFLAGS) EXTRA_LIBRARIES += libbitcoin_wallet.a @@ -105,7 +105,10 @@ if BUILD_BITCOIND endif if BUILD_BITCOIN_UTILS - bin_PROGRAMS += komodo-cli komodo-tx + bin_PROGRAMS += komodo-cli komodo-tx + if ENABLE_WALLET + bin_PROGRAMS += wallet-utility + endif endif LIBZCASH_H = \ @@ -122,6 +125,8 @@ LIBZCASH_H = \ .PHONY: FORCE collate-libsnark check-symbols check-security # bitcoin core # BITCOIN_CORE_H = \ + addressindex.h \ + spentindex.h \ addrman.h \ alert.h \ amount.h \ @@ -235,7 +240,7 @@ BITCOIN_CORE_H = \ obj/build.h: FORCE @$(MKDIR_P) $(builddir)/obj @$(top_srcdir)/share/genbuild.sh $(abs_top_builddir)/src/obj/build.h \ - $(abs_top_srcdir) + $(abs_top_srcdir) libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h # server: zcashd @@ -292,9 +297,9 @@ LIBBITCOIN_ZMQ=libbitcoin_zmq.a libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_zmq_a_SOURCES = \ - zmq/zmqabstractnotifier.cpp \ - zmq/zmqnotificationinterface.cpp \ - zmq/zmqpublishnotifier.cpp + zmq/zmqabstractnotifier.cpp \ + zmq/zmqnotificationinterface.cpp \ + zmq/zmqpublishnotifier.cpp endif if ENABLE_PROTON @@ -336,33 +341,33 @@ libbitcoin_wallet_a_SOURCES = \ crypto_libbitcoin_crypto_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_CONFIG_INCLUDES) crypto_libbitcoin_crypto_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) crypto_libbitcoin_crypto_a_SOURCES = \ - crypto/common.h \ - crypto/equihash.cpp \ - crypto/equihash.h \ - crypto/equihash.tcc \ - crypto/hmac_sha256.cpp \ - crypto/hmac_sha256.h \ - crypto/hmac_sha512.cpp \ - crypto/hmac_sha512.h \ - crypto/ripemd160.cpp \ - crypto/ripemd160.h \ - crypto/sha1.cpp \ - crypto/sha1.h \ - crypto/sha256.cpp \ - crypto/sha256.h \ - crypto/sha512.cpp \ - crypto/sha512.h + crypto/common.h \ + crypto/equihash.cpp \ + crypto/equihash.h \ + crypto/equihash.tcc \ + crypto/hmac_sha256.cpp \ + crypto/hmac_sha256.h \ + crypto/hmac_sha512.cpp \ + crypto/hmac_sha512.h \ + crypto/ripemd160.cpp \ + crypto/ripemd160.h \ + crypto/sha1.cpp \ + crypto/sha1.h \ + crypto/sha256.cpp \ + crypto/sha256.h \ + crypto/sha512.cpp \ + crypto/sha512.h if ENABLE_MINING EQUIHASH_TROMP_SOURCES = \ - pow/tromp/equi_miner.h \ - pow/tromp/equi.h \ - pow/tromp/osx_barrier.h + pow/tromp/equi_miner.h \ + pow/tromp/equi.h \ + pow/tromp/osx_barrier.h crypto_libbitcoin_crypto_a_CPPFLAGS += \ - -DEQUIHASH_TROMP_ATOMIC + -DEQUIHASH_TROMP_ATOMIC crypto_libbitcoin_crypto_a_SOURCES += \ - ${EQUIHASH_TROMP_SOURCES} + ${EQUIHASH_TROMP_SOURCES} endif # common: shared between zcashd and non-server tools @@ -402,23 +407,23 @@ libbitcoin_common_a_SOURCES = \ libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_util_a_SOURCES = \ - support/pagelocker.cpp \ - chainparamsbase.cpp \ - clientversion.cpp \ - compat/glibc_sanity.cpp \ - compat/glibcxx_sanity.cpp \ - compat/strnlen.cpp \ - random.cpp \ - rpcprotocol.cpp \ - support/cleanse.cpp \ - sync.cpp \ - uint256.cpp \ - util.cpp \ - utilmoneystr.cpp \ - utilstrencodings.cpp \ - utiltime.cpp \ - $(BITCOIN_CORE_H) \ - $(LIBZCASH_H) + support/pagelocker.cpp \ + chainparamsbase.cpp \ + clientversion.cpp \ + compat/glibc_sanity.cpp \ + compat/glibcxx_sanity.cpp \ + compat/strnlen.cpp \ + random.cpp \ + rpcprotocol.cpp \ + support/cleanse.cpp \ + sync.cpp \ + uint256.cpp \ + util.cpp \ + utilmoneystr.cpp \ + utilstrencodings.cpp \ + utiltime.cpp \ + $(BITCOIN_CORE_H) \ + $(LIBZCASH_H) if GLIBC_BACK_COMPAT libbitcoin_util_a_SOURCES += compat/glibc_compat.cpp @@ -428,9 +433,9 @@ endif libbitcoin_cli_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_cli_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_cli_a_SOURCES = \ - rpcclient.cpp \ - $(BITCOIN_CORE_H) \ - $(LIBZCASH_H) + rpcclient.cpp \ + $(BITCOIN_CORE_H) \ + $(LIBZCASH_H) nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h # @@ -486,23 +491,44 @@ komodo_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) komodo_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) komodo_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +# wallet-utility binary # +if ENABLE_WALLET +wallet_utility_SOURCES = wallet-utility.cpp +wallet_utility_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +wallet_utility_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +wallet_utility_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +endif + if TARGET_WINDOWS komodo_cli_SOURCES += bitcoin-cli-res.rc endif komodo_cli_LDADD = \ - $(LIBBITCOIN_CLI) \ - $(LIBUNIVALUE) \ - $(LIBBITCOIN_UTIL) \ - $(BOOST_LIBS) \ - $(SSL_LIBS) \ - $(CRYPTO_LIBS) \ - $(EVENT_LIBS) \ - $(LIBZCASH) \ - $(LIBSNARK) \ - $(LIBBITCOIN_CRYPTO) \ - $(LIBZCASH_LIBS) -# + $(LIBBITCOIN_CLI) \ + $(LIBUNIVALUE) \ + $(LIBBITCOIN_UTIL) \ + $(BOOST_LIBS) \ + $(SSL_LIBS) \ + $(CRYPTO_LIBS) \ + $(EVENT_LIBS) \ + $(LIBZCASH) \ + $(LIBBITCOIN_CRYPTO) \ + $(LIBZCASH_LIBS) + +if ENABLE_WALLET +wallet_utility_LDADD = \ + libbitcoin_wallet.a \ + $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_CRYPTO) \ + $(LIBSECP256K1) \ + $(LIBBITCOIN_UTIL) \ + $(BOOST_LIBS) \ + $(BDB_LIBS) \ + $(CRYPTO_LIBS) \ + $(LIBZCASH) \ + $(LIBSNARK) \ + $(LIBZCASH_LIBS) +endif # zcash-tx binary # komodo_tx_SOURCES = komodo-tx.cpp @@ -531,20 +557,20 @@ komodo_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) # zcash protocol primitives # libzcash_a_SOURCES = \ - zcash/IncrementalMerkleTree.cpp \ - zcash/NoteEncryption.cpp \ - zcash/Address.cpp \ - zcash/JoinSplit.cpp \ - zcash/Proof.cpp \ - zcash/Note.cpp \ - zcash/prf.cpp \ - zcash/util.cpp \ - zcash/circuit/commitment.tcc \ - zcash/circuit/gadget.tcc \ - zcash/circuit/merkle.tcc \ - zcash/circuit/note.tcc \ - zcash/circuit/prfs.tcc \ - zcash/circuit/utils.tcc + zcash/IncrementalMerkleTree.cpp \ + zcash/NoteEncryption.cpp \ + zcash/Address.cpp \ + zcash/JoinSplit.cpp \ + zcash/Proof.cpp \ + zcash/Note.cpp \ + zcash/prf.cpp \ + zcash/util.cpp \ + zcash/circuit/commitment.tcc \ + zcash/circuit/gadget.tcc \ + zcash/circuit/merkle.tcc \ + zcash/circuit/note.tcc \ + zcash/circuit/prfs.tcc \ + zcash/circuit/utils.tcc libzcash_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS $(HARDENED_CPPFLAGS) $(HARDENED_CXXFLAGS) $(HARDENED_LDFLAGS) -pipe $(SAN_LDFLAGS) -O1 -g -Wstack-protector $(SAN_CXXFLAGS) -fstack-protector-all -fPIE -fvisibility=hidden -DSTATIC $(BITCOIN_INCLUDES) @@ -572,7 +598,7 @@ libzcashconsensus_la_SOURCES = \ utilstrencodings.cpp if GLIBC_BACK_COMPAT - libzcashconsensus_la_SOURCES += compat/glibc_compat.cpp +libzcashconsensus_la_SOURCES += compat/glibc_compat.cpp endif libzcashconsensus_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 582ecb577..ff270bd6a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -15,87 +15,90 @@ EXTRA_DIST += \ test/data/tx394b54bb.hex \ test/data/txcreate1.hex \ test/data/txcreate2.hex \ - test/data/txcreatesign.hex + test/data/txcreatesign.hex \ + test/wallet-utility.py \ + test/data/wallet.dat JSON_TEST_FILES = \ - test/data/script_valid.json \ - test/data/base58_keys_valid.json \ - test/data/base58_encode_decode.json \ - test/data/base58_keys_invalid.json \ - test/data/script_invalid.json \ - test/data/tx_invalid.json \ - test/data/tx_valid.json \ - test/data/sighash.json \ - test/data/merkle_roots.json \ - test/data/merkle_roots_empty.json \ - test/data/merkle_serialization.json \ - test/data/merkle_witness_serialization.json \ - test/data/merkle_path.json \ - test/data/merkle_commitments.json \ - test/data/g1_compressed.json \ - test/data/g2_compressed.json + test/data/script_valid.json \ + test/data/base58_keys_valid.json \ + test/data/base58_encode_decode.json \ + test/data/base58_keys_invalid.json \ + test/data/script_invalid.json \ + test/data/tx_invalid.json \ + test/data/tx_valid.json \ + test/data/sighash.json \ + test/data/merkle_roots.json \ + test/data/merkle_roots_empty.json \ + test/data/merkle_serialization.json \ + test/data/merkle_witness_serialization.json \ + test/data/merkle_path.json \ + test/data/merkle_commitments.json \ + test/data/g1_compressed.json \ + test/data/g2_compressed.json RAW_TEST_FILES = test/data/alertTests.raw GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.raw.h) BITCOIN_TESTS =\ - test/arith_uint256_tests.cpp \ - test/bignum.h \ - test/addrman_tests.cpp \ - test/alert_tests.cpp \ - test/allocator_tests.cpp \ - test/base32_tests.cpp \ - test/base58_tests.cpp \ - test/base64_tests.cpp \ - test/bip32_tests.cpp \ - test/bloom_tests.cpp \ - test/checkblock_tests.cpp \ - test/Checkpoints_tests.cpp \ - test/coins_tests.cpp \ - test/compress_tests.cpp \ - test/crypto_tests.cpp \ - test/DoS_tests.cpp \ - test/equihash_tests.cpp \ - test/getarg_tests.cpp \ - test/hash_tests.cpp \ - test/key_tests.cpp \ - test/main_tests.cpp \ - test/mempool_tests.cpp \ - test/miner_tests.cpp \ - test/mruset_tests.cpp \ - test/multisig_tests.cpp \ - test/netbase_tests.cpp \ - test/pmt_tests.cpp \ - test/policyestimator_tests.cpp \ - test/pow_tests.cpp \ - test/raii_event_tests.cpp \ - test/reverselock_tests.cpp \ - test/rpc_tests.cpp \ - test/sanity_tests.cpp \ - test/scheduler_tests.cpp \ - test/script_P2SH_tests.cpp \ - test/script_tests.cpp \ - test/scriptnum_tests.cpp \ - test/serialize_tests.cpp \ - test/sighash_tests.cpp \ - test/sigopcount_tests.cpp \ - test/skiplist_tests.cpp \ - test/test_bitcoin.cpp \ - test/test_bitcoin.h \ - test/timedata_tests.cpp \ - test/torcontrol_tests.cpp \ - test/transaction_tests.cpp \ - test/uint256_tests.cpp \ - test/univalue_tests.cpp \ - test/util_tests.cpp \ - test/sha256compress_tests.cpp + test/arith_uint256_tests.cpp \ + test/bignum.h \ + test/addrman_tests.cpp \ + test/alert_tests.cpp \ + test/allocator_tests.cpp \ + test/base32_tests.cpp \ + test/base58_tests.cpp \ + test/base64_tests.cpp \ + test/bip32_tests.cpp \ + test/bloom_tests.cpp \ + test/checkblock_tests.cpp \ + test/Checkpoints_tests.cpp \ + test/coins_tests.cpp \ + test/compress_tests.cpp \ + test/crypto_tests.cpp \ + test/DoS_tests.cpp \ + test/equihash_tests.cpp \ + test/getarg_tests.cpp \ + test/hash_tests.cpp \ + test/key_tests.cpp \ + test/main_tests.cpp \ + test/mempool_tests.cpp \ + test/miner_tests.cpp \ + test/mruset_tests.cpp \ + test/multisig_tests.cpp \ + test/netbase_tests.cpp \ + test/pmt_tests.cpp \ + test/policyestimator_tests.cpp \ + test/pow_tests.cpp \ + test/raii_event_tests.cpp \ + test/reverselock_tests.cpp \ + test/rpc_tests.cpp \ + test/sanity_tests.cpp \ + test/scheduler_tests.cpp \ + test/script_P2SH_tests.cpp \ + test/script_P2PKH_tests.cpp \ + test/script_tests.cpp \ + test/scriptnum_tests.cpp \ + test/serialize_tests.cpp \ + test/sighash_tests.cpp \ + test/sigopcount_tests.cpp \ + test/skiplist_tests.cpp \ + test/test_bitcoin.cpp \ + test/test_bitcoin.h \ + test/timedata_tests.cpp \ + test/torcontrol_tests.cpp \ + test/transaction_tests.cpp \ + test/uint256_tests.cpp \ + test/univalue_tests.cpp \ + test/util_tests.cpp \ + test/sha256compress_tests.cpp if ENABLE_WALLET BITCOIN_TESTS += \ - test/accounting_tests.cpp \ - wallet/test/wallet_tests.cpp \ - test/rpc_wallet_tests.cpp + test/accounting_tests.cpp \ + wallet/test/wallet_tests.cpp \ + test/rpc_wallet_tests.cpp endif test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) @@ -137,6 +140,10 @@ bitcoin_test_clean : FORCE check-local: @echo "Running test/bitcoin-util-test.py..." $(AM_V_at)srcdir=$(srcdir) PYTHONPATH=$(builddir)/test $(srcdir)/test/bitcoin-util-test.py +if ENABLE_WALLET + @echo "Running test/wallet-utility.py..." + $(AM_V_at)srcdir=$(srcdir) PYTHONPATH=$(builddir)/test $(srcdir)/test/wallet-utility.py +endif $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C univalue check diff --git a/src/addressindex.h b/src/addressindex.h new file mode 100644 index 000000000..643f06e13 --- /dev/null +++ b/src/addressindex.h @@ -0,0 +1,82 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_ADDRESSINDEX_H +#define BITCOIN_ADDRESSINDEX_H + +#include "uint256.h" +#include "amount.h" + +struct CMempoolAddressDelta +{ + int64_t time; + CAmount amount; + uint256 prevhash; + unsigned int prevout; + + CMempoolAddressDelta(int64_t t, CAmount a, uint256 hash, unsigned int out) { + time = t; + amount = a; + prevhash = hash; + prevout = out; + } + + CMempoolAddressDelta(int64_t t, CAmount a) { + time = t; + amount = a; + prevhash.SetNull(); + prevout = 0; + } +}; + +struct CMempoolAddressDeltaKey +{ + int type; + uint160 addressBytes; + uint256 txhash; + unsigned int index; + int spending; + + CMempoolAddressDeltaKey(int addressType, uint160 addressHash, uint256 hash, unsigned int i, int s) { + type = addressType; + addressBytes = addressHash; + txhash = hash; + index = i; + spending = s; + } + + CMempoolAddressDeltaKey(int addressType, uint160 addressHash) { + type = addressType; + addressBytes = addressHash; + txhash.SetNull(); + index = 0; + spending = 0; + } +}; + +struct CMempoolAddressDeltaKeyCompare +{ + bool operator()(const CMempoolAddressDeltaKey& a, const CMempoolAddressDeltaKey& b) const { + if (a.type == b.type) { + if (a.addressBytes == b.addressBytes) { + if (a.txhash == b.txhash) { + if (a.index == b.index) { + return a.spending < b.spending; + } else { + return a.index < b.index; + } + } else { + return a.txhash < b.txhash; + } + } else { + return a.addressBytes < b.addressBytes; + } + } else { + return a.type < b.type; + } + } +}; + +#endif // BITCOIN_ADDRESSINDEX_H \ No newline at end of file diff --git a/src/assetchains b/src/assetchains index c3e161142..517687097 100755 --- a/src/assetchains +++ b/src/assetchains @@ -53,3 +53,5 @@ komodo_asset BEER 100000000 komodo_asset NINJA 100000000 komodo_asset OOT 216000000 komodo_asset BNTN 500000000 +komodo_asset CHAIN 999999 +komodo_asset PRLPAY 500000000 diff --git a/src/assetchains.old b/src/assetchains.old index 6bb60d95a..c025e0430 100755 --- a/src/assetchains.old +++ b/src/assetchains.old @@ -30,6 +30,8 @@ echo $pubkey ./komodod -pubkey=$pubkey -ac_name=NINJA -ac_supply=100000000 -addnode=78.47.196.146 & ./komodod -pubkey=$pubkey -ac_name=OOT -ac_supply=216000000 -addnode=174.138.107.226 & ./komodod -pubkey=$pubkey -ac_name=BNTN -ac_supply=500000000 -addnode=94.130.169.205 & +./komodod -pubkey=$pubkey -ac_name=CHAIN -ac_supply=999999 -addnode=78.47.146.222 & +./komodod -pubkey=$pubkey -ac_name=PRLPAY -ac_supply=500000000 -addnode=13.250.226.125 & #sleep $delay #./komodod -pubkey=$pubkey -ac_name=USD -addnode=78.47.196.146 $1 & diff --git a/src/base58.cpp b/src/base58.cpp index c9c64bc58..c086cc854 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -275,6 +275,23 @@ CTxDestination CBitcoinAddress::Get() const return CNoDestination(); } +bool CBitcoinAddress::GetIndexKey(uint160& hashBytes, int& type) const +{ + if (!IsValid()) { + return false; + } else if (vchVersion == Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) { + memcpy(&hashBytes, &vchData[0], 20); + type = 1; + return true; + } else if (vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS)) { + memcpy(&hashBytes, &vchData[0], 20); + type = 2; + return true; + } + + return false; +} + bool CBitcoinAddress::GetKeyID(CKeyID& keyID) const { if (!IsValid() || vchVersion != Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) diff --git a/src/base58.h b/src/base58.h index 24e7abfc8..73e1c4d94 100644 --- a/src/base58.h +++ b/src/base58.h @@ -163,6 +163,7 @@ public: CTxDestination Get() const; bool GetKeyID(CKeyID &keyID) const; + bool GetIndexKey(uint160& hashBytes, int& type) const; bool IsScript() const; }; diff --git a/src/clientversion.cpp b/src/clientversion.cpp index ae67e678f..9b523e582 100644 --- a/src/clientversion.cpp +++ b/src/clientversion.cpp @@ -48,10 +48,11 @@ const std::string CLIENT_NAME("MagicBean"); #include "build.h" #endif -//! git will put "#define GIT_ARCHIVE 1" on the next line inside archives. $Format:%n#define GIT_ARCHIVE 1$ +//! git will put "#define GIT_ARCHIVE 1" on the next line inside archives. +#define GIT_ARCHIVE 1 #ifdef GIT_ARCHIVE -#define GIT_COMMIT_ID "$Format:%h$" -#define GIT_COMMIT_DATE "$Format:%cD$" +#define GIT_COMMIT_ID "a86845f3dc" +#define GIT_COMMIT_DATE "Wed, 21 Feb 2018 16:15:11 +0200" #endif #define RENDER_BETA_STRING(num) "-beta" DO_STRINGIZE(num) diff --git a/src/fiat-cli b/src/fiat-cli index a7ee8c62e..ce2e7a51d 100755 --- a/src/fiat-cli +++ b/src/fiat-cli @@ -25,4 +25,6 @@ echo beer; fiat/beer $1 $2 $3 $4 echo vote2018; fiat/vote2018 $1 $2 $3 $4 echo ninja; fiat/ninja $1 $2 $3 $4 echo oot; fiat/oot $1 $2 $3 $4 -echo bntn: fiat/bntn $1 $2 $3 $4 +echo bntn; fiat/bntn $1 $2 $3 $4 +echo chain; fiat/chain $1 $2 $3 $4 +echo prlpay; fiat/prlpay $1 $2 $3 $4 diff --git a/src/fiat/chain b/src/fiat/chain new file mode 100755 index 000000000..c55d30fbd --- /dev/null +++ b/src/fiat/chain @@ -0,0 +1,2 @@ +#!/bin/bash +./komodo-cli -ac_name=CHAIN $1 $2 $3 $4 $5 $6 diff --git a/src/fiat/prlpay b/src/fiat/prlpay new file mode 100755 index 000000000..3d1063d13 --- /dev/null +++ b/src/fiat/prlpay @@ -0,0 +1,2 @@ +#!/bin/bash +./komodo-cli -ac_name=PRLPAY $1 $2 $3 $4 $5 $6 diff --git a/src/init.cpp b/src/init.cpp index 49c109829..aa55d1782 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -370,7 +370,9 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-sysperms", _("Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)")); #endif strUsage += HelpMessageOpt("-txindex", strprintf(_("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)"), 0)); - + strUsage += HelpMessageOpt("-addressindex", strprintf(_("Maintain a full address index, used to query for the balance, txids and unspent outputs for addresses (default: %u)"), DEFAULT_ADDRESSINDEX)); + strUsage += HelpMessageOpt("-timestampindex", strprintf(_("Maintain a timestamp index for block hashes, used to query blocks hashes by a range of timestamps (default: %u)"), DEFAULT_TIMESTAMPINDEX)); + strUsage += HelpMessageOpt("-spentindex", strprintf(_("Maintain a full spent index, used to query the spending txid and input index for an outpoint (default: %u)"), DEFAULT_SPENTINDEX)); strUsage += HelpMessageGroup(_("Connection options:")); strUsage += HelpMessageOpt("-addnode=", _("Add a node to connect to and attempt to keep the connection open")); strUsage += HelpMessageOpt("-banscore=", strprintf(_("Threshold for disconnecting misbehaving peers (default: %u)"), 100)); @@ -627,7 +629,6 @@ void ThreadImport(std::vector vImportFiles) // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked): InitBlockIndex(); KOMODO_LOADINGBLOCKS = 0; - fprintf(stderr,"KOMODO_LOADINGBLOCKS finished\n"); } // hardcoded $DATADIR/bootstrap.dat @@ -1365,18 +1366,34 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } + // block tree db settings + int dbMaxOpenFiles = GetArg("-dbmaxopenfiles", DEFAULT_DB_MAX_OPEN_FILES); + bool dbCompression = GetBoolArg("-dbcompression", DEFAULT_DB_COMPRESSION); + + LogPrintf("Block index database configuration:\n"); + LogPrintf("* Using %d max open files\n", dbMaxOpenFiles); + LogPrintf("* Compression is %s\n", dbCompression ? "enabled" : "disabled"); + // cache size calculations int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20); nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache int64_t nBlockTreeDBCache = nTotalCache / 8; - if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", true)) - nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB + + if (GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX) || GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)) { + // enable 3/4 of the cache if addressindex and/or spentindex is enabled + nBlockTreeDBCache = nTotalCache * 3 / 4; + } else { + if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) { + nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB + } + } nTotalCache -= nBlockTreeDBCache; int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nTotalCache -= nCoinDBCache; nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache LogPrintf("Cache configuration:\n"); + LogPrintf("* Max cache setting possible %.1fMiB\n", nMaxDbCache); LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for in-memory UTXO set\n", nCoinCacheUsage * (1.0 / 1024 / 1024)); @@ -1397,7 +1414,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) delete pcoinscatcher; delete pblocktree; - pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); + pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex, dbCompression, dbMaxOpenFiles); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinscatcher); @@ -1425,7 +1442,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) break; } KOMODO_LOADINGBLOCKS = 0; - fprintf(stderr,"KOMODO_LOADINGBLOCKS finished\n"); // Check for changed -txindex state if (fTxIndex != GetBoolArg("-txindex", true)) { strLoadError = _("You need to rebuild the database using -reindex to change -txindex"); @@ -1488,7 +1504,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } KOMODO_LOADINGBLOCKS = 0; - fprintf(stderr,"KOMODO_LOADINGBLOCKS finished\n"); // As LoadBlockIndex can take several minutes, it's possible the user // requested to kill the GUI during the last operation. If so, exit. diff --git a/src/komodo_bitcoind.h b/src/komodo_bitcoind.h index cd65ab9dd..ba5e8d255 100644 --- a/src/komodo_bitcoind.h +++ b/src/komodo_bitcoind.h @@ -169,14 +169,14 @@ try_again: curl_handle = curl_easy_init(); init_string(&s); headers = curl_slist_append(0,"Expect:"); - - curl_easy_setopt(curl_handle,CURLOPT_USERAGENT,"mozilla/4.0");//"Mozilla/4.0 (compatible; )"); + + curl_easy_setopt(curl_handle,CURLOPT_USERAGENT,"mozilla/4.0");//"Mozilla/4.0 (compatible; )"); curl_easy_setopt(curl_handle,CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl_handle,CURLOPT_URL, url); curl_easy_setopt(curl_handle,CURLOPT_WRITEFUNCTION, (void *)accumulatebytes); // send all data to this function curl_easy_setopt(curl_handle,CURLOPT_WRITEDATA, &s); // we pass our 's' struct to the callback curl_easy_setopt(curl_handle,CURLOPT_NOSIGNAL, 1L); // supposed to fix "Alarm clock" and long jump crash - curl_easy_setopt(curl_handle,CURLOPT_NOPROGRESS, 1L); // no progress callback + curl_easy_setopt(curl_handle,CURLOPT_NOPROGRESS, 1L); // no progress callback if ( strncmp(url,"https",5) == 0 ) { curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYPEER,0); @@ -198,7 +198,7 @@ try_again: bracket0 = (char *)"["; bracket1 = (char *)"]"; } - + databuf = (char *)malloc(256 + strlen(command) + strlen(params)); sprintf(databuf,"{\"id\":\"jl777\",\"method\":\"%s\",\"params\":%s%s%s}",command,bracket0,params,bracket1); //printf("url.(%s) userpass.(%s) databuf.(%s)\n",url,userpass,databuf); @@ -238,7 +238,7 @@ try_again: free(s.ptr); sleep((1< 10 ) - height -= 10; - if ( ASSETCHAINS_SYMBOL[0] == 0 ) - hash = _komodo_getblockhash(height); - if ( memcmp(&hash,&zero,sizeof(hash)) == 0 ) - hash = komodo_getblockhash(height); - int32_t i; - for (i=0; i<32; i++) - printf("%02x",((uint8_t *)&hash)[i]); - printf(" seed.%d\n",height); - seed = arith_uint256(hash.GetHex()).GetLow64(); - } - else*/ + { + uint256 hash,zero; CBlockIndex *pindex; + memset(&hash,0,sizeof(hash)); + memset(&zero,0,sizeof(zero)); + if ( height > 10 ) + height -= 10; + if ( ASSETCHAINS_SYMBOL[0] == 0 ) + hash = _komodo_getblockhash(height); + if ( memcmp(&hash,&zero,sizeof(hash)) == 0 ) + hash = komodo_getblockhash(height); + int32_t i; + for (i=0; i<32; i++) + printf("%02x",((uint8_t *)&hash)[i]); + printf(" seed.%d\n",height); + seed = arith_uint256(hash.GetHex()).GetLow64(); + } + else*/ { seed = (height << 13) ^ (height << 2); seed <<= 21; @@ -838,13 +838,13 @@ int32_t komodo_eligiblenotary(uint8_t pubkeys[66][33],int32_t *mids,int32_t *non int32_t komodo_minerids(uint8_t *minerids,int32_t height,int32_t width) // deprecate { /*int32_t i,n=0; - for (i=0; i 700000 ) - { - for (j=0; j<66; j++) - fprintf(stderr,"%d ",mids[j]); - fprintf(stderr,"ht.%d repeat notaryid.%d in mids[%d]\n",height,notaryid,i); - } + //for (j=0; j<66; j++) + // fprintf(stderr,"%d ",mids[j]); + //fprintf(stderr,"ht.%d repeat notaryid.%d in mids[%d]\n",height,notaryid,i); if ( height > 792000 ) return(-1); else break; diff --git a/src/komodo_events.h b/src/komodo_events.h index 5cd29ce72..c64ccbaa2 100644 --- a/src/komodo_events.h +++ b/src/komodo_events.h @@ -85,7 +85,7 @@ void komodo_eventadd_pricefeed(struct komodo_state *sp,char *symbol,int32_t heig komodo_eventadd(sp,height,symbol,KOMODO_EVENT_PRICEFEED,(uint8_t *)&F,(int32_t)(sizeof(F.num) + sizeof(*F.prices) * num)); if ( sp != 0 ) komodo_pvals(height,prices,num); - } else fprintf(stderr,"skip pricefeed[%d]\n",num); + } //else fprintf(stderr,"skip pricefeed[%d]\n",num); } void komodo_eventadd_opreturn(struct komodo_state *sp,char *symbol,int32_t height,uint256 txid,uint64_t value,uint16_t vout,uint8_t *buf,uint16_t opretlen) @@ -188,30 +188,30 @@ void komodo_eventadd_kmdheight(struct komodo_state *sp,char *symbol,int32_t heig /*void komodo_eventadd_deposit(int32_t actionflag,char *symbol,int32_t height,uint64_t komodoshis,char *fiat,uint64_t fiatoshis,uint8_t rmd160[20],bits256 kmdtxid,uint16_t kmdvout,uint64_t price) -{ - uint8_t opret[512]; uint16_t opretlen; - komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_DEPOSIT,kmdtxid,komodoshis,kmdvout,opret,opretlen); -} - -void komodo_eventadd_issued(int32_t actionflag,char *symbol,int32_t height,int32_t fiatheight,bits256 fiattxid,uint16_t fiatvout,bits256 kmdtxid,uint16_t kmdvout,uint64_t fiatoshis) -{ - uint8_t opret[512]; uint16_t opretlen; - komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_ISSUED,fiattxid,fiatoshis,fiatvout,opret,opretlen); -} - -void komodo_eventadd_withdraw(int32_t actionflag,char *symbol,int32_t height,uint64_t komodoshis,char *fiat,uint64_t fiatoshis,uint8_t rmd160[20],bits256 fiattxid,int32_t fiatvout,uint64_t price) -{ - uint8_t opret[512]; uint16_t opretlen; - komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_WITHDRAW,fiattxid,fiatoshis,fiatvout,opret,opretlen); -} - -void komodo_eventadd_redeemed(int32_t actionflag,char *symbol,int32_t height,bits256 kmdtxid,uint16_t kmdvout,int32_t fiatheight,bits256 fiattxid,uint16_t fiatvout,uint64_t komodoshis) -{ - uint8_t opret[512]; uint16_t opretlen; - komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_REDEEMED,kmdtxid,komodoshis,kmdvout,opret,opretlen); -}*/ + { + uint8_t opret[512]; uint16_t opretlen; + komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_DEPOSIT,kmdtxid,komodoshis,kmdvout,opret,opretlen); + } + + void komodo_eventadd_issued(int32_t actionflag,char *symbol,int32_t height,int32_t fiatheight,bits256 fiattxid,uint16_t fiatvout,bits256 kmdtxid,uint16_t kmdvout,uint64_t fiatoshis) + { + uint8_t opret[512]; uint16_t opretlen; + komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_ISSUED,fiattxid,fiatoshis,fiatvout,opret,opretlen); + } + + void komodo_eventadd_withdraw(int32_t actionflag,char *symbol,int32_t height,uint64_t komodoshis,char *fiat,uint64_t fiatoshis,uint8_t rmd160[20],bits256 fiattxid,int32_t fiatvout,uint64_t price) + { + uint8_t opret[512]; uint16_t opretlen; + komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_WITHDRAW,fiattxid,fiatoshis,fiatvout,opret,opretlen); + } + + void komodo_eventadd_redeemed(int32_t actionflag,char *symbol,int32_t height,bits256 kmdtxid,uint16_t kmdvout,int32_t fiatheight,bits256 fiattxid,uint16_t fiatvout,uint64_t komodoshis) + { + uint8_t opret[512]; uint16_t opretlen; + komodo_eventadd_opreturn(symbol,height,KOMODO_OPRETURN_REDEEMED,kmdtxid,komodoshis,kmdvout,opret,opretlen); + }*/ // process events -// +// #endif diff --git a/src/komodo_notary.h b/src/komodo_notary.h index 3c2819df5..b770a6514 100644 --- a/src/komodo_notary.h +++ b/src/komodo_notary.h @@ -436,6 +436,7 @@ int32_t komodo_MoMdata(int32_t *notarized_htp,uint256 *MoMp,uint256 *kmdtxidp,in return(0); } + int32_t komodo_notarizeddata(int32_t nHeight,uint256 *notarized_hashp,uint256 *notarized_desttxidp) { struct notarized_checkpoint *np = 0; int32_t i=0,flag = 0; char symbol[KOMODO_ASSETCHAIN_MAXLEN],dest[KOMODO_ASSETCHAIN_MAXLEN]; struct komodo_state *sp; diff --git a/src/leveldbwrapper.cpp b/src/leveldbwrapper.cpp index b5d024abb..3f1abbe14 100644 --- a/src/leveldbwrapper.cpp +++ b/src/leveldbwrapper.cpp @@ -27,14 +27,14 @@ void HandleError(const leveldb::Status& status) throw leveldb_error("Unknown database error"); } -static leveldb::Options GetOptions(size_t nCacheSize) +static leveldb::Options GetOptions(size_t nCacheSize, bool compression, int maxOpenFiles) { leveldb::Options options; options.block_cache = leveldb::NewLRUCache(nCacheSize / 2); options.write_buffer_size = nCacheSize / 4; // up to two write buffers may be held in memory simultaneously options.filter_policy = leveldb::NewBloomFilterPolicy(10); - options.compression = leveldb::kNoCompression; - options.max_open_files = 64; + options.compression = compression ? leveldb::kSnappyCompression : leveldb::kNoCompression; + options.max_open_files = maxOpenFiles; if (leveldb::kMajorVersion > 1 || (leveldb::kMajorVersion == 1 && leveldb::kMinorVersion >= 16)) { // LevelDB versions before 1.16 consider short writes to be corruption. Only trigger error // on corruption in later versions. @@ -43,14 +43,14 @@ static leveldb::Options GetOptions(size_t nCacheSize) return options; } -CLevelDBWrapper::CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory, bool fWipe) +CLevelDBWrapper::CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory, bool fWipe, bool compression, int maxOpenFiles) { penv = NULL; readoptions.verify_checksums = true; iteroptions.verify_checksums = true; iteroptions.fill_cache = false; syncoptions.sync = true; - options = GetOptions(nCacheSize); + options = GetOptions(nCacheSize, compression, maxOpenFiles); options.create_if_missing = true; if (fMemory) { penv = leveldb::NewMemEnv(leveldb::Env::Default()); diff --git a/src/leveldbwrapper.h b/src/leveldbwrapper.h index 639f736a5..2a6e345f1 100644 --- a/src/leveldbwrapper.h +++ b/src/leveldbwrapper.h @@ -86,7 +86,7 @@ private: leveldb::DB* pdb; public: - CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false); + CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool compression = false, int maxOpenFiles = 64); ~CLevelDBWrapper(); template diff --git a/src/main.cpp b/src/main.cpp index 69b5cb875..4a3e5c3ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,6 +68,9 @@ bool fExperimentalMode = false; bool fImporting = false; bool fReindex = false; bool fTxIndex = false; +bool fAddressIndex = false; +bool fTimestampIndex = false; +bool fSpentIndex = false; bool fHavePruned = false; bool fPruneMode = false; bool fIsBareMultisigStd = true; @@ -1484,6 +1487,16 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa if ( komodo_is_notarytx(tx) == 0 ) KOMODO_ON_DEMAND++; pool.addUnchecked(hash, entry, !IsInitialBlockDownload()); + + // Add memory address index + if (fAddressIndex) { + pool.addAddressIndex(entry, view); + } + + // Add memory spent index + if (fSpentIndex) { + pool.addSpentIndex(entry, view); + } } SyncWithWallets(tx, NULL); @@ -1491,6 +1504,55 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return true; } +bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &hashes) +{ + if (!fTimestampIndex) + return error("Timestamp index not enabled"); + + if (!pblocktree->ReadTimestampIndex(high, low, fActiveOnly, hashes)) + return error("Unable to get hashes for timestamps"); + + return true; +} + +bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) +{ + if (!fSpentIndex) + return false; + + if (mempool.getSpentIndex(key, value)) + return true; + + if (!pblocktree->ReadSpentIndex(key, value)) + return false; + + return true; +} + +bool GetAddressIndex(uint160 addressHash, int type, + std::vector > &addressIndex, int start, int end) +{ + if (!fAddressIndex) + return error("address index not enabled"); + + if (!pblocktree->ReadAddressIndex(addressHash, type, addressIndex, start, end)) + return error("unable to get txids for address"); + + return true; +} + +bool GetAddressUnspent(uint160 addressHash, int type, + std::vector > &unspentOutputs) +{ + if (!fAddressIndex) + return error("address index not enabled"); + + if (!pblocktree->ReadAddressUnspentIndex(addressHash, type, unspentOutputs)) + return error("unable to get txids for address"); + + return true; +} + /** Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock */ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) { @@ -2187,7 +2249,6 @@ namespace { catch (const std::exception& e) { return error("%s: Deserialize or I/O error - %s", __func__, e.what()); } - // Verify checksum CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION); hasher << hashBlock; @@ -2269,12 +2330,45 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) return error("DisconnectBlock(): block and undo data inconsistent"); - + std::vector > addressIndex; + std::vector > addressUnspentIndex; + std::vector > spentIndex; + // undo transactions in reverse order for (int i = block.vtx.size() - 1; i >= 0; i--) { const CTransaction &tx = block.vtx[i]; uint256 hash = tx.GetHash(); - + if (fAddressIndex) { + + for (unsigned int k = tx.vout.size(); k-- > 0;) { + const CTxOut &out = tx.vout[k]; + + if (out.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + + // undo receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); + + // undo unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), hash, k), CAddressUnspentValue())); + + } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + + // undo receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); + + // undo unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), hash, k), CAddressUnspentValue())); + + } else { + continue; + } + + } + + } + // Check that all outputs are available and match the outputs in the block itself // exactly. { @@ -2311,6 +2405,39 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex const CTxInUndo &undo = txundo.vprevout[j]; if (!ApplyTxInUndo(undo, view, out)) fClean = false; + + const CTxIn input = tx.vin[j]; + + if (fSpentIndex) { + // undo and delete the spent index + spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue())); + } + + if (fAddressIndex) { + const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); + if (prevout.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22); + + // undo spending activity + addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); + + // restore unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + + + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); + + // undo spending activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); + + // restore unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + + } else { + continue; + } + } } } } @@ -2325,7 +2452,15 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex *pfClean = fClean; return true; } - + + if (fAddressIndex) { + if (!pblocktree->EraseAddressIndex(addressIndex)) { + return AbortNode(state, "Failed to delete address index"); + } + if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { + return AbortNode(state, "Failed to write address unspent index"); + } + } return fClean; } @@ -2429,7 +2564,7 @@ static int64_t nTimeIndex = 0; static int64_t nTimeCallbacks = 0; static int64_t nTimeTotal = 0; -bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool fJustCheck) +bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool fJustCheck,bool fCheckPOW) { const CChainParams& chainparams = Params(); @@ -2447,7 +2582,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin auto disabledVerifier = libzcash::ProofVerifier::Disabled(); // Check it again to verify JoinSplit proofs, and in case a previous version let a bad block in - if (!CheckBlock(pindex->nHeight,pindex,block, state, fExpensiveChecks ? verifier : disabledVerifier, 1, !fJustCheck)) //!fJustCheck, !fJustCheck)) + if (!CheckBlock(pindex->nHeight,pindex,block, state, fExpensiveChecks ? verifier : disabledVerifier, fCheckPOW, !fJustCheck)) //!fJustCheck, !fJustCheck)) return false; // verify that the view's current state corresponds to the previous block @@ -2497,7 +2632,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin std::vector > vPos; vPos.reserve(block.vtx.size()); blockundo.vtxundo.reserve(block.vtx.size() - 1); - + std::vector > addressIndex; + std::vector > addressUnspentIndex; + std::vector > spentIndex; // Construct the incremental merkle tree at the current // block position, auto old_tree_root = view.GetBestAnchor(); @@ -2524,6 +2661,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = block.vtx[i]; + const uint256 txhash = tx.GetHash(); nInputs += tx.vin.size(); nSigOps += GetLegacySigOpCount(tx); if (nSigOps > MAX_BLOCK_SIGOPS) @@ -2540,7 +2678,42 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!view.HaveJoinSplitRequirements(tx)) return state.DoS(100, error("ConnectBlock(): JoinSplit requirements not met"), REJECT_INVALID, "bad-txns-joinsplit-requirements-not-met"); - + if (fAddressIndex || fSpentIndex) + { + for (size_t j = 0; j < tx.vin.size(); j++) { + + const CTxIn input = tx.vin[j]; + const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); + uint160 hashBytes; + int addressType; + + if (prevout.scriptPubKey.IsPayToScriptHash()) { + hashBytes = uint160(vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); + addressType = 2; + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + hashBytes = uint160(vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); + addressType = 1; + } else { + hashBytes.SetNull(); + addressType = 0; + } + + if (fAddressIndex && addressType > 0) { + // record spending activity + addressIndex.push_back(make_pair(CAddressIndexKey(addressType, hashBytes, pindex->nHeight, i, txhash, j, true), prevout.nValue * -1)); + + // remove address from unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(addressType, hashBytes, input.prevout.hash, input.prevout.n), CAddressUnspentValue())); + } + + if (fSpentIndex) { + // add the spent index to determine the txid and input that spent an output + // and to find the amount and address from an input + spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue(txhash, j, pindex->nHeight, prevout.nValue, addressType, hashBytes))); + } + } + + } // Add in sigops done by pay-to-script-hash inputs; // this is to prevent a "rogue miner" from creating // an incredibly-expensive-to-validate block. @@ -2562,6 +2735,36 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return false; control.Add(vChecks); } + + if (fAddressIndex) { + for (unsigned int k = 0; k < tx.vout.size(); k++) { + const CTxOut &out = tx.vout[k]; + + if (out.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + + // record receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); + + // record unspent output + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + + } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + + // record receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); + + // record unspent output + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + + } else { + continue; + } + + } + } + //if ( ASSETCHAINS_SYMBOL[0] == 0 ) // komodo_earned_interest(pindex->nHeight,sum); CTxUndo undoDummy; @@ -2654,7 +2857,41 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (fTxIndex) if (!pblocktree->WriteTxIndex(vPos)) return AbortNode(state, "Failed to write transaction index"); - + if (fAddressIndex) { + if (!pblocktree->WriteAddressIndex(addressIndex)) { + return AbortNode(state, "Failed to write address index"); + } + + if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { + return AbortNode(state, "Failed to write address unspent index"); + } + } + + if (fSpentIndex) + if (!pblocktree->UpdateSpentIndex(spentIndex)) + return AbortNode(state, "Failed to write transaction index"); + + if (fTimestampIndex) { + unsigned int logicalTS = pindex->nTime; + unsigned int prevLogicalTS = 0; + + // retrieve logical timestamp of the previous block + if (pindex->pprev) + if (!pblocktree->ReadTimestampBlockIndex(pindex->pprev->GetBlockHash(), prevLogicalTS)) + LogPrintf("%s: Failed to read previous block's logical timestamp\n", __func__); + + if (logicalTS <= prevLogicalTS) { + logicalTS = prevLogicalTS + 1; + LogPrintf("%s: Previous logical timestamp is newer Actual[%d] prevLogical[%d] Logical[%d]\n", __func__, pindex->nTime, prevLogicalTS, logicalTS); + } + + if (!pblocktree->WriteTimestampIndex(CTimestampIndexKey(logicalTS, pindex->GetBlockHash()))) + return AbortNode(state, "Failed to write timestamp index"); + + if (!pblocktree->WriteTimestampBlockIndex(CTimestampBlockIndexKey(pindex->GetBlockHash()), CTimestampBlockIndexValue(logicalTS))) + return AbortNode(state, "Failed to write blockhash index"); + } + // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); @@ -2923,7 +3160,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock * LogPrint("bench", " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001); { CCoinsViewCache view(pcoinsTip); - bool rv = ConnectBlock(*pblock, state, pindexNew, view); + bool rv = ConnectBlock(*pblock, state, pindexNew, view, false, true); GetMainSignals().BlockChecked(*pblock, state); if (!rv) { if (state.IsInvalid()) @@ -3536,18 +3773,18 @@ int32_t komodo_reverify_blockcheck(CValidationState& state,int32_t height,CBlock { fprintf(stderr,"possible fork: tip.%d longest.%d newblock.%d lag.%d blocktime.%u\n",tipindex->nHeight,KOMODO_LONGESTCHAIN,height,(int32_t)(GetAdjustedTime() - tipindex->nTime),tipindex->nTime); /*KOMODO_REWIND = tipindex->nHeight - 11; - rewindtarget = tipindex->nHeight - 11; - fprintf(stderr,"rewindtarget <- %d\n",rewindtarget); - oneshot = 1; - while ( rewindtarget > 0 && (tipindex= chainActive.Tip()) != 0 && tipindex->nHeight > rewindtarget ) - { - fprintf(stderr,"%d ",(int32_t)tipindex->nHeight); - InvalidateBlock(state,tipindex); - if ( !DisconnectTip(state) ) - break; - } - tipindex = chainActive.Tip(); - fprintf(stderr,"rewind done to %d\n",tipindex!=0?tipindex->nHeight:-1);*/ + rewindtarget = tipindex->nHeight - 11; + fprintf(stderr,"rewindtarget <- %d\n",rewindtarget); + oneshot = 1; + while ( rewindtarget > 0 && (tipindex= chainActive.Tip()) != 0 && tipindex->nHeight > rewindtarget ) + { + fprintf(stderr,"%d ",(int32_t)tipindex->nHeight); + InvalidateBlock(state,tipindex); + if ( !DisconnectTip(state) ) + break; + } + tipindex = chainActive.Tip(); + fprintf(stderr,"rewind done to %d\n",tipindex!=0?tipindex->nHeight:-1);*/ } } } @@ -3564,15 +3801,15 @@ bool CheckBlock(int32_t height,CBlockIndex *pindex,const CBlock& block, CValidat // Check that the header is valid (particularly PoW). This is mostly // redundant with the call in AcceptBlockHeader. if (!CheckBlockHeader(height,pindex,block,state,fCheckPOW)) + { + fprintf(stderr,"checkblockheader error PoW.%d\n",fCheckPOW); return false; + } if ( fCheckPOW && !CheckEquihashSolution(&block, Params()) ) return state.DoS(100, error("CheckBlock(): Equihash solution invalid"),REJECT_INVALID, "invalid-solution"); komodo_block2pubkey33(pubkey33,(CBlock *)&block); if ( fCheckPOW && !CheckProofOfWork(height,pubkey33,block.GetHash(), block.nBits, Params().GetConsensus()) ) - { - //komodo_reverify_blockcheck(state,height,pindex); return state.DoS(1, error("CheckBlock(): proof of work failed"),REJECT_INVALID, "high-hash"); - } // Check the merkle root. if (fCheckMerkleRoot) { bool mutated; @@ -3627,7 +3864,7 @@ bool CheckBlock(int32_t height,CBlockIndex *pindex,const CBlock& block, CValidat //if ( komodo_check_deposit(ASSETCHAINS_SYMBOL[0] == 0 ? height : pindex != 0 ? (int32_t)pindex->nHeight : chainActive.Tip()->nHeight+1,block,pindex==0||pindex->pprev==0?0:pindex->pprev->nTime) < 0 ) { static uint32_t counter; - if ( counter++ < 100 && ASSETCHAINS_STAKED == 0 ) + //if ( counter++ < 100 && ASSETCHAINS_STAKED == 0 ) fprintf(stderr,"check deposit rejection\n"); return(false); } @@ -3969,7 +4206,7 @@ bool TestBlockValidity(CValidationState &state, const CBlock& block, CBlockIndex } if (!CheckBlock(indexDummy.nHeight,0,block, state, verifier, fCheckPOW, fCheckMerkleRoot)) { - //fprintf(stderr,"TestBlockValidity failure B\n"); + fprintf(stderr,"TestBlockValidity failure B\n"); return false; } if (!ContextualCheckBlock(block, state, pindexPrev)) @@ -3977,7 +4214,7 @@ bool TestBlockValidity(CValidationState &state, const CBlock& block, CBlockIndex fprintf(stderr,"TestBlockValidity failure C\n"); return false; } - if (!ConnectBlock(block, state, &indexDummy, viewNew, true)) + if (!ConnectBlock(block, state, &indexDummy, viewNew, true,fCheckPOW)) { //fprintf(stderr,"TestBlockValidity failure D\n"); return false; @@ -4299,7 +4536,18 @@ bool static LoadBlockIndexDB() // Check whether we have a transaction index pblocktree->ReadFlag("txindex", fTxIndex); LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled"); - + // Check whether we have an address index + pblocktree->ReadFlag("addressindex", fAddressIndex); + LogPrintf("%s: address index %s\n", __func__, fAddressIndex ? "enabled" : "disabled"); + + // Check whether we have a timestamp index + pblocktree->ReadFlag("timestampindex", fTimestampIndex); + LogPrintf("%s: timestamp index %s\n", __func__, fTimestampIndex ? "enabled" : "disabled"); + + // Check whether we have a spent index + pblocktree->ReadFlag("spentindex", fSpentIndex); + LogPrintf("%s: spent index %s\n", __func__, fSpentIndex ? "enabled" : "disabled"); + // Fill in-memory data BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex) { @@ -4415,7 +4663,7 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth CBlock block; if (!ReadBlockFromDisk(block, pindex)) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); - if (!ConnectBlock(block, state, pindex, coins)) + if (!ConnectBlock(block, state, pindex, coins,false, true)) return error("VerifyDB(): *** found unconnectable block at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); } } @@ -4619,6 +4867,16 @@ bool InitBlockIndex() { // Use the provided setting for -txindex in the new database fTxIndex = GetBoolArg("-txindex", true); pblocktree->WriteFlag("txindex", fTxIndex); + // Use the provided setting for -addressindex in the new database + fAddressIndex = GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX); + pblocktree->WriteFlag("addressindex", fAddressIndex); + + // Use the provided setting for -timestampindex in the new database + fTimestampIndex = GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX); + pblocktree->WriteFlag("timestampindex", fTimestampIndex); + + fSpentIndex = GetBoolArg("-spentindex", DEFAULT_SPENTINDEX); + pblocktree->WriteFlag("spentindex", fSpentIndex); LogPrintf("Initializing databases...\n"); // Only add the genesis block if not reindexing (in which case we reuse the one already on disk) diff --git a/src/main.h b/src/main.h index 1c864de20..9ae87710a 100644 --- a/src/main.h +++ b/src/main.h @@ -22,6 +22,7 @@ #include "script/script.h" #include "script/serverchecker.h" #include "script/standard.h" +#include "spentindex.h" #include "sync.h" #include "tinyformat.h" #include "txmempool.h" @@ -101,6 +102,12 @@ static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60; /** Maximum length of reject messages. */ static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111; +static const bool DEFAULT_ADDRESSINDEX = false; +static const bool DEFAULT_TIMESTAMPINDEX = false; +static const bool DEFAULT_SPENTINDEX = false; +static const unsigned int DEFAULT_DB_MAX_OPEN_FILES = 1000; +static const bool DEFAULT_DB_COMPRESSION = true; + // Sanity check the magic numbers when we change them BOOST_STATIC_ASSERT(DEFAULT_BLOCK_MAX_SIZE <= MAX_BLOCK_SIZE); BOOST_STATIC_ASSERT(DEFAULT_BLOCK_PRIORITY_SIZE <= DEFAULT_BLOCK_MAX_SIZE); @@ -268,6 +275,338 @@ struct CNodeStateStats { std::vector vHeightInFlight; }; +struct CTimestampIndexIteratorKey { + unsigned int timestamp; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 4; + } + template + void Serialize(Stream& s, int nType, int nVersion) const { + ser_writedata32be(s, timestamp); + } + template + void Unserialize(Stream& s, int nType, int nVersion) { + timestamp = ser_readdata32be(s); + } + + CTimestampIndexIteratorKey(unsigned int time) { + timestamp = time; + } + + CTimestampIndexIteratorKey() { + SetNull(); + } + + void SetNull() { + timestamp = 0; + } +}; + +struct CTimestampIndexKey { + unsigned int timestamp; + uint256 blockHash; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 36; + } + template + void Serialize(Stream& s, int nType, int nVersion) const { + ser_writedata32be(s, timestamp); + blockHash.Serialize(s, nType, nVersion); + } + template + void Unserialize(Stream& s, int nType, int nVersion) { + timestamp = ser_readdata32be(s); + blockHash.Unserialize(s, nType, nVersion); + } + + CTimestampIndexKey(unsigned int time, uint256 hash) { + timestamp = time; + blockHash = hash; + } + + CTimestampIndexKey() { + SetNull(); + } + + void SetNull() { + timestamp = 0; + blockHash.SetNull(); + } +}; + +struct CTimestampBlockIndexKey { + uint256 blockHash; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 32; + } + + template + void Serialize(Stream& s, int nType, int nVersion) const { + blockHash.Serialize(s, nType, nVersion); + } + + template + void Unserialize(Stream& s, int nType, int nVersion) { + blockHash.Unserialize(s, nType, nVersion); + } + + CTimestampBlockIndexKey(uint256 hash) { + blockHash = hash; + } + + CTimestampBlockIndexKey() { + SetNull(); + } + + void SetNull() { + blockHash.SetNull(); + } +}; + +struct CTimestampBlockIndexValue { + unsigned int ltimestamp; + size_t GetSerializeSize(int nType, int nVersion) const { + return 4; + } + + template + void Serialize(Stream& s, int nType, int nVersion) const { + ser_writedata32be(s, ltimestamp); + } + + template + void Unserialize(Stream& s, int nType, int nVersion) { + ltimestamp = ser_readdata32be(s); + } + + CTimestampBlockIndexValue (unsigned int time) { + ltimestamp = time; + } + + CTimestampBlockIndexValue() { + SetNull(); + } + + void SetNull() { + ltimestamp = 0; + } +}; + +struct CAddressUnspentKey { + unsigned int type; + uint160 hashBytes; + uint256 txhash; + size_t index; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 57; + } + template + void Serialize(Stream& s, int nType, int nVersion) const { + ser_writedata8(s, type); + hashBytes.Serialize(s, nType, nVersion); + txhash.Serialize(s, nType, nVersion); + ser_writedata32(s, index); + } + template + void Unserialize(Stream& s, int nType, int nVersion) { + type = ser_readdata8(s); + hashBytes.Unserialize(s, nType, nVersion); + txhash.Unserialize(s, nType, nVersion); + index = ser_readdata32(s); + } + + CAddressUnspentKey(unsigned int addressType, uint160 addressHash, uint256 txid, size_t indexValue) { + type = addressType; + hashBytes = addressHash; + txhash = txid; + index = indexValue; + } + + CAddressUnspentKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + txhash.SetNull(); + index = 0; + } +}; + +struct CAddressUnspentValue { + CAmount satoshis; + CScript script; + int blockHeight; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(satoshis); + READWRITE(script); + READWRITE(blockHeight); + } + + CAddressUnspentValue(CAmount sats, CScript scriptPubKey, int height) { + satoshis = sats; + script = scriptPubKey; + blockHeight = height; + } + + CAddressUnspentValue() { + SetNull(); + } + + void SetNull() { + satoshis = -1; + script.clear(); + blockHeight = 0; + } + + bool IsNull() const { + return (satoshis == -1); + } +}; + +struct CAddressIndexKey { + unsigned int type; + uint160 hashBytes; + int blockHeight; + unsigned int txindex; + uint256 txhash; + size_t index; + bool spending; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 66; + } + template + void Serialize(Stream& s, int nType, int nVersion) const { + ser_writedata8(s, type); + hashBytes.Serialize(s, nType, nVersion); + // Heights are stored big-endian for key sorting in LevelDB + ser_writedata32be(s, blockHeight); + ser_writedata32be(s, txindex); + txhash.Serialize(s, nType, nVersion); + ser_writedata32(s, index); + char f = spending; + ser_writedata8(s, f); + } + template + void Unserialize(Stream& s, int nType, int nVersion) { + type = ser_readdata8(s); + hashBytes.Unserialize(s, nType, nVersion); + blockHeight = ser_readdata32be(s); + txindex = ser_readdata32be(s); + txhash.Unserialize(s, nType, nVersion); + index = ser_readdata32(s); + char f = ser_readdata8(s); + spending = f; + } + + CAddressIndexKey(unsigned int addressType, uint160 addressHash, int height, int blockindex, + uint256 txid, size_t indexValue, bool isSpending) { + type = addressType; + hashBytes = addressHash; + blockHeight = height; + txindex = blockindex; + txhash = txid; + index = indexValue; + spending = isSpending; + } + + CAddressIndexKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + blockHeight = 0; + txindex = 0; + txhash.SetNull(); + index = 0; + spending = false; + } + +}; + +struct CAddressIndexIteratorKey { + unsigned int type; + uint160 hashBytes; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 21; + } + template + void Serialize(Stream& s, int nType, int nVersion) const { + ser_writedata8(s, type); + hashBytes.Serialize(s, nType, nVersion); + } + template + void Unserialize(Stream& s, int nType, int nVersion) { + type = ser_readdata8(s); + hashBytes.Unserialize(s, nType, nVersion); + } + + CAddressIndexIteratorKey(unsigned int addressType, uint160 addressHash) { + type = addressType; + hashBytes = addressHash; + } + + CAddressIndexIteratorKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + } +}; + +struct CAddressIndexIteratorHeightKey { + unsigned int type; + uint160 hashBytes; + int blockHeight; + + size_t GetSerializeSize(int nType, int nVersion) const { + return 25; + } + template + void Serialize(Stream& s, int nType, int nVersion) const { + ser_writedata8(s, type); + hashBytes.Serialize(s, nType, nVersion); + ser_writedata32be(s, blockHeight); + } + template + void Unserialize(Stream& s, int nType, int nVersion) { + type = ser_readdata8(s); + hashBytes.Unserialize(s, nType, nVersion); + blockHeight = ser_readdata32be(s); + } + + CAddressIndexIteratorHeightKey(unsigned int addressType, uint160 addressHash, int height) { + type = addressType; + hashBytes = addressHash; + blockHeight = height; + } + + CAddressIndexIteratorHeightKey() { + SetNull(); + } + + void SetNull() { + type = 0; + hashBytes.SetNull(); + blockHeight = 0; + } +}; + struct CDiskTxPos : public CDiskBlockPos { unsigned int nTxOffset; // after header @@ -431,6 +770,13 @@ public: ScriptError GetScriptError() const { return error; } }; +bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &hashes); +bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value); +bool GetAddressIndex(uint160 addressHash, int type, + std::vector > &addressIndex, + int start = 0, int end = 0); +bool GetAddressUnspent(uint160 addressHash, int type, + std::vector > &unspentOutputs); /** Functions for disk access for blocks */ bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart); @@ -447,7 +793,7 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex); bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool* pfClean = NULL); /** Apply the effects of this block (with given index) on the UTXO set represented by coins */ -bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool fJustCheck = false); +bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool fJustCheck = false,bool fCheckPOW = false); /** Context-independent validity checks */ bool CheckBlockHeader(int32_t height,CBlockIndex *pindex,const CBlockHeader& block, CValidationState& state, bool fCheckPOW = true); diff --git a/src/miner.cpp b/src/miner.cpp index afaf52ee1..533879de7 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -385,7 +385,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) nLastBlockTx = nBlockTx; nLastBlockSize = nBlockSize; blocktime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); - pblock->nTime = blocktime + 1; + //pblock->nTime = blocktime + 1; pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus()); //LogPrintf("CreateNewBlock(): total size %u blocktime.%u nBits.%08x\n", nBlockSize,blocktime,pblock->nBits); if ( ASSETCHAINS_SYMBOL[0] != 0 && ASSETCHAINS_STAKED != 0 && NOTARY_PUBKEY33[0] != 0 ) @@ -479,8 +479,11 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); pblock->hashReserved = uint256(); - //UpdateTime(pblock, Params().GetConsensus(), pindexPrev); - //pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus()); + if ( ASSETCHAINS_SYMBOL[0] == 0 || ASSETCHAINS_STAKED == 0 || NOTARY_PUBKEY33[0] == 0 ) + { + UpdateTime(pblock, Params().GetConsensus(), pindexPrev); + pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus()); + } pblock->nSolution.clear(); pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]); @@ -488,8 +491,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) if ( !TestBlockValidity(state, *pblock, pindexPrev, false, false)) { static uint32_t counter; - if ( counter++ < 100 && ASSETCHAINS_STAKED == 0 ) - fprintf(stderr,"warning: testblockvalidity failed\n"); + //if ( counter++ < 100 && ASSETCHAINS_STAKED == 0 ) + fprintf(stderr,"warning: miner testblockvalidity failed\n"); return(0); } } diff --git a/src/pow.cpp b/src/pow.cpp index 65c907230..c37c5ca09 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -125,7 +125,7 @@ extern int32_t KOMODO_CHOSEN_ONE; extern uint64_t ASSETCHAINS_STAKED; extern char ASSETCHAINS_SYMBOL[]; #define KOMODO_ELECTION_GAP 2000 - + int32_t komodo_eligiblenotary(uint8_t pubkeys[66][33],int32_t *mids,int32_t *nonzpkeysp,int32_t height); int32_t KOMODO_LOADINGBLOCKS = 1; @@ -189,7 +189,7 @@ bool CheckProofOfWork(int32_t height,uint8_t *pubkey33,uint256 hash, unsigned in { if ( KOMODO_LOADINGBLOCKS != 0 ) return true; - if ( ASSETCHAINS_SYMBOL[0] == 0 ) +/* if ( ASSETCHAINS_SYMBOL[0] == 0 ) { if ( 1 && height > 792000 ) { @@ -211,7 +211,9 @@ bool CheckProofOfWork(int32_t height,uint8_t *pubkey33,uint256 hash, unsigned in } if ( height > 792000 ) return false; - } else return false; + } else return false;*/ + if ( ASSETCHAINS_SYMBOL[0] != 0 || height > 792000 ) + return false; } return true; } diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index f1fefcf96..00b62c5b1 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -7,6 +7,7 @@ #include "chain.h" #include "chainparams.h" #include "checkpoints.h" +#include "base58.h" #include "consensus/validation.h" #include "cc/betprotocol.h" #include "main.h" @@ -14,6 +15,10 @@ #include "rpcserver.h" #include "sync.h" #include "util.h" +#include "script/script.h" +#include "script/script_error.h" +#include "script/sign.h" +#include "script/standard.h" #include @@ -124,6 +129,112 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex) return result; } +UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex) +{ + UniValue result(UniValue::VOBJ); + result.push_back(Pair("hash", block.GetHash().GetHex())); + int confirmations = -1; + // Only report confirmations if the block is on the main chain + if (chainActive.Contains(blockindex)) { + confirmations = chainActive.Height() - blockindex->nHeight + 1; + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block is an orphan"); + } + result.push_back(Pair("confirmations", confirmations)); + result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); + result.push_back(Pair("height", blockindex->nHeight)); + result.push_back(Pair("version", block.nVersion)); + result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex())); + + UniValue deltas(UniValue::VARR); + + for (unsigned int i = 0; i < block.vtx.size(); i++) { + const CTransaction &tx = block.vtx[i]; + const uint256 txhash = tx.GetHash(); + + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("txid", txhash.GetHex())); + entry.push_back(Pair("index", (int)i)); + + UniValue inputs(UniValue::VARR); + + if (!tx.IsCoinBase()) { + + for (size_t j = 0; j < tx.vin.size(); j++) { + const CTxIn input = tx.vin[j]; + + UniValue delta(UniValue::VOBJ); + + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(input.prevout.hash, input.prevout.n); + + if (GetSpentIndex(spentKey, spentInfo)) { + if (spentInfo.addressType == 1) { + delta.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); + } else if (spentInfo.addressType == 2) { + delta.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); + } else { + continue; + } + delta.push_back(Pair("satoshis", -1 * spentInfo.satoshis)); + delta.push_back(Pair("index", (int)j)); + delta.push_back(Pair("prevtxid", input.prevout.hash.GetHex())); + delta.push_back(Pair("prevout", (int)input.prevout.n)); + + inputs.push_back(delta); + } else { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Spent information not available"); + } + + } + } + + entry.push_back(Pair("inputs", inputs)); + + UniValue outputs(UniValue::VARR); + + for (unsigned int k = 0; k < tx.vout.size(); k++) { + const CTxOut &out = tx.vout[k]; + + UniValue delta(UniValue::VOBJ); + + if (out.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + delta.push_back(Pair("address", CBitcoinAddress(CScriptID(uint160(hashBytes))).ToString())); + + } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + delta.push_back(Pair("address", CBitcoinAddress(CKeyID(uint160(hashBytes))).ToString())); + } else { + continue; + } + + delta.push_back(Pair("satoshis", out.nValue)); + delta.push_back(Pair("index", (int)k)); + + outputs.push_back(delta); + } + + entry.push_back(Pair("outputs", outputs)); + deltas.push_back(entry); + + } + result.push_back(Pair("deltas", deltas)); + result.push_back(Pair("time", block.GetBlockTime())); + result.push_back(Pair("mediantime", (int64_t)blockindex->GetMedianTimePast())); + result.push_back(Pair("nonce", block.nNonce.GetHex())); + result.push_back(Pair("bits", strprintf("%08x", block.nBits))); + result.push_back(Pair("difficulty", GetDifficulty(blockindex))); + result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); + + if (blockindex->pprev) + result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); + CBlockIndex *pnext = chainActive.Next(blockindex); + if (pnext) + result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); + return result; +} + UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false) { UniValue result(UniValue::VOBJ); @@ -310,6 +421,102 @@ UniValue getrawmempool(const UniValue& params, bool fHelp) return mempoolToJSON(fVerbose); } +UniValue getblockdeltas(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error(""); + + std::string strHash = params[0].get_str(); + uint256 hash(uint256S(strHash)); + + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + CBlock block; + CBlockIndex* pblockindex = mapBlockIndex[hash]; + + if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)"); + + if(!ReadBlockFromDisk(block, pblockindex)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + + return blockToDeltasJSON(block, pblockindex); +} + +UniValue getblockhashes(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 2) + throw runtime_error( + "getblockhashes timestamp\n" + "\nReturns array of hashes of blocks within the timestamp range provided.\n" + "\nArguments:\n" + "1. high (numeric, required) The newer block timestamp\n" + "2. low (numeric, required) The older block timestamp\n" + "3. options (string, required) A json object\n" + " {\n" + " \"noOrphans\":true (boolean) will only include blocks on the main chain\n" + " \"logicalTimes\":true (boolean) will include logical timestamps with hashes\n" + " }\n" + "\nResult:\n" + "[\n" + " \"hash\" (string) The block hash\n" + "]\n" + "[\n" + " {\n" + " \"blockhash\": (string) The block hash\n" + " \"logicalts\": (numeric) The logical timestamp\n" + " }\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getblockhashes", "1231614698 1231024505") + + HelpExampleRpc("getblockhashes", "1231614698, 1231024505") + + HelpExampleCli("getblockhashes", "1231614698 1231024505 '{\"noOrphans\":false, \"logicalTimes\":true}'") + ); + + unsigned int high = params[0].get_int(); + unsigned int low = params[1].get_int(); + bool fActiveOnly = false; + bool fLogicalTS = false; + + if (params.size() > 2) { + if (params[2].isObject()) { + UniValue noOrphans = find_value(params[2].get_obj(), "noOrphans"); + UniValue returnLogical = find_value(params[2].get_obj(), "logicalTimes"); + + if (noOrphans.isBool()) + fActiveOnly = noOrphans.get_bool(); + + if (returnLogical.isBool()) + fLogicalTS = returnLogical.get_bool(); + } + } + + std::vector > blockHashes; + + if (fActiveOnly) + LOCK(cs_main); + + if (!GetTimestampIndex(high, low, fActiveOnly, blockHashes)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for block hashes"); + } + + UniValue result(UniValue::VARR); + + for (std::vector >::const_iterator it=blockHashes.begin(); it!=blockHashes.end(); it++) { + if (fLogicalTS) { + UniValue item(UniValue::VOBJ); + item.push_back(Pair("blockhash", it->first.GetHex())); + item.push_back(Pair("logicalts", (int)it->second)); + result.push_back(item); + } else { + result.push_back(it->first.GetHex()); + } + } + + return result; +} + UniValue getblockhash(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 1) @@ -696,7 +903,7 @@ UniValue height_MoM(const UniValue& params, bool fHelp) ret.push_back(Pair("kmdendi",kmdendi)); } } else ret.push_back(Pair("error",(char *)"no MoM for height")); - + return ret; } diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 3ff7bc8a0..f2c6ae6e2 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -96,6 +96,15 @@ static const CRPCConvertParam vRPCConvertParams[] = { "prioritisetransaction", 2 }, { "setban", 2 }, { "setban", 3 }, + { "getblockhashes", 0 }, + { "getblockhashes", 1 }, + { "getblockhashes", 2 }, + { "getspentinfo", 0}, + { "getaddresstxids", 0}, + { "getaddressbalance", 0}, + { "getaddressdeltas", 0}, + { "getaddressutxos", 0}, + { "getaddressmempool", 0}, { "zcrawjoinsplit", 1 }, { "zcrawjoinsplit", 2 }, { "zcrawjoinsplit", 3 }, diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 1159c12c1..20ea27f0a 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -11,6 +11,7 @@ #include "netbase.h" #include "rpcserver.h" #include "timedata.h" +#include "txmempool.h" #include "util.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" @@ -557,3 +558,530 @@ UniValue setmocktime(const UniValue& params, bool fHelp) return NullUniValue; } + +bool getAddressFromIndex(const int &type, const uint160 &hash, std::string &address) +{ + if (type == 2) { + address = CBitcoinAddress(CScriptID(hash)).ToString(); + } else if (type == 1) { + address = CBitcoinAddress(CKeyID(hash)).ToString(); + } else { + return false; + } + return true; +} + +bool getAddressesFromParams(const UniValue& params, std::vector > &addresses) +{ + if (params[0].isStr()) { + CBitcoinAddress address(params[0].get_str()); + uint160 hashBytes; + int type = 0; + if (!address.GetIndexKey(hashBytes, type)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + addresses.push_back(std::make_pair(hashBytes, type)); + } else if (params[0].isObject()) { + + UniValue addressValues = find_value(params[0].get_obj(), "addresses"); + if (!addressValues.isArray()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Addresses is expected to be an array"); + } + + std::vector values = addressValues.getValues(); + + for (std::vector::iterator it = values.begin(); it != values.end(); ++it) { + + CBitcoinAddress address(it->get_str()); + uint160 hashBytes; + int type = 0; + if (!address.GetIndexKey(hashBytes, type)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + addresses.push_back(std::make_pair(hashBytes, type)); + } + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + return true; +} + +bool heightSort(std::pair a, + std::pair b) { + return a.second.blockHeight < b.second.blockHeight; +} + +bool timestampSort(std::pair a, + std::pair b) { + return a.second.time < b.second.time; +} + +UniValue getaddressmempool(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaddressmempool\n" + "\nReturns all mempool deltas for an address (requires addressindex to be enabled).\n" + "\nArguments:\n" + "{\n" + " \"addresses\"\n" + " [\n" + " \"address\" (string) The base58check encoded address\n" + " ,...\n" + " ]\n" + "}\n" + "\nResult:\n" + "[\n" + " {\n" + " \"address\" (string) The base58check encoded address\n" + " \"txid\" (string) The related txid\n" + " \"index\" (number) The related input or output index\n" + " \"satoshis\" (number) The difference of satoshis\n" + " \"timestamp\" (number) The time the transaction entered the mempool (seconds)\n" + " \"prevtxid\" (string) The previous txid (if spending)\n" + " \"prevout\" (string) The previous transaction output index (if spending)\n" + " }\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getaddressmempool", "'{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}'") + + HelpExampleRpc("getaddressmempool", "{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}") + ); + + std::vector > addresses; + + if (!getAddressesFromParams(params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + std::vector > indexes; + + if (!mempool.getAddressIndex(addresses, indexes)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + + std::sort(indexes.begin(), indexes.end(), timestampSort); + + UniValue result(UniValue::VARR); + + for (std::vector >::iterator it = indexes.begin(); + it != indexes.end(); it++) { + + std::string address; + if (!getAddressFromIndex(it->first.type, it->first.addressBytes, address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type"); + } + + UniValue delta(UniValue::VOBJ); + delta.push_back(Pair("address", address)); + delta.push_back(Pair("txid", it->first.txhash.GetHex())); + delta.push_back(Pair("index", (int)it->first.index)); + delta.push_back(Pair("satoshis", it->second.amount)); + delta.push_back(Pair("timestamp", it->second.time)); + if (it->second.amount < 0) { + delta.push_back(Pair("prevtxid", it->second.prevhash.GetHex())); + delta.push_back(Pair("prevout", (int)it->second.prevout)); + } + result.push_back(delta); + } + + return result; +} + +UniValue getaddressutxos(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaddressutxos\n" + "\nReturns all unspent outputs for an address (requires addressindex to be enabled).\n" + "\nArguments:\n" + "{\n" + " \"addresses\"\n" + " [\n" + " \"address\" (string) The base58check encoded address\n" + " ,...\n" + " ],\n" + " \"chainInfo\" (boolean) Include chain info with results\n" + "}\n" + "\nResult\n" + "[\n" + " {\n" + " \"address\" (string) The address base58check encoded\n" + " \"txid\" (string) The output txid\n" + " \"height\" (number) The block height\n" + " \"outputIndex\" (number) The output index\n" + " \"script\" (strin) The script hex encoded\n" + " \"satoshis\" (number) The number of satoshis of the output\n" + " }\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getaddressutxos", "'{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}'") + + HelpExampleRpc("getaddressutxos", "{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}") + ); + + bool includeChainInfo = false; + if (params[0].isObject()) { + UniValue chainInfo = find_value(params[0].get_obj(), "chainInfo"); + if (chainInfo.isBool()) { + includeChainInfo = chainInfo.get_bool(); + } + } + + std::vector > addresses; + + if (!getAddressesFromParams(params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + std::vector > unspentOutputs; + + for (std::vector >::iterator it = addresses.begin(); it != addresses.end(); it++) { + if (!GetAddressUnspent((*it).first, (*it).second, unspentOutputs)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } + + std::sort(unspentOutputs.begin(), unspentOutputs.end(), heightSort); + + UniValue utxos(UniValue::VARR); + + for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) { + UniValue output(UniValue::VOBJ); + std::string address; + if (!getAddressFromIndex(it->first.type, it->first.hashBytes, address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type"); + } + + output.push_back(Pair("address", address)); + output.push_back(Pair("txid", it->first.txhash.GetHex())); + output.push_back(Pair("outputIndex", (int)it->first.index)); + output.push_back(Pair("script", HexStr(it->second.script.begin(), it->second.script.end()))); + output.push_back(Pair("satoshis", it->second.satoshis)); + output.push_back(Pair("height", it->second.blockHeight)); + utxos.push_back(output); + } + + if (includeChainInfo) { + UniValue result(UniValue::VOBJ); + result.push_back(Pair("utxos", utxos)); + + LOCK(cs_main); + result.push_back(Pair("hash", chainActive.Tip()->GetBlockHash().GetHex())); + result.push_back(Pair("height", (int)chainActive.Height())); + return result; + } else { + return utxos; + } +} + +UniValue getaddressdeltas(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1 || !params[0].isObject()) + throw runtime_error( + "getaddressdeltas\n" + "\nReturns all changes for an address (requires addressindex to be enabled).\n" + "\nArguments:\n" + "{\n" + " \"addresses\"\n" + " [\n" + " \"address\" (string) The base58check encoded address\n" + " ,...\n" + " ]\n" + " \"start\" (number) The start block height\n" + " \"end\" (number) The end block height\n" + " \"chainInfo\" (boolean) Include chain info in results, only applies if start and end specified\n" + "}\n" + "\nResult:\n" + "[\n" + " {\n" + " \"satoshis\" (number) The difference of satoshis\n" + " \"txid\" (string) The related txid\n" + " \"index\" (number) The related input or output index\n" + " \"height\" (number) The block height\n" + " \"address\" (string) The base58check encoded address\n" + " }\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getaddressdeltas", "'{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}'") + + HelpExampleRpc("getaddressdeltas", "{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}") + ); + + + UniValue startValue = find_value(params[0].get_obj(), "start"); + UniValue endValue = find_value(params[0].get_obj(), "end"); + + UniValue chainInfo = find_value(params[0].get_obj(), "chainInfo"); + bool includeChainInfo = false; + if (chainInfo.isBool()) { + includeChainInfo = chainInfo.get_bool(); + } + + int start = 0; + int end = 0; + + if (startValue.isNum() && endValue.isNum()) { + start = startValue.get_int(); + end = endValue.get_int(); + if (start <= 0 || end <= 0) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Start and end is expected to be greater than zero"); + } + if (end < start) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "End value is expected to be greater than start"); + } + } + + std::vector > addresses; + + if (!getAddressesFromParams(params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + std::vector > addressIndex; + + for (std::vector >::iterator it = addresses.begin(); it != addresses.end(); it++) { + if (start > 0 && end > 0) { + if (!GetAddressIndex((*it).first, (*it).second, addressIndex, start, end)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } else { + if (!GetAddressIndex((*it).first, (*it).second, addressIndex)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } + } + + UniValue deltas(UniValue::VARR); + + for (std::vector >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++) { + std::string address; + if (!getAddressFromIndex(it->first.type, it->first.hashBytes, address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type"); + } + + UniValue delta(UniValue::VOBJ); + delta.push_back(Pair("satoshis", it->second)); + delta.push_back(Pair("txid", it->first.txhash.GetHex())); + delta.push_back(Pair("index", (int)it->first.index)); + delta.push_back(Pair("blockindex", (int)it->first.txindex)); + delta.push_back(Pair("height", it->first.blockHeight)); + delta.push_back(Pair("address", address)); + deltas.push_back(delta); + } + + UniValue result(UniValue::VOBJ); + + if (includeChainInfo && start > 0 && end > 0) { + LOCK(cs_main); + + if (start > chainActive.Height() || end > chainActive.Height()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Start or end is outside chain range"); + } + + CBlockIndex* startIndex = chainActive[start]; + CBlockIndex* endIndex = chainActive[end]; + + UniValue startInfo(UniValue::VOBJ); + UniValue endInfo(UniValue::VOBJ); + + startInfo.push_back(Pair("hash", startIndex->GetBlockHash().GetHex())); + startInfo.push_back(Pair("height", start)); + + endInfo.push_back(Pair("hash", endIndex->GetBlockHash().GetHex())); + endInfo.push_back(Pair("height", end)); + + result.push_back(Pair("deltas", deltas)); + result.push_back(Pair("start", startInfo)); + result.push_back(Pair("end", endInfo)); + + return result; + } else { + return deltas; + } +} + +UniValue getaddressbalance(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaddressbalance\n" + "\nReturns the balance for an address(es) (requires addressindex to be enabled).\n" + "\nArguments:\n" + "{\n" + " \"addresses\"\n" + " [\n" + " \"address\" (string) The base58check encoded address\n" + " ,...\n" + " ]\n" + "}\n" + "\nResult:\n" + "{\n" + " \"balance\" (string) The current balance in satoshis\n" + " \"received\" (string) The total number of satoshis received (including change)\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getaddressbalance", "'{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}'") + + HelpExampleRpc("getaddressbalance", "{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}") + ); + + std::vector > addresses; + + if (!getAddressesFromParams(params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + std::vector > addressIndex; + + for (std::vector >::iterator it = addresses.begin(); it != addresses.end(); it++) { + if (!GetAddressIndex((*it).first, (*it).second, addressIndex)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } + + CAmount balance = 0; + CAmount received = 0; + + for (std::vector >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++) { + if (it->second > 0) { + received += it->second; + } + balance += it->second; + } + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("balance", balance)); + result.push_back(Pair("received", received)); + + return result; + +} + +UniValue getaddresstxids(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaddresstxids\n" + "\nReturns the txids for an address(es) (requires addressindex to be enabled).\n" + "\nArguments:\n" + "{\n" + " \"addresses\"\n" + " [\n" + " \"address\" (string) The base58check encoded address\n" + " ,...\n" + " ]\n" + " \"start\" (number) The start block height\n" + " \"end\" (number) The end block height\n" + "}\n" + "\nResult:\n" + "[\n" + " \"transactionid\" (string) The transaction id\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getaddresstxids", "'{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}'") + + HelpExampleRpc("getaddresstxids", "{\"addresses\": [\"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX\"]}") + ); + + std::vector > addresses; + + if (!getAddressesFromParams(params, addresses)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + + int start = 0; + int end = 0; + if (params[0].isObject()) { + UniValue startValue = find_value(params[0].get_obj(), "start"); + UniValue endValue = find_value(params[0].get_obj(), "end"); + if (startValue.isNum() && endValue.isNum()) { + start = startValue.get_int(); + end = endValue.get_int(); + } + } + + std::vector > addressIndex; + + for (std::vector >::iterator it = addresses.begin(); it != addresses.end(); it++) { + if (start > 0 && end > 0) { + if (!GetAddressIndex((*it).first, (*it).second, addressIndex, start, end)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } else { + if (!GetAddressIndex((*it).first, (*it).second, addressIndex)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address"); + } + } + } + + std::set > txids; + UniValue result(UniValue::VARR); + + for (std::vector >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++) { + int height = it->first.blockHeight; + std::string txid = it->first.txhash.GetHex(); + + if (addresses.size() > 1) { + txids.insert(std::make_pair(height, txid)); + } else { + if (txids.insert(std::make_pair(height, txid)).second) { + result.push_back(txid); + } + } + } + + if (addresses.size() > 1) { + for (std::set >::const_iterator it=txids.begin(); it!=txids.end(); it++) { + result.push_back(it->second); + } + } + + return result; + +} + +UniValue getspentinfo(const UniValue& params, bool fHelp) +{ + + if (fHelp || params.size() != 1 || !params[0].isObject()) + throw runtime_error( + "getspentinfo\n" + "\nReturns the txid and index where an output is spent.\n" + "\nArguments:\n" + "{\n" + " \"txid\" (string) The hex string of the txid\n" + " \"index\" (number) The start block height\n" + "}\n" + "\nResult:\n" + "{\n" + " \"txid\" (string) The transaction id\n" + " \"index\" (number) The spending input index\n" + " ,...\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getspentinfo", "'{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}'") + + HelpExampleRpc("getspentinfo", "{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}") + ); + + UniValue txidValue = find_value(params[0].get_obj(), "txid"); + UniValue indexValue = find_value(params[0].get_obj(), "index"); + + if (!txidValue.isStr() || !indexValue.isNum()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid txid or index"); + } + + uint256 txid = ParseHashV(txidValue, "txid"); + int outputIndex = indexValue.get_int(); + + CSpentIndexKey key(txid, outputIndex); + CSpentIndexValue value; + + if (!GetSpentIndex(key, value)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get spent info"); + } + + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("txid", value.txid.GetHex())); + obj.push_back(Pair("index", (int)value.inputIndex)); + obj.push_back(Pair("height", value.blockHeight)); + + return obj; +} \ No newline at end of file diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index e918df2c9..3f93e07d6 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -65,7 +65,9 @@ UniValue TxJoinSplitToJSON(const CTransaction& tx) { UniValue joinsplit(UniValue::VOBJ); joinsplit.push_back(Pair("vpub_old", ValueFromAmount(jsdescription.vpub_old))); + joinsplit.push_back(Pair("vpub_oldZat", jsdescription.vpub_old)); joinsplit.push_back(Pair("vpub_new", ValueFromAmount(jsdescription.vpub_new))); + joinsplit.push_back(Pair("vpub_newZat", jsdescription.vpub_new)); joinsplit.push_back(Pair("anchor", jsdescription.anchor.GetHex())); @@ -115,9 +117,10 @@ UniValue TxJoinSplitToJSON(const CTransaction& tx) { uint64_t komodo_interest(int32_t txheight,uint64_t nValue,uint32_t nLockTime,uint32_t tiptime); -void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) +void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, int nHeight = 0, int nConfirmations = 0, int nBlockTime = 0) { - entry.push_back(Pair("txid", tx.GetHash().GetHex())); + uint256 txid = tx.GetHash(); + entry.push_back(Pair("txid", txid.GetHex())); entry.push_back(Pair("overwintered", tx.fOverwintered)); entry.push_back(Pair("version", tx.nVersion)); if (tx.fOverwintered) { @@ -139,6 +142,19 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) o.push_back(Pair("asm", txin.scriptSig.ToString())); o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); in.push_back(Pair("scriptSig", o)); + + // Add address and value info if spentindex enabled + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(txin.prevout.hash, txin.prevout.n); + if (GetSpentIndex(spentKey, spentInfo)) { + in.push_back(Pair("value", ValueFromAmount(spentInfo.satoshis))); + in.push_back(Pair("valueSat", spentInfo.satoshis)); + if (spentInfo.addressType == 1) { + in.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); + } else if (spentInfo.addressType == 2) { + in.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); + } + } } in.push_back(Pair("sequence", (int64_t)txin.nSequence)); vin.push_back(in); @@ -162,6 +178,16 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) UniValue o(UniValue::VOBJ); ScriptPubKeyToJSON(txout.scriptPubKey, o, true); out.push_back(Pair("scriptPubKey", o)); + + // Add spent information if spentindex is enabled + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(txid, i); + if (GetSpentIndex(spentKey, spentInfo)) { + out.push_back(Pair("spentTxId", spentInfo.txid.GetHex())); + out.push_back(Pair("spentIndex", (int)spentInfo.inputIndex)); + out.push_back(Pair("spentHeight", spentInfo.blockHeight)); + } + vout.push_back(out); } entry.push_back(Pair("vout", vout)); @@ -169,18 +195,76 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) UniValue vjoinsplit = TxJoinSplitToJSON(tx); entry.push_back(Pair("vjoinsplit", vjoinsplit)); + if (!hashBlock.IsNull()) { + entry.push_back(Pair("blockhash", hashBlock.GetHex())); + + if (nConfirmations > 0) { + entry.push_back(Pair("height", nHeight)); + entry.push_back(Pair("confirmations", nConfirmations)); + entry.push_back(Pair("time", nBlockTime)); + entry.push_back(Pair("blocktime", nBlockTime)); + } else { + entry.push_back(Pair("height", -1)); + entry.push_back(Pair("confirmations", 0)); + } + } + +} + +void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) +{ + uint256 txid = tx.GetHash(); + entry.push_back(Pair("txid", txid.GetHex())); + entry.push_back(Pair("size", (int)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); + entry.push_back(Pair("version", tx.nVersion)); + entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); + + UniValue vin(UniValue::VARR); + BOOST_FOREACH(const CTxIn& txin, tx.vin) { + UniValue in(UniValue::VOBJ); + if (tx.IsCoinBase()) + in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + else { + in.push_back(Pair("txid", txin.prevout.hash.GetHex())); + in.push_back(Pair("vout", (int64_t)txin.prevout.n)); + UniValue o(UniValue::VOBJ); + o.push_back(Pair("asm", txin.scriptSig.ToString())); + o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + in.push_back(Pair("scriptSig", o)); + } + in.push_back(Pair("sequence", (int64_t)txin.nSequence)); + vin.push_back(in); + } + entry.push_back(Pair("vin", vin)); + + UniValue vout(UniValue::VARR); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + const CTxOut& txout = tx.vout[i]; + UniValue out(UniValue::VOBJ); + out.push_back(Pair("value", ValueFromAmount(txout.nValue))); + out.push_back(Pair("valueSat", txout.nValue)); + out.push_back(Pair("n", (int64_t)i)); + UniValue o(UniValue::VOBJ); + ScriptPubKeyToJSON(txout.scriptPubKey, o, true); + out.push_back(Pair("scriptPubKey", o)); + vout.push_back(out); + } + entry.push_back(Pair("vout", vout)); + if (!hashBlock.IsNull()) { entry.push_back(Pair("blockhash", hashBlock.GetHex())); BlockMap::iterator mi = mapBlockIndex.find(hashBlock); if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; if (chainActive.Contains(pindex)) { + entry.push_back(Pair("height", pindex->nHeight)); entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); - } - else + } else { + entry.push_back(Pair("height", -1)); entry.push_back(Pair("confirmations", 0)); + } } } } @@ -279,7 +363,6 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp) + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1") ); - LOCK(cs_main); uint256 hash = ParseHashV(params[0], "parameter 1"); @@ -289,8 +372,29 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp) CTransaction tx; uint256 hashBlock; - if (!GetTransaction(hash, tx, hashBlock, true)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); + int nHeight = 0; + int nConfirmations = 0; + int nBlockTime = 0; + + { + LOCK(cs_main); + if (!GetTransaction(hash, tx, hashBlock, true)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); + + BlockMap::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) { + CBlockIndex* pindex = (*mi).second; + if (chainActive.Contains(pindex)) { + nHeight = pindex->nHeight; + nConfirmations = 1 + chainActive.Height() - pindex->nHeight; + nBlockTime = pindex->GetBlockTime(); + } else { + nHeight = -1; + nConfirmations = 0; + nBlockTime = pindex->GetBlockTime(); + } + } + } string strHex = EncodeHexTx(tx); @@ -299,7 +403,7 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp) UniValue result(UniValue::VOBJ); result.push_back(Pair("hex", strHex)); - TxToJSON(tx, hashBlock, result); + TxToJSONExpanded(tx, hashBlock, result, nHeight, nConfirmations, nBlockTime); return result; } diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index cc5f364b4..454953d63 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -281,6 +281,8 @@ static const CRPCCommand vRPCCommands[] = { "blockchain", "getbestblockhash", &getbestblockhash, true }, { "blockchain", "getblockcount", &getblockcount, true }, { "blockchain", "getblock", &getblock, true }, + { "blockchain", "getblockdeltas", &getblockdeltas, false }, + { "blockchain", "getblockhashes", &getblockhashes, true }, { "blockchain", "getblockhash", &getblockhash, true }, { "blockchain", "getblockheader", &getblockheader, true }, { "blockchain", "getchaintips", &getchaintips, true }, @@ -292,6 +294,7 @@ static const CRPCCommand vRPCCommands[] = { "blockchain", "verifytxoutproof", &verifytxoutproof, true }, { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true }, { "blockchain", "verifychain", &verifychain, true }, + { "blockchain", "getspentinfo", &getspentinfo, false }, { "blockchain", "paxprice", &paxprice, true }, { "blockchain", "paxpending", &paxpending, true }, { "blockchain", "paxprices", &paxprices, true }, @@ -332,6 +335,13 @@ static const CRPCCommand vRPCCommands[] = { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, #endif +/* Address index */ + { "addressindex", "getaddressmempool", &getaddressmempool, true }, + { "addressindex", "getaddressutxos", &getaddressutxos, false }, + { "addressindex", "getaddressdeltas", &getaddressdeltas, false }, + { "addressindex", "getaddresstxids", &getaddresstxids, false }, + { "addressindex", "getaddressbalance", &getaddressbalance, false }, + /* Utility functions */ { "util", "createmultisig", &createmultisig, true }, { "util", "validateaddress", &validateaddress, true }, /* uses wallet if enabled */ diff --git a/src/rpcserver.h b/src/rpcserver.h index a042d56f6..cfc52baf8 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -173,6 +173,11 @@ extern std::string HelpExampleRpc(const std::string& methodname, const std::stri extern void EnsureWalletIsUnlocked(); extern UniValue getconnectioncount(const UniValue& params, bool fHelp); // in rpcnet.cpp +extern UniValue getaddressmempool(const UniValue& params, bool fHelp); +extern UniValue getaddressutxos(const UniValue& params, bool fHelp); +extern UniValue getaddressdeltas(const UniValue& params, bool fHelp); +extern UniValue getaddresstxids(const UniValue& params, bool fHelp); +extern UniValue getaddressbalance(const UniValue& params, bool fHelp); extern UniValue getpeerinfo(const UniValue& params, bool fHelp); extern UniValue ping(const UniValue& params, bool fHelp); extern UniValue addnode(const UniValue& params, bool fHelp); @@ -271,6 +276,8 @@ extern UniValue getdifficulty(const UniValue& params, bool fHelp); extern UniValue settxfee(const UniValue& params, bool fHelp); extern UniValue getmempoolinfo(const UniValue& params, bool fHelp); extern UniValue getrawmempool(const UniValue& params, bool fHelp); +extern UniValue getblockhashes(const UniValue& params, bool fHelp); +extern UniValue getblockdeltas(const UniValue& params, bool fHelp); extern UniValue getblockhash(const UniValue& params, bool fHelp); extern UniValue getblockheader(const UniValue& params, bool fHelp); extern UniValue getblock(const UniValue& params, bool fHelp); @@ -280,6 +287,7 @@ extern UniValue verifychain(const UniValue& params, bool fHelp); extern UniValue getchaintips(const UniValue& params, bool fHelp); extern UniValue invalidateblock(const UniValue& params, bool fHelp); extern UniValue reconsiderblock(const UniValue& params, bool fHelp); +extern UniValue getspentinfo(const UniValue& params, bool fHelp); extern UniValue getblocksubsidy(const UniValue& params, bool fHelp); diff --git a/src/script/script.cpp b/src/script/script.cpp index df29b6244..a130df4aa 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -216,6 +216,17 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const return subscript.GetSigOpCount(true); } +bool CScript::IsPayToPublicKeyHash() const +{ + // Extra-fast test for pay-to-pubkey-hash CScripts: + return (this->size() == 25 && + (*this)[0] == OP_DUP && + (*this)[1] == OP_HASH160 && + (*this)[2] == 0x14 && + (*this)[23] == OP_EQUALVERIFY && + (*this)[24] == OP_CHECKSIG); +} + bool CScript::IsPayToScriptHash() const { // Extra-fast test for pay-to-script-hash CScripts: diff --git a/src/script/script.h b/src/script/script.h index 6b8c07b07..e5328cf06 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -566,6 +566,8 @@ public: */ unsigned int GetSigOpCount(const CScript& scriptSig) const; + bool IsPayToPublicKeyHash() const; + bool IsPayToScriptHash() const; bool IsPayToCryptoCondition() const; bool MayAcceptCryptoCondition() const; diff --git a/src/serialize.h b/src/serialize.h index f776d6d99..4501103c9 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -95,6 +95,11 @@ template inline void ser_writedata32(Stream &s, uint32_t obj) obj = htole32(obj); s.write((char*)&obj, 4); } +template inline void ser_writedata32be(Stream &s, uint32_t obj) +{ + obj = htobe32(obj); + s.write((char*)&obj, 4); +} template inline void ser_writedata64(Stream &s, uint64_t obj) { obj = htole64(obj); @@ -118,6 +123,12 @@ template inline uint32_t ser_readdata32(Stream &s) s.read((char*)&obj, 4); return le32toh(obj); } +template inline uint32_t ser_readdata32be(Stream &s) +{ + uint32_t obj; + s.read((char*)&obj, 4); + return be32toh(obj); +} template inline uint64_t ser_readdata64(Stream &s) { uint64_t obj; diff --git a/src/spentindex.h b/src/spentindex.h new file mode 100644 index 000000000..310ef188b --- /dev/null +++ b/src/spentindex.h @@ -0,0 +1,98 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_SPENTINDEX_H +#define BITCOIN_SPENTINDEX_H + +#include "uint256.h" +#include "amount.h" + +struct CSpentIndexKey { + uint256 txid; + unsigned int outputIndex; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(txid); + READWRITE(outputIndex); + } + + CSpentIndexKey(uint256 t, unsigned int i) { + txid = t; + outputIndex = i; + } + + CSpentIndexKey() { + SetNull(); + } + + void SetNull() { + txid.SetNull(); + outputIndex = 0; + } + +}; + +struct CSpentIndexValue { + uint256 txid; + unsigned int inputIndex; + int blockHeight; + CAmount satoshis; + int addressType; + uint160 addressHash; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(txid); + READWRITE(inputIndex); + READWRITE(blockHeight); + READWRITE(satoshis); + READWRITE(addressType); + READWRITE(addressHash); + } + + CSpentIndexValue(uint256 t, unsigned int i, int h, CAmount s, int type, uint160 a) { + txid = t; + inputIndex = i; + blockHeight = h; + satoshis = s; + addressType = type; + addressHash = a; + } + + CSpentIndexValue() { + SetNull(); + } + + void SetNull() { + txid.SetNull(); + inputIndex = 0; + blockHeight = 0; + satoshis = 0; + addressType = 0; + addressHash.SetNull(); + } + + bool IsNull() const { + return txid.IsNull(); + } +}; + +struct CSpentIndexKeyCompare +{ + bool operator()(const CSpentIndexKey& a, const CSpentIndexKey& b) const { + if (a.txid == b.txid) { + return a.outputIndex < b.outputIndex; + } else { + return a.txid < b.txid; + } + } +}; + +#endif // BITCOIN_SPENTINDEX_H \ No newline at end of file diff --git a/src/test/script_P2PKH_tests.cpp b/src/test/script_P2PKH_tests.cpp new file mode 100644 index 000000000..8fbb054b9 --- /dev/null +++ b/src/test/script_P2PKH_tests.cpp @@ -0,0 +1,59 @@ +// Copyright (c) 2012-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "script/script.h" +#include "test/test_bitcoin.h" + +#include + +using namespace std; + +BOOST_FIXTURE_TEST_SUITE(script_P2PKH_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(IsPayToPublicKeyHash) +{ + // Test CScript::IsPayToPublicKeyHash() + uint160 dummy; + CScript p2pkh; + p2pkh << OP_DUP << OP_HASH160 << ToByteVector(dummy) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(p2pkh.IsPayToPublicKeyHash()); + + static const unsigned char direct[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG + }; + BOOST_CHECK(CScript(direct, direct+sizeof(direct)).IsPayToPublicKeyHash()); + + static const unsigned char notp2pkh1[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(notp2pkh1, notp2pkh1+sizeof(notp2pkh1)).IsPayToPublicKeyHash()); + + static const unsigned char p2sh[] = { + OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL + }; + BOOST_CHECK(!CScript(p2sh, p2sh+sizeof(p2sh)).IsPayToPublicKeyHash()); + + static const unsigned char extra[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(extra, extra+sizeof(extra)).IsPayToPublicKeyHash()); + + static const unsigned char missing[] = { + OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_RETURN + }; + BOOST_CHECK(!CScript(missing, missing+sizeof(missing)).IsPayToPublicKeyHash()); + + static const unsigned char missing2[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; + BOOST_CHECK(!CScript(missing2, missing2+sizeof(missing)).IsPayToPublicKeyHash()); + + static const unsigned char tooshort[] = { + OP_DUP, OP_HASH160, 2, 0,0, OP_EQUALVERIFY, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(tooshort, tooshort+sizeof(direct)).IsPayToPublicKeyHash()); + +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 02a3a50d2..63f8ab0f4 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -11,6 +11,7 @@ #include "key.h" #include "main.h" #include "random.h" +#include "chainparamsbase.h" #include "txdb.h" #include "txmempool.h" #include "ui_interface.h" diff --git a/src/test/wallet-utility.py b/src/test/wallet-utility.py new file mode 100644 index 000000000..c0a7bfb9e --- /dev/null +++ b/src/test/wallet-utility.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# Copyright 2014 BitPay, Inc. +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import subprocess +import os +import json +import sys +import buildenv +import shutil + +def assert_equal(thing1, thing2): + if thing1 != thing2: + raise AssertionError("%s != %s"%(str(thing1),str(thing2))) + +if __name__ == '__main__': + datadir = os.environ["srcdir"] + "/test/data" + execprog = './wallet-utility' + buildenv.exeext + execargs = '-datadir=' + datadir + execrun = execprog + ' ' + execargs + + proc = subprocess.Popen(execrun, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) + try: + outs = proc.communicate() + except OSError: + print("OSError, Failed to execute " + execprog) + sys.exit(1) + + output = json.loads(outs[0]) + + assert_equal(output[0], "13EngsxkRi7SJPPqCyJsKf34U8FoX9E9Av") + assert_equal(output[1], "1FKCLGTpPeYBUqfNxktck8k5nqxB8sjim8") + assert_equal(output[2], "13cdtE9tnNeXCZJ8KQ5WELgEmLSBLnr48F") + + execargs = '-datadir=' + datadir + ' -dumppass' + execrun = execprog + ' ' + execargs + + proc = subprocess.Popen(execrun, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) + try: + outs = proc.communicate() + except OSError: + print("OSError, Failed to execute " + execprog) + sys.exit(1) + + output = json.loads(outs[0]) + + assert_equal(output[0]['addr'], "13EngsxkRi7SJPPqCyJsKf34U8FoX9E9Av") + assert_equal(output[0]['pkey'], "5Jz5BWE2WQxp1hGqDZeisQFV1mRFR2AVBAgiXCbNcZyXNjD9aUd") + assert_equal(output[1]['addr'], "1FKCLGTpPeYBUqfNxktck8k5nqxB8sjim8") + assert_equal(output[1]['pkey'], "5HsX2b3v2GjngYQ5ZM4mLp2b2apw6aMNVaPELV1YmpiYR1S4jzc") + assert_equal(output[2]['addr'], "13cdtE9tnNeXCZJ8KQ5WELgEmLSBLnr48F") + assert_equal(output[2]['pkey'], "5KCWAs1wX2ESiL4PfDR8XYVSSETHFd2jaRGxt1QdanBFTit4XcH") + + if os.path.exists(datadir + '/database'): + if os.path.isdir(datadir + '/database'): + shutil.rmtree(datadir + '/database') + + if os.path.exists(datadir + '/db.log'): + os.remove(datadir + '/db.log') + sys.exit(0) \ No newline at end of file diff --git a/src/txdb.cpp b/src/txdb.cpp index f1adb11be..f37f66daf 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -22,6 +22,11 @@ static const char DB_NULLIFIER = 's'; static const char DB_COINS = 'c'; static const char DB_BLOCK_FILES = 'f'; static const char DB_TXINDEX = 't'; +static const char DB_ADDRESSINDEX = 'd'; +static const char DB_ADDRESSUNSPENTINDEX = 'u'; +static const char DB_TIMESTAMPINDEX = 'S'; +static const char DB_BLOCKHASHINDEX = 'z'; +static const char DB_SPENTINDEX = 'p'; static const char DB_BLOCK_INDEX = 'b'; static const char DB_BEST_BLOCK = 'B'; @@ -68,7 +73,7 @@ void static BatchWriteHashBestAnchor(CLevelDBBatch &batch, const uint256 &hash) CCoinsViewDB::CCoinsViewDB(std::string dbName, size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / dbName, nCacheSize, fMemory, fWipe) { } -CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe) { +CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, false, 64) { } @@ -158,7 +163,7 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, return db.WriteBatch(batch); } -CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CLevelDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) { +CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe, bool compression, int maxOpenFiles) : CLevelDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe, compression, maxOpenFiles) { } bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { @@ -260,6 +265,197 @@ bool CBlockTreeDB::WriteTxIndex(const std::vector return WriteBatch(batch); } +bool CBlockTreeDB::ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) { + return Read(make_pair(DB_SPENTINDEX, key), value); +} + +bool CBlockTreeDB::UpdateSpentIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) { + if (it->second.IsNull()) { + batch.Erase(make_pair(DB_SPENTINDEX, it->first)); + } else { + batch.Write(make_pair(DB_SPENTINDEX, it->first), it->second); + } + } + return WriteBatch(batch); +} + +bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) { + if (it->second.IsNull()) { + batch.Erase(make_pair(DB_ADDRESSUNSPENTINDEX, it->first)); + } else { + batch.Write(make_pair(DB_ADDRESSUNSPENTINDEX, it->first), it->second); + } + } + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadAddressUnspentIndex(uint160 addressHash, int type, + std::vector > &unspentOutputs) { + + boost::scoped_ptr pcursor(NewIterator()); + + CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); + ssKeySet << make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, addressHash)); + pcursor->Seek(ssKeySet.str()); + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + try { + leveldb::Slice slKey = pcursor->key(); + CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); + char chType; + CAddressUnspentKey indexKey; + ssKey >> chType; + ssKey >> indexKey; + if (chType == DB_ADDRESSUNSPENTINDEX && indexKey.hashBytes == addressHash) { + try { + leveldb::Slice slValue = pcursor->value(); + CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION); + CAddressUnspentValue nValue; + ssValue >> nValue; + unspentOutputs.push_back(make_pair(indexKey, nValue)); + pcursor->Next(); + } catch (const std::exception& e) { + return error("failed to get address unspent value"); + } + } else { + break; + } + } catch (const std::exception& e) { + break; + } + } + + return true; +} + +bool CBlockTreeDB::WriteAddressIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) + batch.Write(make_pair(DB_ADDRESSINDEX, it->first), it->second); + return WriteBatch(batch); +} + +bool CBlockTreeDB::EraseAddressIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) + batch.Erase(make_pair(DB_ADDRESSINDEX, it->first)); + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadAddressIndex(uint160 addressHash, int type, + std::vector > &addressIndex, + int start, int end) { + + boost::scoped_ptr pcursor(NewIterator()); + + CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); + if (start > 0 && end > 0) { + ssKeySet << make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorHeightKey(type, addressHash, start)); + } else { + ssKeySet << make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorKey(type, addressHash)); + } + pcursor->Seek(ssKeySet.str()); + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + try { + leveldb::Slice slKey = pcursor->key(); + CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); + char chType; + CAddressIndexKey indexKey; + ssKey >> chType; + ssKey >> indexKey; + if (chType == DB_ADDRESSINDEX && indexKey.hashBytes == addressHash) { + if (end > 0 && indexKey.blockHeight > end) { + break; + } + try { + leveldb::Slice slValue = pcursor->value(); + CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION); + CAmount nValue; + ssValue >> nValue; + + addressIndex.push_back(make_pair(indexKey, nValue)); + pcursor->Next(); + } catch (const std::exception& e) { + return error("failed to get address index value"); + } + } else { + break; + } + } catch (const std::exception& e) { + break; + } + } + + return true; +} + +bool CBlockTreeDB::WriteTimestampIndex(const CTimestampIndexKey ×tampIndex) { + CLevelDBBatch batch; + batch.Write(make_pair(DB_TIMESTAMPINDEX, timestampIndex), 0); + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &hashes) { + + boost::scoped_ptr pcursor(NewIterator()); + + CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); + ssKeySet << make_pair(DB_TIMESTAMPINDEX, CTimestampIndexIteratorKey(low)); + pcursor->Seek(ssKeySet.str()); + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + try { + leveldb::Slice slKey = pcursor->key(); + CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); + char chType; + CTimestampIndexKey indexKey; + ssKey >> chType; + ssKey >> indexKey; + if (chType == DB_TIMESTAMPINDEX && indexKey.timestamp < high) { + if (fActiveOnly) { + if (blockOnchainActive(indexKey.blockHash)) { + hashes.push_back(std::make_pair(indexKey.blockHash, indexKey.timestamp)); + } + } else { + hashes.push_back(std::make_pair(indexKey.blockHash, indexKey.timestamp)); + } + + pcursor->Next(); + } else { + break; + } + } catch (const std::exception& e) { + break; + } + } + + return true; +} + +bool CBlockTreeDB::WriteTimestampBlockIndex(const CTimestampBlockIndexKey &blockhashIndex, const CTimestampBlockIndexValue &logicalts) { + CLevelDBBatch batch; + batch.Write(make_pair(DB_BLOCKHASHINDEX, blockhashIndex), logicalts); + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadTimestampBlockIndex(const uint256 &hash, unsigned int <imestamp) { + + CTimestampBlockIndexValue(lts); + if (!Read(std::make_pair(DB_BLOCKHASHINDEX, hash), lts)) + return false; + + ltimestamp = lts.ltimestamp; + return true; +} + bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) { return Write(std::make_pair(DB_FLAG, name), fValue ? '1' : '0'); } @@ -274,6 +470,16 @@ bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) { void komodo_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height); +bool CBlockTreeDB::blockOnchainActive(const uint256 &hash) { + CBlockIndex* pblockindex = mapBlockIndex[hash]; + + if (!chainActive.Contains(pblockindex)) { + return false; + } + + return true; +} + bool CBlockTreeDB::LoadBlockIndexGuts() { boost::scoped_ptr pcursor(NewIterator()); diff --git a/src/txdb.h b/src/txdb.h index 04ac8627b..401f92144 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -17,6 +17,17 @@ class CBlockFileInfo; class CBlockIndex; struct CDiskTxPos; +struct CAddressUnspentKey; +struct CAddressUnspentValue; +struct CAddressIndexKey; +struct CAddressIndexIteratorKey; +struct CAddressIndexIteratorHeightKey; +struct CTimestampIndexKey; +struct CTimestampIndexIteratorKey; +struct CTimestampBlockIndexKey; +struct CTimestampBlockIndexValue; +struct CSpentIndexKey; +struct CSpentIndexValue; class uint256; //! -dbcache default (MiB) @@ -53,7 +64,7 @@ public: class CBlockTreeDB : public CLevelDBWrapper { public: - CBlockTreeDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); + CBlockTreeDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool compression = true, int maxOpenFiles = 1000); private: CBlockTreeDB(const CBlockTreeDB&); void operator=(const CBlockTreeDB&); @@ -65,9 +76,24 @@ public: bool ReadReindexing(bool &fReindex); bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos); bool WriteTxIndex(const std::vector > &list); + bool ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value); + bool UpdateSpentIndex(const std::vector >&vect); + bool UpdateAddressUnspentIndex(const std::vector >&vect); + bool ReadAddressUnspentIndex(uint160 addressHash, int type, + std::vector > &vect); + bool WriteAddressIndex(const std::vector > &vect); + bool EraseAddressIndex(const std::vector > &vect); + bool ReadAddressIndex(uint160 addressHash, int type, + std::vector > &addressIndex, + int start = 0, int end = 0); + bool WriteTimestampIndex(const CTimestampIndexKey ×tampIndex); + bool ReadTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &vect); + bool WriteTimestampBlockIndex(const CTimestampBlockIndexKey &blockhashIndex, const CTimestampBlockIndexValue &logicalts); + bool ReadTimestampBlockIndex(const uint256 &hash, unsigned int &logicalTS); bool WriteFlag(const std::string &name, bool fValue); bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts(); + bool blockOnchainActive(const uint256 &hash); }; #endif // BITCOIN_TXDB_H diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 2dc9a4642..164d5bb47 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -119,6 +119,144 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, return true; } +void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view) +{ + LOCK(cs); + const CTransaction& tx = entry.GetTx(); + std::vector inserted; + + uint256 txhash = tx.GetHash(); + for (unsigned int j = 0; j < tx.vin.size(); j++) { + const CTxIn input = tx.vin[j]; + const CTxOut &prevout = view.GetOutputFor(input); + if (prevout.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22); + CMempoolAddressDeltaKey key(2, uint160(hashBytes), txhash, j, 1); + CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n); + mapAddress.insert(make_pair(key, delta)); + inserted.push_back(key); + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); + CMempoolAddressDeltaKey key(1, uint160(hashBytes), txhash, j, 1); + CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n); + mapAddress.insert(make_pair(key, delta)); + inserted.push_back(key); + } + } + + for (unsigned int k = 0; k < tx.vout.size(); k++) { + const CTxOut &out = tx.vout[k]; + if (out.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + CMempoolAddressDeltaKey key(2, uint160(hashBytes), txhash, k, 0); + mapAddress.insert(make_pair(key, CMempoolAddressDelta(entry.GetTime(), out.nValue))); + inserted.push_back(key); + } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + std::pair ret; + CMempoolAddressDeltaKey key(1, uint160(hashBytes), txhash, k, 0); + mapAddress.insert(make_pair(key, CMempoolAddressDelta(entry.GetTime(), out.nValue))); + inserted.push_back(key); + } + } + + mapAddressInserted.insert(make_pair(txhash, inserted)); +} + +bool CTxMemPool::getAddressIndex(std::vector > &addresses, + std::vector > &results) +{ + LOCK(cs); + for (std::vector >::iterator it = addresses.begin(); it != addresses.end(); it++) { + addressDeltaMap::iterator ait = mapAddress.lower_bound(CMempoolAddressDeltaKey((*it).second, (*it).first)); + while (ait != mapAddress.end() && (*ait).first.addressBytes == (*it).first && (*ait).first.type == (*it).second) { + results.push_back(*ait); + ait++; + } + } + return true; +} + +bool CTxMemPool::removeAddressIndex(const uint256 txhash) +{ + LOCK(cs); + addressDeltaMapInserted::iterator it = mapAddressInserted.find(txhash); + + if (it != mapAddressInserted.end()) { + std::vector keys = (*it).second; + for (std::vector::iterator mit = keys.begin(); mit != keys.end(); mit++) { + mapAddress.erase(*mit); + } + mapAddressInserted.erase(it); + } + + return true; +} + +void CTxMemPool::addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view) +{ + LOCK(cs); + + const CTransaction& tx = entry.GetTx(); + std::vector inserted; + + uint256 txhash = tx.GetHash(); + for (unsigned int j = 0; j < tx.vin.size(); j++) { + const CTxIn input = tx.vin[j]; + const CTxOut &prevout = view.GetOutputFor(input); + uint160 addressHash; + int addressType; + + if (prevout.scriptPubKey.IsPayToScriptHash()) { + addressHash = uint160(vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); + addressType = 2; + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + addressHash = uint160(vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); + addressType = 1; + } else { + addressHash.SetNull(); + addressType = 0; + } + + CSpentIndexKey key = CSpentIndexKey(input.prevout.hash, input.prevout.n); + CSpentIndexValue value = CSpentIndexValue(txhash, j, -1, prevout.nValue, addressType, addressHash); + + mapSpent.insert(make_pair(key, value)); + inserted.push_back(key); + + } + + mapSpentInserted.insert(make_pair(txhash, inserted)); +} + +bool CTxMemPool::getSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) +{ + LOCK(cs); + mapSpentIndex::iterator it; + + it = mapSpent.find(key); + if (it != mapSpent.end()) { + value = it->second; + return true; + } + return false; +} + +bool CTxMemPool::removeSpentIndex(const uint256 txhash) +{ + LOCK(cs); + mapSpentIndexInserted::iterator it = mapSpentInserted.find(txhash); + + if (it != mapSpentInserted.end()) { + std::vector keys = (*it).second; + for (std::vector::iterator mit = keys.begin(); mit != keys.end(); mit++) { + mapSpent.erase(*mit); + } + mapSpentInserted.erase(it); + } + + return true; +} void CTxMemPool::remove(const CTransaction &origTx, std::list& removed, bool fRecursive) { @@ -168,6 +306,8 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list& rem mapTx.erase(hash); nTransactionsUpdated++; minerPolicyEstimator->removeTx(hash); + removeAddressIndex(hash); + removeSpentIndex(hash); } } } diff --git a/src/txmempool.h b/src/txmempool.h index 6b6434f1e..e9bb8f359 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -8,6 +8,8 @@ #include +#include "addressindex.h" +#include "spentindex.h" #include "amount.h" #include "coins.h" #include "primitives/transaction.h" @@ -147,6 +149,21 @@ public: mutable CCriticalSection cs; indexed_transaction_set mapTx; + +private: + typedef std::map addressDeltaMap; + addressDeltaMap mapAddress; + + typedef std::map > addressDeltaMapInserted; + addressDeltaMapInserted mapAddressInserted; + + typedef std::map mapSpentIndex; + mapSpentIndex mapSpent; + + typedef std::map > mapSpentIndexInserted; + mapSpentIndexInserted mapSpentInserted; + +public: std::map mapNextTx; std::map mapNullifiers; std::map > mapDeltas; @@ -164,6 +181,14 @@ public: void setSanityCheck(double dFrequency = 1.0) { nCheckFrequency = dFrequency * 4294967296.0; } bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true); + void addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view); + bool getAddressIndex(std::vector > &addresses, + std::vector > &results); + bool removeAddressIndex(const uint256 txhash); + + void addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view); + bool getSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value); + bool removeSpentIndex(const uint256 txhash); void remove(const CTransaction &tx, std::list& removed, bool fRecursive = false); void removeWithAnchor(const uint256 &invalidRoot); void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags); diff --git a/src/wallet-utility.cpp b/src/wallet-utility.cpp new file mode 100644 index 000000000..f36785df3 --- /dev/null +++ b/src/wallet-utility.cpp @@ -0,0 +1,339 @@ +#include +#include + +// Include local headers +#include "wallet/walletdb.h" +#include "util.h" +#include "base58.h" +#include "wallet/crypter.h" +#include + + +void show_help() +{ + std::cout << + "This program outputs Bitcoin addresses and private keys from a wallet.dat file" << std::endl + << std::endl + << "Usage and options: " + << std::endl + << " -datadir= to tell the program where your wallet is" + << std::endl + << " -wallet= (Optional) if your wallet is not named wallet.dat" + << std::endl + << " -regtest or -testnet (Optional) dumps addresses from regtest/testnet" + << std::endl + << " -dumppass (Optional)if you want to extract private keys associated with addresses" + << std::endl + << " -pass= if you have encrypted private keys stored in your wallet" + << std::endl; +} + + +class WalletUtilityDB : public CDB +{ + private: + typedef std::map MasterKeyMap; + MasterKeyMap mapMasterKeys; + unsigned int nMasterKeyMaxID; + SecureString mPass; + std::vector vMKeys; + + public: + WalletUtilityDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnClose = true) : CDB(strFilename, pszMode, fFlushOnClose) + { + nMasterKeyMaxID = 0; + mPass.reserve(100); + } + + std::string getAddress(CDataStream ssKey); + std::string getKey(CDataStream ssKey, CDataStream ssValue); + std::string getCryptedKey(CDataStream ssKey, CDataStream ssValue, std::string masterPass); + bool updateMasterKeys(CDataStream ssKey, CDataStream ssValue); + bool parseKeys(bool dumppriv, std::string masterPass); + + bool DecryptSecret(const std::vector& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext); + bool Unlock(); + bool DecryptKey(const std::vector& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key); +}; + + +/* + * Address from a public key in base58 + */ +std::string WalletUtilityDB::getAddress(CDataStream ssKey) +{ + CPubKey vchPubKey; + ssKey >> vchPubKey; + CKeyID id = vchPubKey.GetID(); + std::string strAddr = CBitcoinAddress(id).ToString(); + + return strAddr; +} + + +/* + * Non encrypted private key in WIF + */ +std::string WalletUtilityDB::getKey(CDataStream ssKey, CDataStream ssValue) +{ + std::string strKey; + CPubKey vchPubKey; + ssKey >> vchPubKey; + CPrivKey pkey; + CKey key; + + ssValue >> pkey; + if (key.Load(pkey, vchPubKey, true)) + strKey = CBitcoinSecret(key).ToString(); + + return strKey; +} + + +bool WalletUtilityDB::DecryptSecret(const std::vector& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) +{ + CCrypter cKeyCrypter; + std::vector chIV(WALLET_CRYPTO_KEY_SIZE); + memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE); + + BOOST_FOREACH(const CKeyingMaterial vMKey, vMKeys) + { + if(!cKeyCrypter.SetKey(vMKey, chIV)) + continue; + if (cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext))) + return true; + } + return false; +} + + +bool WalletUtilityDB::Unlock() +{ + CCrypter crypter; + CKeyingMaterial vMasterKey; + + BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(mPass, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + continue; // try another master key + vMKeys.push_back(vMasterKey); + } + return true; +} + + +bool WalletUtilityDB::DecryptKey(const std::vector& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) +{ + CKeyingMaterial vchSecret; + if(!DecryptSecret(vchCryptedSecret, vchPubKey.GetHash(), vchSecret)) + return false; + + if (vchSecret.size() != 32) + return false; + + key.Set(vchSecret.begin(), vchSecret.end(), vchPubKey.IsCompressed()); + return true; +} + + +/* + * Encrypted private key in WIF format + */ +std::string WalletUtilityDB::getCryptedKey(CDataStream ssKey, CDataStream ssValue, std::string masterPass) +{ + mPass = masterPass.c_str(); + CPubKey vchPubKey; + ssKey >> vchPubKey; + CKey key; + + std::vector vKey; + ssValue >> vKey; + + if (!Unlock()) + return ""; + + if(!DecryptKey(vKey, vchPubKey, key)) + return ""; + + std::string strKey = CBitcoinSecret(key).ToString(); + return strKey; +} + + +/* + * Master key derivation + */ +bool WalletUtilityDB::updateMasterKeys(CDataStream ssKey, CDataStream ssValue) +{ + unsigned int nID; + ssKey >> nID; + CMasterKey kMasterKey; + ssValue >> kMasterKey; + if (mapMasterKeys.count(nID) != 0) + { + std::cout << "Error reading wallet database: duplicate CMasterKey id " << nID << std::endl; + return false; + } + mapMasterKeys[nID] = kMasterKey; + + if (nMasterKeyMaxID < nID) + nMasterKeyMaxID = nID; + + return true; +} + + +/* + * Look at all the records and parse keys for addresses and private keys + */ +bool WalletUtilityDB::parseKeys(bool dumppriv, std::string masterPass) +{ + DBErrors result = DB_LOAD_OK; + std::string strType; + bool first = true; + + try { + Dbc* pcursor = GetCursor(); + if (!pcursor) + { + LogPrintf("Error getting wallet database cursor\n"); + result = DB_CORRUPT; + } + + if (dumppriv) + { + while (result == DB_LOAD_OK && true) + { + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int result = ReadAtCursor(pcursor, ssKey, ssValue); + + if (result == DB_NOTFOUND) { + break; + } + else if (result != 0) + { + LogPrintf("Error reading next record from wallet database\n"); + result = DB_CORRUPT; + break; + } + + ssKey >> strType; + if (strType == "mkey") + { + updateMasterKeys(ssKey, ssValue); + } + } + pcursor->close(); + pcursor = GetCursor(); + } + + while (result == DB_LOAD_OK && true) + { + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = ReadAtCursor(pcursor, ssKey, ssValue); + + if (ret == DB_NOTFOUND) + { + std::cout << " ]" << std::endl; + first = true; + break; + } + else if (ret != DB_LOAD_OK) + { + LogPrintf("Error reading next record from wallet database\n"); + result = DB_CORRUPT; + break; + } + + ssKey >> strType; + + if (strType == "key" || strType == "ckey") + { + std::string strAddr = getAddress(ssKey); + std::string strKey = ""; + + + if (dumppriv && strType == "key") + strKey = getKey(ssKey, ssValue); + if (dumppriv && strType == "ckey") + { + if (masterPass == "") + { + std::cout << "Encrypted wallet, please provide a password. See help below" << std::endl; + show_help(); + result = DB_LOAD_FAIL; + break; + } + strKey = getCryptedKey(ssKey, ssValue, masterPass); + } + + if (strAddr != "") + { + if (first) + std::cout << "[ "; + else + std::cout << ", "; + } + + if (dumppriv) + { + std::cout << "{\"addr\" : \"" + strAddr + "\", " + << "\"pkey\" : \"" + strKey + "\"}" + << std::flush; + } + else + { + std::cout << "\"" + strAddr + "\""; + } + + first = false; + } + } + + pcursor->close(); + } catch (DbException &e) { + std::cout << "DBException caught " << e.get_errno() << std::endl; + } catch (std::exception &e) { + std::cout << "Exception caught " << std::endl; + } + + if (result == DB_LOAD_OK) + return true; + else + return false; +} + + +int main(int argc, char* argv[]) +{ + ParseParameters(argc, argv); + std::string walletFile = GetArg("-wallet", "wallet.dat"); + std::string masterPass = GetArg("-pass", ""); + bool fDumpPass = GetBoolArg("-dumppass", false); + bool help = GetBoolArg("-h", false); + bool result = false; + + if (help) + { + show_help(); + return 0; + } + + try { + SelectParamsFromCommandLine(); + result = WalletUtilityDB(walletFile, "r").parseKeys(fDumpPass, masterPass); + } + catch (const std::exception& e) { + std::cout << "Error opening wallet file " << walletFile << std::endl; + std::cout << e.what() << std::endl; + } + + if (result) + return 0; + else + return -1; +} \ No newline at end of file