aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--Makefile.am2
-rw-r--r--configure.ac37
-rw-r--r--contrib/bitcoind.bash-completion2
-rw-r--r--contrib/debian/changelog6
-rw-r--r--contrib/debian/examples/bitcoin.conf9
-rw-r--r--contrib/debian/manpages/bitcoin-cli.13
-rw-r--r--contrib/debian/manpages/bitcoin-qt.112
-rw-r--r--contrib/debian/manpages/bitcoin.conf.510
-rw-r--r--contrib/debian/manpages/bitcoind.114
-rwxr-xr-xcontrib/zmq/zmq_sub.py37
-rwxr-xr-xdepends/config.guess2
-rw-r--r--depends/packages/libevent.mk31
-rw-r--r--depends/packages/packages.mk4
-rw-r--r--depends/packages/zeromq.mk26
-rw-r--r--depends/patches/libevent/reuseaddr.patch21
-rw-r--r--doc/REST-interface.md14
-rw-r--r--doc/assets-attribution.md53
-rw-r--r--doc/build-osx.md4
-rw-r--r--doc/build-unix.md10
-rw-r--r--doc/files.md13
-rw-r--r--doc/gitian-building.md139
-rw-r--r--doc/gitian-building/create_new_vm.pngbin0 -> 119839 bytes
-rw-r--r--doc/gitian-building/create_vm_hard_disk.pngbin0 -> 123400 bytes
-rw-r--r--doc/gitian-building/create_vm_hard_disk_file_type.pngbin0 -> 170503 bytes
-rw-r--r--doc/gitian-building/create_vm_hard_drive.pngbin79798 -> 0 bytes
-rw-r--r--doc/gitian-building/create_vm_hard_drive_file_type.pngbin82281 -> 0 bytes
-rw-r--r--doc/gitian-building/create_vm_memsize.pngbin53772 -> 89475 bytes
-rw-r--r--doc/gitian-building/create_vm_page1.pngbin131585 -> 0 bytes
-rw-r--r--doc/gitian-building/create_vm_storage_physical_hard_disk.pngbin0 -> 181681 bytes
-rw-r--r--doc/gitian-building/create_vm_storage_physical_hard_drive.pngbin90350 -> 0 bytes
-rw-r--r--doc/gitian-building/debian_install_10_configure_clock.pngbin7892 -> 7892 bytes
-rw-r--r--doc/gitian-building/debian_install_11_partition_disks.pngbin9511 -> 9511 bytes
-rw-r--r--doc/gitian-building/debian_install_12_choose_disk.pngbin6613 -> 6613 bytes
-rw-r--r--doc/gitian-building/debian_install_13_partition_scheme.pngbin8082 -> 0 bytes
-rw-r--r--doc/gitian-building/debian_install_14_finish.pngbin10467 -> 10794 bytes
-rw-r--r--doc/gitian-building/debian_install_15_write_changes.pngbin8790 -> 8790 bytes
-rw-r--r--doc/gitian-building/debian_install_16_choose_a_mirror.pngbin11340 -> 11134 bytes
-rw-r--r--doc/gitian-building/debian_install_17_choose_a_mirror2.pngbin9788 -> 0 bytes
-rw-r--r--doc/gitian-building/debian_install_18_proxy_settings.pngbin7582 -> 7582 bytes
-rw-r--r--doc/gitian-building/debian_install_19_software_selection.pngbin8939 -> 8767 bytes
-rw-r--r--doc/gitian-building/debian_install_1_boot_menu.pngbin76176 -> 110818 bytes
-rw-r--r--doc/gitian-building/debian_install_20_install_grub.pngbin9784 -> 9784 bytes
-rw-r--r--doc/gitian-building/debian_install_21_install_grub_bootloader.pngbin0 -> 8878 bytes
-rw-r--r--doc/gitian-building/debian_install_22_finish_installation.png (renamed from doc/gitian-building/debian_install_21_finish_installation.png)bin6964 -> 6964 bytes
-rw-r--r--doc/gitian-building/debian_install_2_select_a_language.pngbin13118 -> 13131 bytes
-rw-r--r--doc/gitian-building/debian_install_3_select_location.pngbin9613 -> 10388 bytes
-rw-r--r--doc/gitian-building/debian_install_4_configure_keyboard.pngbin10220 -> 10224 bytes
-rw-r--r--doc/gitian-building/debian_install_5_configure_the_network.pngbin6774 -> 7612 bytes
-rw-r--r--doc/gitian-building/debian_install_6a_set_up_root_password.pngbin11876 -> 11876 bytes
-rw-r--r--doc/gitian-building/debian_install_7_set_up_user_fullname.pngbin7528 -> 8407 bytes
-rw-r--r--doc/gitian-building/debian_install_8_set_up_username.pngbin6304 -> 7058 bytes
-rw-r--r--doc/gitian-building/debian_install_9_user_password.pngbin6323 -> 6322 bytes
-rw-r--r--doc/gitian-building/debian_root_login.pngbin0 -> 7028 bytes
-rw-r--r--doc/gitian-building/network_settings.pngbin59986 -> 72185 bytes
-rw-r--r--doc/gitian-building/port_forwarding_rules.pngbin14044 -> 44052 bytes
-rw-r--r--doc/gitian-building/select_startup_disk.pngbin86323 -> 130324 bytes
-rw-r--r--doc/init.md43
-rw-r--r--doc/release-notes.md53
-rw-r--r--doc/tor.md11
-rw-r--r--doc/zmq.md98
-rwxr-xr-xqa/pull-tester/rpc-tests.sh7
-rwxr-xr-xqa/pull-tester/run-bitcoin-cli13
-rwxr-xr-xqa/pull-tester/tests-config.sh.in1
-rw-r--r--qa/rpc-tests/README.md105
-rwxr-xr-xqa/rpc-tests/bipdersig-p2p.py23
-rwxr-xr-xqa/rpc-tests/fundrawtransaction.py59
-rwxr-xr-xqa/rpc-tests/httpbasics.py5
-rwxr-xr-xqa/rpc-tests/invalidblockrequest.py12
-rwxr-xr-xqa/rpc-tests/keypool.py15
-rwxr-xr-xqa/rpc-tests/listtransactions.py10
-rwxr-xr-xqa/rpc-tests/p2p-acceptblock.py8
-rwxr-xr-xqa/rpc-tests/p2p-fullblocktest.py272
-rwxr-xr-xqa/rpc-tests/rest.py57
-rwxr-xr-xqa/rpc-tests/script_test.py20
-rw-r--r--qa/rpc-tests/test_framework/blockstore.py23
-rw-r--r--qa/rpc-tests/test_framework/blocktools.py20
-rwxr-xr-xqa/rpc-tests/test_framework/comptool.py148
-rw-r--r--qa/rpc-tests/test_framework/key.py215
-rw-r--r--qa/rpc-tests/test_framework/script.py4
-rw-r--r--qa/rpc-tests/test_framework/util.py15
-rwxr-xr-xqa/rpc-tests/zmq_test.py93
-rw-r--r--src/Makefile.am39
-rw-r--r--src/Makefile.qt.include6
-rw-r--r--src/Makefile.qttest.include6
-rw-r--r--src/Makefile.test.include8
-rw-r--r--src/addrman.cpp12
-rw-r--r--src/addrman.h2
-rw-r--r--src/bitcoin-cli.cpp137
-rw-r--r--src/bitcoin-tx.cpp32
-rw-r--r--src/bitcoind.cpp9
-rw-r--r--src/chainparams.cpp10
-rw-r--r--src/chainparams.h13
-rw-r--r--src/checkpoints.h9
-rw-r--r--src/coincontrol.h3
-rw-r--r--src/consensus/validation.h13
-rw-r--r--src/ecwrapper.cpp33
-rw-r--r--src/httprpc.cpp193
-rw-r--r--src/httprpc.h37
-rw-r--r--src/httpserver.cpp575
-rw-r--r--src/httpserver.h149
-rw-r--r--src/init.cpp137
-rw-r--r--src/init.h2
-rw-r--r--src/keystore.cpp41
-rw-r--r--src/keystore.h5
-rw-r--r--src/leveldbwrapper.cpp3
-rw-r--r--src/limitedmap.h19
-rw-r--r--src/main.cpp164
-rw-r--r--src/main.h21
-rw-r--r--src/merkleblock.cpp2
-rw-r--r--src/miner.cpp10
-rw-r--r--src/miner.h2
-rw-r--r--src/net.cpp292
-rw-r--r--src/net.h16
-rw-r--r--src/netbase.cpp4
-rw-r--r--src/policy/fees.cpp2
-rw-r--r--src/policy/fees.h2
-rw-r--r--src/protocol.h4
-rw-r--r--src/qt/bitcoin.cpp9
-rw-r--r--src/qt/bitcoingui.cpp6
-rw-r--r--src/qt/bitcoingui.h2
-rw-r--r--src/qt/paymentserver.cpp36
-rw-r--r--src/qt/paymentserver.h6
-rw-r--r--src/qt/rpcconsole.cpp42
-rw-r--r--src/qt/rpcconsole.h2
-rw-r--r--src/qt/sendcoinsdialog.cpp3
-rw-r--r--src/qt/splashscreen.cpp2
-rw-r--r--src/qt/test/paymentservertests.cpp3
-rw-r--r--src/qt/test/test_main.cpp4
-rw-r--r--src/qt/transactiondesc.cpp4
-rw-r--r--src/qt/transactionrecord.cpp6
-rw-r--r--src/qt/walletmodel.cpp5
-rw-r--r--src/qt/walletmodel.h1
-rw-r--r--src/qt/walletview.cpp21
-rw-r--r--src/qt/walletview.h3
-rw-r--r--src/rest.cpp318
-rw-r--r--src/reverselock.h31
-rw-r--r--src/rpcblockchain.cpp97
-rw-r--r--src/rpcclient.cpp2
-rw-r--r--src/rpcmining.cpp8
-rw-r--r--src/rpcmisc.cpp9
-rw-r--r--src/rpcnet.cpp2
-rw-r--r--src/rpcprotocol.cpp229
-rw-r--r--src/rpcprotocol.h87
-rw-r--r--src/rpcrawtransaction.cpp43
-rw-r--r--src/rpcserver.cpp555
-rw-r--r--src/rpcserver.h73
-rw-r--r--src/scheduler.cpp13
-rw-r--r--src/script/interpreter.cpp2
-rw-r--r--src/script/standard.cpp5
-rw-r--r--src/script/standard.h1
-rw-r--r--src/sync.cpp49
-rw-r--r--src/sync.h2
-rw-r--r--src/test/Checkpoints_tests.cpp2
-rw-r--r--src/test/data/bitcoin-util-test.json30
-rw-r--r--src/test/data/tx_invalid.json4
-rw-r--r--src/test/data/tx_valid.json2
-rw-r--r--src/test/data/txcreatedata1.hex1
-rw-r--r--src/test/data/txcreatedata2.hex1
-rw-r--r--src/test/limitedmap_tests.cpp101
-rw-r--r--src/test/netbase_tests.cpp17
-rw-r--r--src/test/reverselock_tests.cpp64
-rw-r--r--src/test/rpc_tests.cpp33
-rw-r--r--src/test/rpc_wallet_tests.cpp39
-rw-r--r--src/test/univalue_tests.cpp15
-rw-r--r--src/timedata.cpp6
-rw-r--r--src/univalue/univalue_read.cpp14
-rw-r--r--src/util.cpp12
-rw-r--r--src/util.h1
-rw-r--r--src/validationinterface.cpp3
-rw-r--r--src/validationinterface.h3
-rw-r--r--src/version.h5
-rw-r--r--src/wallet/crypter.cpp4
-rw-r--r--src/wallet/rpcdump.cpp135
-rw-r--r--src/wallet/rpcwallet.cpp21
-rw-r--r--src/wallet/wallet.cpp10
-rw-r--r--src/wallet/wallet.h2
-rw-r--r--src/wallet/wallet_ismine.cpp10
-rw-r--r--src/wallet/wallet_ismine.h8
-rw-r--r--src/zmq/zmqabstractnotifier.cpp22
-rw-r--r--src/zmq/zmqabstractnotifier.h42
-rw-r--r--src/zmq/zmqconfig.h24
-rw-r--r--src/zmq/zmqnotificationinterface.cpp155
-rw-r--r--src/zmq/zmqnotificationinterface.h35
-rw-r--r--src/zmq/zmqpublishnotifier.cpp172
-rw-r--r--src/zmq/zmqpublishnotifier.h41
186 files changed, 4663 insertions, 1851 deletions
diff --git a/.travis.yml b/.travis.yml
index 1f6eb15c27..c8785144ad 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,6 +6,7 @@
os: linux
language: cpp
+compiler: gcc
env:
global:
- MAKEJOBS=-j3
@@ -30,18 +31,21 @@ matrix:
- compiler: ": ARM"
env: HOST=arm-linux-gnueabihf PACKAGES="g++-arm-linux-gnueabihf" DEP_OPTS="NO_QT=1" GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports"
- compiler: ": Win32"
- env: HOST=i686-w64-mingw32 PACKAGES="nsis gcc-mingw-w64-i686 g++-mingw-w64-i686 binutils-mingw-w64-i686 mingw-w64-dev wine bc" RUN_TESTS=true GOAL="deploy" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports" MAKEJOBS="-j2"
+ env: HOST=i686-w64-mingw32 PPA="ppa:ubuntu-wine/ppa" PACKAGES="nsis gcc-mingw-w64-i686 g++-mingw-w64-i686 binutils-mingw-w64-i686 mingw-w64-dev wine1.7 bc" RUN_TESTS=true GOAL="deploy" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports" MAKEJOBS="-j2"
- compiler: ": 32-bit + dash"
env: HOST=i686-pc-linux-gnu PACKAGES="g++-multilib bc" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" USE_SHELL="/bin/dash"
- compiler: ": Win64"
- env: HOST=x86_64-w64-mingw32 PACKAGES="nsis gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-dev wine bc" RUN_TESTS=true GOAL="deploy" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports" MAKEJOBS="-j2"
+ env: HOST=x86_64-w64-mingw32 PPA="ppa:ubuntu-wine/ppa" PACKAGES="nsis gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-dev wine1.7 bc" RUN_TESTS=true GOAL="deploy" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports" MAKEJOBS="-j2"
- compiler: ": bitcoind"
env: HOST=x86_64-unknown-linux-gnu PACKAGES="bc" DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CPPFLAGS=-DDEBUG_LOCKORDER"
- compiler: ": No wallet"
env: HOST=x86_64-unknown-linux-gnu DEP_OPTS="NO_WALLET=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports"
- compiler: ": Cross-Mac"
env: HOST=x86_64-apple-darwin11 PACKAGES="cmake libcap-dev libz-dev libbz2-dev" BITCOIN_CONFIG="--enable-reduce-exports" OSX_SDK=10.9 GOAL="deploy"
+ exclude:
+ - compiler: gcc
install:
+ - if [ -n "$PPA" ]; then travis_retry sudo add-apt-repository "$PPA" -y; fi
- if [ -n "$PACKAGES" ]; then travis_retry sudo apt-get update; fi
- if [ -n "$PACKAGES" ]; then travis_retry sudo apt-get install --no-install-recommends --no-upgrade -qq $PACKAGES; fi
before_script:
diff --git a/Makefile.am b/Makefile.am
index ab68d8fa6d..dfde0d43ec 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -204,7 +204,7 @@ endif
dist_noinst_SCRIPTS = autogen.sh
-EXTRA_DIST = $(top_srcdir)/share/genbuild.sh qa/pull-tester/rpc-tests.sh qa/pull-tester/run-bitcoin-cli qa/rpc-tests $(DIST_DOCS) $(WINDOWS_PACKAGING) $(OSX_PACKAGING)
+EXTRA_DIST = $(top_srcdir)/share/genbuild.sh qa/pull-tester/rpc-tests.sh qa/rpc-tests $(DIST_DOCS) $(WINDOWS_PACKAGING) $(OSX_PACKAGING)
CLEANFILES = $(OSX_DMG) $(BITCOIN_WIN_INSTALLER)
diff --git a/configure.ac b/configure.ac
index a524bded6f..4bd28c85db 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,5 +1,5 @@
dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
-AC_PREREQ([2.60])
+AC_PREREQ([2.69])
define(_CLIENT_VERSION_MAJOR, 0)
define(_CLIENT_VERSION_MINOR, 11)
define(_CLIENT_VERSION_REVISION, 99)
@@ -137,6 +137,11 @@ AC_ARG_ENABLE([glibc-back-compat],
[use_glibc_compat=$enableval],
[use_glibc_compat=no])
+AC_ARG_ENABLE([zmq],
+ [AS_HELP_STRING([--disable-zmq],[Disable ZMQ notifications])],
+ [use_zmq=$enableval],
+ [use_zmq=yes])
+
AC_ARG_WITH([protoc-bindir],[AS_HELP_STRING([--with-protoc-bindir=BIN_DIR],[specify protoc bin path])], [protoc_bin_path=$withval], [])
# Enable debug
@@ -664,6 +669,12 @@ if test x$use_pkgconfig = xyes; then
if test x$use_qr != xno; then
BITCOIN_QT_CHECK([PKG_CHECK_MODULES([QR], [libqrencode], [have_qrencode=yes], [have_qrencode=no])])
fi
+ if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then
+ PKG_CHECK_MODULES([EVENT], [libevent],, [AC_MSG_ERROR(libevent not found.)])
+ if test x$TARGET_OS != xwindows; then
+ PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads],, [AC_MSG_ERROR(libevent_pthreads not found.)])
+ fi
+ fi
]
)
else
@@ -673,6 +684,14 @@ else
AC_CHECK_HEADER([openssl/ssl.h],, AC_MSG_ERROR(libssl headers missing),)
AC_CHECK_LIB([ssl], [main],SSL_LIBS=-lssl, AC_MSG_ERROR(libssl missing))
+ if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then
+ AC_CHECK_HEADER([event2/event.h],, AC_MSG_ERROR(libevent headers missing),)
+ AC_CHECK_LIB([event],[main],EVENT_LIBS=-levent,AC_MSG_ERROR(libevent missing))
+ if test x$TARGET_OS != xwindows; then
+ AC_CHECK_LIB([event_pthreads],[main],EVENT_PTHREADS_LIBS=-levent_pthreads,AC_MSG_ERROR(libevent_pthreads missing))
+ fi
+ fi
+
BITCOIN_QT_CHECK(AC_CHECK_LIB([protobuf] ,[main],[PROTOBUF_LIBS=-lprotobuf], BITCOIN_QT_FAIL(libprotobuf not found)))
if test x$use_qr != xno; then
BITCOIN_QT_CHECK([AC_CHECK_LIB([qrencode], [main],[QR_LIBS=-lqrencode], [have_qrencode=no])])
@@ -819,6 +838,22 @@ if test x$bitcoin_enable_qt != xno; then
fi
fi
+# conditional search for and use libzmq
+AC_MSG_CHECKING([whether to build ZMQ support])
+if test "x$use_zmq" = "xyes"; then
+ AC_MSG_RESULT([yes])
+ PKG_CHECK_MODULES([ZMQ],[libzmq],
+ [AC_DEFINE([ENABLE_ZMQ],[1],[Define to 1 to enable ZMQ functions])],
+ [AC_DEFINE([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions])
+ AC_MSG_WARN([libzmq not found, disabling])
+ use_zmq=no])
+else
+ AC_MSG_RESULT([no, --disable-zmq used])
+ AC_DEFINE_UNQUOTED([ENABLE_ZMQ],[0],[Define to 1 to enable ZMQ functions])
+fi
+
+AM_CONDITIONAL([ENABLE_ZMQ], [test "x$use_zmq" = "xyes"])
+
AC_MSG_CHECKING([whether to build test_bitcoin])
if test x$use_tests = xyes; then
AC_MSG_RESULT([yes])
diff --git a/contrib/bitcoind.bash-completion b/contrib/bitcoind.bash-completion
index 3cc959c0a6..1338d2f2b5 100644
--- a/contrib/bitcoind.bash-completion
+++ b/contrib/bitcoind.bash-completion
@@ -96,7 +96,7 @@ _bitcoind() {
esac
case "$cur" in
- -conf=*|-pid=*|-loadblock=*|-wallet=*|-rpcsslcertificatechainfile=*|-rpcsslprivatekeyfile=*)
+ -conf=*|-pid=*|-loadblock=*|-wallet=*)
cur="${cur#*=}"
_filedir
return 0
diff --git a/contrib/debian/changelog b/contrib/debian/changelog
index 7ce3babc1b..bd7ab3524c 100644
--- a/contrib/debian/changelog
+++ b/contrib/debian/changelog
@@ -149,7 +149,7 @@ bitcoin (0.5.3-natty0) natty; urgency=low
bitcoin (0.5.2-natty1) natty; urgency=low
* Remove mentions on anonymity in package descriptions and manpage.
- These should never have been there, bitcoin isnt anonymous without
+ These should never have been there, bitcoin isn't anonymous without
a ton of work that virtually no users will ever be willing and
capable of doing
@@ -190,7 +190,7 @@ bitcoin (0.5.0~rc1-natty1) natty; urgency=low
* Add test_bitcoin to build test
* Fix clean
- * Remove uneccessary build-dependancies
+ * Remove unnecessary build-dependancies
-- Matt Corallo <matt@bluematt.me> Wed, 26 Oct 2011 14:37:18 -0400
@@ -350,7 +350,7 @@ bitcoin (0.3.20.01~dfsg-1) unstable; urgency=low
bitcoin (0.3.19~dfsg-6) unstable; urgency=low
- * Fix override agressive optimizations.
+ * Fix override aggressive optimizations.
* Fix tighten build-dependencies to really fit backporting to Lenny:
+ Add fallback build-dependency on libdb4.6++-dev.
+ Tighten unversioned Boost build-dependencies to recent versions,
diff --git a/contrib/debian/examples/bitcoin.conf b/contrib/debian/examples/bitcoin.conf
index ade80d60ea..62ffd7123a 100644
--- a/contrib/debian/examples/bitcoin.conf
+++ b/contrib/debian/examples/bitcoin.conf
@@ -95,15 +95,6 @@
# running on another host using this option:
#rpcconnect=127.0.0.1
-# Use Secure Sockets Layer (also known as TLS or HTTPS) to communicate
-# with Bitcoin -server or bitcoind
-#rpcssl=1
-
-# OpenSSL settings used when rpcssl=1
-#rpcsslciphers=TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH
-#rpcsslcertificatechainfile=server.cert
-#rpcsslprivatekeyfile=server.pem
-
# Transaction Fee Changes in 0.10.0
# Send transactions as zero-fee transactions if possible (default: 0)
diff --git a/contrib/debian/manpages/bitcoin-cli.1 b/contrib/debian/manpages/bitcoin-cli.1
index f953ae9db7..154b458739 100644
--- a/contrib/debian/manpages/bitcoin-cli.1
+++ b/contrib/debian/manpages/bitcoin-cli.1
@@ -36,9 +36,6 @@ Listen for JSON\-RPC connections on <port> (default: 8332 or testnet: 18332).
.TP
\fB\-rpcconnect=\fR<ip>
Send commands to node running on <ip> (default: 127.0.0.1).
-.TP
-\fB\-rpcssl\fR=\fI1\fR
-Use OpenSSL (https) for JSON\-RPC connections (see the Bitcoin Wiki for SSL setup instructions).
.SH "SEE ALSO"
\fBbitcoind\fP, \fBbitcoin.conf\fP
diff --git a/contrib/debian/manpages/bitcoin-qt.1 b/contrib/debian/manpages/bitcoin-qt.1
index a023582bc0..05eadc94cd 100644
--- a/contrib/debian/manpages/bitcoin-qt.1
+++ b/contrib/debian/manpages/bitcoin-qt.1
@@ -178,18 +178,6 @@ Set maximum block size in bytes (default: 250000)
.HP
\fB\-blockprioritysize=\fR<n> Set maximum size of high\-priority/low\-fee transactions in bytes (default: 27000)
.PP
-SSL options: (see the Bitcoin Wiki for SSL setup instructions)
-.TP
-\fB\-rpcssl\fR
-Use OpenSSL (https) for JSON\-RPC connections
-.TP
-\fB\-rpcsslcertificatechainfile=\fR<file.cert>
-Server certificate file (default: server.cert)
-.TP
-\fB\-rpcsslprivatekeyfile=\fR<file.pem>
-Server private key (default: server.pem)
-.TP
-\fB\-rpcsslciphers=\fR<ciphers>
Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH)
.SS "UI options:"
.TP
diff --git a/contrib/debian/manpages/bitcoin.conf.5 b/contrib/debian/manpages/bitcoin.conf.5
index 8a0078d5d5..0cf4d991e3 100644
--- a/contrib/debian/manpages/bitcoin.conf.5
+++ b/contrib/debian/manpages/bitcoin.conf.5
@@ -46,16 +46,6 @@ Listen for RPC connections on this TCP port.
\fBrpcconnect=\fR\fI'127.0.0.1'\fR
You can use *bitcoin* or *bitcoind(1)* to send commands to *bitcoin*/*bitcoind(1)* running on another host using this option.
.TP
-\fBrpcssl=\fR\fI'1'\fR
-Use Secure Sockets Layer (also known as TLS or HTTPS) to communicate with *bitcoin* '\-server' or *bitcoind(1)*. Example of OpenSSL settings used when *rpcssl*='1':
-.TP
-\fB\-rpcsslciphers=\fR<ciphers>
-Acceptable ciphers (default: TLSv1+HIGH:\:!SSLv2:\:!aNULL:\:!eNULL:\:!AH:\:!3DES:\:@STRENGTH)
-.TP
-\fBrpcsslcertificatechainfile=\fR\fI'server.cert'\fR
-.TP
-\fBrpcsslprivatekeyfile=\fR\fI'server.pem'\fR
-.TP
.SH MISCELLANEOUS OPTIONS
.TP
\fBgen=\fR[\fI'0'\fR|\fI'1'\fR]
diff --git a/contrib/debian/manpages/bitcoind.1 b/contrib/debian/manpages/bitcoind.1
index c225b9f3e9..5b0f2921aa 100644
--- a/contrib/debian/manpages/bitcoind.1
+++ b/contrib/debian/manpages/bitcoind.1
@@ -62,20 +62,6 @@ Allow JSON\-RPC connections from specified IP address
.TP
\fB\-rpcconnect=\fR<ip>
Send commands to node running on <ip>
-.PP
-SSL options: (see the Bitcoin Wiki for SSL setup instructions)
-.TP
-\fB\-rpcssl\fR=\fI1\fR
-Use OpenSSL (https) for JSON\-RPC connections
-.TP
-\fB\-rpcsslcertificatchainfile=\fR<file.cert>
-Server certificate file (default: server.cert)
-.TP
-\fB\-rpcsslprivatekeyfile=\fR<file.pem>
-Server private key (default: server.pem)
-.TP
-\fB\-rpcsslciphers=\fR<ciphers>
-Acceptable ciphers (default: TLSv1+HIGH:\:!SSLv2:\:!aNULL:\:!eNULL:\:!AH:\:!3DES:\:@STRENGTH)
.TP
\-?
This help message
diff --git a/contrib/zmq/zmq_sub.py b/contrib/zmq/zmq_sub.py
new file mode 100755
index 0000000000..decf29d42a
--- /dev/null
+++ b/contrib/zmq/zmq_sub.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python2
+
+import array
+import binascii
+import zmq
+
+port = 28332
+
+zmqContext = zmq.Context()
+zmqSubSocket = zmqContext.socket(zmq.SUB)
+zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashblock")
+zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashtx")
+zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "rawblock")
+zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "rawtx")
+zmqSubSocket.connect("tcp://127.0.0.1:%i" % port)
+
+try:
+ while True:
+ msg = zmqSubSocket.recv_multipart()
+ topic = str(msg[0])
+ body = msg[1]
+
+ if topic == "hashblock":
+ print "- HASH BLOCK -"
+ print binascii.hexlify(body)
+ elif topic == "hashtx":
+ print '- HASH TX -'
+ print binascii.hexlify(body)
+ elif topic == "rawblock":
+ print "- RAW BLOCK HEADER -"
+ print binascii.hexlify(body[:80])
+ elif topic == "rawtx":
+ print '- RAW TX -'
+ print binascii.hexlify(body)
+
+except KeyboardInterrupt:
+ zmqContext.destroy()
diff --git a/depends/config.guess b/depends/config.guess
index f7eb141e75..f357ec6c8f 100755
--- a/depends/config.guess
+++ b/depends/config.guess
@@ -1117,7 +1117,7 @@ EOF
# uname -m prints for DJGPP always 'pc', but it prints nothing about
# the processor, so we play safe by assuming i586.
# Note: whatever this is, it MUST be the same as what config.sub
- # prints for the "djgpp" host, or else GDB configury will decide that
+ # prints for the "djgpp" host, or else GDB configure will decide that
# this is a cross-build.
echo i586-pc-msdosdjgpp
exit ;;
diff --git a/depends/packages/libevent.mk b/depends/packages/libevent.mk
new file mode 100644
index 0000000000..2e9be1e98c
--- /dev/null
+++ b/depends/packages/libevent.mk
@@ -0,0 +1,31 @@
+package=libevent
+$(package)_version=2.0.22
+$(package)_download_path=https://github.com/libevent/libevent/releases/download/release-2.0.22-stable
+$(package)_file_name=$(package)-$($(package)_version)-stable.tar.gz
+$(package)_sha256_hash=71c2c49f0adadacfdbe6332a372c38cf9c8b7895bb73dabeaa53cdcc1d4e1fa3
+$(package)_patches=reuseaddr.patch
+
+define $(package)_preprocess_cmds
+ patch -p1 < $($(package)_patch_dir)/reuseaddr.patch
+endef
+
+define $(package)_set_vars
+ $(package)_config_opts=--disable-shared --disable-openssl --disable-libevent-regress
+ $(package)_config_opts_release=--disable-debug-mode
+ $(package)_config_opts_linux=--with-pic
+endef
+
+define $(package)_config_cmds
+ $($(package)_autoconf)
+endef
+
+define $(package)_build_cmds
+ $(MAKE)
+endef
+
+define $(package)_stage_cmds
+ $(MAKE) DESTDIR=$($(package)_staging_dir) install
+endef
+
+define $(package)_postprocess_cmds
+endef
diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk
index 03908aba59..fc1e5f0b68 100644
--- a/depends/packages/packages.mk
+++ b/depends/packages/packages.mk
@@ -1,4 +1,6 @@
-packages:=boost openssl
+packages:=boost openssl libevent
+packages_darwin:=zeromq
+packages_linux:=zeromq
native_packages := native_ccache native_comparisontool
qt_native_packages = native_protobuf
diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk
new file mode 100644
index 0000000000..24e8e5f1c9
--- /dev/null
+++ b/depends/packages/zeromq.mk
@@ -0,0 +1,26 @@
+package=zeromq
+$(package)_version=4.0.4
+$(package)_download_path=http://download.zeromq.org
+$(package)_file_name=$(package)-$($(package)_version).tar.gz
+$(package)_sha256_hash=1ef71d46e94f33e27dd5a1661ed626cd39be4d2d6967792a275040e34457d399
+
+define $(package)_set_vars
+ $(package)_config_opts=--without-documentation --disable-shared
+ $(package)_config_opts_linux=--with-pic
+endef
+
+define $(package)_config_cmds
+ $($(package)_autoconf)
+endef
+
+define $(package)_build_cmds
+ $(MAKE) -C src
+endef
+
+define $(package)_stage_cmds
+ $(MAKE) -C src DESTDIR=$($(package)_staging_dir) install
+endef
+
+define $(package)_postprocess_cmds
+ rm -rf bin share
+endef
diff --git a/depends/patches/libevent/reuseaddr.patch b/depends/patches/libevent/reuseaddr.patch
new file mode 100644
index 0000000000..58695c11f5
--- /dev/null
+++ b/depends/patches/libevent/reuseaddr.patch
@@ -0,0 +1,21 @@
+--- old/evutil.c 2015-08-28 19:26:23.488765923 -0400
++++ new/evutil.c 2015-08-28 19:27:41.392767019 -0400
+@@ -321,15 +321,16 @@
+ int
+ evutil_make_listen_socket_reuseable(evutil_socket_t sock)
+ {
+-#ifndef WIN32
+ int one = 1;
++#ifndef WIN32
+ /* REUSEADDR on Unix means, "don't hang on to this address after the
+ * listener is closed." On Windows, though, it means "don't keep other
+ * processes from binding to this address while we're using it. */
+ return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one,
+ (ev_socklen_t)sizeof(one));
+ #else
+- return 0;
++ return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*) &one,
++ (ev_socklen_t)sizeof(one));
+ #endif
+ }
+
diff --git a/doc/REST-interface.md b/doc/REST-interface.md
index ac7cd45f70..bf669235e3 100644
--- a/doc/REST-interface.md
+++ b/doc/REST-interface.md
@@ -77,6 +77,20 @@ $ curl localhost:18332/rest/getutxos/checkmempool/b2cdfd7b89def827ff8af7cd9bff76
}
```
+####Memory pool
+`GET /rest/mempool/info.json`
+
+Returns various information about the TX mempool.
+Only supports JSON as output format.
+* size : (numeric) the number of transactions in the TX mempool
+* bytes : (numeric) size of the TX mempool in bytes
+* usage : (numeric) total TX mempool memory usage
+
+`GET /rest/mempool/contents.json`
+
+Returns transactions in the TX mempool.
+Only supports JSON as output format.
+
Risks
-------------
Running a web browser on the same node with a REST enabled bitcoind can be a risk. Accessing prepared XSS websites could read out tx/block data of your node by placing links like `<script src="http://127.0.0.1:8332/rest/tx/1234567890.json">` which might break the nodes privacy.
diff --git a/doc/assets-attribution.md b/doc/assets-attribution.md
index b5a033dd79..460c1f8e2e 100644
--- a/doc/assets-attribution.md
+++ b/doc/assets-attribution.md
@@ -11,28 +11,28 @@ The following is a list of assets used in the bitcoin source and their proper at
### Assets Used
src/qt/res/icons/add.png
- src/qt/res/icons/address-book.png,
+ src/qt/res/icons/address-book.png
src/qt/res/icons/configure.png
src/qt/res/icons/debugwindow.png
- src/qt/res/icons/edit.png,
- src/qt/res/icons/exitcopy.png
- src/qt/res/icons/editpaste.png,
+ src/qt/res/icons/edit.png
+ src/qt/res/icons/editcopy.png
+ src/qt/res/icons/editpaste.png
src/qt/res/icons/export.png
- src/qt/res/icons/eye.png,
+ src/qt/res/icons/eye.png
src/qt/res/icons/filesave.png
- src/qt/res/icons/history.png,
+ src/qt/res/icons/history.png
src/qt/res/icons/info.png
- src/qt/res/icons/key.png,
+ src/qt/res/icons/key.png
src/qt/res/icons/lock_*.png
- src/qt/res/icons/open.png,
+ src/qt/res/icons/open.png
src/qt/res/icons/overview.png
- src/qt/res/icons/quit.png,
+ src/qt/res/icons/quit.png
src/qt/res/icons/receive.png
- src/qt/res/icons/remove.png,
+ src/qt/res/icons/remove.png
src/qt/res/icons/send.png
- src/qt/res/icons/synced.png,
+ src/qt/res/icons/synced.png
src/qt/res/icons/transaction*.png
- src/qt/res/icons/tx_output.png,
+ src/qt/res/icons/tx_output.png
src/qt/res/icons/warning.png
Other
@@ -40,28 +40,31 @@ Other
### Info
* Designer: Jonas Schnelli, Bitboy, Stephen Hutchings, Marco Falke
-* Bitcoin Icon: Based on the original bitcoin logo from Bitboy
-* Network connection icon: Inspired by flow-merge.svg from Stephen Hutchings
-* Some icons are based on Stephan Hutchings Typicons
+* Bitcoin icon: Based on the original bitcoin logo from Bitboy
+* Network connection icons: Marco Falke, inspired by flow-merge.svg from Stephen Hutchings
+* Transaction-mined icon: Jonas Schnelli
+* Other icons are based on Stephan Hutchings Typicons
* License: MIT
### Assets Used
src/qt/res/icons/about.png
- src/qt/res/icons/about_qt.png,
+ src/qt/res/icons/about_qt.png
src/qt/res/icons/bitcoin.icns
- src/qt/res/icons/bitcoin.ico,
+ src/qt/res/icons/bitcoin.ico
src/qt/res/icons/bitcoin.png
- src/qt/res/icons/clock*.png,
+ src/qt/res/icons/clock*.png
src/qt/res/icons/connect*.png
- src/qt/res/icons/eye_minus.png,
+ src/qt/res/icons/eye_minus.png
src/qt/res/icons/eye_plus.png
- src/qt/res/icons/verify.png,
+ src/qt/res/icons/verify.png
src/qt/res/icons/tx_inout.png
- src/qt/res/icons/tx_input.png,
- src/qt/res/src/bitcoin.svg,
- src/qt/res/src/clock*.svg
- src/qt/res/src/connect*.svg,
+ src/qt/res/icons/tx_input.png
+ src/qt/res/icons/tx_mined.png
+ src/qt/res/src/bitcoin.svg
+ src/qt/res/src/clock_*.svg
+ src/qt/res/src/connect-*.svg
src/qt/res/src/mine.svg
src/qt/res/src/qt.svg
- src/qt/res/src/tx*.svg,
+ src/qt/res/src/tx_*.svg
+ src/qt/res/src/transaction0.svg
src/qt/res/src/verify.svg
diff --git a/doc/build-osx.md b/doc/build-osx.md
index dc319dd1c4..201fe9522b 100644
--- a/doc/build-osx.md
+++ b/doc/build-osx.md
@@ -32,7 +32,7 @@ Instructions: Homebrew
#### Install dependencies using Homebrew
- brew install autoconf automake berkeley-db4 libtool boost miniupnpc openssl pkg-config protobuf qt5
+ brew install autoconf automake berkeley-db4 libtool boost miniupnpc openssl pkg-config protobuf qt5 libevent
NOTE: Building with Qt4 is still supported, however, could result in a broken UI. As such, building with Qt5 is recommended.
@@ -70,7 +70,7 @@ Download Qt Creator from http://www.qt.io/download/. Download the "community edi
6. Confirm the "summary page"
7. In the "Projects" tab select "Manage Kits..."
8. Select the default "Desktop" kit and select "Clang (x86 64bit in /usr/bin)" as compiler
-9. Select LLDB as debugger (you might need to set the path to your installtion)
+9. Select LLDB as debugger (you might need to set the path to your installation)
10. Start debugging with Qt Creator
Creating a release build
diff --git a/doc/build-unix.md b/doc/build-unix.md
index 92aed7725e..e02a5e42f7 100644
--- a/doc/build-unix.md
+++ b/doc/build-unix.md
@@ -31,8 +31,9 @@ These dependencies are required:
Library | Purpose | Description
------------|------------------|----------------------
- libssl | SSL Support | Secure communications
- libboost | Boost | C++ Library
+ libssl | Crypto | Random Number Generation, Elliptic Curve Cryptography
+ libboost | Utility | Library for threading, data structures, etc
+ libevent | Networking | OS independent asynchronous networking
Optional dependencies:
@@ -57,7 +58,7 @@ Dependency Build Instructions: Ubuntu & Debian
----------------------------------------------
Build requirements:
- sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev
+ sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev libevent-dev
For Ubuntu 12.04 and later or Debian 7 and later libboost-all-dev has to be installed:
@@ -154,7 +155,8 @@ make install
# Configure Bitcoin Core to use our own-built instance of BDB
cd $BITCOIN_ROOT
-./configure (other args...) LDFLAGS="-L${BDB_PREFIX}/lib/" CPPFLAGS="-I${BDB_PREFIX}/include/"
+./autogen.sh
+./configure LDFLAGS="-L${BDB_PREFIX}/lib/" CPPFLAGS="-I${BDB_PREFIX}/include/" # (other args...)
```
**Note**: You only need Berkeley DB if the wallet is enabled (see the section *Disable-Wallet mode* below).
diff --git a/doc/files.md b/doc/files.md
index 80195535bb..c083bcb038 100644
--- a/doc/files.md
+++ b/doc/files.md
@@ -1,12 +1,17 @@
-Used in 0.8.0
----------------------
-* wallet.dat: personal wallet (BDB) with keys and transactions
-* peers.dat: peer IP address database (custom format); since 0.7.0
+
+* banlist.dat: stores the IPs/Subnets of banned nodes
+* bitcoin.conf: contains configuration settings for bitcoind or bitcoin-qt
+* bitcoind.pid: stores the process id of bitcoind while running
* blocks/blk000??.dat: block data (custom, 128 MiB per file); since 0.8.0
* blocks/rev000??.dat; block undo data (custom); since 0.8.0 (format changed since pre-0.8)
* blocks/index/*; block index (LevelDB); since 0.8.0
* chainstate/*; block chain state database (LevelDB); since 0.8.0
* database/*: BDB database environment; only used for wallet since 0.8.0
+* db.log: wallet database log file
+* debug.log: contains debug information and general logging generated by bitcoind or bitcoin-qt
+* fee_estimates.dat: stores statistics used to estimate minimum transaction fees and priorities required for confirmation; since 0.10.0
+* peers.dat: peer IP address database (custom format); since 0.7.0
+* wallet.dat: personal wallet (BDB) with keys and transactions
Only used in pre-0.8.0
---------------------
diff --git a/doc/gitian-building.md b/doc/gitian-building.md
index 630b3c04a7..169727adc0 100644
--- a/doc/gitian-building.md
+++ b/doc/gitian-building.md
@@ -5,7 +5,7 @@ Gitian building
Gitian is the deterministic build process that is used to build the Bitcoin
Core executables. It provides a way to be reasonably sure that the
-executables are really built from source on GitHub. It also makes sure that
+executables are really built from the source on GitHub. It also makes sure that
the same, tested dependencies are used and statically built into the executable.
Multiple developers build the source code by following a specific descriptor
@@ -13,8 +13,8 @@ Multiple developers build the source code by following a specific descriptor
These results are compared and only if they match, the build is accepted and uploaded
to bitcoin.org.
-More independent gitian builders are needed, which is why I wrote this
-guide. It is preferred to follow these steps yourself instead of using someone else's
+More independent gitian builders are needed, which is why this guide exists.
+It is preferred you follow these steps yourself instead of using someone else's
VM image to avoid 'contaminating' the build.
Table of Contents
@@ -39,46 +39,46 @@ This guide explains how to set up the environment, and how to start the builds.
Debian Linux was chosen as the host distribution because it has a lightweight install (in contrast to Ubuntu) and is readily available.
Any kind of virtualization can be used, for example:
-- [VirtualBox](https://www.virtualbox.org/), covered by this guide
+- [VirtualBox](https://www.virtualbox.org/) (covered by this guide)
- [KVM](http://www.linux-kvm.org/page/Main_Page)
- [LXC](https://linuxcontainers.org/), see also [Gitian host docker container](https://github.com/gdm85/tenku/tree/master/docker/gitian-bitcoin-host/README.md).
-You can also install on actual hardware instead of using virtualization.
+You can also install gitian on actual hardware instead of using virtualization.
Create a new VirtualBox VM
---------------------------
In the VirtualBox GUI click "Create" and choose the following parameters in the wizard:
-![](gitian-building/create_vm_page1.png)
+![](gitian-building/create_new_vm.png)
-- Type: Linux, Debian (64 bit)
+- Type: Linux, Debian (64-bit)
![](gitian-building/create_vm_memsize.png)
-- Memory Size: at least 1024MB, anything lower will really slow the build down
+- Memory Size: at least 1024MB, anything less will really slow down the build.
-![](gitian-building/create_vm_hard_drive.png)
+![](gitian-building/create_vm_hard_disk.png)
-- Hard Drive: Create a virtual hard drive now
+- Hard Disk: Create a virtual hard disk now
-![](gitian-building/create_vm_hard_drive_file_type.png)
+![](gitian-building/create_vm_hard_disk_file_type.png)
-- Hard Drive file type: Use the default, VDI (VirtualBox Disk Image)
+- Hard Disk file type: Use the default, VDI (VirtualBox Disk Image)
-![](gitian-building/create_vm_storage_physical_hard_drive.png)
+![](gitian-building/create_vm_storage_physical_hard_disk.png)
-- Storage on Physical hard drive: Dynamically Allocated
+- Storage on physical hard disk: Dynamically Allocated
![](gitian-building/create_vm_file_location_size.png)
-- Disk size: at least 40GB; as low as 20GB *may* be possible, but better to err on the safe side
-- Push the `Create` button
+- File location and size: at least 40GB; as low as 20GB *may* be possible, but better to err on the safe side
+- Click `Create`
-Get the [Debian 7.8 net installer](http://cdimage.debian.org/cdimage/archive/7.8.0/amd64/iso-cd/debian-7.8.0-amd64-netinst.iso) (a more recent minor version should also work, see also [Debian Network installation](https://www.debian.org/CD/netinst/)).
+Get the [Debian 8.1 net installer](http://cdimage.debian.org/debian-cd/8.1.0/amd64/iso-cd/debian-8.1.0-amd64-netinst.iso) (a more recent minor version should also work, see also [Debian Network installation](https://www.debian.org/CD/netinst/)).
This DVD image can be validated using a SHA256 hashing tool, for example on
Unixy OSes by entering the following in a terminal:
- echo "e39c36d6adc0fd86c6edb0e03e22919086c883b37ca194d063b8e3e8f6ff6a3a debian-7.8.0-amd64-netinst.iso" | sha256sum -c
+ echo "5d0a1f804d73aee73eee7efbb38456390558094fd19894a573f1514ca44347e0 debian-8.1.0-amd64-netinst.iso" | sha256sum -c
# (must return OK)
After creating the VM, we need to configure it.
@@ -115,8 +115,9 @@ This section will explain how to install Debian on the newly created VM.
![](gitian-building/debian_install_1_boot_menu.png)
-**Note**: Navigation in the Debian installer: To keep a setting at the default
-and proceed, just press `Enter`. To select a different button, press `Tab`.
+**Note**: Navigating in the Debian installer:
+To keep a setting at the default and proceed, just press `Enter`.
+To select a different button, press `Tab`.
- Choose locale and keyboard settings (doesn't matter, you can just go with the defaults or select your own information)
@@ -126,7 +127,7 @@ and proceed, just press `Enter`. To select a different button, press `Tab`.
- The VM will detect network settings using DHCP, this should all proceed automatically
- Configure the network:
- - System name `debian`.
+ - Hostname `debian`.
- Leave domain name empty.
![](gitian-building/debian_install_5_configure_the_network.png)
@@ -136,6 +137,7 @@ and proceed, just press `Enter`. To select a different button, press `Tab`.
![](gitian-building/debian_install_6a_set_up_root_password.png)
- Name the new user `debian` (the full name doesn't matter, you can leave it empty)
+- Set the account username as `debian`
![](gitian-building/debian_install_7_set_up_user_fullname.png)
![](gitian-building/debian_install_8_set_up_username.png)
@@ -158,13 +160,9 @@ and proceed, just press `Enter`. To select a different button, press `Tab`.
![](gitian-building/debian_install_12_choose_disk.png)
- - Partitioning scheme: All files in one partition
-
-![](gitian-building/debian_install_13_partition_scheme.png)
-
- Finish partitioning and write changes to disk -> *Yes* (`Tab`, `Enter` to select the `Yes` button)
-![](gitian-building/debian_install_14_finish.png)
+![](gitian-building/debian_install_14_finish.png)
![](gitian-building/debian_install_15_write_changes.png)
- The base system will be installed, this will take a minute or so
@@ -172,51 +170,79 @@ and proceed, just press `Enter`. To select a different button, press `Tab`.
![](gitian-building/debian_install_16_choose_a_mirror.png)
-- Enter proxy information (unless you are on an intranet, you can leave this empty)
+- Enter proxy information (unless you are on an intranet, leave this empty)
![](gitian-building/debian_install_18_proxy_settings.png)
- Wait a bit while 'Select and install software' runs
- Participate in popularity contest -> *No*
-- Choose software to install. We need just the base system.
+- Choose software to install. We need just the base system.
+- Make sure only 'SSH server' and 'Standard System Utilities' are checked
+- Uncheck 'Debian Desktop Environment' and 'Print Server'
![](gitian-building/debian_install_19_software_selection.png)
-- Make sure only 'SSH server' and 'Standard System Utilities' are checked
-- Uncheck 'Debian Desktop Environment' and 'Print Server'
+- Install the GRUB boot loader to the master boot record? -> Yes
![](gitian-building/debian_install_20_install_grub.png)
-- Install the GRUB boot loader to the master boot record? -> Yes
+- Device for boot loader installation -> ata-VBOX_HARDDISK
-![](gitian-building/debian_install_21_finish_installation.png)
+![](gitian-building/debian_install_21_install_grub_bootloader.png)
- Installation Complete -> *Continue*
- After installation, the VM will reboot and you will have a working Debian VM. Congratulations!
+![](gitian-building/debian_install_22_finish_installation.png)
+
+
+After Installation
+-------------------
+The next step in the guide involves logging in as root via SSH.
+SSH login for root users is disabled by default, so we'll enable that now.
+
+Login to the VM using username `root` and the root password you choose earlier.
+You'll be presented with a screen similar to this.
+
+![](gitian-building/debian_root_login.png)
+
+Type:
+
+```
+sed -i 's/^PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
+```
+and press enter. Then,
+```
+/etc/init.d/ssh restart
+```
+and enter to restart SSH. Logout by typing 'logout' and pressing 'enter'.
+
Connecting to the VM
----------------------
After the VM has booted you can connect to it using SSH, and files can be copied from and to the VM using a SFTP utility.
Connect to `localhost`, port `22222` (or the port configured when installing the VM).
-On Windows you can use putty[1] and WinSCP[2].
+On Windows you can use [putty](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html) and [WinSCP](http://winscp.net/eng/index.php).
-For example to connect as `root` from a Linux command prompt use
+For example, to connect as `root` from a Linux command prompt use
$ ssh root@localhost -p 22222
The authenticity of host '[localhost]:22222 ([127.0.0.1]:22222)' can't be established.
- ECDSA key fingerprint is 8e:71:f9:5b:62:46:de:44:01:da:fb:5f:34:b5:f2:18.
+ RSA key fingerprint is ae:f5:c8:9f:17:c6:c7:1b:c2:1b:12:31:1d:bb:d0:c7.
Are you sure you want to continue connecting (yes/no)? yes
- Warning: Permanently added '[localhost]:22222' (ECDSA) to the list of known hosts.
+ Warning: Permanently added '[localhost]:22222' (RSA) to the list of known hosts.
root@localhost's password: (enter root password configured during install)
- Linux debian 3.2.0-4-amd64 #1 SMP Debian 3.2.54-2 x86_64
+
+ The programs included with the Debian GNU/Linux system are free software;
+ the exact distribution terms for each program are described in the
+ individual files in /usr/share/doc/*/copyright.
+
+ Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
+ permitted by applicable law.
root@debian:~#
Replace `root` with `debian` to log in as user.
-[1] http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
-[2] http://winscp.net/eng/index.php
-
Setting up Debian for gitian building
--------------------------------------
@@ -226,13 +252,10 @@ First we need to log in as `root` to set up dependencies and make sure that our
user can use the sudo command. Type/paste the following in the terminal:
```bash
-apt-get install git ruby sudo apt-cacher-ng qemu-utils debootstrap lxc python-cheetah parted kpartx bridge-utils
+apt-get install git ruby sudo apt-cacher-ng qemu-utils debootstrap lxc python-cheetah parted kpartx bridge-utils make ubuntu-archive-keyring
adduser debian sudo
```
-When you get a colorful screen with a question about the 'LXC directory', just
-go with the default (`/var/lib/lxc`).
-
Then set up LXC and the rest with the following, which is a complex jumble of settings and workarounds:
```bash
@@ -255,7 +278,7 @@ reboot
```
At the end the VM is rebooted to make sure that the changes take effect. The steps in this
-section need only to be performed once.
+section only need to be performed once.
Installing gitian
------------------
@@ -300,26 +323,26 @@ cd gitian-builder
bin/make-base-vm --lxc --arch amd64 --suite precise
```
-There will be a lot of warnings printed during build of the image. These can be ignored.
+There will be a lot of warnings printed during the build of the image. These can be ignored.
**Note**: When sudo asks for a password, enter the password for the user *debian* not for *root*.
Getting and building the inputs
--------------------------------
-Follow the instructions in [doc/release-process.md](release-process.md) in the bitcoin repository
-under 'Fetch and build inputs' to install sources which require manual intervention. Also follow
-the next step: 'Seed the Gitian sources cache', which will fetch all necessary source files allowing
-for gitian to work offline.
+Follow the instructions in [doc/release-process.md](release-process.md#fetch-and-build-inputs-first-time-or-when-dependency-versions-change)
+in the bitcoin repository to install sources which require manual intervention. Also follow
+the next step: 'Seed the Gitian sources cache', which will fetch all the necessary source
+files to allow gitian to work offline.
Building Bitcoin
----------------
To build Bitcoin (for Linux, OSX and Windows) just follow the steps under 'perform
-gitian builds' in [doc/release-process.md](release-process.md) in the bitcoin repository.
+gitian builds' in [doc/release-process.md](release-process.md#perform-gitian-builds) in the bitcoin repository.
-This may take a long time as it also builds the dependencies needed for each descriptor.
-These dependencies will be cached after a successful build to avoid rebuilding them where possible.
+This may take some time as it will build all the dependencies needed for each descriptor.
+These dependencies will be cached after a successful build to avoid rebuilding them when possible.
At any time you can check the package installation and build progress with
@@ -331,13 +354,13 @@ tail -f var/build.log
Output from `gbuild` will look something like
Initialized empty Git repository in /home/debian/gitian-builder/inputs/bitcoin/.git/
- remote: Reusing existing pack: 35606, done.
- remote: Total 35606 (delta 0), reused 0 (delta 0)
- Receiving objects: 100% (35606/35606), 26.52 MiB | 4.28 MiB/s, done.
- Resolving deltas: 100% (25724/25724), done.
+ remote: Counting objects: 57959, done.
+ remote: Total 57959 (delta 0), reused 0 (delta 0), pack-reused 57958
+ Receiving objects: 100% (57959/57959), 53.76 MiB | 484.00 KiB/s, done.
+ Resolving deltas: 100% (41590/41590), done.
From https://github.com/bitcoin/bitcoin
... (new tags, new branch etc)
- --- Building for precise x86_64 ---
+ --- Building for precise amd64 ---
Stopping target if it is up
Making a new image copy
stdin: is not a tty
diff --git a/doc/gitian-building/create_new_vm.png b/doc/gitian-building/create_new_vm.png
new file mode 100644
index 0000000000..dd22428e17
--- /dev/null
+++ b/doc/gitian-building/create_new_vm.png
Binary files differ
diff --git a/doc/gitian-building/create_vm_hard_disk.png b/doc/gitian-building/create_vm_hard_disk.png
new file mode 100644
index 0000000000..8e29816fab
--- /dev/null
+++ b/doc/gitian-building/create_vm_hard_disk.png
Binary files differ
diff --git a/doc/gitian-building/create_vm_hard_disk_file_type.png b/doc/gitian-building/create_vm_hard_disk_file_type.png
new file mode 100644
index 0000000000..a157211cf5
--- /dev/null
+++ b/doc/gitian-building/create_vm_hard_disk_file_type.png
Binary files differ
diff --git a/doc/gitian-building/create_vm_hard_drive.png b/doc/gitian-building/create_vm_hard_drive.png
deleted file mode 100644
index a1706e14fd..0000000000
--- a/doc/gitian-building/create_vm_hard_drive.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitian-building/create_vm_hard_drive_file_type.png b/doc/gitian-building/create_vm_hard_drive_file_type.png
deleted file mode 100644
index 251b8ee3e2..0000000000
--- a/doc/gitian-building/create_vm_hard_drive_file_type.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitian-building/create_vm_memsize.png b/doc/gitian-building/create_vm_memsize.png
index 33717867a5..5abfee5337 100644
--- a/doc/gitian-building/create_vm_memsize.png
+++ b/doc/gitian-building/create_vm_memsize.png
Binary files differ
diff --git a/doc/gitian-building/create_vm_page1.png b/doc/gitian-building/create_vm_page1.png
deleted file mode 100644
index edaebc6223..0000000000
--- a/doc/gitian-building/create_vm_page1.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitian-building/create_vm_storage_physical_hard_disk.png b/doc/gitian-building/create_vm_storage_physical_hard_disk.png
new file mode 100644
index 0000000000..cee16a6c63
--- /dev/null
+++ b/doc/gitian-building/create_vm_storage_physical_hard_disk.png
Binary files differ
diff --git a/doc/gitian-building/create_vm_storage_physical_hard_drive.png b/doc/gitian-building/create_vm_storage_physical_hard_drive.png
deleted file mode 100644
index 987efaa40c..0000000000
--- a/doc/gitian-building/create_vm_storage_physical_hard_drive.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitian-building/debian_install_10_configure_clock.png b/doc/gitian-building/debian_install_10_configure_clock.png
index 467c79018e..7cda038ae4 100644
--- a/doc/gitian-building/debian_install_10_configure_clock.png
+++ b/doc/gitian-building/debian_install_10_configure_clock.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_11_partition_disks.png b/doc/gitian-building/debian_install_11_partition_disks.png
index 18110734df..2a648c517f 100644
--- a/doc/gitian-building/debian_install_11_partition_disks.png
+++ b/doc/gitian-building/debian_install_11_partition_disks.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_12_choose_disk.png b/doc/gitian-building/debian_install_12_choose_disk.png
index a00d4abf17..0f3acc498e 100644
--- a/doc/gitian-building/debian_install_12_choose_disk.png
+++ b/doc/gitian-building/debian_install_12_choose_disk.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_13_partition_scheme.png b/doc/gitian-building/debian_install_13_partition_scheme.png
deleted file mode 100644
index 2f80f19b63..0000000000
--- a/doc/gitian-building/debian_install_13_partition_scheme.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitian-building/debian_install_14_finish.png b/doc/gitian-building/debian_install_14_finish.png
index 411d457e95..c8ef0b37ad 100644
--- a/doc/gitian-building/debian_install_14_finish.png
+++ b/doc/gitian-building/debian_install_14_finish.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_15_write_changes.png b/doc/gitian-building/debian_install_15_write_changes.png
index f26093982c..d8de00dec6 100644
--- a/doc/gitian-building/debian_install_15_write_changes.png
+++ b/doc/gitian-building/debian_install_15_write_changes.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_16_choose_a_mirror.png b/doc/gitian-building/debian_install_16_choose_a_mirror.png
index d2c2e9523b..0bd985b38c 100644
--- a/doc/gitian-building/debian_install_16_choose_a_mirror.png
+++ b/doc/gitian-building/debian_install_16_choose_a_mirror.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_17_choose_a_mirror2.png b/doc/gitian-building/debian_install_17_choose_a_mirror2.png
deleted file mode 100644
index cef2db0781..0000000000
--- a/doc/gitian-building/debian_install_17_choose_a_mirror2.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitian-building/debian_install_18_proxy_settings.png b/doc/gitian-building/debian_install_18_proxy_settings.png
index 24ba25c109..2c19919f64 100644
--- a/doc/gitian-building/debian_install_18_proxy_settings.png
+++ b/doc/gitian-building/debian_install_18_proxy_settings.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_19_software_selection.png b/doc/gitian-building/debian_install_19_software_selection.png
index d462757aff..5430456b14 100644
--- a/doc/gitian-building/debian_install_19_software_selection.png
+++ b/doc/gitian-building/debian_install_19_software_selection.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_1_boot_menu.png b/doc/gitian-building/debian_install_1_boot_menu.png
index 27fd849b4f..216502e1c6 100644
--- a/doc/gitian-building/debian_install_1_boot_menu.png
+++ b/doc/gitian-building/debian_install_1_boot_menu.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_20_install_grub.png b/doc/gitian-building/debian_install_20_install_grub.png
index de4f9be0c9..d853c15871 100644
--- a/doc/gitian-building/debian_install_20_install_grub.png
+++ b/doc/gitian-building/debian_install_20_install_grub.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_21_install_grub_bootloader.png b/doc/gitian-building/debian_install_21_install_grub_bootloader.png
new file mode 100644
index 0000000000..493ab806a6
--- /dev/null
+++ b/doc/gitian-building/debian_install_21_install_grub_bootloader.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_21_finish_installation.png b/doc/gitian-building/debian_install_22_finish_installation.png
index b967c3550d..7c4445585b 100644
--- a/doc/gitian-building/debian_install_21_finish_installation.png
+++ b/doc/gitian-building/debian_install_22_finish_installation.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_2_select_a_language.png b/doc/gitian-building/debian_install_2_select_a_language.png
index 1c9e0bcfc1..0228ae2c01 100644
--- a/doc/gitian-building/debian_install_2_select_a_language.png
+++ b/doc/gitian-building/debian_install_2_select_a_language.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_3_select_location.png b/doc/gitian-building/debian_install_3_select_location.png
index 005c395656..7b18fba975 100644
--- a/doc/gitian-building/debian_install_3_select_location.png
+++ b/doc/gitian-building/debian_install_3_select_location.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_4_configure_keyboard.png b/doc/gitian-building/debian_install_4_configure_keyboard.png
index 580c8af7c5..8e46117de4 100644
--- a/doc/gitian-building/debian_install_4_configure_keyboard.png
+++ b/doc/gitian-building/debian_install_4_configure_keyboard.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_5_configure_the_network.png b/doc/gitian-building/debian_install_5_configure_the_network.png
index a7fdffc66b..8e3720f243 100644
--- a/doc/gitian-building/debian_install_5_configure_the_network.png
+++ b/doc/gitian-building/debian_install_5_configure_the_network.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_6a_set_up_root_password.png b/doc/gitian-building/debian_install_6a_set_up_root_password.png
index 31bd210f38..dcade11967 100644
--- a/doc/gitian-building/debian_install_6a_set_up_root_password.png
+++ b/doc/gitian-building/debian_install_6a_set_up_root_password.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_7_set_up_user_fullname.png b/doc/gitian-building/debian_install_7_set_up_user_fullname.png
index bffc6ccd7a..6763c6e08a 100644
--- a/doc/gitian-building/debian_install_7_set_up_user_fullname.png
+++ b/doc/gitian-building/debian_install_7_set_up_user_fullname.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_8_set_up_username.png b/doc/gitian-building/debian_install_8_set_up_username.png
index 9e2750ad4e..bb04de96d2 100644
--- a/doc/gitian-building/debian_install_8_set_up_username.png
+++ b/doc/gitian-building/debian_install_8_set_up_username.png
Binary files differ
diff --git a/doc/gitian-building/debian_install_9_user_password.png b/doc/gitian-building/debian_install_9_user_password.png
index a26d30cba5..981f1181d7 100644
--- a/doc/gitian-building/debian_install_9_user_password.png
+++ b/doc/gitian-building/debian_install_9_user_password.png
Binary files differ
diff --git a/doc/gitian-building/debian_root_login.png b/doc/gitian-building/debian_root_login.png
new file mode 100644
index 0000000000..14cdd5ba5b
--- /dev/null
+++ b/doc/gitian-building/debian_root_login.png
Binary files differ
diff --git a/doc/gitian-building/network_settings.png b/doc/gitian-building/network_settings.png
index 1d9b6428a7..9e714fd154 100644
--- a/doc/gitian-building/network_settings.png
+++ b/doc/gitian-building/network_settings.png
Binary files differ
diff --git a/doc/gitian-building/port_forwarding_rules.png b/doc/gitian-building/port_forwarding_rules.png
index e45c9efffc..9e1fa2af20 100644
--- a/doc/gitian-building/port_forwarding_rules.png
+++ b/doc/gitian-building/port_forwarding_rules.png
Binary files differ
diff --git a/doc/gitian-building/select_startup_disk.png b/doc/gitian-building/select_startup_disk.png
index 729b368fd1..5acdc3fe10 100644
--- a/doc/gitian-building/select_startup_disk.png
+++ b/doc/gitian-building/select_startup_disk.png
Binary files differ
diff --git a/doc/init.md b/doc/init.md
index 1f206a6c02..ed9ce72154 100644
--- a/doc/init.md
+++ b/doc/init.md
@@ -29,28 +29,32 @@ file, however it is recommended that a strong and secure password be used
as this password is security critical to securing the wallet should the
wallet be enabled.
-If bitcoind is run with "-daemon" flag, and no rpcpassword is set, it will
-print a randomly generated suitable password to stderr. You can also
-generate one from the shell yourself like this:
+If bitcoind is run with the "-server" flag (set by default), and no rpcpassword is set,
+it will use a special cookie file for authentication. The cookie is generated with random
+content when the daemon starts, and deleted when it exits. Read access to this file
+controls who can access it through RPC.
-bash -c 'tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo'
+By default the cookie is stored in the data directory, but it's location can be overridden
+with the option '-rpccookiefile'.
-Once you have a password in hand, set rpcpassword= in /etc/bitcoin/bitcoin.conf
+This allows for running bitcoind without having to do any manual configuration.
+
+`conf`, `pid`, and `wallet` accept relative paths which are interpreted as
+relative to the data directory. `wallet` *only* supports relative paths.
For an example configuration file that describes the configuration settings,
-see contrib/debian/examples/bitcoin.conf.
+see `contrib/debian/examples/bitcoin.conf`.
3. Paths
---------------------------------
All three configurations assume several paths that might need to be adjusted.
-Binary: /usr/bin/bitcoind
-Configuration file: /etc/bitcoin/bitcoin.conf
-Data directory: /var/lib/bitcoind
-PID file: /var/run/bitcoind/bitcoind.pid (OpenRC and Upstart)
- /var/lib/bitcoind/bitcoind.pid (systemd)
-Lock file: /var/lock/subsys/bitcoind (CentOS)
+Binary: `/usr/bin/bitcoind`
+Configuration file: `/etc/bitcoin/bitcoin.conf`
+Data directory: `/var/lib/bitcoind`
+PID file: `/var/run/bitcoind/bitcoind.pid` (OpenRC and Upstart) or `/var/lib/bitcoind/bitcoind.pid` (systemd)
+Lock file: `/var/lock/subsys/bitcoind` (CentOS)
The configuration file, PID directory (if applicable) and data directory
should all be owned by the bitcoin user and group. It is advised for security
@@ -65,21 +69,21 @@ can then be controlled by group membership.
Installing this .service file consists of just copying it to
/usr/lib/systemd/system directory, followed by the command
-"systemctl daemon-reload" in order to update running systemd configuration.
+`systemctl daemon-reload` in order to update running systemd configuration.
-To test, run "systemctl start bitcoind" and to enable for system startup run
-"systemctl enable bitcoind"
+To test, run `systemctl start bitcoind` and to enable for system startup run
+`systemctl enable bitcoind`
4b) OpenRC
Rename bitcoind.openrc to bitcoind and drop it in /etc/init.d. Double
check ownership and permissions and make it executable. Test it with
-"/etc/init.d/bitcoind start" and configure it to run on startup with
-"rc-update add bitcoind"
+`/etc/init.d/bitcoind start` and configure it to run on startup with
+`rc-update add bitcoind`
4c) Upstart (for Debian/Ubuntu based distributions)
-Drop bitcoind.conf in /etc/init. Test by running "service bitcoind start"
+Drop bitcoind.conf in /etc/init. Test by running `service bitcoind start`
it will automatically start on reboot.
NOTE: This script is incompatible with CentOS 5 and Amazon Linux 2014 as they
@@ -87,7 +91,7 @@ use old versions of Upstart and do not supply the start-stop-daemon utility.
4d) CentOS
-Copy bitcoind.init to /etc/init.d/bitcoind. Test by running "service bitcoind start".
+Copy bitcoind.init to /etc/init.d/bitcoind. Test by running `service bitcoind start`.
Using this script, you can adjust the path and flags to the bitcoind program by
setting the BITCOIND and FLAGS environment variables in the file
@@ -99,4 +103,3 @@ setting the BITCOIND and FLAGS environment variables in the file
Auto respawning is currently only configured for Upstart and systemd.
Reasonable defaults have been chosen but YMMV.
-
diff --git a/doc/release-notes.md b/doc/release-notes.md
index 9caac4f7ae..e61933ddb2 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -4,6 +4,59 @@ release-notes at release time)
Notable changes
===============
+SSL support for RPC dropped
+----------------------------
+
+SSL support for RPC, previously enabled by the option `rpcssl` has been dropped
+from both the client and the server. This was done in preparation for removing
+the dependency on OpenSSL for the daemon completely.
+
+Trying to use `rpcssl` will result in an error:
+
+ Error: SSL mode for RPC (-rpcssl) is no longer supported.
+
+If you are one of the few people that relies on this feature, a flexible
+migration path is to use `stunnel`. This is an utility that can tunnel
+arbitrary TCP connections inside SSL. On e.g. Ubuntu it can be installed with:
+
+ sudo apt-get install stunnel4
+
+Then, to tunnel a SSL connection on 28332 to a RPC server bound on localhost on port 18332 do:
+
+ stunnel -d 28332 -r 127.0.0.1:18332 -p stunnel.pem -P ''
+
+It can also be set up system-wide in inetd style.
+
+Another way to re-attain SSL would be to setup a httpd reverse proxy. This solution
+would allow the use of different authentication, loadbalancing, on-the-fly compression and
+caching. A sample config for apache2 could look like:
+
+ Listen 443
+
+ NameVirtualHost *:443
+ <VirtualHost *:443>
+
+ SSLEngine On
+ SSLCertificateFile /etc/apache2/ssl/server.crt
+ SSLCertificateKeyFile /etc/apache2/ssl/server.key
+
+ <Location /bitcoinrpc>
+ ProxyPass http://127.0.0.1:8332/
+ ProxyPassReverse http://127.0.0.1:8332/
+ # optional enable digest auth
+ # AuthType Digest
+ # ...
+
+ # optional bypass bitcoind rpc basic auth
+ # RequestHeader set Authorization "Basic <hash>"
+ # get the <hash> from the shell with: base64 <<< bitcoinrpc:<password>
+ </Location>
+
+ # Or, balance the load:
+ # ProxyPass / balancer://balancer_cluster_name
+
+ </VirtualHost>
+
Random-cookie RPC authentication
---------------------------------
diff --git a/doc/tor.md b/doc/tor.md
index 560f71fa27..f8b94d19d1 100644
--- a/doc/tor.md
+++ b/doc/tor.md
@@ -70,9 +70,14 @@ In a typical situation, where you're only reachable via Tor, this should suffice
./bitcoind -proxy=127.0.0.1:9050 -externalip=57qr3yd1nyntf5k.onion -listen
-(obviously, replace the Onion address with your own). If you don't care too much
-about hiding your node, and want to be reachable on IPv4 as well, additionally
-specify:
+(obviously, replace the Onion address with your own). It should be noted that you still
+listen on all devices and another node could establish a clearnet connection, when knowing
+your address. To mitigate this, additionally bind the address of your Tor proxy:
+
+ ./bitcoind ... -bind=127.0.0.1
+
+If you don't care too much about hiding your node, and want to be reachable on IPv4
+as well, use `discover` instead:
./bitcoind ... -discover
diff --git a/doc/zmq.md b/doc/zmq.md
new file mode 100644
index 0000000000..fd04f6d9f0
--- /dev/null
+++ b/doc/zmq.md
@@ -0,0 +1,98 @@
+# Block and Transaction Broadcasting With ZeroMQ
+
+[ZeroMQ](http://zeromq.org/) is a lightweight wrapper around TCP
+connections, inter-process communications, and shared-memory,
+providing various message-oriented semantics such as publish/subcribe,
+request/reply, and push/pull.
+
+The Bitcoin Core daemon can be configured to act as a trusted "border
+router", implementing the bitcoin wire protocol and relay, making
+consensus decisions, maintaining the local blockchain database,
+broadcasting locally generated transactions into the network, and
+providing a queryable RPC interface to interact on a polled basis for
+requesting blockchain related data. However, there exists only a
+limited service to notify external software of events like the arrival
+of new blocks or transactions.
+
+The ZeroMQ facility implements a notification interface through a
+set of specific notifiers. Currently there are notifiers that publish
+blocks and transactions. This read-only facility requires only the
+connection of a corresponding ZeroMQ subscriber port in receiving
+software; it is not authenticated nor is there any two-way protocol
+involvement. Therefore, subscribers should validate the received data
+since it may be out of date, incomplete or even invalid.
+
+ZeroMQ sockets are self-connecting and self-healing; that is, connects
+made between two endpoints will be automatically restored after an
+outage, and either end may be freely started or stopped in any order.
+
+Because ZeroMQ is message oriented, subscribers receive transactions
+and blocks all-at-once and do not need to implement any sort of
+buffering or reassembly.
+
+## Prerequisites
+
+The ZeroMQ feature in Bitcoin Core uses only a very small part of the
+ZeroMQ C API, and is thus compatible with any version of ZeroMQ
+from 2.1 onward, including all versions in the 3.x and 4.x release
+series. Typically, it is packaged by distributions as something like
+*libzmq-dev*.
+
+The C++ wrapper for ZeroMQ is *not* needed.
+
+## Enabling
+
+By default, the ZeroMQ port functionality is enabled. Two steps are
+required to enable--compiling in the ZeroMQ code, and configuring
+runtime operation on the command-line or configuration file.
+
+ $ ./configure --enable-zmq (other options)
+
+This will produce a binary that is capable of providing the ZeroMQ
+facility, but will not do so until also configured properly.
+
+## Usage
+
+Currently, the following notifications are supported:
+
+ -zmqpubhashtx=address
+ -zmqpubhashblock=address
+ -zmqpubrawblock=address
+ -zmqpubrawtx=address
+
+The socket type is PUB and the address must be a valid ZeroMQ
+socket address. The same address can be used in more than one notification.
+
+For instance:
+
+ $ bitcoind -zmqpubhashtx=tcp://127.0.0.1:28332 -zmqpubrawtx=ipc:///tmp/bitcoind.tx.raw
+
+Each PUB notification has a topic and body, where the header
+corresponds to the notification type. For instance, for the notification
+`-zmqpubhashtx` the topic is `hashtx` (no null terminator) and the body is the
+hexadecimal transaction hash (32 bytes).
+
+These options can also be provided in bitcoin.conf.
+
+ZeroMQ endpoint specifiers for TCP (and others) are documented in the
+[ZeroMQ API](http://api.zeromq.org).
+
+Client side, then, the ZeroMQ subscriber socket must have the
+ZMQ_SUBSCRIBE option set to one or either of these prefixes (for instance, just `hash`); without
+doing so will result in no messages arriving. Please see `contrib/zmq/zmq_sub.py`
+for a working example.
+
+## Remarks
+
+From the perspective of bitcoind, the ZeroMQ socket is write-only; PUB
+sockets don't even have a read function. Thus, there is no state
+introduced into bitcoind directly. Furthermore, no information is
+broadcast that wasn't already received from the public P2P network.
+
+No authentication or authorization is done on connecting clients; it
+is assumed that the ZeroMQ port is exposed only to trusted entities,
+using other means such as firewalling.
+
+Note that when the block chain tip changes, a reorganisation may occur and just
+the tip will be notified. It is up to the subscriber to retrieve the chain
+from the last known block to the new tip.
diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh
index 72a282bc00..c6181c48ca 100755
--- a/qa/pull-tester/rpc-tests.sh
+++ b/qa/pull-tester/rpc-tests.sh
@@ -5,8 +5,8 @@ CURDIR=$(cd $(dirname "$0"); pwd)
# Get BUILDDIR and REAL_BITCOIND
. "${CURDIR}/tests-config.sh"
-export BITCOINCLI=${BUILDDIR}/qa/pull-tester/run-bitcoin-cli
export BITCOIND=${REAL_BITCOIND}
+export BITCOINCLI=${REAL_BITCOINCLI}
if [ "x${EXEEXT}" = "x.exe" ]; then
echo "Win tests currently disabled"
@@ -36,6 +36,7 @@ testScripts=(
'nodehandling.py'
'reindex.py'
'decodescript.py'
+ 'p2p-fullblocktest.py'
);
testScriptsExt=(
'bipdersig-p2p.py'
@@ -58,6 +59,10 @@ testScriptsExt=(
'p2p-acceptblock.py'
);
+if [ "x$ENABLE_ZMQ" = "x1" ]; then
+ testScripts=( ${testScripts[@]} 'zmq_test.py' )
+fi
+
extArg="-extended"
passOn=${@#$extArg}
diff --git a/qa/pull-tester/run-bitcoin-cli b/qa/pull-tester/run-bitcoin-cli
deleted file mode 100755
index 93c25bb9fc..0000000000
--- a/qa/pull-tester/run-bitcoin-cli
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-# This is a thin wrapper around bitcoin-cli that strips the Windows-style EOLs
-# from the output if present. It is necessary when using bitcoin-cli.exe on
-# Linux since shells will interpret the line-endings as part of the result.
-
-CURDIR=$(cd $(dirname "$0"); pwd)
-# Get BUILDDIR and REAL_BITCOIND
-
-# Grab the value of $REAL_BITCOINCLI which may be bitcoin-cli.exe.
-. "${CURDIR}/tests-config.sh"
-
-"${REAL_BITCOINCLI}" "$@" | sed 's/\r//'
diff --git a/qa/pull-tester/tests-config.sh.in b/qa/pull-tester/tests-config.sh.in
index 10f4d33e47..e881a95110 100755
--- a/qa/pull-tester/tests-config.sh.in
+++ b/qa/pull-tester/tests-config.sh.in
@@ -10,6 +10,7 @@ EXEEXT="@EXEEXT@"
@ENABLE_WALLET_TRUE@ENABLE_WALLET=1
@BUILD_BITCOIN_UTILS_TRUE@ENABLE_UTILS=1
@BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=1
+@ENABLE_ZMQ_TRUE@ENABLE_ZMQ=1
REAL_BITCOIND="$BUILDDIR/src/bitcoind${EXEEXT}"
REAL_BITCOINCLI="$BUILDDIR/src/bitcoin-cli${EXEEXT}"
diff --git a/qa/rpc-tests/README.md b/qa/rpc-tests/README.md
index cfda8fe91f..c6d1721282 100644
--- a/qa/rpc-tests/README.md
+++ b/qa/rpc-tests/README.md
@@ -1,5 +1,5 @@
-Regression tests of RPC interface
-=================================
+Regression tests
+================
### [python-bitcoinrpc](https://github.com/jgarzik/python-bitcoinrpc)
Git subtree of [https://github.com/jgarzik/python-bitcoinrpc](https://github.com/jgarzik/python-bitcoinrpc).
@@ -12,6 +12,28 @@ Base class for new regression tests.
### [test_framework/util.py](test_framework/util.py)
Generally useful functions.
+### [test_framework/mininode.py](test_framework/mininode.py)
+Basic code to support p2p connectivity to a bitcoind.
+
+### [test_framework/comptool.py](test_framework/comptool.py)
+Framework for comparison-tool style, p2p tests.
+
+### [test_framework/script.py](test_framework/script.py)
+Utilities for manipulating transaction scripts (originally from python-bitcoinlib)
+
+### [test_framework/blockstore.py](test_framework/blockstore.py)
+Implements disk-backed block and tx storage.
+
+### [test_framework/key.py](test_framework/key.py)
+Wrapper around OpenSSL EC_Key (originally from python-bitcoinlib)
+
+### [test_framework/bignum.py](test_framework/bignum.py)
+Helpers for script.py
+
+### [test_framework/blocktools.py](test_framework/blocktools.py)
+Helper functions for creating blocks and transactions.
+
+
Notes
=====
@@ -49,3 +71,82 @@ to recover with:
rm -rf cache
killall bitcoind
```
+
+P2P test design notes
+---------------------
+
+## Mininode
+
+* ```mininode.py``` contains all the definitions for objects that pass
+over the network (```CBlock```, ```CTransaction```, etc, along with the network-level
+wrappers for them, ```msg_block```, ```msg_tx```, etc).
+
+* P2P tests have two threads. One thread handles all network communication
+with the bitcoind(s) being tested (using python's asyncore package); the other
+implements the test logic.
+
+* ```NodeConn``` is the class used to connect to a bitcoind. If you implement
+a callback class that derives from ```NodeConnCB``` and pass that to the
+```NodeConn``` object, your code will receive the appropriate callbacks when
+events of interest arrive. NOTE: be sure to call
+```self.create_callback_map()``` in your derived classes' ```__init__```
+function, so that the correct mappings are set up between p2p messages and your
+callback functions.
+
+* You can pass the same handler to multiple ```NodeConn```'s if you like, or pass
+different ones to each -- whatever makes the most sense for your test.
+
+* Call ```NetworkThread.start()``` after all ```NodeConn``` objects are created to
+start the networking thread. (Continue with the test logic in your existing
+thread.)
+
+* RPC calls are available in p2p tests.
+
+* Can be used to write free-form tests, where specific p2p-protocol behavior
+is tested. Examples: ```p2p-accept-block.py```, ```maxblocksinflight.py```.
+
+## Comptool
+
+* Testing framework for writing tests that compare the block/tx acceptance
+behavior of a bitcoind against 1 or more other bitcoind instances, or against
+known outcomes, or both.
+
+* Set the ```num_nodes``` variable (defined in ```ComparisonTestFramework```) to start up
+1 or more nodes. If using 1 node, then ```--testbinary``` can be used as a command line
+option to change the bitcoind binary used by the test. If using 2 or more nodes,
+then ```--refbinary``` can be optionally used to change the bitcoind that will be used
+on nodes 2 and up.
+
+* Implement a (generator) function called ```get_tests()``` which yields ```TestInstance```s.
+Each ```TestInstance``` consists of:
+ - a list of ```[object, outcome, hash]``` entries
+ * ```object``` is a ```CBlock```, ```CTransaction```, or
+ ```CBlockHeader```. ```CBlock```'s and ```CTransaction```'s are tested for
+ acceptance. ```CBlockHeader```s can be used so that the test runner can deliver
+ complete headers-chains when requested from the bitcoind, to allow writing
+ tests where blocks can be delivered out of order but still processed by
+ headers-first bitcoind's.
+ * ```outcome``` is ```True```, ```False```, or ```None```. If ```True```
+ or ```False```, the tip is compared with the expected tip -- either the
+ block passed in, or the hash specified as the optional 3rd entry. If
+ ```None``` is specified, then the test will compare all the bitcoind's
+ being tested to see if they all agree on what the best tip is.
+ * ```hash``` is the block hash of the tip to compare against. Optional to
+ specify; if left out then the hash of the block passed in will be used as
+ the expected tip. This allows for specifying an expected tip while testing
+ the handling of either invalid blocks or blocks delivered out of order,
+ which complete a longer chain.
+ - ```sync_every_block```: ```True/False```. If ```False```, then all blocks
+ are inv'ed together, and the test runner waits until the node receives the
+ last one, and tests only the last block for tip acceptance using the
+ outcome and specified tip. If ```True```, then each block is tested in
+ sequence and synced (this is slower when processing many blocks).
+ - ```sync_every_transaction```: ```True/False```. Analogous to
+ ```sync_every_block```, except if the outcome on the last tx is "None",
+ then the contents of the entire mempool are compared across all bitcoind
+ connections. If ```True``` or ```False```, then only the last tx's
+ acceptance is tested against the given outcome.
+
+* For examples of tests written in this framework, see
+ ```invalidblockrequest.py``` and ```p2p-fullblocktest.py```.
+
diff --git a/qa/rpc-tests/bipdersig-p2p.py b/qa/rpc-tests/bipdersig-p2p.py
index 41717377b2..ec1678cc2c 100755
--- a/qa/rpc-tests/bipdersig-p2p.py
+++ b/qa/rpc-tests/bipdersig-p2p.py
@@ -75,6 +75,7 @@ class BIP66Test(ComparisonTestFramework):
def get_tests(self):
self.coinbase_blocks = self.nodes[0].generate(2)
+ height = 3 # height of the next block to build
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
self.nodeaddress = self.nodes[0].getnewaddress()
self.last_block_time = time.time()
@@ -82,25 +83,27 @@ class BIP66Test(ComparisonTestFramework):
''' 98 more version 2 blocks '''
test_blocks = []
for i in xrange(98):
- block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 2
block.rehash()
block.solve()
test_blocks.append([block, True])
self.last_block_time += 1
self.tip = block.sha256
+ height += 1
yield TestInstance(test_blocks, sync_every_block=False)
''' Mine 749 version 3 blocks '''
test_blocks = []
for i in xrange(749):
- block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 3
block.rehash()
block.solve()
test_blocks.append([block, True])
self.last_block_time += 1
self.tip = block.sha256
+ height += 1
yield TestInstance(test_blocks, sync_every_block=False)
'''
@@ -112,7 +115,7 @@ class BIP66Test(ComparisonTestFramework):
unDERify(spendtx)
spendtx.rehash()
- block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 3
block.vtx.append(spendtx)
block.hashMerkleRoot = block.calc_merkle_root()
@@ -121,6 +124,7 @@ class BIP66Test(ComparisonTestFramework):
self.last_block_time += 1
self.tip = block.sha256
+ height += 1
yield TestInstance([[block, True]])
'''
@@ -132,7 +136,7 @@ class BIP66Test(ComparisonTestFramework):
unDERify(spendtx)
spendtx.rehash()
- block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 3
block.vtx.append(spendtx)
block.hashMerkleRoot = block.calc_merkle_root()
@@ -144,35 +148,38 @@ class BIP66Test(ComparisonTestFramework):
''' Mine 199 new version blocks on last valid tip '''
test_blocks = []
for i in xrange(199):
- block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 3
block.rehash()
block.solve()
test_blocks.append([block, True])
self.last_block_time += 1
self.tip = block.sha256
+ height += 1
yield TestInstance(test_blocks, sync_every_block=False)
''' Mine 1 old version block '''
- block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 2
block.rehash()
block.solve()
self.last_block_time += 1
self.tip = block.sha256
+ height += 1
yield TestInstance([[block, True]])
''' Mine 1 new version block '''
- block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 3
block.rehash()
block.solve()
self.last_block_time += 1
self.tip = block.sha256
+ height += 1
yield TestInstance([[block, True]])
''' Mine 1 old version block, should be invalid '''
- block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
block.nVersion = 2
block.rehash()
block.solve()
diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py
index ce52247b2e..fc29789218 100755
--- a/qa/rpc-tests/fundrawtransaction.py
+++ b/qa/rpc-tests/fundrawtransaction.py
@@ -13,14 +13,15 @@ class RawTransactionsTest(BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
- initialize_chain_clean(self.options.tmpdir, 3)
+ initialize_chain_clean(self.options.tmpdir, 4)
def setup_network(self, split=False):
- self.nodes = start_nodes(3, self.options.tmpdir)
+ self.nodes = start_nodes(4, self.options.tmpdir)
connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
+ connect_nodes_bi(self.nodes,0,3)
self.is_network_split=False
self.sync_all()
@@ -31,11 +32,20 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[2].generate(1)
self.sync_all()
- self.nodes[0].generate(101)
+ self.nodes[0].generate(121)
self.sync_all()
+
+ watchonly_address = self.nodes[0].getnewaddress()
+ watchonly_pubkey = self.nodes[0].validateaddress(watchonly_address)["pubkey"]
+ watchonly_amount = 200
+ self.nodes[3].importpubkey(watchonly_pubkey, "", True)
+ watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount)
+ self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10);
+
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5);
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0);
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);
+
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
@@ -428,11 +438,12 @@ class RawTransactionsTest(BitcoinTestFramework):
stop_nodes(self.nodes)
wait_bitcoinds()
- self.nodes = start_nodes(3, self.options.tmpdir)
+ self.nodes = start_nodes(4, self.options.tmpdir)
connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
+ connect_nodes_bi(self.nodes,0,3)
self.is_network_split=False
self.sync_all()
@@ -541,5 +552,45 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(len(dec_tx['vout']), 2) # one change output added
+ ##################################################
+ # test a fundrawtransaction using only watchonly #
+ ##################################################
+
+ inputs = []
+ outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2}
+ rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
+
+ result = self.nodes[3].fundrawtransaction(rawtx, True)
+ res_dec = self.nodes[0].decoderawtransaction(result["hex"])
+ assert_equal(len(res_dec["vin"]), 1)
+ assert_equal(res_dec["vin"][0]["txid"], watchonly_txid)
+
+ assert_equal("fee" in result.keys(), True)
+ assert_greater_than(result["changepos"], -1)
+
+ ###############################################################
+ # test fundrawtransaction using the entirety of watched funds #
+ ###############################################################
+
+ inputs = []
+ outputs = {self.nodes[2].getnewaddress() : watchonly_amount}
+ rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
+
+ result = self.nodes[3].fundrawtransaction(rawtx, True)
+ res_dec = self.nodes[0].decoderawtransaction(result["hex"])
+ assert_equal(len(res_dec["vin"]), 2)
+ assert(res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid)
+
+ assert_greater_than(result["fee"], 0)
+ assert_greater_than(result["changepos"], -1)
+ assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10)
+
+ signedtx = self.nodes[3].signrawtransaction(result["hex"])
+ assert(not signedtx["complete"])
+ signedtx = self.nodes[0].signrawtransaction(signedtx["hex"])
+ assert(signedtx["complete"])
+ self.nodes[0].sendrawtransaction(signedtx["hex"])
+
+
if __name__ == '__main__':
RawTransactionsTest().main()
diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py
index 8ccb821286..b66533543d 100755
--- a/qa/rpc-tests/httpbasics.py
+++ b/qa/rpc-tests/httpbasics.py
@@ -22,7 +22,7 @@ except ImportError:
class HTTPBasicsTest (BitcoinTestFramework):
def setup_nodes(self):
- return start_nodes(4, self.options.tmpdir, extra_args=[['-rpckeepalive=1'], ['-rpckeepalive=0'], [], []])
+ return start_nodes(4, self.options.tmpdir)
def run_test(self):
@@ -84,9 +84,8 @@ class HTTPBasicsTest (BitcoinTestFramework):
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
- assert_equal(conn.sock!=None, False) #connection must be closed because keep-alive was set to false
- #node2 (third node) is running with standard keep-alive parameters which means keep-alive is off
+ #node2 (third node) is running with standard keep-alive parameters which means keep-alive is on
urlNode2 = urlparse.urlparse(self.nodes[2].url)
authpair = urlNode2.username + ':' + urlNode2.password
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
diff --git a/qa/rpc-tests/invalidblockrequest.py b/qa/rpc-tests/invalidblockrequest.py
index 64b8e26395..6a7980cd45 100755
--- a/qa/rpc-tests/invalidblockrequest.py
+++ b/qa/rpc-tests/invalidblockrequest.py
@@ -46,12 +46,14 @@ class InvalidBlockRequestTest(ComparisonTestFramework):
'''
Create a new block with an anyone-can-spend coinbase
'''
- block = create_block(self.tip, create_coinbase(), self.block_time)
+ height = 1
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
self.block_time += 1
block.solve()
# Save the coinbase for later
self.block1 = block
self.tip = block.sha256
+ height += 1
yield TestInstance([[block, True]])
'''
@@ -59,11 +61,12 @@ class InvalidBlockRequestTest(ComparisonTestFramework):
'''
test = TestInstance(sync_every_block=False)
for i in xrange(100):
- block = create_block(self.tip, create_coinbase(), self.block_time)
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
block.solve()
self.tip = block.sha256
self.block_time += 1
test.blocks_and_transactions.append([block, True])
+ height += 1
yield test
'''
@@ -73,7 +76,7 @@ class InvalidBlockRequestTest(ComparisonTestFramework):
coinbase, spend of that spend). Duplicate the 3rd transaction to
leave merkle root and blockheader unchanged but invalidate the block.
'''
- block2 = create_block(self.tip, create_coinbase(), self.block_time)
+ block2 = create_block(self.tip, create_coinbase(height), self.block_time)
self.block_time += 1
# chr(81) is OP_TRUE
@@ -95,11 +98,12 @@ class InvalidBlockRequestTest(ComparisonTestFramework):
self.tip = block2.sha256
yield TestInstance([[block2, False], [block2_orig, True]])
+ height += 1
'''
Make sure that a totally screwed up block is not valid.
'''
- block3 = create_block(self.tip, create_coinbase(), self.block_time)
+ block3 = create_block(self.tip, create_coinbase(height), self.block_time)
self.block_time += 1
block3.vtx[0].vout[0].nValue = 100*100000000 # Too high!
block3.vtx[0].sha256=None
diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py
index aee29a596a..5a67220021 100755
--- a/qa/rpc-tests/keypool.py
+++ b/qa/rpc-tests/keypool.py
@@ -73,6 +73,21 @@ def run_test(nodes, tmpdir):
except JSONRPCException,e:
assert(e.error['code']==-12)
+ # refill keypool with three new addresses
+ nodes[0].walletpassphrase('test', 12000)
+ nodes[0].keypoolrefill(3)
+ nodes[0].walletlock()
+
+ # drain them by mining
+ nodes[0].generate(1)
+ nodes[0].generate(1)
+ nodes[0].generate(1)
+ nodes[0].generate(1)
+ try:
+ nodes[0].generate(1)
+ raise AssertionError('Keypool should be exhausted after three addesses')
+ except JSONRPCException,e:
+ assert(e.error['code']==-12)
def main():
import optparse
diff --git a/qa/rpc-tests/listtransactions.py b/qa/rpc-tests/listtransactions.py
index eeae2d2fa2..b30a6bc9d1 100755
--- a/qa/rpc-tests/listtransactions.py
+++ b/qa/rpc-tests/listtransactions.py
@@ -93,6 +93,16 @@ class ListTransactionsTest(BitcoinTestFramework):
{"category":"receive","amount":Decimal("0.44")},
{"txid":txid, "account" : "toself"} )
+ multisig = self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
+ self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
+ txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
+ self.nodes[1].generate(1)
+ self.sync_all()
+ assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0)
+ check_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True),
+ {"category":"receive","amount":Decimal("0.1")},
+ {"txid":txid, "account" : "watchonly"} )
+
if __name__ == '__main__':
ListTransactionsTest().main()
diff --git a/qa/rpc-tests/p2p-acceptblock.py b/qa/rpc-tests/p2p-acceptblock.py
index 83c03eeb78..700deab207 100755
--- a/qa/rpc-tests/p2p-acceptblock.py
+++ b/qa/rpc-tests/p2p-acceptblock.py
@@ -153,7 +153,7 @@ class AcceptBlockTest(BitcoinTestFramework):
blocks_h2 = [] # the height 2 blocks on each node's chain
block_time = time.time() + 1
for i in xrange(2):
- blocks_h2.append(create_block(tips[i], create_coinbase(), block_time))
+ blocks_h2.append(create_block(tips[i], create_coinbase(2), block_time))
blocks_h2[i].solve()
block_time += 1
test_node.send_message(msg_block(blocks_h2[0]))
@@ -167,7 +167,7 @@ class AcceptBlockTest(BitcoinTestFramework):
# 3. Send another block that builds on the original tip.
blocks_h2f = [] # Blocks at height 2 that fork off the main chain
for i in xrange(2):
- blocks_h2f.append(create_block(tips[i], create_coinbase(), blocks_h2[i].nTime+1))
+ blocks_h2f.append(create_block(tips[i], create_coinbase(2), blocks_h2[i].nTime+1))
blocks_h2f[i].solve()
test_node.send_message(msg_block(blocks_h2f[0]))
white_node.send_message(msg_block(blocks_h2f[1]))
@@ -186,7 +186,7 @@ class AcceptBlockTest(BitcoinTestFramework):
# 4. Now send another block that builds on the forking chain.
blocks_h3 = []
for i in xrange(2):
- blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(), blocks_h2f[i].nTime+1))
+ blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(3), blocks_h2f[i].nTime+1))
blocks_h3[i].solve()
test_node.send_message(msg_block(blocks_h3[0]))
white_node.send_message(msg_block(blocks_h3[1]))
@@ -217,7 +217,7 @@ class AcceptBlockTest(BitcoinTestFramework):
all_blocks = [] # node0's blocks
for j in xrange(2):
for i in xrange(288):
- next_block = create_block(tips[j].sha256, create_coinbase(), tips[j].nTime+1)
+ next_block = create_block(tips[j].sha256, create_coinbase(i + 4), tips[j].nTime+1)
next_block.solve()
if j==0:
test_node.send_message(msg_block(next_block))
diff --git a/qa/rpc-tests/p2p-fullblocktest.py b/qa/rpc-tests/p2p-fullblocktest.py
new file mode 100755
index 0000000000..9555940cec
--- /dev/null
+++ b/qa/rpc-tests/p2p-fullblocktest.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python2
+
+#
+# Distributed under the MIT/X11 software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import *
+from test_framework.comptool import TestManager, TestInstance
+from test_framework.mininode import *
+from test_framework.blocktools import *
+import logging
+import copy
+import time
+import numbers
+from test_framework.key import CECKey
+from test_framework.script import CScript, CScriptOp, SignatureHash, SIGHASH_ALL, OP_TRUE
+
+class PreviousSpendableOutput(object):
+ def __init__(self, tx = CTransaction(), n = -1):
+ self.tx = tx
+ self.n = n # the output we're spending
+
+'''
+This reimplements tests from the bitcoinj/FullBlockTestGenerator used
+by the pull-tester.
+
+We use the testing framework in which we expect a particular answer from
+each test.
+'''
+
+class FullBlockTest(ComparisonTestFramework):
+
+ ''' Can either run this test as 1 node with expected answers, or two and compare them.
+ Change the "outcome" variable from each TestInstance object to only do the comparison. '''
+ def __init__(self):
+ self.num_nodes = 1
+ self.block_heights = {}
+ self.coinbase_key = CECKey()
+ self.coinbase_key.set_secretbytes(bytes("horsebattery"))
+ self.coinbase_pubkey = self.coinbase_key.get_pubkey()
+ self.block_time = int(time.time())+1
+ self.tip = None
+ self.blocks = {}
+
+ def run_test(self):
+ test = TestManager(self, self.options.tmpdir)
+ test.add_all_connections(self.nodes)
+ NetworkThread().start() # Start up network handling in another thread
+ test.run()
+
+ def add_transactions_to_block(self, block, tx_list):
+ [ tx.rehash() for tx in tx_list ]
+ block.vtx.extend(tx_list)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ return block
+
+ # Create a block on top of self.tip, and advance self.tip to point to the new block
+ # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output,
+ # and rest will go to fees.
+ def next_block(self, number, spend=None, additional_coinbase_value=0, script=None):
+ if self.tip == None:
+ base_block_hash = self.genesis_hash
+ else:
+ base_block_hash = self.tip.sha256
+ # First create the coinbase
+ height = self.block_heights[base_block_hash] + 1
+ coinbase = create_coinbase(height, self.coinbase_pubkey)
+ coinbase.vout[0].nValue += additional_coinbase_value
+ if (spend != None):
+ coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees
+ coinbase.rehash()
+ block = create_block(base_block_hash, coinbase, self.block_time)
+ if (spend != None):
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), "", 0xffffffff)) # no signature yet
+ # This copies the java comparison tool testing behavior: the first
+ # txout has a garbage scriptPubKey, "to make sure we're not
+ # pre-verifying too much" (?)
+ tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255])))
+ if script == None:
+ tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
+ else:
+ tx.vout.append(CTxOut(1, script))
+ # Now sign it if necessary
+ scriptSig = ""
+ scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey)
+ if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend
+ scriptSig = CScript([OP_TRUE])
+ else:
+ # We have to actually sign it
+ (sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL)
+ scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))])
+ tx.vin[0].scriptSig = scriptSig
+ # Now add the transaction to the block
+ block = self.add_transactions_to_block(block, [tx])
+ block.solve()
+ self.tip = block
+ self.block_heights[block.sha256] = height
+ self.block_time += 1
+ assert number not in self.blocks
+ self.blocks[number] = block
+ return block
+
+ def get_tests(self):
+ self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
+ self.block_heights[self.genesis_hash] = 0
+ spendable_outputs = []
+
+ # save the current tip so it can be spent by a later block
+ def save_spendable_output():
+ spendable_outputs.append(self.tip)
+
+ # get an output that we previous marked as spendable
+ def get_spendable_output():
+ return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)
+
+ # returns a test case that asserts that the current tip was accepted
+ def accepted():
+ return TestInstance([[self.tip, True]])
+
+ # returns a test case that asserts that the current tip was rejected
+ def rejected():
+ return TestInstance([[self.tip, False]])
+
+ # move the tip back to a previous block
+ def tip(number):
+ self.tip = self.blocks[number]
+
+ # creates a new block and advances the tip to that block
+ block = self.next_block
+
+
+ # Create a new block
+ block(0)
+ save_spendable_output()
+ yield accepted()
+
+
+ # Now we need that block to mature so we can spend the coinbase.
+ test = TestInstance(sync_every_block=False)
+ for i in range(100):
+ block(1000 + i)
+ test.blocks_and_transactions.append([self.tip, True])
+ save_spendable_output()
+ yield test
+
+
+ # Start by bulding a couple of blocks on top (which output is spent is in parentheses):
+ # genesis -> b1 (0) -> b2 (1)
+ out0 = get_spendable_output()
+ block(1, spend=out0)
+ save_spendable_output()
+ yield accepted()
+
+ out1 = get_spendable_output()
+ block(2, spend=out1)
+ # Inv again, then deliver twice (shouldn't break anything).
+ yield accepted()
+
+
+ # so fork like this:
+ #
+ # genesis -> b1 (0) -> b2 (1)
+ # \-> b3 (1)
+ #
+ # Nothing should happen at this point. We saw b2 first so it takes priority.
+ tip(1)
+ block(3, spend=out1)
+ # Deliver twice (should still not break anything)
+ yield rejected()
+
+
+ # Now we add another block to make the alternative chain longer.
+ #
+ # genesis -> b1 (0) -> b2 (1)
+ # \-> b3 (1) -> b4 (2)
+ out2 = get_spendable_output()
+ block(4, spend=out2)
+ yield accepted()
+
+
+ # ... and back to the first chain.
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b3 (1) -> b4 (2)
+ tip(2)
+ block(5, spend=out2)
+ save_spendable_output()
+ yield rejected()
+
+ out3 = get_spendable_output()
+ block(6, spend=out3)
+ yield accepted()
+
+
+ # Try to create a fork that double-spends
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b7 (2) -> b8 (4)
+ # \-> b3 (1) -> b4 (2)
+ tip(5)
+ block(7, spend=out2)
+ yield rejected()
+
+ out4 = get_spendable_output()
+ block(8, spend=out4)
+ yield rejected()
+
+
+ # Try to create a block that has too much fee
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b9 (4)
+ # \-> b3 (1) -> b4 (2)
+ tip(6)
+ block(9, spend=out4, additional_coinbase_value=1)
+ yield rejected()
+
+
+ # Create a fork that ends in a block with too much fee (the one that causes the reorg)
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b10 (3) -> b11 (4)
+ # \-> b3 (1) -> b4 (2)
+ tip(5)
+ block(10, spend=out3)
+ yield rejected()
+
+ block(11, spend=out4, additional_coinbase_value=1)
+ yield rejected()
+
+
+ # Try again, but with a valid fork first
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b14 (5)
+ # (b12 added last)
+ # \-> b3 (1) -> b4 (2)
+ tip(5)
+ b12 = block(12, spend=out3)
+ save_spendable_output()
+ #yield TestInstance([[b12, False]])
+ b13 = block(13, spend=out4)
+ # Deliver the block header for b12, and the block b13.
+ # b13 should be accepted but the tip won't advance until b12 is delivered.
+ yield TestInstance([[CBlockHeader(b12), None], [b13, False]])
+
+ save_spendable_output()
+ out5 = get_spendable_output()
+ # b14 is invalid, but the node won't know that until it tries to connect
+ # Tip still can't advance because b12 is missing
+ block(14, spend=out5, additional_coinbase_value=1)
+ yield rejected()
+
+ yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13.
+
+
+ # Test that a block with a lot of checksigs is okay
+ lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50 - 1))
+ tip(13)
+ block(15, spend=out5, script=lots_of_checksigs)
+ yield accepted()
+
+
+ # Test that a block with too many checksigs is rejected
+ out6 = get_spendable_output()
+ too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50))
+ block(16, spend=out6, script=too_many_checksigs)
+ yield rejected()
+
+
+
+if __name__ == '__main__':
+ FullBlockTest().main()
diff --git a/qa/rpc-tests/rest.py b/qa/rpc-tests/rest.py
index b0cde7268e..e084ad55ab 100755
--- a/qa/rpc-tests/rest.py
+++ b/qa/rpc-tests/rest.py
@@ -32,10 +32,20 @@ def deser_uint256(f):
r += t << (i * 32)
return r
-#allows simple http get calls with a request body
-def http_get_call(host, port, path, requestdata = '', response_object = 0):
+#allows simple http get calls
+def http_get_call(host, port, path, response_object = 0):
conn = httplib.HTTPConnection(host, port)
- conn.request('GET', path, requestdata)
+ conn.request('GET', path)
+
+ if response_object:
+ return conn.getresponse()
+
+ return conn.getresponse().read()
+
+#allows simple http post calls with a request body
+def http_post_call(host, port, path, requestdata = '', response_object = 0):
+ conn = httplib.HTTPConnection(host, port)
+ conn.request('POST', path, requestdata)
if response_object:
return conn.getresponse()
@@ -137,7 +147,7 @@ class RESTTest (BitcoinTestFramework):
binaryRequest += binascii.unhexlify(vintx);
binaryRequest += pack("i", 0);
- bin_response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest)
+ bin_response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest)
output = StringIO.StringIO()
output.write(bin_response)
output.seek(0)
@@ -175,14 +185,14 @@ class RESTTest (BitcoinTestFramework):
#do some invalid requests
json_request = '{"checkmempool'
- response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
assert_equal(response.status, 500) #must be a 500 because we send a invalid json request
json_request = '{"checkmempool'
- response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True)
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True)
assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
- response = http_get_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True)
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True)
assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
#test limits
@@ -190,17 +200,17 @@ class RESTTest (BitcoinTestFramework):
for x in range(0, 20):
json_request += txid+'-'+str(n)+'/'
json_request = json_request.rstrip("/")
- response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
assert_equal(response.status, 500) #must be a 500 because we exceeding the limits
json_request = '/checkmempool/'
for x in range(0, 15):
json_request += txid+'-'+str(n)+'/'
json_request = json_request.rstrip("/");
- response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
assert_equal(response.status, 200) #must be a 500 because we exceeding the limits
- self.nodes[0].generate(1) #generate block to not affect upcomming tests
+ self.nodes[0].generate(1) #generate block to not affect upcoming tests
self.sync_all()
################
@@ -208,27 +218,27 @@ class RESTTest (BitcoinTestFramework):
################
# check binary format
- response = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"bin", "", True)
+ response = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True)
assert_equal(response.status, 200)
assert_greater_than(int(response.getheader('content-length')), 80)
response_str = response.read()
# compare with block header
- response_header = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"bin", "", True)
+ response_header = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True)
assert_equal(response_header.status, 200)
assert_equal(int(response_header.getheader('content-length')), 80)
response_header_str = response_header.read()
assert_equal(response_str[0:80], response_header_str)
# check block hex format
- response_hex = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"hex", "", True)
+ response_hex = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True)
assert_equal(response_hex.status, 200)
assert_greater_than(int(response_hex.getheader('content-length')), 160)
response_hex_str = response_hex.read()
assert_equal(response_str.encode("hex")[0:160], response_hex_str[0:160])
# compare with hex block header
- response_header_hex = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"hex", "", True)
+ response_header_hex = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True)
assert_equal(response_header_hex.status, 200)
assert_greater_than(int(response_header_hex.getheader('content-length')), 160)
response_header_hex_str = response_header_hex.read()
@@ -241,7 +251,7 @@ class RESTTest (BitcoinTestFramework):
assert_equal(block_json_obj['hash'], bb_hash)
# compare with json block header
- response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", "", True)
+ response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", True)
assert_equal(response_header_json.status, 200)
response_header_json_str = response_header_json.read()
json_obj = json.loads(response_header_json_str, parse_float=decimal.Decimal)
@@ -265,7 +275,7 @@ class RESTTest (BitcoinTestFramework):
#see if we can get 5 headers in one response
self.nodes[1].generate(5)
self.sync_all()
- response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/5/'+bb_hash+self.FORMAT_SEPARATOR+"json", "", True)
+ response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/5/'+bb_hash+self.FORMAT_SEPARATOR+"json", True)
assert_equal(response_header_json.status, 200)
response_header_json_str = response_header_json.read()
json_obj = json.loads(response_header_json_str)
@@ -278,7 +288,7 @@ class RESTTest (BitcoinTestFramework):
assert_equal(json_obj['txid'], tx_hash)
# check hex format response
- hex_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"hex", "", True)
+ hex_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"hex", True)
assert_equal(hex_string.status, 200)
assert_greater_than(int(response.getheader('content-length')), 10)
@@ -292,6 +302,19 @@ class RESTTest (BitcoinTestFramework):
txs.append(self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11))
self.sync_all()
+ # check that there are exactly 3 transactions in the TX memory pool before generating the block
+ json_string = http_get_call(url.hostname, url.port, '/rest/mempool/info'+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+ assert_equal(json_obj['size'], 3)
+ # the size of the memory pool should be greater than 3x ~100 bytes
+ assert_greater_than(json_obj['bytes'], 300)
+
+ # check that there are our submitted transactions in the TX memory pool
+ json_string = http_get_call(url.hostname, url.port, '/rest/mempool/contents'+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+ for tx in txs:
+ assert_equal(tx in json_obj, True)
+
# now mine the transactions
newblockhash = self.nodes[1].generate(1)
self.sync_all()
diff --git a/qa/rpc-tests/script_test.py b/qa/rpc-tests/script_test.py
index 860fa56b64..afc44b51b5 100755
--- a/qa/rpc-tests/script_test.py
+++ b/qa/rpc-tests/script_test.py
@@ -124,10 +124,10 @@ def ParseScript(json_script):
return parsed_script
class TestBuilder(object):
- def create_credit_tx(self, scriptPubKey):
+ def create_credit_tx(self, scriptPubKey, height):
# self.tx1 is a coinbase transaction, modeled after the one created by script_tests.cpp
# This allows us to reuse signatures created in the unit test framework.
- self.tx1 = create_coinbase() # this has a bip34 scriptsig,
+ self.tx1 = create_coinbase(height) # this has a bip34 scriptsig,
self.tx1.vin[0].scriptSig = CScript([0, 0]) # but this matches the unit tests
self.tx1.vout[0].nValue = 0
self.tx1.vout[0].scriptPubKey = scriptPubKey
@@ -168,7 +168,7 @@ class ScriptTest(ComparisonTestFramework):
test = TestInstance(sync_every_block=False)
test_build = TestBuilder()
- test_build.create_credit_tx(scriptpubkey)
+ test_build.create_credit_tx(scriptpubkey, self.height)
test_build.create_spend_tx(scriptsig)
test_build.rehash()
@@ -176,16 +176,18 @@ class ScriptTest(ComparisonTestFramework):
self.block_time += 1
block.solve()
self.tip = block.sha256
+ self.height += 1
test.blocks_and_transactions = [[block, True]]
for i in xrange(100):
- block = create_block(self.tip, create_coinbase(), self.block_time)
+ block = create_block(self.tip, create_coinbase(self.height), self.block_time)
self.block_time += 1
block.solve()
self.tip = block.sha256
+ self.height += 1
test.blocks_and_transactions.append([block, True])
- block = create_block(self.tip, create_coinbase(), self.block_time)
+ block = create_block(self.tip, create_coinbase(self.height), self.block_time)
self.block_time += 1
block.vtx.append(test_build.tx2)
block.hashMerkleRoot = block.calc_merkle_root()
@@ -198,14 +200,16 @@ class ScriptTest(ComparisonTestFramework):
def get_tests(self):
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
self.block_time = 1333230000 # before the BIP16 switchover
+ self.height = 1
'''
Create a new block with an anyone-can-spend coinbase
'''
- block = create_block(self.tip, create_coinbase(), self.block_time)
+ block = create_block(self.tip, create_coinbase(self.height), self.block_time)
self.block_time += 1
block.solve()
self.tip = block.sha256
+ self.height += 1
yield TestInstance(objects=[[block, True]])
'''
@@ -213,11 +217,12 @@ class ScriptTest(ComparisonTestFramework):
'''
test = TestInstance(objects=[], sync_every_block=False, sync_every_tx=False)
for i in xrange(100):
- b = create_block(self.tip, create_coinbase(), self.block_time)
+ b = create_block(self.tip, create_coinbase(self.height), self.block_time)
b.solve()
test.blocks_and_transactions.append([b, True])
self.tip = b.sha256
self.block_time += 1
+ self.height += 1
yield test
''' Iterate through script tests. '''
@@ -229,6 +234,7 @@ class ScriptTest(ComparisonTestFramework):
self.nodes[1].invalidateblock(self.nodes[1].getblockhash(102))
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
+ self.height = 102
[scriptsig, scriptpubkey, flags] = script_test[0:3]
flags = ParseScriptFlags(flags)
diff --git a/qa/rpc-tests/test_framework/blockstore.py b/qa/rpc-tests/test_framework/blockstore.py
index c57b6df81b..b9775b477c 100644
--- a/qa/rpc-tests/test_framework/blockstore.py
+++ b/qa/rpc-tests/test_framework/blockstore.py
@@ -10,6 +10,7 @@ class BlockStore(object):
def __init__(self, datadir):
self.blockDB = dbm.open(datadir + "/blocks", 'c')
self.currentBlock = 0L
+ self.headers_map = dict()
def close(self):
self.blockDB.close()
@@ -26,24 +27,30 @@ class BlockStore(object):
ret.calc_sha256()
return ret
+ def get_header(self, blockhash):
+ try:
+ return self.headers_map[blockhash]
+ except KeyError:
+ return None
+
# Note: this pulls full blocks out of the database just to retrieve
# the headers -- perhaps we could keep a separate data structure
# to avoid this overhead.
def headers_for(self, locator, hash_stop, current_tip=None):
if current_tip is None:
current_tip = self.currentBlock
- current_block = self.get(current_tip)
- if current_block is None:
+ current_block_header = self.get_header(current_tip)
+ if current_block_header is None:
return None
response = msg_headers()
- headersList = [ CBlockHeader(current_block) ]
+ headersList = [ current_block_header ]
maxheaders = 2000
while (headersList[0].sha256 not in locator.vHave):
prevBlockHash = headersList[0].hashPrevBlock
- prevBlock = self.get(prevBlockHash)
- if prevBlock is not None:
- headersList.insert(0, CBlockHeader(prevBlock))
+ prevBlockHeader = self.get_header(prevBlockHash)
+ if prevBlockHeader is not None:
+ headersList.insert(0, prevBlockHeader)
else:
break
headersList = headersList[:maxheaders] # truncate if we have too many
@@ -61,6 +68,10 @@ class BlockStore(object):
except TypeError as e:
print "Unexpected error: ", sys.exc_info()[0], e.args
self.currentBlock = block.sha256
+ self.headers_map[block.sha256] = CBlockHeader(block)
+
+ def add_header(self, header):
+ self.headers_map[header.sha256] = header
def get_blocks(self, inv):
responses = []
diff --git a/qa/rpc-tests/test_framework/blocktools.py b/qa/rpc-tests/test_framework/blocktools.py
index f397fe7cd6..59aa8c15cc 100644
--- a/qa/rpc-tests/test_framework/blocktools.py
+++ b/qa/rpc-tests/test_framework/blocktools.py
@@ -5,7 +5,7 @@
#
from mininode import *
-from script import CScript, CScriptOp
+from script import CScript, CScriptOp, OP_TRUE, OP_CHECKSIG
# Create a block (with regtest difficulty)
def create_block(hashprev, coinbase, nTime=None):
@@ -37,19 +37,21 @@ def serialize_script_num(value):
r[-1] |= 0x80
return r
-counter=1
-# Create an anyone-can-spend coinbase transaction, assuming no miner fees
-def create_coinbase(heightAdjust = 0):
- global counter
+# Create a coinbase transaction, assuming no miner fees.
+# If pubkey is passed in, the coinbase output will be a P2PK output;
+# otherwise an anyone-can-spend output.
+def create_coinbase(height, pubkey = None):
coinbase = CTransaction()
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff),
- ser_string(serialize_script_num(counter+heightAdjust)), 0xffffffff))
- counter += 1
+ ser_string(serialize_script_num(height)), 0xffffffff))
coinbaseoutput = CTxOut()
coinbaseoutput.nValue = 50*100000000
- halvings = int((counter+heightAdjust)/150) # regtest
+ halvings = int(height/150) # regtest
coinbaseoutput.nValue >>= halvings
- coinbaseoutput.scriptPubKey = ""
+ if (pubkey != None):
+ coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG])
+ else:
+ coinbaseoutput.scriptPubKey = CScript([OP_TRUE])
coinbase.vout = [ coinbaseoutput ]
coinbase.calc_sha256()
return coinbase
diff --git a/qa/rpc-tests/test_framework/comptool.py b/qa/rpc-tests/test_framework/comptool.py
index 7fb31d4a06..e0b3ce040d 100755
--- a/qa/rpc-tests/test_framework/comptool.py
+++ b/qa/rpc-tests/test_framework/comptool.py
@@ -27,6 +27,20 @@ generator that returns TestInstance objects. See below for definition.
global mininode_lock
+def wait_until(predicate, attempts=float('inf'), timeout=float('inf')):
+ attempt = 0
+ elapsed = 0
+
+ while attempt < attempts and elapsed < timeout:
+ with mininode_lock:
+ if predicate():
+ return True
+ attempt += 1
+ elapsed += 0.05
+ time.sleep(0.05)
+
+ return False
+
class TestNode(NodeConnCB):
def __init__(self, block_store, tx_store):
@@ -43,6 +57,10 @@ class TestNode(NodeConnCB):
# a response
self.pingMap = {}
self.lastInv = []
+ self.closed = False
+
+ def on_close(self, conn):
+ self.closed = True
def add_connection(self, conn):
self.conn = conn
@@ -104,19 +122,26 @@ class TestNode(NodeConnCB):
# Instances of these are generated by the test generator, and fed into the
# comptool.
#
-# "blocks_and_transactions" should be an array of [obj, True/False/None]:
-# - obj is either a CBlock or a CTransaction, and
+# "blocks_and_transactions" should be an array of
+# [obj, True/False/None, hash/None]:
+# - obj is either a CBlock, CBlockHeader, or a CTransaction, and
# - the second value indicates whether the object should be accepted
# into the blockchain or mempool (for tests where we expect a certain
# answer), or "None" if we don't expect a certain answer and are just
# comparing the behavior of the nodes being tested.
+# - the third value is the hash to test the tip against (if None or omitted,
+# use the hash of the block)
+# - NOTE: if a block header, no test is performed; instead the header is
+# just added to the block_store. This is to facilitate block delivery
+# when communicating with headers-first clients (when withholding an
+# intermediate block).
# sync_every_block: if True, then each block will be inv'ed, synced, and
# nodes will be tested based on the outcome for the block. If False,
# then inv's accumulate until all blocks are processed (or max inv size
# is reached) and then sent out in one inv message. Then the final block
# will be synced across all connections, and the outcome of the final
# block will be tested.
-# sync_every_tx: analagous to behavior for sync_every_block, except if outcome
+# sync_every_tx: analogous to behavior for sync_every_block, except if outcome
# on the final tx is None, then contents of entire mempool are compared
# across all connections. (If outcome of final tx is specified as true
# or false, then only the last tx is tested against outcome.)
@@ -132,6 +157,7 @@ class TestManager(object):
def __init__(self, testgen, datadir):
self.test_generator = testgen
self.connections = []
+ self.test_nodes = []
self.block_store = BlockStore(datadir)
self.tx_store = TxStore(datadir)
self.ping_counter = 1
@@ -139,57 +165,42 @@ class TestManager(object):
def add_all_connections(self, nodes):
for i in range(len(nodes)):
# Create a p2p connection to each node
- self.connections.append(NodeConn('127.0.0.1', p2p_port(i),
- nodes[i], TestNode(self.block_store, self.tx_store)))
+ test_node = TestNode(self.block_store, self.tx_store)
+ self.test_nodes.append(test_node)
+ self.connections.append(NodeConn('127.0.0.1', p2p_port(i), nodes[i], test_node))
# Make sure the TestNode (callback class) has a reference to its
# associated NodeConn
- self.connections[-1].cb.add_connection(self.connections[-1])
+ test_node.add_connection(self.connections[-1])
+
+ def wait_for_disconnections(self):
+ def disconnected():
+ return all(node.closed for node in self.test_nodes)
+ return wait_until(disconnected, timeout=10)
def wait_for_verack(self):
- sleep_time = 0.05
- max_tries = 10 / sleep_time # Wait at most 10 seconds
- while max_tries > 0:
- done = True
- with mininode_lock:
- for c in self.connections:
- if c.cb.verack_received is False:
- done = False
- break
- if done:
- break
- time.sleep(sleep_time)
+ def veracked():
+ return all(node.verack_received for node in self.test_nodes)
+ return wait_until(veracked, timeout=10)
def wait_for_pings(self, counter):
- received_pongs = False
- while received_pongs is not True:
- time.sleep(0.05)
- received_pongs = True
- with mininode_lock:
- for c in self.connections:
- if c.cb.received_ping_response(counter) is not True:
- received_pongs = False
- break
+ def received_pongs():
+ return all(node.received_ping_response(counter) for node in self.test_nodes)
+ return wait_until(received_pongs)
# sync_blocks: Wait for all connections to request the blockhash given
# then send get_headers to find out the tip of each node, and synchronize
# the response by using a ping (and waiting for pong with same nonce).
def sync_blocks(self, blockhash, num_blocks):
- # Wait for nodes to request block (50ms sleep * 20 tries * num_blocks)
- max_tries = 20*num_blocks
- while max_tries > 0:
- with mininode_lock:
- results = [ blockhash in c.cb.block_request_map and
- c.cb.block_request_map[blockhash] for c in self.connections ]
- if False not in results:
- break
- time.sleep(0.05)
- max_tries -= 1
+ def blocks_requested():
+ return all(
+ blockhash in node.block_request_map and node.block_request_map[blockhash]
+ for node in self.test_nodes
+ )
# --> error if not requested
- if max_tries == 0:
+ if not wait_until(blocks_requested, attempts=20*num_blocks):
# print [ c.cb.block_request_map for c in self.connections ]
raise AssertionError("Not all nodes requested block")
- # --> Answer request (we did this inline!)
# Send getheaders message
[ c.cb.send_getheaders() for c in self.connections ]
@@ -202,21 +213,16 @@ class TestManager(object):
# Analogous to sync_block (see above)
def sync_transaction(self, txhash, num_events):
# Wait for nodes to request transaction (50ms sleep * 20 tries * num_events)
- max_tries = 20*num_events
- while max_tries > 0:
- with mininode_lock:
- results = [ txhash in c.cb.tx_request_map and
- c.cb.tx_request_map[txhash] for c in self.connections ]
- if False not in results:
- break
- time.sleep(0.05)
- max_tries -= 1
+ def transaction_requested():
+ return all(
+ txhash in node.tx_request_map and node.tx_request_map[txhash]
+ for node in self.test_nodes
+ )
# --> error if not requested
- if max_tries == 0:
+ if not wait_until(transaction_requested, attempts=20*num_events):
# print [ c.cb.tx_request_map for c in self.connections ]
raise AssertionError("Not all nodes requested transaction")
- # --> Answer request (we did this inline!)
# Get the mempool
[ c.cb.send_mempool() for c in self.connections ]
@@ -271,29 +277,55 @@ class TestManager(object):
# We use these variables to keep track of the last block
# and last transaction in the tests, which are used
# if we're not syncing on every block or every tx.
- [ block, block_outcome ] = [ None, None ]
+ [ block, block_outcome, tip ] = [ None, None, None ]
[ tx, tx_outcome ] = [ None, None ]
invqueue = []
- for b_or_t, outcome in test_instance.blocks_and_transactions:
+ for test_obj in test_instance.blocks_and_transactions:
+ b_or_t = test_obj[0]
+ outcome = test_obj[1]
# Determine if we're dealing with a block or tx
if isinstance(b_or_t, CBlock): # Block test runner
block = b_or_t
block_outcome = outcome
+ tip = block.sha256
+ # each test_obj can have an optional third argument
+ # to specify the tip we should compare with
+ # (default is to use the block being tested)
+ if len(test_obj) >= 3:
+ tip = test_obj[2]
+
# Add to shared block_store, set as current block
+ # If there was an open getdata request for the block
+ # previously, and we didn't have an entry in the
+ # block_store, then immediately deliver, because the
+ # node wouldn't send another getdata request while
+ # the earlier one is outstanding.
+ first_block_with_hash = True
+ if self.block_store.get(block.sha256) is not None:
+ first_block_with_hash = False
with mininode_lock:
self.block_store.add_block(block)
for c in self.connections:
- c.cb.block_request_map[block.sha256] = False
+ if first_block_with_hash and block.sha256 in c.cb.block_request_map and c.cb.block_request_map[block.sha256] == True:
+ # There was a previous request for this block hash
+ # Most likely, we delivered a header for this block
+ # but never had the block to respond to the getdata
+ c.send_message(msg_block(block))
+ else:
+ c.cb.block_request_map[block.sha256] = False
# Either send inv's to each node and sync, or add
# to invqueue for later inv'ing.
if (test_instance.sync_every_block):
[ c.cb.send_inv(block) for c in self.connections ]
self.sync_blocks(block.sha256, 1)
- if (not self.check_results(block.sha256, outcome)):
+ if (not self.check_results(tip, outcome)):
raise AssertionError("Test failed at test %d" % test_number)
else:
invqueue.append(CInv(2, block.sha256))
+ elif isinstance(b_or_t, CBlockHeader):
+ block_header = b_or_t
+ self.block_store.add_header(block_header)
else: # Tx test runner
assert(isinstance(b_or_t, CTransaction))
tx = b_or_t
@@ -321,9 +353,8 @@ class TestManager(object):
if len(invqueue) > 0:
[ c.send_message(msg_inv(invqueue)) for c in self.connections ]
invqueue = []
- self.sync_blocks(block.sha256,
- len(test_instance.blocks_and_transactions))
- if (not self.check_results(block.sha256, block_outcome)):
+ self.sync_blocks(block.sha256, len(test_instance.blocks_and_transactions))
+ if (not self.check_results(tip, block_outcome)):
raise AssertionError("Block test failed at test %d" % test_number)
if (not test_instance.sync_every_tx and tx is not None):
if len(invqueue) > 0:
@@ -336,6 +367,7 @@ class TestManager(object):
print "Test %d: PASS" % test_number, [ c.rpc.getblockcount() for c in self.connections ]
test_number += 1
+ [ c.disconnect_node() for c in self.connections ]
+ self.wait_for_disconnections()
self.block_store.close()
self.tx_store.close()
- [ c.disconnect_node() for c in self.connections ]
diff --git a/qa/rpc-tests/test_framework/key.py b/qa/rpc-tests/test_framework/key.py
new file mode 100644
index 0000000000..ba3038fe04
--- /dev/null
+++ b/qa/rpc-tests/test_framework/key.py
@@ -0,0 +1,215 @@
+# Copyright (c) 2011 Sam Rushing
+#
+# key.py - OpenSSL wrapper
+#
+# This file is modified from python-bitcoinlib.
+#
+
+"""ECC secp256k1 crypto routines
+
+WARNING: This module does not mlock() secrets; your private keys may end up on
+disk in swap! Use with caution!
+"""
+
+import ctypes
+import ctypes.util
+import hashlib
+import sys
+
+ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library ('ssl') or 'libeay32')
+
+ssl.BN_new.restype = ctypes.c_void_p
+ssl.BN_new.argtypes = []
+
+ssl.BN_bin2bn.restype = ctypes.c_void_p
+ssl.BN_bin2bn.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p]
+
+ssl.BN_CTX_free.restype = None
+ssl.BN_CTX_free.argtypes = [ctypes.c_void_p]
+
+ssl.BN_CTX_new.restype = ctypes.c_void_p
+ssl.BN_CTX_new.argtypes = []
+
+ssl.ECDH_compute_key.restype = ctypes.c_int
+ssl.ECDH_compute_key.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.ECDSA_sign.restype = ctypes.c_int
+ssl.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.ECDSA_verify.restype = ctypes.c_int
+ssl.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
+
+ssl.EC_KEY_free.restype = None
+ssl.EC_KEY_free.argtypes = [ctypes.c_void_p]
+
+ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
+ssl.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int]
+
+ssl.EC_KEY_get0_group.restype = ctypes.c_void_p
+ssl.EC_KEY_get0_group.argtypes = [ctypes.c_void_p]
+
+ssl.EC_KEY_get0_public_key.restype = ctypes.c_void_p
+ssl.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p]
+
+ssl.EC_KEY_set_private_key.restype = ctypes.c_int
+ssl.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.EC_KEY_set_conv_form.restype = None
+ssl.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p, ctypes.c_int]
+
+ssl.EC_KEY_set_public_key.restype = ctypes.c_int
+ssl.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.i2o_ECPublicKey.restype = ctypes.c_void_p
+ssl.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.EC_POINT_new.restype = ctypes.c_void_p
+ssl.EC_POINT_new.argtypes = [ctypes.c_void_p]
+
+ssl.EC_POINT_free.restype = None
+ssl.EC_POINT_free.argtypes = [ctypes.c_void_p]
+
+ssl.EC_POINT_mul.restype = ctypes.c_int
+ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+# this specifies the curve used with ECDSA.
+NID_secp256k1 = 714 # from openssl/obj_mac.h
+
+# Thx to Sam Devlin for the ctypes magic 64-bit fix.
+def _check_result(val, func, args):
+ if val == 0:
+ raise ValueError
+ else:
+ return ctypes.c_void_p (val)
+
+ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
+ssl.EC_KEY_new_by_curve_name.errcheck = _check_result
+
+class CECKey(object):
+ """Wrapper around OpenSSL's EC_KEY"""
+
+ POINT_CONVERSION_COMPRESSED = 2
+ POINT_CONVERSION_UNCOMPRESSED = 4
+
+ def __init__(self):
+ self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1)
+
+ def __del__(self):
+ if ssl:
+ ssl.EC_KEY_free(self.k)
+ self.k = None
+
+ def set_secretbytes(self, secret):
+ priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new())
+ group = ssl.EC_KEY_get0_group(self.k)
+ pub_key = ssl.EC_POINT_new(group)
+ ctx = ssl.BN_CTX_new()
+ if not ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx):
+ raise ValueError("Could not derive public key from the supplied secret.")
+ ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
+ ssl.EC_KEY_set_private_key(self.k, priv_key)
+ ssl.EC_KEY_set_public_key(self.k, pub_key)
+ ssl.EC_POINT_free(pub_key)
+ ssl.BN_CTX_free(ctx)
+ return self.k
+
+ def set_privkey(self, key):
+ self.mb = ctypes.create_string_buffer(key)
+ return ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
+
+ def set_pubkey(self, key):
+ self.mb = ctypes.create_string_buffer(key)
+ return ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
+
+ def get_privkey(self):
+ size = ssl.i2d_ECPrivateKey(self.k, 0)
+ mb_pri = ctypes.create_string_buffer(size)
+ ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri)))
+ return mb_pri.raw
+
+ def get_pubkey(self):
+ size = ssl.i2o_ECPublicKey(self.k, 0)
+ mb = ctypes.create_string_buffer(size)
+ ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb)))
+ return mb.raw
+
+ def get_raw_ecdh_key(self, other_pubkey):
+ ecdh_keybuffer = ctypes.create_string_buffer(32)
+ r = ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32,
+ ssl.EC_KEY_get0_public_key(other_pubkey.k),
+ self.k, 0)
+ if r != 32:
+ raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed')
+ return ecdh_keybuffer.raw
+
+ def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()):
+ # FIXME: be warned it's not clear what the kdf should be as a default
+ r = self.get_raw_ecdh_key(other_pubkey)
+ return kdf(r)
+
+ def sign(self, hash):
+ # FIXME: need unit tests for below cases
+ if not isinstance(hash, bytes):
+ raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
+ if len(hash) != 32:
+ raise ValueError('Hash must be exactly 32 bytes long')
+
+ sig_size0 = ctypes.c_uint32()
+ sig_size0.value = ssl.ECDSA_size(self.k)
+ mb_sig = ctypes.create_string_buffer(sig_size0.value)
+ result = ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k)
+ assert 1 == result
+ return mb_sig.raw[:sig_size0.value]
+
+ def verify(self, hash, sig):
+ """Verify a DER signature"""
+ return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1
+
+ def set_compressed(self, compressed):
+ if compressed:
+ form = self.POINT_CONVERSION_COMPRESSED
+ else:
+ form = self.POINT_CONVERSION_UNCOMPRESSED
+ ssl.EC_KEY_set_conv_form(self.k, form)
+
+
+class CPubKey(bytes):
+ """An encapsulated public key
+
+ Attributes:
+
+ is_valid - Corresponds to CPubKey.IsValid()
+ is_fullyvalid - Corresponds to CPubKey.IsFullyValid()
+ is_compressed - Corresponds to CPubKey.IsCompressed()
+ """
+
+ def __new__(cls, buf, _cec_key=None):
+ self = super(CPubKey, cls).__new__(cls, buf)
+ if _cec_key is None:
+ _cec_key = CECKey()
+ self._cec_key = _cec_key
+ self.is_fullyvalid = _cec_key.set_pubkey(self) != 0
+ return self
+
+ @property
+ def is_valid(self):
+ return len(self) > 0
+
+ @property
+ def is_compressed(self):
+ return len(self) == 33
+
+ def verify(self, hash, sig):
+ return self._cec_key.verify(hash, sig)
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ # Always have represent as b'<secret>' so test cases don't have to
+ # change for py2/3
+ if sys.version > '3':
+ return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__())
+ else:
+ return '%s(b%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__())
+
diff --git a/qa/rpc-tests/test_framework/script.py b/qa/rpc-tests/test_framework/script.py
index e37ab5d45a..0a78cf6fb1 100644
--- a/qa/rpc-tests/test_framework/script.py
+++ b/qa/rpc-tests/test_framework/script.py
@@ -27,7 +27,7 @@ if sys.version > '3':
import copy
import struct
-import test_framework.bignum
+from test_framework.bignum import bn2vch
MAX_SCRIPT_SIZE = 10000
MAX_SCRIPT_ELEMENT_SIZE = 520
@@ -664,7 +664,7 @@ class CScript(bytes):
elif other == -1:
other = bytes(bchr(OP_1NEGATE))
else:
- other = CScriptOp.encode_op_pushdata(bignum.bn2vch(other))
+ other = CScriptOp.encode_op_pushdata(bn2vch(other))
elif isinstance(other, (bytes, bytearray)):
other = CScriptOp.encode_op_pushdata(other)
return other
diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py
index c236ec2602..3759cc8162 100644
--- a/qa/rpc-tests/test_framework/util.py
+++ b/qa/rpc-tests/test_framework/util.py
@@ -78,8 +78,17 @@ def initialize_chain(test_dir):
bitcoind and bitcoin-cli must be in search path.
"""
- if not os.path.isdir(os.path.join("cache", "node0")):
- devnull = open("/dev/null", "w+")
+ if (not os.path.isdir(os.path.join("cache","node0"))
+ or not os.path.isdir(os.path.join("cache","node1"))
+ or not os.path.isdir(os.path.join("cache","node2"))
+ or not os.path.isdir(os.path.join("cache","node3"))):
+
+ #find and delete old cache directories if any exist
+ for i in range(4):
+ if os.path.isdir(os.path.join("cache","node"+str(i))):
+ shutil.rmtree(os.path.join("cache","node"+str(i)))
+
+ devnull = open(os.devnull, "w")
# Create cache directories, run bitcoinds:
for i in range(4):
datadir=initialize_datadir("cache", i)
@@ -171,7 +180,7 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
args = [ binary, "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
if extra_args is not None: args.extend(extra_args)
bitcoind_processes[i] = subprocess.Popen(args)
- devnull = open("/dev/null", "w+")
+ devnull = open(os.devnull, "w")
if os.getenv("PYTHON_DEBUG", ""):
print "start_node: bitcoind started, calling bitcoin-cli -rpcwait getblockcount"
subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir] +
diff --git a/qa/rpc-tests/zmq_test.py b/qa/rpc-tests/zmq_test.py
new file mode 100755
index 0000000000..fffaf677d6
--- /dev/null
+++ b/qa/rpc-tests/zmq_test.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python2
+# Copyright (c) 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 ZMQ interface
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import zmq
+import binascii
+from test_framework.mininode import hash256
+
+try:
+ import http.client as httplib
+except ImportError:
+ import httplib
+try:
+ import urllib.parse as urlparse
+except ImportError:
+ import urlparse
+
+class ZMQTest (BitcoinTestFramework):
+
+ port = 28332
+
+ def setup_nodes(self):
+ self.zmqContext = zmq.Context()
+ self.zmqSubSocket = self.zmqContext.socket(zmq.SUB)
+ self.zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashblock")
+ self.zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashtx")
+ self.zmqSubSocket.connect("tcp://127.0.0.1:%i" % self.port)
+ # Note: proxies are not used to connect to local nodes
+ # this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
+ return start_nodes(4, self.options.tmpdir, extra_args=[
+ ['-zmqpubhashtx=tcp://127.0.0.1:'+str(self.port), '-zmqpubhashblock=tcp://127.0.0.1:'+str(self.port)],
+ [],
+ [],
+ []
+ ])
+
+ def run_test(self):
+ self.sync_all()
+
+ genhashes = self.nodes[0].generate(1);
+ self.sync_all()
+
+ print "listen..."
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = str(msg[0])
+ body = msg[1]
+
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = str(msg[0])
+ body = msg[1]
+ blkhash = binascii.hexlify(body)
+
+ assert_equal(genhashes[0], blkhash) #blockhash from generate must be equal to the hash received over zmq
+
+ n = 10
+ genhashes = self.nodes[1].generate(n);
+ self.sync_all()
+
+ zmqHashes = []
+ for x in range(0,n*2):
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = str(msg[0])
+ body = msg[1]
+ if topic == "hashblock":
+ zmqHashes.append(binascii.hexlify(body))
+
+ for x in range(0,n):
+ assert_equal(genhashes[x], zmqHashes[x]) #blockhash from generate must be equal to the hash received over zmq
+
+ #test tx from a second node
+ hashRPC = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
+ self.sync_all()
+
+ #now we should receive a zmq msg because the tx was broadcastet
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = str(msg[0])
+ body = msg[1]
+ hashZMQ = ""
+ if topic == "hashtx":
+ hashZMQ = binascii.hexlify(body)
+
+ assert_equal(hashRPC, hashZMQ) #blockhash from generate must be equal to the hash received over zmq
+
+
+if __name__ == '__main__':
+ ZMQTest ().main ()
diff --git a/src/Makefile.am b/src/Makefile.am
index cc8dded413..67e848be39 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -48,6 +48,9 @@ if ENABLE_WALLET
BITCOIN_INCLUDES += $(BDB_CPPFLAGS)
EXTRA_LIBRARIES += libbitcoin_wallet.a
endif
+if ENABLE_ZMQ
+EXTRA_LIBRARIES += libbitcoin_zmq.a
+endif
if BUILD_BITCOIN_LIBS
lib_LTLIBRARIES = libbitcoinconsensus.la
@@ -98,6 +101,8 @@ BITCOIN_CORE_H = \
eccryptoverify.h \
ecwrapper.h \
hash.h \
+ httprpc.h \
+ httpserver.h \
init.h \
key.h \
keystore.h \
@@ -119,6 +124,7 @@ BITCOIN_CORE_H = \
protocol.h \
pubkey.h \
random.h \
+ reverselock.h \
rpcclient.h \
rpcprotocol.h \
rpcserver.h \
@@ -154,7 +160,12 @@ BITCOIN_CORE_H = \
wallet/db.h \
wallet/wallet.h \
wallet/wallet_ismine.h \
- wallet/walletdb.h
+ wallet/walletdb.h \
+ zmq/zmqabstractnotifier.h \
+ zmq/zmqconfig.h\
+ zmq/zmqnotificationinterface.h \
+ zmq/zmqpublishnotifier.h
+
obj/build.h: FORCE
@$(MKDIR_P) $(builddir)/obj
@@ -163,13 +174,15 @@ obj/build.h: FORCE
libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h
# server: shared between bitcoind and bitcoin-qt
-libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS)
+libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS)
libbitcoin_server_a_SOURCES = \
addrman.cpp \
alert.cpp \
bloom.cpp \
chain.cpp \
checkpoints.cpp \
+ httprpc.cpp \
+ httpserver.cpp \
init.cpp \
leveldbwrapper.cpp \
main.cpp \
@@ -194,6 +207,17 @@ libbitcoin_server_a_SOURCES = \
validationinterface.cpp \
$(BITCOIN_CORE_H)
+if ENABLE_ZMQ
+LIBBITCOIN_ZMQ=libbitcoin_zmq.a
+
+libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES)
+libbitcoin_zmq_a_SOURCES = \
+ zmq/zmqabstractnotifier.cpp \
+ zmq/zmqnotificationinterface.cpp \
+ zmq/zmqpublishnotifier.cpp
+endif
+
+
# wallet: shared between bitcoind and bitcoin-qt, but only linked
# when wallet enabled
libbitcoin_wallet_a_CPPFLAGS = $(BITCOIN_INCLUDES)
@@ -315,16 +339,19 @@ bitcoind_LDADD = \
$(LIBMEMENV) \
$(LIBSECP256K1)
+if ENABLE_ZMQ
+bitcoind_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
+endif
+
if ENABLE_WALLET
bitcoind_LDADD += libbitcoin_wallet.a
endif
-bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS)
-#
+bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
# bitcoin-cli binary #
bitcoin_cli_SOURCES = bitcoin-cli.cpp
-bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES)
+bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES) $(EVENT_CFLAGS)
bitcoin_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
if TARGET_WINDOWS
@@ -337,7 +364,7 @@ bitcoin_cli_LDADD = \
$(LIBBITCOIN_UTIL) \
$(LIBSECP256K1)
-bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS)
+bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS)
#
# bitcoin-tx binary #
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index 748f2b14d5..3e8eda1782 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -361,8 +361,12 @@ qt_bitcoin_qt_LDADD = qt/libbitcoinqt.a $(LIBBITCOIN_SERVER)
if ENABLE_WALLET
qt_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET)
endif
+if ENABLE_ZMQ
+qt_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
+endif
qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
- $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1)
+ $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \
+ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
qt_bitcoin_qt_LIBTOOLFLAGS = --tag CXX
diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include
index c5392cf307..6554580bea 100644
--- a/src/Makefile.qttest.include
+++ b/src/Makefile.qttest.include
@@ -30,9 +30,13 @@ qt_test_test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN_SERVER)
if ENABLE_WALLET
qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET)
endif
+if ENABLE_ZMQ
+qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
+endif
qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) \
$(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \
- $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1)
+ $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \
+ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
CLEAN_BITCOIN_QT_TEST = $(TEST_QT_MOC_CPP) qt/test/*.gcda qt/test/*.gcno
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index f9384a09a4..cee35926a5 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -15,6 +15,8 @@ EXTRA_DIST += \
test/data/tx394b54bb.hex \
test/data/txcreate1.hex \
test/data/txcreate2.hex \
+ test/data/txcreatedata1.hex \
+ test/data/txcreatedata2.hex \
test/data/txcreatesign.hex
JSON_TEST_FILES = \
@@ -50,6 +52,7 @@ BITCOIN_TESTS =\
test/getarg_tests.cpp \
test/hash_tests.cpp \
test/key_tests.cpp \
+ test/limitedmap_tests.cpp \
test/main_tests.cpp \
test/mempool_tests.cpp \
test/miner_tests.cpp \
@@ -59,6 +62,7 @@ BITCOIN_TESTS =\
test/pmt_tests.cpp \
test/policyestimator_tests.cpp \
test/pow_tests.cpp \
+ test/reverselock_tests.cpp \
test/rpc_tests.cpp \
test/sanity_tests.cpp \
test/scheduler_tests.cpp \
@@ -96,6 +100,10 @@ endif
test_test_bitcoin_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS)
test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
+if ENABLE_ZMQ
+test_test_bitcoin_LDADD += $(ZMQ_LIBS)
+endif
+
nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES)
$(BITCOIN_TESTS): $(GENERATED_TEST_FILES)
diff --git a/src/addrman.cpp b/src/addrman.cpp
index b605f4351d..ff1f7e9187 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -341,8 +341,10 @@ CAddrInfo CAddrMan::Select_()
while (1) {
int nKBucket = GetRandInt(ADDRMAN_TRIED_BUCKET_COUNT);
int nKBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE);
- if (vvTried[nKBucket][nKBucketPos] == -1)
- continue;
+ while (vvTried[nKBucket][nKBucketPos] == -1) {
+ nKBucket = (nKBucket + insecure_rand()) % ADDRMAN_TRIED_BUCKET_COUNT;
+ nKBucketPos = (nKBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE;
+ }
int nId = vvTried[nKBucket][nKBucketPos];
assert(mapInfo.count(nId) == 1);
CAddrInfo& info = mapInfo[nId];
@@ -356,8 +358,10 @@ CAddrInfo CAddrMan::Select_()
while (1) {
int nUBucket = GetRandInt(ADDRMAN_NEW_BUCKET_COUNT);
int nUBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE);
- if (vvNew[nUBucket][nUBucketPos] == -1)
- continue;
+ while (vvNew[nUBucket][nUBucketPos] == -1) {
+ nUBucket = (nUBucket + insecure_rand()) % ADDRMAN_NEW_BUCKET_COUNT;
+ nUBucketPos = (nUBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE;
+ }
int nId = vvNew[nUBucket][nUBucketPos];
assert(mapInfo.count(nId) == 1);
CAddrInfo& info = mapInfo[nId];
diff --git a/src/addrman.h b/src/addrman.h
index 2623d89809..384b6cfdb9 100644
--- a/src/addrman.h
+++ b/src/addrman.h
@@ -265,7 +265,7 @@ public:
* Notice that vvTried, mapAddr and vVector are never encoded explicitly;
* they are instead reconstructed from the other information.
*
- * vvNew is serialized, but only used if ADDRMAN_UNKOWN_BUCKET_COUNT didn't change,
+ * vvNew is serialized, but only used if ADDRMAN_UNKNOWN_BUCKET_COUNT didn't change,
* otherwise it is reconstructed as well.
*
* This format is more complex, but significantly smaller (at most 1.5 MiB), and supports
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 1c5a312874..866c6f2d44 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -11,6 +11,12 @@
#include "utilstrencodings.h"
#include <boost/filesystem/operations.hpp>
+#include <stdio.h>
+
+#include <event2/event.h>
+#include <event2/http.h>
+#include <event2/buffer.h>
+#include <event2/keyvalq_struct.h>
#include "univalue/univalue.h"
@@ -32,9 +38,6 @@ std::string HelpMessageCli()
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
- strUsage += HelpMessageGroup(_("SSL options: (see the Bitcoin Wiki for SSL setup instructions)"));
- strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections"));
-
return strUsage;
}
@@ -92,32 +95,75 @@ static bool AppInitRPC(int argc, char* argv[])
fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n");
return false;
}
+ if (GetBoolArg("-rpcssl", false))
+ {
+ fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
+ return false;
+ }
return true;
}
-UniValue CallRPC(const string& strMethod, const UniValue& params)
+
+/** Reply structure for request_done to fill in */
+struct HTTPReply
{
- // Connect to localhost
- bool fUseSSL = GetBoolArg("-rpcssl", false);
- boost::asio::io_service io_service;
- boost::asio::ssl::context context(io_service, boost::asio::ssl::context::sslv23);
- context.set_options(boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3);
- boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslStream(io_service, context);
- SSLIOStreamDevice<boost::asio::ip::tcp> d(sslStream, fUseSSL);
- boost::iostreams::stream< SSLIOStreamDevice<boost::asio::ip::tcp> > stream(d);
-
- const bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort())));
- if (!fConnected)
- throw CConnectionFailed("couldn't connect to server");
+ int status;
+ std::string body;
+};
+
+static void http_request_done(struct evhttp_request *req, void *ctx)
+{
+ HTTPReply *reply = static_cast<HTTPReply*>(ctx);
+
+ if (req == NULL) {
+ /* If req is NULL, it means an error occurred while connecting, but
+ * I'm not sure how to find out which one. We also don't really care.
+ */
+ reply->status = 0;
+ return;
+ }
- // Find credentials to use
+ reply->status = evhttp_request_get_response_code(req);
+
+ struct evbuffer *buf = evhttp_request_get_input_buffer(req);
+ if (buf)
+ {
+ size_t size = evbuffer_get_length(buf);
+ const char *data = (const char*)evbuffer_pullup(buf, size);
+ if (data)
+ reply->body = std::string(data, size);
+ evbuffer_drain(buf, size);
+ }
+}
+
+UniValue CallRPC(const string& strMethod, const UniValue& params)
+{
+ std::string host = GetArg("-rpcconnect", "127.0.0.1");
+ int port = GetArg("-rpcport", BaseParams().RPCPort());
+
+ // Create event base
+ struct event_base *base = event_base_new(); // TODO RAII
+ if (!base)
+ throw runtime_error("cannot create event_base");
+
+ // Synchronously look up hostname
+ struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, host.c_str(), port); // TODO RAII
+ if (evcon == NULL)
+ throw runtime_error("create connection failed");
+ evhttp_connection_set_timeout(evcon, GetArg("-rpctimeout", 30));
+
+ HTTPReply response;
+ struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII
+ if (req == NULL)
+ throw runtime_error("create http request failed");
+
+ // Get credentials
std::string strRPCUserColonPass;
if (mapArgs["-rpcpassword"] == "") {
// Try fall back to cookie-based authentication if no password is provided
if (!GetAuthCookie(&strRPCUserColonPass)) {
throw runtime_error(strprintf(
- _("You must set rpcpassword=<password> in the configuration file:\n%s\n"
- "If the file does not exist, create it with owner-readable-only file permissions."),
+ _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
GetConfigFile().string().c_str()));
}
@@ -125,34 +171,41 @@ UniValue CallRPC(const string& strMethod, const UniValue& params)
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
- // HTTP basic authentication
- map<string, string> mapRequestHeaders;
- mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
-
- // Send request
- string strRequest = JSONRPCRequest(strMethod, params, 1);
- string strPost = HTTPPost(strRequest, mapRequestHeaders);
- stream << strPost << std::flush;
-
- // Receive HTTP reply status
- int nProto = 0;
- int nStatus = ReadHTTPStatus(stream, nProto);
+ struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req);
+ assert(output_headers);
+ evhttp_add_header(output_headers, "Host", host.c_str());
+ evhttp_add_header(output_headers, "Connection", "close");
+ evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
+
+ // Attach request data
+ std::string strRequest = JSONRPCRequest(strMethod, params, 1);
+ struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req);
+ assert(output_buffer);
+ evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
+
+ int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/");
+ if (r != 0) {
+ evhttp_connection_free(evcon);
+ event_base_free(base);
+ throw CConnectionFailed("send http request failed");
+ }
- // Receive HTTP reply message headers and body
- map<string, string> mapHeaders;
- string strReply;
- ReadHTTPMessage(stream, mapHeaders, strReply, nProto, std::numeric_limits<size_t>::max());
+ event_base_dispatch(base);
+ evhttp_connection_free(evcon);
+ event_base_free(base);
- if (nStatus == HTTP_UNAUTHORIZED)
+ if (response.status == 0)
+ throw CConnectionFailed("couldn't connect to server");
+ else if (response.status == HTTP_UNAUTHORIZED)
throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
- else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR)
- throw runtime_error(strprintf("server returned HTTP error %d", nStatus));
- else if (strReply.empty())
+ else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
+ throw runtime_error(strprintf("server returned HTTP error %d", response.status));
+ else if (response.body.empty())
throw runtime_error("no response from server");
// Parse reply
UniValue valReply(UniValue::VSTR);
- if (!valReply.read(strReply))
+ if (!valReply.read(response.body))
throw runtime_error("couldn't parse reply from server");
const UniValue& reply = valReply.get_obj();
if (reply.empty())
@@ -248,6 +301,10 @@ int CommandLineRPC(int argc, char *argv[])
int main(int argc, char* argv[])
{
SetupEnvironment();
+ if (!SetupNetworking()) {
+ fprintf(stderr, "Error: Initializing networking failed\n");
+ exit(1);
+ }
try {
if(!AppInitRPC(argc, argv))
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index e389d51a73..97a073174d 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -70,6 +70,7 @@ static bool AppInitRawTx(int argc, char* argv[])
strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N"));
strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N"));
strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX"));
+ strUsage += HelpMessageOpt("outdata=[VALUE:]DATA", _("Add data-based output to TX"));
strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT", _("Add raw script output to TX"));
strUsage += HelpMessageOpt("sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " +
_("This command requires JSON registers:") +
@@ -231,6 +232,35 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput)
tx.vout.push_back(txout);
}
+static void MutateTxAddOutData(CMutableTransaction& tx, const string& strInput)
+{
+ CAmount value = 0;
+
+ // separate [VALUE:]DATA in string
+ size_t pos = strInput.find(':');
+
+ if (pos==0)
+ throw runtime_error("TX output value not specified");
+
+ if (pos != string::npos) {
+ // extract and validate VALUE
+ string strValue = strInput.substr(0, pos);
+ if (!ParseMoney(strValue, value))
+ throw runtime_error("invalid TX output value");
+ }
+
+ // extract and validate DATA
+ string strData = strInput.substr(pos + 1, string::npos);
+
+ if (!IsHex(strData))
+ throw runtime_error("invalid TX output data");
+
+ std::vector<unsigned char> data = ParseHex(strData);
+
+ CTxOut txout(value, CScript() << OP_RETURN << data);
+ tx.vout.push_back(txout);
+}
+
static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput)
{
// separate VALUE:SCRIPT in string
@@ -470,6 +500,8 @@ static void MutateTx(CMutableTransaction& tx, const string& command,
MutateTxDelOutput(tx, commandVal);
else if (command == "outaddr")
MutateTxAddOutAddr(tx, commandVal);
+ else if (command == "outdata")
+ MutateTxAddOutData(tx, commandVal);
else if (command == "outscript")
MutateTxAddOutScript(tx, commandVal);
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index 39bb301f44..b512f74c22 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -10,11 +10,16 @@
#include "noui.h"
#include "scheduler.h"
#include "util.h"
+#include "httpserver.h"
+#include "httprpc.h"
+#include "rpcserver.h"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
+#include <stdio.h>
+
/* Introduction text for doxygen: */
/*! \mainpage Developer documentation
@@ -44,7 +49,7 @@ void WaitForShutdown(boost::thread_group* threadGroup)
}
if (threadGroup)
{
- threadGroup->interrupt_all();
+ Interrupt(*threadGroup);
threadGroup->join_all();
}
}
@@ -154,7 +159,7 @@ bool AppInit(int argc, char* argv[])
if (!fRet)
{
- threadGroup.interrupt_all();
+ Interrupt(threadGroup);
// threadGroup.join_all(); was left out intentionally here, because we didn't re-test all of
// the startup-failure cases to make sure they don't result in a hang due to some
// thread-blocking-waiting-for-another-thread-during-startup case
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 623104690a..9c843f6e7e 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -12,8 +12,6 @@
#include <boost/assign/list_of.hpp>
-using namespace std;
-
#include "chainparamsseeds.h"
static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward)
@@ -22,7 +20,7 @@ static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesi
txNew.nVersion = 1;
txNew.vin.resize(1);
txNew.vout.resize(1);
- txNew.vin[0].scriptSig = CScript() << 486604799 << CScriptNum(4) << vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
+ txNew.vin[0].scriptSig = CScript() << 486604799 << CScriptNum(4) << std::vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
txNew.vout[0].nValue = genesisReward;
txNew.vout[0].scriptPubKey = genesisOutputScript;
@@ -117,7 +115,7 @@ public:
fMineBlocksOnDemand = false;
fTestnetToBeDeprecatedFieldRPC = false;
- checkpointData = (Checkpoints::CCheckpointData) {
+ checkpointData = (CCheckpointData) {
boost::assign::map_list_of
( 11111, uint256S("0x0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d"))
( 33333, uint256S("0x000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6"))
@@ -190,7 +188,7 @@ public:
fMineBlocksOnDemand = false;
fTestnetToBeDeprecatedFieldRPC = true;
- checkpointData = (Checkpoints::CCheckpointData) {
+ checkpointData = (CCheckpointData) {
boost::assign::map_list_of
( 546, uint256S("000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70")),
1337966069,
@@ -239,7 +237,7 @@ public:
fMineBlocksOnDemand = true;
fTestnetToBeDeprecatedFieldRPC = false;
- checkpointData = (Checkpoints::CCheckpointData){
+ checkpointData = (CCheckpointData){
boost::assign::map_list_of
( 0, uint256S("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")),
0,
diff --git a/src/chainparams.h b/src/chainparams.h
index 66d865b620..5db39aa09c 100644
--- a/src/chainparams.h
+++ b/src/chainparams.h
@@ -7,7 +7,6 @@
#define BITCOIN_CHAINPARAMS_H
#include "chainparamsbase.h"
-#include "checkpoints.h"
#include "consensus/params.h"
#include "primitives/block.h"
#include "protocol.h"
@@ -24,6 +23,14 @@ struct SeedSpec6 {
uint16_t port;
};
+typedef std::map<int, uint256> MapCheckpoints;
+
+struct CCheckpointData {
+ MapCheckpoints mapCheckpoints;
+ int64_t nTimeLastCheckpoint;
+ int64_t nTransactionsLastCheckpoint;
+ double fTransactionsPerDay;
+};
/**
* CChainParams defines various tweakable parameters of a given instance of the
@@ -67,7 +74,7 @@ public:
const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
- const Checkpoints::CCheckpointData& Checkpoints() const { return checkpointData; }
+ const CCheckpointData& Checkpoints() const { return checkpointData; }
protected:
CChainParams() {}
@@ -87,7 +94,7 @@ protected:
bool fRequireStandard;
bool fMineBlocksOnDemand;
bool fTestnetToBeDeprecatedFieldRPC;
- Checkpoints::CCheckpointData checkpointData;
+ CCheckpointData checkpointData;
};
/**
diff --git a/src/checkpoints.h b/src/checkpoints.h
index 001e3cc801..5fce6fa81e 100644
--- a/src/checkpoints.h
+++ b/src/checkpoints.h
@@ -10,6 +10,7 @@
#include <map>
class CBlockIndex;
+struct CCheckpointData;
/**
* Block-chain checkpoints are compiled-in sanity checks.
@@ -17,14 +18,6 @@ class CBlockIndex;
*/
namespace Checkpoints
{
-typedef std::map<int, uint256> MapCheckpoints;
-
-struct CCheckpointData {
- MapCheckpoints mapCheckpoints;
- int64_t nTimeLastCheckpoint;
- int64_t nTransactionsLastCheckpoint;
- double fTransactionsPerDay;
-};
//! Return conservative estimate of total number of blocks, 0 if unknown
int GetTotalBlocksEstimate(const CCheckpointData& data);
diff --git a/src/coincontrol.h b/src/coincontrol.h
index 3e8de83c39..bc965f9e19 100644
--- a/src/coincontrol.h
+++ b/src/coincontrol.h
@@ -14,6 +14,8 @@ public:
CTxDestination destChange;
//! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs;
+ //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
+ bool fAllowWatchOnly;
CCoinControl()
{
@@ -24,6 +26,7 @@ public:
{
destChange = CNoDestination();
fAllowOtherInputs = false;
+ fAllowWatchOnly = false;
setSelected.clear();
}
diff --git a/src/consensus/validation.h b/src/consensus/validation.h
index 719e090a42..d6051edc38 100644
--- a/src/consensus/validation.h
+++ b/src/consensus/validation.h
@@ -30,14 +30,17 @@ private:
std::string strRejectReason;
unsigned int chRejectCode;
bool corruptionPossible;
+ std::string strDebugMessage;
public:
CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {}
bool DoS(int level, bool ret = false,
- unsigned int chRejectCodeIn=0, std::string strRejectReasonIn="",
- bool corruptionIn=false) {
+ unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="",
+ bool corruptionIn=false,
+ const std::string &strDebugMessageIn="") {
chRejectCode = chRejectCodeIn;
strRejectReason = strRejectReasonIn;
corruptionPossible = corruptionIn;
+ strDebugMessage = strDebugMessageIn;
if (mode == MODE_ERROR)
return ret;
nDoS += level;
@@ -45,8 +48,9 @@ public:
return ret;
}
bool Invalid(bool ret = false,
- unsigned int _chRejectCode=0, std::string _strRejectReason="") {
- return DoS(0, ret, _chRejectCode, _strRejectReason);
+ unsigned int _chRejectCode=0, const std::string &_strRejectReason="",
+ const std::string &_strDebugMessage="") {
+ return DoS(0, ret, _chRejectCode, _strRejectReason, false, _strDebugMessage);
}
bool Error(const std::string& strRejectReasonIn) {
if (mode == MODE_VALID)
@@ -75,6 +79,7 @@ public:
}
unsigned int GetRejectCode() const { return chRejectCode; }
std::string GetRejectReason() const { return strRejectReason; }
+ std::string GetDebugMessage() const { return strDebugMessage; }
};
#endif // BITCOIN_CONSENSUS_VALIDATION_H
diff --git a/src/ecwrapper.cpp b/src/ecwrapper.cpp
index 5e3aec25ba..f94bc954fd 100644
--- a/src/ecwrapper.cpp
+++ b/src/ecwrapper.cpp
@@ -13,6 +13,29 @@
namespace {
+class ecgroup_order
+{
+public:
+ static const EC_GROUP* get()
+ {
+ static const ecgroup_order wrapper;
+ return wrapper.pgroup;
+ }
+
+private:
+ ecgroup_order()
+ : pgroup(EC_GROUP_new_by_curve_name(NID_secp256k1))
+ {
+ }
+
+ ~ecgroup_order()
+ {
+ EC_GROUP_free(pgroup);
+ }
+
+ EC_GROUP* pgroup;
+};
+
/**
* Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields
* recid selects which key is recovered
@@ -92,8 +115,10 @@ err:
} // anon namespace
CECKey::CECKey() {
- pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
+ pkey = EC_KEY_new();
assert(pkey != NULL);
+ int result = EC_KEY_set_group(pkey, ecgroup_order::get());
+ assert(result);
}
CECKey::~CECKey() {
@@ -185,11 +210,9 @@ bool CECKey::TweakPublic(const unsigned char vchTweak[32]) {
bool CECKey::SanityCheck()
{
- EC_KEY *pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
- if(pkey == NULL)
+ const EC_GROUP *pgroup = ecgroup_order::get();
+ if(pgroup == NULL)
return false;
- EC_KEY_free(pkey);
-
// TODO Is there more EC functionality that could be missing?
return true;
}
diff --git a/src/httprpc.cpp b/src/httprpc.cpp
new file mode 100644
index 0000000000..98ac750bb1
--- /dev/null
+++ b/src/httprpc.cpp
@@ -0,0 +1,193 @@
+#include "httprpc.h"
+
+#include "base58.h"
+#include "chainparams.h"
+#include "httpserver.h"
+#include "rpcprotocol.h"
+#include "rpcserver.h"
+#include "random.h"
+#include "sync.h"
+#include "util.h"
+#include "utilstrencodings.h"
+#include "ui_interface.h"
+
+#include <boost/algorithm/string.hpp> // boost::trim
+
+/** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
+ * re-lock the wellet.
+ */
+class HTTPRPCTimer : public RPCTimerBase
+{
+public:
+ HTTPRPCTimer(struct event_base* eventBase, boost::function<void(void)>& func, int64_t millis) :
+ ev(eventBase, false, func)
+ {
+ struct timeval tv;
+ tv.tv_sec = millis/1000;
+ tv.tv_usec = (millis%1000)*1000;
+ ev.trigger(&tv);
+ }
+private:
+ HTTPEvent ev;
+};
+
+class HTTPRPCTimerInterface : public RPCTimerInterface
+{
+public:
+ HTTPRPCTimerInterface(struct event_base* base) : base(base)
+ {
+ }
+ const char* Name()
+ {
+ return "HTTP";
+ }
+ RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis)
+ {
+ return new HTTPRPCTimer(base, func, millis);
+ }
+private:
+ struct event_base* base;
+};
+
+
+/* Pre-base64-encoded authentication token */
+static std::string strRPCUserColonPass;
+/* Stored RPC timer interface (for unregistration) */
+static HTTPRPCTimerInterface* httpRPCTimerInterface = 0;
+
+static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id)
+{
+ // Send error reply from json-rpc error object
+ int nStatus = HTTP_INTERNAL_SERVER_ERROR;
+ int code = find_value(objError, "code").get_int();
+
+ if (code == RPC_INVALID_REQUEST)
+ nStatus = HTTP_BAD_REQUEST;
+ else if (code == RPC_METHOD_NOT_FOUND)
+ nStatus = HTTP_NOT_FOUND;
+
+ std::string strReply = JSONRPCReply(NullUniValue, objError, id);
+
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(nStatus, strReply);
+}
+
+static bool RPCAuthorized(const std::string& strAuth)
+{
+ if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
+ return false;
+ if (strAuth.substr(0, 6) != "Basic ")
+ return false;
+ std::string strUserPass64 = strAuth.substr(6);
+ boost::trim(strUserPass64);
+ std::string strUserPass = DecodeBase64(strUserPass64);
+ return TimingResistantEqual(strUserPass, strRPCUserColonPass);
+}
+
+static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
+{
+ // JSONRPC handles only POST
+ if (req->GetRequestMethod() != HTTPRequest::POST) {
+ req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
+ return false;
+ }
+ // Check authorization
+ std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
+ if (!authHeader.first) {
+ req->WriteReply(HTTP_UNAUTHORIZED);
+ return false;
+ }
+
+ if (!RPCAuthorized(authHeader.second)) {
+ LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString());
+
+ /* Deter brute-forcing
+ If this results in a DoS the user really
+ shouldn't have their RPC port exposed. */
+ MilliSleep(250);
+
+ req->WriteReply(HTTP_UNAUTHORIZED);
+ return false;
+ }
+
+ JSONRequest jreq;
+ try {
+ // Parse request
+ UniValue valRequest;
+ if (!valRequest.read(req->ReadBody()))
+ throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
+
+ std::string strReply;
+ // singleton request
+ if (valRequest.isObject()) {
+ jreq.parse(valRequest);
+
+ UniValue result = tableRPC.execute(jreq.strMethod, jreq.params);
+
+ // Send reply
+ strReply = JSONRPCReply(result, NullUniValue, jreq.id);
+
+ // array of requests
+ } else if (valRequest.isArray())
+ strReply = JSONRPCExecBatch(valRequest.get_array());
+ else
+ throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
+
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strReply);
+ } catch (const UniValue& objError) {
+ JSONErrorReply(req, objError, jreq.id);
+ return false;
+ } catch (const std::exception& e) {
+ JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
+ return false;
+ }
+ return true;
+}
+
+static bool InitRPCAuthentication()
+{
+ if (mapArgs["-rpcpassword"] == "")
+ {
+ LogPrintf("No rpcpassword set - using random cookie authentication\n");
+ if (!GenerateAuthCookie(&strRPCUserColonPass)) {
+ uiInterface.ThreadSafeMessageBox(
+ _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode
+ "", CClientUIInterface::MSG_ERROR);
+ return false;
+ }
+ } else {
+ strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
+ }
+ return true;
+}
+
+bool StartHTTPRPC()
+{
+ LogPrint("rpc", "Starting HTTP RPC server\n");
+ if (!InitRPCAuthentication())
+ return false;
+
+ RegisterHTTPHandler("/", true, HTTPReq_JSONRPC);
+
+ assert(EventBase());
+ httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase());
+ RPCRegisterTimerInterface(httpRPCTimerInterface);
+ return true;
+}
+
+void InterruptHTTPRPC()
+{
+ LogPrint("rpc", "Interrupting HTTP RPC server\n");
+}
+
+void StopHTTPRPC()
+{
+ LogPrint("rpc", "Stopping HTTP RPC server\n");
+ UnregisterHTTPHandler("/", true);
+ if (httpRPCTimerInterface) {
+ RPCUnregisterTimerInterface(httpRPCTimerInterface);
+ delete httpRPCTimerInterface;
+ httpRPCTimerInterface = 0;
+ }
+}
diff --git a/src/httprpc.h b/src/httprpc.h
new file mode 100644
index 0000000000..d354457188
--- /dev/null
+++ b/src/httprpc.h
@@ -0,0 +1,37 @@
+// Copyright (c) 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_HTTPRPC_H
+#define BITCOIN_HTTPRPC_H
+
+#include <string>
+#include <map>
+
+class HTTPRequest;
+
+/** Start HTTP RPC subsystem.
+ * Precondition; HTTP and RPC has been started.
+ */
+bool StartHTTPRPC();
+/** Interrupt HTTP RPC subsystem.
+ */
+void InterruptHTTPRPC();
+/** Stop HTTP RPC subsystem.
+ * Precondition; HTTP and RPC has been stopped.
+ */
+void StopHTTPRPC();
+
+/** Start HTTP REST subsystem.
+ * Precondition; HTTP and RPC has been started.
+ */
+bool StartREST();
+/** Interrupt RPC REST subsystem.
+ */
+void InterruptREST();
+/** Stop HTTP REST subsystem.
+ * Precondition; HTTP and RPC has been stopped.
+ */
+void StopREST();
+
+#endif
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
new file mode 100644
index 0000000000..baca007571
--- /dev/null
+++ b/src/httpserver.cpp
@@ -0,0 +1,575 @@
+// Copyright (c) 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 "httpserver.h"
+
+#include "chainparamsbase.h"
+#include "compat.h"
+#include "util.h"
+#include "netbase.h"
+#include "rpcprotocol.h" // For HTTP status codes
+#include "sync.h"
+#include "ui_interface.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+
+#include <event2/event.h>
+#include <event2/http.h>
+#include <event2/thread.h>
+#include <event2/buffer.h>
+#include <event2/util.h>
+#include <event2/keyvalq_struct.h>
+
+#ifdef EVENT__HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#ifdef _XOPEN_SOURCE_EXTENDED
+#include <arpa/inet.h>
+#endif
+#endif
+
+#include <boost/algorithm/string/case_conv.hpp> // for to_lower()
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+/** HTTP request work item */
+class HTTPWorkItem : public HTTPClosure
+{
+public:
+ HTTPWorkItem(HTTPRequest* req, const std::string &path, const HTTPRequestHandler& func):
+ req(req), path(path), func(func)
+ {
+ }
+ void operator()()
+ {
+ func(req.get(), path);
+ }
+
+ boost::scoped_ptr<HTTPRequest> req;
+
+private:
+ std::string path;
+ HTTPRequestHandler func;
+};
+
+/** Simple work queue for distributing work over multiple threads.
+ * Work items are simply callable objects.
+ */
+template <typename WorkItem>
+class WorkQueue
+{
+private:
+ /** Mutex protects entire object */
+ CWaitableCriticalSection cs;
+ CConditionVariable cond;
+ /* XXX in C++11 we can use std::unique_ptr here and avoid manual cleanup */
+ std::deque<WorkItem*> queue;
+ bool running;
+ size_t maxDepth;
+
+public:
+ WorkQueue(size_t maxDepth) : running(true),
+ maxDepth(maxDepth)
+ {
+ }
+ /* Precondition: worker threads have all stopped */
+ ~WorkQueue()
+ {
+ while (!queue.empty()) {
+ delete queue.front();
+ queue.pop_front();
+ }
+ }
+ /** Enqueue a work item */
+ bool Enqueue(WorkItem* item)
+ {
+ boost::unique_lock<boost::mutex> lock(cs);
+ if (queue.size() >= maxDepth) {
+ return false;
+ }
+ queue.push_back(item);
+ cond.notify_one();
+ return true;
+ }
+ /** Thread function */
+ void Run()
+ {
+ while (running) {
+ WorkItem* i = 0;
+ {
+ boost::unique_lock<boost::mutex> lock(cs);
+ while (running && queue.empty())
+ cond.wait(lock);
+ if (!running)
+ break;
+ i = queue.front();
+ queue.pop_front();
+ }
+ (*i)();
+ delete i;
+ }
+ }
+ /** Interrupt and exit loops */
+ void Interrupt()
+ {
+ boost::unique_lock<boost::mutex> lock(cs);
+ running = false;
+ cond.notify_all();
+ }
+
+ /** Return current depth of queue */
+ size_t Depth()
+ {
+ boost::unique_lock<boost::mutex> lock(cs);
+ return queue.size();
+ }
+};
+
+struct HTTPPathHandler
+{
+ HTTPPathHandler() {}
+ HTTPPathHandler(std::string prefix, bool exactMatch, HTTPRequestHandler handler):
+ prefix(prefix), exactMatch(exactMatch), handler(handler)
+ {
+ }
+ std::string prefix;
+ bool exactMatch;
+ HTTPRequestHandler handler;
+};
+
+/** HTTP module state */
+
+//! libevent event loop
+static struct event_base* eventBase = 0;
+//! HTTP server
+struct evhttp* eventHTTP = 0;
+//! List of subnets to allow RPC connections from
+static std::vector<CSubNet> rpc_allow_subnets;
+//! Work queue for handling longer requests off the event loop thread
+static WorkQueue<HTTPClosure>* workQueue = 0;
+//! Handlers for (sub)paths
+std::vector<HTTPPathHandler> pathHandlers;
+
+/** Check if a network address is allowed to access the HTTP server */
+static bool ClientAllowed(const CNetAddr& netaddr)
+{
+ if (!netaddr.IsValid())
+ return false;
+ BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets)
+ if (subnet.Match(netaddr))
+ return true;
+ return false;
+}
+
+/** Initialize ACL list for HTTP server */
+static bool InitHTTPAllowList()
+{
+ rpc_allow_subnets.clear();
+ rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet
+ rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost
+ if (mapMultiArgs.count("-rpcallowip")) {
+ const std::vector<std::string>& vAllow = mapMultiArgs["-rpcallowip"];
+ BOOST_FOREACH (std::string strAllow, vAllow) {
+ CSubNet subnet(strAllow);
+ if (!subnet.IsValid()) {
+ uiInterface.ThreadSafeMessageBox(
+ strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow),
+ "", CClientUIInterface::MSG_ERROR);
+ return false;
+ }
+ rpc_allow_subnets.push_back(subnet);
+ }
+ }
+ std::string strAllowed;
+ BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets)
+ strAllowed += subnet.ToString() + " ";
+ LogPrint("http", "Allowing HTTP connections from: %s\n", strAllowed);
+ return true;
+}
+
+/** HTTP request method as string - use for logging only */
+static std::string RequestMethodString(HTTPRequest::RequestMethod m)
+{
+ switch (m) {
+ case HTTPRequest::GET:
+ return "GET";
+ break;
+ case HTTPRequest::POST:
+ return "POST";
+ break;
+ case HTTPRequest::HEAD:
+ return "HEAD";
+ break;
+ case HTTPRequest::PUT:
+ return "PUT";
+ break;
+ default:
+ return "unknown";
+ }
+}
+
+/** HTTP request callback */
+static void http_request_cb(struct evhttp_request* req, void* arg)
+{
+ std::auto_ptr<HTTPRequest> hreq(new HTTPRequest(req));
+
+ LogPrint("http", "Received a %s request for %s from %s\n",
+ RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString());
+
+ // Early address-based allow check
+ if (!ClientAllowed(hreq->GetPeer())) {
+ hreq->WriteReply(HTTP_FORBIDDEN);
+ return;
+ }
+
+ // Early reject unknown HTTP methods
+ if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) {
+ hreq->WriteReply(HTTP_BADMETHOD);
+ return;
+ }
+
+ // Find registered handler for prefix
+ std::string strURI = hreq->GetURI();
+ std::string path;
+ std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin();
+ std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end();
+ for (; i != iend; ++i) {
+ bool match = false;
+ if (i->exactMatch)
+ match = (strURI == i->prefix);
+ else
+ match = (strURI.substr(0, i->prefix.size()) == i->prefix);
+ if (match) {
+ path = strURI.substr(i->prefix.size());
+ break;
+ }
+ }
+
+ // Dispatch to worker thread
+ if (i != iend) {
+ std::auto_ptr<HTTPWorkItem> item(new HTTPWorkItem(hreq.release(), path, i->handler));
+ assert(workQueue);
+ if (workQueue->Enqueue(item.get()))
+ item.release(); /* if true, queue took ownership */
+ else
+ item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded");
+ } else {
+ hreq->WriteReply(HTTP_NOTFOUND);
+ }
+}
+
+/** Event dispatcher thread */
+static void ThreadHTTP(struct event_base* base, struct evhttp* http)
+{
+ RenameThread("bitcoin-http");
+ LogPrint("http", "Entering http event loop\n");
+ event_base_dispatch(base);
+ // Event loop will be interrupted by InterruptHTTPServer()
+ LogPrint("http", "Exited http event loop\n");
+}
+
+/** Bind HTTP server to specified addresses */
+static bool HTTPBindAddresses(struct evhttp* http)
+{
+ int defaultPort = GetArg("-rpcport", BaseParams().RPCPort());
+ int nBound = 0;
+ std::vector<std::pair<std::string, uint16_t> > endpoints;
+
+ // Determine what addresses to bind to
+ if (!mapArgs.count("-rpcallowip")) { // Default to loopback if not allowing external IPs
+ endpoints.push_back(std::make_pair("::1", defaultPort));
+ endpoints.push_back(std::make_pair("127.0.0.1", defaultPort));
+ if (mapArgs.count("-rpcbind")) {
+ LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n");
+ }
+ } else if (mapArgs.count("-rpcbind")) { // Specific bind address
+ const std::vector<std::string>& vbind = mapMultiArgs["-rpcbind"];
+ for (std::vector<std::string>::const_iterator i = vbind.begin(); i != vbind.end(); ++i) {
+ int port = defaultPort;
+ std::string host;
+ SplitHostPort(*i, port, host);
+ endpoints.push_back(std::make_pair(host, port));
+ }
+ } else { // No specific bind address specified, bind to any
+ endpoints.push_back(std::make_pair("::", defaultPort));
+ endpoints.push_back(std::make_pair("0.0.0.0", defaultPort));
+ }
+
+ // Bind addresses
+ for (std::vector<std::pair<std::string, uint16_t> >::iterator i = endpoints.begin(); i != endpoints.end(); ++i) {
+ LogPrint("http", "Binding RPC on address %s port %i\n", i->first, i->second);
+ if (evhttp_bind_socket(http, i->first.empty() ? NULL : i->first.c_str(), i->second) == 0) {
+ nBound += 1;
+ } else {
+ LogPrintf("Binding RPC on address %s port %i failed.\n", i->first, i->second);
+ }
+ }
+ return nBound > 0;
+}
+
+/** Simple wrapper to set thread name and run work queue */
+static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue)
+{
+ RenameThread("bitcoin-httpworker");
+ queue->Run();
+}
+
+bool InitHTTPServer()
+{
+ struct evhttp* http = 0;
+ struct event_base* base = 0;
+
+ if (!InitHTTPAllowList())
+ return false;
+
+ if (GetBoolArg("-rpcssl", false)) {
+ uiInterface.ThreadSafeMessageBox(
+ "SSL mode for RPC (-rpcssl) is no longer supported.",
+ "", CClientUIInterface::MSG_ERROR);
+ return false;
+ }
+
+#ifdef WIN32
+ evthread_use_windows_threads();
+#else
+ evthread_use_pthreads();
+#endif
+
+ base = event_base_new(); // XXX RAII
+ if (!base) {
+ LogPrintf("Couldn't create an event_base: exiting\n");
+ return false;
+ }
+
+ /* Create a new evhttp object to handle requests. */
+ http = evhttp_new(base); // XXX RAII
+ if (!http) {
+ LogPrintf("couldn't create evhttp. Exiting.\n");
+ event_base_free(base);
+ return false;
+ }
+
+ evhttp_set_timeout(http, GetArg("-rpctimeout", DEFAULT_HTTP_TIMEOUT));
+ evhttp_set_max_body_size(http, MAX_SIZE);
+ evhttp_set_gencb(http, http_request_cb, NULL);
+
+ if (!HTTPBindAddresses(http)) {
+ LogPrintf("Unable to bind any endpoint for RPC server\n");
+ evhttp_free(http);
+ event_base_free(base);
+ return false;
+ }
+
+ LogPrint("http", "Initialized HTTP server\n");
+ int workQueueDepth = std::max((long)GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L);
+ LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth);
+
+ workQueue = new WorkQueue<HTTPClosure>(workQueueDepth);
+ eventBase = base;
+ eventHTTP = http;
+ return true;
+}
+
+bool StartHTTPServer(boost::thread_group& threadGroup)
+{
+ LogPrint("http", "Starting HTTP server\n");
+ int rpcThreads = std::max((long)GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L);
+ LogPrintf("HTTP: starting %d worker threads\n", rpcThreads);
+ threadGroup.create_thread(boost::bind(&ThreadHTTP, eventBase, eventHTTP));
+
+ for (int i = 0; i < rpcThreads; i++)
+ threadGroup.create_thread(boost::bind(&HTTPWorkQueueRun, workQueue));
+ return true;
+}
+
+void InterruptHTTPServer()
+{
+ LogPrint("http", "Interrupting HTTP server\n");
+ if (eventBase)
+ event_base_loopbreak(eventBase);
+ if (workQueue)
+ workQueue->Interrupt();
+}
+
+void StopHTTPServer()
+{
+ LogPrint("http", "Stopping HTTP server\n");
+ delete workQueue;
+ if (eventHTTP) {
+ evhttp_free(eventHTTP);
+ eventHTTP = 0;
+ }
+ if (eventBase) {
+ event_base_free(eventBase);
+ eventBase = 0;
+ }
+}
+
+struct event_base* EventBase()
+{
+ return eventBase;
+}
+
+static void httpevent_callback_fn(evutil_socket_t, short, void* data)
+{
+ // Static handler: simply call inner handler
+ HTTPEvent *self = ((HTTPEvent*)data);
+ self->handler();
+ if (self->deleteWhenTriggered)
+ delete self;
+}
+
+HTTPEvent::HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const boost::function<void(void)>& handler):
+ deleteWhenTriggered(deleteWhenTriggered), handler(handler)
+{
+ ev = event_new(base, -1, 0, httpevent_callback_fn, this);
+ assert(ev);
+}
+HTTPEvent::~HTTPEvent()
+{
+ event_free(ev);
+}
+void HTTPEvent::trigger(struct timeval* tv)
+{
+ if (tv == NULL)
+ event_active(ev, 0, 0); // immediately trigger event in main thread
+ else
+ evtimer_add(ev, tv); // trigger after timeval passed
+}
+HTTPRequest::HTTPRequest(struct evhttp_request* req) : req(req),
+ replySent(false)
+{
+}
+HTTPRequest::~HTTPRequest()
+{
+ if (!replySent) {
+ // Keep track of whether reply was sent to avoid request leaks
+ LogPrintf("%s: Unhandled request\n", __func__);
+ WriteReply(HTTP_INTERNAL, "Unhandled request");
+ }
+ // evhttpd cleans up the request, as long as a reply was sent.
+}
+
+std::pair<bool, std::string> HTTPRequest::GetHeader(const std::string& hdr)
+{
+ const struct evkeyvalq* headers = evhttp_request_get_input_headers(req);
+ assert(headers);
+ const char* val = evhttp_find_header(headers, hdr.c_str());
+ if (val)
+ return std::make_pair(true, val);
+ else
+ return std::make_pair(false, "");
+}
+
+std::string HTTPRequest::ReadBody()
+{
+ struct evbuffer* buf = evhttp_request_get_input_buffer(req);
+ if (!buf)
+ return "";
+ size_t size = evbuffer_get_length(buf);
+ /** Trivial implementation: if this is ever a performance bottleneck,
+ * internal copying can be avoided in multi-segment buffers by using
+ * evbuffer_peek and an awkward loop. Though in that case, it'd be even
+ * better to not copy into an intermediate string but use a stream
+ * abstraction to consume the evbuffer on the fly in the parsing algorithm.
+ */
+ const char* data = (const char*)evbuffer_pullup(buf, size);
+ if (!data) // returns NULL in case of empty buffer
+ return "";
+ std::string rv(data, size);
+ evbuffer_drain(buf, size);
+ return rv;
+}
+
+void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value)
+{
+ struct evkeyvalq* headers = evhttp_request_get_output_headers(req);
+ assert(headers);
+ evhttp_add_header(headers, hdr.c_str(), value.c_str());
+}
+
+/** Closure sent to main thread to request a reply to be sent to
+ * a HTTP request.
+ * Replies must be sent in the main loop in the main http thread,
+ * this cannot be done from worker threads.
+ */
+void HTTPRequest::WriteReply(int nStatus, const std::string& strReply)
+{
+ assert(!replySent && req);
+ // Send event to main http thread to send reply message
+ struct evbuffer* evb = evhttp_request_get_output_buffer(req);
+ assert(evb);
+ evbuffer_add(evb, strReply.data(), strReply.size());
+ HTTPEvent* ev = new HTTPEvent(eventBase, true,
+ boost::bind(evhttp_send_reply, req, nStatus, (const char*)NULL, (struct evbuffer *)NULL));
+ ev->trigger(0);
+ replySent = true;
+ req = 0; // transferred back to main thread
+}
+
+CService HTTPRequest::GetPeer()
+{
+ evhttp_connection* con = evhttp_request_get_connection(req);
+ CService peer;
+ if (con) {
+ // evhttp retains ownership over returned address string
+ const char* address = "";
+ uint16_t port = 0;
+ evhttp_connection_get_peer(con, (char**)&address, &port);
+ peer = CService(address, port);
+ }
+ return peer;
+}
+
+std::string HTTPRequest::GetURI()
+{
+ return evhttp_request_get_uri(req);
+}
+
+HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod()
+{
+ switch (evhttp_request_get_command(req)) {
+ case EVHTTP_REQ_GET:
+ return GET;
+ break;
+ case EVHTTP_REQ_POST:
+ return POST;
+ break;
+ case EVHTTP_REQ_HEAD:
+ return HEAD;
+ break;
+ case EVHTTP_REQ_PUT:
+ return PUT;
+ break;
+ default:
+ return UNKNOWN;
+ break;
+ }
+}
+
+void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler)
+{
+ LogPrint("http", "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch);
+ pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler));
+}
+
+void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
+{
+ std::vector<HTTPPathHandler>::iterator i = pathHandlers.begin();
+ std::vector<HTTPPathHandler>::iterator iend = pathHandlers.end();
+ for (; i != iend; ++i)
+ if (i->prefix == prefix && i->exactMatch == exactMatch)
+ break;
+ if (i != iend)
+ {
+ LogPrint("http", "Unregistering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch);
+ pathHandlers.erase(i);
+ }
+}
+
diff --git a/src/httpserver.h b/src/httpserver.h
new file mode 100644
index 0000000000..459c60c047
--- /dev/null
+++ b/src/httpserver.h
@@ -0,0 +1,149 @@
+// Copyright (c) 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_HTTPSERVER_H
+#define BITCOIN_HTTPSERVER_H
+
+#include <string>
+#include <stdint.h>
+#include <boost/thread.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/function.hpp>
+
+static const int DEFAULT_HTTP_THREADS=4;
+static const int DEFAULT_HTTP_WORKQUEUE=16;
+static const int DEFAULT_HTTP_TIMEOUT=30;
+
+struct evhttp_request;
+struct event_base;
+class CService;
+class HTTPRequest;
+
+/** Initialize HTTP server.
+ * Call this before RegisterHTTPHandler or EventBase().
+ */
+bool InitHTTPServer();
+/** Start HTTP server.
+ * This is separate from InitHTTPServer to give users race-condition-free time
+ * to register their handlers between InitHTTPServer and StartHTTPServer.
+ */
+bool StartHTTPServer(boost::thread_group& threadGroup);
+/** Interrupt HTTP server threads */
+void InterruptHTTPServer();
+/** Stop HTTP server */
+void StopHTTPServer();
+
+/** Handler for requests to a certain HTTP path */
+typedef boost::function<void(HTTPRequest* req, const std::string &)> HTTPRequestHandler;
+/** Register handler for prefix.
+ * If multiple handlers match a prefix, the first-registered one will
+ * be invoked.
+ */
+void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler);
+/** Unregister handler for prefix */
+void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch);
+
+/** Return evhttp event base. This can be used by submodules to
+ * queue timers or custom events.
+ */
+struct event_base* EventBase();
+
+/** In-flight HTTP request.
+ * Thin C++ wrapper around evhttp_request.
+ */
+class HTTPRequest
+{
+private:
+ struct evhttp_request* req;
+ bool replySent;
+
+public:
+ HTTPRequest(struct evhttp_request* req);
+ ~HTTPRequest();
+
+ enum RequestMethod {
+ UNKNOWN,
+ GET,
+ POST,
+ HEAD,
+ PUT
+ };
+
+ /** Get requested URI.
+ */
+ std::string GetURI();
+
+ /** Get CService (address:ip) for the origin of the http request.
+ */
+ CService GetPeer();
+
+ /** Get request method.
+ */
+ RequestMethod GetRequestMethod();
+
+ /**
+ * Get the request header specified by hdr, or an empty string.
+ * Return an pair (isPresent,string).
+ */
+ std::pair<bool, std::string> GetHeader(const std::string& hdr);
+
+ /**
+ * Read request body.
+ *
+ * @note As this consumes the underlying buffer, call this only once.
+ * Repeated calls will return an empty string.
+ */
+ std::string ReadBody();
+
+ /**
+ * Write output header.
+ *
+ * @note call this before calling WriteErrorReply or Reply.
+ */
+ void WriteHeader(const std::string& hdr, const std::string& value);
+
+ /**
+ * Write HTTP reply.
+ * nStatus is the HTTP status code to send.
+ * strReply is the body of the reply. Keep it empty to send a standard message.
+ *
+ * @note Can be called only once. As this will give the request back to the
+ * main thread, do not call any other HTTPRequest methods after calling this.
+ */
+ void WriteReply(int nStatus, const std::string& strReply = "");
+};
+
+/** Event handler closure.
+ */
+class HTTPClosure
+{
+public:
+ virtual void operator()() = 0;
+ virtual ~HTTPClosure() {}
+};
+
+/** Event class. This can be used either as an cross-thread trigger or as a timer.
+ */
+class HTTPEvent
+{
+public:
+ /** Create a new event.
+ * deleteWhenTriggered deletes this event object after the event is triggered (and the handler called)
+ * handler is the handler to call when the event is triggered.
+ */
+ HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const boost::function<void(void)>& handler);
+ ~HTTPEvent();
+
+ /** Trigger the event. If tv is 0, trigger it immediately. Otherwise trigger it after
+ * the given time has elapsed.
+ */
+ void trigger(struct timeval* tv);
+
+ bool deleteWhenTriggered;
+ boost::function<void(void)> handler;
+private:
+ struct event* ev;
+};
+
+#endif // BITCOIN_HTTPSERVER_H
diff --git a/src/init.cpp b/src/init.cpp
index ef1c39db25..f03388120c 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -16,6 +16,8 @@
#include "checkpoints.h"
#include "compat/sanity.h"
#include "consensus/validation.h"
+#include "httpserver.h"
+#include "httprpc.h"
#include "key.h"
#include "main.h"
#include "miner.h"
@@ -36,7 +38,6 @@
#include "wallet/wallet.h"
#include "wallet/walletdb.h"
#endif
-
#include <stdint.h>
#include <stdio.h>
@@ -53,6 +54,10 @@
#include <boost/thread.hpp>
#include <openssl/crypto.h>
+#if ENABLE_ZMQ
+#include "zmq/zmqnotificationinterface.h"
+#endif
+
using namespace std;
#ifdef ENABLE_WALLET
@@ -60,6 +65,10 @@ CWallet* pwalletMain = NULL;
#endif
bool fFeeEstimatesInitialized = false;
+#if ENABLE_ZMQ
+static CZMQNotificationInterface* pzmqNotificationInterface = NULL;
+#endif
+
#ifdef WIN32
// Win32 LevelDB doesn't use filedescriptors, and the ones used for
// accessing block files don't count towards the fd_set size limit
@@ -144,6 +153,15 @@ public:
static CCoinsViewDB *pcoinsdbview = NULL;
static CCoinsViewErrorCatcher *pcoinscatcher = NULL;
+void Interrupt(boost::thread_group& threadGroup)
+{
+ InterruptHTTPServer();
+ InterruptHTTPRPC();
+ InterruptRPC();
+ InterruptREST();
+ threadGroup.interrupt_all();
+}
+
void Shutdown()
{
LogPrintf("%s: In progress...\n", __func__);
@@ -158,7 +176,11 @@ void Shutdown()
/// module was initialized.
RenameThread("bitcoin-shutoff");
mempool.AddTransactionsUpdated(1);
- StopRPCThreads();
+
+ StopHTTPRPC();
+ StopREST();
+ StopRPC();
+ StopHTTPServer();
#ifdef ENABLE_WALLET
if (pwalletMain)
pwalletMain->Flush(false);
@@ -196,6 +218,16 @@ void Shutdown()
if (pwalletMain)
pwalletMain->Flush(true);
#endif
+
+#if ENABLE_ZMQ
+ if (pzmqNotificationInterface) {
+ UnregisterValidationInterface(pzmqNotificationInterface);
+ pzmqNotificationInterface->Shutdown();
+ delete pzmqNotificationInterface;
+ pzmqNotificationInterface = NULL;
+ }
+#endif
+
#ifndef WIN32
try {
boost::filesystem::remove(GetPidFile());
@@ -293,7 +325,7 @@ std::string HelpMessage(HelpMessageMode mode)
#ifndef WIN32
strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "bitcoind.pid"));
#endif
- strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by pruning (deleting) old blocks. This mode disables wallet support and is incompatible with -txindex. "
+ strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by pruning (deleting) old blocks. This mode is incompatible with -txindex and -rescan. "
"Warning: Reverting this setting requires re-downloading the entire blockchain. "
"(default: 0 = disable pruning blocks, >%u = target size in MiB to use for block files)"), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
strUsage += HelpMessageOpt("-reindex", _("Rebuild block chain index from current blk000??.dat files on startup"));
@@ -335,7 +367,6 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6"));
strUsage += HelpMessageOpt("-whitelist=<netmask>", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") +
" " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway"));
- strUsage += HelpMessageOpt("-whiteconnections=<n>", strprintf(_("Reserve this many inbound connections for whitelisted peers (default: %d)"), 0));
#ifdef ENABLE_WALLET
strUsage += HelpMessageGroup(_("Wallet options:"));
@@ -361,6 +392,14 @@ std::string HelpMessage(HelpMessageMode mode)
" " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)"));
#endif
+#if ENABLE_ZMQ
+ strUsage += HelpMessageGroup(_("ZeroMQ notification options:"));
+ strUsage += HelpMessageOpt("-zmqpubhashblock=<address>", _("Enable publish hash block in <address>"));
+ strUsage += HelpMessageOpt("-zmqpubhashtransaction=<address>", _("Enable publish hash transaction in <address>"));
+ strUsage += HelpMessageOpt("-zmqpubrawblock=<address>", _("Enable publish raw block in <address>"));
+ strUsage += HelpMessageOpt("-zmqpubrawtransaction=<address>", _("Enable publish raw transaction in <address>"));
+#endif
+
strUsage += HelpMessageGroup(_("Debugging/Testing options:"));
if (showDebug)
{
@@ -373,7 +412,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", 1));
strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", 0));
}
- string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, net, proxy, prune"; // Don't translate these and qt below
+ string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, mempoolrej, net, proxy, prune, http"; // Don't translate these and qt below
if (mode == HMM_BITCOIN_QT)
debugCategories += ", qt";
strUsage += HelpMessageOpt("-debug=<category>", strprintf(_("Output debugging information (default: %u, supplying <category> is optional)"), 0) + ". " +
@@ -423,14 +462,11 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)"), 8332, 18332));
strUsage += HelpMessageOpt("-rpcallowip=<ip>", _("Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times"));
- strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), 4));
- strUsage += HelpMessageOpt("-rpckeepalive", strprintf(_("RPC support for HTTP persistent connections (default: %d)"), 1));
-
- strUsage += HelpMessageGroup(_("RPC SSL options: (see the Bitcoin Wiki for SSL setup instructions)"));
- strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections"));
- strUsage += HelpMessageOpt("-rpcsslcertificatechainfile=<file.cert>", strprintf(_("Server certificate file (default: %s)"), "server.cert"));
- strUsage += HelpMessageOpt("-rpcsslprivatekeyfile=<file.pem>", strprintf(_("Server private key (default: %s)"), "server.pem"));
- strUsage += HelpMessageOpt("-rpcsslciphers=<ciphers>", strprintf(_("Acceptable ciphers (default: %s)"), "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH"));
+ strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), DEFAULT_HTTP_THREADS));
+ if (showDebug) {
+ strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE));
+ strUsage += HelpMessageOpt("-rpctimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_TIMEOUT));
+ }
if (mode == HMM_BITCOIN_QT)
{
@@ -602,6 +638,23 @@ bool InitSanityCheck(void)
return true;
}
+bool AppInitServers(boost::thread_group& threadGroup)
+{
+ RPCServer::OnStopped(&OnRPCStopped);
+ RPCServer::OnPreCommand(&OnRPCPreCommand);
+ if (!InitHTTPServer())
+ return false;
+ if (!StartRPC())
+ return false;
+ if (!StartHTTPRPC())
+ return false;
+ if (GetBoolArg("-rest", false) && !StartREST())
+ return false;
+ if (!StartHTTPServer(threadGroup))
+ return false;
+ return true;
+}
+
/** Initialize bitcoin.
* @pre Parameters should be parsed and config file should be read.
*/
@@ -629,17 +682,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD);
PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy");
if (setProcDEPPol != NULL) setProcDEPPol(PROCESS_DEP_ENABLE);
-
- // Initialize Windows Sockets
- WSADATA wsadata;
- int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
- if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2)
- {
- return InitError(strprintf("Error: Winsock library failed to start (WSAStartup returned error %d)", ret));
- }
#endif
-#ifndef WIN32
+ if (!SetupNetworking())
+ return InitError("Error: Initializing networking failed");
+
+#ifndef WIN32
if (GetBoolArg("-sysperms", false)) {
#ifdef ENABLE_WALLET
if (!GetBoolArg("-disablewallet", false))
@@ -754,25 +802,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1);
int nUserMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
nMaxConnections = std::max(nUserMaxConnections, 0);
- int nUserWhiteConnections = GetArg("-whiteconnections", 0);
- nWhiteConnections = std::max(nUserWhiteConnections, 0);
-
- if ((mapArgs.count("-whitelist")) || (mapArgs.count("-whitebind"))) {
- if (!(mapArgs.count("-maxconnections"))) {
- // User is using whitelist feature,
- // but did not specify -maxconnections parameter.
- // Silently increase the default to compensate,
- // so that the whitelist connection reservation feature
- // does not inadvertently reduce the default
- // inbound connection capacity of the network.
- nMaxConnections += nWhiteConnections;
- }
- } else {
- // User not using whitelist feature.
- // Silently disable connection reservation,
- // for the same reason as above.
- nWhiteConnections = 0;
- }
// Trim requested connection counts, to fit into system limitations
nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0);
@@ -784,13 +813,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (nMaxConnections < nUserMaxConnections)
InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections));
- // Connection capacity is prioritized in this order:
- // outbound connections (hardcoded to 8),
- // then whitelisted connections,
- // then non-whitelisted connections get whatever's left (if any).
- if ((nWhiteConnections > 0) && (nWhiteConnections >= (nMaxConnections - 8)))
- InitWarning(strprintf(_("All non-whitelisted incoming connections will be dropped, because -whiteconnections is %d and -maxconnections is only %d."), nWhiteConnections, nMaxConnections));
-
// ********************************************************* Step 3: parameter-to-internal-flags
fDebug = !mapMultiArgs["-debug"].empty();
@@ -921,6 +943,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
// Option to startup with mocktime set (used for regression testing):
SetMockTime(GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op
+ if (GetBoolArg("-peerbloomfilters", true))
+ nLocalServices |= NODE_BLOOM;
+
// ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log
// Initialize elliptic curve code
@@ -968,8 +993,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
LogPrintf("Using data directory %s\n", strDataDir);
LogPrintf("Using config file %s\n", GetConfigFile().string());
LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD);
- if (nWhiteConnections > 0)
- LogPrintf("Reserving %i of these connections for whitelisted inbound peers\n", nWhiteConnections);
std::ostringstream strErrors;
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
@@ -990,9 +1013,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (fServer)
{
uiInterface.InitMessage.connect(SetRPCWarmupStatus);
- RPCServer::OnStopped(&OnRPCStopped);
- RPCServer::OnPreCommand(&OnRPCPreCommand);
- StartRPCThreads();
+ if (!AppInitServers(threadGroup))
+ return InitError(_("Unable to start HTTP server. See debug log for details."));
}
int64_t nStart;
@@ -1128,6 +1150,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
BOOST_FOREACH(const std::string& strDest, mapMultiArgs["-seednode"])
AddOneShot(strDest);
+#if ENABLE_ZMQ
+ pzmqNotificationInterface = CZMQNotificationInterface::CreateWithArguments(mapArgs);
+
+ if (pzmqNotificationInterface) {
+ pzmqNotificationInterface->Initialize();
+ RegisterValidationInterface(pzmqNotificationInterface);
+ }
+#endif
+
// ********************************************************* Step 7: load block chain
fReindex = GetBoolArg("-reindex", false);
diff --git a/src/init.h b/src/init.h
index dcb2b29360..8cd51b0286 100644
--- a/src/init.h
+++ b/src/init.h
@@ -20,6 +20,8 @@ extern CWallet* pwalletMain;
void StartShutdown();
bool ShutdownRequested();
+/** Interrupt threads */
+void Interrupt(boost::thread_group& threadGroup);
void Shutdown();
bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler);
diff --git a/src/keystore.cpp b/src/keystore.cpp
index 3bae24b7b9..cf49ba83ad 100644
--- a/src/keystore.cpp
+++ b/src/keystore.cpp
@@ -6,23 +6,30 @@
#include "keystore.h"
#include "key.h"
+#include "pubkey.h"
#include "util.h"
#include <boost/foreach.hpp>
-bool CKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const
+bool CKeyStore::AddKey(const CKey &key) {
+ return AddKeyPubKey(key, key.GetPubKey());
+}
+
+bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const
{
CKey key;
- if (!GetKey(address, key))
+ if (!GetKey(address, key)) {
+ WatchKeyMap::const_iterator it = mapWatchKeys.find(address);
+ if (it != mapWatchKeys.end()) {
+ vchPubKeyOut = it->second;
+ return true;
+ }
return false;
+ }
vchPubKeyOut = key.GetPubKey();
return true;
}
-bool CKeyStore::AddKey(const CKey &key) {
- return AddKeyPubKey(key, key.GetPubKey());
-}
-
bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
LOCK(cs_KeyStore);
@@ -58,10 +65,29 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
return false;
}
+static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut)
+{
+ //TODO: Use Solver to extract this?
+ CScript::const_iterator pc = dest.begin();
+ opcodetype opcode;
+ std::vector<unsigned char> vch;
+ if (!dest.GetOp(pc, opcode, vch) || vch.size() < 33 || vch.size() > 65)
+ return false;
+ pubKeyOut = CPubKey(vch);
+ if (!pubKeyOut.IsFullyValid())
+ return false;
+ if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch))
+ return false;
+ return true;
+}
+
bool CBasicKeyStore::AddWatchOnly(const CScript &dest)
{
LOCK(cs_KeyStore);
setWatchOnly.insert(dest);
+ CPubKey pubKey;
+ if (ExtractPubKey(dest, pubKey))
+ mapWatchKeys[pubKey.GetID()] = pubKey;
return true;
}
@@ -69,6 +95,9 @@ bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest)
{
LOCK(cs_KeyStore);
setWatchOnly.erase(dest);
+ CPubKey pubKey;
+ if (ExtractPubKey(dest, pubKey))
+ mapWatchKeys.erase(pubKey.GetID());
return true;
}
diff --git a/src/keystore.h b/src/keystore.h
index 4a4b6d20af..b917bf20b4 100644
--- a/src/keystore.h
+++ b/src/keystore.h
@@ -32,7 +32,7 @@ public:
virtual bool HaveKey(const CKeyID &address) const =0;
virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0;
virtual void GetKeys(std::set<CKeyID> &setAddress) const =0;
- virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
+ virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const =0;
//! Support for BIP 0013 : see https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki
virtual bool AddCScript(const CScript& redeemScript) =0;
@@ -47,6 +47,7 @@ public:
};
typedef std::map<CKeyID, CKey> KeyMap;
+typedef std::map<CKeyID, CPubKey> WatchKeyMap;
typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CScript> WatchOnlySet;
@@ -55,11 +56,13 @@ class CBasicKeyStore : public CKeyStore
{
protected:
KeyMap mapKeys;
+ WatchKeyMap mapWatchKeys;
ScriptMap mapScripts;
WatchOnlySet setWatchOnly;
public:
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
+ bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
bool HaveKey(const CKeyID &address) const
{
bool result;
diff --git a/src/leveldbwrapper.cpp b/src/leveldbwrapper.cpp
index c353dfa6d9..26cacf95ae 100644
--- a/src/leveldbwrapper.cpp
+++ b/src/leveldbwrapper.cpp
@@ -58,7 +58,8 @@ CLevelDBWrapper::CLevelDBWrapper(const boost::filesystem::path& path, size_t nCa
} else {
if (fWipe) {
LogPrintf("Wiping LevelDB in %s\n", path.string());
- leveldb::DestroyDB(path.string(), options);
+ leveldb::Status result = leveldb::DestroyDB(path.string(), options);
+ HandleError(result);
}
TryCreateDirectory(path);
LogPrintf("Opening LevelDB in %s\n", path.string());
diff --git a/src/limitedmap.h b/src/limitedmap.h
index e8ea549653..5456dfc7c4 100644
--- a/src/limitedmap.h
+++ b/src/limitedmap.h
@@ -27,7 +27,11 @@ protected:
size_type nMaxSize;
public:
- limitedmap(size_type nMaxSizeIn = 0) { nMaxSize = nMaxSizeIn; }
+ limitedmap(size_type nMaxSizeIn)
+ {
+ assert(nMaxSizeIn > 0);
+ nMaxSize = nMaxSizeIn;
+ }
const_iterator begin() const { return map.begin(); }
const_iterator end() const { return map.end(); }
size_type size() const { return map.size(); }
@@ -38,13 +42,12 @@ public:
{
std::pair<iterator, bool> ret = map.insert(x);
if (ret.second) {
- if (nMaxSize && map.size() == nMaxSize) {
+ if (map.size() > nMaxSize) {
map.erase(rmap.begin()->second);
rmap.erase(rmap.begin());
}
rmap.insert(make_pair(x.second, ret.first));
}
- return;
}
void erase(const key_type& k)
{
@@ -81,11 +84,11 @@ public:
size_type max_size() const { return nMaxSize; }
size_type max_size(size_type s)
{
- if (s)
- while (map.size() > s) {
- map.erase(rmap.begin()->second);
- rmap.erase(rmap.begin());
- }
+ assert(s > 0);
+ while (map.size() > s) {
+ map.erase(rmap.begin()->second);
+ rmap.erase(rmap.begin());
+ }
nMaxSize = s;
return nMaxSize;
}
diff --git a/src/main.cpp b/src/main.cpp
index 755bec47b8..27278b977a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -696,30 +696,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
{
// Basic checks that don't depend on any context
if (tx.vin.empty())
- return state.DoS(10, error("CheckTransaction(): vin empty"),
- REJECT_INVALID, "bad-txns-vin-empty");
+ return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty");
if (tx.vout.empty())
- return state.DoS(10, error("CheckTransaction(): vout empty"),
- REJECT_INVALID, "bad-txns-vout-empty");
+ return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty");
// Size limits
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
- return state.DoS(100, error("CheckTransaction(): size limits failed"),
- REJECT_INVALID, "bad-txns-oversize");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize");
// Check for negative or overflow output values
CAmount nValueOut = 0;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
{
if (txout.nValue < 0)
- return state.DoS(100, error("CheckTransaction(): txout.nValue negative"),
- REJECT_INVALID, "bad-txns-vout-negative");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY)
- return state.DoS(100, error("CheckTransaction(): txout.nValue too high"),
- REJECT_INVALID, "bad-txns-vout-toolarge");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
- return state.DoS(100, error("CheckTransaction(): txout total out of range"),
- REJECT_INVALID, "bad-txns-txouttotal-toolarge");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
// Check for duplicate inputs
@@ -727,23 +721,20 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
if (vInOutPoints.count(txin.prevout))
- return state.DoS(100, error("CheckTransaction(): duplicate inputs"),
- REJECT_INVALID, "bad-txns-inputs-duplicate");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
vInOutPoints.insert(txin.prevout);
}
if (tx.IsCoinBase())
{
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
- return state.DoS(100, error("CheckTransaction(): coinbase script size"),
- REJECT_INVALID, "bad-cb-length");
+ return state.DoS(100, false, REJECT_INVALID, "bad-cb-length");
}
else
{
BOOST_FOREACH(const CTxIn& txin, tx.vin)
if (txin.prevout.IsNull())
- return state.DoS(10, error("CheckTransaction(): prevout is null"),
- REJECT_INVALID, "bad-txns-prevout-null");
+ return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null");
}
return true;
@@ -778,6 +769,14 @@ CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowF
return nMinFee;
}
+/** Convert CValidationState to a human-readable message for logging */
+static std::string FormatStateMessage(const CValidationState &state)
+{
+ return strprintf("%s%s (code %i)",
+ state.GetRejectReason(),
+ state.GetDebugMessage().empty() ? "" : ", "+state.GetDebugMessage(),
+ state.GetRejectCode());
+}
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fRejectAbsurdFee)
@@ -787,31 +786,27 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
*pfMissingInputs = false;
if (!CheckTransaction(tx, state))
- return error("AcceptToMemoryPool: CheckTransaction failed");
+ return false;
// Coinbase is only valid in a block, not as a loose transaction
if (tx.IsCoinBase())
- return state.DoS(100, error("AcceptToMemoryPool: coinbase as individual tx"),
- REJECT_INVALID, "coinbase");
+ return state.DoS(100, false, REJECT_INVALID, "coinbase");
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
string reason;
if (fRequireStandard && !IsStandardTx(tx, reason))
- return state.DoS(0,
- error("AcceptToMemoryPool: nonstandard transaction: %s", reason),
- REJECT_NONSTANDARD, reason);
+ return state.DoS(0, false, REJECT_NONSTANDARD, reason);
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
if (!CheckFinalTx(tx))
- return state.DoS(0, error("AcceptToMemoryPool: non-final"),
- REJECT_NONSTANDARD, "non-final");
+ return state.DoS(0, false, REJECT_NONSTANDARD, "non-final");
// is it already in the memory pool?
uint256 hash = tx.GetHash();
if (pool.exists(hash))
- return false;
+ return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool");
// Check for conflicts with in-memory transactions
{
@@ -822,7 +817,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (pool.mapNextTx.count(outpoint))
{
// Disable replacement feature for now
- return false;
+ return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict");
}
}
}
@@ -839,7 +834,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// do we already have it?
if (view.HaveCoins(hash))
- return false;
+ return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
// do all inputs exist?
// Note that this does not check for the presence of actual outputs (see the next check for that),
@@ -848,14 +843,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (!view.HaveCoins(txin.prevout.hash)) {
if (pfMissingInputs)
*pfMissingInputs = true;
- return false;
+ return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
}
}
// are the actual inputs available?
if (!view.HaveInputs(tx))
- return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),
- REJECT_DUPLICATE, "bad-txns-inputs-spent");
+ return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent");
// Bring the best block into scope
view.GetBestBlock();
@@ -868,7 +862,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Check for non-standard pay-to-script-hash in inputs
if (fRequireStandard && !AreInputsStandard(tx, view))
- return error("AcceptToMemoryPool: nonstandard transaction input");
+ return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs");
// Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction
@@ -878,10 +872,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
unsigned int nSigOps = GetLegacySigOpCount(tx);
nSigOps += GetP2SHSigOpCount(tx, view);
if (nSigOps > MAX_STANDARD_TX_SIGOPS)
- return state.DoS(0,
- error("AcceptToMemoryPool: too many sigops %s, %d > %d",
- hash.ToString(), nSigOps, MAX_STANDARD_TX_SIGOPS),
- REJECT_NONSTANDARD, "bad-txns-too-many-sigops");
+ return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false,
+ strprintf("%d > %d", nSigOps, MAX_STANDARD_TX_SIGOPS));
CAmount nValueOut = tx.GetValueOut();
CAmount nFees = nValueIn-nValueOut;
@@ -893,9 +885,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// Don't accept it if it can't get into a block
CAmount txMinFee = GetMinRelayFee(tx, nSize, true);
if (fLimitFree && nFees < txMinFee)
- return state.DoS(0, error("AcceptToMemoryPool: not enough fees %s, %d < %d",
- hash.ToString(), nFees, txMinFee),
- REJECT_INSUFFICIENTFEE, "insufficient fee");
+ return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient fee", false,
+ strprintf("%d < %d", nFees, txMinFee));
// Require that free transactions have sufficient priority to be mined in the next block.
if (GetBoolArg("-relaypriority", true) && nFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(view.GetPriority(tx, chainActive.Height() + 1))) {
@@ -920,24 +911,20 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// -limitfreerelay unit is thousand-bytes-per-minute
// At default rate it would take over a month to fill 1GB
if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000)
- return state.DoS(0, error("AcceptToMemoryPool: free transaction rejected by rate limiter"),
- REJECT_INSUFFICIENTFEE, "rate limited free transaction");
+ return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "rate limited free transaction");
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
dFreeCount += nSize;
}
if (fRejectAbsurdFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000)
- return state.Invalid(error("AcceptToMemoryPool: absurdly high fees %s, %d > %d",
- hash.ToString(),
- nFees, ::minRelayTxFee.GetFee(nSize) * 10000),
- REJECT_HIGHFEE, "absurdly-high-fee");
+ return state.Invalid(false,
+ REJECT_HIGHFEE, "absurdly-high-fee",
+ strprintf("%d > %d", nFees, ::minRelayTxFee.GetFee(nSize) * 10000));
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
- {
- return error("AcceptToMemoryPool: ConnectInputs failed %s", hash.ToString());
- }
+ return false;
// Check again against just the consensus-critical mandatory script
// verification flags, in case of bugs in the standard flags that cause
@@ -950,7 +937,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// can be exploited as a DoS attack.
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
{
- return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
+ return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s, %s",
+ __func__, hash.ToString(), FormatStateMessage(state));
}
// Store transaction in memory
@@ -1035,7 +1023,7 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock
// CBlock and CBlockIndex
//
-bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart)
+bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart)
{
// Open history file to append
CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION);
@@ -1241,7 +1229,7 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state
if (state.IsInvalid(nDoS)) {
std::map<uint256, NodeId>::iterator it = mapBlockSource.find(pindex->GetBlockHash());
if (it != mapBlockSource.end() && State(it->second)) {
- assert(state.GetRejectCode() < 0x100);
+ assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes
CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), pindex->GetBlockHash()};
State(it->second)->rejects.push_back(reject);
if (nDoS > 0)
@@ -1292,7 +1280,7 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, cacheStore), &error)) {
- return ::error("CScriptCheck(): %s:%d VerifySignature failed: %s", ptxTo->GetHash().ToString(), nIn, ScriptErrorString(error));
+ return false;
}
return true;
}
@@ -1310,7 +1298,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!inputs.HaveInputs(tx))
- return state.Invalid(error("CheckInputs(): %s inputs unavailable", tx.GetHash().ToString()));
+ return state.Invalid(false, 0, "", "Inputs unavailable");
CAmount nValueIn = 0;
CAmount nFees = 0;
@@ -1323,33 +1311,29 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
// If prev is coinbase, check that it's matured
if (coins->IsCoinBase()) {
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY)
- return state.Invalid(
- error("CheckInputs(): tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight),
- REJECT_INVALID, "bad-txns-premature-spend-of-coinbase");
+ return state.Invalid(false,
+ REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
+ strprintf("tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight));
}
// Check for negative or overflow input values
nValueIn += coins->vout[prevout.n].nValue;
if (!MoneyRange(coins->vout[prevout.n].nValue) || !MoneyRange(nValueIn))
- return state.DoS(100, error("CheckInputs(): txin values out of range"),
- REJECT_INVALID, "bad-txns-inputvalues-outofrange");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
}
if (nValueIn < tx.GetValueOut())
- return state.DoS(100, error("CheckInputs(): %s value in (%s) < value out (%s)",
- tx.GetHash().ToString(), FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())),
- REJECT_INVALID, "bad-txns-in-belowout");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
+ strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())));
// Tally transaction fees
CAmount nTxFee = nValueIn - tx.GetValueOut();
if (nTxFee < 0)
- return state.DoS(100, error("CheckInputs(): %s nTxFee < 0", tx.GetHash().ToString()),
- REJECT_INVALID, "bad-txns-fee-negative");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-negative");
nFees += nTxFee;
if (!MoneyRange(nFees))
- return state.DoS(100, error("CheckInputs(): nFees out of range"),
- REJECT_INVALID, "bad-txns-fee-outofrange");
+ return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
return true;
}
}// namespace Consensus
@@ -1473,7 +1457,7 @@ bool AbortNode(const std::string& strMessage, const std::string& userMessage="")
strMiscWarning = strMessage;
LogPrintf("*** %s\n", strMessage);
uiInterface.ThreadSafeMessageBox(
- userMessage.empty() ? _("Error: A fatal internal error occured, see debug.log for details") : userMessage,
+ userMessage.empty() ? _("Error: A fatal internal error occurred, see debug.log for details") : userMessage,
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return false;
@@ -1520,7 +1504,7 @@ static bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const CO
return fClean;
}
-bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean)
+bool DisconnectBlock(const CBlock& block, CValidationState& state, const CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean)
{
assert(pindex->GetBlockHash() == view.GetBestBlock());
@@ -1795,7 +1779,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
std::vector<CScriptCheck> vChecks;
if (!CheckInputs(tx, state, view, fScriptChecks, flags, false, nScriptCheckThreads ? &vChecks : NULL))
- return false;
+ return error("ConnectBlock(): CheckInputs on %s failed with %s",
+ tx.GetHash().ToString(), FormatStateMessage(state));
control.Add(vChecks);
}
@@ -2077,7 +2062,7 @@ static int64_t nTimePostConnect = 0;
* Connect a new block to chainActive. pblock is either NULL or a pointer to a CBlock
* corresponding to pindexNew, to bypass loading it again from disk.
*/
-bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *pblock) {
+bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, const CBlock *pblock) {
assert(pindexNew->pprev == chainActive.Tip());
mempool.check(pcoinsTip);
// Read block from disk.
@@ -2209,7 +2194,7 @@ static void PruneBlockIndexCandidates() {
* Try to make some progress towards making pindexMostWork the active block.
* pblock is either NULL or a pointer to a CBlock corresponding to pindexMostWork.
*/
-static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMostWork, CBlock *pblock) {
+static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMostWork, const CBlock *pblock) {
AssertLockHeld(cs_main);
bool fInvalidFound = false;
const CBlockIndex *pindexOldTip = chainActive.Tip();
@@ -2278,7 +2263,7 @@ static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMo
* or an activated best chain. pblock is either NULL or a pointer to a block
* that is already loaded (to avoid loading it again from disk).
*/
-bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
+bool ActivateBestChain(CValidationState &state, const CBlock *pblock) {
CBlockIndex *pindexNewTip = NULL;
CBlockIndex *pindexMostWork = NULL;
const CChainParams& chainParams = Params();
@@ -2318,6 +2303,7 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
pnode->PushInventory(CInv(MSG_BLOCK, hashNewTip));
}
// Notify external listeners about the new tip.
+ GetMainSignals().UpdatedBlockTip(hashNewTip);
uiInterface.NotifyBlockTip(hashNewTip);
}
} while(pindexMostWork != chainActive.Tip());
@@ -2622,7 +2608,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
// Check transactions
BOOST_FOREACH(const CTransaction& tx, block.vtx)
if (!CheckTransaction(tx, state))
- return error("CheckBlock(): CheckTransaction failed");
+ return error("CheckBlock(): CheckTransaction of %s failed with %s",
+ tx.GetHash().ToString(),
+ FormatStateMessage(state));
unsigned int nSigOps = 0;
BOOST_FOREACH(const CTransaction& tx, block.vtx)
@@ -2749,7 +2737,7 @@ bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBloc
return true;
}
-bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, bool fRequested, CDiskBlockPos* dbp)
+bool AcceptBlock(const CBlock& block, CValidationState& state, CBlockIndex** ppindex, bool fRequested, CDiskBlockPos* dbp)
{
const CChainParams& chainparams = Params();
AssertLockHeld(cs_main);
@@ -2826,7 +2814,7 @@ static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned
}
-bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp)
+bool ProcessNewBlock(CValidationState &state, const CNode* pfrom, const CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp)
{
// Preliminary checks
bool checked = CheckBlock(*pblock, state);
@@ -4357,11 +4345,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
int nDoS = 0;
if (state.IsInvalid(nDoS))
{
- LogPrint("mempool", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(),
+ LogPrint("mempoolrej", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(),
pfrom->id, pfrom->cleanSubVer,
- state.GetRejectReason());
- pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
- state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
+ FormatStateMessage(state));
+ if (state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P
+ pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
+ state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
if (nDoS > 0)
Misbehaving(pfrom->GetId(), nDoS);
}
@@ -4441,6 +4430,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
ProcessNewBlock(state, pfrom, &block, forceProcessing, NULL);
int nDoS;
if (state.IsInvalid(nDoS)) {
+ assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
if (nDoS > 0) {
@@ -4533,6 +4523,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (pingUsecTime > 0) {
// Successful ping time measurement, replace previous
pfrom->nPingUsecTime = pingUsecTime;
+ pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime, pingUsecTime);
} else {
// This should never happen
sProblem = "Timing mishap";
@@ -4601,6 +4592,21 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
}
+ else if (!(nLocalServices & NODE_BLOOM) &&
+ (strCommand == "filterload" ||
+ strCommand == "filteradd" ||
+ strCommand == "filterclear") &&
+ //TODO: Remove this line after reasonable network upgrade
+ pfrom->nVersion >= NO_BLOOM_VERSION)
+ {
+ if (pfrom->nVersion >= NO_BLOOM_VERSION)
+ Misbehaving(pfrom->GetId(), 100);
+ //TODO: Enable this after reasonable network upgrade
+ //else
+ // pfrom->fDisconnect = true;
+ }
+
+
else if (strCommand == "filterload")
{
CBloomFilter filter;
diff --git a/src/main.h b/src/main.h
index eec7e6fa59..e3479b4b3b 100644
--- a/src/main.h
+++ b/src/main.h
@@ -142,7 +142,7 @@ void UnregisterNodeSignals(CNodeSignals& nodeSignals);
* @param[out] dbp If pblock is stored to disk (or already there), this will be set to its location.
* @return True if state.IsValid()
*/
-bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp);
+bool ProcessNewBlock(CValidationState &state, const CNode* pfrom, const CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp);
/** Check whether enough disk space is available for an incoming block */
bool CheckDiskSpace(uint64_t nAdditionalBytes = 0);
/** Open a block file (blk?????.dat) */
@@ -179,7 +179,7 @@ std::string GetWarnings(const std::string& strFor);
/** Retrieve a transaction (from memory pool, or from disk, if possible) */
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock, bool fAllowSlow = false);
/** Find the best known block, and make it the tip of the block chain */
-bool ActivateBestChain(CValidationState &state, CBlock *pblock = NULL);
+bool ActivateBestChain(CValidationState &state, const CBlock *pblock = NULL);
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams);
/**
@@ -335,7 +335,7 @@ public:
/** Functions for disk access for blocks */
-bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart);
+bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart);
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos);
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex);
@@ -346,7 +346,7 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex);
* In case pfClean is provided, operation will try to be tolerant about errors, and *pfClean
* will be true if no problems were found. Otherwise, the return value will be false in case
* of problems. Note that in any case, coins may be modified. */
-bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool* pfClean = NULL);
+bool DisconnectBlock(const CBlock& block, CValidationState& state, const 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);
@@ -363,7 +363,7 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn
bool TestBlockValidity(CValidationState &state, const CBlock& block, CBlockIndex *pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true);
/** Store block on disk. If dbp is non-NULL, the file is known to already reside on disk */
-bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex **pindex, bool fRequested, CDiskBlockPos* dbp);
+bool AcceptBlock(const CBlock& block, CValidationState& state, CBlockIndex **pindex, bool fRequested, CDiskBlockPos* dbp);
bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBlockIndex **ppindex= NULL);
@@ -455,7 +455,16 @@ extern CBlockTreeDB *pblocktree;
*/
int GetSpendHeight(const CCoinsViewCache& inputs);
-/** local "reject" message codes for RPC which can not be triggered by p2p trasactions */
+/** Reject codes greater or equal to this can be returned by AcceptToMemPool
+ * for transactions, to signal internal conditions. They cannot and should not
+ * be sent over the P2P network.
+ */
+static const unsigned int REJECT_INTERNAL = 0x100;
+/** Too high fee. Can not be triggered by P2P transactions */
static const unsigned int REJECT_HIGHFEE = 0x100;
+/** Transaction is already known (either in mempool or blockchain) */
+static const unsigned int REJECT_ALREADY_KNOWN = 0x101;
+/** Transaction conflicts with a transaction already known */
+static const unsigned int REJECT_CONFLICT = 0x102;
#endif // BITCOIN_MAIN_H
diff --git a/src/merkleblock.cpp b/src/merkleblock.cpp
index 4d90fd8cd7..f8e877df25 100644
--- a/src/merkleblock.cpp
+++ b/src/merkleblock.cpp
@@ -168,7 +168,7 @@ uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch) {
// traverse the partial tree
unsigned int nBitsUsed = 0, nHashUsed = 0;
uint256 hashMerkleRoot = TraverseAndExtract(nHeight, 0, nBitsUsed, nHashUsed, vMatch);
- // verify that no problems occured during the tree traversal
+ // verify that no problems occurred during the tree traversal
if (fBad)
return uint256();
// verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence)
diff --git a/src/miner.cpp b/src/miner.cpp
index 864e4bf31f..9dd1d459b5 100644
--- a/src/miner.cpp
+++ b/src/miner.cpp
@@ -352,7 +352,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
return pblocktemplate.release();
}
-void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
+void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
// Update nExtraNonce
static uint256 hashPrevBlock;
@@ -409,7 +409,7 @@ bool static ScanHash(const CBlockHeader *pblock, uint32_t& nNonce, uint256 *phas
}
}
-static bool ProcessBlockFound(CBlock* pblock, const CChainParams& chainparams)
+static bool ProcessBlockFound(const CBlock* pblock, const CChainParams& chainparams)
{
LogPrintf("%s\n", pblock->ToString());
LogPrintf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue));
@@ -444,8 +444,10 @@ void static BitcoinMiner(const CChainParams& chainparams)
GetMainSignals().ScriptForMining(coinbaseScript);
try {
- //throw an error if no script was provided
- if (!coinbaseScript->reserveScript.size())
+ // Throw an error if no script was provided. This can happen
+ // due to some internal error but also if the keypool is empty.
+ // In the latter case, already the pointer is NULL.
+ if (!coinbaseScript || coinbaseScript->reserveScript.empty())
throw std::runtime_error("No coinbase script available (mining requires a wallet)");
while (true) {
diff --git a/src/miner.h b/src/miner.h
index d690e9447d..7e0e58d540 100644
--- a/src/miner.h
+++ b/src/miner.h
@@ -29,7 +29,7 @@ void GenerateBitcoins(bool fGenerate, int nThreads, const CChainParams& chainpar
/** Generate a new block, without valid proof-of-work */
CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn);
/** Modify the extranonce in a block */
-void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
+void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev);
#endif // BITCOIN_MINER_H
diff --git a/src/net.cpp b/src/net.cpp
index e4ead3c92e..87c4f0af0a 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -81,7 +81,6 @@ uint64_t nLocalHostNonce = 0;
static std::vector<ListenSocket> vhListenSocket;
CAddrMan addrman;
int nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS;
-int nWhiteConnections = 0;
bool fAddressesInitialized = false;
std::string strSubVersion;
@@ -629,6 +628,7 @@ void CNode::copyStats(CNodeStats &stats)
// Raw ping time is in microseconds, but show it to user as whole seconds (Bitcoin users should be well used to small numbers with many decimal places by now :)
stats.dPingTime = (((double)nPingUsecTime) / 1e6);
+ stats.dPingMin = (((double)nMinPingUsecTime) / 1e6);
stats.dPingWait = (((double)nPingUsecWait) / 1e6);
// Leave string empty if addrLocal invalid (not filled in yet)
@@ -659,7 +659,7 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes)
return false;
if (msg.in_data && msg.hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) {
- LogPrint("net", "Oversized message from peer=%i, disconnecting", GetId());
+ LogPrint("net", "Oversized message from peer=%i, disconnecting\n", GetId());
return false;
}
@@ -776,6 +776,222 @@ void SocketSendData(CNode *pnode)
static list<CNode*> vNodesDisconnected;
+class CNodeRef {
+public:
+ CNodeRef(CNode *pnode) : _pnode(pnode) {
+ LOCK(cs_vNodes);
+ _pnode->AddRef();
+ }
+
+ ~CNodeRef() {
+ LOCK(cs_vNodes);
+ _pnode->Release();
+ }
+
+ CNode& operator *() const {return *_pnode;};
+ CNode* operator ->() const {return _pnode;};
+
+ CNodeRef& operator =(const CNodeRef& other)
+ {
+ if (this != &other) {
+ LOCK(cs_vNodes);
+
+ _pnode->Release();
+ _pnode = other._pnode;
+ _pnode->AddRef();
+ }
+ return *this;
+ }
+
+ CNodeRef(const CNodeRef& other):
+ _pnode(other._pnode)
+ {
+ LOCK(cs_vNodes);
+ _pnode->AddRef();
+ }
+private:
+ CNode *_pnode;
+};
+
+static bool ReverseCompareNodeMinPingTime(const CNodeRef &a, const CNodeRef &b)
+{
+ return a->nMinPingUsecTime > b->nMinPingUsecTime;
+}
+
+static bool ReverseCompareNodeTimeConnected(const CNodeRef &a, const CNodeRef &b)
+{
+ return a->nTimeConnected > b->nTimeConnected;
+}
+
+class CompareNetGroupKeyed
+{
+ std::vector<unsigned char> vchSecretKey;
+public:
+ CompareNetGroupKeyed()
+ {
+ vchSecretKey.resize(32, 0);
+ GetRandBytes(vchSecretKey.data(), vchSecretKey.size());
+ }
+
+ bool operator()(const CNodeRef &a, const CNodeRef &b)
+ {
+ std::vector<unsigned char> vchGroupA, vchGroupB;
+ CSHA256 hashA, hashB;
+ std::vector<unsigned char> vchA(32), vchB(32);
+
+ vchGroupA = a->addr.GetGroup();
+ vchGroupB = b->addr.GetGroup();
+
+ hashA.Write(begin_ptr(vchGroupA), vchGroupA.size());
+ hashB.Write(begin_ptr(vchGroupB), vchGroupB.size());
+
+ hashA.Write(begin_ptr(vchSecretKey), vchSecretKey.size());
+ hashB.Write(begin_ptr(vchSecretKey), vchSecretKey.size());
+
+ hashA.Finalize(begin_ptr(vchA));
+ hashB.Finalize(begin_ptr(vchB));
+
+ return vchA < vchB;
+ }
+};
+
+static bool AttemptToEvictConnection(bool fPreferNewConnection) {
+ std::vector<CNodeRef> vEvictionCandidates;
+ {
+ LOCK(cs_vNodes);
+
+ BOOST_FOREACH(CNode *node, vNodes) {
+ if (node->fWhitelisted)
+ continue;
+ if (!node->fInbound)
+ continue;
+ if (node->fDisconnect)
+ continue;
+ if (node->addr.IsLocal())
+ continue;
+ vEvictionCandidates.push_back(CNodeRef(node));
+ }
+ }
+
+ if (vEvictionCandidates.empty()) return false;
+
+ // Protect connections with certain characteristics
+
+ // Deterministically select 4 peers to protect by netgroup.
+ // An attacker cannot predict which netgroups will be protected.
+ static CompareNetGroupKeyed comparerNetGroupKeyed;
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), comparerNetGroupKeyed);
+ vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
+
+ if (vEvictionCandidates.empty()) return false;
+
+ // Protect the 8 nodes with the best ping times.
+ // An attacker cannot manipulate this metric without physically moving nodes closer to the target.
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeMinPingTime);
+ vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(8, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
+
+ if (vEvictionCandidates.empty()) return false;
+
+ // Protect the half of the remaining nodes which have been connected the longest.
+ // This replicates the existing implicit behavior.
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
+ vEvictionCandidates.erase(vEvictionCandidates.end() - static_cast<int>(vEvictionCandidates.size() / 2), vEvictionCandidates.end());
+
+ if (vEvictionCandidates.empty()) return false;
+
+ // Identify the network group with the most connections
+ std::vector<unsigned char> naMostConnections;
+ unsigned int nMostConnections = 0;
+ std::map<std::vector<unsigned char>, std::vector<CNodeRef> > mapAddrCounts;
+ BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) {
+ mapAddrCounts[node->addr.GetGroup()].push_back(node);
+
+ if (mapAddrCounts[node->addr.GetGroup()].size() > nMostConnections) {
+ nMostConnections = mapAddrCounts[node->addr.GetGroup()].size();
+ naMostConnections = node->addr.GetGroup();
+ }
+ }
+
+ // Reduce to the network group with the most connections
+ vEvictionCandidates = mapAddrCounts[naMostConnections];
+
+ // Do not disconnect peers if there is only 1 connection from their network group
+ if (vEvictionCandidates.size() <= 1)
+ // unless we prefer the new connection (for whitelisted peers)
+ if (!fPreferNewConnection)
+ return false;
+
+ // Disconnect the most recent connection from the network group with the most connections
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
+ vEvictionCandidates[0]->fDisconnect = true;
+
+ return true;
+}
+
+static void AcceptConnection(const ListenSocket& hListenSocket) {
+ struct sockaddr_storage sockaddr;
+ socklen_t len = sizeof(sockaddr);
+ SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
+ CAddress addr;
+ int nInbound = 0;
+ int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS;
+
+ if (hSocket != INVALID_SOCKET)
+ if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr))
+ LogPrintf("Warning: Unknown socket family\n");
+
+ bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr);
+ {
+ LOCK(cs_vNodes);
+ BOOST_FOREACH(CNode* pnode, vNodes)
+ if (pnode->fInbound)
+ nInbound++;
+ }
+
+ if (hSocket == INVALID_SOCKET)
+ {
+ int nErr = WSAGetLastError();
+ if (nErr != WSAEWOULDBLOCK)
+ LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr));
+ return;
+ }
+
+ if (!IsSelectableSocket(hSocket))
+ {
+ LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString());
+ CloseSocket(hSocket);
+ return;
+ }
+
+ if (CNode::IsBanned(addr) && !whitelisted)
+ {
+ LogPrintf("connection from %s dropped (banned)\n", addr.ToString());
+ CloseSocket(hSocket);
+ return;
+ }
+
+ if (nInbound >= nMaxInbound)
+ {
+ if (!AttemptToEvictConnection(whitelisted)) {
+ // No connection to evict, disconnect the new connection
+ LogPrint("net", "failed to find an eviction candidate - connection dropped (full)\n");
+ CloseSocket(hSocket);
+ return;
+ }
+ }
+
+ CNode* pnode = new CNode(hSocket, addr, "", true);
+ pnode->AddRef();
+ pnode->fWhitelisted = whitelisted;
+
+ LogPrint("net", "connection from %s accepted\n", addr.ToString());
+
+ {
+ LOCK(cs_vNodes);
+ vNodes.push_back(pnode);
+ }
+}
+
void ThreadSocketHandler()
{
unsigned int nPrevNodeCount = 0;
@@ -933,64 +1149,7 @@ void ThreadSocketHandler()
{
if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv))
{
- struct sockaddr_storage sockaddr;
- socklen_t len = sizeof(sockaddr);
- SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
- CAddress addr;
- int nInbound = 0;
- int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS;
-
- if (hSocket != INVALID_SOCKET)
- if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr))
- LogPrintf("Warning: Unknown socket family\n");
-
- bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr);
- {
- LOCK(cs_vNodes);
- BOOST_FOREACH(CNode* pnode, vNodes)
- if (pnode->fInbound)
- nInbound++;
- }
-
- if (hSocket == INVALID_SOCKET)
- {
- int nErr = WSAGetLastError();
- if (nErr != WSAEWOULDBLOCK)
- LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr));
- }
- else if (!IsSelectableSocket(hSocket))
- {
- LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString());
- CloseSocket(hSocket);
- }
- else if (nInbound >= nMaxInbound)
- {
- LogPrint("net", "connection from %s dropped (full)\n", addr.ToString());
- CloseSocket(hSocket);
- }
- else if (!whitelisted && (nInbound >= (nMaxInbound - nWhiteConnections)))
- {
- LogPrint("net", "connection from %s dropped (non-whitelisted)\n", addr.ToString());
- CloseSocket(hSocket);
- }
- else if (CNode::IsBanned(addr) && !whitelisted)
- {
- LogPrintf("connection from %s dropped (banned)\n", addr.ToString());
- CloseSocket(hSocket);
- }
- else
- {
- CNode* pnode = new CNode(hSocket, addr, "", true);
- pnode->AddRef();
- pnode->fWhitelisted = whitelisted;
-
- LogPrint("net", "connection from %s accepted\n", addr.ToString());
-
- {
- LOCK(cs_vNodes);
- vNodes.push_back(pnode);
- }
- }
+ AcceptConnection(hListenSocket);
}
}
@@ -1120,10 +1279,14 @@ void ThreadMapPort()
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0);
-#else
+#elif MINIUPNPC_API_VERSION < 14
/* miniupnpc 1.6 */
int error = 0;
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error);
+#else
+ /* miniupnpc 1.9.20150730 */
+ int error = 0;
+ devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
#endif
struct UPNPUrls urls;
@@ -1625,8 +1788,10 @@ bool BindListenPort(const CService &addrBind, string& strError, bool fWhiteliste
setsockopt(hListenSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&nOne, sizeof(int));
#endif
// Allow binding if the port is still in TIME_WAIT state after
- // the program was closed and restarted. Not an issue on windows!
+ // the program was closed and restarted.
setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (void*)&nOne, sizeof(int));
+#else
+ setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&nOne, sizeof(int));
#endif
// Set to non-blocking, incoming connections will also inherit this
@@ -2096,6 +2261,7 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
nPingUsecStart = 0;
nPingUsecTime = 0;
fPingQueued = false;
+ nMinPingUsecTime = std::numeric_limits<int64_t>::max();
{
LOCK(cs_nLastNodeId);
@@ -2319,7 +2485,7 @@ void DumpBanlist()
{
int64_t nStart = GetTimeMillis();
- CNode::SweepBanned(); //clean unused entires (if bantime has expired)
+ CNode::SweepBanned(); //clean unused entries (if bantime has expired)
CBanDB bandb;
banmap_t banmap;
diff --git a/src/net.h b/src/net.h
index efa2e29bb5..6842ee5edc 100644
--- a/src/net.h
+++ b/src/net.h
@@ -143,19 +143,8 @@ extern uint64_t nLocalServices;
extern uint64_t nLocalHostNonce;
extern CAddrMan addrman;
-// The allocation of connections against the maximum allowed (nMaxConnections)
-// is prioritized as follows:
-// 1st: Outbound connections (MAX_OUTBOUND_CONNECTIONS)
-// 2nd: Inbound connections from whitelisted peers (nWhiteConnections)
-// 3rd: Inbound connections from non-whitelisted peers
-// Thus, the number of connection slots for the general public to use is:
-// nMaxConnections - (MAX_OUTBOUND_CONNECTIONS + nWhiteConnections)
-// Any additional inbound connections beyond limits will be immediately closed
-
/** Maximum number of connections to simultaneously allow (aka connection slots) */
extern int nMaxConnections;
-/** Number of connection slots to reserve for inbound from whitelisted peers */
-extern int nWhiteConnections;
extern std::vector<CNode*> vNodes;
extern CCriticalSection cs_vNodes;
@@ -200,6 +189,7 @@ public:
bool fWhitelisted;
double dPingTime;
double dPingWait;
+ double dPingMin;
std::string addrLocal;
};
@@ -395,6 +385,8 @@ public:
int64_t nPingUsecStart;
// Last measured round-trip time.
int64_t nPingUsecTime;
+ // Best measured round-trip time.
+ int64_t nMinPingUsecTime;
// Whether a ping is requested.
bool fPingQueued;
@@ -695,7 +687,7 @@ public:
static bool BannedSetIsDirty();
//!set the "dirty" flag for the banlist
static void SetBannedSetDirty(bool dirty=true);
- //!clean unused entires (if bantime has expired)
+ //!clean unused entries (if bantime has expired)
static void SweepBanned();
void copyStats(CNodeStats &stats);
diff --git a/src/netbase.cpp b/src/netbase.cpp
index b7e2e57917..7a87d125c2 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -349,7 +349,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
}
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
CloseSocket(hSocket);
- return error("Proxy authentication unsuccesful");
+ return error("Proxy authentication unsuccessful");
}
} else if (pchRet1[1] == 0x00) {
// Perform no authentication
@@ -983,7 +983,7 @@ std::vector<unsigned char> CNetAddr::GetGroup() const
nBits -= 8;
}
if (nBits > 0)
- vchRet.push_back(GetByte(15 - nStartByte) | ((1 << nBits) - 1));
+ vchRet.push_back(GetByte(15 - nStartByte) | ((1 << (8 - nBits)) - 1));
return vchRet;
}
diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp
index cdee541d2f..ffe31d1942 100644
--- a/src/policy/fees.cpp
+++ b/src/policy/fees.cpp
@@ -261,7 +261,7 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe
blocksAgo = 0;
if (blocksAgo < 0) {
LogPrint("estimatefee", "Blockpolicy error, blocks ago is negative for mempool tx\n");
- return; //This can't happen becasue we call this with our best seen height, no entries can have higher
+ return; //This can't happen because we call this with our best seen height, no entries can have higher
}
if (blocksAgo >= (int)unconfTxs.size()) {
diff --git a/src/policy/fees.h b/src/policy/fees.h
index ce4d782566..15577d128a 100644
--- a/src/policy/fees.h
+++ b/src/policy/fees.h
@@ -118,7 +118,7 @@ public:
/**
* Initialize the data structures. This is called by BlockPolicyEstimator's
* constructor with default values.
- * @param defaultBuckets contains the upper limits for the bucket boundries
+ * @param defaultBuckets contains the upper limits for the bucket boundaries
* @param maxConfirms max number of confirms to track
* @param decay how much to decay the historical moving average per block
* @param dataTypeString for logging purposes
diff --git a/src/protocol.h b/src/protocol.h
index b5e65032a2..50aeaf44ba 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -75,6 +75,10 @@ enum {
// Bitcoin Core does not support this but a patch set called Bitcoin XT does.
// See BIP 64 for details on how this is implemented.
NODE_GETUTXO = (1 << 1),
+ // NODE_BLOOM means the node is capable and willing to handle bloom-filtered connections.
+ // Bitcoin Core nodes used to support this by default, without advertising this bit,
+ // but no longer do as of protocol version 70011 (= NO_BLOOM_VERSION)
+ NODE_BLOOM = (1 << 2),
// Bits 24-31 are reserved for temporary experiments. Just pick a bit that
// isn't getting used, or one not being used much, and notify the
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 1da2d3e344..ea7f86d18e 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -266,13 +266,6 @@ void BitcoinCore::initialize()
{
qDebug() << __func__ << ": Running AppInit2 in thread";
int rv = AppInit2(threadGroup, scheduler);
- if(rv)
- {
- /* Start a dummy RPC thread if no RPC thread is active yet
- * to handle timeouts.
- */
- StartDummyRPCThread();
- }
Q_EMIT initializeResult(rv);
} catch (const std::exception& e) {
handleRunawayException(&e);
@@ -286,7 +279,7 @@ void BitcoinCore::shutdown()
try
{
qDebug() << __func__ << ": Running Shutdown in thread";
- threadGroup.interrupt_all();
+ Interrupt(threadGroup);
threadGroup.join_all();
Shutdown();
qDebug() << __func__ << ": Shutdown finished";
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 1d3f7762ab..db9e558764 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -97,6 +97,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n
trayIconMenu(0),
notificator(0),
rpcConsole(0),
+ helpMessageDialog(0),
prevBlocks(0),
spinnerFrame(0),
platformStyle(platformStyle)
@@ -132,6 +133,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n
#endif
rpcConsole = new RPCConsole(platformStyle, 0);
+ helpMessageDialog = new HelpMessageDialog(this, false);
#ifdef ENABLE_WALLET
if(enableWallet)
{
@@ -590,9 +592,7 @@ void BitcoinGUI::aboutClicked()
void BitcoinGUI::showHelpMessageClicked()
{
- HelpMessageDialog *help = new HelpMessageDialog(this, false);
- help->setAttribute(Qt::WA_DeleteOnClose);
- help->show();
+ helpMessageDialog->show();
}
#ifdef ENABLE_WALLET
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index dd0d4bb0e2..f1b7a502ba 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -28,6 +28,7 @@ class SendCoinsRecipient;
class UnitDisplayStatusBarControl;
class WalletFrame;
class WalletModel;
+class HelpMessageDialog;
class CWallet;
@@ -113,6 +114,7 @@ private:
QMenu *trayIconMenu;
Notificator *notificator;
RPCConsole *rpcConsole;
+ HelpMessageDialog *helpMessageDialog;
/** Keep track of previous number of blocks, to detect progress */
int prevBlocks;
diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp
index 0827d99125..31a6d65a8d 100644
--- a/src/qt/paymentserver.cpp
+++ b/src/qt/paymentserver.cpp
@@ -509,12 +509,7 @@ bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentR
}
// BIP70 DoS protection
- if (f.size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
- qWarning() << QString("PaymentServer::%1: Payment request %2 is too large (%3 bytes, allowed %4 bytes).")
- .arg(__func__)
- .arg(filename)
- .arg(f.size())
- .arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
+ if (!verifySize(f.size())) {
return false;
}
@@ -685,14 +680,13 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply)
reply->deleteLater();
// BIP70 DoS protection
- if (reply->size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
- QString msg = tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
- .arg(reply->request().url().toString())
- .arg(reply->size())
- .arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
-
- qWarning() << QString("PaymentServer::%1:").arg(__func__) << msg;
- Q_EMIT message(tr("Payment request DoS protection"), msg, CClientUIInterface::MSG_ERROR);
+ if (!verifySize(reply->size())) {
+ Q_EMIT message(tr("Payment request rejected"),
+ tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
+ .arg(reply->request().url().toString())
+ .arg(reply->size())
+ .arg(BIP70_MAX_PAYMENTREQUEST_SIZE),
+ CClientUIInterface::MSG_ERROR);
return;
}
@@ -762,7 +756,7 @@ void PaymentServer::setOptionsModel(OptionsModel *optionsModel)
void PaymentServer::handlePaymentACK(const QString& paymentACKMsg)
{
- // currently we don't futher process or store the paymentACK message
+ // currently we don't further process or store the paymentACK message
Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL);
}
@@ -790,6 +784,18 @@ bool PaymentServer::verifyExpired(const payments::PaymentDetails& requestDetails
return fVerified;
}
+bool PaymentServer::verifySize(qint64 requestSize)
+{
+ bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE);
+ if (!fVerified) {
+ qWarning() << QString("PaymentServer::%1: Payment request too large (%2 bytes, allowed %3 bytes).")
+ .arg(__func__)
+ .arg(requestSize)
+ .arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
+ }
+ return fVerified;
+}
+
bool PaymentServer::verifyAmount(const CAmount& requestAmount)
{
bool fVerified = MoneyRange(requestAmount);
diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h
index 5df0a14cf7..fa120a435c 100644
--- a/src/qt/paymentserver.h
+++ b/src/qt/paymentserver.h
@@ -88,13 +88,12 @@ public:
// OptionsModel is used for getting proxy settings and display unit
void setOptionsModel(OptionsModel *optionsModel);
- // This is now public, because we use it in paymentservertests.cpp
- static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request);
-
// Verify that the payment request network matches the client network
static bool verifyNetwork(const payments::PaymentDetails& requestDetails);
// Verify if the payment request is expired
static bool verifyExpired(const payments::PaymentDetails& requestDetails);
+ // Verify the payment request size is valid as per BIP70
+ static bool verifySize(qint64 requestSize);
// Verify the payment request amount is valid
static bool verifyAmount(const CAmount& requestAmount);
@@ -131,6 +130,7 @@ protected:
bool eventFilter(QObject *object, QEvent *event);
private:
+ static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request);
bool processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient);
void fetchRequest(const QUrl& url);
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 14339e1ab5..ec18ea8f71 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -27,6 +27,7 @@
#include <QScrollBar>
#include <QThread>
#include <QTime>
+#include <QTimer>
#if QT_VERSION < 0x050000
#include <QUrl>
@@ -65,6 +66,40 @@ Q_SIGNALS:
void reply(int category, const QString &command);
};
+/** Class for handling RPC timers
+ * (used for e.g. re-locking the wallet after a timeout)
+ */
+class QtRPCTimerBase: public QObject, public RPCTimerBase
+{
+ Q_OBJECT
+public:
+ QtRPCTimerBase(boost::function<void(void)>& func, int64_t millis):
+ func(func)
+ {
+ timer.setSingleShot(true);
+ connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
+ timer.start(millis);
+ }
+ ~QtRPCTimerBase() {}
+private Q_SLOTS:
+ void timeout() { func(); }
+private:
+ QTimer timer;
+ boost::function<void(void)> func;
+};
+
+class QtRPCTimerInterface: public RPCTimerInterface
+{
+public:
+ ~QtRPCTimerInterface() {}
+ const char *Name() { return "Qt"; }
+ RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis)
+ {
+ return new QtRPCTimerBase(func, millis);
+ }
+};
+
+
#include "rpcconsole.moc"
/**
@@ -231,6 +266,9 @@ RPCConsole::RPCConsole(const PlatformStyle *platformStyle, QWidget *parent) :
ui->label_berkeleyDBVersion->hide();
ui->berkeleyDBVersion->hide();
#endif
+ // Register RPC timer interface
+ rpcTimerInterface = new QtRPCTimerInterface();
+ RPCRegisterTimerInterface(rpcTimerInterface);
startExecutor();
setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
@@ -245,6 +283,8 @@ RPCConsole::~RPCConsole()
{
GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this);
Q_EMIT stopExecutor();
+ RPCUnregisterTimerInterface(rpcTimerInterface);
+ delete rpcTimerInterface;
delete ui;
}
@@ -569,7 +609,7 @@ void RPCConsole::peerLayoutChanged()
if (detailNodeRow < 0)
{
- // detail node dissapeared from table (node disconnected)
+ // detail node disappeared from table (node disconnected)
fUnselect = true;
}
else
diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h
index b94efee84a..1409fca525 100644
--- a/src/qt/rpcconsole.h
+++ b/src/qt/rpcconsole.h
@@ -14,6 +14,7 @@
class ClientModel;
class PlatformStyle;
+class RPCTimerInterface;
namespace Ui {
class RPCConsole;
@@ -108,6 +109,7 @@ private:
NodeId cachedNodeid;
QMenu *contextMenu;
const PlatformStyle *platformStyle;
+ RPCTimerInterface *rpcTimerInterface;
};
#endif // BITCOIN_QT_RPCCONSOLE_H
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 449a7bc5e8..60a3fc128e 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -754,10 +754,9 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text)
}
else // Valid address
{
- CPubKey pubkey;
CKeyID keyid;
addr.GetKeyID(keyid);
- if (!model->getPubKey(keyid, pubkey)) // Unknown change address
+ if (!model->havePrivKey(keyid)) // Unknown change address
{
ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
}
diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp
index 8430e017c1..c15b64c327 100644
--- a/src/qt/splashscreen.cpp
+++ b/src/qt/splashscreen.cpp
@@ -57,7 +57,7 @@ SplashScreen::SplashScreen(Qt::WindowFlags f, const NetworkStyle *networkStyle)
QPainter pixPaint(&pixmap);
pixPaint.setPen(QColor(100,100,100));
- // draw a slighly radial gradient
+ // draw a slightly radial gradient
QRadialGradient gradient(QPoint(0,0), splashSize.width()/devicePixelRatio);
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, QColor(247,247,247));
diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp
index b28934cd31..fa5696325d 100644
--- a/src/qt/test/paymentservertests.cpp
+++ b/src/qt/test/paymentservertests.cpp
@@ -185,7 +185,8 @@ void PaymentServerTests::paymentServerTests()
tempFile.open();
tempFile.write((const char*)randData, sizeof(randData));
tempFile.close();
- QCOMPARE(PaymentServer::readPaymentRequestFromFile(tempFile.fileName(), r.paymentRequest), false);
+ // compares 50001 <= BIP70_MAX_PAYMENTREQUEST_SIZE == false
+ QCOMPARE(PaymentServer::verifySize(tempFile.size()), false);
// Payment request with amount overflow (amount is set to 21000001 BTC):
data = DecodeBase64(paymentrequest5_cert2_BASE64);
diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp
index bb768f1325..f91de2008c 100644
--- a/src/qt/test/test_main.cpp
+++ b/src/qt/test/test_main.cpp
@@ -17,6 +17,8 @@
#include <QObject>
#include <QTest>
+#include <openssl/ssl.h>
+
#if defined(QT_STATICPLUGIN) && QT_VERSION < 0x050000
#include <QtPlugin>
Q_IMPORT_PLUGIN(qcncodecs)
@@ -36,6 +38,8 @@ int main(int argc, char *argv[])
QCoreApplication app(argc, argv);
app.setApplicationName("Bitcoin-Qt-test");
+ SSL_library_init();
+
URITests test1;
if (QTest::qExec(&test1) != 0)
fInvalid = true;
diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
index af78a51d0f..801c6c62d2 100644
--- a/src/qt/transactiondesc.cpp
+++ b/src/qt/transactiondesc.cpp
@@ -165,7 +165,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
if (fAllFromMe)
{
- if(fAllFromMe == ISMINE_WATCH_ONLY)
+ if(fAllFromMe & ISMINE_WATCH_ONLY)
strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
//
@@ -190,7 +190,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
if(toSelf == ISMINE_SPENDABLE)
strHTML += " (own address)";
- else if(toSelf == ISMINE_WATCH_ONLY)
+ else if(toSelf & ISMINE_WATCH_ONLY)
strHTML += " (watch-only)";
strHTML += "<br>";
}
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index 15d13e9fc9..d8623daf5d 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -56,7 +56,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
CTxDestination address;
sub.idx = parts.size(); // sequence number
sub.credit = txout.nValue;
- sub.involvesWatchAddress = mine == ISMINE_WATCH_ONLY;
+ sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY;
if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address))
{
// Received by Bitcoin Address
@@ -86,7 +86,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
{
isminetype mine = wallet->IsMine(txin);
- if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true;
+ if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(fAllFromMe > mine) fAllFromMe = mine;
}
@@ -94,7 +94,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
{
isminetype mine = wallet->IsMine(txout);
- if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true;
+ if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(fAllToMe > mine) fAllToMe = mine;
}
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index f580853732..5c21db8bdf 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -556,6 +556,11 @@ bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
return wallet->GetPubKey(address, vchPubKeyOut);
}
+bool WalletModel::havePrivKey(const CKeyID &address) const
+{
+ return wallet->HaveKey(address);
+}
+
// returns a list of COutputs from COutPoints
void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs)
{
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index c29682e4f6..a5e877d81f 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -187,6 +187,7 @@ public:
UnlockContext requestUnlock();
bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
+ bool havePrivKey(const CKeyID &address) const;
void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs);
bool isSpent(const COutPoint& outpoint) const;
void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const;
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
index fa96f62e03..77efdb5cdd 100644
--- a/src/qt/walletview.cpp
+++ b/src/qt/walletview.cpp
@@ -56,6 +56,9 @@ WalletView::WalletView(const PlatformStyle *platformStyle, QWidget *parent):
receiveCoinsPage = new ReceiveCoinsDialog(platformStyle);
sendCoinsPage = new SendCoinsDialog(platformStyle);
+ usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this);
+ usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this);
+
addWidget(overviewPage);
addWidget(transactionsPage);
addWidget(receiveCoinsPage);
@@ -115,6 +118,8 @@ void WalletView::setWalletModel(WalletModel *walletModel)
overviewPage->setWalletModel(walletModel);
receiveCoinsPage->setModel(walletModel);
sendCoinsPage->setModel(walletModel);
+ usedReceivingAddressesPage->setModel(walletModel->getAddressTableModel());
+ usedSendingAddressesPage->setModel(walletModel->getAddressTableModel());
if (walletModel)
{
@@ -273,20 +278,20 @@ void WalletView::usedSendingAddresses()
{
if(!walletModel)
return;
- AddressBookPage *dlg = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this);
- dlg->setAttribute(Qt::WA_DeleteOnClose);
- dlg->setModel(walletModel->getAddressTableModel());
- dlg->show();
+
+ usedSendingAddressesPage->show();
+ usedSendingAddressesPage->raise();
+ usedSendingAddressesPage->activateWindow();
}
void WalletView::usedReceivingAddresses()
{
if(!walletModel)
return;
- AddressBookPage *dlg = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this);
- dlg->setAttribute(Qt::WA_DeleteOnClose);
- dlg->setModel(walletModel->getAddressTableModel());
- dlg->show();
+
+ usedReceivingAddressesPage->show();
+ usedReceivingAddressesPage->raise();
+ usedReceivingAddressesPage->activateWindow();
}
void WalletView::showProgress(const QString &title, int nProgress)
diff --git a/src/qt/walletview.h b/src/qt/walletview.h
index f97cf1ee80..2a6a6a2df2 100644
--- a/src/qt/walletview.h
+++ b/src/qt/walletview.h
@@ -18,6 +18,7 @@ class SendCoinsDialog;
class SendCoinsRecipient;
class TransactionView;
class WalletModel;
+class AddressBookPage;
QT_BEGIN_NAMESPACE
class QModelIndex;
@@ -61,6 +62,8 @@ private:
QWidget *transactionsPage;
ReceiveCoinsDialog *receiveCoinsPage;
SendCoinsDialog *sendCoinsPage;
+ AddressBookPage *usedSendingAddressesPage;
+ AddressBookPage *usedReceivingAddressesPage;
TransactionView *transactionView;
diff --git a/src/rest.cpp b/src/rest.cpp
index 0dd238b683..226e237fc6 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -7,6 +7,7 @@
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "main.h"
+#include "httpserver.h"
#include "rpcserver.h"
#include "streams.h"
#include "sync.h"
@@ -56,35 +57,38 @@ struct CCoin {
}
};
-class RestErr
-{
-public:
- enum HTTPStatusCode status;
- string message;
-};
-
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
extern UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false);
+extern UniValue mempoolInfoToJSON();
+extern UniValue mempoolToJSON(bool fVerbose = false);
extern void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
extern UniValue blockheaderToJSON(const CBlockIndex* blockindex);
-static RestErr RESTERR(enum HTTPStatusCode status, string message)
+static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, string message)
{
- RestErr re;
- re.status = status;
- re.message = message;
- return re;
+ req->WriteHeader("Content-Type", "text/plain");
+ req->WriteReply(status, message + "\r\n");
+ return false;
}
-static enum RetFormat ParseDataFormat(vector<string>& params, const string strReq)
+static enum RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
{
- boost::split(params, strReq, boost::is_any_of("."));
- if (params.size() > 1) {
- for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++)
- if (params[1] == rf_names[i].name)
- return rf_names[i].rf;
+ const std::string::size_type pos = strReq.rfind('.');
+ if (pos == std::string::npos)
+ {
+ param = strReq;
+ return rf_names[0].rf;
}
+ param = strReq.substr(0, pos);
+ const std::string suff(strReq, pos + 1);
+
+ for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++)
+ if (suff == rf_names[i].name)
+ return rf_names[i].rf;
+
+ /* If no suffix is found, return original string. */
+ param = strReq;
return rf_names[0].rf;
}
@@ -113,28 +117,35 @@ static bool ParseHashStr(const string& strReq, uint256& v)
return true;
}
-static bool rest_headers(AcceptedConnection* conn,
- const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun)
+static bool CheckWarmup(HTTPRequest* req)
{
- vector<string> params;
- const RetFormat rf = ParseDataFormat(params, strURIPart);
+ std::string statusmessage;
+ if (RPCIsInWarmup(&statusmessage))
+ return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
+ return true;
+}
+
+static bool rest_headers(HTTPRequest* req,
+ const std::string& strURIPart)
+{
+ if (!CheckWarmup(req))
+ return false;
+ std::string param;
+ const RetFormat rf = ParseDataFormat(param, strURIPart);
vector<string> path;
- boost::split(path, params[0], boost::is_any_of("/"));
+ boost::split(path, param, boost::is_any_of("/"));
if (path.size() != 2)
- throw RESTERR(HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
+ return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
long count = strtol(path[0].c_str(), NULL, 10);
if (count < 1 || count > 2000)
- throw RESTERR(HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
+ return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
string hashStr = path[1];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
- throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
std::vector<const CBlockIndex *> headers;
headers.reserve(count);
@@ -158,28 +169,29 @@ static bool rest_headers(AcceptedConnection* conn,
switch (rf) {
case RF_BINARY: {
string binaryHeader = ssHeader.str();
- conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryHeader.size(), "application/octet-stream") << binaryHeader << std::flush;
+ req->WriteHeader("Content-Type", "application/octet-stream");
+ req->WriteReply(HTTP_OK, binaryHeader);
return true;
}
case RF_HEX: {
string strHex = HexStr(ssHeader.begin(), ssHeader.end()) + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
+ req->WriteHeader("Content-Type", "text/plain");
+ req->WriteReply(HTTP_OK, strHex);
return true;
}
-
case RF_JSON: {
UniValue jsonHeaders(UniValue::VARR);
BOOST_FOREACH(const CBlockIndex *pindex, headers) {
jsonHeaders.push_back(blockheaderToJSON(pindex));
}
string strJSON = jsonHeaders.write() + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
return true;
}
-
default: {
- throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)");
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)");
}
}
@@ -187,34 +199,32 @@ static bool rest_headers(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
-static bool rest_block(AcceptedConnection* conn,
+static bool rest_block(HTTPRequest* req,
const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun,
bool showTxDetails)
{
- vector<string> params;
- const RetFormat rf = ParseDataFormat(params, strURIPart);
+ if (!CheckWarmup(req))
+ return false;
+ std::string hashStr;
+ const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
- string hashStr = params[0];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
- throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
CBlock block;
CBlockIndex* pblockindex = NULL;
{
LOCK(cs_main);
if (mapBlockIndex.count(hash) == 0)
- throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
+ return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
pblockindex = mapBlockIndex[hash];
if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
- throw RESTERR(HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
+ return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
if (!ReadBlockFromDisk(block, pblockindex))
- throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
+ return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
@@ -223,25 +233,28 @@ static bool rest_block(AcceptedConnection* conn,
switch (rf) {
case RF_BINARY: {
string binaryBlock = ssBlock.str();
- conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryBlock.size(), "application/octet-stream") << binaryBlock << std::flush;
+ req->WriteHeader("Content-Type", "application/octet-stream");
+ req->WriteReply(HTTP_OK, binaryBlock);
return true;
}
case RF_HEX: {
string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
+ req->WriteHeader("Content-Type", "text/plain");
+ req->WriteReply(HTTP_OK, strHex);
return true;
}
case RF_JSON: {
UniValue objBlock = blockToJSON(block, pblockindex, showTxDetails);
string strJSON = objBlock.write() + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
- throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
@@ -249,43 +262,34 @@ static bool rest_block(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
-static bool rest_block_extended(AcceptedConnection* conn,
- const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun)
+static bool rest_block_extended(HTTPRequest* req, const std::string& strURIPart)
{
- return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, true);
+ return rest_block(req, strURIPart, true);
}
-static bool rest_block_notxdetails(AcceptedConnection* conn,
- const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun)
+static bool rest_block_notxdetails(HTTPRequest* req, const std::string& strURIPart)
{
- return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, false);
+ return rest_block(req, strURIPart, false);
}
-static bool rest_chaininfo(AcceptedConnection* conn,
- const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun)
+static bool rest_chaininfo(HTTPRequest* req, const std::string& strURIPart)
{
- vector<string> params;
- const RetFormat rf = ParseDataFormat(params, strURIPart);
+ if (!CheckWarmup(req))
+ return false;
+ std::string param;
+ const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RF_JSON: {
UniValue rpcParams(UniValue::VARR);
UniValue chainInfoObject = getblockchaininfo(rpcParams, false);
string strJSON = chainInfoObject.write() + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
- throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)");
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
}
}
@@ -293,24 +297,71 @@ static bool rest_chaininfo(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
-static bool rest_tx(AcceptedConnection* conn,
- const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun)
+static bool rest_mempool_info(HTTPRequest* req, const std::string& strURIPart)
{
- vector<string> params;
- const RetFormat rf = ParseDataFormat(params, strURIPart);
+ if (!CheckWarmup(req))
+ return false;
+ std::string param;
+ const RetFormat rf = ParseDataFormat(param, strURIPart);
+
+ switch (rf) {
+ case RF_JSON: {
+ UniValue mempoolInfoObject = mempoolInfoToJSON();
+
+ string strJSON = mempoolInfoObject.write() + "\n";
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
+ return true;
+ }
+ default: {
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
+ }
+ }
+
+ // not reached
+ return true; // continue to process further HTTP reqs on this cxn
+}
+
+static bool rest_mempool_contents(HTTPRequest* req, const std::string& strURIPart)
+{
+ if (!CheckWarmup(req))
+ return false;
+ std::string param;
+ const RetFormat rf = ParseDataFormat(param, strURIPart);
+
+ switch (rf) {
+ case RF_JSON: {
+ UniValue mempoolObject = mempoolToJSON(true);
+
+ string strJSON = mempoolObject.write() + "\n";
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
+ return true;
+ }
+ default: {
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
+ }
+ }
+
+ // not reached
+ return true; // continue to process further HTTP reqs on this cxn
+}
+
+static bool rest_tx(HTTPRequest* req, const std::string& strURIPart)
+{
+ if (!CheckWarmup(req))
+ return false;
+ std::string hashStr;
+ const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
- string hashStr = params[0];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
- throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
CTransaction tx;
uint256 hashBlock = uint256();
if (!GetTransaction(hash, tx, hashBlock, true))
- throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
+ return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << tx;
@@ -318,13 +369,15 @@ static bool rest_tx(AcceptedConnection* conn,
switch (rf) {
case RF_BINARY: {
string binaryTx = ssTx.str();
- conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryTx.size(), "application/octet-stream") << binaryTx << std::flush;
+ req->WriteHeader("Content-Type", "application/octet-stream");
+ req->WriteReply(HTTP_OK, binaryTx);
return true;
}
case RF_HEX: {
string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
+ req->WriteHeader("Content-Type", "text/plain");
+ req->WriteReply(HTTP_OK, strHex);
return true;
}
@@ -332,12 +385,13 @@ static bool rest_tx(AcceptedConnection* conn,
UniValue objTx(UniValue::VOBJ);
TxToJSON(tx, hashBlock, objTx);
string strJSON = objTx.write() + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
- throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
@@ -345,25 +399,24 @@ static bool rest_tx(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
-static bool rest_getutxos(AcceptedConnection* conn,
- const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun)
+static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
{
- vector<string> params;
- enum RetFormat rf = ParseDataFormat(params, strURIPart);
+ if (!CheckWarmup(req))
+ return false;
+ std::string param;
+ const RetFormat rf = ParseDataFormat(param, strURIPart);
vector<string> uriParts;
- if (params.size() > 0 && params[0].length() > 1)
+ if (param.length() > 1)
{
- std::string strUriParams = params[0].substr(1);
+ std::string strUriParams = param.substr(1);
boost::split(uriParts, strUriParams, boost::is_any_of("/"));
}
// throw exception in case of a empty request
- if (strRequest.length() == 0 && uriParts.size() == 0)
- throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
+ std::string strRequestMutable = req->ReadBody();
+ if (strRequestMutable.length() == 0 && uriParts.size() == 0)
+ return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
bool fInputParsed = false;
bool fCheckMemPool = false;
@@ -387,7 +440,7 @@ static bool rest_getutxos(AcceptedConnection* conn,
std::string strOutput = uriParts[i].substr(uriParts[i].find("-")+1);
if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
- throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
+ return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error");
txid.SetHex(strTxid);
vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
@@ -396,15 +449,13 @@ static bool rest_getutxos(AcceptedConnection* conn,
if (vOutPoints.size() > 0)
fInputParsed = true;
else
- throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
+ return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
}
- string strRequestMutable = strRequest; //convert const string to string for allowing hex to bin converting
-
switch (rf) {
case RF_HEX: {
// convert hex to bin, continue then with bin part
- std::vector<unsigned char> strRequestV = ParseHex(strRequest);
+ std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
}
@@ -414,7 +465,7 @@ static bool rest_getutxos(AcceptedConnection* conn,
if (strRequestMutable.size() > 0)
{
if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
- throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed");
+ return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed");
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
oss << strRequestMutable;
@@ -423,24 +474,24 @@ static bool rest_getutxos(AcceptedConnection* conn,
}
} catch (const std::ios_base::failure& e) {
// abort in case of unreadable binary data
- throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
+ return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error");
}
break;
}
case RF_JSON: {
if (!fInputParsed)
- throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
+ return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
break;
}
default: {
- throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
// limit max outpoints
if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
- throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
+ return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
// check spentness and form a bitmap (as well as a JSON capable human-readble string representation)
vector<unsigned char> bitmap;
@@ -490,7 +541,8 @@ static bool rest_getutxos(AcceptedConnection* conn,
ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
string ssGetUTXOResponseString = ssGetUTXOResponse.str();
- conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, ssGetUTXOResponseString.size(), "application/octet-stream") << ssGetUTXOResponseString << std::flush;
+ req->WriteHeader("Content-Type", "application/octet-stream");
+ req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
return true;
}
@@ -499,7 +551,8 @@ static bool rest_getutxos(AcceptedConnection* conn,
ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
string strHex = HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
+ req->WriteHeader("Content-Type", "text/plain");
+ req->WriteReply(HTTP_OK, strHex);
return true;
}
@@ -529,11 +582,12 @@ static bool rest_getutxos(AcceptedConnection* conn,
// return json string
string strJSON = objGetUTXOResponse.write() + "\n";
- conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
- throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
@@ -543,43 +597,31 @@ static bool rest_getutxos(AcceptedConnection* conn,
static const struct {
const char* prefix;
- bool (*handler)(AcceptedConnection* conn,
- const std::string& strURIPart,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun);
+ bool (*handler)(HTTPRequest* req, const std::string& strReq);
} uri_prefixes[] = {
{"/rest/tx/", rest_tx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
{"/rest/block/", rest_block_extended},
{"/rest/chaininfo", rest_chaininfo},
+ {"/rest/mempool/info", rest_mempool_info},
+ {"/rest/mempool/contents", rest_mempool_contents},
{"/rest/headers/", rest_headers},
{"/rest/getutxos", rest_getutxos},
};
-bool HTTPReq_REST(AcceptedConnection* conn,
- const std::string& strURI,
- const string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun)
+bool StartREST()
{
- try {
- std::string statusmessage;
- if (RPCIsInWarmup(&statusmessage))
- throw RESTERR(HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
-
- for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) {
- unsigned int plen = strlen(uri_prefixes[i].prefix);
- if (strURI.substr(0, plen) == uri_prefixes[i].prefix) {
- string strURIPart = strURI.substr(plen);
- return uri_prefixes[i].handler(conn, strURIPart, strRequest, mapHeaders, fRun);
- }
- }
- } catch (const RestErr& re) {
- conn->stream() << HTTPReply(re.status, re.message + "\r\n", false, false, "text/plain") << std::flush;
- return false;
- }
+ for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
+ RegisterHTTPHandler(uri_prefixes[i].prefix, false, uri_prefixes[i].handler);
+ return true;
+}
- conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush;
- return false;
+void InterruptREST()
+{
+}
+
+void StopREST()
+{
+ for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
+ UnregisterHTTPHandler(uri_prefixes[i].prefix, false);
}
diff --git a/src/reverselock.h b/src/reverselock.h
new file mode 100644
index 0000000000..567636e16a
--- /dev/null
+++ b/src/reverselock.h
@@ -0,0 +1,31 @@
+// Copyright (c) 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_REVERSELOCK_H
+#define BITCOIN_REVERSELOCK_H
+
+/**
+ * An RAII-style reverse lock. Unlocks on construction and locks on destruction.
+ */
+template<typename Lock>
+class reverse_lock
+{
+public:
+
+ explicit reverse_lock(Lock& lock) : lock(lock) {
+ lock.unlock();
+ }
+
+ ~reverse_lock() {
+ lock.lock();
+ }
+
+private:
+ reverse_lock(reverse_lock const&);
+ reverse_lock& operator=(reverse_lock const&);
+
+ Lock& lock;
+};
+
+#endif // BITCOIN_REVERSELOCK_H
diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp
index 80d49490d2..e6751de96b 100644
--- a/src/rpcblockchain.cpp
+++ b/src/rpcblockchain.cpp
@@ -175,45 +175,8 @@ UniValue getdifficulty(const UniValue& params, bool fHelp)
return GetDifficulty();
}
-
-UniValue getrawmempool(const UniValue& params, bool fHelp)
+UniValue mempoolToJSON(bool fVerbose = false)
{
- if (fHelp || params.size() > 1)
- throw runtime_error(
- "getrawmempool ( verbose )\n"
- "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
- "\nArguments:\n"
- "1. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n"
- "\nResult: (for verbose = false):\n"
- "[ (json array of string)\n"
- " \"transactionid\" (string) The transaction id\n"
- " ,...\n"
- "]\n"
- "\nResult: (for verbose = true):\n"
- "{ (json object)\n"
- " \"transactionid\" : { (json object)\n"
- " \"size\" : n, (numeric) transaction size in bytes\n"
- " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
- " \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
- " \"height\" : n, (numeric) block height when transaction entered pool\n"
- " \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
- " \"currentpriority\" : n, (numeric) transaction priority now\n"
- " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
- " \"transactionid\", (string) parent transaction id\n"
- " ... ]\n"
- " }, ...\n"
- "}\n"
- "\nExamples\n"
- + HelpExampleCli("getrawmempool", "true")
- + HelpExampleRpc("getrawmempool", "true")
- );
-
- LOCK(cs_main);
-
- bool fVerbose = false;
- if (params.size() > 0)
- fVerbose = params[0].get_bool();
-
if (fVerbose)
{
LOCK(mempool.cs);
@@ -261,6 +224,47 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
}
}
+UniValue getrawmempool(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() > 1)
+ throw runtime_error(
+ "getrawmempool ( verbose )\n"
+ "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
+ "\nArguments:\n"
+ "1. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n"
+ "\nResult: (for verbose = false):\n"
+ "[ (json array of string)\n"
+ " \"transactionid\" (string) The transaction id\n"
+ " ,...\n"
+ "]\n"
+ "\nResult: (for verbose = true):\n"
+ "{ (json object)\n"
+ " \"transactionid\" : { (json object)\n"
+ " \"size\" : n, (numeric) transaction size in bytes\n"
+ " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
+ " \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
+ " \"height\" : n, (numeric) block height when transaction entered pool\n"
+ " \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
+ " \"currentpriority\" : n, (numeric) transaction priority now\n"
+ " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
+ " \"transactionid\", (string) parent transaction id\n"
+ " ... ]\n"
+ " }, ...\n"
+ "}\n"
+ "\nExamples\n"
+ + HelpExampleCli("getrawmempool", "true")
+ + HelpExampleRpc("getrawmempool", "true")
+ );
+
+ LOCK(cs_main);
+
+ bool fVerbose = false;
+ if (params.size() > 0)
+ fVerbose = params[0].get_bool();
+
+ return mempoolToJSON(fVerbose);
+}
+
UniValue getblockhash(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
@@ -757,6 +761,16 @@ UniValue getchaintips(const UniValue& params, bool fHelp)
return res;
}
+UniValue mempoolInfoToJSON()
+{
+ UniValue ret(UniValue::VOBJ);
+ ret.push_back(Pair("size", (int64_t) mempool.size()));
+ ret.push_back(Pair("bytes", (int64_t) mempool.GetTotalTxSize()));
+ ret.push_back(Pair("usage", (int64_t) mempool.DynamicMemoryUsage()));
+
+ return ret;
+}
+
UniValue getmempoolinfo(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 0)
@@ -774,12 +788,7 @@ UniValue getmempoolinfo(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getmempoolinfo", "")
);
- UniValue ret(UniValue::VOBJ);
- ret.push_back(Pair("size", (int64_t) mempool.size()));
- ret.push_back(Pair("bytes", (int64_t) mempool.GetTotalTxSize()));
- ret.push_back(Pair("usage", (int64_t) mempool.DynamicMemoryUsage()));
-
- return ret;
+ return mempoolInfoToJSON();
}
UniValue invalidateblock(const UniValue& params, bool fHelp)
diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp
index b41e960e8a..0c8e6d6d66 100644
--- a/src/rpcclient.cpp
+++ b/src/rpcclient.cpp
@@ -87,6 +87,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "lockunspent", 1 },
{ "importprivkey", 2 },
{ "importaddress", 2 },
+ { "importaddress", 3 },
+ { "importpubkey", 2 },
{ "verifychain", 0 },
{ "verifychain", 1 },
{ "keypoolrefill", 0 },
diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp
index b7d4ff58fc..91de37fdcc 100644
--- a/src/rpcmining.cpp
+++ b/src/rpcmining.cpp
@@ -119,7 +119,7 @@ UniValue generate(const UniValue& params, bool fHelp)
"generate numblocks\n"
"\nMine blocks immediately (before the RPC call returns)\n"
"\nNote: this function can only be used on the regtest network\n"
- "1. numblocks (numeric) How many blocks are generated immediately.\n"
+ "1. numblocks (numeric, required) How many blocks are generated immediately.\n"
"\nResult\n"
"[ blockhashes ] (array) hashes of blocks generated\n"
"\nExamples:\n"
@@ -138,8 +138,12 @@ UniValue generate(const UniValue& params, bool fHelp)
boost::shared_ptr<CReserveScript> coinbaseScript;
GetMainSignals().ScriptForMining(coinbaseScript);
+ // If the keypool is exhausted, no script is returned at all. Catch this.
+ if (!coinbaseScript)
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
+
//throw an error if no script was provided
- if (!coinbaseScript->reserveScript.size())
+ if (coinbaseScript->reserveScript.empty())
throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet)");
{ // Don't keep cs_main locked
diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp
index 6d10d1ce15..e2b6d5826c 100644
--- a/src/rpcmisc.cpp
+++ b/src/rpcmisc.cpp
@@ -157,13 +157,14 @@ UniValue validateaddress(const UniValue& params, bool fHelp)
"1. \"bitcoinaddress\" (string, required) The bitcoin address to validate\n"
"\nResult:\n"
"{\n"
- " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n"
+ " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n"
" \"address\" : \"bitcoinaddress\", (string) The bitcoin address validated\n"
" \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n"
- " \"ismine\" : true|false, (boolean) If the address is yours or not\n"
- " \"isscript\" : true|false, (boolean) If the key is a script\n"
+ " \"ismine\" : true|false, (boolean) If the address is yours or not\n"
+ " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
+ " \"isscript\" : true|false, (boolean) If the key is a script\n"
" \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n"
- " \"iscompressed\" : true|false, (boolean) If the address is compressed\n"
+ " \"iscompressed\" : true|false, (boolean) If the address is compressed\n"
" \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n"
"}\n"
"\nExamples:\n"
diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp
index 56ec59171d..30d0ed6270 100644
--- a/src/rpcnet.cpp
+++ b/src/rpcnet.cpp
@@ -96,6 +96,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp)
" \"conntime\": ttt, (numeric) The connection time in seconds since epoch (Jan 1 1970 GMT)\n"
" \"timeoffset\": ttt, (numeric) The time offset in seconds\n"
" \"pingtime\": n, (numeric) ping time\n"
+ " \"minping\": n, (numeric) minimum observed ping time\n"
" \"pingwait\": n, (numeric) ping wait\n"
" \"version\": v, (numeric) The peer version, such as 7001\n"
" \"subver\": \"/Satoshi:0.8.5/\", (string) The string version\n"
@@ -139,6 +140,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp)
obj.push_back(Pair("conntime", stats.nTimeConnected));
obj.push_back(Pair("timeoffset", stats.nTimeOffset));
obj.push_back(Pair("pingtime", stats.dPingTime));
+ obj.push_back(Pair("minping", stats.dPingMin));
if (stats.dPingWait > 0.0)
obj.push_back(Pair("pingwait", stats.dPingWait));
obj.push_back(Pair("version", stats.nVersion));
diff --git a/src/rpcprotocol.cpp b/src/rpcprotocol.cpp
index 2e5c913734..d83cd87f94 100644
--- a/src/rpcprotocol.cpp
+++ b/src/rpcprotocol.cpp
@@ -5,7 +5,6 @@
#include "rpcprotocol.h"
-#include "clientversion.h"
#include "random.h"
#include "tinyformat.h"
#include "util.h"
@@ -16,236 +15,8 @@
#include <stdint.h>
#include <fstream>
-#include <boost/algorithm/string.hpp>
-#include <boost/asio.hpp>
-#include <boost/asio/ssl.hpp>
-#include <boost/bind.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/foreach.hpp>
-#include <boost/iostreams/concepts.hpp>
-#include <boost/iostreams/stream.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include "univalue/univalue.h"
-
using namespace std;
-//! Number of bytes to allocate and read at most at once in post data
-const size_t POST_READ_SIZE = 256 * 1024;
-
-/**
- * HTTP protocol
- *
- * This ain't Apache. We're just using HTTP header for the length field
- * and to be compatible with other JSON-RPC implementations.
- */
-
-string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders)
-{
- ostringstream s;
- s << "POST / HTTP/1.1\r\n"
- << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n"
- << "Host: 127.0.0.1\r\n"
- << "Content-Type: application/json\r\n"
- << "Content-Length: " << strMsg.size() << "\r\n"
- << "Connection: close\r\n"
- << "Accept: application/json\r\n";
- BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders)
- s << item.first << ": " << item.second << "\r\n";
- s << "\r\n" << strMsg;
-
- return s.str();
-}
-
-static string rfc1123Time()
-{
- return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime());
-}
-
-static const char *httpStatusDescription(int nStatus)
-{
- switch (nStatus) {
- case HTTP_OK: return "OK";
- case HTTP_BAD_REQUEST: return "Bad Request";
- case HTTP_FORBIDDEN: return "Forbidden";
- case HTTP_NOT_FOUND: return "Not Found";
- case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error";
- default: return "";
- }
-}
-
-string HTTPError(int nStatus, bool keepalive, bool headersOnly)
-{
- if (nStatus == HTTP_UNAUTHORIZED)
- return strprintf("HTTP/1.0 401 Authorization Required\r\n"
- "Date: %s\r\n"
- "Server: bitcoin-json-rpc/%s\r\n"
- "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n"
- "Content-Type: text/html\r\n"
- "Content-Length: 296\r\n"
- "\r\n"
- "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n"
- "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n"
- "<HTML>\r\n"
- "<HEAD>\r\n"
- "<TITLE>Error</TITLE>\r\n"
- "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n"
- "</HEAD>\r\n"
- "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"
- "</HTML>\r\n", rfc1123Time(), FormatFullVersion());
-
- return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive,
- headersOnly, "text/plain");
-}
-
-string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType)
-{
- return strprintf(
- "HTTP/1.1 %d %s\r\n"
- "Date: %s\r\n"
- "Connection: %s\r\n"
- "Content-Length: %u\r\n"
- "Content-Type: %s\r\n"
- "Server: bitcoin-json-rpc/%s\r\n"
- "\r\n",
- nStatus,
- httpStatusDescription(nStatus),
- rfc1123Time(),
- keepalive ? "keep-alive" : "close",
- contentLength,
- contentType,
- FormatFullVersion());
-}
-
-string HTTPReply(int nStatus, const string& strMsg, bool keepalive,
- bool headersOnly, const char *contentType)
-{
- if (headersOnly)
- {
- return HTTPReplyHeader(nStatus, keepalive, 0, contentType);
- } else {
- return HTTPReplyHeader(nStatus, keepalive, strMsg.size(), contentType) + strMsg;
- }
-}
-
-bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto,
- string& http_method, string& http_uri)
-{
- string str;
- getline(stream, str);
-
- // HTTP request line is space-delimited
- vector<string> vWords;
- boost::split(vWords, str, boost::is_any_of(" "));
- if (vWords.size() < 2)
- return false;
-
- // HTTP methods permitted: GET, POST
- http_method = vWords[0];
- if (http_method != "GET" && http_method != "POST")
- return false;
-
- // HTTP URI must be an absolute path, relative to current host
- http_uri = vWords[1];
- if (http_uri.size() == 0 || http_uri[0] != '/')
- return false;
-
- // parse proto, if present
- string strProto = "";
- if (vWords.size() > 2)
- strProto = vWords[2];
-
- proto = 0;
- const char *ver = strstr(strProto.c_str(), "HTTP/1.");
- if (ver != NULL)
- proto = atoi(ver+7);
-
- return true;
-}
-
-int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto)
-{
- string str;
- getline(stream, str);
- vector<string> vWords;
- boost::split(vWords, str, boost::is_any_of(" "));
- if (vWords.size() < 2)
- return HTTP_INTERNAL_SERVER_ERROR;
- proto = 0;
- const char *ver = strstr(str.c_str(), "HTTP/1.");
- if (ver != NULL)
- proto = atoi(ver+7);
- return atoi(vWords[1].c_str());
-}
-
-int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet)
-{
- int nLen = 0;
- while (true)
- {
- string str;
- std::getline(stream, str);
- if (str.empty() || str == "\r")
- break;
- string::size_type nColon = str.find(":");
- if (nColon != string::npos)
- {
- string strHeader = str.substr(0, nColon);
- boost::trim(strHeader);
- boost::to_lower(strHeader);
- string strValue = str.substr(nColon+1);
- boost::trim(strValue);
- mapHeadersRet[strHeader] = strValue;
- if (strHeader == "content-length")
- nLen = atoi(strValue.c_str());
- }
- }
- return nLen;
-}
-
-
-int ReadHTTPMessage(std::basic_istream<char>& stream, map<string,
- string>& mapHeadersRet, string& strMessageRet,
- int nProto, size_t max_size)
-{
- mapHeadersRet.clear();
- strMessageRet = "";
-
- // Read header
- int nLen = ReadHTTPHeaders(stream, mapHeadersRet);
- if (nLen < 0 || (size_t)nLen > max_size)
- return HTTP_INTERNAL_SERVER_ERROR;
-
- // Read message
- if (nLen > 0)
- {
- vector<char> vch;
- size_t ptr = 0;
- while (ptr < (size_t)nLen)
- {
- size_t bytes_to_read = std::min((size_t)nLen - ptr, POST_READ_SIZE);
- vch.resize(ptr + bytes_to_read);
- stream.read(&vch[ptr], bytes_to_read);
- if (!stream) // Connection lost while reading
- return HTTP_INTERNAL_SERVER_ERROR;
- ptr += bytes_to_read;
- }
- strMessageRet = string(vch.begin(), vch.end());
- }
-
- string sConHdr = mapHeadersRet["connection"];
-
- if ((sConHdr != "close") && (sConHdr != "keep-alive"))
- {
- if (nProto >= 1)
- mapHeadersRet["connection"] = "keep-alive";
- else
- mapHeadersRet["connection"] = "close";
- }
-
- return HTTP_OK;
-}
-
/**
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h
index 2360ec2c60..5381e4bcfd 100644
--- a/src/rpcprotocol.h
+++ b/src/rpcprotocol.h
@@ -10,10 +10,6 @@
#include <map>
#include <stdint.h>
#include <string>
-#include <boost/iostreams/concepts.hpp>
-#include <boost/iostreams/stream.hpp>
-#include <boost/asio.hpp>
-#include <boost/asio/ssl.hpp>
#include <boost/filesystem.hpp>
#include "univalue/univalue.h"
@@ -26,6 +22,7 @@ enum HTTPStatusCode
HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
+ HTTP_BAD_METHOD = 405,
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_SERVICE_UNAVAILABLE = 503,
};
@@ -79,88 +76,6 @@ enum RPCErrorCode
RPC_WALLET_ALREADY_UNLOCKED = -17, //! Wallet is already unlocked
};
-/**
- * IOStream device that speaks SSL but can also speak non-SSL
- */
-template <typename Protocol>
-class SSLIOStreamDevice : public boost::iostreams::device<boost::iostreams::bidirectional> {
-public:
- SSLIOStreamDevice(boost::asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn)
- {
- fUseSSL = fUseSSLIn;
- fNeedHandshake = fUseSSLIn;
- }
-
- void handshake(boost::asio::ssl::stream_base::handshake_type role)
- {
- if (!fNeedHandshake) return;
- fNeedHandshake = false;
- stream.handshake(role);
- }
- std::streamsize read(char* s, std::streamsize n)
- {
- handshake(boost::asio::ssl::stream_base::server); // HTTPS servers read first
- if (fUseSSL) return stream.read_some(boost::asio::buffer(s, n));
- return stream.next_layer().read_some(boost::asio::buffer(s, n));
- }
- std::streamsize write(const char* s, std::streamsize n)
- {
- handshake(boost::asio::ssl::stream_base::client); // HTTPS clients write first
- if (fUseSSL) return boost::asio::write(stream, boost::asio::buffer(s, n));
- return boost::asio::write(stream.next_layer(), boost::asio::buffer(s, n));
- }
- bool connect(const std::string& server, const std::string& port)
- {
- using namespace boost::asio::ip;
- tcp::resolver resolver(stream.get_io_service());
- tcp::resolver::iterator endpoint_iterator;
-#if BOOST_VERSION >= 104300
- try {
-#endif
- // The default query (flags address_configured) tries IPv6 if
- // non-localhost IPv6 configured, and IPv4 if non-localhost IPv4
- // configured.
- tcp::resolver::query query(server.c_str(), port.c_str());
- endpoint_iterator = resolver.resolve(query);
-#if BOOST_VERSION >= 104300
- } catch (const boost::system::system_error&) {
- // If we at first don't succeed, try blanket lookup (IPv4+IPv6 independent of configured interfaces)
- tcp::resolver::query query(server.c_str(), port.c_str(), resolver_query_base::flags());
- endpoint_iterator = resolver.resolve(query);
- }
-#endif
- boost::system::error_code error = boost::asio::error::host_not_found;
- tcp::resolver::iterator end;
- while (error && endpoint_iterator != end)
- {
- stream.lowest_layer().close();
- stream.lowest_layer().connect(*endpoint_iterator++, error);
- }
- if (error)
- return false;
- return true;
- }
-
-private:
- bool fNeedHandshake;
- bool fUseSSL;
- boost::asio::ssl::stream<typename Protocol::socket>& stream;
-};
-
-std::string HTTPPost(const std::string& strMsg, const std::map<std::string,std::string>& mapRequestHeaders);
-std::string HTTPError(int nStatus, bool keepalive,
- bool headerOnly = false);
-std::string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength,
- const char *contentType = "application/json");
-std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive,
- bool headerOnly = false,
- const char *contentType = "application/json");
-bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto,
- std::string& http_method, std::string& http_uri);
-int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto);
-int ReadHTTPHeaders(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet);
-int ReadHTTPMessage(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet,
- std::string& strMessageRet, int nProto, size_t max_size);
std::string JSONRPCRequest(const std::string& strMethod, const UniValue& params, const UniValue& id);
UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id);
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id);
diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp
index a3246b41fb..9eeca5b7d9 100644
--- a/src/rpcrawtransaction.cpp
+++ b/src/rpcrawtransaction.cpp
@@ -318,8 +318,9 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 2)
throw runtime_error(
- "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,...}\n"
- "\nCreate a transaction spending the given inputs and sending to the given addresses.\n"
+ "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...}\n"
+ "\nCreate a transaction spending the given inputs and creating new outputs.\n"
+ "Outputs can be addresses or data.\n"
"Returns hex-encoded raw transaction.\n"
"Note that the transaction's inputs are not signed, and\n"
"it is not stored in the wallet or transmitted to the network.\n"
@@ -328,23 +329,25 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp)
"1. \"transactions\" (string, required) A json array of json objects\n"
" [\n"
" {\n"
- " \"txid\":\"id\", (string, required) The transaction id\n"
+ " \"txid\":\"id\", (string, required) The transaction id\n"
" \"vout\":n (numeric, required) The output number\n"
" }\n"
" ,...\n"
" ]\n"
- "2. \"addresses\" (string, required) a json object with addresses as keys and amounts as values\n"
+ "2. \"outputs\" (string, required) a json object with outputs\n"
" {\n"
" \"address\": x.xxx (numeric, required) The key is the bitcoin address, the value is the " + CURRENCY_UNIT + " amount\n"
- " ,...\n"
+ " \"data\": \"hex\", (string, required) The key is \"data\", the value is hex encoded data\n"
+ " ...\n"
" }\n"
-
"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
"\nExamples\n"
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"")
+ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"data\\\":\\\"00010203\\\"}\"")
+ HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"address\\\":0.01}\"")
+ + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"data\\\":\\\"00010203\\\"}\"")
);
LOCK(cs_main);
@@ -375,19 +378,27 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp)
set<CBitcoinAddress> setAddress;
vector<string> addrList = sendTo.getKeys();
BOOST_FOREACH(const string& name_, addrList) {
- CBitcoinAddress address(name_);
- if (!address.IsValid())
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_);
- if (setAddress.count(address))
- throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_);
- setAddress.insert(address);
+ if (name_ == "data") {
+ std::vector<unsigned char> data = ParseHexV(sendTo[name_].getValStr(),"Data");
+
+ CTxOut out(0, CScript() << OP_RETURN << data);
+ rawTx.vout.push_back(out);
+ } else {
+ CBitcoinAddress address(name_);
+ if (!address.IsValid())
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_);
- CScript scriptPubKey = GetScriptForDestination(address.Get());
- CAmount nAmount = AmountFromValue(sendTo[name_]);
+ if (setAddress.count(address))
+ throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_);
+ setAddress.insert(address);
- CTxOut out(nAmount, scriptPubKey);
- rawTx.vout.push_back(out);
+ CScript scriptPubKey = GetScriptForDestination(address.Get());
+ CAmount nAmount = AmountFromValue(sendTo[name_]);
+
+ CTxOut out(nAmount, scriptPubKey);
+ rawTx.vout.push_back(out);
+ }
}
return EncodeHexTx(rawTx);
diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp
index 68091010f7..b831d3d3b2 100644
--- a/src/rpcserver.cpp
+++ b/src/rpcserver.cpp
@@ -12,13 +12,9 @@
#include "ui_interface.h"
#include "util.h"
#include "utilstrencodings.h"
-#ifdef ENABLE_WALLET
-#include "wallet/wallet.h"
-#endif
-#include <boost/algorithm/string.hpp>
-#include <boost/asio.hpp>
-#include <boost/asio/ssl.hpp>
+#include "univalue/univalue.h"
+
#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
@@ -27,28 +23,20 @@
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/thread.hpp>
+#include <boost/algorithm/string/case_conv.hpp> // for to_upper()
-#include "univalue/univalue.h"
-
-using namespace boost::asio;
using namespace RPCServer;
using namespace std;
-static std::string strRPCUserColonPass;
-
static bool fRPCRunning = false;
static bool fRPCInWarmup = true;
static std::string rpcWarmupStatus("RPC server started");
static CCriticalSection cs_rpcWarmup;
-
-//! These are created by StartRPCThreads, destroyed in StopRPCThreads
-static boost::asio::io_service* rpc_io_service = NULL;
-static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers;
-static ssl::context* rpc_ssl_context = NULL;
-static boost::thread_group* rpc_worker_group = NULL;
-static boost::asio::io_service::work *rpc_dummy_work = NULL;
-static std::vector<CSubNet> rpc_allow_subnets; //!< List of subnets to allow RPC connections from
-static std::vector< boost::shared_ptr<ip::tcp::acceptor> > rpc_acceptors;
+/* Timer-creating functions */
+static std::vector<RPCTimerInterface*> timerInterfaces;
+/* Map of name to timer.
+ * @note Can be changed to std::unique_ptr when C++11 */
+static std::map<std::string, boost::shared_ptr<RPCTimerBase> > deadlineTimers;
static struct CRPCSignals
{
@@ -169,7 +157,6 @@ vector<unsigned char> ParseHexO(const UniValue& o, string strKey)
return ParseHexV(find_value(o, strKey), strKey);
}
-
/**
* Note: This interface may still be subject to change.
*/
@@ -261,8 +248,6 @@ UniValue stop(const UniValue& params, bool fHelp)
return "Bitcoin server stopping";
}
-
-
/**
* Call Table
*/
@@ -363,6 +348,7 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "importprivkey", &importprivkey, true },
{ "wallet", "importwallet", &importwallet, true },
{ "wallet", "importaddress", &importaddress, true },
+ { "wallet", "importpubkey", &importpubkey, true },
{ "wallet", "keypoolrefill", &keypoolrefill, true },
{ "wallet", "listaccounts", &listaccounts, false },
{ "wallet", "listaddressgroupings", &listaddressgroupings, false },
@@ -398,7 +384,7 @@ CRPCTable::CRPCTable()
}
}
-const CRPCCommand *CRPCTable::operator[](const std::string& name) const
+const CRPCCommand *CRPCTable::operator[](const std::string &name) const
{
map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name);
if (it == mapCommands.end())
@@ -406,373 +392,26 @@ const CRPCCommand *CRPCTable::operator[](const std::string& name) const
return (*it).second;
}
-
-bool HTTPAuthorized(map<string, string>& mapHeaders)
-{
- string strAuth = mapHeaders["authorization"];
- if (strAuth.substr(0,6) != "Basic ")
- return false;
- string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64);
- string strUserPass = DecodeBase64(strUserPass64);
- return TimingResistantEqual(strUserPass, strRPCUserColonPass);
-}
-
-void ErrorReply(std::ostream& stream, const UniValue& objError, const UniValue& id)
-{
- // Send error reply from json-rpc error object
- int nStatus = HTTP_INTERNAL_SERVER_ERROR;
- int code = find_value(objError, "code").get_int();
- if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST;
- else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND;
- string strReply = JSONRPCReply(NullUniValue, objError, id);
- stream << HTTPReply(nStatus, strReply, false) << std::flush;
-}
-
-CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address)
-{
- CNetAddr netaddr;
- // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses
- if (address.is_v6()
- && (address.to_v6().is_v4_compatible()
- || address.to_v6().is_v4_mapped()))
- address = address.to_v6().to_v4();
-
- if(address.is_v4())
- {
- boost::asio::ip::address_v4::bytes_type bytes = address.to_v4().to_bytes();
- netaddr.SetRaw(NET_IPV4, &bytes[0]);
- }
- else
- {
- boost::asio::ip::address_v6::bytes_type bytes = address.to_v6().to_bytes();
- netaddr.SetRaw(NET_IPV6, &bytes[0]);
- }
- return netaddr;
-}
-
-bool ClientAllowed(const boost::asio::ip::address& address)
-{
- CNetAddr netaddr = BoostAsioToCNetAddr(address);
- BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets)
- if (subnet.Match(netaddr))
- return true;
- return false;
-}
-
-template <typename Protocol>
-class AcceptedConnectionImpl : public AcceptedConnection
-{
-public:
- AcceptedConnectionImpl(
- boost::asio::io_service& io_service,
- ssl::context &context,
- bool fUseSSL) :
- sslStream(io_service, context),
- _d(sslStream, fUseSSL),
- _stream(_d)
- {
- }
-
- virtual std::iostream& stream()
- {
- return _stream;
- }
-
- virtual std::string peer_address_to_string() const
- {
- return peer.address().to_string();
- }
-
- virtual void close()
- {
- _stream.close();
- }
-
- typename Protocol::endpoint peer;
- boost::asio::ssl::stream<typename Protocol::socket> sslStream;
-
-private:
- SSLIOStreamDevice<Protocol> _d;
- boost::iostreams::stream< SSLIOStreamDevice<Protocol> > _stream;
-};
-
-void ServiceConnection(AcceptedConnection *conn);
-
-//! Forward declaration required for RPCListen
-template <typename Protocol, typename SocketAcceptorService>
-static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
- ssl::context& context,
- bool fUseSSL,
- boost::shared_ptr< AcceptedConnection > conn,
- const boost::system::error_code& error);
-
-/**
- * Sets up I/O resources to accept and handle a new connection.
- */
-template <typename Protocol, typename SocketAcceptorService>
-static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
- ssl::context& context,
- const bool fUseSSL)
-{
- // Accept connection
- boost::shared_ptr< AcceptedConnectionImpl<Protocol> > conn(new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL));
-
- acceptor->async_accept(
- conn->sslStream.lowest_layer(),
- conn->peer,
- boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>,
- acceptor,
- boost::ref(context),
- fUseSSL,
- conn,
- _1));
-}
-
-
-/**
- * Accept and handle incoming connection.
- */
-template <typename Protocol, typename SocketAcceptorService>
-static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
- ssl::context& context,
- const bool fUseSSL,
- boost::shared_ptr< AcceptedConnection > conn,
- const boost::system::error_code& error)
+bool StartRPC()
{
- // Immediately start accepting new connections, except when we're cancelled or our socket is closed.
- if (error != boost::asio::error::operation_aborted && acceptor->is_open())
- RPCListen(acceptor, context, fUseSSL);
-
- AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn.get());
-
- if (error)
- {
- // TODO: Actually handle errors
- LogPrintf("%s: Error: %s\n", __func__, error.message());
- }
- // Restrict callers by IP. It is important to
- // do this before starting client thread, to filter out
- // certain DoS and misbehaving clients.
- else if (tcp_conn && !ClientAllowed(tcp_conn->peer.address()))
- {
- // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
- if (!fUseSSL)
- conn->stream() << HTTPError(HTTP_FORBIDDEN, false) << std::flush;
- conn->close();
- }
- else {
- ServiceConnection(conn.get());
- conn->close();
- }
-}
-
-static ip::tcp::endpoint ParseEndpoint(const std::string &strEndpoint, int defaultPort)
-{
- std::string addr;
- int port = defaultPort;
- SplitHostPort(strEndpoint, port, addr);
- return ip::tcp::endpoint(boost::asio::ip::address::from_string(addr), port);
-}
-
-void StartRPCThreads()
-{
- rpc_allow_subnets.clear();
- rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet
- rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost
- if (mapMultiArgs.count("-rpcallowip"))
- {
- const vector<string>& vAllow = mapMultiArgs["-rpcallowip"];
- BOOST_FOREACH(string strAllow, vAllow)
- {
- CSubNet subnet(strAllow);
- if(!subnet.IsValid())
- {
- uiInterface.ThreadSafeMessageBox(
- strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow),
- "", CClientUIInterface::MSG_ERROR);
- StartShutdown();
- return;
- }
- rpc_allow_subnets.push_back(subnet);
- }
- }
- std::string strAllowed;
- BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets)
- strAllowed += subnet.ToString() + " ";
- LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
-
- if (mapArgs["-rpcpassword"] == "")
- {
- LogPrintf("No rpcpassword set - using random cookie authentication\n");
- if (!GenerateAuthCookie(&strRPCUserColonPass)) {
- uiInterface.ThreadSafeMessageBox(
- _("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode
- "", CClientUIInterface::MSG_ERROR);
- StartShutdown();
- return;
- }
- } else {
- strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
- }
-
- assert(rpc_io_service == NULL);
- rpc_io_service = new boost::asio::io_service();
- rpc_ssl_context = new ssl::context(*rpc_io_service, ssl::context::sslv23);
-
- const bool fUseSSL = GetBoolArg("-rpcssl", false);
-
- if (fUseSSL)
- {
- rpc_ssl_context->set_options(ssl::context::no_sslv2 | ssl::context::no_sslv3);
-
- boost::filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert"));
- if (!pathCertFile.is_complete()) pathCertFile = boost::filesystem::path(GetDataDir()) / pathCertFile;
- if (boost::filesystem::exists(pathCertFile)) rpc_ssl_context->use_certificate_chain_file(pathCertFile.string());
- else LogPrintf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string());
-
- boost::filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem"));
- if (!pathPKFile.is_complete()) pathPKFile = boost::filesystem::path(GetDataDir()) / pathPKFile;
- if (boost::filesystem::exists(pathPKFile)) rpc_ssl_context->use_private_key_file(pathPKFile.string(), ssl::context::pem);
- else LogPrintf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string());
-
- string strCiphers = GetArg("-rpcsslciphers", "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH");
- SSL_CTX_set_cipher_list(rpc_ssl_context->impl(), strCiphers.c_str());
- }
-
- std::vector<ip::tcp::endpoint> vEndpoints;
- bool bBindAny = false;
- int defaultPort = GetArg("-rpcport", BaseParams().RPCPort());
- if (!mapArgs.count("-rpcallowip")) // Default to loopback if not allowing external IPs
- {
- vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::loopback(), defaultPort));
- vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::loopback(), defaultPort));
- if (mapArgs.count("-rpcbind"))
- {
- LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n");
- }
- } else if (mapArgs.count("-rpcbind")) // Specific bind address
- {
- BOOST_FOREACH(const std::string &addr, mapMultiArgs["-rpcbind"])
- {
- try {
- vEndpoints.push_back(ParseEndpoint(addr, defaultPort));
- }
- catch (const boost::system::system_error&)
- {
- uiInterface.ThreadSafeMessageBox(
- strprintf(_("Could not parse -rpcbind value %s as network address"), addr),
- "", CClientUIInterface::MSG_ERROR);
- StartShutdown();
- return;
- }
- }
- } else { // No specific bind address specified, bind to any
- vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::any(), defaultPort));
- vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::any(), defaultPort));
- // Prefer making the socket dual IPv6/IPv4 instead of binding
- // to both addresses seperately.
- bBindAny = true;
- }
-
- bool fListening = false;
- std::string strerr;
- std::string straddress;
- BOOST_FOREACH(const ip::tcp::endpoint &endpoint, vEndpoints)
- {
- try {
- boost::asio::ip::address bindAddress = endpoint.address();
- straddress = bindAddress.to_string();
- LogPrintf("Binding RPC on address %s port %i (IPv4+IPv6 bind any: %i)\n", straddress, endpoint.port(), bBindAny);
- boost::system::error_code v6_only_error;
- boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(*rpc_io_service));
-
- acceptor->open(endpoint.protocol());
- acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
-
- // Try making the socket dual IPv6/IPv4 when listening on the IPv6 "any" address
- acceptor->set_option(boost::asio::ip::v6_only(
- !bBindAny || bindAddress != boost::asio::ip::address_v6::any()), v6_only_error);
-
- acceptor->bind(endpoint);
- acceptor->listen(socket_base::max_connections);
-
- RPCListen(acceptor, *rpc_ssl_context, fUseSSL);
-
- fListening = true;
- rpc_acceptors.push_back(acceptor);
- // If dual IPv6/IPv4 bind successful, skip binding to IPv4 separately
- if(bBindAny && bindAddress == boost::asio::ip::address_v6::any() && !v6_only_error)
- break;
- }
- catch (const boost::system::system_error& e)
- {
- LogPrintf("ERROR: Binding RPC on address %s port %i failed: %s\n", straddress, endpoint.port(), e.what());
- strerr = strprintf(_("An error occurred while setting up the RPC address %s port %u for listening: %s"), straddress, endpoint.port(), e.what());
- }
- }
-
- if (!fListening) {
- uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR);
- StartShutdown();
- return;
- }
-
- rpc_worker_group = new boost::thread_group();
- for (int i = 0; i < GetArg("-rpcthreads", 4); i++)
- rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service));
+ LogPrint("rpc", "Starting RPC\n");
fRPCRunning = true;
g_rpcSignals.Started();
+ return true;
}
-void StartDummyRPCThread()
+void InterruptRPC()
{
- if(rpc_io_service == NULL)
- {
- rpc_io_service = new boost::asio::io_service();
- /* Create dummy "work" to keep the thread from exiting when no timeouts active,
- * see http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.stopping_the_io_service_from_running_out_of_work */
- rpc_dummy_work = new boost::asio::io_service::work(*rpc_io_service);
- rpc_worker_group = new boost::thread_group();
- rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service));
- fRPCRunning = true;
- }
+ LogPrint("rpc", "Interrupting RPC\n");
+ // Interrupt e.g. running longpolls
+ fRPCRunning = false;
}
-void StopRPCThreads()
+void StopRPC()
{
- if (rpc_io_service == NULL) return;
- // Set this to false first, so that longpolling loops will exit when woken up
- fRPCRunning = false;
-
- // First, cancel all timers and acceptors
- // This is not done automatically by ->stop(), and in some cases the destructor of
- // boost::asio::io_service can hang if this is skipped.
- boost::system::error_code ec;
- BOOST_FOREACH(const boost::shared_ptr<ip::tcp::acceptor> &acceptor, rpc_acceptors)
- {
- acceptor->cancel(ec);
- if (ec)
- LogPrintf("%s: Warning: %s when cancelling acceptor\n", __func__, ec.message());
- }
- rpc_acceptors.clear();
- BOOST_FOREACH(const PAIRTYPE(std::string, boost::shared_ptr<deadline_timer>) &timer, deadlineTimers)
- {
- timer.second->cancel(ec);
- if (ec)
- LogPrintf("%s: Warning: %s when cancelling timer\n", __func__, ec.message());
- }
+ LogPrint("rpc", "Stopping RPC\n");
deadlineTimers.clear();
-
- DeleteAuthCookie();
-
- rpc_io_service->stop();
g_rpcSignals.Stopped();
- if (rpc_worker_group != NULL)
- rpc_worker_group->join_all();
- delete rpc_dummy_work; rpc_dummy_work = NULL;
- delete rpc_worker_group; rpc_worker_group = NULL;
- delete rpc_ssl_context; rpc_ssl_context = NULL;
- delete rpc_io_service; rpc_io_service = NULL;
}
bool IsRPCRunning()
@@ -801,36 +440,6 @@ bool RPCIsInWarmup(std::string *outStatus)
return fRPCInWarmup;
}
-void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func)
-{
- if (!err)
- func();
-}
-
-void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds)
-{
- assert(rpc_io_service != NULL);
-
- if (deadlineTimers.count(name) == 0)
- {
- deadlineTimers.insert(make_pair(name,
- boost::shared_ptr<deadline_timer>(new deadline_timer(*rpc_io_service))));
- }
- deadlineTimers[name]->expires_from_now(boost::posix_time::seconds(nSeconds));
- deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func));
-}
-
-class JSONRequest
-{
-public:
- UniValue id;
- string strMethod;
- UniValue params;
-
- JSONRequest() { id = NullUniValue; }
- void parse(const UniValue& valRequest);
-};
-
void JSONRequest::parse(const UniValue& valRequest)
{
// Parse request
@@ -861,7 +470,6 @@ void JSONRequest::parse(const UniValue& valRequest)
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array");
}
-
static UniValue JSONRPCExecOne(const UniValue& req)
{
UniValue rpc_result(UniValue::VOBJ);
@@ -886,7 +494,7 @@ static UniValue JSONRPCExecOne(const UniValue& req)
return rpc_result;
}
-static string JSONRPCExecBatch(const UniValue& vReq)
+std::string JSONRPCExecBatch(const UniValue& vReq)
{
UniValue ret(UniValue::VARR);
for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++)
@@ -895,107 +503,6 @@ static string JSONRPCExecBatch(const UniValue& vReq)
return ret.write() + "\n";
}
-static bool HTTPReq_JSONRPC(AcceptedConnection *conn,
- string& strRequest,
- map<string, string>& mapHeaders,
- bool fRun)
-{
- // Check authorization
- if (mapHeaders.count("authorization") == 0)
- {
- conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush;
- return false;
- }
-
- if (!HTTPAuthorized(mapHeaders))
- {
- LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string());
- /* Deter brute-forcing
- We don't support exposing the RPC port, so this shouldn't result
- in a DoS. */
- MilliSleep(250);
-
- conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush;
- return false;
- }
-
- JSONRequest jreq;
- try
- {
- // Parse request
- UniValue valRequest;
- if (!valRequest.read(strRequest))
- throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
-
- string strReply;
-
- // singleton request
- if (valRequest.isObject()) {
- jreq.parse(valRequest);
-
- UniValue result = tableRPC.execute(jreq.strMethod, jreq.params);
-
- // Send reply
- strReply = JSONRPCReply(result, NullUniValue, jreq.id);
-
- // array of requests
- } else if (valRequest.isArray())
- strReply = JSONRPCExecBatch(valRequest.get_array());
- else
- throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
-
- conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, strReply.size()) << strReply << std::flush;
- }
- catch (const UniValue& objError)
- {
- ErrorReply(conn->stream(), objError, jreq.id);
- return false;
- }
- catch (const std::exception& e)
- {
- ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
- return false;
- }
- return true;
-}
-
-void ServiceConnection(AcceptedConnection *conn)
-{
- bool fRun = true;
- while (fRun && !ShutdownRequested())
- {
- int nProto = 0;
- map<string, string> mapHeaders;
- string strRequest, strMethod, strURI;
-
- // Read HTTP request line
- if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI))
- break;
-
- // Read HTTP message headers and body
- ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto, MAX_SIZE);
-
- // HTTP Keep-Alive is false; close connection immediately
- if ((mapHeaders["connection"] == "close") || (!GetBoolArg("-rpckeepalive", true)))
- fRun = false;
-
- // Process via JSON-RPC API
- if (strURI == "/") {
- if (!HTTPReq_JSONRPC(conn, strRequest, mapHeaders, fRun))
- break;
-
- // Process via HTTP REST API
- } else if (strURI.substr(0, 6) == "/rest/" && GetBoolArg("-rest", false)) {
- if (!HTTPReq_REST(conn, strURI, strRequest, mapHeaders, fRun))
- break;
-
- } else {
- conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush;
- break;
- }
- }
-}
-
UniValue CRPCTable::execute(const std::string &strMethod, const UniValue &params) const
{
// Return immediately if in warmup
@@ -1036,4 +543,26 @@ std::string HelpExampleRpc(const std::string& methodname, const std::string& arg
"\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n";
}
+void RPCRegisterTimerInterface(RPCTimerInterface *iface)
+{
+ timerInterfaces.push_back(iface);
+}
+
+void RPCUnregisterTimerInterface(RPCTimerInterface *iface)
+{
+ std::vector<RPCTimerInterface*>::iterator i = std::find(timerInterfaces.begin(), timerInterfaces.end(), iface);
+ assert(i != timerInterfaces.end());
+ timerInterfaces.erase(i);
+}
+
+void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds)
+{
+ if (timerInterfaces.empty())
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC");
+ deadlineTimers.erase(name);
+ RPCTimerInterface* timerInterface = timerInterfaces[0];
+ LogPrint("rpc", "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name());
+ deadlineTimers.insert(std::make_pair(name, timerInterface->NewTimer(func, nSeconds*1000)));
+}
+
const CRPCTable tableRPC;
diff --git a/src/rpcserver.h b/src/rpcserver.h
index 89d3980223..83cc37918b 100644
--- a/src/rpcserver.h
+++ b/src/rpcserver.h
@@ -32,26 +32,17 @@ namespace RPCServer
class CBlockIndex;
class CNetAddr;
-class AcceptedConnection
+class JSONRequest
{
public:
- virtual ~AcceptedConnection() {}
+ UniValue id;
+ std::string strMethod;
+ UniValue params;
- virtual std::iostream& stream() = 0;
- virtual std::string peer_address_to_string() const = 0;
- virtual void close() = 0;
+ JSONRequest() { id = NullUniValue; }
+ void parse(const UniValue& valRequest);
};
-/** Start RPC threads */
-void StartRPCThreads();
-/**
- * Alternative to StartRPCThreads for the GUI, when no server is
- * used. The RPC thread in this case is only used to handle timeouts.
- * If real RPC threads have already been started this is a no-op.
- */
-void StartDummyRPCThread();
-/** Stop RPC threads */
-void StopRPCThreads();
/** Query whether RPC is running */
bool IsRPCRunning();
@@ -81,15 +72,45 @@ void RPCTypeCheck(const UniValue& params,
void RPCTypeCheckObj(const UniValue& o,
const std::map<std::string, UniValue::VType>& typesExpected, bool fAllowNull=false);
+/** Opaque base class for timers returned by NewTimerFunc.
+ * This provides no methods at the moment, but makes sure that delete
+ * cleans up the whole state.
+ */
+class RPCTimerBase
+{
+public:
+ virtual ~RPCTimerBase() {}
+};
+
/**
- * Run func nSeconds from now. Uses boost deadline timers.
+ * RPC timer "driver".
+ */
+class RPCTimerInterface
+{
+public:
+ virtual ~RPCTimerInterface() {}
+ /** Implementation name */
+ virtual const char *Name() = 0;
+ /** Factory function for timers.
+ * RPC will call the function to create a timer that will call func in *millis* milliseconds.
+ * @note As the RPC mechanism is backend-neutral, it can use different implementations of timers.
+ * This is needed to cope with the case in which there is no HTTP server, but
+ * only GUI RPC console, and to break the dependency of pcserver on httprpc.
+ */
+ virtual RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) = 0;
+};
+
+/** Register factory function for timers */
+void RPCRegisterTimerInterface(RPCTimerInterface *iface);
+/** Unregister factory function for timers */
+void RPCUnregisterTimerInterface(RPCTimerInterface *iface);
+
+/**
+ * Run func nSeconds from now.
* Overrides previous timer <name> (if any).
*/
void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds);
-//! Convert boost::asio address to CNetAddr
-extern CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address);
-
typedef UniValue(*rpcfn_type)(const UniValue& params, bool fHelp);
class CRPCCommand
@@ -134,9 +155,6 @@ extern uint256 ParseHashO(const UniValue& o, std::string strKey);
extern std::vector<unsigned char> ParseHexV(const UniValue& v, std::string strName);
extern std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey);
-extern void InitRPCMining();
-extern void ShutdownRPCMining();
-
extern int64_t nWalletUnlockTime;
extern CAmount AmountFromValue(const UniValue& value);
extern UniValue ValueFromAmount(const CAmount& amount);
@@ -161,6 +179,7 @@ extern UniValue clearbanned(const UniValue& params, bool fHelp);
extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp
extern UniValue importprivkey(const UniValue& params, bool fHelp);
extern UniValue importaddress(const UniValue& params, bool fHelp);
+extern UniValue importpubkey(const UniValue& params, bool fHelp);
extern UniValue dumpwallet(const UniValue& params, bool fHelp);
extern UniValue importwallet(const UniValue& params, bool fHelp);
@@ -243,11 +262,9 @@ extern UniValue getchaintips(const UniValue& params, bool fHelp);
extern UniValue invalidateblock(const UniValue& params, bool fHelp);
extern UniValue reconsiderblock(const UniValue& params, bool fHelp);
-// in rest.cpp
-extern bool HTTPReq_REST(AcceptedConnection *conn,
- const std::string& strURI,
- const std::string& strRequest,
- const std::map<std::string, std::string>& mapHeaders,
- bool fRun);
+bool StartRPC();
+void InterruptRPC();
+void StopRPC();
+std::string JSONRPCExecBatch(const UniValue& vReq);
#endif // BITCOIN_RPCSERVER_H
diff --git a/src/scheduler.cpp b/src/scheduler.cpp
index d5bb588b71..184ddc28ab 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -4,6 +4,8 @@
#include "scheduler.h"
+#include "reverselock.h"
+
#include <assert.h>
#include <boost/bind.hpp>
#include <utility>
@@ -65,11 +67,12 @@ void CScheduler::serviceQueue()
Function f = taskQueue.begin()->second;
taskQueue.erase(taskQueue.begin());
- // Unlock before calling f, so it can reschedule itself or another task
- // without deadlocking:
- lock.unlock();
- f();
- lock.lock();
+ {
+ // Unlock before calling f, so it can reschedule itself or another task
+ // without deadlocking:
+ reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
+ f();
+ }
} catch (...) {
--nThreadsServicingQueue;
throw;
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index 0b78fdf5a8..bd5e54b33f 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1128,7 +1128,7 @@ bool TransactionSignatureChecker::CheckSig(const vector<unsigned char>& vchSigIn
bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const
{
- // There are two times of nLockTime: lock-by-blockheight
+ // There are two kinds of nLockTime: lock-by-blockheight
// and lock-by-blocktime, distinguished by whether
// nLockTime < LOCKTIME_THRESHOLD.
//
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index 66657127ab..1d5aac7b34 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -286,6 +286,11 @@ CScript GetScriptForDestination(const CTxDestination& dest)
return script;
}
+CScript GetScriptForRawPubKey(const CPubKey& pubKey)
+{
+ return CScript() << std::vector<unsigned char>(pubKey.begin(), pubKey.end()) << OP_CHECKSIG;
+}
+
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
{
CScript script;
diff --git a/src/script/standard.h b/src/script/standard.h
index 46ae5f9f10..9e17dac700 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -73,6 +73,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);
CScript GetScriptForDestination(const CTxDestination& dest);
+CScript GetScriptForRawPubKey(const CPubKey& pubkey);
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
#endif // BITCOIN_SCRIPT_STANDARD_H
diff --git a/src/sync.cpp b/src/sync.cpp
index a422939964..1837e8d53d 100644
--- a/src/sync.cpp
+++ b/src/sync.cpp
@@ -33,20 +33,22 @@ void PrintLockContention(const char* pszName, const char* pszFile, int nLine)
//
struct CLockLocation {
- CLockLocation(const char* pszName, const char* pszFile, int nLine)
+ CLockLocation(const char* pszName, const char* pszFile, int nLine, bool fTryIn)
{
mutexName = pszName;
sourceFile = pszFile;
sourceLine = nLine;
+ fTry = fTryIn;
}
std::string ToString() const
{
- return mutexName + " " + sourceFile + ":" + itostr(sourceLine);
+ return mutexName + " " + sourceFile + ":" + itostr(sourceLine) + (fTry ? " (TRY)" : "");
}
std::string MutexName() const { return mutexName; }
+ bool fTry;
private:
std::string mutexName;
std::string sourceFile;
@@ -62,23 +64,52 @@ static boost::thread_specific_ptr<LockStack> lockstack;
static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, const LockStack& s1, const LockStack& s2)
{
+ // We attempt to not assert on probably-not deadlocks by assuming that
+ // a try lock will immediately have otherwise bailed if it had
+ // failed to get the lock
+ // We do this by, for the locks which triggered the potential deadlock,
+ // in either lockorder, checking that the second of the two which is locked
+ // is only a TRY_LOCK, ignoring locks if they are reentrant.
+ bool firstLocked = false;
+ bool secondLocked = false;
+ bool onlyMaybeDeadlock = false;
+
LogPrintf("POTENTIAL DEADLOCK DETECTED\n");
LogPrintf("Previous lock order was:\n");
BOOST_FOREACH (const PAIRTYPE(void*, CLockLocation) & i, s2) {
- if (i.first == mismatch.first)
+ if (i.first == mismatch.first) {
LogPrintf(" (1)");
- if (i.first == mismatch.second)
+ if (!firstLocked && secondLocked && i.second.fTry)
+ onlyMaybeDeadlock = true;
+ firstLocked = true;
+ }
+ if (i.first == mismatch.second) {
LogPrintf(" (2)");
+ if (!secondLocked && firstLocked && i.second.fTry)
+ onlyMaybeDeadlock = true;
+ secondLocked = true;
+ }
LogPrintf(" %s\n", i.second.ToString());
}
+ firstLocked = false;
+ secondLocked = false;
LogPrintf("Current lock order is:\n");
BOOST_FOREACH (const PAIRTYPE(void*, CLockLocation) & i, s1) {
- if (i.first == mismatch.first)
+ if (i.first == mismatch.first) {
LogPrintf(" (1)");
- if (i.first == mismatch.second)
+ if (!firstLocked && secondLocked && i.second.fTry)
+ onlyMaybeDeadlock = true;
+ firstLocked = true;
+ }
+ if (i.first == mismatch.second) {
LogPrintf(" (2)");
+ if (!secondLocked && firstLocked && i.second.fTry)
+ onlyMaybeDeadlock = true;
+ secondLocked = true;
+ }
LogPrintf(" %s\n", i.second.ToString());
}
+ assert(onlyMaybeDeadlock);
}
static void push_lock(void* c, const CLockLocation& locklocation, bool fTry)
@@ -101,10 +132,8 @@ static void push_lock(void* c, const CLockLocation& locklocation, bool fTry)
lockorders[p1] = (*lockstack);
std::pair<void*, void*> p2 = std::make_pair(c, i.first);
- if (lockorders.count(p2)) {
+ if (lockorders.count(p2))
potential_deadlock_detected(p1, lockorders[p2], lockorders[p1]);
- break;
- }
}
}
dd_mutex.unlock();
@@ -119,7 +148,7 @@ static void pop_lock()
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry)
{
- push_lock(cs, CLockLocation(pszName, pszFile, nLine), fTry);
+ push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry), fTry);
}
void LeaveCritical()
diff --git a/src/sync.h b/src/sync.h
index 705647e4a5..68a9443084 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -16,7 +16,7 @@
////////////////////////////////////////////////
// //
-// THE SIMPLE DEFINITON, EXCLUDING DEBUG CODE //
+// THE SIMPLE DEFINITION, EXCLUDING DEBUG CODE //
// //
////////////////////////////////////////////////
diff --git a/src/test/Checkpoints_tests.cpp b/src/test/Checkpoints_tests.cpp
index 703cf307d1..0a23c430ed 100644
--- a/src/test/Checkpoints_tests.cpp
+++ b/src/test/Checkpoints_tests.cpp
@@ -20,7 +20,7 @@ BOOST_FIXTURE_TEST_SUITE(Checkpoints_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(sanity)
{
- const Checkpoints::CCheckpointData& checkpoints = Params(CBaseChainParams::MAIN).Checkpoints();
+ const CCheckpointData& checkpoints = Params(CBaseChainParams::MAIN).Checkpoints();
BOOST_CHECK(Checkpoints::GetTotalBlocksEstimate(checkpoints) >= 134444);
}
diff --git a/src/test/data/bitcoin-util-test.json b/src/test/data/bitcoin-util-test.json
index afd35af503..3bf80ca434 100644
--- a/src/test/data/bitcoin-util-test.json
+++ b/src/test/data/bitcoin-util-test.json
@@ -56,5 +56,35 @@
"sign=ALL",
"outaddr=0.001:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7"],
"output_cmp": "txcreatesign.hex"
+ },
+ { "exec": "./bitcoin-tx",
+ "args":
+ ["-create",
+ "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
+ "outdata=4:badhexdata"],
+ "return_code": 1
+ },
+ { "exec": "./bitcoin-tx",
+ "args":
+ ["-create",
+ "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
+ "outdata=badhexdata"],
+ "return_code": 1
+ },
+ { "exec": "./bitcoin-tx",
+ "args":
+ ["-create",
+ "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
+ "outaddr=0.18:13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
+ "outdata=4:54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e"],
+ "output_cmp": "txcreatedata1.hex"
+ },
+ { "exec": "./bitcoin-tx",
+ "args":
+ ["-create",
+ "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
+ "outaddr=0.18:13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
+ "outdata=54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e"],
+ "output_cmp": "txcreatedata2.hex"
}
]
diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json
index 20bdbd08a5..5cad5af7c3 100644
--- a/src/test/data/tx_invalid.json
+++ b/src/test/data/tx_invalid.json
@@ -128,7 +128,7 @@
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP2 1"]],
"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000fe64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"],
-["By-time locks, with argument just beyond tx nLockTime (but within numerical boundries)"],
+["By-time locks, with argument just beyond tx nLockTime (but within numerical boundaries)"],
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000001 NOP2 1"]],
"01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"],
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 NOP2 1"]],
@@ -181,7 +181,7 @@
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 NOP2 1"]],
"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffff7f", "P2SH,CHECKLOCKTIMEVERIFY"],
-["6 byte non-minimally-encoded arguments are invalid even in their contents are valid"],
+["6 byte non-minimally-encoded arguments are invalid even if their contents are valid"],
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x06 0x000000000000 NOP2 1"]],
"010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"],
diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json
index 24fff575c1..9744a3c848 100644
--- a/src/test/data/tx_valid.json
+++ b/src/test/data/tx_valid.json
@@ -197,7 +197,7 @@
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"]],
"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"],
-["By-time locks, with argument just beyond tx nLockTime (but within numerical boundries)"],
+["By-time locks, with argument just beyond tx nLockTime (but within numerical boundaries)"],
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP2 1"]],
"01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"],
[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 NOP2 1"]],
diff --git a/src/test/data/txcreatedata1.hex b/src/test/data/txcreatedata1.hex
new file mode 100644
index 0000000000..eccc7604e6
--- /dev/null
+++ b/src/test/data/txcreatedata1.hex
@@ -0,0 +1 @@
+01000000011f5c38dfcf6f1a5f5a87c416076d392c87e6d41970d5ad5e477a02d66bde97580000000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d71700000000526a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e00000000
diff --git a/src/test/data/txcreatedata2.hex b/src/test/data/txcreatedata2.hex
new file mode 100644
index 0000000000..3c7644c297
--- /dev/null
+++ b/src/test/data/txcreatedata2.hex
@@ -0,0 +1 @@
+01000000011f5c38dfcf6f1a5f5a87c416076d392c87e6d41970d5ad5e477a02d66bde97580000000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0000000000000000526a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e00000000
diff --git a/src/test/limitedmap_tests.cpp b/src/test/limitedmap_tests.cpp
new file mode 100644
index 0000000000..faaddffad8
--- /dev/null
+++ b/src/test/limitedmap_tests.cpp
@@ -0,0 +1,101 @@
+// 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 "limitedmap.h"
+
+#include "test/test_bitcoin.h"
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(limitedmap_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(limitedmap_test)
+{
+ // create a limitedmap capped at 10 items
+ limitedmap<int, int> map(10);
+
+ // check that the max size is 10
+ BOOST_CHECK(map.max_size() == 10);
+
+ // check that it's empty
+ BOOST_CHECK(map.size() == 0);
+
+ // insert (-1, -1)
+ map.insert(std::pair<int, int>(-1, -1));
+
+ // make sure that the size is updated
+ BOOST_CHECK(map.size() == 1);
+
+ // make sure that the new items is in the map
+ BOOST_CHECK(map.count(-1) == 1);
+
+ // insert 10 new items
+ for (int i = 0; i < 10; i++) {
+ map.insert(std::pair<int, int>(i, i + 1));
+ }
+
+ // make sure that the map now contains 10 items...
+ BOOST_CHECK(map.size() == 10);
+
+ // ...and that the first item has been discarded
+ BOOST_CHECK(map.count(-1) == 0);
+
+ // iterate over the map, both with an index and an iterator
+ limitedmap<int, int>::const_iterator it = map.begin();
+ for (int i = 0; i < 10; i++) {
+ // make sure the item is present
+ BOOST_CHECK(map.count(i) == 1);
+
+ // use the iterator to check for the expected key adn value
+ BOOST_CHECK(it->first == i);
+ BOOST_CHECK(it->second == i + 1);
+
+ // use find to check for the value
+ BOOST_CHECK(map.find(i)->second == i + 1);
+
+ // update and recheck
+ map.update(it, i + 2);
+ BOOST_CHECK(map.find(i)->second == i + 2);
+
+ it++;
+ }
+
+ // check that we've exhausted the iterator
+ BOOST_CHECK(it == map.end());
+
+ // resize the map to 5 items
+ map.max_size(5);
+
+ // check that the max size and size are now 5
+ BOOST_CHECK(map.max_size() == 5);
+ BOOST_CHECK(map.size() == 5);
+
+ // check that items less than 5 have been discarded
+ // and items greater than 5 are retained
+ for (int i = 0; i < 10; i++) {
+ if (i < 5) {
+ BOOST_CHECK(map.count(i) == 0);
+ } else {
+ BOOST_CHECK(map.count(i) == 1);
+ }
+ }
+
+ // erase some items not in the map
+ for (int i = 100; i < 1000; i += 100) {
+ map.erase(i);
+ }
+
+ // check that the size is unaffected
+ BOOST_CHECK(map.size() == 5);
+
+ // erase the remaining elements
+ for (int i = 5; i < 10; i++) {
+ map.erase(i);
+ }
+
+ // check that the map is now empty
+ BOOST_CHECK(map.empty());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index 7154476c7c..3f99dc98d9 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -7,6 +7,7 @@
#include <string>
+#include <boost/assign/list_of.hpp>
#include <boost/test/unit_test.hpp>
using namespace std;
@@ -156,4 +157,20 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_CHECK(CSubNet(CNetAddr("1:2:3:4:5:6:7:8")).ToString() == "1:2:3:4:5:6:7:8/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
}
+BOOST_AUTO_TEST_CASE(netbase_getgroup)
+{
+ BOOST_CHECK(CNetAddr("127.0.0.1").GetGroup() == boost::assign::list_of(0)); // Local -> !Routable()
+ BOOST_CHECK(CNetAddr("257.0.0.1").GetGroup() == boost::assign::list_of(0)); // !Valid -> !Routable()
+ BOOST_CHECK(CNetAddr("10.0.0.1").GetGroup() == boost::assign::list_of(0)); // RFC1918 -> !Routable()
+ BOOST_CHECK(CNetAddr("169.254.1.1").GetGroup() == boost::assign::list_of(0)); // RFC3927 -> !Routable()
+ BOOST_CHECK(CNetAddr("1.2.3.4").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // IPv4
+ BOOST_CHECK(CNetAddr("::FFFF:0:102:304").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC6145
+ BOOST_CHECK(CNetAddr("64:FF9B::102:304").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC6052
+ BOOST_CHECK(CNetAddr("2002:102:304:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC3964
+ BOOST_CHECK(CNetAddr("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC4380
+ BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == boost::assign::list_of((unsigned char)NET_TOR)(239)); // Tor
+ BOOST_CHECK(CNetAddr("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(4)(112)(175)); //he.net
+ BOOST_CHECK(CNetAddr("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(32)(1)); //IPv6
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/reverselock_tests.cpp b/src/test/reverselock_tests.cpp
new file mode 100644
index 0000000000..e7e627ae0f
--- /dev/null
+++ b/src/test/reverselock_tests.cpp
@@ -0,0 +1,64 @@
+// Copyright (c) 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 "reverselock.h"
+#include "test/test_bitcoin.h"
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(reverselock_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(reverselock_basics)
+{
+ boost::mutex mutex;
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ BOOST_CHECK(lock.owns_lock());
+ {
+ reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
+ BOOST_CHECK(!lock.owns_lock());
+ }
+ BOOST_CHECK(lock.owns_lock());
+}
+
+BOOST_AUTO_TEST_CASE(reverselock_errors)
+{
+ boost::mutex mutex;
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ // Make sure trying to reverse lock an unlocked lock fails
+ lock.unlock();
+
+ BOOST_CHECK(!lock.owns_lock());
+
+ bool failed = false;
+ try {
+ reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
+ } catch(...) {
+ failed = true;
+ }
+
+ BOOST_CHECK(failed);
+ BOOST_CHECK(!lock.owns_lock());
+
+ // Make sure trying to lock a lock after it has been reverse locked fails
+ failed = false;
+ bool locked = false;
+
+ lock.lock();
+ BOOST_CHECK(lock.owns_lock());
+
+ try {
+ reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
+ lock.lock();
+ locked = true;
+ } catch(...) {
+ failed = true;
+ }
+
+ BOOST_CHECK(locked && failed);
+ BOOST_CHECK(lock.owns_lock());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp
index 7946b02855..c0476db99b 100644
--- a/src/test/rpc_tests.cpp
+++ b/src/test/rpc_tests.cpp
@@ -110,6 +110,24 @@ BOOST_AUTO_TEST_CASE(rpc_rawsign)
BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true);
}
+BOOST_AUTO_TEST_CASE(rpc_createraw_op_return)
+{
+ BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\"}"));
+
+ // Allow more than one data transaction output
+ BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\",\"data\":\"68656c6c6f776f726c64\"}"));
+
+ // Key not "data" (bad address)
+ BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"somedata\":\"68656c6c6f776f726c64\"}"), runtime_error);
+
+ // Bad hex encoding of data output
+ BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"12345\"}"), runtime_error);
+ BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"12345g\"}"), runtime_error);
+
+ // Data 81 bytes long
+ BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081\"}"));
+}
+
BOOST_AUTO_TEST_CASE(rpc_format_monetary_values)
{
BOOST_CHECK(ValueFromAmount(0LL).write() == "0.00000000");
@@ -206,21 +224,6 @@ BOOST_AUTO_TEST_CASE(json_parse_errors)
BOOST_CHECK_THROW(ParseNonRFCJSONValue("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNL"), std::runtime_error);
}
-BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
-{
- // Check IPv4 addresses
- BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4");
- BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1");
- // Check IPv6 addresses
- BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1");
- BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(),
- "123:4567:89ab:cdef:123:4567:89ab:cdef");
- // v4 compatible must be interpreted as IPv4
- BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1");
- // v4 mapped must be interpreted as IPv4
- BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1");
-}
-
BOOST_AUTO_TEST_CASE(rpc_ban)
{
BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned")));
diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp
index 9368963ff2..52f41be8ae 100644
--- a/src/test/rpc_wallet_tests.cpp
+++ b/src/test/rpc_wallet_tests.cpp
@@ -27,8 +27,6 @@ BOOST_FIXTURE_TEST_SUITE(rpc_wallet_tests, TestingSetup)
BOOST_AUTO_TEST_CASE(rpc_addmultisig)
{
- LOCK(pwalletMain->cs_wallet);
-
rpcfn_type addmultisig = tableRPC["addmultisigaddress"]->actor;
// old, 65-byte-long:
@@ -68,25 +66,28 @@ BOOST_AUTO_TEST_CASE(rpc_wallet)
{
// Test RPC calls for various wallet statistics
UniValue r;
-
- LOCK2(cs_main, pwalletMain->cs_wallet);
-
- CPubKey demoPubkey = pwalletMain->GenerateNewKey();
- CBitcoinAddress demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID()));
+ CPubKey demoPubkey;
+ CBitcoinAddress demoAddress;
UniValue retValue;
string strAccount = "walletDemoAccount";
- string strPurpose = "receive";
- BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */
- CWalletDB walletdb(pwalletMain->strWalletFile);
- CAccount account;
- account.vchPubKey = demoPubkey;
- pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, strPurpose);
- walletdb.WriteAccount(strAccount, account);
- });
-
- CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey();
- CBitcoinAddress setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID()));
-
+ CBitcoinAddress setaccountDemoAddress;
+ {
+ LOCK(pwalletMain->cs_wallet);
+
+ demoPubkey = pwalletMain->GenerateNewKey();
+ demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID()));
+ string strPurpose = "receive";
+ BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */
+ CWalletDB walletdb(pwalletMain->strWalletFile);
+ CAccount account;
+ account.vchPubKey = demoPubkey;
+ pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, strPurpose);
+ walletdb.WriteAccount(strAccount, account);
+ });
+
+ CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey();
+ setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID()));
+ }
/*********************************
* setaccount
*********************************/
diff --git a/src/test/univalue_tests.cpp b/src/test/univalue_tests.cpp
index 67cb9b9623..ee31c0955b 100644
--- a/src/test/univalue_tests.cpp
+++ b/src/test/univalue_tests.cpp
@@ -314,6 +314,21 @@ BOOST_AUTO_TEST_CASE(univalue_readwrite)
BOOST_CHECK(obj["key3"].isObject());
BOOST_CHECK_EQUAL(strJson1, v.write());
+
+ /* Check for (correctly reporting) a parsing error if the initial
+ JSON construct is followed by more stuff. Note that whitespace
+ is, of course, exempt. */
+
+ BOOST_CHECK(v.read(" {}\n "));
+ BOOST_CHECK(v.isObject());
+ BOOST_CHECK(v.read(" []\n "));
+ BOOST_CHECK(v.isArray());
+
+ BOOST_CHECK(!v.read("@{}"));
+ BOOST_CHECK(!v.read("{} garbage"));
+ BOOST_CHECK(!v.read("[]{}"));
+ BOOST_CHECK(!v.read("{}[]"));
+ BOOST_CHECK(!v.read("{} 42"));
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/timedata.cpp b/src/timedata.cpp
index c3e9c75f6e..a14d69c116 100644
--- a/src/timedata.cpp
+++ b/src/timedata.cpp
@@ -40,16 +40,20 @@ static int64_t abs64(int64_t n)
return (n >= 0 ? n : -n);
}
+#define BITCOIN_TIMEDATA_MAX_SAMPLES 200
+
void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample)
{
LOCK(cs_nTimeOffset);
// Ignore duplicates
static set<CNetAddr> setKnown;
+ if (setKnown.size() == BITCOIN_TIMEDATA_MAX_SAMPLES)
+ return;
if (!setKnown.insert(ip).second)
return;
// Add data
- static CMedianFilter<int64_t> vTimeOffsets(200,0);
+ static CMedianFilter<int64_t> vTimeOffsets(BITCOIN_TIMEDATA_MAX_SAMPLES, 0);
vTimeOffsets.input(nOffsetSample);
LogPrintf("Added time data, samples %d, offset %+d (%+d minutes)\n", vTimeOffsets.size(), nOffsetSample, nOffsetSample/60);
diff --git a/src/univalue/univalue_read.cpp b/src/univalue/univalue_read.cpp
index 261771811d..64591234cb 100644
--- a/src/univalue/univalue_read.cpp
+++ b/src/univalue/univalue_read.cpp
@@ -244,16 +244,16 @@ bool UniValue::read(const char *raw)
bool expectColon = false;
vector<UniValue*> stack;
+ string tokenVal;
+ unsigned int consumed;
enum jtokentype tok = JTOK_NONE;
enum jtokentype last_tok = JTOK_NONE;
- while (1) {
+ do {
last_tok = tok;
- string tokenVal;
- unsigned int consumed;
tok = getJsonToken(tokenVal, consumed, raw);
if (tok == JTOK_NONE || tok == JTOK_ERR)
- break;
+ return false;
raw += consumed;
switch (tok) {
@@ -377,9 +377,11 @@ bool UniValue::read(const char *raw)
default:
return false;
}
- }
+ } while (!stack.empty ());
- if (stack.size() != 0)
+ /* Check that nothing follows the initial construct (parsed above). */
+ tok = getJsonToken(tokenVal, consumed, raw);
+ if (tok != JTOK_NONE)
return false;
return true;
diff --git a/src/util.cpp b/src/util.cpp
index a7ec740de8..f50d25e17a 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -794,6 +794,18 @@ void SetupEnvironment()
boost::filesystem::path::imbue(loc);
}
+bool SetupNetworking()
+{
+#ifdef WIN32
+ // Initialize Windows Sockets
+ WSADATA wsadata;
+ int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
+ if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2)
+ return false;
+#endif
+ return true;
+}
+
void SetThreadPriority(int nPriority)
{
#ifdef WIN32
diff --git a/src/util.h b/src/util.h
index afc9a378bb..0b2dc01ac6 100644
--- a/src/util.h
+++ b/src/util.h
@@ -59,6 +59,7 @@ inline std::string _(const char* psz)
}
void SetupEnvironment();
+bool SetupNetworking();
/** Return true if log accepts specified category */
bool LogAcceptCategory(const char* category);
diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp
index d365f03008..81f3b775f4 100644
--- a/src/validationinterface.cpp
+++ b/src/validationinterface.cpp
@@ -13,6 +13,7 @@ CMainSignals& GetMainSignals()
}
void RegisterValidationInterface(CValidationInterface* pwalletIn) {
+ g_signals.UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1));
g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2));
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
@@ -32,6 +33,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2));
+ g_signals.UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1));
}
void UnregisterAllValidationInterfaces() {
@@ -43,6 +45,7 @@ void UnregisterAllValidationInterfaces() {
g_signals.SetBestChain.disconnect_all_slots();
g_signals.UpdatedTransaction.disconnect_all_slots();
g_signals.SyncTransaction.disconnect_all_slots();
+ g_signals.UpdatedBlockTip.disconnect_all_slots();
}
void SyncWithWallets(const CTransaction &tx, const CBlock *pblock) {
diff --git a/src/validationinterface.h b/src/validationinterface.h
index fb0ce0bdaa..6f95ad74eb 100644
--- a/src/validationinterface.h
+++ b/src/validationinterface.h
@@ -30,6 +30,7 @@ void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL);
class CValidationInterface {
protected:
+ virtual void UpdatedBlockTip(const uint256 &newHashTip) {}
virtual void SyncTransaction(const CTransaction &tx, const CBlock *pblock) {}
virtual void SetBestChain(const CBlockLocator &locator) {}
virtual void UpdatedTransaction(const uint256 &hash) {}
@@ -44,6 +45,8 @@ protected:
};
struct CMainSignals {
+ /** Notifies listeners of updated block chain tip */
+ boost::signals2::signal<void (const uint256 &)> UpdatedBlockTip;
/** Notifies listeners of updated transaction data (transaction, and optionally the block it is found in. */
boost::signals2::signal<void (const CTransaction &, const CBlock *)> SyncTransaction;
/** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */
diff --git a/src/version.h b/src/version.h
index 38b3d2e734..6cdddf9255 100644
--- a/src/version.h
+++ b/src/version.h
@@ -9,7 +9,7 @@
* network protocol versioning
*/
-static const int PROTOCOL_VERSION = 70002;
+static const int PROTOCOL_VERSION = 70011;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
@@ -34,4 +34,7 @@ static const int BIP0031_VERSION = 60000;
//! "mempool" command, enhanced "getdata" behavior starts with this version
static const int MEMPOOL_GD_VERSION = 60002;
+//! "filter*" commands are disabled without NODE_BLOOM after and including this version
+static const int NO_BLOOM_VERSION = 70011;
+
#endif // BITCOIN_VERSION_H
diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp
index 0b0fb562e0..c86ad9758e 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -255,7 +255,7 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
{
LOCK(cs_KeyStore);
if (!IsCrypted())
- return CKeyStore::GetPubKey(address, vchPubKeyOut);
+ return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end())
@@ -263,6 +263,8 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
vchPubKeyOut = (*mi).second.first;
return true;
}
+ // Check for watch-only pubkeys
+ return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
}
return false;
}
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index dbe36a2be1..8d557979c0 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -149,46 +149,124 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
return NullUniValue;
}
+void ImportAddress(const CBitcoinAddress& address, const string& strLabel);
+void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript)
+{
+ if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE)
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+
+ pwalletMain->MarkDirty();
+
+ if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script))
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+
+ if (isRedeemScript) {
+ if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script))
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
+ ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel);
+ }
+}
+
+void ImportAddress(const CBitcoinAddress& address, const string& strLabel)
+{
+ CScript script = GetScriptForDestination(address.Get());
+ ImportScript(script, strLabel, false);
+ // add to address book or update label
+ if (address.IsValid())
+ pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
+}
+
UniValue importaddress(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
- if (fHelp || params.size() < 1 || params.size() > 3)
+ if (fHelp || params.size() < 1 || params.size() > 4)
throw runtime_error(
- "importaddress \"address\" ( \"label\" rescan )\n"
- "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
+ "importaddress \"address\" ( \"label\" rescan p2sh )\n"
+ "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n"
"\nArguments:\n"
- "1. \"address\" (string, required) The address\n"
+ "1. \"script\" (string, required) The hex-encoded script (or address)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
+ "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
"\nNote: This call can take minutes to complete if rescan is true.\n"
+ "If you have the full public key, you should call importpublickey instead of this.\n"
"\nExamples:\n"
- "\nImport an address with rescan\n"
- + HelpExampleCli("importaddress", "\"myaddress\"") +
+ "\nImport a script with rescan\n"
+ + HelpExampleCli("importaddress", "\"myscript\"") +
"\nImport using a label without rescan\n"
- + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
+ + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") +
"\nAs a JSON-RPC call\n"
- + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false")
+ + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false")
);
if (fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing addresses is disabled in pruned mode");
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ string strLabel = "";
+ if (params.size() > 1)
+ strLabel = params[1].get_str();
+
+ // Whether to perform rescan after import
+ bool fRescan = true;
+ if (params.size() > 2)
+ fRescan = params[2].get_bool();
- CScript script;
+ // Whether to import a p2sh version, too
+ bool fP2SH = false;
+ if (params.size() > 3)
+ fP2SH = params[3].get_bool();
+
+ LOCK2(cs_main, pwalletMain->cs_wallet);
CBitcoinAddress address(params[0].get_str());
if (address.IsValid()) {
- script = GetScriptForDestination(address.Get());
+ if (fP2SH)
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
+ ImportAddress(address, strLabel);
} else if (IsHex(params[0].get_str())) {
std::vector<unsigned char> data(ParseHex(params[0].get_str()));
- script = CScript(data.begin(), data.end());
+ ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH);
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
}
+ if (fRescan)
+ {
+ pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
+ pwalletMain->ReacceptWalletTransactions();
+ }
+
+ return NullUniValue;
+}
+
+UniValue importpubkey(const UniValue& params, bool fHelp)
+{
+ if (!EnsureWalletIsAvailable(fHelp))
+ return NullUniValue;
+
+ if (fHelp || params.size() < 1 || params.size() > 4)
+ throw runtime_error(
+ "importpubkey \"pubkey\" ( \"label\" rescan )\n"
+ "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
+ "\nArguments:\n"
+ "1. \"pubkey\" (string, required) The hex-encoded public key\n"
+ "2. \"label\" (string, optional, default=\"\") An optional label\n"
+ "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
+ "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nExamples:\n"
+ "\nImport a public key with rescan\n"
+ + HelpExampleCli("importpubkey", "\"mypubkey\"") +
+ "\nImport using a label without rescan\n"
+ + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")
+ );
+
+ if (fPruneMode)
+ throw JSONRPCError(RPC_WALLET_ERROR, "Importing public keys is disabled in pruned mode");
+
string strLabel = "";
if (params.size() > 1)
strLabel = params[1].get_str();
@@ -198,33 +276,28 @@ UniValue importaddress(const UniValue& params, bool fHelp)
if (params.size() > 2)
fRescan = params[2].get_bool();
- {
- if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE)
- throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
-
- // add to address book or update label
- if (address.IsValid())
- pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
-
- // Don't throw error in case an address is already there
- if (pwalletMain->HaveWatchOnly(script))
- return NullUniValue;
+ if (!IsHex(params[0].get_str()))
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
+ std::vector<unsigned char> data(ParseHex(params[0].get_str()));
+ CPubKey pubKey(data.begin(), data.end());
+ if (!pubKey.IsFullyValid())
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
- pwalletMain->MarkDirty();
+ LOCK2(cs_main, pwalletMain->cs_wallet);
- if (!pwalletMain->AddWatchOnly(script))
- throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel);
+ ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false);
- if (fRescan)
- {
- pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
- pwalletMain->ReacceptWalletTransactions();
- }
+ if (fRescan)
+ {
+ pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
+ pwalletMain->ReacceptWalletTransactions();
}
return NullUniValue;
}
+
UniValue importwallet(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 198b5baf60..5d182f3d42 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -797,7 +797,7 @@ UniValue movecmd(const UniValue& params, bool fHelp)
"4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n"
"5. \"comment\" (string, optional) An optional comment, stored in the wallet only.\n"
"\nResult:\n"
- "true|false (boolean) true if successfull.\n"
+ "true|false (boolean) true if successful.\n"
"\nExamples:\n"
"\nMove 0.01 " + CURRENCY_UNIT + " from the default account to the account named tabby\n"
+ HelpExampleCli("move", "\"\" \"tabby\" 0.01") +
@@ -2368,15 +2368,20 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
- if (fHelp || params.size() != 1)
+ if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error(
- "fundrawtransaction \"hexstring\"\n"
+ "fundrawtransaction \"hexstring\" includeWatching\n"
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
"This will not modify existing inputs, and will add one change output to the outputs.\n"
"Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n"
"The inputs added will not be signed, use signrawtransaction for that.\n"
+ "Note that all existing inputs must have their previous output transaction be in the wallet.\n"
+ "Note that all inputs selected must be of standard form and P2SH scripts must be"
+ "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n"
+ "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n"
"\nArguments:\n"
- "1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
+ "1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
+ "2. includeWatching (boolean, optional, default false) Also select inputs which are watch only\n"
"\nResult:\n"
"{\n"
" \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
@@ -2395,18 +2400,22 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
);
- RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR));
+ RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL));
// parse hex string from parameter
CTransaction origTx;
if (!DecodeHexTx(origTx, params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
+ bool includeWatching = false;
+ if (params.size() > 1)
+ includeWatching = true;
+
CMutableTransaction tx(origTx);
CAmount nFee;
string strFailReason;
int nChangePos = -1;
- if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason))
+ if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason, includeWatching))
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
UniValue result(UniValue::VOBJ);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index dcc2983139..c3b1172201 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -114,6 +114,9 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
script = GetScriptForDestination(pubkey.GetID());
if (HaveWatchOnly(script))
RemoveWatchOnly(script);
+ script = GetScriptForRawPubKey(pubkey);
+ if (HaveWatchOnly(script))
+ RemoveWatchOnly(script);
if (!fFileBacked)
return true;
@@ -1527,7 +1530,9 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i)))
- vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO));
+ vCoins.push_back(COutput(pcoin, i, nDepth,
+ ((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
+ (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO)));
}
}
}
@@ -1743,7 +1748,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
return res;
}
-bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason)
+bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching)
{
vector<CRecipient> vecSend;
@@ -1756,6 +1761,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
CCoinControl coinControl;
coinControl.fAllowOtherInputs = true;
+ coinControl.fAllowWatchOnly = includeWatching;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
coinControl.Select(txin.prevout);
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index ae007e4673..bd30b67b09 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -627,7 +627,7 @@ public:
CAmount GetWatchOnlyBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const;
- bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason);
+ bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching);
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp
index 5482348e35..d27b1531e3 100644
--- a/src/wallet/wallet_ismine.cpp
+++ b/src/wallet/wallet_ismine.cpp
@@ -9,6 +9,7 @@
#include "keystore.h"
#include "script/script.h"
#include "script/standard.h"
+#include "script/sign.h"
#include <boost/foreach.hpp>
@@ -40,7 +41,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
txnouttype whichType;
if (!Solver(scriptPubKey, whichType, vSolutions)) {
if (keystore.HaveWatchOnly(scriptPubKey))
- return ISMINE_WATCH_ONLY;
+ return ISMINE_WATCH_UNSOLVABLE;
return ISMINE_NO;
}
@@ -85,7 +86,10 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
}
}
- if (keystore.HaveWatchOnly(scriptPubKey))
- return ISMINE_WATCH_ONLY;
+ if (keystore.HaveWatchOnly(scriptPubKey)) {
+ // TODO: This could be optimized some by doing some work after the above solver
+ CScript scriptSig;
+ return ProduceSignature(DummySignatureCreator(&keystore), scriptPubKey, scriptSig) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE;
+ }
return ISMINE_NO;
}
diff --git a/src/wallet/wallet_ismine.h b/src/wallet/wallet_ismine.h
index 7846565f8d..9f45f76c6b 100644
--- a/src/wallet/wallet_ismine.h
+++ b/src/wallet/wallet_ismine.h
@@ -17,8 +17,12 @@ class CScript;
enum isminetype
{
ISMINE_NO = 0,
- ISMINE_WATCH_ONLY = 1,
- ISMINE_SPENDABLE = 2,
+ //! Indicates that we dont know how to create a scriptSig that would solve this if we were given the appropriate private keys
+ ISMINE_WATCH_UNSOLVABLE = 1,
+ //! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys
+ ISMINE_WATCH_SOLVABLE = 2,
+ ISMINE_WATCH_ONLY = ISMINE_WATCH_SOLVABLE | ISMINE_WATCH_UNSOLVABLE,
+ ISMINE_SPENDABLE = 4,
ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE
};
/** used for bitflags of isminetype */
diff --git a/src/zmq/zmqabstractnotifier.cpp b/src/zmq/zmqabstractnotifier.cpp
new file mode 100644
index 0000000000..744ec59234
--- /dev/null
+++ b/src/zmq/zmqabstractnotifier.cpp
@@ -0,0 +1,22 @@
+// Copyright (c) 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 "zmqabstractnotifier.h"
+#include "util.h"
+
+
+CZMQAbstractNotifier::~CZMQAbstractNotifier()
+{
+ assert(!psocket);
+}
+
+bool CZMQAbstractNotifier::NotifyBlock(const uint256 &/*hash*/)
+{
+ return true;
+}
+
+bool CZMQAbstractNotifier::NotifyTransaction(const CTransaction &/*transaction*/)
+{
+ return true;
+}
diff --git a/src/zmq/zmqabstractnotifier.h b/src/zmq/zmqabstractnotifier.h
new file mode 100644
index 0000000000..626d1ddf92
--- /dev/null
+++ b/src/zmq/zmqabstractnotifier.h
@@ -0,0 +1,42 @@
+// Copyright (c) 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_ZMQ_ZMQABSTRACTNOTIFIER_H
+#define BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H
+
+#include "zmqconfig.h"
+
+class CZMQAbstractNotifier;
+typedef CZMQAbstractNotifier* (*CZMQNotifierFactory)();
+
+class CZMQAbstractNotifier
+{
+public:
+ CZMQAbstractNotifier() : psocket(0) { }
+ virtual ~CZMQAbstractNotifier();
+
+ template <typename T>
+ static CZMQAbstractNotifier* Create()
+ {
+ return new T();
+ }
+
+ std::string GetType() const { return type; }
+ void SetType(const std::string &t) { type = t; }
+ std::string GetAddress() const { return address; }
+ void SetAddress(const std::string &a) { address = a; }
+
+ virtual bool Initialize(void *pcontext) = 0;
+ virtual void Shutdown() = 0;
+
+ virtual bool NotifyBlock(const uint256 &hash);
+ virtual bool NotifyTransaction(const CTransaction &transaction);
+
+protected:
+ void *psocket;
+ std::string type;
+ std::string address;
+};
+
+#endif // BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H
diff --git a/src/zmq/zmqconfig.h b/src/zmq/zmqconfig.h
new file mode 100644
index 0000000000..6057f5d1a0
--- /dev/null
+++ b/src/zmq/zmqconfig.h
@@ -0,0 +1,24 @@
+// Copyright (c) 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_ZMQ_ZMQCONFIG_H
+#define BITCOIN_ZMQ_ZMQCONFIG_H
+
+#if defined(HAVE_CONFIG_H)
+#include "config/bitcoin-config.h"
+#endif
+
+#include <stdarg.h>
+#include <string>
+
+#if ENABLE_ZMQ
+#include <zmq.h>
+#endif
+
+#include "primitives/block.h"
+#include "primitives/transaction.h"
+
+void zmqError(const char *str);
+
+#endif // BITCOIN_ZMQ_ZMQCONFIG_H
diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp
new file mode 100644
index 0000000000..71ccb59a4a
--- /dev/null
+++ b/src/zmq/zmqnotificationinterface.cpp
@@ -0,0 +1,155 @@
+// Copyright (c) 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 "zmqnotificationinterface.h"
+#include "zmqpublishnotifier.h"
+
+#include "version.h"
+#include "main.h"
+#include "streams.h"
+#include "util.h"
+
+void zmqError(const char *str)
+{
+ LogPrint("zmq", "Error: %s, errno=%s\n", str, zmq_strerror(errno));
+}
+
+CZMQNotificationInterface::CZMQNotificationInterface() : pcontext(NULL)
+{
+}
+
+CZMQNotificationInterface::~CZMQNotificationInterface()
+{
+ // ensure Shutdown if Initialize is called
+ assert(!pcontext);
+
+ for (std::list<CZMQAbstractNotifier*>::iterator i=notifiers.begin(); i!=notifiers.end(); ++i)
+ {
+ delete *i;
+ }
+}
+
+CZMQNotificationInterface* CZMQNotificationInterface::CreateWithArguments(const std::map<std::string, std::string> &args)
+{
+ CZMQNotificationInterface* notificationInterface = NULL;
+ std::map<std::string, CZMQNotifierFactory> factories;
+ std::list<CZMQAbstractNotifier*> notifiers;
+
+ factories["pubhashblock"] = CZMQAbstractNotifier::Create<CZMQPublishHashBlockNotifier>;
+ factories["pubhashtx"] = CZMQAbstractNotifier::Create<CZMQPublishHashTransactionNotifier>;
+ factories["pubrawblock"] = CZMQAbstractNotifier::Create<CZMQPublishRawBlockNotifier>;
+ factories["pubrawtx"] = CZMQAbstractNotifier::Create<CZMQPublishRawTransactionNotifier>;
+
+ for (std::map<std::string, CZMQNotifierFactory>::const_iterator i=factories.begin(); i!=factories.end(); ++i)
+ {
+ std::map<std::string, std::string>::const_iterator j = args.find("-zmq" + i->first);
+ if (j!=args.end())
+ {
+ CZMQNotifierFactory factory = i->second;
+ std::string address = j->second;
+ CZMQAbstractNotifier *notifier = factory();
+ notifier->SetType(i->first);
+ notifier->SetAddress(address);
+ notifiers.push_back(notifier);
+ }
+ }
+
+ if (!notifiers.empty())
+ {
+ notificationInterface = new CZMQNotificationInterface();
+ notificationInterface->notifiers = notifiers;
+ }
+
+ return notificationInterface;
+}
+
+// Called at startup to conditionally set up ZMQ socket(s)
+bool CZMQNotificationInterface::Initialize()
+{
+ LogPrint("zmq", "Initialize notification interface\n");
+ assert(!pcontext);
+
+ pcontext = zmq_init(1);
+
+ if (!pcontext)
+ {
+ zmqError("Unable to initialize context");
+ return false;
+ }
+
+ std::list<CZMQAbstractNotifier*>::iterator i=notifiers.begin();
+ for (; i!=notifiers.end(); ++i)
+ {
+ CZMQAbstractNotifier *notifier = *i;
+ if (notifier->Initialize(pcontext))
+ {
+ LogPrint("zmq", " Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress());
+ }
+ else
+ {
+ LogPrint("zmq", " Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress());
+ break;
+ }
+ }
+
+ if (i!=notifiers.end())
+ {
+ Shutdown();
+ return false;
+ }
+
+ return false;
+}
+
+// Called during shutdown sequence
+void CZMQNotificationInterface::Shutdown()
+{
+ LogPrint("zmq", "Shutdown notification interface\n");
+ if (pcontext)
+ {
+ for (std::list<CZMQAbstractNotifier*>::iterator i=notifiers.begin(); i!=notifiers.end(); ++i)
+ {
+ CZMQAbstractNotifier *notifier = *i;
+ LogPrint("zmq", " Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress());
+ notifier->Shutdown();
+ }
+ zmq_ctx_destroy(pcontext);
+
+ pcontext = 0;
+ }
+}
+
+void CZMQNotificationInterface::UpdatedBlockTip(const uint256 &hash)
+{
+ for (std::list<CZMQAbstractNotifier*>::iterator i = notifiers.begin(); i!=notifiers.end(); )
+ {
+ CZMQAbstractNotifier *notifier = *i;
+ if (notifier->NotifyBlock(hash))
+ {
+ i++;
+ }
+ else
+ {
+ notifier->Shutdown();
+ i = notifiers.erase(i);
+ }
+ }
+}
+
+void CZMQNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlock *pblock)
+{
+ for (std::list<CZMQAbstractNotifier*>::iterator i = notifiers.begin(); i!=notifiers.end(); )
+ {
+ CZMQAbstractNotifier *notifier = *i;
+ if (notifier->NotifyTransaction(tx))
+ {
+ i++;
+ }
+ else
+ {
+ notifier->Shutdown();
+ i = notifiers.erase(i);
+ }
+ }
+}
diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h
new file mode 100644
index 0000000000..afc0b8d24e
--- /dev/null
+++ b/src/zmq/zmqnotificationinterface.h
@@ -0,0 +1,35 @@
+// Copyright (c) 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_ZMQ_ZMQNOTIFICATIONINTERFACE_H
+#define BITCOIN_ZMQ_ZMQNOTIFICATIONINTERFACE_H
+
+#include "validationinterface.h"
+#include <string>
+#include <map>
+
+class CZMQAbstractNotifier;
+
+class CZMQNotificationInterface : public CValidationInterface
+{
+public:
+ virtual ~CZMQNotificationInterface();
+
+ static CZMQNotificationInterface* CreateWithArguments(const std::map<std::string, std::string> &args);
+
+ bool Initialize();
+ void Shutdown();
+
+protected: // CValidationInterface
+ void SyncTransaction(const CTransaction &tx, const CBlock *pblock);
+ void UpdatedBlockTip(const uint256 &newHashTip);
+
+private:
+ CZMQNotificationInterface();
+
+ void *pcontext;
+ std::list<CZMQAbstractNotifier*> notifiers;
+};
+
+#endif // BITCOIN_ZMQ_ZMQNOTIFICATIONINTERFACE_H
diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp
new file mode 100644
index 0000000000..0a6d7d0dbc
--- /dev/null
+++ b/src/zmq/zmqpublishnotifier.cpp
@@ -0,0 +1,172 @@
+// Copyright (c) 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 "zmqpublishnotifier.h"
+#include "main.h"
+#include "util.h"
+
+static std::multimap<std::string, CZMQAbstractPublishNotifier*> mapPublishNotifiers;
+
+// Internal function to send multipart message
+static int zmq_send_multipart(void *sock, const void* data, size_t size, ...)
+{
+ va_list args;
+ va_start(args, size);
+
+ while (1)
+ {
+ zmq_msg_t msg;
+
+ int rc = zmq_msg_init_size(&msg, size);
+ if (rc != 0)
+ {
+ zmqError("Unable to initialize ZMQ msg");
+ return -1;
+ }
+
+ void *buf = zmq_msg_data(&msg);
+ memcpy(buf, data, size);
+
+ data = va_arg(args, const void*);
+
+ rc = zmq_msg_send(&msg, sock, data ? ZMQ_SNDMORE : 0);
+ if (rc == -1)
+ {
+ zmqError("Unable to send ZMQ msg");
+ zmq_msg_close(&msg);
+ return -1;
+ }
+
+ zmq_msg_close(&msg);
+
+ if (!data)
+ break;
+
+ size = va_arg(args, size_t);
+ }
+ return 0;
+}
+
+bool CZMQAbstractPublishNotifier::Initialize(void *pcontext)
+{
+ assert(!psocket);
+
+ // check if address is being used by other publish notifier
+ std::multimap<std::string, CZMQAbstractPublishNotifier*>::iterator i = mapPublishNotifiers.find(address);
+
+ if (i==mapPublishNotifiers.end())
+ {
+ psocket = zmq_socket(pcontext, ZMQ_PUB);
+ if (!psocket)
+ {
+ zmqError("Failed to create socket");
+ return false;
+ }
+
+ int rc = zmq_bind(psocket, address.c_str());
+ if (rc!=0)
+ {
+ zmqError("Failed to bind address");
+ return false;
+ }
+
+ // register this notifier for the address, so it can be reused for other publish notifier
+ mapPublishNotifiers.insert(std::make_pair(address, this));
+ return true;
+ }
+ else
+ {
+ LogPrint("zmq", " Reuse socket for address %s\n", address);
+
+ psocket = i->second->psocket;
+ mapPublishNotifiers.insert(std::make_pair(address, this));
+
+ return true;
+ }
+}
+
+void CZMQAbstractPublishNotifier::Shutdown()
+{
+ assert(psocket);
+
+ int count = mapPublishNotifiers.count(address);
+
+ // remove this notifier from the list of publishers using this address
+ typedef std::multimap<std::string, CZMQAbstractPublishNotifier*>::iterator iterator;
+ std::pair<iterator, iterator> iterpair = mapPublishNotifiers.equal_range(address);
+
+ for (iterator it = iterpair.first; it != iterpair.second; ++it)
+ {
+ if (it->second==this)
+ {
+ mapPublishNotifiers.erase(it);
+ break;
+ }
+ }
+
+ if (count == 1)
+ {
+ LogPrint("zmq", "Close socket at address %s\n", address);
+ int linger = 0;
+ zmq_setsockopt(psocket, ZMQ_LINGER, &linger, sizeof(linger));
+ zmq_close(psocket);
+ }
+
+ psocket = 0;
+}
+
+bool CZMQPublishHashBlockNotifier::NotifyBlock(const uint256 &hash)
+{
+ LogPrint("zmq", "Publish hash block %s\n", hash.GetHex());
+ char data[32];
+ for (unsigned int i = 0; i < 32; i++)
+ data[31 - i] = hash.begin()[i];
+ int rc = zmq_send_multipart(psocket, "hashblock", 9, data, 32, 0);
+ return rc == 0;
+}
+
+bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &transaction)
+{
+ uint256 hash = transaction.GetHash();
+ LogPrint("zmq", "Publish hash transaction %s\n", hash.GetHex());
+ char data[32];
+ for (unsigned int i = 0; i < 32; i++)
+ data[31 - i] = hash.begin()[i];
+ int rc = zmq_send_multipart(psocket, "hashtx", 6, data, 32, 0);
+ return rc == 0;
+}
+
+bool CZMQPublishRawBlockNotifier::NotifyBlock(const uint256 &hash)
+{
+ LogPrint("zmq", "Publish raw block %s\n", hash.GetHex());
+
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ {
+ LOCK(cs_main);
+
+ CBlock block;
+ CBlockIndex* pblockindex = mapBlockIndex[hash];
+
+ if(!ReadBlockFromDisk(block, pblockindex))
+ {
+ zmqError("Can't read block from disk");
+ return false;
+ }
+
+ ss << block;
+ }
+
+ int rc = zmq_send_multipart(psocket, "rawblock", 8, &(*ss.begin()), ss.size(), 0);
+ return rc == 0;
+}
+
+bool CZMQPublishRawTransactionNotifier::NotifyTransaction(const CTransaction &transaction)
+{
+ uint256 hash = transaction.GetHash();
+ LogPrint("zmq", "Publish raw transaction %s\n", hash.GetHex());
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << transaction;
+ int rc = zmq_send_multipart(psocket, "rawtx", 5, &(*ss.begin()), ss.size(), 0);
+ return rc == 0;
+}
diff --git a/src/zmq/zmqpublishnotifier.h b/src/zmq/zmqpublishnotifier.h
new file mode 100644
index 0000000000..a0eb26f5e2
--- /dev/null
+++ b/src/zmq/zmqpublishnotifier.h
@@ -0,0 +1,41 @@
+// Copyright (c) 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_ZMQ_ZMQPUBLISHNOTIFIER_H
+#define BITCOIN_ZMQ_ZMQPUBLISHNOTIFIER_H
+
+#include "zmqabstractnotifier.h"
+
+class CZMQAbstractPublishNotifier : public CZMQAbstractNotifier
+{
+public:
+ bool Initialize(void *pcontext);
+ void Shutdown();
+};
+
+class CZMQPublishHashBlockNotifier : public CZMQAbstractPublishNotifier
+{
+public:
+ bool NotifyBlock(const uint256 &hash);
+};
+
+class CZMQPublishHashTransactionNotifier : public CZMQAbstractPublishNotifier
+{
+public:
+ bool NotifyTransaction(const CTransaction &transaction);
+};
+
+class CZMQPublishRawBlockNotifier : public CZMQAbstractPublishNotifier
+{
+public:
+ bool NotifyBlock(const uint256 &hash);
+};
+
+class CZMQPublishRawTransactionNotifier : public CZMQAbstractPublishNotifier
+{
+public:
+ bool NotifyTransaction(const CTransaction &transaction);
+};
+
+#endif // BITCOIN_ZMQ_ZMQPUBLISHNOTIFIER_H