From 84a4a7763f386934da90e2bd1e355b70023fa9ca Mon Sep 17 00:00:00 2001 From: alexhz Date: Mon, 15 Apr 2013 18:39:58 +0000 Subject: [PATCH] update to 0.4 preview --- novacoin-qt.pro | 110 +- src/addrman.cpp | 11 +- src/addrman.h | 19 +- src/alert.cpp | 239 +++ src/alert.h | 102 + src/allocators.h | 165 ++- src/base58.h | 123 +- src/bignum.h | 53 +- src/bitcoinrpc.cpp | 3543 ++++++------------------------- src/bitcoinrpc.h | 160 ++ src/checkpoints.cpp | 28 +- src/checkpoints.h | 1 - src/clientversion.h | 19 + src/crypter.cpp | 12 - src/crypter.h | 11 +- src/db.cpp | 664 ++++--- src/db.h | 131 +- src/init.cpp | 874 +++++--- src/init.h | 4 +- src/irc.cpp | 48 +- src/irc.h | 1 - src/kernel.cpp | 7 +- src/key.cpp | 55 +- src/key.h | 62 +- src/keystore.cpp | 49 +- src/keystore.h | 62 +- src/main.cpp | 897 ++++++--- src/main.h | 351 +--- src/makefile.bsd | 65 +- src/makefile.linux-mingw | 57 +- src/makefile.mingw | 111 +- src/makefile.osx | 30 +- src/makefile.unix | 58 +- src/mruset.h | 2 +- src/net.cpp | 808 +++++--- src/net.h | 93 +- src/netbase.cpp | 666 +++++- src/netbase.h | 46 +- src/noui.cpp | 32 +- src/protocol.cpp | 24 +- src/protocol.h | 23 +- src/qt/addressbookpage.cpp | 99 +- src/qt/addressbookpage.h | 13 + src/qt/addresstablemodel.cpp | 107 +- src/qt/addresstablemodel.h | 9 +- src/qt/askpassphrasedialog.cpp | 28 +- src/qt/bitcoin.cpp | 149 +- src/qt/bitcoin.qrc | 9 +- src/qt/bitcoinaddressvalidator.cpp | 16 +- src/qt/bitcoinamountfield.h | 4 +- src/qt/bitcoingui.cpp | 323 ++-- src/qt/bitcoingui.h | 36 +- src/qt/bitcoinstrings.cpp | 261 ++- src/qt/bitcoinunits.cpp | 8 +- src/qt/bitcoinunits.h | 6 +- src/qt/clientmodel.cpp | 114 +- src/qt/clientmodel.h | 19 +- src/qt/forms/aboutdialog.ui | 33 +- src/qt/forms/addressbookpage.ui | 40 +- src/qt/forms/askpassphrasedialog.ui | 27 +- src/qt/forms/messagepage.ui | 170 -- src/qt/forms/optionsdialog.ui | 460 ++++ src/qt/forms/overviewpage.ui | 506 +++-- src/qt/forms/qrcodedialog.ui | 244 ++-- src/qt/forms/rpcconsole.ui | 456 ++++ src/qt/forms/sendcoinsdialog.ui | 15 +- src/qt/forms/sendcoinsentry.ui | 2 +- src/qt/forms/signverifymessagedialog.ui | 386 ++++ src/qt/forms/transactiondescdialog.ui | 4 +- src/qt/guiconstants.h | 15 +- src/qt/guiutil.cpp | 257 +++- src/qt/guiutil.h | 52 +- src/qt/locale/bitcoin_en.ts | 2645 ++++++++++++++--------- src/qt/locale/bitcoin_ru.ts | 2320 +++++++++++++------- src/qt/macdockiconhandler.h | 9 +- src/qt/macdockiconhandler.mm | 4 +- src/qt/messagepage.cpp | 107 - src/qt/messagepage.h | 38 - src/qt/notificator.cpp | 12 +- src/qt/notificator.h | 4 +- src/qt/optionsdialog.cpp | 423 ++-- src/qt/optionsdialog.h | 63 +- src/qt/optionsmodel.cpp | 163 +- src/qt/optionsmodel.h | 36 +- src/qt/overviewpage.cpp | 86 +- src/qt/overviewpage.h | 9 +- src/qt/qrcodedialog.cpp | 90 +- src/qt/qrcodedialog.h | 18 +- src/qt/qtipcserver.cpp | 154 +- src/qt/qtipcserver.h | 10 +- src/qt/qvaluecombobox.cpp | 6 +- src/qt/qvaluecombobox.h | 9 +- src/qt/res/bitcoin-qt.rc | 38 +- src/qt/res/icons/debugwindow.png | Bin 0 -> 5402 bytes src/qt/res/icons/qrcode.png | Bin 0 -> 237 bytes src/qt/res/icons/toolbar.png | Bin 1035 -> 0 bytes src/qt/res/icons/toolbar_testnet.png | Bin 1037 -> 0 bytes src/qt/res/images/qrcode.png | Bin 5993 -> 0 bytes src/qt/res/images/splash2.jpg | Bin 5587 -> 5816 bytes src/qt/rpcconsole.cpp | 437 ++++ src/qt/rpcconsole.h | 66 + src/qt/sendcoinsdialog.cpp | 53 +- src/qt/sendcoinsdialog.h | 8 +- src/qt/sendcoinsentry.cpp | 24 +- src/qt/sendcoinsentry.h | 3 +- src/qt/signverifymessagedialog.cpp | 274 +++ src/qt/signverifymessagedialog.h | 49 + src/qt/test/test_main.cpp | 7 +- src/qt/test/uritests.cpp | 4 +- src/qt/transactiondesc.cpp | 119 +- src/qt/transactionfilterproxy.h | 2 +- src/qt/transactionrecord.cpp | 202 +- src/qt/transactionrecord.h | 3 +- src/qt/transactiontablemodel.cpp | 164 +- src/qt/transactiontablemodel.h | 7 +- src/qt/transactionview.cpp | 29 +- src/qt/transactionview.h | 1 + src/qt/walletmodel.cpp | 138 +- src/qt/walletmodel.h | 27 +- src/rpcblockchain.cpp | 232 ++ src/rpcdump.cpp | 31 +- src/rpcmining.cpp | 516 +++++ src/rpcnet.cpp | 136 ++ src/rpcrawtransaction.cpp | 532 +++++ src/rpcwallet.cpp | 1677 +++++++++++++++ src/script.cpp | 331 +++- src/script.h | 56 +- src/serialize.h | 63 +- src/sync.cpp | 128 ++ src/sync.h | 212 ++ src/test/DoS_tests.cpp | 34 +- src/test/accounting_tests.cpp | 123 ++ src/test/allocator_tests.cpp | 115 + src/test/base32_tests.cpp | 20 + src/test/base58_tests.cpp | 302 ++- src/test/base64_tests.cpp | 2 +- src/test/bignum_tests.cpp | 125 ++ src/test/data/base58_encode_decode.json | 14 + src/test/data/base58_keys_invalid.json | 152 ++ src/test/data/base58_keys_valid.json | 452 ++++ src/test/data/script_invalid.json | 224 ++- src/test/data/script_valid.json | 239 ++- src/test/data/tx_invalid.json | 64 + src/test/data/tx_valid.json | 71 + src/test/key_tests.cpp | 27 +- src/test/miner_tests.cpp | 191 ++ src/test/mruset_tests.cpp | 2 +- src/test/multisig_tests.cpp | 24 +- src/test/netbase_tests.cpp | 102 + src/test/rpc_tests.cpp | 17 +- src/test/script_P2SH_tests.cpp | 30 +- src/test/script_tests.cpp | 130 ++- src/test/sigopcount_tests.cpp | 4 +- src/test/test_bitcoin.cpp | 14 +- src/test/transaction_tests.cpp | 148 ++- src/test/util_tests.cpp | 4 +- src/test/wallet_tests.cpp | 295 +++ src/ui_interface.h | 133 +- src/uint256.h | 2 +- src/util.cpp | 728 +++---- src/util.h | 389 ++--- src/version.cpp | 15 +- src/version.h | 28 +- src/wallet.cpp | 632 ++++-- src/wallet.h | 212 ++- src/walletdb.cpp | 609 ++++-- src/walletdb.h | 56 +- 167 files changed, 20951 insertions(+), 10237 deletions(-) create mode 100644 src/alert.cpp create mode 100644 src/alert.h create mode 100644 src/clientversion.h delete mode 100644 src/qt/forms/messagepage.ui create mode 100644 src/qt/forms/optionsdialog.ui create mode 100644 src/qt/forms/rpcconsole.ui create mode 100644 src/qt/forms/signverifymessagedialog.ui delete mode 100644 src/qt/messagepage.cpp delete mode 100644 src/qt/messagepage.h create mode 100644 src/qt/res/icons/debugwindow.png create mode 100644 src/qt/res/icons/qrcode.png delete mode 100644 src/qt/res/icons/toolbar.png delete mode 100644 src/qt/res/icons/toolbar_testnet.png delete mode 100644 src/qt/res/images/qrcode.png create mode 100644 src/qt/rpcconsole.cpp create mode 100644 src/qt/rpcconsole.h create mode 100644 src/qt/signverifymessagedialog.cpp create mode 100644 src/qt/signverifymessagedialog.h create mode 100644 src/rpcblockchain.cpp create mode 100644 src/rpcmining.cpp create mode 100644 src/rpcnet.cpp create mode 100644 src/rpcrawtransaction.cpp create mode 100644 src/rpcwallet.cpp create mode 100644 src/sync.cpp create mode 100644 src/sync.h create mode 100644 src/test/accounting_tests.cpp create mode 100644 src/test/allocator_tests.cpp create mode 100644 src/test/base32_tests.cpp create mode 100644 src/test/bignum_tests.cpp create mode 100644 src/test/data/base58_encode_decode.json create mode 100644 src/test/data/base58_keys_invalid.json create mode 100644 src/test/data/base58_keys_valid.json create mode 100644 src/test/data/tx_invalid.json create mode 100644 src/test/data/tx_valid.json create mode 100644 src/test/netbase_tests.cpp create mode 100644 src/test/wallet_tests.cpp diff --git a/novacoin-qt.pro b/novacoin-qt.pro index 575af29..5b1dc22 100644 --- a/novacoin-qt.pro +++ b/novacoin-qt.pro @@ -1,19 +1,20 @@ TEMPLATE = app TARGET = novacoin-qt -VERSION = 0.6.3.0 +VERSION = 0.7.2 INCLUDEPATH += src src/json src/qt DEFINES += QT_GUI BOOST_THREAD_USE_LIB BOOST_SPIRIT_THREADSAFE CONFIG += no_include_pwd +CONFIG += thread -# for boost 1.37, add -mt to the boost libraries +# for boost 1.37, add -mt to the boost libraries # use: qmake BOOST_LIB_SUFFIX=-mt # for boost thread win32 with _win32 sufix # use: BOOST_THREAD_LIB_SUFFIX=_win32-... # or when linking against a specific BerkelyDB version: BDB_LIB_SUFFIX=-4.8 -# Dependency library locations can be customized with BOOST_INCLUDE_PATH, -# BOOST_LIB_PATH, BDB_INCLUDE_PATH, BDB_LIB_PATH -# OPENSSL_INCLUDE_PATH and OPENSSL_LIB_PATH respectively +# Dependency library locations can be customized with: +# BOOST_INCLUDE_PATH, BOOST_LIB_PATH, BDB_INCLUDE_PATH, +# BDB_LIB_PATH, OPENSSL_INCLUDE_PATH and OPENSSL_LIB_PATH respectively OBJECTS_DIR = build MOC_DIR = build @@ -30,6 +31,16 @@ contains(RELEASE, 1) { } } +!win32 { +# for extra security against potential buffer overflows: enable GCCs Stack Smashing Protection +QMAKE_CXXFLAGS *= -fstack-protector-all --param ssp-buffer-size=1 +QMAKE_LFLAGS *= -fstack-protector-all --param ssp-buffer-size=1 +# We need to exclude this for Windows cross compile with MinGW 4.2.x, as it will result in a non-working executable! +# This can be enabled for Windows, when we switch to MinGW >= 4.4.x. +} +# for extra security on Windows: enable ASLR and DEP via GCC linker flags +win32:QMAKE_LFLAGS *= -Wl,--dynamicbase -Wl,--nxcompat + # use: qmake "USE_QRCODE=1" # libqrencode (http://fukuchi.org/works/qrencode/index.en.html) must be installed for support contains(USE_QRCODE, 1) { @@ -62,10 +73,16 @@ contains(USE_DBUS, 1) { QT += dbus } -# use: qmake "FIRST_CLASS_MESSAGING=1" -contains(FIRST_CLASS_MESSAGING, 1) { - message(Building with first-class messaging) - DEFINES += FIRST_CLASS_MESSAGING +# use: qmake "USE_IPV6=1" ( enabled by default; default) +# or: qmake "USE_IPV6=0" (disabled by default) +# or: qmake "USE_IPV6=-" (not supported) +contains(USE_IPV6, -) { + message(Building without IPv6 support) +} else { + count(USE_IPV6, 0) { + USE_IPV6=1 + } + DEFINES += USE_IPV6=$$USE_IPV6 } contains(BITCOIN_NEED_QT_PLUGINS, 1) { @@ -73,27 +90,18 @@ contains(BITCOIN_NEED_QT_PLUGINS, 1) { QTPLUGIN += qcncodecs qjpcodecs qtwcodecs qkrcodecs qtaccessiblewidgets } -!windows { - # for extra security against potential buffer overflows - QMAKE_CXXFLAGS += -fstack-protector - QMAKE_LFLAGS += -fstack-protector - # do not enable this on windows, as it will result in a non-working executable! -} # regenerate src/build.h -!windows | contains(USE_BUILD_INFO, 1) { +!windows|contains(USE_BUILD_INFO, 1) { genbuild.depends = FORCE genbuild.commands = cd $$PWD; /bin/sh share/genbuild.sh $$OUT_PWD/build/build.h - genbuild.target = genbuildhook - PRE_TARGETDEPS += genbuildhook + genbuild.target = $$OUT_PWD/build/build.h + PRE_TARGETDEPS += $$OUT_PWD/build/build.h QMAKE_EXTRA_TARGETS += genbuild DEFINES += HAVE_BUILD_INFO } -QMAKE_CXXFLAGS += -msse2 -QMAKE_CFLAGS += -msse2 - -QMAKE_CXXFLAGS_WARN_ON = -fdiagnostics-show-option -Wall -Wextra -Wformat -Wformat-security -Wno-invalid-offsetof -Wno-sign-compare -Wno-unused-parameter +QMAKE_CXXFLAGS_WARN_ON = -fdiagnostics-show-option -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter -Wstack-protector # Input DEPENDPATH += src src/json src/qt @@ -103,20 +111,23 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/optionsdialog.h \ src/qt/sendcoinsdialog.h \ src/qt/addressbookpage.h \ - src/qt/messagepage.h \ + src/qt/signverifymessagedialog.h \ src/qt/aboutdialog.h \ src/qt/editaddressdialog.h \ src/qt/bitcoinaddressvalidator.h \ + src/alert.h \ src/addrman.h \ src/base58.h \ src/bignum.h \ src/checkpoints.h \ src/compat.h \ + src/sync.h \ src/util.h \ src/uint256.h \ - src/serialize.h \ + src/kernel.h \ src/scrypt_mine.h \ src/pbkdf2.h \ + src/serialize.h \ src/strlcpy.h \ src/main.h \ src/net.h \ @@ -164,7 +175,10 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/qtipcserver.h \ src/allocators.h \ src/ui_interface.h \ - src/kernel.h + src/qt/rpcconsole.h \ + src/version.h \ + src/netbase.h \ + src/clientversion.h SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/transactiontablemodel.cpp \ @@ -172,18 +186,18 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/optionsdialog.cpp \ src/qt/sendcoinsdialog.cpp \ src/qt/addressbookpage.cpp \ - src/qt/messagepage.cpp \ + src/qt/signverifymessagedialog.cpp \ src/qt/aboutdialog.cpp \ src/qt/editaddressdialog.cpp \ src/qt/bitcoinaddressvalidator.cpp \ + src/alert.cpp \ src/version.cpp \ + src/sync.cpp \ src/util.cpp \ src/netbase.cpp \ src/key.cpp \ src/script.cpp \ src/main.cpp \ - src/scrypt_mine.cpp \ - src/pbkdf2.cpp \ src/init.cpp \ src/net.cpp \ src/irc.cpp \ @@ -191,9 +205,6 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/addrman.cpp \ src/db.cpp \ src/walletdb.cpp \ - src/json/json_spirit_writer.cpp \ - src/json/json_spirit_value.cpp \ - src/json/json_spirit_reader.cpp \ src/qt/clientmodel.cpp \ src/qt/guiutil.cpp \ src/qt/transactionrecord.cpp \ @@ -210,6 +221,11 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/walletmodel.cpp \ src/bitcoinrpc.cpp \ src/rpcdump.cpp \ + src/rpcnet.cpp \ + src/rpcmining.cpp \ + src/rpcwallet.cpp \ + src/rpcblockchain.cpp \ + src/rpcrawtransaction.cpp \ src/qt/overviewpage.cpp \ src/qt/csvmodelwriter.cpp \ src/crypter.cpp \ @@ -221,10 +237,13 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/protocol.cpp \ src/qt/notificator.cpp \ src/qt/qtipcserver.cpp \ + src/qt/rpcconsole.cpp \ + src/noui.cpp \ src/kernel.cpp \ src/scrypt-x86.S \ - src/scrypt-x86_64.S - + src/scrypt-x86_64.S \ + src/scrypt_mine.cpp \ + src/pbkdf2.cpp RESOURCES += \ src/qt/bitcoin.qrc @@ -232,13 +251,15 @@ RESOURCES += \ FORMS += \ src/qt/forms/sendcoinsdialog.ui \ src/qt/forms/addressbookpage.ui \ - src/qt/forms/messagepage.ui \ + src/qt/forms/signverifymessagedialog.ui \ src/qt/forms/aboutdialog.ui \ src/qt/forms/editaddressdialog.ui \ src/qt/forms/transactiondescdialog.ui \ src/qt/forms/overviewpage.ui \ src/qt/forms/sendcoinsentry.ui \ - src/qt/forms/askpassphrasedialog.ui + src/qt/forms/askpassphrasedialog.ui \ + src/qt/forms/rpcconsole.ui \ + src/qt/forms/optionsdialog.ui contains(USE_QRCODE, 1) { HEADERS += src/qt/qrcodedialog.h @@ -266,24 +287,23 @@ isEmpty(QMAKE_LRELEASE) { win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\\lrelease.exe else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease } -isEmpty(TS_DIR):TS_DIR = src/qt/locale +isEmpty(QM_DIR):QM_DIR = $$PWD/src/qt/locale # automatically build translations, so they can be included in resource file TSQM.name = lrelease ${QMAKE_FILE_IN} TSQM.input = TRANSLATIONS -TSQM.output = $$TS_DIR/${QMAKE_FILE_BASE}.qm -TSQM.commands = $$QMAKE_LRELEASE ${QMAKE_FILE_IN} +TSQM.output = $$QM_DIR/${QMAKE_FILE_BASE}.qm +TSQM.commands = $$QMAKE_LRELEASE ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_OUT} TSQM.CONFIG = no_link QMAKE_EXTRA_COMPILERS += TSQM -PRE_TARGETDEPS += compiler_TSQM_make_all # "Other files" to show in Qt Creator OTHER_FILES += \ - doc/*.rst doc/*.txt doc/README README.md res/bitcoin-qt.rc + doc/*.rst doc/*.txt doc/README README.md res/bitcoin-qt.rc src/test/*.cpp src/test/*.h src/qt/test/*.cpp src/qt/test/*.h # platform specific defaults, if not overridden on command line isEmpty(BOOST_LIB_SUFFIX) { macx:BOOST_LIB_SUFFIX = -mt - windows:BOOST_LIB_SUFFIX = -mgw44-mt-1_53 + windows:BOOST_LIB_SUFFIX = -mgw44-mt-s-1_50 } isEmpty(BOOST_THREAD_LIB_SUFFIX) { @@ -324,7 +344,7 @@ windows:!contains(MINGW_THREAD_BUGFIX, 0) { QMAKE_LIBS_QT_ENTRY = -lmingwthrd $$QMAKE_LIBS_QT_ENTRY } -!windows:!mac { +!windows:!macx { DEFINES += LINUX LIBS += -lrt } @@ -335,14 +355,18 @@ macx:LIBS += -framework Foundation -framework ApplicationServices -framework App macx:DEFINES += MAC_OSX MSG_NOSIGNAL=0 macx:ICON = src/qt/res/icons/bitcoin.icns macx:TARGET = "NovaCoin-Qt" +macx:QMAKE_CFLAGS_THREAD += -pthread +macx:QMAKE_LFLAGS_THREAD += -pthread +macx:QMAKE_CXXFLAGS_THREAD += -pthread # Set libraries and includes at end, to use platform-defined defaults if not overridden INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH LIBS += $$join(BOOST_LIB_PATH,,-L,) $$join(BDB_LIB_PATH,,-L,) $$join(OPENSSL_LIB_PATH,,-L,) $$join(QRENCODE_LIB_PATH,,-L,) LIBS += -lssl -lcrypto -ldb_cxx$$BDB_LIB_SUFFIX # -lgdi32 has to happen after -lcrypto (see #681) -windows:LIBS += -lmswsock -loleaut32 -lws2_32 -lshlwapi -lole32 -luuid -lgdi32 +windows:LIBS += -lws2_32 -lshlwapi -lmswsock -lole32 -loleaut32 -luuid -lgdi32 LIBS += -lboost_system$$BOOST_LIB_SUFFIX -lboost_filesystem$$BOOST_LIB_SUFFIX -lboost_program_options$$BOOST_LIB_SUFFIX -lboost_thread$$BOOST_THREAD_LIB_SUFFIX +windows:LIBS += -lboost_chrono$$BOOST_LIB_SUFFIX contains(RELEASE, 1) { !windows:!macx { diff --git a/src/addrman.cpp b/src/addrman.cpp index 8257a46..4428cd1 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -102,12 +102,11 @@ CAddrInfo* CAddrMan::Create(const CAddress &addr, const CNetAddr &addrSource, in return &mapInfo[nId]; } -void CAddrMan::SwapRandom(int nRndPos1, int nRndPos2) +void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) { if (nRndPos1 == nRndPos2) return; - assert(nRndPos1 >= 0 && nRndPos2 >= 0); assert(nRndPos1 < vRandom.size() && nRndPos2 < vRandom.size()); int nId1 = vRandom[nRndPos1]; @@ -149,7 +148,7 @@ int CAddrMan::SelectTried(int nKBucket) int CAddrMan::ShrinkNew(int nUBucket) { - assert(nUBucket >= 0 && nUBucket < vvNew.size()); + assert(nUBucket >= 0 && (unsigned int)nUBucket < vvNew.size()); std::set &vNew = vvNew[nUBucket]; // first look for deletable items @@ -188,7 +187,7 @@ int CAddrMan::ShrinkNew(int nUBucket) } assert(mapInfo.count(nOldest) == 1); CAddrInfo &info = mapInfo[nOldest]; - if (--info.nRefCount == 0) + if (--info.nRefCount == 0) { SwapRandom(info.nRandomPos, vRandom.size()-1); vRandom.pop_back(); @@ -242,7 +241,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin) infoOld.nRefCount = 1; // do not update nTried, as we are going to move something else there immediately - // check whether there is place in that one, + // check whether there is place in that one, if (vNew.size() < ADDRMAN_NEW_BUCKET_SIZE) { // if so, move it back there @@ -411,7 +410,7 @@ CAddress CAddrMan::Select_(int nUnkBias) fChanceFactor *= 1.2; } } else { - // use an new node + // use a new node double fChanceFactor = 1.0; while(1) { diff --git a/src/addrman.h b/src/addrman.h index aa42025..7af6afd 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -7,6 +7,7 @@ #include "netbase.h" #include "protocol.h" #include "util.h" +#include "sync.h" #include @@ -22,13 +23,13 @@ private: // where knowledge about this address first came from CNetAddr source; - // last succesfull connection by us + // last successful connection by us int64 nLastSuccess; // last try whatsoever by us: // int64 CAddress::nLastTry - // connection attempts since last succesful attempt + // connection attempts since last successful attempt int nAttempts; // reference count in new sets (memory only) @@ -116,7 +117,7 @@ public: // * Bucket selection is based on cryptographic hashing, using a randomly-generated 256-bit key, which should not // be observable by adversaries. // * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN will introduce frequent (and expensive) -// consistency checks for the entire datastructure. +// consistency checks for the entire data structure. // total number of buckets for tried addresses #define ADDRMAN_TRIED_BUCKET_COUNT 64 @@ -173,13 +174,13 @@ private: // last used nId int nIdCount; - // table with information about all nId's + // table with information about all nIds std::map mapInfo; // find an nId based on its network address std::map mapAddr; - // randomly-ordered vector of all nId's + // randomly-ordered vector of all nIds std::vector vRandom; // number of "tried" entries @@ -204,7 +205,7 @@ protected: CAddrInfo* Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId = NULL); // Swap two elements in vRandom. - void SwapRandom(int nRandomPos1, int nRandomPos2); + void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2); // Return position in given bucket to replace. int SelectTried(int nKBucket); @@ -213,7 +214,7 @@ protected: // This is the only place where actual deletes occur. // They are never deleted while in the "tried" table, only possibly evicted back to the "new" table. int ShrinkNew(int nUBucket); - + // Move an entry from the "new" table(s) to the "tried" table // @pre vvUnkown[nOrigin].count(nId) != 0 void MakeTried(CAddrInfo& info, int nId, int nOrigin); @@ -252,8 +253,8 @@ public: // * nNew // * nTried // * number of "new" buckets - // * all nNew addrinfo's in vvNew - // * all nTried addrinfo's in vvTried + // * all nNew addrinfos in vvNew + // * all nTried addrinfos in vvTried // * for each bucket: // * number of elements // * for each element: index diff --git a/src/alert.cpp b/src/alert.cpp new file mode 100644 index 0000000..473bc98 --- /dev/null +++ b/src/alert.cpp @@ -0,0 +1,239 @@ +// +// Alert system +// + +#include +#include + +#include "alert.h" +#include "key.h" +#include "net.h" +#include "sync.h" +#include "ui_interface.h" + +using namespace std; + +map mapAlerts; +CCriticalSection cs_mapAlerts; + +static const char* pszMainKey = "043fa441fd4203d03f5df2b75ea14e36f20d39f43e7a61aa7552ab9bcd7ecb0e77a3be4585b13fcdaa22ef6e51f1ff6f2929bec2494385b086fb86610e33193195"; +static const char* pszTestKey = "043fa441fd4203d03f5df2b75ea14e36f20d39f43e7a61aa7552ab9bcd7ecb0e77a3be4585b13fcdaa22ef6e51f1ff6f2929bec2494385b086fb86610e33193195"; + +void CUnsignedAlert::SetNull() +{ + nVersion = 1; + nRelayUntil = 0; + nExpiration = 0; + nID = 0; + nCancel = 0; + setCancel.clear(); + nMinVer = 0; + nMaxVer = 0; + setSubVer.clear(); + nPriority = 0; + + strComment.clear(); + strStatusBar.clear(); + strReserved.clear(); +} + +std::string CUnsignedAlert::ToString() const +{ + std::string strSetCancel; + BOOST_FOREACH(int n, setCancel) + strSetCancel += strprintf("%d ", n); + std::string strSetSubVer; + BOOST_FOREACH(std::string str, setSubVer) + strSetSubVer += "\"" + str + "\" "; + return strprintf( + "CAlert(\n" + " nVersion = %d\n" + " nRelayUntil = %"PRI64d"\n" + " nExpiration = %"PRI64d"\n" + " nID = %d\n" + " nCancel = %d\n" + " setCancel = %s\n" + " nMinVer = %d\n" + " nMaxVer = %d\n" + " setSubVer = %s\n" + " nPriority = %d\n" + " strComment = \"%s\"\n" + " strStatusBar = \"%s\"\n" + ")\n", + nVersion, + nRelayUntil, + nExpiration, + nID, + nCancel, + strSetCancel.c_str(), + nMinVer, + nMaxVer, + strSetSubVer.c_str(), + nPriority, + strComment.c_str(), + strStatusBar.c_str()); +} + +void CUnsignedAlert::print() const +{ + printf("%s", ToString().c_str()); +} + +void CAlert::SetNull() +{ + CUnsignedAlert::SetNull(); + vchMsg.clear(); + vchSig.clear(); +} + +bool CAlert::IsNull() const +{ + return (nExpiration == 0); +} + +uint256 CAlert::GetHash() const +{ + return Hash(this->vchMsg.begin(), this->vchMsg.end()); +} + +bool CAlert::IsInEffect() const +{ + return (GetAdjustedTime() < nExpiration); +} + +bool CAlert::Cancels(const CAlert& alert) const +{ + if (!IsInEffect()) + return false; // this was a no-op before 31403 + return (alert.nID <= nCancel || setCancel.count(alert.nID)); +} + +bool CAlert::AppliesTo(int nVersion, std::string strSubVerIn) const +{ + // TODO: rework for client-version-embedded-in-strSubVer ? + return (IsInEffect() && + nMinVer <= nVersion && nVersion <= nMaxVer && + (setSubVer.empty() || setSubVer.count(strSubVerIn))); +} + +bool CAlert::AppliesToMe() const +{ + return AppliesTo(PROTOCOL_VERSION, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector())); +} + +bool CAlert::RelayTo(CNode* pnode) const +{ + if (!IsInEffect()) + return false; + // returns true if wasn't already contained in the set + if (pnode->setKnown.insert(GetHash()).second) + { + if (AppliesTo(pnode->nVersion, pnode->strSubVer) || + AppliesToMe() || + GetAdjustedTime() < nRelayUntil) + { + pnode->PushMessage("alert", *this); + return true; + } + } + return false; +} + +bool CAlert::CheckSignature() const +{ + CKey key; + if (!key.SetPubKey(ParseHex(fTestNet ? pszTestKey : pszMainKey))) + return error("CAlert::CheckSignature() : SetPubKey failed"); + if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) + return error("CAlert::CheckSignature() : verify signature failed"); + + // Now unserialize the data + CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); + sMsg >> *(CUnsignedAlert*)this; + return true; +} + +CAlert CAlert::getAlertByHash(const uint256 &hash) +{ + CAlert retval; + { + LOCK(cs_mapAlerts); + map::iterator mi = mapAlerts.find(hash); + if(mi != mapAlerts.end()) + retval = mi->second; + } + return retval; +} + +bool CAlert::ProcessAlert() +{ + if (!CheckSignature()) + return false; + if (!IsInEffect()) + return false; + + // alert.nID=max is reserved for if the alert key is + // compromised. It must have a pre-defined message, + // must never expire, must apply to all versions, + // and must cancel all previous + // alerts or it will be ignored (so an attacker can't + // send an "everything is OK, don't panic" version that + // cannot be overridden): + int maxInt = std::numeric_limits::max(); + if (nID == maxInt) + { + if (!( + nExpiration == maxInt && + nCancel == (maxInt-1) && + nMinVer == 0 && + nMaxVer == maxInt && + setSubVer.empty() && + nPriority == maxInt && + strStatusBar == "URGENT: Alert key compromised, upgrade required" + )) + return false; + } + + { + LOCK(cs_mapAlerts); + // Cancel previous alerts + for (map::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();) + { + const CAlert& alert = (*mi).second; + if (Cancels(alert)) + { + printf("cancelling alert %d\n", alert.nID); + uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); + mapAlerts.erase(mi++); + } + else if (!alert.IsInEffect()) + { + printf("expiring alert %d\n", alert.nID); + uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); + mapAlerts.erase(mi++); + } + else + mi++; + } + + // Check if this alert has been cancelled + BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) + { + const CAlert& alert = item.second; + if (alert.Cancels(*this)) + { + printf("alert already cancelled by %d\n", alert.nID); + return false; + } + } + + // Add to mapAlerts + mapAlerts.insert(make_pair(GetHash(), *this)); + // Notify UI if it applies to me + if(AppliesToMe()) + uiInterface.NotifyAlertChanged(GetHash(), CT_NEW); + } + + printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); + return true; +} diff --git a/src/alert.h b/src/alert.h new file mode 100644 index 0000000..7949c76 --- /dev/null +++ b/src/alert.h @@ -0,0 +1,102 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef _BITCOINALERT_H_ +#define _BITCOINALERT_H_ 1 + +#include +#include + +#include "uint256.h" +#include "util.h" + +class CNode; + +/** Alerts are for notifying old versions if they become too obsolete and + * need to upgrade. The message is displayed in the status bar. + * Alert messages are broadcast as a vector of signed data. Unserializing may + * not read the entire buffer if the alert is for a newer version, but older + * versions can still relay the original data. + */ +class CUnsignedAlert +{ +public: + int nVersion; + int64 nRelayUntil; // when newer nodes stop relaying to newer nodes + int64 nExpiration; + int nID; + int nCancel; + std::set setCancel; + int nMinVer; // lowest version inclusive + int nMaxVer; // highest version inclusive + std::set setSubVer; // empty matches all + int nPriority; + + // Actions + std::string strComment; + std::string strStatusBar; + std::string strReserved; + + IMPLEMENT_SERIALIZE + ( + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(nRelayUntil); + READWRITE(nExpiration); + READWRITE(nID); + READWRITE(nCancel); + READWRITE(setCancel); + READWRITE(nMinVer); + READWRITE(nMaxVer); + READWRITE(setSubVer); + READWRITE(nPriority); + + READWRITE(strComment); + READWRITE(strStatusBar); + READWRITE(strReserved); + ) + + void SetNull(); + + std::string ToString() const; + void print() const; +}; + +/** An alert is a combination of a serialized CUnsignedAlert and a signature. */ +class CAlert : public CUnsignedAlert +{ +public: + std::vector vchMsg; + std::vector vchSig; + + CAlert() + { + SetNull(); + } + + IMPLEMENT_SERIALIZE + ( + READWRITE(vchMsg); + READWRITE(vchSig); + ) + + void SetNull(); + bool IsNull() const; + uint256 GetHash() const; + bool IsInEffect() const; + bool Cancels(const CAlert& alert) const; + bool AppliesTo(int nVersion, std::string strSubVerIn) const; + bool AppliesToMe() const; + bool RelayTo(CNode* pnode) const; + bool CheckSignature() const; + bool ProcessAlert(); + + /* + * Get copy of (active) alert object by hash. Returns a null alert if it is not found. + */ + static CAlert getAlertByHash(const uint256 &hash); +}; + +#endif diff --git a/src/allocators.h b/src/allocators.h index ddeabc4..99afa10 100644 --- a/src/allocators.h +++ b/src/allocators.h @@ -7,6 +7,8 @@ #include #include +#include +#include #ifdef WIN32 #ifdef _WIN32_WINNT @@ -22,23 +24,156 @@ // Note that VirtualLock does not provide this as a guarantee on Windows, // but, in practice, memory that has been VirtualLock'd almost never gets written to // the pagefile except in rare circumstances where memory is extremely low. -#define mlock(p, n) VirtualLock((p), (n)); -#define munlock(p, n) VirtualUnlock((p), (n)); #else #include -#include -/* This comes from limits.h if it's not defined there set a sane default */ -#ifndef PAGESIZE -#include -#define PAGESIZE sysconf(_SC_PAGESIZE) +#include // for PAGESIZE +#include // for sysconf #endif -#define mlock(a,b) \ - mlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\ - (((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1)))) -#define munlock(a,b) \ - munlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\ - (((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1)))) + +/** + * Thread-safe class to keep track of locked (ie, non-swappable) memory pages. + * + * Memory locks do not stack, that is, pages which have been locked several times by calls to mlock() + * will be unlocked by a single call to munlock(). This can result in keying material ending up in swap when + * those functions are used naively. This class simulates stacking memory locks by keeping a counter per page. + * + * @note By using a map from each page base address to lock count, this class is optimized for + * small objects that span up to a few pages, mostly smaller than a page. To support large allocations, + * something like an interval tree would be the preferred data structure. + */ +template class LockedPageManagerBase +{ +public: + LockedPageManagerBase(size_t page_size): + page_size(page_size) + { + // Determine bitmask for extracting page from address + assert(!(page_size & (page_size-1))); // size must be power of two + page_mask = ~(page_size - 1); + } + + // For all pages in affected range, increase lock count + void LockRange(void *p, size_t size) + { + boost::mutex::scoped_lock lock(mutex); + if(!size) return; + const size_t base_addr = reinterpret_cast(p); + const size_t start_page = base_addr & page_mask; + const size_t end_page = (base_addr + size - 1) & page_mask; + for(size_t page = start_page; page <= end_page; page += page_size) + { + Histogram::iterator it = histogram.find(page); + if(it == histogram.end()) // Newly locked page + { + locker.Lock(reinterpret_cast(page), page_size); + histogram.insert(std::make_pair(page, 1)); + } + else // Page was already locked; increase counter + { + it->second += 1; + } + } + } + + // For all pages in affected range, decrease lock count + void UnlockRange(void *p, size_t size) + { + boost::mutex::scoped_lock lock(mutex); + if(!size) return; + const size_t base_addr = reinterpret_cast(p); + const size_t start_page = base_addr & page_mask; + const size_t end_page = (base_addr + size - 1) & page_mask; + for(size_t page = start_page; page <= end_page; page += page_size) + { + Histogram::iterator it = histogram.find(page); + assert(it != histogram.end()); // Cannot unlock an area that was not locked + // Decrease counter for page, when it is zero, the page will be unlocked + it->second -= 1; + if(it->second == 0) // Nothing on the page anymore that keeps it locked + { + // Unlock page and remove the count from histogram + locker.Unlock(reinterpret_cast(page), page_size); + histogram.erase(it); + } + } + } + + // Get number of locked pages for diagnostics + int GetLockedPageCount() + { + boost::mutex::scoped_lock lock(mutex); + return histogram.size(); + } + +private: + Locker locker; + boost::mutex mutex; + size_t page_size, page_mask; + // map of page base address to lock count + typedef std::map Histogram; + Histogram histogram; +}; + +/** Determine system page size in bytes */ +static inline size_t GetSystemPageSize() +{ + size_t page_size; +#if defined(WIN32) + SYSTEM_INFO sSysInfo; + GetSystemInfo(&sSysInfo); + page_size = sSysInfo.dwPageSize; +#elif defined(PAGESIZE) // defined in limits.h + page_size = PAGESIZE; +#else // assume some POSIX OS + page_size = sysconf(_SC_PAGESIZE); +#endif + return page_size; +} + +/** + * OS-dependent memory page locking/unlocking. + * Defined as policy class to make stubbing for test possible. + */ +class MemoryPageLocker +{ +public: + /** Lock memory pages. + * addr and len must be a multiple of the system page size + */ + bool Lock(const void *addr, size_t len) + { +#ifdef WIN32 + return VirtualLock(const_cast(addr), len); +#else + return mlock(addr, len) == 0; +#endif + } + /** Unlock memory pages. + * addr and len must be a multiple of the system page size + */ + bool Unlock(const void *addr, size_t len) + { +#ifdef WIN32 + return VirtualUnlock(const_cast(addr), len); +#else + return munlock(addr, len) == 0; #endif + } +}; + +/** + * Singleton class to keep track of locked (ie, non-swappable) memory pages, for use in + * std::allocator templates. + */ +class LockedPageManager: public LockedPageManagerBase +{ +public: + static LockedPageManager instance; // instantiated in util.cpp +private: + LockedPageManager(): + LockedPageManagerBase(GetSystemPageSize()) + {} +}; // // Allocator that locks its contents from being paged @@ -69,7 +204,7 @@ struct secure_allocator : public std::allocator T *p; p = std::allocator::allocate(n, hint); if (p != NULL) - mlock(p, sizeof(T) * n); + LockedPageManager::instance.LockRange(p, sizeof(T) * n); return p; } @@ -78,7 +213,7 @@ struct secure_allocator : public std::allocator if (p != NULL) { memset(p, 0, sizeof(T) * n); - munlock(p, sizeof(T) * n); + LockedPageManager::instance.UnlockRange(p, sizeof(T) * n); } std::allocator::deallocate(p, n); } diff --git a/src/base58.h b/src/base58.h index 6dde681..e3dc380 100644 --- a/src/base58.h +++ b/src/base58.h @@ -1,7 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin Developers -// Copyright (c) 2011-2012 The PPCoin developers -// Copyright (c) 2013 NovaCoin Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -12,7 +10,7 @@ // could be used to create visually identical looking account numbers. // - A string with non-alphanumeric characters is not as easily accepted as an account number. // - E-mail usually won't line-break if there's no punctuation to break at. -// - Doubleclicking selects the whole number as one word if it's all alphanumeric. +// - Double-clicking selects the whole number as one word if it's all alphanumeric. // #ifndef BITCOIN_BASE58_H #define BITCOIN_BASE58_H @@ -21,6 +19,7 @@ #include #include "bignum.h" #include "key.h" +#include "script.h" static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; @@ -72,7 +71,7 @@ inline std::string EncodeBase58(const std::vector& vch) } // Decode a base58-encoded string psz into byte vector vchRet -// returns true if decoding is succesful +// returns true if decoding is successful inline bool DecodeBase58(const char* psz, std::vector& vchRet) { CAutoBN_CTX pctx; @@ -120,7 +119,7 @@ inline bool DecodeBase58(const char* psz, std::vector& vchRet) } // Decode a base58-encoded string str into byte vector vchRet -// returns true if decoding is succesful +// returns true if decoding is successful inline bool DecodeBase58(const std::string& str, std::vector& vchRet) { return DecodeBase58(str.c_str(), vchRet); @@ -140,7 +139,7 @@ inline std::string EncodeBase58Check(const std::vector& vchIn) } // Decode a base58-encoded string psz that includes a checksum, into byte vector vchRet -// returns true if decoding is succesful +// returns true if decoding is successful inline bool DecodeBase58Check(const char* psz, std::vector& vchRet) { if (!DecodeBase58(psz, vchRet)) @@ -161,7 +160,7 @@ inline bool DecodeBase58Check(const char* psz, std::vector& vchRe } // Decode a base58-encoded string str that includes a checksum, into byte vector vchRet -// returns true if decoding is succesful +// returns true if decoding is successful inline bool DecodeBase58Check(const std::string& str, std::vector& vchRet) { return DecodeBase58Check(str.c_str(), vchRet); @@ -254,12 +253,24 @@ public: bool operator> (const CBase58Data& b58) const { return CompareTo(b58) > 0; } }; -/** base58-encoded bitcoin addresses. - * Public-key-hash-addresses have version 55 (or 111 testnet). +/** base58-encoded Bitcoin addresses. + * Public-key-hash-addresses have version 0 (or 111 testnet). * The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. - * Script-hash-addresses have version 117 (or 196 testnet). + * Script-hash-addresses have version 5 (or 196 testnet). * The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script. */ +class CBitcoinAddress; +class CBitcoinAddressVisitor : public boost::static_visitor +{ +private: + CBitcoinAddress *addr; +public: + CBitcoinAddressVisitor(CBitcoinAddress *addrIn) : addr(addrIn) { } + bool operator()(const CKeyID &id) const; + bool operator()(const CScriptID &id) const; + bool operator()(const CNoDestination &no) const; +}; + class CBitcoinAddress : public CBase58Data { public: @@ -271,21 +282,19 @@ public: SCRIPT_ADDRESS_TEST = 196, }; - bool SetHash160(const uint160& hash160) - { - SetData(fTestNet ? PUBKEY_ADDRESS_TEST : PUBKEY_ADDRESS, &hash160, 20); + bool Set(const CKeyID &id) { + SetData(fTestNet ? PUBKEY_ADDRESS_TEST : PUBKEY_ADDRESS, &id, 20); return true; } - void SetPubKey(const std::vector& vchPubKey) - { - SetHash160(Hash160(vchPubKey)); + bool Set(const CScriptID &id) { + SetData(fTestNet ? SCRIPT_ADDRESS_TEST : SCRIPT_ADDRESS, &id, 20); + return true; } - bool SetScriptHash160(const uint160& hash160) + bool Set(const CTxDestination &dest) { - SetData(fTestNet ? SCRIPT_ADDRESS_TEST : SCRIPT_ADDRESS, &hash160, 20); - return true; + return boost::apply_visitor(CBitcoinAddressVisitor(this), dest); } bool IsValid() const @@ -317,27 +326,14 @@ public: } return fExpectTestNet == fTestNet && vchData.size() == nExpectedSize; } - bool IsScript() const - { - if (!IsValid()) - return false; - if (fTestNet) - return nVersion == SCRIPT_ADDRESS_TEST; - return nVersion == SCRIPT_ADDRESS; - } CBitcoinAddress() { } - CBitcoinAddress(uint160 hash160In) - { - SetHash160(hash160In); - } - - CBitcoinAddress(const std::vector& vchPubKey) + CBitcoinAddress(const CTxDestination &dest) { - SetPubKey(vchPubKey); + Set(dest); } CBitcoinAddress(const std::string& strAddress) @@ -350,21 +346,64 @@ public: SetString(pszAddress); } - uint160 GetHash160() const - { - assert(vchData.size() == 20); - uint160 hash160; - memcpy(&hash160, &vchData[0], 20); - return hash160; + CTxDestination Get() const { + if (!IsValid()) + return CNoDestination(); + switch (nVersion) { + case PUBKEY_ADDRESS: + case PUBKEY_ADDRESS_TEST: { + uint160 id; + memcpy(&id, &vchData[0], 20); + return CKeyID(id); + } + case SCRIPT_ADDRESS: + case SCRIPT_ADDRESS_TEST: { + uint160 id; + memcpy(&id, &vchData[0], 20); + return CScriptID(id); + } + } + return CNoDestination(); + } + + bool GetKeyID(CKeyID &keyID) const { + if (!IsValid()) + return false; + switch (nVersion) { + case PUBKEY_ADDRESS: + case PUBKEY_ADDRESS_TEST: { + uint160 id; + memcpy(&id, &vchData[0], 20); + keyID = CKeyID(id); + return true; + } + default: return false; + } + } + + bool IsScript() const { + if (!IsValid()) + return false; + switch (nVersion) { + case SCRIPT_ADDRESS: + case SCRIPT_ADDRESS_TEST: { + return true; + } + default: return false; + } } }; +bool inline CBitcoinAddressVisitor::operator()(const CKeyID &id) const { return addr->Set(id); } +bool inline CBitcoinAddressVisitor::operator()(const CScriptID &id) const { return addr->Set(id); } +bool inline CBitcoinAddressVisitor::operator()(const CNoDestination &id) const { return false; } + /** A base58-encoded secret key */ class CBitcoinSecret : public CBase58Data { public: void SetSecret(const CSecret& vchSecret, bool fCompressed) - { + { assert(vchSecret.size() == 32); SetData(128 + (fTestNet ? CBitcoinAddress::PUBKEY_ADDRESS_TEST : CBitcoinAddress::PUBKEY_ADDRESS), &vchSecret[0], vchSecret.size()); if (fCompressed) @@ -385,7 +424,7 @@ public: bool fExpectTestNet = false; switch(nVersion) { - case (128 + CBitcoinAddress::PUBKEY_ADDRESS): + case (128 + CBitcoinAddress::PUBKEY_ADDRESS): break; case (128 + CBitcoinAddress::PUBKEY_ADDRESS_TEST): diff --git a/src/bignum.h b/src/bignum.h index f09c4aa..c214275 100644 --- a/src/bignum.h +++ b/src/bignum.h @@ -122,16 +122,24 @@ public: return (n > (unsigned long)std::numeric_limits::max() ? std::numeric_limits::min() : -(int)n); } - void setint64(int64 n) + void setint64(int64 sn) { - unsigned char pch[sizeof(n) + 6]; + unsigned char pch[sizeof(sn) + 6]; unsigned char* p = pch + 4; - bool fNegative = false; - if (n < (int64)0) + bool fNegative; + uint64 n; + + if (sn < (int64)0) { - n = -n; + // Since the minimum signed integer cannot be represented as positive so long as its type is signed, and it's not well-defined what happens if you make it unsigned before negating it, we instead increment the negative integer by 1, convert it, then increment the (now positive) unsigned integer by 1 to compensate + n = -(sn + 1); + ++n; fNegative = true; + } else { + n = sn; + fNegative = false; } + bool fLeadingZeroes = true; for (int i = 0; i < 8; i++) { @@ -157,6 +165,21 @@ public: BN_mpi2bn(pch, p - pch, this); } + uint64 getuint64() + { + unsigned int nSize = BN_bn2mpi(this, NULL); + if (nSize < 4) + return 0; + std::vector vch(nSize); + BN_bn2mpi(this, &vch[0]); + if (vch.size() > 4) + vch[4] &= 0x7f; + uint64 n = 0; + for (unsigned int i = 0, j = vch.size()-1; i < sizeof(n) && j >= 4; i++, j--) + ((unsigned char*)&n)[i] = vch[j]; + return n; + } + void setuint64(uint64 n) { unsigned char pch[sizeof(n) + 6]; @@ -184,21 +207,6 @@ public: BN_mpi2bn(pch, p - pch, this); } - uint64 getuint64() - { - unsigned int nSize = BN_bn2mpi(this, NULL); - if (nSize < 4) - return 0; - std::vector vch(nSize); - BN_bn2mpi(this, &vch[0]); - if (vch.size() > 4) - vch[4] &= 0x7f; - uint64 n = 0; - for (int i = 0, j = vch.size()-1; i < sizeof(n) && j >= 4; i++, j--) - ((unsigned char*)&n)[i] = vch[j]; - return n; - } - void setuint256(uint256 n) { unsigned char pch[sizeof(n) + 6]; @@ -242,6 +250,7 @@ public: return n; } + void setvch(const std::vector& vch) { std::vector vch2(vch.size() + 4); @@ -312,7 +321,7 @@ public: psz++; // hex string to bignum - static signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 }; + static const signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 }; *this = 0; while (isxdigit(*psz)) { @@ -423,7 +432,7 @@ public: CBigNum& operator>>=(unsigned int shift) { // Note: BN_rshift segfaults on 64-bit if 2^shift is greater than the number - // if built on ubuntu 9.04 or 9.10, probably depends on version of openssl + // if built on ubuntu 9.04 or 9.10, probably depends on version of OpenSSL CBigNum a = 1; a <<= shift; if (BN_cmp(&a, this) > 0) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 3020691..091491c 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1,36 +1,32 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers -// Copyright (c) 2013 NovaCoin Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "main.h" -#include "wallet.h" -#include "db.h" -#include "walletdb.h" -#include "net.h" #include "init.h" -#include "checkpoints.h" +#include "util.h" +#include "sync.h" #include "ui_interface.h" +#include "base58.h" #include "bitcoinrpc.h" +#include "db.h" #undef printf #include +#include +#include #include +#include #include #include #include #include -#include +#include #include -typedef boost::asio::ssl::stream SSLStream; +#include +#include #define printf OutputDebugStringF -// MinGW 3.4.5 gets "fatal error: had to relocate PCH" if the json headers are -// precompiled in headers.h. The problem might be when the pch file goes over -// a certain size around 145MB. If we need access to json_spirit outside this -// file, we could use the compiled json_spirit option. using namespace std; using namespace boost; @@ -41,11 +37,14 @@ void ThreadRPCServer2(void* parg); static std::string strRPCUserColonPass; -static int64 nWalletUnlockTime; -static CCriticalSection cs_nWalletUnlockTime; +const Object emptyobj; + +void ThreadRPCServer3(void* parg); -extern Value dumpprivkey(const Array& params, bool fHelp); -extern Value importprivkey(const Array& params, bool fHelp); +static inline unsigned short GetDefaultRPCPort() +{ + return GetBoolArg("-testnet", false) ? 18344 : 8344; +} Object JSONRPCError(int code, const string& message) { @@ -55,46 +54,54 @@ Object JSONRPCError(int code, const string& message) return error; } -double GetDifficulty(const CBlockIndex* blockindex = NULL) +void RPCTypeCheck(const Array& params, + const list& typesExpected, + bool fAllowNull) { - // Floating point number that is a multiple of the minimum difficulty, - // minimum difficulty = 1.0. - if (blockindex == NULL) + unsigned int i = 0; + BOOST_FOREACH(Value_type t, typesExpected) { - if (pindexBest == NULL) - return 1.0; - else - blockindex = GetLastBlockIndex(pindexBest, false); - } - - int nShift = (blockindex->nBits >> 24) & 0xff; - - double dDiff = - (double)0x0000ffff / (double)(blockindex->nBits & 0x00ffffff); + if (params.size() <= i) + break; - while (nShift < 29) - { - dDiff *= 256.0; - nShift++; + const Value& v = params[i]; + if (!((v.type() == t) || (fAllowNull && (v.type() == null_type)))) + { + string err = strprintf("Expected type %s, got %s", + Value_type_name[t], Value_type_name[v.type()]); + throw JSONRPCError(RPC_TYPE_ERROR, err); + } + i++; } - while (nShift > 29) +} + +void RPCTypeCheck(const Object& o, + const map& typesExpected, + bool fAllowNull) +{ + BOOST_FOREACH(const PAIRTYPE(string, Value_type)& t, typesExpected) { - dDiff /= 256.0; - nShift--; - } + const Value& v = find_value(o, t.first); + if (!fAllowNull && v.type() == null_type) + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first.c_str())); - return dDiff; + if (!((v.type() == t.second) || (fAllowNull && (v.type() == null_type)))) + { + string err = strprintf("Expected type %s for %s, got %s", + Value_type_name[t.second], t.first.c_str(), Value_type_name[v.type()]); + throw JSONRPCError(RPC_TYPE_ERROR, err); + } + } } - int64 AmountFromValue(const Value& value) { double dAmount = value.get_real(); if (dAmount <= 0.0 || dAmount > MAX_MONEY) - throw JSONRPCError(-3, "Invalid amount"); + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); int64 nAmount = roundint64(dAmount * COIN); if (!MoneyRange(nAmount)) - throw JSONRPCError(-3, "Invalid amount"); + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); return nAmount; } @@ -103,8 +110,7 @@ Value ValueFromAmount(int64 amount) return (double)amount / (double)COIN; } -std::string -HexBits(unsigned int nBits) +std::string HexBits(unsigned int nBits) { union { int32_t nBits; @@ -114,121 +120,6 @@ HexBits(unsigned int nBits) return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); } -unsigned int BitsHex(std::string HexBits) -{ - union { - int32_t nBits; - char cBits[4]; - } uBits; - - vector vchBits = ParseHex(HexBits); - copy(vchBits.begin(), vchBits.begin() + 4, uBits.cBits); - uBits.nBits = htonl((int32_t)uBits.nBits); - return uBits.nBits; -} - -void TxToJSON(const CTransaction &tx, Object& entry) -{ - entry.push_back(Pair("version", tx.nVersion)); - entry.push_back(Pair("locktime", (boost::int64_t)tx.nLockTime)); - entry.push_back(Pair("size", (boost::int64_t)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); - Array vin; - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - Object in; - if (tx.IsCoinBase()) - in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); - else - { - Object prevout; - prevout.push_back(Pair("hash", txin.prevout.hash.GetHex())); - prevout.push_back(Pair("n", (boost::int64_t)txin.prevout.n)); - in.push_back(Pair("prevout", prevout)); - in.push_back(Pair("scriptSig", txin.scriptSig.ToString())); - } - in.push_back(Pair("sequence", (boost::int64_t)txin.nSequence)); - vin.push_back(in); - } - entry.push_back(Pair("vin", vin)); - Array vout; - BOOST_FOREACH(const CTxOut& txout, tx.vout) - { - Object out; - out.push_back(Pair("value", ValueFromAmount(txout.nValue))); - out.push_back(Pair("scriptPubKey", txout.scriptPubKey.ToString())); - vout.push_back(out); - } - entry.push_back(Pair("vout", vout)); -} - -void WalletTxToJSON(const CWalletTx& wtx, Object& entry) -{ - int confirms = wtx.GetDepthInMainChain(); - entry.push_back(Pair("confirmations", confirms)); - if (confirms) - { - entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex())); - entry.push_back(Pair("blockindex", wtx.nIndex)); - } - entry.push_back(Pair("txid", wtx.GetHash().GetHex())); - entry.push_back(Pair("time", (boost::int64_t)wtx.GetTxTime())); - BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) - entry.push_back(Pair(item.first, item.second)); -} - -string AccountFromValue(const Value& value) -{ - string strAccount = value.get_str(); - if (strAccount == "*") - throw JSONRPCError(-11, "Invalid account name"); - return strAccount; -} - -Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fPrintTransactionDetail) -{ - Object result; - result.push_back(Pair("hash", block.GetHash().GetHex())); - result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); - result.push_back(Pair("height", blockindex->nHeight)); - result.push_back(Pair("version", block.nVersion)); - result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex())); - result.push_back(Pair("time", DateTimeStrFormat(block.GetBlockTime()))); - result.push_back(Pair("nonce", (boost::uint64_t)block.nNonce)); - result.push_back(Pair("bits", HexBits(block.nBits))); - result.push_back(Pair("difficulty", GetDifficulty(blockindex))); - result.push_back(Pair("mint", ValueFromAmount(blockindex->nMint))); - if (blockindex->pprev) - result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); - if (blockindex->pnext) - result.push_back(Pair("nextblockhash", blockindex->pnext->GetBlockHash().GetHex())); - result.push_back(Pair("flags", strprintf("%s%s", blockindex->IsProofOfStake()? "proof-of-stake" : "proof-of-work", blockindex->GeneratedStakeModifier()? " stake-modifier": ""))); - result.push_back(Pair("proofhash", blockindex->IsProofOfStake()? blockindex->hashProofOfStake.GetHex() : blockindex->GetBlockHash().GetHex())); - result.push_back(Pair("entropybit", (int)blockindex->GetStakeEntropyBit())); - result.push_back(Pair("modifier", strprintf("%016"PRI64x, blockindex->nStakeModifier))); - result.push_back(Pair("modifierchecksum", strprintf("%08x", blockindex->nStakeModifierChecksum))); - Array txinfo; - BOOST_FOREACH (const CTransaction& tx, block.vtx) - { - if (fPrintTransactionDetail) - { - Object entry; - - entry.push_back(Pair("txid", tx.GetHash().GetHex())); - TxToJSON(tx, entry); - entry.push_back(Pair("time", DateTimeStrFormat(tx.nTime))); - - txinfo.push_back(entry); - } - else - txinfo.push_back(tx.GetHash().GetHex()); - } - - result.push_back(Pair("tx", txinfo)); - result.push_back(Pair("signature", HexStr(block.vchBlockSig.begin(), block.vchBlockSig.end()))); - - return result; -} - /// @@ -244,10 +135,7 @@ string CRPCTable::help(string strCommand) const const CRPCCommand *pcmd = mi->second; string strMethod = mi->first; // We already filter duplicates, but these deprecated screw up the sort order - if (strMethod == "getamountreceived" || - strMethod == "getallreceived" || - strMethod == "getblocknumber" || // deprecated - (strMethod.find("label") != string::npos)) + if (strMethod.find("label") != string::npos) continue; if (strCommand != "" && strMethod != strCommand) continue; @@ -291,2496 +179,16 @@ Value help(const Array& params, bool fHelp) Value stop(const Array& params, bool fHelp) { - if (fHelp || params.size() != 0) - throw runtime_error( - "stop\n" - "Stop novacoin server."); - // Shutdown will take long enough that the response should get back - StartShutdown(); - return "novacoin server stopping"; -} - - -Value getblockcount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getblockcount\n" - "Returns the number of blocks in the longest block chain."); - - return nBestHeight; -} - - -// deprecated -Value getblocknumber(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getblocknumber\n" - "Deprecated. Use getblockcount."); - - return nBestHeight; -} - - -Value getconnectioncount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getconnectioncount\n" - "Returns the number of connections to other nodes."); - - return (int)vNodes.size(); -} - - -Value getdifficulty(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getdifficulty\n" - "Returns difficulty as a multiple of the minimum difficulty."); - - Object obj; - obj.push_back(Pair("proof-of-work", GetDifficulty())); - obj.push_back(Pair("proof-of-stake", GetDifficulty(GetLastBlockIndex(pindexBest, true)))); - obj.push_back(Pair("search-interval", (int)nLastCoinStakeSearchInterval)); - return obj; -} - - -Value getgenerate(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getgenerate\n" - "Returns true or false."); - - return GetBoolArg("-gen"); -} - - -Value setgenerate(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "setgenerate [genproclimit]\n" - " is true or false to turn generation on or off.\n" - "Generation is limited to [genproclimit] processors, -1 is unlimited."); - - bool fGenerate = true; - if (params.size() > 0) - fGenerate = params[0].get_bool(); - - if (params.size() > 1) - { - int nGenProcLimit = params[1].get_int(); - mapArgs["-genproclimit"] = itostr(nGenProcLimit); - if (nGenProcLimit == 0) - fGenerate = false; - } - mapArgs["-gen"] = (fGenerate ? "1" : "0"); - - GenerateBitcoins(fGenerate, pwalletMain); - return Value::null; -} - - -Value gethashespersec(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "gethashespersec\n" - "Returns a recent hashes per second performance measurement while generating."); - - if (GetTimeMillis() - nHPSTimerStart > 8000) - return (boost::int64_t)0; - return (boost::int64_t)dHashesPerSec; -} - - -Value getinfo(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getinfo\n" - "Returns an object containing various state info."); - - Object obj; - obj.push_back(Pair("version", FormatFullVersion())); - obj.push_back(Pair("protocolversion",(int)PROTOCOL_VERSION)); - obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); - obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); - obj.push_back(Pair("newmint", ValueFromAmount(pwalletMain->GetNewMint()))); - obj.push_back(Pair("stake", ValueFromAmount(pwalletMain->GetStake()))); - obj.push_back(Pair("blocks", (int)nBestHeight)); - obj.push_back(Pair("moneysupply", ValueFromAmount(pindexBest->nMoneySupply))); - obj.push_back(Pair("connections", (int)vNodes.size())); - obj.push_back(Pair("proxy", (fUseProxy ? addrProxy.ToStringIPPort() : string()))); - obj.push_back(Pair("ip", addrSeenByPeer.ToStringIP())); - obj.push_back(Pair("difficulty", (double)GetDifficulty())); - obj.push_back(Pair("testnet", fTestNet)); - obj.push_back(Pair("keypoololdest", (boost::int64_t)pwalletMain->GetOldestKeyPoolTime())); - obj.push_back(Pair("keypoolsize", pwalletMain->GetKeyPoolSize())); - obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee))); - if (pwalletMain->IsCrypted()) - obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime / 1000)); - obj.push_back(Pair("errors", GetWarnings("statusbar"))); - return obj; -} - - -Value getmininginfo(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getmininginfo\n" - "Returns an object containing mining-related information."); - - Object obj; - obj.push_back(Pair("blocks", (int)nBestHeight)); - obj.push_back(Pair("currentblocksize",(uint64_t)nLastBlockSize)); - obj.push_back(Pair("currentblocktx",(uint64_t)nLastBlockTx)); - obj.push_back(Pair("difficulty", (double)GetDifficulty())); - obj.push_back(Pair("errors", GetWarnings("statusbar"))); - obj.push_back(Pair("generate", GetBoolArg("-gen"))); - obj.push_back(Pair("genproclimit", (int)GetArg("-genproclimit", -1))); - obj.push_back(Pair("hashespersec", gethashespersec(params, false))); - obj.push_back(Pair("pooledtx", (uint64_t)mempool.size())); - obj.push_back(Pair("testnet", fTestNet)); - return obj; -} - - -Value getnewaddress(const Array& params, bool fHelp) -{ if (fHelp || params.size() > 1) throw runtime_error( - "getnewaddress [account]\n" - "Returns a new novacoin address for receiving payments. " - "If [account] is specified (recommended), it is added to the address book " - "so payments received with the address will be credited to [account]."); - - // Parse the account first so we don't generate a key if there's an error - string strAccount; - if (params.size() > 0) - strAccount = AccountFromValue(params[0]); - - if (!pwalletMain->IsLocked()) - pwalletMain->TopUpKeyPool(); - - // Generate a new key that is added to wallet - std::vector newKey; - if (!pwalletMain->GetKeyFromPool(newKey, false)) - throw JSONRPCError(-12, "Error: Keypool ran out, please call keypoolrefill first"); - CBitcoinAddress address(newKey); - - pwalletMain->SetAddressBookName(address, strAccount); - - return address.ToString(); -} - - -CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) -{ - CWalletDB walletdb(pwalletMain->strWalletFile); - - CAccount account; - walletdb.ReadAccount(strAccount, account); - - bool bKeyUsed = false; - - // Check if the current key has been used - if (!account.vchPubKey.empty()) - { - CScript scriptPubKey; - scriptPubKey.SetBitcoinAddress(account.vchPubKey); - for (map::iterator it = pwalletMain->mapWallet.begin(); - it != pwalletMain->mapWallet.end() && !account.vchPubKey.empty(); - ++it) - { - const CWalletTx& wtx = (*it).second; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - if (txout.scriptPubKey == scriptPubKey) - bKeyUsed = true; - } - } - - // Generate a new key - if (account.vchPubKey.empty() || bForceNew || bKeyUsed) - { - if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false)) - throw JSONRPCError(-12, "Error: Keypool ran out, please call keypoolrefill first"); - - pwalletMain->SetAddressBookName(CBitcoinAddress(account.vchPubKey), strAccount); - walletdb.WriteAccount(strAccount, account); - } - - return CBitcoinAddress(account.vchPubKey); -} - -Value getaccountaddress(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 1) - throw runtime_error( - "getaccountaddress \n" - "Returns the current novacoin address for receiving payments to this account."); - - // Parse the account first so we don't generate a key if there's an error - string strAccount = AccountFromValue(params[0]); - - Value ret; - - ret = GetAccountAddress(strAccount).ToString(); - - return ret; -} - - - -Value setaccount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "setaccount \n" - "Sets the account associated with the given address."); - - CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid novacoin address"); - - - string strAccount; - if (params.size() > 1) - strAccount = AccountFromValue(params[1]); - - // Detect when changing the account of an address that is the 'unused current key' of another account: - if (pwalletMain->mapAddressBook.count(address)) - { - string strOldAccount = pwalletMain->mapAddressBook[address]; - if (address == GetAccountAddress(strOldAccount)) - GetAccountAddress(strOldAccount, true); - } - - pwalletMain->SetAddressBookName(address, strAccount); - - return Value::null; -} - - -Value getaccount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 1) - throw runtime_error( - "getaccount \n" - "Returns the account associated with the given address."); - - CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid novacoin address"); - - string strAccount; - map::iterator mi = pwalletMain->mapAddressBook.find(address); - if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.empty()) - strAccount = (*mi).second; - return strAccount; -} - - -Value getaddressesbyaccount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 1) - throw runtime_error( - "getaddressesbyaccount \n" - "Returns the list of addresses for the given account."); - - string strAccount = AccountFromValue(params[0]); - - // Find all addresses that have the given account - Array ret; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) - { - const CBitcoinAddress& address = item.first; - const string& strName = item.second; - if (strName == strAccount) - ret.push_back(address.ToString()); - } - return ret; -} - -Value settxfee(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 1 || AmountFromValue(params[0]) < MIN_TX_FEE) - throw runtime_error( - "settxfee \n" - " is a real and is rounded to 0.01 (cent)\n" - "Minimum and default transaction fee per KB is 1 cent"); - - nTransactionFee = AmountFromValue(params[0]); - nTransactionFee = (nTransactionFee / CENT) * CENT; // round to cent - return true; -} - -Value sendtoaddress(const Array& params, bool fHelp) -{ - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendtoaddress [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.000001\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendtoaddress [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.000001"); - - CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid novacoin address"); - - // Amount - int64 nAmount = AmountFromValue(params[1]); - if (nAmount < MIN_TXOUT_AMOUNT) - throw JSONRPCError(-101, "Send amount too small"); - - // Wallet comments - CWalletTx wtx; - if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty()) - wtx.mapValue["comment"] = params[2].get_str(); - if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) - wtx.mapValue["to"] = params[3].get_str(); - - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); - - string strError = pwalletMain->SendMoneyToBitcoinAddress(address, nAmount, wtx); - if (strError != "") - throw JSONRPCError(-4, strError); - - return wtx.GetHash().GetHex(); -} - -Value signmessage(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 2) - throw runtime_error( - "signmessage \n" - "Sign a message with the private key of an address"); - - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); - - string strAddress = params[0].get_str(); - string strMessage = params[1].get_str(); - - CBitcoinAddress addr(strAddress); - if (!addr.IsValid()) - throw JSONRPCError(-3, "Invalid address"); - - CKey key; - if (!pwalletMain->GetKey(addr, key)) - throw JSONRPCError(-4, "Private key not available"); - - CDataStream ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << strMessage; - - vector vchSig; - if (!key.SignCompact(Hash(ss.begin(), ss.end()), vchSig)) - throw JSONRPCError(-5, "Sign failed"); - - return EncodeBase64(&vchSig[0], vchSig.size()); -} - -Value verifymessage(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 3) - throw runtime_error( - "verifymessage \n" - "Verify a signed message"); - - string strAddress = params[0].get_str(); - string strSign = params[1].get_str(); - string strMessage = params[2].get_str(); - - CBitcoinAddress addr(strAddress); - if (!addr.IsValid()) - throw JSONRPCError(-3, "Invalid address"); - - bool fInvalid = false; - vector vchSig = DecodeBase64(strSign.c_str(), &fInvalid); - - if (fInvalid) - throw JSONRPCError(-5, "Malformed base64 encoding"); - - CDataStream ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << strMessage; - - CKey key; - if (!key.SetCompactSignature(Hash(ss.begin(), ss.end()), vchSig)) - return false; - - return (CBitcoinAddress(key.GetPubKey()) == addr); -} - - -Value getreceivedbyaddress(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "getreceivedbyaddress [minconf=1]\n" - "Returns the total amount received by in transactions with at least [minconf] confirmations."); - - // Bitcoin address - CBitcoinAddress address = CBitcoinAddress(params[0].get_str()); - CScript scriptPubKey; - if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid novacoin address"); - scriptPubKey.SetBitcoinAddress(address); - if (!IsMine(*pwalletMain,scriptPubKey)) - return (double)0.0; - - // Minimum confirmations - int nMinDepth = 1; - if (params.size() > 1) - nMinDepth = params[1].get_int(); - - // Tally - int64 nAmount = 0; - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; - if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) - continue; - - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - if (txout.scriptPubKey == scriptPubKey) - if (wtx.GetDepthInMainChain() >= nMinDepth) - nAmount += txout.nValue; - } - - return ValueFromAmount(nAmount); -} - - -void GetAccountAddresses(string strAccount, set& setAddress) -{ - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) - { - const CBitcoinAddress& address = item.first; - const string& strName = item.second; - if (strName == strAccount) - setAddress.insert(address); - } -} - - -Value getreceivedbyaccount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "getreceivedbyaccount [minconf=1]\n" - "Returns the total amount received by addresses with in transactions with at least [minconf] confirmations."); - - // Minimum confirmations - int nMinDepth = 1; - if (params.size() > 1) - nMinDepth = params[1].get_int(); - - // Get the set of pub keys assigned to account - string strAccount = AccountFromValue(params[0]); - set setAddress; - GetAccountAddresses(strAccount, setAddress); - - // Tally - int64 nAmount = 0; - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; - if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) - continue; - - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - { - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, address) && pwalletMain->HaveKey(address) && setAddress.count(address)) - if (wtx.GetDepthInMainChain() >= nMinDepth) - nAmount += txout.nValue; - } - } - - return (double)nAmount / (double)COIN; -} - - -int64 GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMinDepth) -{ - int64 nBalance = 0; - - // Tally wallet transactions - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; - if (!wtx.IsFinal()) - continue; - - int64 nGenerated, nReceived, nSent, nFee; - wtx.GetAccountAmounts(strAccount, nGenerated, nReceived, nSent, nFee); - - if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth) - nBalance += nReceived; - nBalance += nGenerated - nSent - nFee; - } - - // Tally internal accounting entries - nBalance += walletdb.GetAccountCreditDebit(strAccount); - - return nBalance; -} - -int64 GetAccountBalance(const string& strAccount, int nMinDepth) -{ - CWalletDB walletdb(pwalletMain->strWalletFile); - return GetAccountBalance(walletdb, strAccount, nMinDepth); -} - - -Value getbalance(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 2) - throw runtime_error( - "getbalance [account] [minconf=1]\n" - "If [account] is not specified, returns the server's total available balance.\n" - "If [account] is specified, returns the balance in the account."); - - if (params.size() == 0) - return ValueFromAmount(pwalletMain->GetBalance()); - - int nMinDepth = 1; - if (params.size() > 1) - nMinDepth = params[1].get_int(); - - if (params[0].get_str() == "*") { - // Calculate total balance a different way from GetBalance() - // (GetBalance() sums up all unspent TxOuts) - // getbalance and getbalance '*' should always return the same number. - int64 nBalance = 0; - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; - if (!wtx.IsFinal()) - continue; - - int64 allGeneratedImmature, allGeneratedMature, allFee; - allGeneratedImmature = allGeneratedMature = allFee = 0; - string strSentAccount; - list > listReceived; - list > listSent; - wtx.GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount); - if (wtx.GetDepthInMainChain() >= nMinDepth) - { - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listReceived) - nBalance += r.second; - } - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listSent) - nBalance -= r.second; - nBalance -= allFee; - nBalance += allGeneratedMature; - } - return ValueFromAmount(nBalance); - } - - string strAccount = AccountFromValue(params[0]); - - int64 nBalance = GetAccountBalance(strAccount, nMinDepth); - - return ValueFromAmount(nBalance); -} - - -Value getpowreward(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "getpowreward [nBits]\n" - "Returns PoW reward for block with provided difficulty."); - - if (params.size() == 0) - throw JSONRPCError(-200, "no bits provided"); - - std::string sBits = params[0].get_str(); - - if (sBits.length() != 8) - throw JSONRPCError(-201, "incorrect bits provided"); - - unsigned int nBits = BitsHex(sBits); - - return (int)GetProofOfWorkReward(nBits); -} - - - -Value movecmd(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 3 || params.size() > 5) - throw runtime_error( - "move [minconf=1] [comment]\n" - "Move from one account in your wallet to another."); - - string strFrom = AccountFromValue(params[0]); - string strTo = AccountFromValue(params[1]); - int64 nAmount = AmountFromValue(params[2]); - if (params.size() > 3) - // unused parameter, used to be nMinDepth, keep type-checking it though - (void)params[3].get_int(); - string strComment; - if (params.size() > 4) - strComment = params[4].get_str(); - - CWalletDB walletdb(pwalletMain->strWalletFile); - if (!walletdb.TxnBegin()) - throw JSONRPCError(-20, "database error"); - - int64 nNow = GetAdjustedTime(); - - // Debit - CAccountingEntry debit; - debit.strAccount = strFrom; - debit.nCreditDebit = -nAmount; - debit.nTime = nNow; - debit.strOtherAccount = strTo; - debit.strComment = strComment; - walletdb.WriteAccountingEntry(debit); - - // Credit - CAccountingEntry credit; - credit.strAccount = strTo; - credit.nCreditDebit = nAmount; - credit.nTime = nNow; - credit.strOtherAccount = strFrom; - credit.strComment = strComment; - walletdb.WriteAccountingEntry(credit); - - if (!walletdb.TxnCommit()) - throw JSONRPCError(-20, "database error"); - - return true; -} - - -Value sendfrom(const Array& params, bool fHelp) -{ - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) - throw runtime_error( - "sendfrom [minconf=1] [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.000001\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) - throw runtime_error( - "sendfrom [minconf=1] [comment] [comment-to]\n" - " is a real and is rounded to the nearest 0.000001"); - - string strAccount = AccountFromValue(params[0]); - CBitcoinAddress address(params[1].get_str()); - if (!address.IsValid()) - throw JSONRPCError(-5, "Invalid novacoin address"); - int64 nAmount = AmountFromValue(params[2]); - if (nAmount < MIN_TXOUT_AMOUNT) - throw JSONRPCError(-101, "Send amount too small"); - int nMinDepth = 1; - if (params.size() > 3) - nMinDepth = params[3].get_int(); - - CWalletTx wtx; - wtx.strFromAccount = strAccount; - if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty()) - wtx.mapValue["comment"] = params[4].get_str(); - if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty()) - wtx.mapValue["to"] = params[5].get_str(); - - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); - - // Check funds - int64 nBalance = GetAccountBalance(strAccount, nMinDepth); - if (nAmount > nBalance) - throw JSONRPCError(-6, "Account has insufficient funds"); - - // Send - string strError = pwalletMain->SendMoneyToBitcoinAddress(address, nAmount, wtx); - if (strError != "") - throw JSONRPCError(-4, strError); - - return wtx.GetHash().GetHex(); -} - - -Value sendmany(const Array& params, bool fHelp) -{ - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendmany {address:amount,...} [minconf=1] [comment]\n" - "amounts are double-precision floating point numbers\n" - "requires wallet passphrase to be set with walletpassphrase first"); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) - throw runtime_error( - "sendmany {address:amount,...} [minconf=1] [comment]\n" - "amounts are double-precision floating point numbers"); - - string strAccount = AccountFromValue(params[0]); - Object sendTo = params[1].get_obj(); - int nMinDepth = 1; - if (params.size() > 2) - nMinDepth = params[2].get_int(); - - CWalletTx wtx; - wtx.strFromAccount = strAccount; - if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) - wtx.mapValue["comment"] = params[3].get_str(); - - set setAddress; - vector > vecSend; - - int64 totalAmount = 0; - BOOST_FOREACH(const Pair& s, sendTo) - { - CBitcoinAddress address(s.name_); - if (!address.IsValid()) - throw JSONRPCError(-5, string("Invalid novacoin address:")+s.name_); - - if (setAddress.count(address)) - throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+s.name_); - setAddress.insert(address); - - CScript scriptPubKey; - scriptPubKey.SetBitcoinAddress(address); - int64 nAmount = AmountFromValue(s.value_); - if (nAmount < MIN_TXOUT_AMOUNT) - throw JSONRPCError(-101, "Send amount too small"); - totalAmount += nAmount; - - vecSend.push_back(make_pair(scriptPubKey, nAmount)); - } - - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); - if (fWalletUnlockMintOnly) - throw JSONRPCError(-13, "Error: Wallet unlocked for block minting only."); - - // Check funds - int64 nBalance = GetAccountBalance(strAccount, nMinDepth); - if (totalAmount > nBalance) - throw JSONRPCError(-6, "Account has insufficient funds"); - - // Send - CReserveKey keyChange(pwalletMain); - int64 nFeeRequired = 0; - bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired); - if (!fCreated) - { - if (totalAmount + nFeeRequired > pwalletMain->GetBalance()) - throw JSONRPCError(-6, "Insufficient funds"); - throw JSONRPCError(-4, "Transaction creation failed"); - } - if (!pwalletMain->CommitTransaction(wtx, keyChange)) - throw JSONRPCError(-4, "Transaction commit failed"); - - return wtx.GetHash().GetHex(); -} - -Value addmultisigaddress(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 2 || params.size() > 3) - { - string msg = "addmultisigaddress <'[\"key\",\"key\"]'> [account]\n" - "Add a nrequired-to-sign multisignature address to the wallet\"\n" - "each key is a bitcoin address or hex-encoded public key\n" - "If [account] is specified, assign address to [account]."; - throw runtime_error(msg); - } - - int nRequired = params[0].get_int(); - const Array& keys = params[1].get_array(); - string strAccount; - if (params.size() > 2) - strAccount = AccountFromValue(params[2]); - - // Gather public keys - if (nRequired < 1) - throw runtime_error("a multisignature address must require at least one key to redeem"); - if ((int)keys.size() < nRequired) - throw runtime_error( - strprintf("not enough keys supplied " - "(got %d keys, but need at least %d to redeem)", keys.size(), nRequired)); - std::vector pubkeys; - pubkeys.resize(keys.size()); - for (unsigned int i = 0; i < keys.size(); i++) - { - const std::string& ks = keys[i].get_str(); - - // Case 1: bitcoin address and we have full public key: - CBitcoinAddress address(ks); - if (address.IsValid()) - { - if (address.IsScript()) - throw runtime_error( - strprintf("%s is a pay-to-script address",ks.c_str())); - std::vector vchPubKey; - if (!pwalletMain->GetPubKey(address, vchPubKey)) - throw runtime_error( - strprintf("no full public key for address %s",ks.c_str())); - if (vchPubKey.empty() || !pubkeys[i].SetPubKey(vchPubKey)) - throw runtime_error(" Invalid public key: "+ks); - } - - // Case 2: hex public key - else if (IsHex(ks)) - { - vector vchPubKey = ParseHex(ks); - if (vchPubKey.empty() || !pubkeys[i].SetPubKey(vchPubKey)) - throw runtime_error(" Invalid public key: "+ks); - } - else - { - throw runtime_error(" Invalid public key: "+ks); - } - } - - // Construct using pay-to-script-hash: - CScript inner; - inner.SetMultisig(nRequired, pubkeys); - - uint160 scriptHash = Hash160(inner); - CScript scriptPubKey; - scriptPubKey.SetPayToScriptHash(inner); - pwalletMain->AddCScript(inner); - CBitcoinAddress address; - address.SetScriptHash160(scriptHash); - - pwalletMain->SetAddressBookName(address, strAccount); - return address.ToString(); -} - - -struct tallyitem -{ - int64 nAmount; - int nConf; - tallyitem() - { - nAmount = 0; - nConf = std::numeric_limits::max(); - } -}; - -Value ListReceived(const Array& params, bool fByAccounts) -{ - // Minimum confirmations - int nMinDepth = 1; - if (params.size() > 0) - nMinDepth = params[0].get_int(); - - // Whether to include empty accounts - bool fIncludeEmpty = false; - if (params.size() > 1) - fIncludeEmpty = params[1].get_bool(); - - // Tally - map mapTally; - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; - - if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) - continue; - - int nDepth = wtx.GetDepthInMainChain(); - if (nDepth < nMinDepth) - continue; - - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - { - CBitcoinAddress address; - if (!ExtractAddress(txout.scriptPubKey, address) || !pwalletMain->HaveKey(address) || !address.IsValid()) - continue; - - tallyitem& item = mapTally[address]; - item.nAmount += txout.nValue; - item.nConf = min(item.nConf, nDepth); - } - } - - // Reply - Array ret; - map mapAccountTally; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) - { - const CBitcoinAddress& address = item.first; - const string& strAccount = item.second; - map::iterator it = mapTally.find(address); - if (it == mapTally.end() && !fIncludeEmpty) - continue; - - int64 nAmount = 0; - int nConf = std::numeric_limits::max(); - if (it != mapTally.end()) - { - nAmount = (*it).second.nAmount; - nConf = (*it).second.nConf; - } - - if (fByAccounts) - { - tallyitem& item = mapAccountTally[strAccount]; - item.nAmount += nAmount; - item.nConf = min(item.nConf, nConf); - } - else - { - Object obj; - obj.push_back(Pair("address", address.ToString())); - obj.push_back(Pair("account", strAccount)); - obj.push_back(Pair("amount", ValueFromAmount(nAmount))); - obj.push_back(Pair("confirmations", (nConf == std::numeric_limits::max() ? 0 : nConf))); - ret.push_back(obj); - } - } - - if (fByAccounts) - { - for (map::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it) - { - int64 nAmount = (*it).second.nAmount; - int nConf = (*it).second.nConf; - Object obj; - obj.push_back(Pair("account", (*it).first)); - obj.push_back(Pair("amount", ValueFromAmount(nAmount))); - obj.push_back(Pair("confirmations", (nConf == std::numeric_limits::max() ? 0 : nConf))); - ret.push_back(obj); - } - } - - return ret; -} - -Value listreceivedbyaddress(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 2) - throw runtime_error( - "listreceivedbyaddress [minconf=1] [includeempty=false]\n" - "[minconf] is the minimum number of confirmations before payments are included.\n" - "[includeempty] whether to include addresses that haven't received any payments.\n" - "Returns an array of objects containing:\n" - " \"address\" : receiving address\n" - " \"account\" : the account of the receiving address\n" - " \"amount\" : total amount received by the address\n" - " \"confirmations\" : number of confirmations of the most recent transaction included"); - - return ListReceived(params, false); -} - -Value listreceivedbyaccount(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 2) - throw runtime_error( - "listreceivedbyaccount [minconf=1] [includeempty=false]\n" - "[minconf] is the minimum number of confirmations before payments are included.\n" - "[includeempty] whether to include accounts that haven't received any payments.\n" - "Returns an array of objects containing:\n" - " \"account\" : the account of the receiving addresses\n" - " \"amount\" : total amount received by addresses with this account\n" - " \"confirmations\" : number of confirmations of the most recent transaction included"); - - return ListReceived(params, true); -} - -void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, Array& ret) -{ - int64 nGeneratedImmature, nGeneratedMature, nFee; - string strSentAccount; - list > listReceived; - list > listSent; - - wtx.GetAmounts(nGeneratedImmature, nGeneratedMature, listReceived, listSent, nFee, strSentAccount); - - bool fAllAccounts = (strAccount == string("*")); - - // Generated blocks assigned to account "" - if ((nGeneratedMature+nGeneratedImmature) != 0 && (fAllAccounts || strAccount == "")) - { - Object entry; - entry.push_back(Pair("account", string(""))); - if (nGeneratedImmature) - { - entry.push_back(Pair("category", wtx.GetDepthInMainChain() ? "immature" : "orphan")); - entry.push_back(Pair("amount", ValueFromAmount(nGeneratedImmature))); - } - else - { - entry.push_back(Pair("category", "generate")); - entry.push_back(Pair("amount", ValueFromAmount(nGeneratedMature))); - } - if (fLong) - WalletTxToJSON(wtx, entry); - ret.push_back(entry); - } - - // Sent - if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) - { - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& s, listSent) - { - Object entry; - entry.push_back(Pair("account", strSentAccount)); - entry.push_back(Pair("address", s.first.ToString())); - entry.push_back(Pair("category", "send")); - entry.push_back(Pair("amount", ValueFromAmount(-s.second))); - entry.push_back(Pair("fee", ValueFromAmount(-nFee))); - if (fLong) - WalletTxToJSON(wtx, entry); - ret.push_back(entry); - } - } - - // Received - if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) - { - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& r, listReceived) - { - string account; - if (pwalletMain->mapAddressBook.count(r.first)) - account = pwalletMain->mapAddressBook[r.first]; - if (fAllAccounts || (account == strAccount)) - { - Object entry; - entry.push_back(Pair("account", account)); - entry.push_back(Pair("address", r.first.ToString())); - entry.push_back(Pair("category", "receive")); - entry.push_back(Pair("amount", ValueFromAmount(r.second))); - if (fLong) - WalletTxToJSON(wtx, entry); - ret.push_back(entry); - } - } - } -} - -void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, Array& ret) -{ - bool fAllAccounts = (strAccount == string("*")); - - if (fAllAccounts || acentry.strAccount == strAccount) - { - Object entry; - entry.push_back(Pair("account", acentry.strAccount)); - entry.push_back(Pair("category", "move")); - entry.push_back(Pair("time", (boost::int64_t)acentry.nTime)); - entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit))); - entry.push_back(Pair("otheraccount", acentry.strOtherAccount)); - entry.push_back(Pair("comment", acentry.strComment)); - ret.push_back(entry); - } -} - -Value listtransactions(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 3) - throw runtime_error( - "listtransactions [account] [count=10] [from=0]\n" - "Returns up to [count] most recent transactions skipping the first [from] transactions for account [account]."); - - string strAccount = "*"; - if (params.size() > 0) - strAccount = params[0].get_str(); - int nCount = 10; - if (params.size() > 1) - nCount = params[1].get_int(); - int nFrom = 0; - if (params.size() > 2) - nFrom = params[2].get_int(); - - if (nCount < 0) - throw JSONRPCError(-8, "Negative count"); - if (nFrom < 0) - throw JSONRPCError(-8, "Negative from"); - - Array ret; - CWalletDB walletdb(pwalletMain->strWalletFile); - - // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap. - typedef pair TxPair; - typedef multimap TxItems; - TxItems txByTime; - - // Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry - // would make this much faster for applications that do this a lot. - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - CWalletTx* wtx = &((*it).second); - txByTime.insert(make_pair(wtx->GetTxTime(), TxPair(wtx, (CAccountingEntry*)0))); - } - list acentries; - walletdb.ListAccountCreditDebit(strAccount, acentries); - BOOST_FOREACH(CAccountingEntry& entry, acentries) - { - txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry))); - } - - // iterate backwards until we have nCount items to return: - for (TxItems::reverse_iterator it = txByTime.rbegin(); it != txByTime.rend(); ++it) - { - CWalletTx *const pwtx = (*it).second.first; - if (pwtx != 0) - ListTransactions(*pwtx, strAccount, 0, true, ret); - CAccountingEntry *const pacentry = (*it).second.second; - if (pacentry != 0) - AcentryToJSON(*pacentry, strAccount, ret); - - if (ret.size() >= (nCount+nFrom)) break; - } - // ret is newest to oldest - - if (nFrom > (int)ret.size()) - nFrom = ret.size(); - if ((nFrom + nCount) > (int)ret.size()) - nCount = ret.size() - nFrom; - Array::iterator first = ret.begin(); - std::advance(first, nFrom); - Array::iterator last = ret.begin(); - std::advance(last, nFrom+nCount); - - if (last != ret.end()) ret.erase(last, ret.end()); - if (first != ret.begin()) ret.erase(ret.begin(), first); - - std::reverse(ret.begin(), ret.end()); // Return oldest to newest - - return ret; -} - -Value listaccounts(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "listaccounts [minconf=1]\n" - "Returns Object that has account names as keys, account balances as values."); - - int nMinDepth = 1; - if (params.size() > 0) - nMinDepth = params[0].get_int(); - - map mapAccountBalances; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& entry, pwalletMain->mapAddressBook) { - if (pwalletMain->HaveKey(entry.first)) // This address belongs to me - mapAccountBalances[entry.second] = 0; - } - - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; - int64 nGeneratedImmature, nGeneratedMature, nFee; - string strSentAccount; - list > listReceived; - list > listSent; - wtx.GetAmounts(nGeneratedImmature, nGeneratedMature, listReceived, listSent, nFee, strSentAccount); - mapAccountBalances[strSentAccount] -= nFee; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& s, listSent) - mapAccountBalances[strSentAccount] -= s.second; - if (wtx.GetDepthInMainChain() >= nMinDepth) - { - mapAccountBalances[""] += nGeneratedMature; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, int64)& r, listReceived) - if (pwalletMain->mapAddressBook.count(r.first)) - mapAccountBalances[pwalletMain->mapAddressBook[r.first]] += r.second; - else - mapAccountBalances[""] += r.second; - } - } - - list acentries; - CWalletDB(pwalletMain->strWalletFile).ListAccountCreditDebit("*", acentries); - BOOST_FOREACH(const CAccountingEntry& entry, acentries) - mapAccountBalances[entry.strAccount] += entry.nCreditDebit; - - Object ret; - BOOST_FOREACH(const PAIRTYPE(string, int64)& accountBalance, mapAccountBalances) { - ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second))); - } - return ret; -} - -Value listsinceblock(const Array& params, bool fHelp) -{ - if (fHelp) - throw runtime_error( - "listsinceblock [blockhash] [target-confirmations]\n" - "Get all transactions in blocks since block [blockhash], or all transactions if omitted"); - - CBlockIndex *pindex = NULL; - int target_confirms = 1; - + "stop \n" + " is true or false to detach the database or not for this stop only\n" + "Stop NovaCoin server (and possibly override the detachdb config value)."); + // Shutdown will take long enough that the response should get back if (params.size() > 0) - { - uint256 blockId = 0; - - blockId.SetHex(params[0].get_str()); - pindex = CBlockLocator(blockId).GetBlockIndex(); - } - - if (params.size() > 1) - { - target_confirms = params[1].get_int(); - - if (target_confirms < 1) - throw JSONRPCError(-8, "Invalid parameter"); - } - - int depth = pindex ? (1 + nBestHeight - pindex->nHeight) : -1; - - Array transactions; - - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); it++) - { - CWalletTx tx = (*it).second; - - if (depth == -1 || tx.GetDepthInMainChain() < depth) - ListTransactions(tx, "*", 0, true, transactions); - } - - uint256 lastblock; - - if (target_confirms == 1) - { - lastblock = hashBestChain; - } - else - { - int target_height = pindexBest->nHeight + 1 - target_confirms; - - CBlockIndex *block; - for (block = pindexBest; - block && block->nHeight > target_height; - block = block->pprev) { } - - lastblock = block ? block->GetBlockHash() : 0; - } - - Object ret; - ret.push_back(Pair("transactions", transactions)); - ret.push_back(Pair("lastblock", lastblock.GetHex())); - - return ret; -} - -Value gettransaction(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 1) - throw runtime_error( - "gettransaction \n" - "Get detailed information about "); - - uint256 hash; - hash.SetHex(params[0].get_str()); - - Object entry; - - if (pwalletMain->mapWallet.count(hash)) - { - const CWalletTx& wtx = pwalletMain->mapWallet[hash]; - - TxToJSON(wtx, entry); - - int64 nCredit = wtx.GetCredit(); - int64 nDebit = wtx.GetDebit(); - int64 nNet = nCredit - nDebit; - int64 nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0); - - entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); - if (wtx.IsFromMe()) - entry.push_back(Pair("fee", ValueFromAmount(nFee))); - - WalletTxToJSON(wtx, entry); - - Array details; - ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details); - entry.push_back(Pair("details", details)); - } - else - { - CTransaction tx; - uint256 hashBlock = 0; - if (GetTransaction(hash, tx, hashBlock)) - { - entry.push_back(Pair("txid", hash.GetHex())); - TxToJSON(tx, entry); - if (hashBlock == 0) - entry.push_back(Pair("confirmations", 0)); - else - { - entry.push_back(Pair("blockhash", hashBlock.GetHex())); - map::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end() && (*mi).second) - { - CBlockIndex* pindex = (*mi).second; - if (pindex->IsInMainChain()) - { - entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight)); - entry.push_back(Pair("txntime", (boost::int64_t)tx.nTime)); - entry.push_back(Pair("time", (boost::int64_t)pindex->nTime)); - } - else - entry.push_back(Pair("confirmations", 0)); - } - } - } - else - throw JSONRPCError(-5, "No information available about transaction"); - } - - return entry; -} - - -Value backupwallet(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 1) - throw runtime_error( - "backupwallet \n" - "Safely copies wallet.dat to destination, which can be a directory or a path with filename."); - - string strDest = params[0].get_str(); - BackupWallet(*pwalletMain, strDest); - - return Value::null; -} - - -Value keypoolrefill(const Array& params, bool fHelp) -{ - if (pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) - throw runtime_error( - "keypoolrefill\n" - "Fills the keypool, requires wallet passphrase to be set."); - if (!pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) - throw runtime_error( - "keypoolrefill\n" - "Fills the keypool."); - - if (pwalletMain->IsLocked()) - throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); - - pwalletMain->TopUpKeyPool(); - - if (pwalletMain->GetKeyPoolSize() < GetArg("-keypool", 100)) - throw JSONRPCError(-4, "Error refreshing keypool."); - - return Value::null; -} - - -void ThreadTopUpKeyPool(void* parg) -{ - pwalletMain->TopUpKeyPool(); -} - -void ThreadCleanWalletPassphrase(void* parg) -{ - int64 nMyWakeTime = GetTimeMillis() + *((int64*)parg) * 1000; - - ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime); - - if (nWalletUnlockTime == 0) - { - nWalletUnlockTime = nMyWakeTime; - - do - { - if (nWalletUnlockTime==0) - break; - int64 nToSleep = nWalletUnlockTime - GetTimeMillis(); - if (nToSleep <= 0) - break; - - LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime); - Sleep(nToSleep); - ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime); - - } while(1); - - if (nWalletUnlockTime) - { - nWalletUnlockTime = 0; - pwalletMain->Lock(); - } - } - else - { - if (nWalletUnlockTime < nMyWakeTime) - nWalletUnlockTime = nMyWakeTime; - } - - LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime); - - delete (int64*)parg; -} - -Value walletpassphrase(const Array& params, bool fHelp) -{ - if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 3)) - throw runtime_error( - "walletpassphrase [mintonly]\n" - "Stores the wallet decryption key in memory for seconds.\n" - "mintonly is optional true/false allowing only block minting."); - if (fHelp) - return true; - if (!pwalletMain->IsCrypted()) - throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletpassphrase was called."); - - if (!pwalletMain->IsLocked()) - throw JSONRPCError(-17, "Error: Wallet is already unlocked, use walletlock first if need to change unlock settings."); - - // Note that the walletpassphrase is stored in params[0] which is not mlock()ed - SecureString strWalletPass; - strWalletPass.reserve(100); - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make params[0] mlock()'d to begin with. - strWalletPass = params[0].get_str().c_str(); - - if (strWalletPass.length() > 0) - { - if (!pwalletMain->Unlock(strWalletPass)) - throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); - } - else - throw runtime_error( - "walletpassphrase \n" - "Stores the wallet decryption key in memory for seconds."); - - CreateThread(ThreadTopUpKeyPool, NULL); - int64* pnSleepTime = new int64(params[1].get_int64()); - CreateThread(ThreadCleanWalletPassphrase, pnSleepTime); - - // ppcoin: if user OS account compromised prevent trivial sendmoney commands - if (params.size() > 2) - fWalletUnlockMintOnly = params[2].get_bool(); - else - fWalletUnlockMintOnly = false; - - return Value::null; -} - - -Value walletpassphrasechange(const Array& params, bool fHelp) -{ - if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2)) - throw runtime_error( - "walletpassphrasechange \n" - "Changes the wallet passphrase from to ."); - if (fHelp) - return true; - if (!pwalletMain->IsCrypted()) - throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); - - // TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string) - // Alternately, find a way to make params[0] mlock()'d to begin with. - SecureString strOldWalletPass; - strOldWalletPass.reserve(100); - strOldWalletPass = params[0].get_str().c_str(); - - SecureString strNewWalletPass; - strNewWalletPass.reserve(100); - strNewWalletPass = params[1].get_str().c_str(); - - if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) - throw runtime_error( - "walletpassphrasechange \n" - "Changes the wallet passphrase from to ."); - - if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) - throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); - - return Value::null; -} - - -Value walletlock(const Array& params, bool fHelp) -{ - if (pwalletMain->IsCrypted() && (fHelp || params.size() != 0)) - throw runtime_error( - "walletlock\n" - "Removes the wallet encryption key from memory, locking the wallet.\n" - "After calling this method, you will need to call walletpassphrase again\n" - "before being able to call any methods which require the wallet to be unlocked."); - if (fHelp) - return true; - if (!pwalletMain->IsCrypted()) - throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletlock was called."); - - { - LOCK(cs_nWalletUnlockTime); - pwalletMain->Lock(); - nWalletUnlockTime = 0; - } - - return Value::null; -} - - -Value encryptwallet(const Array& params, bool fHelp) -{ - if (!pwalletMain->IsCrypted() && (fHelp || params.size() != 1)) - throw runtime_error( - "encryptwallet \n" - "Encrypts the wallet with ."); - if (fHelp) - return true; - if (pwalletMain->IsCrypted()) - throw JSONRPCError(-15, "Error: running with an encrypted wallet, but encryptwallet was called."); - - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make params[0] mlock()'d to begin with. - SecureString strWalletPass; - strWalletPass.reserve(100); - strWalletPass = params[0].get_str().c_str(); - - if (strWalletPass.length() < 1) - throw runtime_error( - "encryptwallet \n" - "Encrypts the wallet with ."); - - if (!pwalletMain->EncryptWallet(strWalletPass)) - throw JSONRPCError(-16, "Error: Failed to encrypt the wallet."); - - // BDB seems to have a bad habit of writing old data into - // slack space in .dat files; that is bad if the old data is - // unencrypted private keys. So: + bitdb.SetDetach(params[0].get_bool()); StartShutdown(); - return "wallet encrypted; novacoin server stopping, restart to run with encrypted wallet"; -} - - -Value validateaddress(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 1) - throw runtime_error( - "validateaddress \n" - "Return information about ."); - - CBitcoinAddress address(params[0].get_str()); - bool isValid = address.IsValid(); - - Object ret; - ret.push_back(Pair("isvalid", isValid)); - if (isValid) - { - // Call Hash160ToAddress() so we always return current ADDRESSVERSION - // version of the address: - string currentAddress = address.ToString(); - ret.push_back(Pair("address", currentAddress)); - if (pwalletMain->HaveKey(address)) - { - ret.push_back(Pair("ismine", true)); - std::vector vchPubKey; - pwalletMain->GetPubKey(address, vchPubKey); - ret.push_back(Pair("pubkey", HexStr(vchPubKey))); - CKey key; - key.SetPubKey(vchPubKey); - ret.push_back(Pair("iscompressed", key.IsCompressed())); - } - else if (pwalletMain->HaveCScript(address.GetHash160())) - { - ret.push_back(Pair("isscript", true)); - CScript subscript; - pwalletMain->GetCScript(address.GetHash160(), subscript); - ret.push_back(Pair("ismine", ::IsMine(*pwalletMain, subscript))); - std::vector addresses; - txnouttype whichType; - int nRequired; - ExtractAddresses(subscript, whichType, addresses, nRequired); - ret.push_back(Pair("script", GetTxnOutputType(whichType))); - Array a; - BOOST_FOREACH(const CBitcoinAddress& addr, addresses) - a.push_back(addr.ToString()); - ret.push_back(Pair("addresses", a)); - if (whichType == TX_MULTISIG) - ret.push_back(Pair("sigsrequired", nRequired)); - } - else - ret.push_back(Pair("ismine", false)); - if (pwalletMain->mapAddressBook.count(address)) - ret.push_back(Pair("account", pwalletMain->mapAddressBook[address])); - } - return ret; -} - -Value validatepubkey(const Array& params, bool fHelp) -{ - if (fHelp || !params.size() || params.size() > 2) - throw runtime_error( - "validatepubkey \n" - "Return information about ."); - - std::vector vchPubKey = ParseHex(params[0].get_str()); - bool isValid; - - if(vchPubKey.size() == 33) // Compressed key - isValid = (vchPubKey[0] == 0x02 || vchPubKey[0] == 0x03); - else if(vchPubKey.size() == 65) // Uncompressed key - isValid = vchPubKey[0] == 0x04; - else - isValid = false; - - CBitcoinAddress address(vchPubKey); - isValid = isValid ? address.IsValid() : false; - - Object ret; - ret.push_back(Pair("isvalid", isValid)); - if (isValid) - { - // Call Hash160ToAddress() so we always return current ADDRESSVERSION - // version of the address: - string currentAddress = address.ToString(); - ret.push_back(Pair("address", currentAddress)); - if (pwalletMain->HaveKey(address)) - { - ret.push_back(Pair("ismine", true)); - CKey key; - key.SetPubKey(vchPubKey); - ret.push_back(Pair("iscompressed", key.IsCompressed())); - } - else - ret.push_back(Pair("ismine", false)); - if (pwalletMain->mapAddressBook.count(address)) - ret.push_back(Pair("account", pwalletMain->mapAddressBook[address])); - } - return ret; -} - -Value getworkex(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 2) - throw runtime_error( - "getworkex [data, coinbase]\n" - "If [data, coinbase] is not specified, returns extended work data.\n" - ); - - if (vNodes.empty()) - throw JSONRPCError(-9, "NovaCoin is not connected!"); - - if (IsInitialBlockDownload()) - throw JSONRPCError(-10, "NovaCoin is downloading blocks..."); - - typedef map > mapNewBlock_t; - static mapNewBlock_t mapNewBlock; - static vector vNewBlock; - static CReserveKey reservekey(pwalletMain); - - if (params.size() == 0) - { - // Update block - static unsigned int nTransactionsUpdatedLast; - static CBlockIndex* pindexPrev; - static int64 nStart; - static CBlock* pblock; - if (pindexPrev != pindexBest || - (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)) - { - if (pindexPrev != pindexBest) - { - // Deallocate old blocks since they're obsolete now - mapNewBlock.clear(); - BOOST_FOREACH(CBlock* pblock, vNewBlock) - delete pblock; - vNewBlock.clear(); - } - nTransactionsUpdatedLast = nTransactionsUpdated; - pindexPrev = pindexBest; - nStart = GetTime(); - - // Create new block - pblock = CreateNewBlock(pwalletMain); - if (!pblock) - throw JSONRPCError(-7, "Out of memory"); - vNewBlock.push_back(pblock); - } - - // Update nTime - pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); - pblock->nNonce = 0; - - // Update nExtraNonce - static unsigned int nExtraNonce = 0; - IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); - - // Save - mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig); - - // Prebuild hash buffers - char pmidstate[32]; - char pdata[128]; - char phash1[64]; - FormatHashBuffers(pblock, pmidstate, pdata, phash1); - - uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - - CTransaction coinbaseTx = pblock->vtx[0]; - std::vector merkle = pblock->GetMerkleBranch(0); - - Object result; - result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata)))); - result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget)))); - - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << coinbaseTx; - result.push_back(Pair("coinbase", HexStr(ssTx.begin(), ssTx.end()))); - - Array merkle_arr; - printf("DEBUG: merkle size %i\n", merkle.size()); - - BOOST_FOREACH(uint256 merkleh, merkle) { - printf("%s\n", merkleh.ToString().c_str()); - merkle_arr.push_back(HexStr(BEGIN(merkleh), END(merkleh))); - } - - result.push_back(Pair("merkle", merkle_arr)); - - - return result; - } - else - { - // Parse parameters - vector vchData = ParseHex(params[0].get_str()); - vector coinbase; - - if(params.size() == 2) - coinbase = ParseHex(params[1].get_str()); - - if (vchData.size() != 128) - throw JSONRPCError(-8, "Invalid parameter"); - - CBlock* pdata = (CBlock*)&vchData[0]; - - // Byte reverse - for (int i = 0; i < 128/4; i++) - ((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]); - - // Get saved block - if (!mapNewBlock.count(pdata->hashMerkleRoot)) - return false; - CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first; - - pblock->nTime = pdata->nTime; - pblock->nNonce = pdata->nNonce; - - if(coinbase.size() == 0) - pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second; - else - CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - HACK! - - pblock->hashMerkleRoot = pblock->BuildMerkleTree(); - - if (!pblock->SignBlock(*pwalletMain)) - throw JSONRPCError(-100, "Unable to sign block, wallet locked?"); - - - return CheckWork(pblock, *pwalletMain, reservekey); - } -} - - -Value getwork(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "getwork [data]\n" - "If [data] is not specified, returns formatted hash data to work on:\n" - " \"midstate\" : precomputed hash state after hashing the first half of the data (DEPRECATED)\n" // deprecated - " \"data\" : block data\n" - " \"hash1\" : formatted hash buffer for second hash (DEPRECATED)\n" // deprecated - " \"target\" : little endian hash target\n" - "If [data] is specified, tries to solve the block and returns true if it was successful."); - - if (vNodes.empty()) - throw JSONRPCError(-9, "NovaCoin is not connected!"); - - if (IsInitialBlockDownload()) - throw JSONRPCError(-10, "NovaCoin is downloading blocks..."); - - typedef map > mapNewBlock_t; - static mapNewBlock_t mapNewBlock; - static vector vNewBlock; - static CReserveKey reservekey(pwalletMain); - - if (params.size() == 0) - { - // Update block - static unsigned int nTransactionsUpdatedLast; - static CBlockIndex* pindexPrev; - static int64 nStart; - static CBlock* pblock; - if (pindexPrev != pindexBest || - (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)) - { - if (pindexPrev != pindexBest) - { - // Deallocate old blocks since they're obsolete now - mapNewBlock.clear(); - BOOST_FOREACH(CBlock* pblock, vNewBlock) - delete pblock; - vNewBlock.clear(); - } - nTransactionsUpdatedLast = nTransactionsUpdated; - pindexPrev = pindexBest; - nStart = GetTime(); - - // Create new block - pblock = CreateNewBlock(pwalletMain); - if (!pblock) - throw JSONRPCError(-7, "Out of memory"); - vNewBlock.push_back(pblock); - } - - // Update nTime - pblock->UpdateTime(pindexPrev); - pblock->nNonce = 0; - - // Update nExtraNonce - static unsigned int nExtraNonce = 0; - IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); - - // Save - mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig); - - // Prebuild hash buffers - char pmidstate[32]; - char pdata[128]; - char phash1[64]; - FormatHashBuffers(pblock, pmidstate, pdata, phash1); - - uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - - Object result; - result.push_back(Pair("midstate", HexStr(BEGIN(pmidstate), END(pmidstate)))); // deprecated - result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata)))); - result.push_back(Pair("hash1", HexStr(BEGIN(phash1), END(phash1)))); // deprecated - result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget)))); - return result; - } - else - { - // Parse parameters - vector vchData = ParseHex(params[0].get_str()); - if (vchData.size() != 128) - throw JSONRPCError(-8, "Invalid parameter"); - CBlock* pdata = (CBlock*)&vchData[0]; - - // Byte reverse - for (int i = 0; i < 128/4; i++) - ((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]); - - // Get saved block - if (!mapNewBlock.count(pdata->hashMerkleRoot)) - return false; - CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first; - - pblock->nTime = pdata->nTime; - pblock->nNonce = pdata->nNonce; - pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second; - pblock->hashMerkleRoot = pblock->BuildMerkleTree(); - if (!pblock->SignBlock(*pwalletMain)) - throw JSONRPCError(-100, "Unable to sign block, wallet locked?"); - - return CheckWork(pblock, *pwalletMain, reservekey); - } -} - -Value getblocktemplate(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "getblocktemplate [params]\n" - "Returns data needed to construct a block to work on:\n" - " \"version\" : block version\n" - " \"previousblockhash\" : hash of current highest block\n" - " \"transactions\" : contents of non-coinbase transactions that should be included in the next block\n" - " \"coinbaseaux\" : data that should be included in coinbase\n" - " \"coinbasevalue\" : maximum allowable input to coinbase transaction, including the generation award and transaction fees\n" - " \"target\" : hash target\n" - " \"mintime\" : minimum timestamp appropriate for next block\n" - " \"curtime\" : current timestamp\n" - " \"mutable\" : list of ways the block template may be changed\n" - " \"noncerange\" : range of valid nonces\n" - " \"sigoplimit\" : limit of sigops in blocks\n" - " \"sizelimit\" : limit of block size\n" - " \"bits\" : compressed target of next block\n" - " \"height\" : height of the next block\n" - "See https://en.bitcoin.it/wiki/BIP_0022 for full specification."); - - std::string strMode = "template"; - if (params.size() > 0) - { - const Object& oparam = params[0].get_obj(); - const Value& modeval = find_value(oparam, "mode"); - if (modeval.type() == str_type) - strMode = modeval.get_str(); - else - throw JSONRPCError(-8, "Invalid mode"); - } - - if (strMode != "template") - throw JSONRPCError(-8, "Invalid mode"); - - if (vNodes.empty()) - throw JSONRPCError(-9, "NovaCoin is not connected!"); - - if (IsInitialBlockDownload()) - throw JSONRPCError(-10, "NovaCoin is downloading blocks..."); - - static CReserveKey reservekey(pwalletMain); - - // Update block - static unsigned int nTransactionsUpdatedLast; - static CBlockIndex* pindexPrev; - static int64 nStart; - static CBlock* pblock; - if (pindexPrev != pindexBest || - (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 5)) - { - // Clear pindexPrev so future calls make a new block, despite any failures from here on - pindexPrev = NULL; - - // Store the pindexBest used before CreateNewBlock, to avoid races - nTransactionsUpdatedLast = nTransactionsUpdated; - CBlockIndex* pindexPrevNew = pindexBest; - nStart = GetTime(); - - // Create new block - if(pblock) - { - delete pblock; - pblock = NULL; - } - pblock = CreateNewBlock(pwalletMain); - if (!pblock) - throw JSONRPCError(-7, "Out of memory"); - - // Need to update only after we know CreateNewBlock succeeded - pindexPrev = pindexPrevNew; - } - - // Update nTime - pblock->UpdateTime(pindexPrev); - pblock->nNonce = 0; - - Array transactions; - map setTxIndex; - int i = 0; - CTxDB txdb("r"); - BOOST_FOREACH (CTransaction& tx, pblock->vtx) - { - uint256 txHash = tx.GetHash(); - setTxIndex[txHash] = i++; - - if (tx.IsCoinBase() || tx.IsCoinStake()) - continue; - - Object entry; - - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - entry.push_back(Pair("data", HexStr(ssTx.begin(), ssTx.end()))); - - entry.push_back(Pair("hash", txHash.GetHex())); - - MapPrevTx mapInputs; - map mapUnused; - bool fInvalid = false; - if (tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid)) - { - entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(mapInputs) - tx.GetValueOut()))); - - Array deps; - BOOST_FOREACH (MapPrevTx::value_type& inp, mapInputs) - { - if (setTxIndex.count(inp.first)) - deps.push_back(setTxIndex[inp.first]); - } - entry.push_back(Pair("depends", deps)); - - int64_t nSigOps = tx.GetLegacySigOpCount(); - nSigOps += tx.GetP2SHSigOpCount(mapInputs); - entry.push_back(Pair("sigops", nSigOps)); - } - - transactions.push_back(entry); - } - - Object aux; - aux.push_back(Pair("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end()))); - - uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); - - static Array aMutable; - if (aMutable.empty()) - { - aMutable.push_back("time"); - aMutable.push_back("transactions"); - aMutable.push_back("prevblock"); - } - - Object result; - result.push_back(Pair("version", pblock->nVersion)); - result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex())); - result.push_back(Pair("transactions", transactions)); - result.push_back(Pair("coinbaseaux", aux)); - result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue)); - result.push_back(Pair("target", hashTarget.GetHex())); - result.push_back(Pair("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1)); - result.push_back(Pair("mutable", aMutable)); - result.push_back(Pair("noncerange", "00000000ffffffff")); - result.push_back(Pair("sigoplimit", (int64_t)MAX_BLOCK_SIGOPS)); - result.push_back(Pair("sizelimit", (int64_t)MAX_BLOCK_SIZE)); - result.push_back(Pair("curtime", (int64_t)pblock->nTime)); - result.push_back(Pair("bits", HexBits(pblock->nBits))); - result.push_back(Pair("height", (int64_t)(pindexPrev->nHeight+1))); - - return result; -} - -Value submitblock(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "submitblock [optional-params-obj]\n" - "[optional-params-obj] parameter is currently ignored.\n" - "Attempts to submit new block to network.\n" - "See https://en.bitcoin.it/wiki/BIP_0022 for full specification."); - - vector blockData(ParseHex(params[0].get_str())); - CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION); - CBlock block; - try { - ssBlock >> block; - } - catch (std::exception &e) { - throw JSONRPCError(-22, "Block decode failed"); - } - - static CReserveKey reservekey(pwalletMain); - - if(!block.SignBlock(*pwalletMain)) - throw JSONRPCError(-100, "Unable to sign block, wallet locked?"); - - bool fAccepted = CheckWork(&block, *pwalletMain, reservekey); - if (!fAccepted) - return "rejected"; - - return Value::null; -} - - -Value getmemorypool(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "getmemorypool [data]\n" - "If [data] is not specified, returns data needed to construct a block to work on:\n" - " \"version\" : block version\n" - " \"previousblockhash\" : hash of current highest block\n" - " \"transactions\" : contents of non-coinbase transactions that should be included in the next block\n" - " \"coinbasevalue\" : maximum allowable input to coinbase transaction, including the generation award and transaction fees\n" - " \"coinbaseflags\" : data that should be included in coinbase so support for new features can be judged\n" - " \"time\" : timestamp appropriate for next block\n" - " \"mintime\" : minimum timestamp appropriate for next block\n" - " \"curtime\" : current timestamp\n" - " \"bits\" : compressed target of next block\n" - "If [data] is specified, tries to solve the block and returns true if it was successful."); - - if (params.size() == 0) - { - if (vNodes.empty()) - throw JSONRPCError(-9, "NovaCoin is not connected!"); - - if (IsInitialBlockDownload()) - throw JSONRPCError(-10, "NovaCoin is downloading blocks..."); - - static CReserveKey reservekey(pwalletMain); - - // Update block - static unsigned int nTransactionsUpdatedLast; - static CBlockIndex* pindexPrev; - static int64 nStart; - static CBlock* pblock; - if (pindexPrev != pindexBest || - (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 5)) - { - nTransactionsUpdatedLast = nTransactionsUpdated; - pindexPrev = pindexBest; - nStart = GetTime(); - - // Create new block - if(pblock) - delete pblock; - pblock = CreateNewBlock(pwalletMain); - if (!pblock) - throw JSONRPCError(-7, "Out of memory"); - } - - // Update nTime - pblock->UpdateTime(pindexPrev); - pblock->nNonce = 0; - - Array transactions; - BOOST_FOREACH(CTransaction tx, pblock->vtx) { - if(tx.IsCoinBase() || tx.IsCoinStake()) - continue; - - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - - transactions.push_back(HexStr(ssTx.begin(), ssTx.end())); - } - - Object result; - result.push_back(Pair("version", pblock->nVersion)); - result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex())); - result.push_back(Pair("transactions", transactions)); - result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue)); - result.push_back(Pair("coinbaseflags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end()))); - result.push_back(Pair("time", (int64_t)pblock->nTime)); - result.push_back(Pair("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1)); - result.push_back(Pair("curtime", (int64_t)GetAdjustedTime())); - result.push_back(Pair("bits", HexBits(pblock->nBits))); - - return result; - } - else - { - // Parse parameters - CDataStream ssBlock(ParseHex(params[0].get_str()), SER_NETWORK, PROTOCOL_VERSION); - CBlock pblock; - ssBlock >> pblock; - - static CReserveKey reservekey(pwalletMain); - - if(!pblock.SignBlock(*pwalletMain)) - throw JSONRPCError(-100, "Unable to sign block, wallet locked?"); - - return CheckWork(&pblock, *pwalletMain, reservekey); - } -} - -Value getnewpubkey(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "getnewpubkey [account]\n" - "Returns new public key for coinbase generation."); - - // Parse the account first so we don't generate a key if there's an error - string strAccount; - if (params.size() > 0) - strAccount = AccountFromValue(params[0]); - - if (!pwalletMain->IsLocked()) - pwalletMain->TopUpKeyPool(); - - // Generate a new key that is added to wallet - std::vector newKey = pwalletMain->GenerateNewKey(true); - - if(!newKey.size()) - throw JSONRPCError(-12, "Error: Unable to create key"); - - CBitcoinAddress address(newKey); - pwalletMain->SetAddressBookName(address, strAccount); - - return HexStr(newKey.begin(), newKey.end()); -} - -Value getblockhash(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 1) - throw runtime_error( - "getblockhash \n" - "Returns hash of block in best-block-chain at ."); - - int nHeight = params[0].get_int(); - if (nHeight < 0 || nHeight > nBestHeight) - throw runtime_error("Block number out of range."); - - CBlock block; - CBlockIndex* pblockindex = mapBlockIndex[hashBestChain]; - while (pblockindex->nHeight > nHeight) - pblockindex = pblockindex->pprev; - return pblockindex->phashBlock->GetHex(); -} - -Value getblock(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "getblock [txinfo]\n" - "txinfo optional to print more detailed tx info\n" - "Returns details of a block with given block-hash."); - - std::string strHash = params[0].get_str(); - uint256 hash(strHash); - - if (mapBlockIndex.count(hash) == 0) - throw JSONRPCError(-5, "Block not found"); - - CBlock block; - CBlockIndex* pblockindex = mapBlockIndex[hash]; - block.ReadFromDisk(pblockindex, true); - - return blockToJSON(block, pblockindex, params.size() > 1 ? params[1].get_bool() : false); -} - -Value getblockbynumber(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "getblock [txinfo]\n" - "txinfo optional to print more detailed tx info\n" - "Returns details of a block with given block-number."); - - int nHeight = params[0].get_int(); - if (nHeight < 0 || nHeight > nBestHeight) - throw runtime_error("Block number out of range."); - - CBlock block; - CBlockIndex* pblockindex = mapBlockIndex[hashBestChain]; - while (pblockindex->nHeight > nHeight) - pblockindex = pblockindex->pprev; - - uint256 hash = *pblockindex->phashBlock; - - pblockindex = mapBlockIndex[hash]; - block.ReadFromDisk(pblockindex, true); - - return blockToJSON(block, pblockindex, params.size() > 1 ? params[1].get_bool() : false); -} - -// ppcoin: get information of sync-checkpoint -Value getcheckpoint(const Array& params, bool fHelp) -{ - if (fHelp || params.size() != 0) - throw runtime_error( - "getcheckpoint\n" - "Show info of synchronized checkpoint.\n"); - - Object result; - CBlockIndex* pindexCheckpoint; - - result.push_back(Pair("synccheckpoint", Checkpoints::hashSyncCheckpoint.ToString().c_str())); - pindexCheckpoint = mapBlockIndex[Checkpoints::hashSyncCheckpoint]; - result.push_back(Pair("height", pindexCheckpoint->nHeight)); - result.push_back(Pair("timestamp", DateTimeStrFormat(pindexCheckpoint->GetBlockTime()).c_str())); - if (mapArgs.count("-checkpointkey")) - result.push_back(Pair("checkpointmaster", true)); - - return result; -} - - -// ppcoin: reserve balance from being staked for network protection -Value reservebalance(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 2) - throw runtime_error( - "reservebalance [ [amount]]\n" - " is true or false to turn balance reserve on or off.\n" - " is a real and rounded to cent.\n" - "Set reserve amount not participating in network protection.\n" - "If no parameters provided current setting is printed.\n"); - - if (params.size() > 0) - { - bool fReserve = params[0].get_bool(); - if (fReserve) - { - if (params.size() == 1) - throw runtime_error("must provide amount to reserve balance.\n"); - int64 nAmount = AmountFromValue(params[1]); - nAmount = (nAmount / CENT) * CENT; // round to cent - if (nAmount < 0) - throw runtime_error("amount cannot be negative.\n"); - mapArgs["-reservebalance"] = FormatMoney(nAmount).c_str(); - } - else - { - if (params.size() > 1) - throw runtime_error("cannot specify amount to turn off reserve.\n"); - mapArgs["-reservebalance"] = "0"; - } - } - - Object result; - int64 nReserveBalance = 0; - if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) - throw runtime_error("invalid reserve balance amount\n"); - result.push_back(Pair("reserve", (nReserveBalance > 0))); - result.push_back(Pair("amount", ValueFromAmount(nReserveBalance))); - return result; -} - - -// ppcoin: check wallet integrity -Value checkwallet(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 0) - throw runtime_error( - "checkwallet\n" - "Check wallet for integrity.\n"); - - int nMismatchSpent; - int64 nBalanceInQuestion; - pwalletMain->FixSpentCoins(nMismatchSpent, nBalanceInQuestion, true); - Object result; - if (nMismatchSpent == 0) - result.push_back(Pair("wallet check passed", true)); - else - { - result.push_back(Pair("mismatched spent coins", nMismatchSpent)); - result.push_back(Pair("amount in question", ValueFromAmount(nBalanceInQuestion))); - } - return result; -} - - -// ppcoin: repair wallet -Value repairwallet(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 0) - throw runtime_error( - "repairwallet\n" - "Repair wallet if checkwallet reports any problem.\n"); - - int nMismatchSpent; - int64 nBalanceInQuestion; - pwalletMain->FixSpentCoins(nMismatchSpent, nBalanceInQuestion); - Object result; - if (nMismatchSpent == 0) - result.push_back(Pair("wallet check passed", true)); - else - { - result.push_back(Pair("mismatched spent coins", nMismatchSpent)); - result.push_back(Pair("amount affected by repair", ValueFromAmount(nBalanceInQuestion))); - } - return result; -} - -// NovaCoin: resend unconfirmed wallet transactions -Value resendtx(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "resendtx\n" - "Re-send unconfirmed transactions.\n" - ); - - ResendWalletTransactions(); - - return Value::null; -} - - -// ppcoin: make a public-private key pair -Value makekeypair(const Array& params, bool fHelp) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "makekeypair [prefix]\n" - "Make a public/private key pair.\n" - "[prefix] is optional preferred prefix for the public key.\n"); - - string strPrefix = ""; - if (params.size() > 0) - strPrefix = params[0].get_str(); - - CKey key; - int nCount = 0; - do - { - key.MakeNewKey(false); - nCount++; - } while (nCount < 10000 && strPrefix != HexStr(key.GetPubKey()).substr(0, strPrefix.size())); - - if (strPrefix != HexStr(key.GetPubKey()).substr(0, strPrefix.size())) - return Value::null; - - CPrivKey vchPrivKey = key.GetPrivKey(); - Object result; - result.push_back(Pair("PrivateKey", HexStr(vchPrivKey.begin(), vchPrivKey.end()))); - result.push_back(Pair("PublicKey", HexStr(key.GetPubKey()))); - return result; -} - -extern CCriticalSection cs_mapAlerts; -extern map mapAlerts; - -// ppcoin: send alert. -// There is a known deadlock situation with ThreadMessageHandler -// ThreadMessageHandler: holds cs_vSend and acquiring cs_main in SendMessages() -// ThreadRPCServer: holds cs_main and acquiring cs_vSend in alert.RelayTo()/PushMessage()/BeginMessage() -Value sendalert(const Array& params, bool fHelp) -{ - if (fHelp || params.size() < 6) - throw runtime_error( - "sendalert [cancelupto]\n" - " is the alert text message\n" - " is hex string of alert master private key\n" - " is the minimum applicable internal client version\n" - " is the maximum applicable internal client version\n" - " is integer priority number\n" - " is the alert id\n" - "[cancelupto] cancels all alert id's up to this number\n" - "Returns true or false."); - - CAlert alert; - CKey key; - - alert.strStatusBar = params[0].get_str(); - alert.nMinVer = params[2].get_int(); - alert.nMaxVer = params[3].get_int(); - alert.nPriority = params[4].get_int(); - alert.nID = params[5].get_int(); - if (params.size() > 6) - alert.nCancel = params[6].get_int(); - alert.nVersion = PROTOCOL_VERSION; - alert.nRelayUntil = GetAdjustedTime() + 365*24*60*60; - alert.nExpiration = GetAdjustedTime() + 365*24*60*60; - - CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION); - sMsg << (CUnsignedAlert)alert; - alert.vchMsg = vector(sMsg.begin(), sMsg.end()); - - vector vchPrivKey = ParseHex(params[1].get_str()); - key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash - if (!key.Sign(Hash(alert.vchMsg.begin(), alert.vchMsg.end()), alert.vchSig)) - throw runtime_error( - "Unable to sign alert, check private key?\n"); - if(!alert.ProcessAlert()) - throw runtime_error( - "Failed to process alert.\n"); - // Relay alert - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - alert.RelayTo(pnode); - } - - Object result; - result.push_back(Pair("strStatusBar", alert.strStatusBar)); - result.push_back(Pair("nVersion", alert.nVersion)); - result.push_back(Pair("nMinVer", alert.nMinVer)); - result.push_back(Pair("nMaxVer", alert.nMaxVer)); - result.push_back(Pair("nPriority", alert.nPriority)); - result.push_back(Pair("nID", alert.nID)); - if (alert.nCancel > 0) - result.push_back(Pair("nCancel", alert.nCancel)); - return result; + return "NovaCoin server stopping"; } @@ -2791,68 +199,71 @@ Value sendalert(const Array& params, bool fHelp) static const CRPCCommand vRPCCommands[] = -{ // name function safe mode? - // ------------------------ ----------------------- ---------- - { "help", &help, true }, - { "stop", &stop, true }, - { "getblockcount", &getblockcount, true }, - { "getblocknumber", &getblocknumber, true }, - { "getconnectioncount", &getconnectioncount, true }, - { "getdifficulty", &getdifficulty, true }, - { "getpowreward", &getpowreward, true }, - { "getgenerate", &getgenerate, true }, - { "setgenerate", &setgenerate, true }, - { "gethashespersec", &gethashespersec, true }, - { "getinfo", &getinfo, true }, - { "getmininginfo", &getmininginfo, true }, - { "getnewaddress", &getnewaddress, true }, - { "getnewpubkey", &getnewpubkey, true }, - { "getaccountaddress", &getaccountaddress, true }, - { "setaccount", &setaccount, true }, - { "getaccount", &getaccount, false }, - { "getaddressesbyaccount", &getaddressesbyaccount, true }, - { "sendtoaddress", &sendtoaddress, false }, - { "getreceivedbyaddress", &getreceivedbyaddress, false }, - { "getreceivedbyaccount", &getreceivedbyaccount, false }, - { "listreceivedbyaddress", &listreceivedbyaddress, false }, - { "listreceivedbyaccount", &listreceivedbyaccount, false }, - { "backupwallet", &backupwallet, true }, - { "keypoolrefill", &keypoolrefill, true }, - { "walletpassphrase", &walletpassphrase, true }, - { "walletpassphrasechange", &walletpassphrasechange, false }, - { "walletlock", &walletlock, true }, - { "encryptwallet", &encryptwallet, false }, - { "validateaddress", &validateaddress, true }, - { "validatepubkey", &validatepubkey, true }, - { "getbalance", &getbalance, false }, - { "move", &movecmd, false }, - { "sendfrom", &sendfrom, false }, - { "sendmany", &sendmany, false }, - { "addmultisigaddress", &addmultisigaddress, false }, - { "getblock", &getblock, false }, - { "getblockhash", &getblockhash, false }, - { "getblockbynumber", &getblockbynumber, false }, - { "gettransaction", &gettransaction, false }, - { "listtransactions", &listtransactions, false }, - { "signmessage", &signmessage, false }, - { "verifymessage", &verifymessage, false }, - { "getwork", &getwork, true }, - { "getworkex", &getworkex, true }, - { "listaccounts", &listaccounts, false }, - { "settxfee", &settxfee, false }, - { "getmemorypool", &getmemorypool, true }, - { "getblocktemplate", &getblocktemplate, true }, - { "submitblock", &submitblock, false }, - { "listsinceblock", &listsinceblock, false }, - { "dumpprivkey", &dumpprivkey, false }, - { "importprivkey", &importprivkey, false }, - { "getcheckpoint", &getcheckpoint, true }, - { "reservebalance", &reservebalance, false}, - { "checkwallet", &checkwallet, false}, - { "repairwallet", &repairwallet, false}, - { "resendtx", &resendtx, false}, - { "makekeypair", &makekeypair, false}, - { "sendalert", &sendalert, false}, +{ // name function safemd unlocked + // ------------------------ ----------------------- ------ -------- + { "help", &help, true, true }, + { "stop", &stop, true, true }, + { "getblockcount", &getblockcount, true, false }, + { "getconnectioncount", &getconnectioncount, true, false }, + { "getpeerinfo", &getpeerinfo, true, false }, + { "getdifficulty", &getdifficulty, true, false }, + { "getgenerate", &getgenerate, true, false }, + { "setgenerate", &setgenerate, true, false }, + { "gethashespersec", &gethashespersec, true, false }, + { "getinfo", &getinfo, true, false }, + { "getmininginfo", &getmininginfo, true, false }, + { "getnewaddress", &getnewaddress, true, false }, + { "getaccountaddress", &getaccountaddress, true, false }, + { "setaccount", &setaccount, true, false }, + { "getaccount", &getaccount, false, false }, + { "getaddressesbyaccount", &getaddressesbyaccount, true, false }, + { "sendtoaddress", &sendtoaddress, false, false }, + { "getreceivedbyaddress", &getreceivedbyaddress, false, false }, + { "getreceivedbyaccount", &getreceivedbyaccount, false, false }, + { "listreceivedbyaddress", &listreceivedbyaddress, false, false }, + { "listreceivedbyaccount", &listreceivedbyaccount, false, false }, + { "backupwallet", &backupwallet, true, false }, + { "keypoolrefill", &keypoolrefill, true, false }, + { "walletpassphrase", &walletpassphrase, true, false }, + { "walletpassphrasechange", &walletpassphrasechange, false, false }, + { "walletlock", &walletlock, true, false }, + { "encryptwallet", &encryptwallet, false, false }, + { "validateaddress", &validateaddress, true, false }, + { "getbalance", &getbalance, false, false }, + { "move", &movecmd, false, false }, + { "sendfrom", &sendfrom, false, false }, + { "sendmany", &sendmany, false, false }, + { "addmultisigaddress", &addmultisigaddress, false, false }, + { "getrawmempool", &getrawmempool, true, false }, + { "getblock", &getblock, false, false }, + { "getblockbynumber", &getblockbynumber, false, false }, + { "getblockhash", &getblockhash, false, false }, + { "gettransaction", &gettransaction, false, false }, + { "listtransactions", &listtransactions, false, false }, + { "listaddressgroupings", &listaddressgroupings, false, false }, + { "signmessage", &signmessage, false, false }, + { "verifymessage", &verifymessage, false, false }, + { "getwork", &getwork, true, false }, + { "listaccounts", &listaccounts, false, false }, + { "settxfee", &settxfee, false, false }, + { "getblocktemplate", &getblocktemplate, true, false }, + { "submitblock", &submitblock, false, false }, + { "listsinceblock", &listsinceblock, false, false }, + { "dumpprivkey", &dumpprivkey, false, false }, + { "importprivkey", &importprivkey, false, false }, + { "listunspent", &listunspent, false, false }, + { "getrawtransaction", &getrawtransaction, false, false }, + { "createrawtransaction", &createrawtransaction, false, false }, + { "decoderawtransaction", &decoderawtransaction, false, false }, + { "signrawtransaction", &signrawtransaction, false, false }, + { "sendrawtransaction", &sendrawtransaction, false, false }, + { "getcheckpoint", &getcheckpoint, true, false }, + { "reservebalance", &reservebalance, false, true}, + { "checkwallet", &checkwallet, false, true}, + { "repairwallet", &repairwallet, false, true}, + { "resendtx", &resendtx, false, true}, + { "makekeypair", &makekeypair, false, true}, + { "sendalert", &sendalert, false, false}, }; CRPCTable::CRPCTable() @@ -2906,15 +317,15 @@ string rfc1123Time() time(&now); struct tm* now_gmt = gmtime(&now); string locale(setlocale(LC_TIME, NULL)); - setlocale(LC_TIME, "C"); // we want posix (aka "C") weekday/month strings + setlocale(LC_TIME, "C"); // we want POSIX (aka "C") weekday/month strings strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt); setlocale(LC_TIME, locale.c_str()); return string(buffer); } -static string HTTPReply(int nStatus, const string& strMsg) +static string HTTPReply(int nStatus, const string& strMsg, bool keepalive) { - if (nStatus == 401) + if (nStatus == HTTP_UNAUTHORIZED) return strprintf("HTTP/1.0 401 Authorization Required\r\n" "Date: %s\r\n" "Server: novacoin-json-rpc/%s\r\n" @@ -2932,17 +343,17 @@ static string HTTPReply(int nStatus, const string& strMsg) "

401 Unauthorized.

\r\n" "\r\n", rfc1123Time().c_str(), FormatFullVersion().c_str()); const char *cStatus; - if (nStatus == 200) cStatus = "OK"; - else if (nStatus == 400) cStatus = "Bad Request"; - else if (nStatus == 403) cStatus = "Forbidden"; - else if (nStatus == 404) cStatus = "Not Found"; - else if (nStatus == 500) cStatus = "Internal Server Error"; + if (nStatus == HTTP_OK) cStatus = "OK"; + else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request"; + else if (nStatus == HTTP_FORBIDDEN) cStatus = "Forbidden"; + else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found"; + else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error"; else cStatus = ""; return strprintf( "HTTP/1.1 %d %s\r\n" "Date: %s\r\n" - "Connection: close\r\n" - "Content-Length: %d\r\n" + "Connection: %s\r\n" + "Content-Length: %"PRIszu"\r\n" "Content-Type: application/json\r\n" "Server: novacoin-json-rpc/%s\r\n" "\r\n" @@ -2950,19 +361,24 @@ static string HTTPReply(int nStatus, const string& strMsg) nStatus, cStatus, rfc1123Time().c_str(), + keepalive ? "keep-alive" : "close", strMsg.size(), FormatFullVersion().c_str(), strMsg.c_str()); } -int ReadHTTPStatus(std::basic_istream& stream) +int ReadHTTPStatus(std::basic_istream& stream, int &proto) { string str; getline(stream, str); vector vWords; boost::split(vWords, str, boost::is_any_of(" ")); if (vWords.size() < 2) - return 500; + 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()); } @@ -2997,12 +413,13 @@ int ReadHTTP(std::basic_istream& stream, map& mapHeadersRe strMessageRet = ""; // Read status - int nStatus = ReadHTTPStatus(stream); + int nProto = 0; + int nStatus = ReadHTTPStatus(stream, nProto); // Read header int nLen = ReadHTTPHeader(stream, mapHeadersRet); if (nLen < 0 || nLen > (int)MAX_SIZE) - return 500; + return HTTP_INTERNAL_SERVER_ERROR; // Read message if (nLen > 0) @@ -3012,6 +429,16 @@ int ReadHTTP(std::basic_istream& stream, map& mapHeadersRe 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 nStatus; } @@ -3044,7 +471,7 @@ string JSONRPCRequest(const string& strMethod, const Array& params, const Value& return write_string(Value(request), false) + "\n"; } -string JSONRPCReply(const Value& result, const Value& error, const Value& id) +Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) { Object reply; if (error.type() != null_type) @@ -3053,24 +480,42 @@ string JSONRPCReply(const Value& result, const Value& error, const Value& id) reply.push_back(Pair("result", result)); reply.push_back(Pair("error", error)); reply.push_back(Pair("id", id)); + return reply; +} + +string JSONRPCReply(const Value& result, const Value& error, const Value& id) +{ + Object reply = JSONRPCReplyObj(result, error, id); return write_string(Value(reply), false) + "\n"; } void ErrorReply(std::ostream& stream, const Object& objError, const Value& id) { // Send error reply from json-rpc error object - int nStatus = 500; + int nStatus = HTTP_INTERNAL_SERVER_ERROR; int code = find_value(objError, "code").get_int(); - if (code == -32600) nStatus = 400; - else if (code == -32601) nStatus = 404; + if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; + else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; string strReply = JSONRPCReply(Value::null, objError, id); - stream << HTTPReply(nStatus, strReply) << std::flush; + stream << HTTPReply(nStatus, strReply, false) << std::flush; } -bool ClientAllowed(const string& strAddress) +bool ClientAllowed(const boost::asio::ip::address& address) { - if (strAddress == asio::ip::address_v4::loopback().to_string()) + // 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())) + return ClientAllowed(address.to_v6().to_v4()); + + if (address == asio::ip::address_v4::loopback() + || address == asio::ip::address_v6::loopback() + || (address.is_v4() + // Check whether IPv4 addresses match 127.0.0.0/8 (loopback subnet) + && (address.to_v4().to_ulong() & 0xff000000) == 0x7f000000)) return true; + + const string strAddress = address.to_string(); const vector& vAllow = mapMultiArgs["-rpcallowip"]; BOOST_FOREACH(string strAllow, vAllow) if (WildcardMatch(strAddress, strAllow)) @@ -3081,9 +526,10 @@ bool ClientAllowed(const string& strAddress) // // IOStream device that speaks SSL but can also speak non-SSL // +template class SSLIOStreamDevice : public iostreams::device { public: - SSLIOStreamDevice(SSLStream &streamIn, bool fUseSSLIn) : stream(streamIn) + SSLIOStreamDevice(asio::ssl::stream &streamIn, bool fUseSSLIn) : stream(streamIn) { fUseSSL = fUseSSLIn; fNeedHandshake = fUseSSLIn; @@ -3127,26 +573,151 @@ public: private: bool fNeedHandshake; bool fUseSSL; - SSLStream& stream; + asio::ssl::stream& stream; +}; + +class AcceptedConnection +{ +public: + virtual ~AcceptedConnection() {} + + virtual std::iostream& stream() = 0; + virtual std::string peer_address_to_string() const = 0; + virtual void close() = 0; +}; + +template +class AcceptedConnectionImpl : public AcceptedConnection +{ +public: + AcceptedConnectionImpl( + 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; + asio::ssl::stream sslStream; + +private: + SSLIOStreamDevice _d; + iostreams::stream< SSLIOStreamDevice > _stream; }; void ThreadRPCServer(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadRPCServer(parg)); + // Make this thread recognisable as the RPC listener + RenameThread("bitcoin-rpclist"); + try { - vnThreadsRunning[THREAD_RPCSERVER]++; + vnThreadsRunning[THREAD_RPCLISTENER]++; ThreadRPCServer2(parg); - vnThreadsRunning[THREAD_RPCSERVER]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; } catch (std::exception& e) { - vnThreadsRunning[THREAD_RPCSERVER]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; PrintException(&e, "ThreadRPCServer()"); } catch (...) { - vnThreadsRunning[THREAD_RPCSERVER]--; + vnThreadsRunning[THREAD_RPCLISTENER]--; PrintException(NULL, "ThreadRPCServer()"); } - printf("ThreadRPCServer exiting\n"); + printf("ThreadRPCServer exited\n"); +} + +// Forward declaration required for RPCListen +template +static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor > acceptor, + ssl::context& context, + bool fUseSSL, + AcceptedConnection* conn, + const boost::system::error_code& error); + +/** + * Sets up I/O resources to accept and handle a new connection. + */ +template +static void RPCListen(boost::shared_ptr< basic_socket_acceptor > acceptor, + ssl::context& context, + const bool fUseSSL) +{ + // Accept connection + AcceptedConnectionImpl* conn = new AcceptedConnectionImpl(acceptor->get_io_service(), context, fUseSSL); + + acceptor->async_accept( + conn->sslStream.lowest_layer(), + conn->peer, + boost::bind(&RPCAcceptHandler, + acceptor, + boost::ref(context), + fUseSSL, + conn, + boost::asio::placeholders::error)); +} + +/** + * Accept and handle incoming connection. + */ +template +static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor > acceptor, + ssl::context& context, + const bool fUseSSL, + AcceptedConnection* conn, + const boost::system::error_code& error) +{ + vnThreadsRunning[THREAD_RPCLISTENER]++; + + // Immediately start accepting new connections, except when we're cancelled or our socket is closed. + if (error != asio::error::operation_aborted + && acceptor->is_open()) + RPCListen(acceptor, context, fUseSSL); + + AcceptedConnectionImpl* tcp_conn = dynamic_cast< AcceptedConnectionImpl* >(conn); + + // TODO: Actually handle errors + if (error) + { + delete conn; + } + + // 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() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush; + delete conn; + } + + // start HTTP client thread + else if (!NewThread(ThreadRPCServer3, conn)) { + printf("Failed to create RPC server client thread\n"); + delete conn; + } + + vnThreadsRunning[THREAD_RPCLISTENER]--; } void ThreadRPCServer2(void* parg) @@ -3158,46 +729,29 @@ void ThreadRPCServer2(void* parg) { unsigned char rand_pwd[32]; RAND_bytes(rand_pwd, 32); - string strWhatAmI = "To use novacoind"; + string strWhatAmI = "To use bitcoind"; if (mapArgs.count("-server")) strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); else if (mapArgs.count("-daemon")) strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); - ThreadSafeMessageBox(strprintf( + uiInterface.ThreadSafeMessageBox(strprintf( _("%s, you must set a rpcpassword in the configuration file:\n %s\n" "It is recommended you use the following random password:\n" - "rpcuser=novarpc\n" + "rpcuser=bitcoinrpc\n" "rpcpassword=%s\n" "(you do not need to remember this password)\n" "If the file does not exist, create it with owner-readable-only file permissions.\n"), strWhatAmI.c_str(), GetConfigFile().string().c_str(), EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), - _("Error"), wxOK | wxMODAL); + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); StartShutdown(); return; } - bool fUseSSL = GetBoolArg("-rpcssl"); - asio::ip::address bindAddress = mapArgs.count("-rpcallowip") ? asio::ip::address_v4::any() : asio::ip::address_v4::loopback(); + const bool fUseSSL = GetBoolArg("-rpcssl"); asio::io_service io_service; - ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", RPC_PORT)); - ip::tcp::acceptor acceptor(io_service); - try - { - acceptor.open(endpoint.protocol()); - acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - acceptor.bind(endpoint); - acceptor.listen(socket_base::max_connections); - } - catch(boost::system::system_error &e) - { - ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()), - _("Error"), wxOK | wxMODAL); - StartShutdown(); - return; - } ssl::context context(io_service, ssl::context::sslv23); if (fUseSSL) @@ -3218,132 +772,284 @@ void ThreadRPCServer2(void* parg) SSL_CTX_set_cipher_list(context.impl(), strCiphers.c_str()); } - loop + // Try a dual IPv6/IPv4 socket, falling back to separate IPv4 and IPv6 sockets + const bool loopback = !mapArgs.count("-rpcallowip"); + asio::ip::address bindAddress = loopback ? asio::ip::address_v6::loopback() : asio::ip::address_v6::any(); + ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", GetDefaultRPCPort())); + boost::system::error_code v6_only_error; + boost::shared_ptr acceptor(new ip::tcp::acceptor(io_service)); + + boost::signals2::signal StopRequests; + + bool fListening = false; + std::string strerr; + try { - // Accept connection - SSLStream sslStream(io_service, context); - SSLIOStreamDevice d(sslStream, fUseSSL); - iostreams::stream stream(d); - - ip::tcp::endpoint peer; - vnThreadsRunning[THREAD_RPCSERVER]--; - acceptor.accept(sslStream.lowest_layer(), peer); - vnThreadsRunning[4]++; - if (fShutdown) - return; + acceptor->open(endpoint.protocol()); + acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + + // Try making the socket dual IPv6/IPv4 (if listening on the "any" address) + acceptor->set_option(boost::asio::ip::v6_only(loopback), v6_only_error); + + acceptor->bind(endpoint); + acceptor->listen(socket_base::max_connections); - // Restrict callers by IP - if (!ClientAllowed(peer.address().to_string())) + RPCListen(acceptor, context, fUseSSL); + // Cancel outstanding listen-requests for this acceptor when shutting down + StopRequests.connect(signals2::slot( + static_cast(&ip::tcp::acceptor::close), acceptor.get()) + .track(acceptor)); + + fListening = true; + } + catch(boost::system::system_error &e) + { + strerr = strprintf(_("An error occurred while setting up the RPC port %u for listening on IPv6, falling back to IPv4: %s"), endpoint.port(), e.what()); + } + + try { + // If dual IPv6/IPv4 failed (or we're opening loopback interfaces only), open IPv4 separately + if (!fListening || loopback || v6_only_error) { - // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. - if (!fUseSSL) - stream << HTTPReply(403, "") << std::flush; - continue; + bindAddress = loopback ? asio::ip::address_v4::loopback() : asio::ip::address_v4::any(); + endpoint.address(bindAddress); + + acceptor.reset(new ip::tcp::acceptor(io_service)); + acceptor->open(endpoint.protocol()); + acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + acceptor->bind(endpoint); + acceptor->listen(socket_base::max_connections); + + RPCListen(acceptor, context, fUseSSL); + // Cancel outstanding listen-requests for this acceptor when shutting down + StopRequests.connect(signals2::slot( + static_cast(&ip::tcp::acceptor::close), acceptor.get()) + .track(acceptor)); + + fListening = true; } + } + catch(boost::system::system_error &e) + { + strerr = strprintf(_("An error occurred while setting up the RPC port %u for listening on IPv4: %s"), endpoint.port(), e.what()); + } + + if (!fListening) { + uiInterface.ThreadSafeMessageBox(strerr, _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + StartShutdown(); + return; + } + vnThreadsRunning[THREAD_RPCLISTENER]--; + while (!fShutdown) + io_service.run_one(); + vnThreadsRunning[THREAD_RPCLISTENER]++; + StopRequests(); +} + +class JSONRequest +{ +public: + Value id; + string strMethod; + Array params; + + JSONRequest() { id = Value::null; } + void parse(const Value& valRequest); +}; + +void JSONRequest::parse(const Value& valRequest) +{ + // Parse request + if (valRequest.type() != obj_type) + throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); + const Object& request = valRequest.get_obj(); + + // Parse id now so errors from here on will have the id + id = find_value(request, "id"); + + // Parse method + Value valMethod = find_value(request, "method"); + if (valMethod.type() == null_type) + throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); + if (valMethod.type() != str_type) + throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); + strMethod = valMethod.get_str(); + if (strMethod != "getwork" && strMethod != "getblocktemplate") + printf("ThreadRPCServer method=%s\n", strMethod.c_str()); + + // Parse params + Value valParams = find_value(request, "params"); + if (valParams.type() == array_type) + params = valParams.get_array(); + else if (valParams.type() == null_type) + params = Array(); + else + throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); +} + +static Object JSONRPCExecOne(const Value& req) +{ + Object rpc_result; + + JSONRequest jreq; + try { + jreq.parse(req); + + Value result = tableRPC.execute(jreq.strMethod, jreq.params); + rpc_result = JSONRPCReplyObj(result, Value::null, jreq.id); + } + catch (Object& objError) + { + rpc_result = JSONRPCReplyObj(Value::null, objError, jreq.id); + } + catch (std::exception& e) + { + rpc_result = JSONRPCReplyObj(Value::null, + JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + } + + return rpc_result; +} + +static string JSONRPCExecBatch(const Array& vReq) +{ + Array ret; + for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) + ret.push_back(JSONRPCExecOne(vReq[reqIdx])); + + return write_string(Value(ret), false) + "\n"; +} + +static CCriticalSection cs_THREAD_RPCHANDLER; + +void ThreadRPCServer3(void* parg) +{ + // Make this thread recognisable as the RPC handler + RenameThread("bitcoin-rpchand"); + + { + LOCK(cs_THREAD_RPCHANDLER); + vnThreadsRunning[THREAD_RPCHANDLER]++; + } + AcceptedConnection *conn = (AcceptedConnection *) parg; + + bool fRun = true; + loop { + if (fShutdown || !fRun) + { + conn->close(); + delete conn; + { + LOCK(cs_THREAD_RPCHANDLER); + --vnThreadsRunning[THREAD_RPCHANDLER]; + } + return; + } map mapHeaders; string strRequest; - boost::thread api_caller(ReadHTTP, boost::ref(stream), boost::ref(mapHeaders), boost::ref(strRequest)); - if (!api_caller.timed_join(boost::posix_time::seconds(GetArg("-rpctimeout", 30)))) - { // Timed out: - acceptor.cancel(); - printf("ThreadRPCServer ReadHTTP timeout\n"); - continue; - } + ReadHTTP(conn->stream(), mapHeaders, strRequest); // Check authorization if (mapHeaders.count("authorization") == 0) { - stream << HTTPReply(401, "") << std::flush; - continue; + conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; + break; } if (!HTTPAuthorized(mapHeaders)) { - printf("ThreadRPCServer incorrect password attempt from %s\n",peer.address().to_string().c_str()); + printf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string().c_str()); /* Deter brute-forcing short passwords. If this results in a DOS the user really shouldn't have their RPC port exposed.*/ if (mapArgs["-rpcpassword"].size() < 20) Sleep(250); - stream << HTTPReply(401, "") << std::flush; - continue; + conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush; + break; } + if (mapHeaders["connection"] == "close") + fRun = false; - Value id = Value::null; + JSONRequest jreq; try { // Parse request Value valRequest; - if (!read_string(strRequest, valRequest) || valRequest.type() != obj_type) - throw JSONRPCError(-32700, "Parse error"); - const Object& request = valRequest.get_obj(); - - // Parse id now so errors from here on will have the id - id = find_value(request, "id"); - - // Parse method - Value valMethod = find_value(request, "method"); - if (valMethod.type() == null_type) - throw JSONRPCError(-32600, "Missing method"); - if (valMethod.type() != str_type) - throw JSONRPCError(-32600, "Method must be a string"); - string strMethod = valMethod.get_str(); - if (strMethod != "getwork" && strMethod != "getmemorypool") - printf("ThreadRPCServer method=%s\n", strMethod.c_str()); - - // Parse params - Value valParams = find_value(request, "params"); - Array params; - if (valParams.type() == array_type) - params = valParams.get_array(); - else if (valParams.type() == null_type) - params = Array(); - else - throw JSONRPCError(-32600, "Params must be an array"); + if (!read_string(strRequest, valRequest)) + throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); - // Find method - const CRPCCommand *pcmd = tableRPC[strMethod]; - if (!pcmd) - throw JSONRPCError(-32601, "Method not found"); + string strReply; - // Observe safe mode - string strWarning = GetWarnings("rpc"); - if (strWarning != "" && !GetBoolArg("-disablesafemode") && - !pcmd->okSafeMode) - throw JSONRPCError(-2, string("Safe mode: ") + strWarning); + // singleton request + if (valRequest.type() == obj_type) { + jreq.parse(valRequest); - try - { - // Execute - Value result; - { - LOCK2(cs_main, pwalletMain->cs_wallet); - result = pcmd->actor(params, false); - } + Value result = tableRPC.execute(jreq.strMethod, jreq.params); // Send reply - string strReply = JSONRPCReply(result, Value::null, id); - stream << HTTPReply(200, strReply) << std::flush; - } - catch (std::exception& e) - { - ErrorReply(stream, JSONRPCError(-1, e.what()), id); - } + strReply = JSONRPCReply(result, Value::null, jreq.id); + + // array of requests + } else if (valRequest.type() == array_type) + strReply = JSONRPCExecBatch(valRequest.get_array()); + else + throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); + + conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush; } catch (Object& objError) { - ErrorReply(stream, objError, id); + ErrorReply(conn->stream(), objError, jreq.id); + break; } catch (std::exception& e) { - ErrorReply(stream, JSONRPCError(-32700, e.what()), id); + ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + break; } } + + delete conn; + { + LOCK(cs_THREAD_RPCHANDLER); + vnThreadsRunning[THREAD_RPCHANDLER]--; + } } +json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_spirit::Array ¶ms) const +{ + // Find method + const CRPCCommand *pcmd = tableRPC[strMethod]; + if (!pcmd) + throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); + + // Observe safe mode + string strWarning = GetWarnings("rpc"); + if (strWarning != "" && !GetBoolArg("-disablesafemode") && + !pcmd->okSafeMode) + throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, string("Safe mode: ") + strWarning); + try + { + // Execute + Value result; + { + if (pcmd->unlocked) + result = pcmd->actor(params, false); + else { + LOCK2(cs_main, pwalletMain->cs_wallet); + result = pcmd->actor(params, false); + } + } + return result; + } + catch (std::exception& e) + { + throw JSONRPCError(RPC_MISC_ERROR, e.what()); + } +} Object CallRPC(const string& strMethod, const Array& params) @@ -3359,10 +1065,10 @@ Object CallRPC(const string& strMethod, const Array& params) asio::io_service io_service; ssl::context context(io_service, ssl::context::sslv23); context.set_options(ssl::context::no_sslv2); - SSLStream sslStream(io_service, context); - SSLIOStreamDevice d(sslStream, fUseSSL); - iostreams::stream stream(d); - if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", CBigNum(RPC_PORT).ToString().c_str()))) + asio::ssl::stream sslStream(io_service, context); + SSLIOStreamDevice d(sslStream, fUseSSL); + iostreams::stream< SSLIOStreamDevice > stream(d); + if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(GetDefaultRPCPort())))) throw runtime_error("couldn't connect to server"); // HTTP basic authentication @@ -3379,9 +1085,9 @@ Object CallRPC(const string& strMethod, const Array& params) map mapHeaders; string strReply; int nStatus = ReadHTTP(stream, mapHeaders, strReply); - if (nStatus == 401) + if (nStatus == HTTP_UNAUTHORIZED) throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); - else if (nStatus >= 400 && nStatus != 400 && nStatus != 404 && nStatus != 500) + 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()) throw runtime_error("no response from server"); @@ -3401,15 +1107,19 @@ Object CallRPC(const string& strMethod, const Array& params) template -void ConvertTo(Value& value) +void ConvertTo(Value& value, bool fAllowNull=false) { + if (fAllowNull && value.type() == null_type) + return; if (value.type() == str_type) { // reinterpret string as unquoted json value Value value2; - if (!read_string(value.get_str(), value2)) - throw runtime_error("type mismatch"); - value = value2.get_value(); + string strJSON = value.get_str(); + if (!read_string(strJSON, value2)) + throw runtime_error(string("Error parsing JSON:")+strJSON); + ConvertTo(value2, fAllowNull); + value = value2; } else { @@ -3417,6 +1127,63 @@ void ConvertTo(Value& value) } } +// Convert strings to command-specific RPC representation +Array RPCConvertValues(const std::string &strMethod, const std::vector &strParams) +{ + Array params; + BOOST_FOREACH(const std::string ¶m, strParams) + params.push_back(param); + + int n = params.size(); + + // + // Special case non-string parameter types + // + if (strMethod == "stop" && n > 0) ConvertTo(params[0]); + if (strMethod == "setgenerate" && n > 0) ConvertTo(params[0]); + if (strMethod == "setgenerate" && n > 1) ConvertTo(params[1]); + if (strMethod == "sendtoaddress" && n > 1) ConvertTo(params[1]); + if (strMethod == "settxfee" && n > 0) ConvertTo(params[0]); + if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo(params[1]); + if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo(params[1]); + if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo(params[0]); + if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo(params[1]); + if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo(params[0]); + if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo(params[1]); + if (strMethod == "getbalance" && n > 1) ConvertTo(params[1]); + if (strMethod == "getblock" && n > 1) ConvertTo(params[1]); + if (strMethod == "getblockbynumber" && n > 0) ConvertTo(params[0]); + if (strMethod == "getblockbynumber" && n > 1) ConvertTo(params[1]); + if (strMethod == "getblockhash" && n > 0) ConvertTo(params[0]); + if (strMethod == "move" && n > 2) ConvertTo(params[2]); + if (strMethod == "move" && n > 3) ConvertTo(params[3]); + if (strMethod == "sendfrom" && n > 2) ConvertTo(params[2]); + if (strMethod == "sendfrom" && n > 3) ConvertTo(params[3]); + if (strMethod == "listtransactions" && n > 1) ConvertTo(params[1]); + if (strMethod == "listtransactions" && n > 2) ConvertTo(params[2]); + if (strMethod == "listaccounts" && n > 0) ConvertTo(params[0]); + if (strMethod == "walletpassphrase" && n > 1) ConvertTo(params[1]); + if (strMethod == "walletpassphrase" && n > 2) ConvertTo(params[2]); + if (strMethod == "getblocktemplate" && n > 0) ConvertTo(params[0]); + if (strMethod == "listsinceblock" && n > 1) ConvertTo(params[1]); + if (strMethod == "sendmany" && n > 1) ConvertTo(params[1]); + if (strMethod == "sendmany" && n > 2) ConvertTo(params[2]); + if (strMethod == "reservebalance" && n > 0) ConvertTo(params[0]); + if (strMethod == "reservebalance" && n > 1) ConvertTo(params[1]); + if (strMethod == "addmultisigaddress" && n > 0) ConvertTo(params[0]); + if (strMethod == "addmultisigaddress" && n > 1) ConvertTo(params[1]); + if (strMethod == "listunspent" && n > 0) ConvertTo(params[0]); + if (strMethod == "listunspent" && n > 1) ConvertTo(params[1]); + if (strMethod == "listunspent" && n > 2) ConvertTo(params[2]); + if (strMethod == "getrawtransaction" && n > 1) ConvertTo(params[1]); + if (strMethod == "createrawtransaction" && n > 0) ConvertTo(params[0]); + if (strMethod == "createrawtransaction" && n > 1) ConvertTo(params[1]); + if (strMethod == "signrawtransaction" && n > 1) ConvertTo(params[1], true); + if (strMethod == "signrawtransaction" && n > 2) ConvertTo(params[2], true); + + return params; +} + int CommandLineRPC(int argc, char *argv[]) { string strPrint; @@ -3436,64 +1203,8 @@ int CommandLineRPC(int argc, char *argv[]) string strMethod = argv[1]; // Parameters default to strings - Array params; - for (int i = 2; i < argc; i++) - params.push_back(argv[i]); - int n = params.size(); - - // - // Special case non-string parameter types - // - if (strMethod == "setgenerate" && n > 0) ConvertTo(params[0]); - if (strMethod == "setgenerate" && n > 1) ConvertTo(params[1]); - if (strMethod == "sendtoaddress" && n > 1) ConvertTo(params[1]); - if (strMethod == "settxfee" && n > 0) ConvertTo(params[0]); - if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo(params[1]); - if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo(params[1]); - if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo(params[0]); - if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo(params[1]); - if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo(params[0]); - if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo(params[1]); - if (strMethod == "getbalance" && n > 1) ConvertTo(params[1]); - if (strMethod == "getblockhash" && n > 0) ConvertTo(params[0]); - if (strMethod == "getblockbynumber" && n > 0) ConvertTo(params[0]); - if (strMethod == "getblockbynumber" && n > 1) ConvertTo(params[1]); - if (strMethod == "getblock" && n > 1) ConvertTo(params[1]); - if (strMethod == "move" && n > 2) ConvertTo(params[2]); - if (strMethod == "move" && n > 3) ConvertTo(params[3]); - if (strMethod == "sendfrom" && n > 2) ConvertTo(params[2]); - if (strMethod == "sendfrom" && n > 3) ConvertTo(params[3]); - if (strMethod == "listtransactions" && n > 1) ConvertTo(params[1]); - if (strMethod == "listtransactions" && n > 2) ConvertTo(params[2]); - if (strMethod == "listaccounts" && n > 0) ConvertTo(params[0]); - if (strMethod == "walletpassphrase" && n > 1) ConvertTo(params[1]); - if (strMethod == "walletpassphrase" && n > 2) ConvertTo(params[2]); - if (strMethod == "listsinceblock" && n > 1) ConvertTo(params[1]); - if (strMethod == "sendalert" && n > 2) ConvertTo(params[2]); - if (strMethod == "sendalert" && n > 3) ConvertTo(params[3]); - if (strMethod == "sendalert" && n > 4) ConvertTo(params[4]); - if (strMethod == "sendalert" && n > 5) ConvertTo(params[5]); - if (strMethod == "sendalert" && n > 6) ConvertTo(params[6]); - if (strMethod == "sendmany" && n > 1) - { - string s = params[1].get_str(); - Value v; - if (!read_string(s, v) || v.type() != obj_type) - throw runtime_error("type mismatch"); - params[1] = v.get_obj(); - } - if (strMethod == "sendmany" && n > 2) ConvertTo(params[2]); - if (strMethod == "reservebalance" && n > 0) ConvertTo(params[0]); - if (strMethod == "reservebalance" && n > 1) ConvertTo(params[1]); - if (strMethod == "addmultisigaddress" && n > 0) ConvertTo(params[0]); - if (strMethod == "addmultisigaddress" && n > 1) - { - string s = params[1].get_str(); - Value v; - if (!read_string(s, v) || v.type() != array_type) - throw runtime_error("type mismatch "+s); - params[1] = v.get_array(); - } + std::vector strParams(&argv[2], &argv[argc]); + Array params = RPCConvertValues(strMethod, strParams); // Execute Object reply = CallRPC(strMethod, params); @@ -3544,7 +1255,7 @@ int CommandLineRPC(int argc, char *argv[]) int main(int argc, char *argv[]) { #ifdef _MSC_VER - // Turn off microsoft heap dump noise + // Turn off Microsoft heap dump noise _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_WARN, CreateFile("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0)); #endif diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index f821686..8327ad8 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -7,15 +7,87 @@ #define _BITCOINRPC_H_ 1 #include +#include #include +class CBlockIndex; + #include "json/json_spirit_reader_template.h" #include "json/json_spirit_writer_template.h" #include "json/json_spirit_utils.h" +#include "util.h" +#include "checkpoints.h" + +// HTTP status codes +enum HTTPStatusCode +{ + HTTP_OK = 200, + HTTP_BAD_REQUEST = 400, + HTTP_UNAUTHORIZED = 401, + HTTP_FORBIDDEN = 403, + HTTP_NOT_FOUND = 404, + HTTP_INTERNAL_SERVER_ERROR = 500, +}; + +// Bitcoin RPC error codes +enum RPCErrorCode +{ + // Standard JSON-RPC 2.0 errors + RPC_INVALID_REQUEST = -32600, + RPC_METHOD_NOT_FOUND = -32601, + RPC_INVALID_PARAMS = -32602, + RPC_INTERNAL_ERROR = -32603, + RPC_PARSE_ERROR = -32700, + + // General application defined errors + RPC_MISC_ERROR = -1, // std::exception thrown in command handling + RPC_FORBIDDEN_BY_SAFE_MODE = -2, // Server is in safe mode, and command is not allowed in safe mode + RPC_TYPE_ERROR = -3, // Unexpected type was passed as parameter + RPC_INVALID_ADDRESS_OR_KEY = -5, // Invalid address or key + RPC_OUT_OF_MEMORY = -7, // Ran out of memory during operation + RPC_INVALID_PARAMETER = -8, // Invalid, missing or duplicate parameter + RPC_DATABASE_ERROR = -20, // Database error + RPC_DESERIALIZATION_ERROR = -22, // Error parsing or validating structure in raw format + + // P2P client errors + RPC_CLIENT_NOT_CONNECTED = -9, // Bitcoin is not connected + RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10, // Still downloading initial blocks + + // Wallet errors + RPC_WALLET_ERROR = -4, // Unspecified problem with wallet (key not found etc.) + RPC_WALLET_INSUFFICIENT_FUNDS = -6, // Not enough funds in wallet or account + RPC_WALLET_INVALID_ACCOUNT_NAME = -11, // Invalid account name + RPC_WALLET_KEYPOOL_RAN_OUT = -12, // Keypool ran out, call keypoolrefill first + RPC_WALLET_UNLOCK_NEEDED = -13, // Enter the wallet passphrase with walletpassphrase first + RPC_WALLET_PASSPHRASE_INCORRECT = -14, // The wallet passphrase entered was incorrect + RPC_WALLET_WRONG_ENC_STATE = -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) + RPC_WALLET_ENCRYPTION_FAILED = -16, // Failed to encrypt the wallet + RPC_WALLET_ALREADY_UNLOCKED = -17, // Wallet is already unlocked +}; + +json_spirit::Object JSONRPCError(int code, const std::string& message); + void ThreadRPCServer(void* parg); int CommandLineRPC(int argc, char *argv[]); +/** Convert parameter values for RPC call from strings to command-specific JSON objects. */ +json_spirit::Array RPCConvertValues(const std::string &strMethod, const std::vector &strParams); + +/* + Type-check arguments; throws JSONRPCError if wrong type given. Does not check that + the right number of arguments are passed, just that any passed are the correct type. + Use like: RPCTypeCheck(params, boost::assign::list_of(str_type)(int_type)(obj_type)); +*/ +void RPCTypeCheck(const json_spirit::Array& params, + const std::list& typesExpected, bool fAllowNull=false); +/* + Check for expected keys/value types in an Object. + Use like: RPCTypeCheck(object, boost::assign::map_list_of("name", str_type)("value", int_type)); +*/ +void RPCTypeCheck(const json_spirit::Object& o, + const std::map& typesExpected, bool fAllowNull=false); + typedef json_spirit::Value(*rpcfn_type)(const json_spirit::Array& params, bool fHelp); class CRPCCommand @@ -24,8 +96,12 @@ public: std::string name; rpcfn_type actor; bool okSafeMode; + bool unlocked; }; +/** + * Bitcoin RPC command dispatcher. + */ class CRPCTable { private: @@ -34,8 +110,92 @@ public: CRPCTable(); const CRPCCommand* operator[](std::string name) const; std::string help(std::string name) const; + + /** + * Execute a method. + * @param method Method to execute + * @param params Array of arguments (JSON objects) + * @returns Result of the call. + * @throws an exception (json_spirit::Value) when an error happens. + */ + json_spirit::Value execute(const std::string &method, const json_spirit::Array ¶ms) const; }; extern const CRPCTable tableRPC; +extern int64 nWalletUnlockTime; +extern int64 AmountFromValue(const json_spirit::Value& value); +extern json_spirit::Value ValueFromAmount(int64 amount); +extern double GetDifficulty(const CBlockIndex* blockindex = NULL); +extern std::string HexBits(unsigned int nBits); +extern std::string HelpRequiringPassphrase(); +extern void EnsureWalletIsUnlocked(); + + +extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, bool fHelp); // in rpcnet.cpp +extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp +extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendalert(const json_spirit::Array& params, bool fHelp); + +extern json_spirit::Value getgenerate(const json_spirit::Array& params, bool fHelp); // in rpcmining.cpp +extern json_spirit::Value setgenerate(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value gethashespersec(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getmininginfo(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getwork(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getblocktemplate(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value submitblock(const json_spirit::Array& params, bool fHelp); + +extern json_spirit::Value getnewaddress(const json_spirit::Array& params, bool fHelp); // in rpcwallet.cpp +extern json_spirit::Value getaccountaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value setaccount(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getaccount(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getaddressesbyaccount(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendtoaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value signmessage(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value verifymessage(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getreceivedbyaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getreceivedbyaccount(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getbalance(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value movecmd(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendfrom(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendmany(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value addmultisigaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listreceivedbyaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listreceivedbyaccount(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listtransactions(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listaddressgroupings(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listaccounts(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listsinceblock(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value gettransaction(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value backupwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value keypoolrefill(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value walletpassphrase(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value walletpassphrasechange(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value walletlock(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value encryptwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value validateaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getinfo(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value reservebalance(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value checkwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value repairwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value resendtx(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value makekeypair(const json_spirit::Array& params, bool fHelp); + +extern json_spirit::Value getrawtransaction(const json_spirit::Array& params, bool fHelp); // in rcprawtransaction.cpp +extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value createrawtransaction(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value decoderawtransaction(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value signrawtransaction(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendrawtransaction(const json_spirit::Array& params, bool fHelp); + +extern json_spirit::Value getblockcount(const json_spirit::Array& params, bool fHelp); // in rpcblockchain.cpp +extern json_spirit::Value getdifficulty(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value settxfee(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getrawmempool(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getblockhash(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getblock(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getblockbynumber(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool fHelp); + #endif diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index ac884ec..6256fb4 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -1,6 +1,4 @@ // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers -// Copyright (c) 2013 NovaCoin Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -15,7 +13,7 @@ namespace Checkpoints { - typedef std::map MapCheckpoints; // hardened checkpoints + typedef std::map MapCheckpoints; // // What makes a good checkpoint block? @@ -33,32 +31,32 @@ namespace Checkpoints ( 14189, uint256("0x00000000020f76474d2522b19c7bfafc43ba6ecbabae54293bcd9546159c8c1d")) ; + static MapCheckpoints mapCheckpointsTestnet = + boost::assign::map_list_of + ( 0, hashGenesisBlockOfficial ) + ; + bool CheckHardened(int nHeight, const uint256& hash) { - if (fTestNet) return true; // Testnet has no checkpoints + MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints); - MapCheckpoints::const_iterator i = mapCheckpoints.find(nHeight); - if (i == mapCheckpoints.end()) return true; + MapCheckpoints::const_iterator i = checkpoints.find(nHeight); + if (i == checkpoints.end()) return true; return hash == i->second; } int GetTotalBlocksEstimate() { - if (fTestNet) return 0; + MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints); - return mapCheckpoints.rbegin()->first; + return checkpoints.rbegin()->first; } CBlockIndex* GetLastCheckpoint(const std::map& mapBlockIndex) { - if (fTestNet) { - std::map::const_iterator t = mapBlockIndex.find(hashGenesisBlock); - if (t != mapBlockIndex.end()) - return t->second; - return NULL; - } + MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints); - BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, mapCheckpoints) + BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, checkpoints) { const uint256& hash = i.second; std::map::const_iterator t = mapBlockIndex.find(hash); diff --git a/src/checkpoints.h b/src/checkpoints.h index 0e08b30..4f5db11 100644 --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -1,5 +1,4 @@ // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_CHECKPOINT_H diff --git a/src/clientversion.h b/src/clientversion.h new file mode 100644 index 0000000..5ada4d8 --- /dev/null +++ b/src/clientversion.h @@ -0,0 +1,19 @@ +#ifndef CLIENTVERSION_H +#define CLIENTVERSION_H + +// +// client versioning +// + +// These need to be macros, as version.cpp's and bitcoin-qt.rc's voodoo requires it +#define CLIENT_VERSION_MAJOR 0 +#define CLIENT_VERSION_MINOR 7 +#define CLIENT_VERSION_REVISION 2 +#define CLIENT_VERSION_BUILD 0 + +// Converts the parameter X to a string after macro replacement on X has been performed. +// Don't merge these into one macro! +#define STRINGIZE(X) DO_STRINGIZE(X) +#define DO_STRINGIZE(X) #X + +#endif // CLIENTVERSION_H diff --git a/src/crypter.cpp b/src/crypter.cpp index 4f37575..181b8fa 100644 --- a/src/crypter.cpp +++ b/src/crypter.cpp @@ -17,12 +17,6 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE) return false; - // Try to keep the keydata out of swap (and be a bit over-careful to keep the IV that we don't even use out of swap) - // Note that this does nothing about suspend-to-disk (which will put all our key data on disk) - // Note as well that at no point in this program is any attempt made to prevent stealing of keys by reading the memory of the running process. - mlock(&chKey[0], sizeof chKey); - mlock(&chIV[0], sizeof chIV); - int i = 0; if (nDerivationMethod == 0) i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0], @@ -44,12 +38,6 @@ bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector mapFileUseCount; -static map mapDb; +CDBEnv bitdb; -static void EnvShutdown() +void CDBEnv::EnvShutdown() { if (!fDbEnvInit) return; fDbEnvInit = false; - try - { - dbenv.close(0); - } - catch (const DbException& e) - { - printf("EnvShutdown exception: %s (%d)\n", e.what(), e.get_errno()); - } - DbEnv(0).remove(GetDataDir().string().c_str(), 0); + int ret = dbenv.close(0); + if (ret != 0) + printf("EnvShutdown exception: %s (%d)\n", DbEnv::strerror(ret), ret); + if (!fMockDb) + DbEnv(0).remove(strPath.c_str(), 0); +} + +CDBEnv::CDBEnv() : dbenv(DB_CXX_NO_EXCEPTIONS) +{ + fDbEnvInit = false; + fMockDb = false; } -class CDBInit +CDBEnv::~CDBEnv() { -public: - CDBInit() + EnvShutdown(); +} + +void CDBEnv::Close() +{ + EnvShutdown(); +} + +bool CDBEnv::Open(boost::filesystem::path pathEnv_) +{ + if (fDbEnvInit) + return true; + + if (fShutdown) + return false; + + pathEnv = pathEnv_; + filesystem::path pathDataDir = pathEnv; + strPath = pathDataDir.string(); + filesystem::path pathLogDir = pathDataDir / "database"; + filesystem::create_directory(pathLogDir); + filesystem::path pathErrorFile = pathDataDir / "db.log"; + printf("dbenv.open LogDir=%s ErrorFile=%s\n", pathLogDir.string().c_str(), pathErrorFile.string().c_str()); + + unsigned int nEnvFlags = 0; + if (GetBoolArg("-privdb", true)) + nEnvFlags |= DB_PRIVATE; + + int nDbCache = GetArg("-dbcache", 25); + dbenv.set_lg_dir(pathLogDir.string().c_str()); + dbenv.set_cachesize(nDbCache / 1024, (nDbCache % 1024)*1048576, 1); + dbenv.set_lg_bsize(1048576); + dbenv.set_lg_max(10485760); + dbenv.set_lk_max_locks(10000); + dbenv.set_lk_max_objects(10000); + dbenv.set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug + dbenv.set_flags(DB_AUTO_COMMIT, 1); + dbenv.set_flags(DB_TXN_WRITE_NOSYNC, 1); +// dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); + int ret = dbenv.open(strPath.c_str(), + DB_CREATE | + DB_INIT_LOCK | + DB_INIT_LOG | + DB_INIT_MPOOL | + DB_INIT_TXN | + DB_THREAD | + DB_RECOVER | + nEnvFlags, + S_IRUSR | S_IWUSR); + if (ret != 0) + return error("CDB() : error %s (%d) opening database environment", DbEnv::strerror(ret), ret); + + fDbEnvInit = true; + fMockDb = false; + return true; +} + +void CDBEnv::MakeMock() +{ + if (fDbEnvInit) + throw runtime_error("CDBEnv::MakeMock(): already initialized"); + + if (fShutdown) + throw runtime_error("CDBEnv::MakeMock(): during shutdown"); + + printf("CDBEnv::MakeMock()\n"); + + dbenv.set_cachesize(1, 0, 1); + dbenv.set_lg_bsize(10485760*4); + dbenv.set_lg_max(10485760); + dbenv.set_lk_max_locks(10000); + dbenv.set_lk_max_objects(10000); + dbenv.set_flags(DB_AUTO_COMMIT, 1); +// dbenv.log_set_config(DB_LOG_IN_MEMORY, 1); + int ret = dbenv.open(NULL, + DB_CREATE | + DB_INIT_LOCK | + DB_INIT_LOG | + DB_INIT_MPOOL | + DB_INIT_TXN | + DB_THREAD | + DB_PRIVATE, + S_IRUSR | S_IWUSR); + if (ret > 0) + throw runtime_error(strprintf("CDBEnv::MakeMock(): error %d opening database environment", ret)); + + fDbEnvInit = true; + fMockDb = true; +} + +CDBEnv::VerifyResult CDBEnv::Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile)) +{ + LOCK(cs_db); + assert(mapFileUseCount.count(strFile) == 0); + + Db db(&dbenv, 0); + int result = db.verify(strFile.c_str(), NULL, NULL, 0); + if (result == 0) + return VERIFY_OK; + else if (recoverFunc == NULL) + return RECOVER_FAIL; + + // Try to recover: + bool fRecovered = (*recoverFunc)(*this, strFile); + return (fRecovered ? RECOVER_OK : RECOVER_FAIL); +} + +bool CDBEnv::Salvage(std::string strFile, bool fAggressive, + std::vector& vResult) +{ + LOCK(cs_db); + assert(mapFileUseCount.count(strFile) == 0); + + u_int32_t flags = DB_SALVAGE; + if (fAggressive) flags |= DB_AGGRESSIVE; + + stringstream strDump; + + Db db(&dbenv, 0); + int result = db.verify(strFile.c_str(), NULL, &strDump, flags); + if (result != 0) { + printf("ERROR: db salvage failed\n"); + return false; } - ~CDBInit() + + // Format of bdb dump is ascii lines: + // header lines... + // HEADER=END + // hexadecimal key + // hexadecimal value + // ... repeated + // DATA=END + + string strLine; + while (!strDump.eof() && strLine != "HEADER=END") + getline(strDump, strLine); // Skip past header + + std::string keyHex, valueHex; + while (!strDump.eof() && keyHex != "DATA=END") { - EnvShutdown(); + getline(strDump, keyHex); + if (keyHex != "DATA_END") + { + getline(strDump, valueHex); + vResult.push_back(make_pair(ParseHex(keyHex),ParseHex(valueHex))); + } } + + return (result == 0); +} + + +void CDBEnv::CheckpointLSN(std::string strFile) +{ + dbenv.txn_checkpoint(0, 0, 0); + if (fMockDb) + return; + dbenv.lsn_reset(strFile.c_str(), 0); } -instance_of_cdbinit; -CDB::CDB(const char *pszFile, const char* pszMode) : pdb(NULL) +CDB::CDB(const char *pszFile, const char* pszMode) : + pdb(NULL), activeTxn(NULL) { int ret; if (pszFile == NULL) @@ -81,64 +228,38 @@ CDB::CDB(const char *pszFile, const char* pszMode) : pdb(NULL) nFlags |= DB_CREATE; { - LOCK(cs_db); - if (!fDbEnvInit) - { - if (fShutdown) - return; - filesystem::path pathDataDir = GetDataDir(); - filesystem::path pathLogDir = pathDataDir / "database"; - filesystem::create_directory(pathLogDir); - filesystem::path pathErrorFile = pathDataDir / "db.log"; - printf("dbenv.open LogDir=%s ErrorFile=%s\n", pathLogDir.string().c_str(), pathErrorFile.string().c_str()); - - int nDbCache = GetArg("-dbcache", 25); - dbenv.set_lg_dir(pathLogDir.string().c_str()); - dbenv.set_cachesize(nDbCache / 1024, (nDbCache % 1024)*1048576, 1); - dbenv.set_lg_bsize(1048576); - dbenv.set_lg_max(10485760); - dbenv.set_lk_max_locks(10000); - dbenv.set_lk_max_objects(10000); - dbenv.set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug - dbenv.set_flags(DB_TXN_WRITE_NOSYNC, 1); - dbenv.set_flags(DB_AUTO_COMMIT, 1); - dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); - ret = dbenv.open(pathDataDir.string().c_str(), - DB_CREATE | - DB_INIT_LOCK | - DB_INIT_LOG | - DB_INIT_MPOOL | - DB_INIT_TXN | - DB_THREAD | - DB_RECOVER, - S_IRUSR | S_IWUSR); - if (ret > 0) - throw runtime_error(strprintf("CDB() : error %d opening database environment", ret)); - fDbEnvInit = true; - } + LOCK(bitdb.cs_db); + if (!bitdb.Open(GetDataDir())) + throw runtime_error("env open failed"); strFile = pszFile; - ++mapFileUseCount[strFile]; - pdb = mapDb[strFile]; + ++bitdb.mapFileUseCount[strFile]; + pdb = bitdb.mapDb[strFile]; if (pdb == NULL) { - pdb = new Db(&dbenv, 0); + pdb = new Db(&bitdb.dbenv, 0); + + bool fMockDb = bitdb.IsMock(); + if (fMockDb) + { + DbMpoolFile*mpf = pdb->get_mpf(); + ret = mpf->set_flags(DB_MPOOL_NOFILE, 1); + if (ret != 0) + throw runtime_error(strprintf("CDB() : failed to configure for no temp file backing for database %s", pszFile)); + } ret = pdb->open(NULL, // Txn pointer - pszFile, // Filename + fMockDb ? NULL : pszFile, // Filename "main", // Logical db name DB_BTREE, // Database type nFlags, // Flags 0); - if (ret > 0) + if (ret != 0) { delete pdb; pdb = NULL; - { - LOCK(cs_db); - --mapFileUseCount[strFile]; - } + --bitdb.mapFileUseCount[strFile]; strFile = ""; throw runtime_error(strprintf("CDB() : can't open database file %s, error %d", pszFile, ret)); } @@ -151,40 +272,46 @@ CDB::CDB(const char *pszFile, const char* pszMode) : pdb(NULL) fReadOnly = fTmp; } - mapDb[strFile] = pdb; + bitdb.mapDb[strFile] = pdb; } } } +static bool IsChainFile(std::string strFile) +{ + if (strFile == "blkindex.dat") + return true; + + return false; +} + void CDB::Close() { if (!pdb) return; - if (!vTxn.empty()) - vTxn.front()->abort(); - vTxn.clear(); + if (activeTxn) + activeTxn->abort(); + activeTxn = NULL; pdb = NULL; // Flush database activity from memory pool to disk log unsigned int nMinutes = 0; if (fReadOnly) nMinutes = 1; - if (strFile == "addr.dat") + if (IsChainFile(strFile)) nMinutes = 2; - if (strFile == "blkindex.dat") - nMinutes = 2; - if (strFile == "blkindex.dat" && IsInitialBlockDownload()) + if (IsChainFile(strFile) && IsInitialBlockDownload()) nMinutes = 5; - dbenv.txn_checkpoint(nMinutes ? GetArg("-dblogsize", 100)*1024 : 0, nMinutes, 0); + bitdb.dbenv.txn_checkpoint(nMinutes ? GetArg("-dblogsize", 100)*1024 : 0, nMinutes, 0); { - LOCK(cs_db); - --mapFileUseCount[strFile]; + LOCK(bitdb.cs_db); + --bitdb.mapFileUseCount[strFile]; } } -void CloseDb(const string& strFile) +void CDBEnv::CloseDb(const string& strFile) { { LOCK(cs_db); @@ -199,27 +326,35 @@ void CloseDb(const string& strFile) } } +bool CDBEnv::RemoveDb(const string& strFile) +{ + this->CloseDb(strFile); + + LOCK(cs_db); + int rc = dbenv.dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT); + return (rc == 0); +} + bool CDB::Rewrite(const string& strFile, const char* pszSkip) { while (!fShutdown) { { - LOCK(cs_db); - if (!mapFileUseCount.count(strFile) || mapFileUseCount[strFile] == 0) + LOCK(bitdb.cs_db); + if (!bitdb.mapFileUseCount.count(strFile) || bitdb.mapFileUseCount[strFile] == 0) { // Flush log data to the dat file - CloseDb(strFile); - dbenv.txn_checkpoint(0, 0, 0); - dbenv.lsn_reset(strFile.c_str(), 0); - mapFileUseCount.erase(strFile); + bitdb.CloseDb(strFile); + bitdb.CheckpointLSN(strFile); + bitdb.mapFileUseCount.erase(strFile); bool fSuccess = true; printf("Rewriting %s...\n", strFile.c_str()); string strFileRes = strFile + ".rewrite"; { // surround usage of db with extra {} CDB db(strFile.c_str(), "r"); - Db* pdbCopy = new Db(&dbenv, 0); - + Db* pdbCopy = new Db(&bitdb.dbenv, 0); + int ret = pdbCopy->open(NULL, // Txn pointer strFileRes.c_str(), // Filename "main", // Logical db name @@ -231,7 +366,7 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip) printf("Cannot create database file %s\n", strFileRes.c_str()); fSuccess = false; } - + Dbc* pcursor = db.GetCursor(); if (pcursor) while (fSuccess) @@ -268,7 +403,7 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip) if (fSuccess) { db.Close(); - CloseDb(strFile); + bitdb.CloseDb(strFile); if (pdbCopy->close(0)) fSuccess = false; delete pdbCopy; @@ -276,10 +411,10 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip) } if (fSuccess) { - Db dbA(&dbenv, 0); + Db dbA(&bitdb.dbenv, 0); if (dbA.remove(strFile.c_str(), NULL, 0)) fSuccess = false; - Db dbB(&dbenv, 0); + Db dbB(&bitdb.dbenv, 0); if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0)) fSuccess = false; } @@ -294,11 +429,12 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip) } -void DBFlush(bool fShutdown) +void CDBEnv::Flush(bool fShutdown) { + int64 nStart = GetTimeMillis(); // Flush log data to the actual data file // on all files that are not in use - printf("DBFlush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started"); + printf("Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started"); if (!fDbEnvInit) return; { @@ -315,9 +451,10 @@ void DBFlush(bool fShutdown) CloseDb(strFile); printf("%s checkpoint\n", strFile.c_str()); dbenv.txn_checkpoint(0, 0, 0); - if ((strFile != "blkindex.dat" && strFile != "addr.dat") || fDetachDB) { + if (!IsChainFile(strFile) || fDetachDB) { printf("%s detach\n", strFile.c_str()); - dbenv.lsn_reset(strFile.c_str(), 0); + if (!fMockDb) + dbenv.lsn_reset(strFile.c_str(), 0); } printf("%s closed\n", strFile.c_str()); mapFileUseCount.erase(mi++); @@ -325,13 +462,14 @@ void DBFlush(bool fShutdown) else mi++; } + printf("DBFlush(%s)%s ended %15"PRI64d"ms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started", GetTimeMillis() - nStart); if (fShutdown) { char** listp; if (mapFileUseCount.empty()) { dbenv.log_archive(&listp, DB_ARCH_REMOVE); - EnvShutdown(); + Close(); } } } @@ -383,66 +521,6 @@ bool CTxDB::ContainsTx(uint256 hash) return Exists(make_pair(string("tx"), hash)); } -bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector& vtx) -{ - assert(!fClient); - vtx.clear(); - - // Get cursor - Dbc* pcursor = GetCursor(); - if (!pcursor) - return false; - - unsigned int fFlags = DB_SET_RANGE; - loop - { - // Read next record - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - if (fFlags == DB_SET_RANGE) - ssKey << string("owner") << hash160 << CDiskTxPos(0, 0, 0); - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); - fFlags = DB_NEXT; - if (ret == DB_NOTFOUND) - break; - else if (ret != 0) - { - pcursor->close(); - return false; - } - - // Unserialize - string strType; - uint160 hashItem; - CDiskTxPos pos; - int nItemHeight; - - try { - ssKey >> strType >> hashItem >> pos; - ssValue >> nItemHeight; - } - catch (std::exception &e) { - return error("%s() : deserialize error", __PRETTY_FUNCTION__); - } - - // Read transaction - if (strType != "owner" || hashItem != hash160) - break; - if (nItemHeight >= nMinHeight) - { - vtx.resize(vtx.size()+1); - if (!vtx.back().ReadFromDisk(pos)) - { - pcursor->close(); - return false; - } - } - } - - pcursor->close(); - return true; -} - bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex) { assert(!fClient); @@ -474,11 +552,6 @@ bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex) return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex); } -bool CTxDB::EraseBlockIndex(uint256 hash) -{ - return Erase(make_pair(string("blockindex"), hash)); -} - bool CTxDB::ReadHashBestChain(uint256& hashBestChain) { return Read(string("hashBestChain"), hashBestChain); @@ -541,79 +614,9 @@ CBlockIndex static * InsertBlockIndex(uint256 hash) bool CTxDB::LoadBlockIndex() { - // Get database cursor - Dbc* pcursor = GetCursor(); - if (!pcursor) + if (!LoadBlockIndexGuts()) return false; - // Load mapBlockIndex - unsigned int fFlags = DB_SET_RANGE; - loop - { - // Read next record - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - if (fFlags == DB_SET_RANGE) - ssKey << make_pair(string("blockindex"), uint256(0)); - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); - fFlags = DB_NEXT; - if (ret == DB_NOTFOUND) - break; - else if (ret != 0) - return false; - - // Unserialize - - try { - string strType; - ssKey >> strType; - if (strType == "blockindex" && !fRequestShutdown) - { - CDiskBlockIndex diskindex; - ssValue >> diskindex; - - // Construct block index object - CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash()); - pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev); - pindexNew->pnext = InsertBlockIndex(diskindex.hashNext); - pindexNew->nFile = diskindex.nFile; - pindexNew->nBlockPos = diskindex.nBlockPos; - pindexNew->nHeight = diskindex.nHeight; - pindexNew->nMint = diskindex.nMint; - pindexNew->nMoneySupply = diskindex.nMoneySupply; - pindexNew->nFlags = diskindex.nFlags; - pindexNew->nStakeModifier = diskindex.nStakeModifier; - pindexNew->prevoutStake = diskindex.prevoutStake; - pindexNew->nStakeTime = diskindex.nStakeTime; - pindexNew->hashProofOfStake = diskindex.hashProofOfStake; - pindexNew->nVersion = diskindex.nVersion; - pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; - pindexNew->nTime = diskindex.nTime; - pindexNew->nBits = diskindex.nBits; - pindexNew->nNonce = diskindex.nNonce; - - // Watch for genesis block - if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == hashGenesisBlock) - pindexGenesisBlock = pindexNew; - - if (!pindexNew->CheckIndex()) - return error("LoadBlockIndex() : CheckIndex failed at %d", pindexNew->nHeight); - - // ppcoin: build setStakeSeen - if (pindexNew->IsProofOfStake()) - setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); - } - else - { - break; // if shutdown requested or finished loading block index - } - } // try - catch (std::exception &e) { - return error("%s() : deserialize error", __PRETTY_FUNCTION__); - } - } - pcursor->close(); - if (fRequestShutdown) return true; @@ -648,7 +651,9 @@ bool CTxDB::LoadBlockIndex() pindexBest = mapBlockIndex[hashBestChain]; nBestHeight = pindexBest->nHeight; bnBestChainTrust = pindexBest->bnChainTrust; - printf("LoadBlockIndex(): hashBestChain=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainTrust.ToString().c_str()); + printf("LoadBlockIndex(): hashBestChain=%s height=%d trust=%s date=%s\n", + hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainTrust.ToString().c_str(), + DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); // ppcoin: load hashSyncCheckpoint if (!ReadSyncCheckpoint(Checkpoints::hashSyncCheckpoint)) @@ -670,7 +675,7 @@ bool CTxDB::LoadBlockIndex() map, CBlockIndex*> mapBlockPos; for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) { - if (pindex->nHeight < nBestHeight-nCheckDepth) + if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth) break; CBlock block; if (!block.ReadFromDisk(pindex)) @@ -772,7 +777,7 @@ bool CTxDB::LoadBlockIndex() } } } - if (pindexFork) + if (pindexFork && !fRequestShutdown) { // Reorg back to the fork printf("LoadBlockIndex() : *** moving best chain pointer back to block %d\n", pindexFork->nHeight); @@ -788,71 +793,184 @@ bool CTxDB::LoadBlockIndex() - - -// -// CAddrDB -// - -bool CAddrDB::WriteAddrman(const CAddrMan& addrman) -{ - return Write(string("addrman"), addrman); -} - -bool CAddrDB::LoadAddresses() +bool CTxDB::LoadBlockIndexGuts() { - if (Read(string("addrman"), addrman)) - { - printf("Loaded %i addresses\n", addrman.size()); - return true; - } - - // Read pre-0.6 addr records - - vector vAddr; - vector > vDelete; - - // Get cursor + // Get database cursor Dbc* pcursor = GetCursor(); if (!pcursor) return false; + // Load mapBlockIndex + unsigned int fFlags = DB_SET_RANGE; loop { // Read next record CDataStream ssKey(SER_DISK, CLIENT_VERSION); + if (fFlags == DB_SET_RANGE) + ssKey << make_pair(string("blockindex"), uint256(0)); CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue); + int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); + fFlags = DB_NEXT; if (ret == DB_NOTFOUND) break; else if (ret != 0) return false; // Unserialize + + try { string strType; ssKey >> strType; - if (strType == "addr") + if (strType == "blockindex" && !fRequestShutdown) + { + CDiskBlockIndex diskindex; + ssValue >> diskindex; + + // Construct block index object + CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash()); + pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev); + pindexNew->pnext = InsertBlockIndex(diskindex.hashNext); + pindexNew->nFile = diskindex.nFile; + pindexNew->nBlockPos = diskindex.nBlockPos; + pindexNew->nHeight = diskindex.nHeight; + pindexNew->nMint = diskindex.nMint; + pindexNew->nMoneySupply = diskindex.nMoneySupply; + pindexNew->nFlags = diskindex.nFlags; + pindexNew->nStakeModifier = diskindex.nStakeModifier; + pindexNew->prevoutStake = diskindex.prevoutStake; + pindexNew->nStakeTime = diskindex.nStakeTime; + pindexNew->hashProofOfStake = diskindex.hashProofOfStake; + pindexNew->nVersion = diskindex.nVersion; + pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; + pindexNew->nTime = diskindex.nTime; + pindexNew->nBits = diskindex.nBits; + pindexNew->nNonce = diskindex.nNonce; + + // Watch for genesis block + if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == hashGenesisBlock) + pindexGenesisBlock = pindexNew; + + if (!pindexNew->CheckIndex()) + return error("LoadBlockIndex() : CheckIndex failed at %d", pindexNew->nHeight); + + // ppcoin: build setStakeSeen + if (pindexNew->IsProofOfStake()) + setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime)); + } + else { - CAddress addr; - ssValue >> addr; - vAddr.push_back(addr); + break; // if shutdown requested or finished loading block index + } + } // try + catch (std::exception &e) { + return error("%s() : deserialize error", __PRETTY_FUNCTION__); } } pcursor->close(); - addrman.Add(vAddr, CNetAddr("0.0.0.0")); - printf("Loaded %i addresses\n", addrman.size()); + return true; +} - // Note: old records left; we ran into hangs-on-startup - // bugs for some users who (we think) were running after - // an unclean shutdown. - return true; + + + +// +// CAddrDB +// + + +CAddrDB::CAddrDB() +{ + pathAddr = GetDataDir() / "peers.dat"; } -bool LoadAddresses() +bool CAddrDB::Write(const CAddrMan& addr) { - return CAddrDB("cr+").LoadAddresses(); + // Generate random temporary filename + unsigned short randv = 0; + RAND_bytes((unsigned char *)&randv, sizeof(randv)); + std::string tmpfn = strprintf("peers.dat.%04x", randv); + + // serialize addresses, checksum data up to that point, then append csum + CDataStream ssPeers(SER_DISK, CLIENT_VERSION); + ssPeers << FLATDATA(pchMessageStart); + ssPeers << addr; + uint256 hash = Hash(ssPeers.begin(), ssPeers.end()); + ssPeers << hash; + + // open temp output file, and associate with CAutoFile + boost::filesystem::path pathTmp = GetDataDir() / tmpfn; + FILE *file = fopen(pathTmp.string().c_str(), "wb"); + CAutoFile fileout = CAutoFile(file, SER_DISK, CLIENT_VERSION); + if (!fileout) + return error("CAddrman::Write() : open failed"); + + // Write and commit header, data + try { + fileout << ssPeers; + } + catch (std::exception &e) { + return error("CAddrman::Write() : I/O error"); + } + FileCommit(fileout); + fileout.fclose(); + + // replace existing peers.dat, if any, with new peers.dat.XXXX + if (!RenameOver(pathTmp, pathAddr)) + return error("CAddrman::Write() : Rename-into-place failed"); + + return true; } +bool CAddrDB::Read(CAddrMan& addr) +{ + // open input file, and associate with CAutoFile + FILE *file = fopen(pathAddr.string().c_str(), "rb"); + CAutoFile filein = CAutoFile(file, SER_DISK, CLIENT_VERSION); + if (!filein) + return error("CAddrman::Read() : open failed"); + + // use file size to size memory buffer + int fileSize = GetFilesize(filein); + int dataSize = fileSize - sizeof(uint256); + vector vchData; + vchData.resize(dataSize); + uint256 hashIn; + + // read data and checksum from file + try { + filein.read((char *)&vchData[0], dataSize); + filein >> hashIn; + } + catch (std::exception &e) { + return error("CAddrman::Read() 2 : I/O error or stream data corrupted"); + } + filein.fclose(); + + CDataStream ssPeers(vchData, SER_DISK, CLIENT_VERSION); + + // verify stored checksum matches input data + uint256 hashTmp = Hash(ssPeers.begin(), ssPeers.end()); + if (hashIn != hashTmp) + return error("CAddrman::Read() : checksum mismatch; data corrupted"); + + unsigned char pchMsgTmp[4]; + try { + // de-serialize file header (pchMessageStart magic number) and + ssPeers >> FLATDATA(pchMsgTmp); + + // verify the network matches ours + if (memcmp(pchMsgTmp, pchMessageStart, sizeof(pchMsgTmp))) + return error("CAddrman::Read() : invalid network magic number"); + + // de-serialize address data into one CAddrMan object + ssPeers >> addr; + } + catch (std::exception &e) { + return error("CAddrman::Read() : I/O error or stream data corrupted"); + } + + return true; +} diff --git a/src/db.h b/src/db.h index db9fb8a..eeb3a0b 100644 --- a/src/db.h +++ b/src/db.h @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_DB_H @@ -26,21 +25,81 @@ class CWallet; class CWalletTx; extern unsigned int nWalletDBUpdated; -extern bool fDetachDB; -extern DbEnv dbenv; -extern void DBFlush(bool fShutdown); void ThreadFlushWalletDB(void* parg); bool BackupWallet(const CWallet& wallet, const std::string& strDest); +class CDBEnv +{ +private: + bool fDetachDB; + bool fDbEnvInit; + bool fMockDb; + boost::filesystem::path pathEnv; + std::string strPath; + + void EnvShutdown(); + +public: + mutable CCriticalSection cs_db; + DbEnv dbenv; + std::map mapFileUseCount; + std::map mapDb; + + CDBEnv(); + ~CDBEnv(); + void MakeMock(); + bool IsMock() { return fMockDb; }; + + /* + * Verify that database file strFile is OK. If it is not, + * call the callback to try to recover. + * This must be called BEFORE strFile is opened. + * Returns true if strFile is OK. + */ + enum VerifyResult { VERIFY_OK, RECOVER_OK, RECOVER_FAIL }; + VerifyResult Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile)); + /* + * Salvage data from a file that Verify says is bad. + * fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation). + * Appends binary key/value pairs to vResult, returns true if successful. + * NOTE: reads the entire database into memory, so cannot be used + * for huge databases. + */ + typedef std::pair, std::vector > KeyValPair; + bool Salvage(std::string strFile, bool fAggressive, std::vector& vResult); + + bool Open(boost::filesystem::path pathEnv_); + void Close(); + void Flush(bool fShutdown); + void CheckpointLSN(std::string strFile); + void SetDetach(bool fDetachDB_) { fDetachDB = fDetachDB_; } + bool GetDetach() { return fDetachDB; } + + void CloseDb(const std::string& strFile); + bool RemoveDb(const std::string& strFile); + + DbTxn *TxnBegin(int flags=DB_TXN_WRITE_NOSYNC) + { + DbTxn* ptxn = NULL; + int ret = dbenv.txn_begin(NULL, &ptxn, flags); + if (!ptxn || ret != 0) + return NULL; + return ptxn; + } +}; + +extern CDBEnv bitdb; + + /** RAII class that provides access to a Berkeley database */ class CDB { protected: Db* pdb; std::string strFile; - std::vector vTxn; + DbTxn *activeTxn; bool fReadOnly; explicit CDB(const char* pszFile, const char* pszMode="r+"); @@ -67,7 +126,7 @@ protected: // Read Dbt datValue; datValue.set_flags(DB_DBT_MALLOC); - int ret = pdb->get(GetTxn(), &datKey, &datValue, 0); + int ret = pdb->get(activeTxn, &datKey, &datValue, 0); memset(datKey.get_data(), 0, datKey.get_size()); if (datValue.get_data() == NULL) return false; @@ -108,7 +167,7 @@ protected: Dbt datValue(&ssValue[0], ssValue.size()); // Write - int ret = pdb->put(GetTxn(), &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); + int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); // Clear memory in case it was a private key memset(datKey.get_data(), 0, datKey.get_size()); @@ -131,7 +190,7 @@ protected: Dbt datKey(&ssKey[0], ssKey.size()); // Erase - int ret = pdb->del(GetTxn(), &datKey, 0); + int ret = pdb->del(activeTxn, &datKey, 0); // Clear memory memset(datKey.get_data(), 0, datKey.get_size()); @@ -151,7 +210,7 @@ protected: Dbt datKey(&ssKey[0], ssKey.size()); // Exists - int ret = pdb->exists(GetTxn(), &datKey, 0); + int ret = pdb->exists(activeTxn, &datKey, 0); // Clear memory memset(datKey.get_data(), 0, datKey.get_size()); @@ -208,46 +267,33 @@ protected: return 0; } - DbTxn* GetTxn() - { - if (!vTxn.empty()) - return vTxn.back(); - else - return NULL; - } - public: bool TxnBegin() { - if (!pdb) + if (!pdb || activeTxn) return false; - DbTxn* ptxn = NULL; - int ret = dbenv.txn_begin(GetTxn(), &ptxn, DB_TXN_WRITE_NOSYNC); - if (!ptxn || ret != 0) + DbTxn* ptxn = bitdb.TxnBegin(); + if (!ptxn) return false; - vTxn.push_back(ptxn); + activeTxn = ptxn; return true; } bool TxnCommit() { - if (!pdb) - return false; - if (vTxn.empty()) + if (!pdb || !activeTxn) return false; - int ret = vTxn.back()->commit(0); - vTxn.pop_back(); + int ret = activeTxn->commit(0); + activeTxn = NULL; return (ret == 0); } bool TxnAbort() { - if (!pdb) - return false; - if (vTxn.empty()) + if (!pdb || !activeTxn) return false; - int ret = vTxn.back()->abort(); - vTxn.pop_back(); + int ret = activeTxn->abort(); + activeTxn = NULL; return (ret == 0); } @@ -285,13 +331,11 @@ public: bool AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight); bool EraseTxIndex(const CTransaction& tx); bool ContainsTx(uint256 hash); - bool ReadOwnerTxes(uint160 hash160, int nHeight, std::vector& vtx); bool ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex); bool ReadDiskTx(uint256 hash, CTransaction& tx); bool ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex); bool ReadDiskTx(COutPoint outpoint, CTransaction& tx); bool WriteBlockIndex(const CDiskBlockIndex& blockindex); - bool EraseBlockIndex(uint256 hash); bool ReadHashBestChain(uint256& hashBestChain); bool WriteHashBestChain(uint256 hashBestChain); bool ReadBestInvalidTrust(CBigNum& bnBestInvalidTrust); @@ -301,25 +345,22 @@ public: bool ReadCheckpointPubKey(std::string& strPubKey); bool WriteCheckpointPubKey(const std::string& strPubKey); bool LoadBlockIndex(); +private: + bool LoadBlockIndexGuts(); }; -/** Access to the (IP) address database (addr.dat) */ -class CAddrDB : public CDB +/** Access to the (IP) address database (peers.dat) */ +class CAddrDB { -public: - CAddrDB(const char* pszMode="r+") : CDB("addr.dat", pszMode) { } private: - CAddrDB(const CAddrDB&); - void operator=(const CAddrDB&); + boost::filesystem::path pathAddr; public: - bool WriteAddrman(const CAddrMan& addr); - bool LoadAddresses(); + CAddrDB(); + bool Write(const CAddrMan& addr); + bool Read(CAddrMan& addr); }; -bool LoadAddresses(); - - #endif // BITCOIN_DB_H diff --git a/src/init.cpp b/src/init.cpp index 96ba319..42f80a2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,7 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers -// Copyright (c) 2013 NovaCoin Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "db.h" @@ -16,6 +14,8 @@ #include #include #include +#include +#include #ifndef WIN32 #include @@ -25,6 +25,7 @@ using namespace std; using namespace boost; CWallet* pwalletMain; +CClientUIInterface uiInterface; ////////////////////////////////////////////////////////////////////////////// // @@ -43,10 +44,10 @@ void StartShutdown() { #ifdef QT_GUI // ensure we leave the Qt main loop for a clean GUI exit (Shutdown() is called in bitcoin.cpp afterwards) - QueueShutdown(); + uiInterface.QueueShutdown(); #else // Without UI, Shutdown() can simply be started in a new thread - CreateThread(Shutdown, NULL); + NewThread(Shutdown, NULL); #endif } @@ -54,6 +55,10 @@ void Shutdown(void* parg) { static CCriticalSection cs_Shutdown; static bool fTaken; + + // Make this thread recognisable as the shutdown thread + RenameThread("bitcoin-shutoff"); + bool fFirstThread = false; { TRY_LOCK(cs_Shutdown, lockShutdown); @@ -68,18 +73,18 @@ void Shutdown(void* parg) { fShutdown = true; nTransactionsUpdated++; - DBFlush(false); + bitdb.Flush(false); StopNode(); - DBFlush(true); + bitdb.Flush(true); boost::filesystem::remove(GetPidFile()); UnregisterWallet(pwalletMain); delete pwalletMain; - CreateThread(ExitTimeout, NULL); + NewThread(ExitTimeout, NULL); Sleep(50); - printf("NovaCoin exiting\n\n"); + printf("NovaCoin exited\n\n"); fExit = true; #ifndef QT_GUI - // ensure non UI client get's exited here, but let Bitcoin-Qt reach return 0; in bitcoin.cpp + // ensure non-UI client gets exited here, but let Bitcoin-Qt reach 'return 0;' in bitcoin.cpp exit(0); #endif } @@ -97,6 +102,10 @@ void HandleSIGTERM(int) fRequestShutdown = true; } +void HandleSIGHUP(int) +{ + fReopenDebugLog = true; +} @@ -107,24 +116,51 @@ void HandleSIGTERM(int) // Start // #if !defined(QT_GUI) -int main(int argc, char* argv[]) -{ - bool fRet = false; - fRet = AppInit(argc, argv); - - if (fRet && fDaemon) - return 0; - - return 1; -} -#endif - bool AppInit(int argc, char* argv[]) { bool fRet = false; try { - fRet = AppInit2(argc, argv); + // + // Parameters + // + // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main() + ParseParameters(argc, argv); + if (!boost::filesystem::is_directory(GetDataDir(false))) + { + fprintf(stderr, "Error: Specified directory does not exist\n"); + Shutdown(NULL); + } + ReadConfigFile(mapArgs, mapMultiArgs); + + if (mapArgs.count("-?") || mapArgs.count("--help")) + { + // First part of help message is specific to bitcoind / RPC client + std::string strUsage = _("NovaCoin version") + " " + FormatFullVersion() + "\n\n" + + _("Usage:") + "\n" + + " novacoind [options] " + "\n" + + " novacoind [options] [params] " + _("Send command to -server or novacoind") + "\n" + + " novacoind [options] help " + _("List commands") + "\n" + + " novacoind [options] help " + _("Get help for a command") + "\n"; + + strUsage += "\n" + HelpMessage(); + + fprintf(stdout, "%s", strUsage.c_str()); + return false; + } + + // Command-line RPC + for (int i = 1; i < argc; i++) + if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "novacoin:")) + fCommandLine = true; + + if (fCommandLine) + { + int ret = CommandLineRPC(argc, argv); + exit(ret); + } + + fRet = AppInit2(); } catch (std::exception& e) { PrintException(&e, "AppInit()"); @@ -136,21 +172,164 @@ bool AppInit(int argc, char* argv[]) return fRet; } -bool AppInit2(int argc, char* argv[]) +extern void noui_connect(); +int main(int argc, char* argv[]) { + bool fRet = false; + + // Connect bitcoind signal handlers + noui_connect(); + + fRet = AppInit(argc, argv); + + if (fRet && fDaemon) + return 0; + + return 1; +} +#endif + +bool static InitError(const std::string &str) +{ + uiInterface.ThreadSafeMessageBox(str, _("NovaCoin"), CClientUIInterface::OK | CClientUIInterface::MODAL); + return false; +} + +bool static InitWarning(const std::string &str) +{ + uiInterface.ThreadSafeMessageBox(str, _("NovaCoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + return true; +} + + +bool static Bind(const CService &addr, bool fError = true) { + if (IsLimited(addr)) + return false; + std::string strError; + if (!BindListenPort(addr, strError)) { + if (fError) + return InitError(strError); + return false; + } + return true; +} + +// Core-specific options shared between UI and daemon +std::string HelpMessage() +{ + string strUsage = _("Options:") + "\n" + + " -? " + _("This help message") + "\n" + + " -conf= " + _("Specify configuration file (default: novacoin.conf)") + "\n" + + " -pid= " + _("Specify pid file (default: novacoind.pid)") + "\n" + + " -gen " + _("Generate coins") + "\n" + + " -gen=0 " + _("Don't generate coins") + "\n" + + " -datadir= " + _("Specify data directory") + "\n" + + " -dbcache= " + _("Set database cache size in megabytes (default: 25)") + "\n" + + " -dblogsize= " + _("Set database disk log size in megabytes (default: 100)") + "\n" + + " -timeout= " + _("Specify connection timeout in milliseconds (default: 5000)") + "\n" + + " -proxy= " + _("Connect through socks proxy") + "\n" + + " -socks= " + _("Select the version of socks proxy to use (4-5, default: 5)") + "\n" + + " -tor= " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n" + " -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + "\n" + + " -port= " + _("Listen for connections on (default: 7777 or testnet: 17777)") + "\n" + + " -maxconnections= " + _("Maintain at most connections to peers (default: 125)") + "\n" + + " -addnode= " + _("Add a node to connect to and attempt to keep the connection open") + "\n" + + " -connect= " + _("Connect only to the specified node(s)") + "\n" + + " -seednode= " + _("Connect to a node to retrieve peer addresses, and disconnect") + "\n" + + " -externalip= " + _("Specify your own public address") + "\n" + + " -onlynet= " + _("Only connect to nodes in network (IPv4, IPv6 or Tor)") + "\n" + + " -discover " + _("Discover own IP address (default: 1 when listening and no -externalip)") + "\n" + + " -irc " + _("Find peers using internet relay chat (default: 1)") + "\n" + + " -listen " + _("Accept connections from outside (default: 1 if no -proxy or -connect)") + "\n" + + " -bind= " + _("Bind to given address. Use [host]:port notation for IPv6") + "\n" + + " -dnsseed " + _("Find peers using DNS lookup (default: 0)") + "\n" + + " -banscore= " + _("Threshold for disconnecting misbehaving peers (default: 100)") + "\n" + + " -bantime= " + _("Number of seconds to keep misbehaving peers from reconnecting (default: 86400)") + "\n" + + " -maxreceivebuffer= " + _("Maximum per-connection receive buffer, *1000 bytes (default: 5000)") + "\n" + + " -maxsendbuffer= " + _("Maximum per-connection send buffer, *1000 bytes (default: 1000)") + "\n" + +#ifdef USE_UPNP +#if USE_UPNP + " -upnp " + _("Use UPnP to map the listening port (default: 1 when listening)") + "\n" + +#else + " -upnp " + _("Use UPnP to map the listening port (default: 0)") + "\n" + +#endif +#endif + " -detachdb " + _("Detach block and address databases. Increases shutdown time (default: 0)") + "\n" + + " -paytxfee= " + _("Fee per KB to add to transactions you send") + "\n" + +#ifdef QT_GUI + " -server " + _("Accept command line and JSON-RPC commands") + "\n" + +#endif +#if !defined(WIN32) && !defined(QT_GUI) + " -daemon " + _("Run in the background as a daemon and accept commands") + "\n" + +#endif + " -testnet " + _("Use the test network") + "\n" + + " -debug " + _("Output extra debugging information. Implies all other -debug* options") + "\n" + + " -debugnet " + _("Output extra network debugging information") + "\n" + + " -logtimestamps " + _("Prepend debug output with timestamp") + "\n" + + " -shrinkdebugfile " + _("Shrink debug.log file on client startup (default: 1 when no -debug)") + "\n" + + " -printtoconsole " + _("Send trace/debug info to console instead of debug.log file") + "\n" + +#ifdef WIN32 + " -printtodebugger " + _("Send trace/debug info to debugger") + "\n" + +#endif + " -rpcuser= " + _("Username for JSON-RPC connections") + "\n" + + " -rpcpassword= " + _("Password for JSON-RPC connections") + "\n" + + " -rpcport= " + _("Listen for JSON-RPC connections on (default: 8344 or testnet: 18344)") + "\n" + + " -rpcallowip= " + _("Allow JSON-RPC connections from specified IP address") + "\n" + + " -rpcconnect= " + _("Send commands to node running on (default: 127.0.0.1)") + "\n" + + " -blocknotify= " + _("Execute command when the best block changes (%s in cmd is replaced by block hash)") + "\n" + + " -upgradewallet " + _("Upgrade wallet to latest format") + "\n" + + " -keypool= " + _("Set key pool size to (default: 100)") + "\n" + + " -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" + + " -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\n" + + " -checkblocks= " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" + + " -checklevel= " + _("How thorough the block verification is (0-6, default: 1)") + "\n" + + " -loadblock= " + _("Imports blocks from external blk000?.dat file") + "\n" + + + "\n" + _("Block creation options:") + "\n" + + " -blockminsize= " + _("Set minimum block size in bytes (default: 0)") + "\n" + + " -blockmaxsize= " + _("Set maximum block size in bytes (default: 250000)") + "\n" + + " -blockprioritysize= " + _("Set maximum size of high-priority/low-fee transactions in bytes (default: 27000)") + "\n" + + + "\n" + _("SSL options: (see the Bitcoin Wiki for SSL setup instructions)") + "\n" + + " -rpcssl " + _("Use OpenSSL (https) for JSON-RPC connections") + "\n" + + " -rpcsslcertificatechainfile= " + _("Server certificate file (default: server.cert)") + "\n" + + " -rpcsslprivatekeyfile= " + _("Server private key (default: server.pem)") + "\n" + + " -rpcsslciphers= " + _("Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH)") + "\n"; + + return strUsage; +} + +/** Initialize bitcoin. + * @pre Parameters should be parsed and config file should be read. + */ +bool AppInit2() +{ + // ********************************************************* Step 1: setup #ifdef _MSC_VER - // Turn off microsoft heap dump noise + // Turn off Microsoft heap dump noise _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0)); #endif #if _MSC_VER >= 1400 - // Disable confusing "helpful" text message on abort, ctrl-c + // Disable confusing "helpful" text message on abort, Ctrl-C _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); #endif -#ifndef WIN32 - umask(077); +#ifdef WIN32 + // Enable Data Execution Prevention (DEP) + // Minimum supported OS versions: WinXP SP3, WinVista >= SP1, Win Server 2008 + // A failure is non-critical and needs no further attention! +#ifndef PROCESS_DEP_ENABLE +// We define this here, because GCCs winbase.h limits this to _WIN32_WINNT >= 0x0601 (Windows 7), +// which is not correct. Can be removed, when GCCs winbase.h is fixed! +#define PROCESS_DEP_ENABLE 0x00000001 +#endif + typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD); + PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy"); + if (setProcDEPPol != NULL) setProcDEPPol(PROCESS_DEP_ENABLE); #endif #ifndef WIN32 + umask(077); + // Clean shutdown on SIGTERM struct sigaction sa; sa.sa_handler = HandleSIGTERM; @@ -158,121 +337,66 @@ bool AppInit2(int argc, char* argv[]) sa.sa_flags = 0; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); - sigaction(SIGHUP, &sa, NULL); -#endif - // - // Parameters - // - // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main() -#if !defined(QT_GUI) - ParseParameters(argc, argv); - if (!boost::filesystem::is_directory(GetDataDir(false))) - { - fprintf(stderr, "Error: Specified directory does not exist\n"); - Shutdown(NULL); - } - ReadConfigFile(mapArgs, mapMultiArgs); + // Reopen debug.log on SIGHUP + struct sigaction sa_hup; + sa_hup.sa_handler = HandleSIGHUP; + sigemptyset(&sa_hup.sa_mask); + sa_hup.sa_flags = 0; + sigaction(SIGHUP, &sa_hup, NULL); #endif - if (mapArgs.count("-?") || mapArgs.count("--help")) - { - string strUsage = string() + - _("NovaCoin version") + " " + FormatFullVersion() + "\n\n" + - _("Usage:") + "\t\t\t\t\t\t\t\t\t\t\n" + - " novacoind [options] \t " + "\n" + - " novacoind [options] [params]\t " + _("Send command to -server or novacoind") + "\n" + - " novacoind [options] help \t\t " + _("List commands") + "\n" + - " novacoind [options] help \t\t " + _("Get help for a command") + "\n" + - _("Options:") + "\n" + - " -conf= \t\t " + _("Specify configuration file (default: novacoin.conf)") + "\n" + - " -pid= \t\t " + _("Specify pid file (default: novacoind.pid)") + "\n" + - " -gen \t\t " + _("Generate coins") + "\n" + - " -gen=0 \t\t " + _("Don't generate coins") + "\n" + - " -min \t\t " + _("Start minimized") + "\n" + - " -splash \t\t " + _("Show splash screen on startup (default: 1)") + "\n" + - " -datadir= \t\t " + _("Specify data directory") + "\n" + - " -dbcache= \t\t " + _("Set database cache size in megabytes (default: 25)") + "\n" + - " -dblogsize= \t\t " + _("Set database disk log size in megabytes (default: 100)") + "\n" + - " -timeout= \t " + _("Specify connection timeout (in milliseconds)") + "\n" + - " -proxy= \t " + _("Connect through socks4 proxy") + "\n" + - " -dns \t " + _("Allow DNS lookups for addnode and connect") + "\n" + - " -port= \t\t " + _("Listen for connections on (default: 7777 or testnet: 17777)") + "\n" + - " -maxconnections=\t " + _("Maintain at most connections to peers (default: 125)") + "\n" + - " -addnode= \t " + _("Add a node to connect to and attempt to keep the connection open") + "\n" + - " -connect= \t\t " + _("Connect only to the specified node") + "\n" + - " -listen \t " + _("Accept connections from outside (default: 1)") + "\n" + -#ifdef QT_GUI - " -lang= \t\t " + _("Set language, for example \"de_DE\" (default: system locale)") + "\n" + -#endif - " -dnsseed \t " + _("Find peers using DNS lookup (default: 1)") + "\n" + - " -banscore= \t " + _("Threshold for disconnecting misbehaving peers (default: 100)") + "\n" + - " -bantime= \t " + _("Number of seconds to keep misbehaving peers from reconnecting (default: 86400)") + "\n" + - " -maxreceivebuffer=\t " + _("Maximum per-connection receive buffer, *1000 bytes (default: 10000)") + "\n" + - " -maxsendbuffer=\t " + _("Maximum per-connection send buffer, *1000 bytes (default: 10000)") + "\n" + -#ifdef USE_UPNP -#if USE_UPNP - " -upnp \t " + _("Use Universal Plug and Play to map the listening port (default: 1)") + "\n" + -#else - " -upnp \t " + _("Use Universal Plug and Play to map the listening port (default: 0)") + "\n" + -#endif - " -detachdb \t " + _("Detach block and address databases. Increases shutdown time (default: 0)") + "\n" + -#endif - " -paytxfee= \t " + _("Fee per KB to add to transactions you send") + "\n" + -#ifdef QT_GUI - " -server \t\t " + _("Accept command line and JSON-RPC commands") + "\n" + -#endif -#if !defined(WIN32) && !defined(QT_GUI) - " -daemon \t\t " + _("Run in the background as a daemon and accept commands") + "\n" + -#endif - " -testnet \t\t " + _("Use the test network") + "\n" + - " -debug \t\t " + _("Output extra debugging information") + "\n" + - " -logtimestamps \t " + _("Prepend debug output with timestamp") + "\n" + - " -printtoconsole \t " + _("Send trace/debug info to console instead of debug.log file") + "\n" + -#ifdef WIN32 - " -printtodebugger \t " + _("Send trace/debug info to debugger") + "\n" + -#endif - " -rpcuser= \t " + _("Username for JSON-RPC connections") + "\n" + - " -rpcpassword=\t " + _("Password for JSON-RPC connections") + "\n" + - " -rpcport= \t\t " + _("Listen for JSON-RPC connections on (default: 8344)") + "\n" + - " -rpcallowip= \t\t " + _("Allow JSON-RPC connections from specified IP address") + "\n" + - " -rpcconnect= \t " + _("Send commands to node running on (default: 127.0.0.1)") + "\n" + - " -blocknotify= " + _("Execute command when the best block changes (%s in cmd is replaced by block hash)") + "\n" + - " -upgradewallet \t " + _("Upgrade wallet to latest format") + "\n" + - " -keypool= \t " + _("Set key pool size to (default: 100)") + "\n" + - " -rescan \t " + _("Rescan the block chain for missing wallet transactions") + "\n" + - " -checkblocks= \t\t " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" + - " -checklevel= \t\t " + _("How thorough the block verification is (0-6, default: 1)") + "\n"; - - strUsage += string() + - _("\nSSL options: (see the Bitcoin Wiki for SSL setup instructions)") + "\n" + - " -rpcssl \t " + _("Use OpenSSL (https) for JSON-RPC connections") + "\n" + - " -rpcsslcertificatechainfile=\t " + _("Server certificate file (default: server.cert)") + "\n" + - " -rpcsslprivatekeyfile= \t " + _("Server private key (default: server.pem)") + "\n" + - " -rpcsslciphers= \t " + _("Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH)") + "\n"; - - strUsage += string() + - " -? \t\t " + _("This help message") + "\n"; - - // Remove tabs - strUsage.erase(std::remove(strUsage.begin(), strUsage.end(), '\t'), strUsage.end()); -#if defined(QT_GUI) && defined(WIN32) - // On windows, show a message box, as there is no stderr - ThreadSafeMessageBox(strUsage, _("Usage"), wxOK | wxMODAL); -#else - fprintf(stderr, "%s", strUsage.c_str()); -#endif - return false; - } + // ********************************************************* Step 2: parameter interactions fTestNet = GetBoolArg("-testnet"); - if (fTestNet) - { + if (fTestNet) { SoftSetBoolArg("-irc", true); } + if (mapArgs.count("-bind")) { + // when specifying an explicit binding address, you want to listen on it + // even when -connect or -proxy is specified + SoftSetBoolArg("-listen", true); + } + + if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0) { + // when only connecting to trusted nodes, do not seed via DNS, or listen by default + SoftSetBoolArg("-dnsseed", false); + SoftSetBoolArg("-listen", false); + } + + if (mapArgs.count("-proxy")) { + // to protect privacy, do not listen by default if a proxy server is specified + SoftSetBoolArg("-listen", false); + } + + if (!GetBoolArg("-listen", true)) { + // do not map ports or try to retrieve public IP when not listening (pointless) + SoftSetBoolArg("-upnp", false); + SoftSetBoolArg("-discover", false); + } + + if (mapArgs.count("-externalip")) { + // if an explicit public IP is specified, do not try to find others + SoftSetBoolArg("-discover", false); + } + + if (GetBoolArg("-salvagewallet")) { + // Rewrite just private keys: rescan to find transactions + SoftSetBoolArg("-rescan", true); + } + + // ********************************************************* Step 3: parameter-to-internal-flags + fDebug = GetBoolArg("-debug"); - fDetachDB = GetBoolArg("-detachdb", false); + + // -debug implies fDebug* + if (fDebug) + fDebugNet = true; + else + fDebugNet = GetBoolArg("-debugnet"); + + bitdb.SetDetach(GetBoolArg("-detachdb", false)); #if !defined(WIN32) && !defined(QT_GUI) fDaemon = GetBoolArg("-daemon"); @@ -293,17 +417,39 @@ bool AppInit2(int argc, char* argv[]) fPrintToDebugger = GetBoolArg("-printtodebugger"); fLogTimestamps = GetBoolArg("-logtimestamps"); -#ifndef QT_GUI - for (int i = 1; i < argc; i++) - if (!IsSwitchChar(argv[i][0]) && !(strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0)) - fCommandLine = true; + if (mapArgs.count("-timeout")) + { + int nNewTimeout = GetArg("-timeout", 5000); + if (nNewTimeout > 0 && nNewTimeout < 600000) + nConnectTimeout = nNewTimeout; + } - if (fCommandLine) + // Continue to put "/P2SH/" in the coinbase to monitor + // BIP16 support. + // This can be removed eventually... + const char* pszP2SH = "/P2SH/"; + COINBASE_FLAGS << std::vector(pszP2SH, pszP2SH+strlen(pszP2SH)); + + + if (mapArgs.count("-paytxfee")) { - int ret = CommandLineRPC(argc, argv); - exit(ret); + if (!ParseMoney(mapArgs["-paytxfee"], nTransactionFee)) + return InitError(strprintf(_("Invalid amount for -paytxfee=: '%s'"), mapArgs["-paytxfee"].c_str())); + if (nTransactionFee > 0.25 * COIN) + InitWarning(_("Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction.")); } -#endif + + // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log + + std::string strDataDir = GetDataDir().string(); + + // Make sure only a single Bitcoin process is using the data directory. + boost::filesystem::path pathLockFile = GetDataDir() / ".lock"; + FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist. + if (file) fclose(file); + static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); + if (!lock.try_lock()) + return InitError(strprintf(_("Cannot obtain a lock on data directory %s. NovaCoin is probably already running."), strDataDir.c_str())); #if !defined(WIN32) && !defined(QT_GUI) if (fDaemon) @@ -327,51 +473,201 @@ bool AppInit2(int argc, char* argv[]) } #endif - if (!fDebug) + if (GetBoolArg("-shrinkdebugfile", !fDebug)) ShrinkDebugFile(); printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); printf("NovaCoin version %s (%s)\n", FormatFullVersion().c_str(), CLIENT_DATE.c_str()); + printf("Using OpenSSL version %s\n", SSLeay_version(SSLEAY_VERSION)); + if (!fLogTimestamps) + printf("Startup time: %s\n", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); printf("Default data directory %s\n", GetDefaultDataDir().string().c_str()); + printf("Used data directory %s\n", strDataDir.c_str()); + std::ostringstream strErrors; - if (GetBoolArg("-loadblockindextest")) + if (fDaemon) + fprintf(stdout, "NovaCoin server starting\n"); + + int64 nStart; + + // ********************************************************* Step 5: verify database integrity + + uiInterface.InitMessage(_("Verifying database integrity...")); + + if (!bitdb.Open(GetDataDir())) { - CTxDB txdb("r"); - txdb.LoadBlockIndex(); - PrintBlockTree(); - return false; + string msg = strprintf(_("Error initializing database environment %s!" + " To recover, BACKUP THAT DIRECTORY, then remove" + " everything from it except for wallet.dat."), strDataDir.c_str()); + return InitError(msg); } - // Make sure only a single bitcoin process is using the data directory. - boost::filesystem::path pathLockFile = GetDataDir() / ".lock"; - FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist. - if (file) fclose(file); - static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); - if (!lock.try_lock()) + if (GetBoolArg("-salvagewallet")) { - ThreadSafeMessageBox(strprintf(_("Cannot obtain a lock on data directory %s. NovaCoin is probably already running."), GetDataDir().string().c_str()), _("NovaCoin"), wxOK|wxMODAL); - return false; + // Recover readable keypairs: + if (!CWalletDB::Recover(bitdb, "wallet.dat", true)) + return false; } - std::ostringstream strErrors; - // - // Load data files - // - if (fDaemon) - fprintf(stdout, "novacoin server starting\n"); - int64 nStart; + if (filesystem::exists(GetDataDir() / "wallet.dat")) + { + CDBEnv::VerifyResult r = bitdb.Verify("wallet.dat", CWalletDB::Recover); + if (r == CDBEnv::RECOVER_OK) + { + string msg = strprintf(_("Warning: wallet.dat corrupt, data salvaged!" + " Original wallet.dat saved as wallet.{timestamp}.bak in %s; if" + " your balance or transactions are incorrect you should" + " restore from a backup."), strDataDir.c_str()); + uiInterface.ThreadSafeMessageBox(msg, _("NovaCoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + } + if (r == CDBEnv::RECOVER_FAIL) + return InitError(_("wallet.dat corrupt, salvage failed")); + } - InitMessage(_("Loading addresses...")); - printf("Loading addresses...\n"); - nStart = GetTimeMillis(); - if (!LoadAddresses()) - strErrors << _("Error loading addr.dat") << "\n"; - printf(" addresses %15"PRI64d"ms\n", GetTimeMillis() - nStart); + // ********************************************************* Step 6: network initialization + + int nSocksVersion = GetArg("-socks", 5); + + if (nSocksVersion != 4 && nSocksVersion != 5) + return InitError(strprintf(_("Unknown -socks proxy version requested: %i"), nSocksVersion)); + + if (mapArgs.count("-onlynet")) { + std::set nets; + BOOST_FOREACH(std::string snet, mapMultiArgs["-onlynet"]) { + enum Network net = ParseNetwork(snet); + if (net == NET_UNROUTABLE) + return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet.c_str())); + nets.insert(net); + } + for (int n = 0; n < NET_MAX; n++) { + enum Network net = (enum Network)n; + if (!nets.count(net)) + SetLimited(net); + } + } +#if defined(USE_IPV6) +#if ! USE_IPV6 + else + SetLimited(NET_IPV6); +#endif +#endif + + CService addrProxy; + bool fProxy = false; + if (mapArgs.count("-proxy")) { + addrProxy = CService(mapArgs["-proxy"], 9050); + if (!addrProxy.IsValid()) + return InitError(strprintf(_("Invalid -proxy address: '%s'"), mapArgs["-proxy"].c_str())); + + if (!IsLimited(NET_IPV4)) + SetProxy(NET_IPV4, addrProxy, nSocksVersion); + if (nSocksVersion > 4) { +#ifdef USE_IPV6 + if (!IsLimited(NET_IPV6)) + SetProxy(NET_IPV6, addrProxy, nSocksVersion); +#endif + SetNameProxy(addrProxy, nSocksVersion); + } + fProxy = true; + } + + // -tor can override normal proxy, -notor disables tor entirely + if (!(mapArgs.count("-tor") && mapArgs["-tor"] == "0") && (fProxy || mapArgs.count("-tor"))) { + CService addrOnion; + if (!mapArgs.count("-tor")) + addrOnion = addrProxy; + else + addrOnion = CService(mapArgs["-tor"], 9050); + if (!addrOnion.IsValid()) + return InitError(strprintf(_("Invalid -tor address: '%s'"), mapArgs["-tor"].c_str())); + SetProxy(NET_TOR, addrOnion, 5); + SetReachable(NET_TOR); + } + + // see Step 2: parameter interactions for more information about these + fNoListen = !GetBoolArg("-listen", true); + fDiscover = GetBoolArg("-discover", true); + fNameLookup = GetBoolArg("-dns", true); +#ifdef USE_UPNP + fUseUPnP = GetBoolArg("-upnp", USE_UPNP); +#endif + + bool fBound = false; + if (!fNoListen) + { + std::string strError; + if (mapArgs.count("-bind")) { + BOOST_FOREACH(std::string strBind, mapMultiArgs["-bind"]) { + CService addrBind; + if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) + return InitError(strprintf(_("Cannot resolve -bind address: '%s'"), strBind.c_str())); + fBound |= Bind(addrBind); + } + } else { + struct in_addr inaddr_any; + inaddr_any.s_addr = INADDR_ANY; +#ifdef USE_IPV6 + if (!IsLimited(NET_IPV6)) + fBound |= Bind(CService(in6addr_any, GetListenPort()), false); +#endif + if (!IsLimited(NET_IPV4)) + fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound); + } + if (!fBound) + return InitError(_("Failed to listen on any port. Use -listen=0 if you want this.")); + } - InitMessage(_("Loading block index...")); + if (mapArgs.count("-externalip")) + { + BOOST_FOREACH(string strAddr, mapMultiArgs["-externalip"]) { + CService addrLocal(strAddr, GetListenPort(), fNameLookup); + if (!addrLocal.IsValid()) + return InitError(strprintf(_("Cannot resolve -externalip address: '%s'"), strAddr.c_str())); + AddLocal(CService(strAddr, GetListenPort(), fNameLookup), LOCAL_MANUAL); + } + } + + if (mapArgs.count("-reservebalance")) // ppcoin: reserve balance amount + { + int64 nReserveBalance = 0; + if (!ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) + { + InitError(_("Invalid amount for -reservebalance=")); + return false; + } + } + + if (mapArgs.count("-checkpointkey")) // ppcoin: checkpoint master priv key + { + if (!Checkpoints::SetCheckpointPrivKey(GetArg("-checkpointkey", ""))) + InitError(_("Unable to sign checkpoint, wrong checkpointkey?\n")); + } + + BOOST_FOREACH(string strDest, mapMultiArgs["-seednode"]) + AddOneShot(strDest); + + // ********************************************************* Step 7: load blockchain + + if (!bitdb.Open(GetDataDir())) + { + string msg = strprintf(_("Error initializing database environment %s!" + " To recover, BACKUP THAT DIRECTORY, then remove" + " everything from it except for wallet.dat."), strDataDir.c_str()); + return InitError(msg); + } + + if (GetBoolArg("-loadblockindextest")) + { + CTxDB txdb("r"); + txdb.LoadBlockIndex(); + PrintBlockTree(); + return false; + } + + uiInterface.InitMessage(_("Loading block index...")); printf("Loading block index...\n"); nStart = GetTimeMillis(); if (!LoadBlockIndex()) - strErrors << _("Error loading blkindex.dat. Remove blk*.dat from your data directory and try again.") << "\n"; + return InitError(_("Error loading blkindex.dat")); // as LoadBlockIndex can take several minutes, it's possible the user // requested to kill bitcoin-qt during the last operation. If so, exit. @@ -383,24 +679,60 @@ bool AppInit2(int argc, char* argv[]) } printf(" block index %15"PRI64d"ms\n", GetTimeMillis() - nStart); - InitMessage(_("Loading wallet...")); + if (GetBoolArg("-printblockindex") || GetBoolArg("-printblocktree")) + { + PrintBlockTree(); + return false; + } + + if (mapArgs.count("-printblock")) + { + string strMatch = mapArgs["-printblock"]; + int nFound = 0; + for (map::iterator mi = mapBlockIndex.begin(); mi != mapBlockIndex.end(); ++mi) + { + uint256 hash = (*mi).first; + if (strncmp(hash.ToString().c_str(), strMatch.c_str(), strMatch.size()) == 0) + { + CBlockIndex* pindex = (*mi).second; + CBlock block; + block.ReadFromDisk(pindex); + block.BuildMerkleTree(); + block.print(); + printf("\n"); + nFound++; + } + } + if (nFound == 0) + printf("No blocks matching %s were found\n", strMatch.c_str()); + return false; + } + + // ********************************************************* Step 8: load wallet + + uiInterface.InitMessage(_("Loading wallet...")); printf("Loading wallet...\n"); nStart = GetTimeMillis(); - bool fFirstRun; + bool fFirstRun = true; pwalletMain = new CWallet("wallet.dat"); - int nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun); + DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun); if (nLoadWalletRet != DB_LOAD_OK) { if (nLoadWalletRet == DB_CORRUPT) strErrors << _("Error loading wallet.dat: Wallet corrupted") << "\n"; + else if (nLoadWalletRet == DB_NONCRITICAL_ERROR) + { + string msg(_("Warning: error reading wallet.dat! All keys read correctly, but transaction data" + " or address book entries might be missing or incorrect.")); + uiInterface.ThreadSafeMessageBox(msg, _("NovaCoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + } else if (nLoadWalletRet == DB_TOO_NEW) strErrors << _("Error loading wallet.dat: Wallet requires newer version of NovaCoin") << "\n"; else if (nLoadWalletRet == DB_NEED_REWRITE) { strErrors << _("Wallet needed to be rewritten: restart NovaCoin to complete") << "\n"; printf("%s", strErrors.str().c_str()); - ThreadSafeMessageBox(strErrors.str(), _("NovaCoin"), wxOK | wxICON_ERROR | wxMODAL); - return false; + return InitError(strErrors.str()); } else strErrors << _("Error loading wallet.dat") << "\n"; @@ -409,7 +741,7 @@ bool AppInit2(int argc, char* argv[]) if (GetBoolArg("-upgradewallet", fFirstRun)) { int nMaxVersion = GetArg("-upgradewallet", 0); - if (nMaxVersion == 0) // the -walletupgrade without argument case + if (nMaxVersion == 0) // the -upgradewallet without argument case { printf("Performing wallet upgrade to %i\n", FEATURE_LATEST); nMaxVersion = CLIENT_VERSION; @@ -427,11 +759,11 @@ bool AppInit2(int argc, char* argv[]) // Create new keyUser and set as default key RandAddSeedPerfmon(); - std::vector newDefaultKey; + CPubKey newDefaultKey; if (!pwalletMain->GetKeyFromPool(newDefaultKey, false)) strErrors << _("Cannot initialize keypool") << "\n"; pwalletMain->SetDefaultKey(newDefaultKey); - if (!pwalletMain->SetAddressBookName(CBitcoinAddress(pwalletMain->vchDefaultKey), "")) + if (!pwalletMain->SetAddressBookName(pwalletMain->vchDefaultKey.GetID(), "")) strErrors << _("Cannot write default address") << "\n"; } @@ -452,179 +784,91 @@ bool AppInit2(int argc, char* argv[]) } if (pindexBest != pindexRescan && pindexBest && pindexRescan && pindexBest->nHeight > pindexRescan->nHeight) { - InitMessage(_("Rescanning...")); + uiInterface.InitMessage(_("Rescanning...")); printf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); pwalletMain->ScanForWalletTransactions(pindexRescan, true); printf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart); } - InitMessage(_("Done loading")); - printf("Done loading\n"); - - //// debug print - printf("mapBlockIndex.size() = %d\n", mapBlockIndex.size()); - printf("nBestHeight = %d\n", nBestHeight); - printf("setKeyPool.size() = %d\n", pwalletMain->setKeyPool.size()); - printf("mapWallet.size() = %d\n", pwalletMain->mapWallet.size()); - printf("mapAddressBook.size() = %d\n", pwalletMain->mapAddressBook.size()); - - if (!strErrors.str().empty()) - { - ThreadSafeMessageBox(strErrors.str(), _("NovaCoin"), wxOK | wxICON_ERROR | wxMODAL); - return false; - } - - // Add wallet transactions that aren't already in a block to mapTransactions - pwalletMain->ReacceptWalletTransactions(); - - // Note: Bitcoin-QT stores several settings in the wallet, so we want - // to load the wallet BEFORE parsing command-line arguments, so - // the command-line/bitcoin.conf settings override GUI setting. - - // - // Parameters - // - if (GetBoolArg("-printblockindex") || GetBoolArg("-printblocktree")) - { - PrintBlockTree(); - return false; - } - - if (mapArgs.count("-timeout")) - { - int nNewTimeout = GetArg("-timeout", 5000); - if (nNewTimeout > 0 && nNewTimeout < 600000) - nConnectTimeout = nNewTimeout; - } + // ********************************************************* Step 9: import blocks - if (mapArgs.count("-printblock")) + if (mapArgs.count("-loadblock")) { - string strMatch = mapArgs["-printblock"]; - int nFound = 0; - for (map::iterator mi = mapBlockIndex.begin(); mi != mapBlockIndex.end(); ++mi) - { - uint256 hash = (*mi).first; - if (strncmp(hash.ToString().c_str(), strMatch.c_str(), strMatch.size()) == 0) - { - CBlockIndex* pindex = (*mi).second; - CBlock block; - block.ReadFromDisk(pindex); - block.BuildMerkleTree(); - block.print(); - printf("\n"); - nFound++; - } - } - if (nFound == 0) - printf("No blocks matching %s were found\n", strMatch.c_str()); - return false; - } + uiInterface.InitMessage(_("Importing blockchain data file.")); - if (mapArgs.count("-proxy")) - { - fUseProxy = true; - addrProxy = CService(mapArgs["-proxy"], 9050); - if (!addrProxy.IsValid()) + BOOST_FOREACH(string strFile, mapMultiArgs["-loadblock"]) { - ThreadSafeMessageBox(_("Invalid -proxy address"), _("PPCcoin"), wxOK | wxMODAL); - return false; + FILE *file = fopen(strFile.c_str(), "rb"); + if (file) + LoadExternalBlockFile(file); } } - bool fTor = (fUseProxy && addrProxy.GetPort() == 9050); - if (fTor) - { - // Use SoftSetBoolArg here so user can override any of these if they wish. - // Note: the GetBoolArg() calls for all of these must happen later. - SoftSetBoolArg("-listen", false); - SoftSetBoolArg("-irc", false); - SoftSetBoolArg("-dnsseed", false); - SoftSetBoolArg("-upnp", false); - SoftSetBoolArg("-dns", false); - } + filesystem::path pathBootstrap = GetDataDir() / "bootstrap.dat"; + if (filesystem::exists(pathBootstrap)) { + uiInterface.InitMessage(_("Importing bootstrap blockchain data file.")); - fAllowDNS = GetBoolArg("-dns"); - fNoListen = !GetBoolArg("-listen", true); - - // Continue to put "/P2SH/" in the coinbase to monitor - // BIP16 support. - // This can be removed eventually... - const char* pszP2SH = "/P2SH/"; - COINBASE_FLAGS << std::vector(pszP2SH, pszP2SH+strlen(pszP2SH)); - - if (!fNoListen) - { - std::string strError; - if (!BindListenPort(strError)) - { - ThreadSafeMessageBox(strError, _("NovaCoin"), wxOK | wxMODAL); - return false; + FILE *file = fopen(pathBootstrap.string().c_str(), "rb"); + if (file) { + filesystem::path pathBootstrapOld = GetDataDir() / "bootstrap.dat.old"; + LoadExternalBlockFile(file); + RenameOver(pathBootstrap, pathBootstrapOld); } } - if (mapArgs.count("-addnode")) - { - BOOST_FOREACH(string strAddr, mapMultiArgs["-addnode"]) - { - CAddress addr(CService(strAddr, GetDefaultPort(), fAllowDNS)); - addr.nTime = 0; // so it won't relay unless successfully connected - if (addr.IsValid()) - addrman.Add(addr, CNetAddr("127.0.0.1")); - } - } + // ********************************************************* Step 10: load peers - if (mapArgs.count("-paytxfee")) - { - if (!ParseMoney(mapArgs["-paytxfee"], nTransactionFee) || nTransactionFee < MIN_TX_FEE) - { - ThreadSafeMessageBox(_("Invalid amount for -paytxfee="), _("NovaCoin"), wxOK | wxMODAL); - return false; - } - if (nTransactionFee > 0.25 * COIN) - ThreadSafeMessageBox(_("Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction."), _("NovaCoin"), wxOK | wxICON_EXCLAMATION | wxMODAL); - } + uiInterface.InitMessage(_("Loading addresses...")); + printf("Loading addresses...\n"); + nStart = GetTimeMillis(); - if (mapArgs.count("-reservebalance")) // ppcoin: reserve balance amount { - int64 nReserveBalance = 0; - if (!ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) - { - ThreadSafeMessageBox(_("Invalid amount for -reservebalance="), _("NovaCoin"), wxOK | wxMODAL); - return false; - } + CAddrDB adb; + if (!adb.Read(addrman)) + printf("Invalid or missing peers.dat; recreating\n"); } - if (mapArgs.count("-checkpointkey")) // ppcoin: checkpoint master priv key - { - if (!Checkpoints::SetCheckpointPrivKey(GetArg("-checkpointkey", ""))) - ThreadSafeMessageBox(_("Unable to sign checkpoint, wrong checkpointkey?\n"), _("NovaCoin"), wxOK | wxMODAL); - } + printf("Loaded %i addresses from peers.dat %"PRI64d"ms\n", + addrman.size(), GetTimeMillis() - nStart); + + // ********************************************************* Step 11: start node - // - // Start the node - // if (!CheckDiskSpace()) return false; RandAddSeedPerfmon(); - if (!CreateThread(StartNode, NULL)) - ThreadSafeMessageBox(_("Error: CreateThread(StartNode) failed"), _("NovaCoin"), wxOK | wxMODAL); + //// debug print + printf("mapBlockIndex.size() = %"PRIszu"\n", mapBlockIndex.size()); + printf("nBestHeight = %d\n", nBestHeight); + printf("setKeyPool.size() = %"PRIszu"\n", pwalletMain->setKeyPool.size()); + printf("mapWallet.size() = %"PRIszu"\n", pwalletMain->mapWallet.size()); + printf("mapAddressBook.size() = %"PRIszu"\n", pwalletMain->mapAddressBook.size()); + + if (!NewThread(StartNode, NULL)) + InitError(_("Error: could not start node")); if (fServer) - CreateThread(ThreadRPCServer, NULL); + NewThread(ThreadRPCServer, NULL); -#ifdef QT_GUI - if (GetStartOnSystemStartup()) - SetStartOnSystemStartup(true); // Remove startup links -#endif + // ********************************************************* Step 12: finished + + uiInterface.InitMessage(_("Done loading")); + printf("Done loading\n"); + + if (!strErrors.str().empty()) + return InitError(strErrors.str()); + + // Add wallet transactions that aren't already in a block to mapTransactions + pwalletMain->ReacceptWalletTransactions(); #if !defined(QT_GUI) + // Loop until process is exit()ed from shutdown() function, + // called from ThreadRPCServer thread when a "stop" command is received. while (1) Sleep(5000); #endif return true; } - diff --git a/src/init.h b/src/init.h index cc276a2..8308ee6 100644 --- a/src/init.h +++ b/src/init.h @@ -11,7 +11,7 @@ extern CWallet* pwalletMain; void StartShutdown(); void Shutdown(void* parg); -bool AppInit(int argc, char* argv[]); -bool AppInit2(int argc, char* argv[]); +bool AppInit2(); +std::string HelpMessage(); #endif diff --git a/src/irc.cpp b/src/irc.cpp index a968023..2057419 100644 --- a/src/irc.cpp +++ b/src/irc.cpp @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2013 NovaCoin Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -13,7 +12,6 @@ using namespace std; using namespace boost; int nGotIRCAddresses = 0; -bool fGotExternalIP = false; void ThreadIRCSeed2(void* parg); @@ -178,8 +176,6 @@ bool GetIPFromIRC(SOCKET hSocket, string strMyName, CNetAddr& ipRet) // Hybrid IRC used by lfnet always returns IP when you userhost yourself, // but in case another IRC is ever used this should work. printf("GetIPFromIRC() got userhost %s\n", strHost.c_str()); - if (fUseProxy) - return false; CNetAddr addr(strHost, true); if (!addr.IsValid()) return false; @@ -192,7 +188,9 @@ bool GetIPFromIRC(SOCKET hSocket, string strMyName, CNetAddr& ipRet) void ThreadIRCSeed(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadIRCSeed(parg)); + // Make this thread recognisable as the IRC seeding thread + RenameThread("bitcoin-ircseed"); + try { ThreadIRCSeed2(parg); @@ -202,22 +200,27 @@ void ThreadIRCSeed(void* parg) } catch (...) { PrintExceptionContinue(NULL, "ThreadIRCSeed()"); } - printf("ThreadIRCSeed exiting\n"); + printf("ThreadIRCSeed exited\n"); } void ThreadIRCSeed2(void* parg) { - /* Dont advertise on IRC if we don't allow incoming connections */ - if (mapArgs.count("-connect") || fNoListen) + // Don't connect to IRC if we won't use IPv4 connections. + if (IsLimited(NET_IPV4)) + return; + + // ... or if we won't make outbound connections and won't accept inbound ones. + if (mapArgs.count("-connect") && fNoListen) return; + // ... or if IRC is not enabled. if (!GetBoolArg("-irc", true)) return; printf("ThreadIRCSeed started\n"); int nErrorWait = 10; int nRetryWait = 10; - bool fNameInUse = false; + int nNameRetry = 0; while (!fShutdown) { @@ -249,11 +252,15 @@ void ThreadIRCSeed2(void* parg) return; } + CNetAddr addrIPv4("1.2.3.4"); // arbitrary IPv4 address to make GetLocal prefer IPv4 addresses + CService addrLocal; string strMyName; - if (addrLocalHost.IsRoutable() && !fUseProxy && !fNameInUse) - strMyName = EncodeAddress(addrLocalHost); - else - strMyName = strprintf("x%u", GetRand(1000000000)); + // Don't use our IP as our nick if we're not listening + // or if it keeps failing because the nick is already in use. + if (!fNoListen && GetLocal(addrLocal, &addrIPv4) && nNameRetry<3) + strMyName = EncodeAddress(GetLocalAddress(&addrConnect)); + if (strMyName == "") + strMyName = strprintf("x%"PRI64u"", GetRand(1000000000)); Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str()); Send(hSocket, strprintf("USER %s 8 * : %s\r", strMyName.c_str(), strMyName.c_str()).c_str()); @@ -266,7 +273,7 @@ void ThreadIRCSeed2(void* parg) if (nRet == 2) { printf("IRC name already in use\n"); - fNameInUse = true; + nNameRetry++; Wait(10); continue; } @@ -276,6 +283,7 @@ void ThreadIRCSeed2(void* parg) else return; } + nNameRetry = 0; Sleep(500); // Get our external IP from the IRC server and re-nick before joining the channel @@ -283,23 +291,23 @@ void ThreadIRCSeed2(void* parg) if (GetIPFromIRC(hSocket, strMyName, addrFromIRC)) { printf("GetIPFromIRC() returned %s\n", addrFromIRC.ToString().c_str()); - if (!fUseProxy && addrFromIRC.IsRoutable()) + // Don't use our IP as our nick if we're not listening + if (!fNoListen && addrFromIRC.IsRoutable()) { // IRC lets you to re-nick - fGotExternalIP = true; - addrLocalHost.SetIP(addrFromIRC); - strMyName = EncodeAddress(addrLocalHost); + AddLocal(addrFromIRC, LOCAL_IRC); + strMyName = EncodeAddress(GetLocalAddress(&addrConnect)); Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str()); } } - + if (fTestNet) { Send(hSocket, "JOIN #novacoinTEST\r"); Send(hSocket, "WHO #novacoinTEST\r"); } else { // randomly join #novacoin00-#novacoin05 // int channel_number = GetRandInt(5); - + // Channel number is always 0 for initial release int channel_number = 0; Send(hSocket, strprintf("JOIN #novacoin%02d\r", channel_number).c_str()); diff --git a/src/irc.h b/src/irc.h index 4a42f76..119aeb3 100644 --- a/src/irc.h +++ b/src/irc.h @@ -8,6 +8,5 @@ void ThreadIRCSeed(void* parg); extern int nGotIRCAddresses; -extern bool fGotExternalIP; #endif diff --git a/src/kernel.cpp b/src/kernel.cpp index 719d780..663e811 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -9,6 +9,9 @@ using namespace std; +extern int nStakeMaxAge; +extern int nStakeTargetSpacing; + // Modifier interval: time to elapse before new modifier is computed // Set to 6-hour for production network and 20-minute for test network unsigned int nModifierInterval = MODIFIER_INTERVAL; @@ -136,7 +139,7 @@ bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModif // Sort candidate blocks by timestamp vector > vSortedByTimestamp; - vSortedByTimestamp.reserve(64 * nModifierInterval / STAKE_TARGET_SPACING); + vSortedByTimestamp.reserve(64 * nModifierInterval / nStakeTargetSpacing); int64 nSelectionInterval = GetStakeModifierSelectionInterval(); int64 nSelectionIntervalStart = (pindexPrev->GetBlockTime() / nModifierInterval) * nModifierInterval - nSelectionInterval; const CBlockIndex* pindex = pindexPrev; @@ -274,7 +277,7 @@ bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned // v0.3 protocol kernel hash weight starts from 0 at the 30-day min age // this change increases active coins participating the hash and helps // to secure the network when proof-of-stake difficulty is low - int64 nTimeWeight = min((int64)nTimeTx - txPrev.nTime, (int64)STAKE_MAX_AGE) - nStakeMinAge; + int64 nTimeWeight = min((int64)nTimeTx - txPrev.nTime, (int64)nStakeMaxAge) - nStakeMinAge; CBigNum bnCoinDayWeight = CBigNum(nValueIn) * nTimeWeight / COIN / (24 * 60 * 60); // Calculate hash diff --git a/src/key.cpp b/src/key.cpp index 18b4672..23f3152 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -8,7 +8,6 @@ #include #include "key.h" -#include "util.h" // Generate a private key from just the secret parameter int EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key) @@ -49,7 +48,7 @@ err: // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields // recid selects which key is recovered -// if check is nonzero, additional checks are performed +// if check is non-zero, additional checks are performed int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) { if (!eckey) return 0; @@ -130,6 +129,8 @@ void CKey::SetCompressedPubKey() void CKey::Reset() { fCompressedPubKey = false; + if (pkey != NULL) + EC_KEY_free(pkey); pkey = EC_KEY_new_by_curve_name(NID_secp256k1); if (pkey == NULL) throw key_error("CKey::CKey() : EC_KEY_new_by_curve_name failed"); @@ -138,6 +139,7 @@ void CKey::Reset() CKey::CKey() { + pkey = NULL; Reset(); } @@ -184,10 +186,24 @@ void CKey::MakeNewKey(bool fCompressed) bool CKey::SetPrivKey(const CPrivKey& vchPrivKey) { const unsigned char* pbegin = &vchPrivKey[0]; - if (!d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size())) - return false; - fSet = true; - return true; + if (d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size())) + { + // In testing, d2i_ECPrivateKey can return true + // but fill in pkey with a key that fails + // EC_KEY_check_key, so: + if (EC_KEY_check_key(pkey)) + { + fSet = true; + return true; + } + } + // If vchPrivKey data is bad d2i_ECPrivateKey() can + // leave pkey in a state where calling EC_KEY_free() + // crashes. To avoid that, set pkey to NULL and + // leak the memory (a leak is better than a crash) + pkey = NULL; + Reset(); + return false; } bool CKey::SetSecret(const CSecret& vchSecret, bool fCompressed) @@ -240,18 +256,22 @@ CPrivKey CKey::GetPrivKey() const return vchPrivKey; } -bool CKey::SetPubKey(const std::vector& vchPubKey) +bool CKey::SetPubKey(const CPubKey& vchPubKey) { - const unsigned char* pbegin = &vchPubKey[0]; - if (!o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.size())) - return false; - fSet = true; - if (vchPubKey.size() == 33) - SetCompressedPubKey(); - return true; + const unsigned char* pbegin = &vchPubKey.vchPubKey[0]; + if (o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.vchPubKey.size())) + { + fSet = true; + if (vchPubKey.vchPubKey.size() == 33) + SetCompressedPubKey(); + return true; + } + pkey = NULL; + Reset(); + return false; } -std::vector CKey::GetPubKey() const +CPubKey CKey::GetPubKey() const { int nSize = i2o_ECPublicKey(pkey, NULL); if (!nSize) @@ -260,7 +280,7 @@ std::vector CKey::GetPubKey() const unsigned char* pbegin = &vchPubKey[0]; if (i2o_ECPublicKey(pkey, &pbegin) != nSize) throw key_error("CKey::GetPubKey() : i2o_ECPublicKey returned unexpected size"); - return vchPubKey; + return CPubKey(vchPubKey); } bool CKey::Sign(uint256 hash, std::vector& vchSig) @@ -375,6 +395,9 @@ bool CKey::IsValid() if (!fSet) return false; + if (!EC_KEY_check_key(pkey)) + return false; + bool fCompr; CSecret secret = GetSecret(fCompr); CKey key2; diff --git a/src/key.h b/src/key.h index bd58c84..c98f52e 100644 --- a/src/key.h +++ b/src/key.h @@ -9,7 +9,9 @@ #include #include "allocators.h" +#include "serialize.h" #include "uint256.h" +#include "util.h" #include // for EC_KEY definition @@ -42,8 +44,62 @@ public: explicit key_error(const std::string& str) : std::runtime_error(str) {} }; +/** A reference to a CKey: the Hash160 of its serialized public key */ +class CKeyID : public uint160 +{ +public: + CKeyID() : uint160(0) { } + CKeyID(const uint160 &in) : uint160(in) { } +}; + +/** A reference to a CScript: the Hash160 of its serialization (see script.h) */ +class CScriptID : public uint160 +{ +public: + CScriptID() : uint160(0) { } + CScriptID(const uint160 &in) : uint160(in) { } +}; + +/** An encapsulated public key. */ +class CPubKey { +private: + std::vector vchPubKey; + friend class CKey; + +public: + CPubKey() { } + CPubKey(const std::vector &vchPubKeyIn) : vchPubKey(vchPubKeyIn) { } + friend bool operator==(const CPubKey &a, const CPubKey &b) { return a.vchPubKey == b.vchPubKey; } + friend bool operator!=(const CPubKey &a, const CPubKey &b) { return a.vchPubKey != b.vchPubKey; } + friend bool operator<(const CPubKey &a, const CPubKey &b) { return a.vchPubKey < b.vchPubKey; } + + IMPLEMENT_SERIALIZE( + READWRITE(vchPubKey); + ) + + CKeyID GetID() const { + return CKeyID(Hash160(vchPubKey)); + } + + uint256 GetHash() const { + return Hash(vchPubKey.begin(), vchPubKey.end()); + } + + bool IsValid() const { + return vchPubKey.size() == 33 || vchPubKey.size() == 65; + } + + bool IsCompressed() const { + return vchPubKey.size() == 33; + } + + std::vector Raw() const { + return vchPubKey; + } +}; + -// secure_allocator is defined in serialize.h +// secure_allocator is defined in allocators.h // CPrivKey is a serialized private key, with all parameters included (279 bytes) typedef std::vector > CPrivKey; // CSecret is a serialization of just the secret parameter (32 bytes) @@ -78,8 +134,8 @@ public: bool SetSecret(const CSecret& vchSecret, bool fCompressed = false); CSecret GetSecret(bool &fCompressed) const; CPrivKey GetPrivKey() const; - bool SetPubKey(const std::vector& vchPubKey); - std::vector GetPubKey() const; + bool SetPubKey(const CPubKey& vchPubKey); + CPubKey GetPubKey() const; bool Sign(uint256 hash, std::vector& vchSig); diff --git a/src/keystore.cpp b/src/keystore.cpp index 3cb9576..e0cf805 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -6,7 +6,7 @@ #include "keystore.h" #include "script.h" -bool CKeyStore::GetPubKey(const CBitcoinAddress &address, std::vector &vchPubKeyOut) const +bool CKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { CKey key; if (!GetKey(address, key)) @@ -21,7 +21,7 @@ bool CBasicKeyStore::AddKey(const CKey& key) CSecret secret = key.GetSecret(fCompressed); { LOCK(cs_KeyStore); - mapKeys[CBitcoinAddress(key.GetPubKey())] = make_pair(secret, fCompressed); + mapKeys[key.GetPubKey().GetID()] = make_pair(secret, fCompressed); } return true; } @@ -30,12 +30,12 @@ bool CBasicKeyStore::AddCScript(const CScript& redeemScript) { { LOCK(cs_KeyStore); - mapScripts[Hash160(redeemScript)] = redeemScript; + mapScripts[redeemScript.GetID()] = redeemScript; } return true; } -bool CBasicKeyStore::HaveCScript(const uint160& hash) const +bool CBasicKeyStore::HaveCScript(const CScriptID& hash) const { bool result; { @@ -46,7 +46,7 @@ bool CBasicKeyStore::HaveCScript(const uint160& hash) const } -bool CBasicKeyStore::GetCScript(const uint160 &hash, CScript& redeemScriptOut) const +bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const { { LOCK(cs_KeyStore); @@ -73,6 +73,20 @@ bool CCryptoKeyStore::SetCrypted() return true; } +bool CCryptoKeyStore::Lock() +{ + if (!SetCrypted()) + return false; + + { + LOCK(cs_KeyStore); + vMasterKey.clear(); + } + + NotifyStatusChanged(this); + return true; +} + bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) { { @@ -83,10 +97,10 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); for (; mi != mapCryptedKeys.end(); ++mi) { - const std::vector &vchPubKey = (*mi).second.first; + const CPubKey &vchPubKey = (*mi).second.first; const std::vector &vchCryptedSecret = (*mi).second.second; CSecret vchSecret; - if(!DecryptSecret(vMasterKeyIn, vchCryptedSecret, Hash(vchPubKey.begin(), vchPubKey.end()), vchSecret)) + if(!DecryptSecret(vMasterKeyIn, vchCryptedSecret, vchPubKey.GetHash(), vchSecret)) return false; if (vchSecret.size() != 32) return false; @@ -99,6 +113,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) } vMasterKey = vMasterKeyIn; } + NotifyStatusChanged(this); return true; } @@ -113,9 +128,9 @@ bool CCryptoKeyStore::AddKey(const CKey& key) return false; std::vector vchCryptedSecret; - std::vector vchPubKey = key.GetPubKey(); + CPubKey vchPubKey = key.GetPubKey(); bool fCompressed; - if (!EncryptSecret(vMasterKey, key.GetSecret(fCompressed), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) + if (!EncryptSecret(vMasterKey, key.GetSecret(fCompressed), vchPubKey.GetHash(), vchCryptedSecret)) return false; if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret)) @@ -125,19 +140,19 @@ bool CCryptoKeyStore::AddKey(const CKey& key) } -bool CCryptoKeyStore::AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret) +bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { { LOCK(cs_KeyStore); if (!SetCrypted()) return false; - mapCryptedKeys[CBitcoinAddress(vchPubKey)] = make_pair(vchPubKey, vchCryptedSecret); + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); } return true; } -bool CCryptoKeyStore::GetKey(const CBitcoinAddress &address, CKey& keyOut) const +bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const { { LOCK(cs_KeyStore); @@ -147,10 +162,10 @@ bool CCryptoKeyStore::GetKey(const CBitcoinAddress &address, CKey& keyOut) const CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); if (mi != mapCryptedKeys.end()) { - const std::vector &vchPubKey = (*mi).second.first; + const CPubKey &vchPubKey = (*mi).second.first; const std::vector &vchCryptedSecret = (*mi).second.second; CSecret vchSecret; - if (!DecryptSecret(vMasterKey, vchCryptedSecret, Hash(vchPubKey.begin(), vchPubKey.end()), vchSecret)) + if (!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret)) return false; if (vchSecret.size() != 32) return false; @@ -162,7 +177,7 @@ bool CCryptoKeyStore::GetKey(const CBitcoinAddress &address, CKey& keyOut) const return false; } -bool CCryptoKeyStore::GetPubKey(const CBitcoinAddress &address, std::vector& vchPubKeyOut) const +bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const { { LOCK(cs_KeyStore); @@ -192,10 +207,10 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) CKey key; if (!key.SetSecret(mKey.second.first, mKey.second.second)) return false; - const std::vector vchPubKey = key.GetPubKey(); + const CPubKey vchPubKey = key.GetPubKey(); std::vector vchCryptedSecret; bool fCompressed; - if (!EncryptSecret(vMasterKeyIn, key.GetSecret(fCompressed), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) + if (!EncryptSecret(vMasterKeyIn, key.GetSecret(fCompressed), vchPubKey.GetHash(), vchCryptedSecret)) return false; if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; diff --git a/src/keystore.h b/src/keystore.h index 3f1f0ce..ab369bb 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -6,8 +6,8 @@ #define BITCOIN_KEYSTORE_H #include "crypter.h" -#include "util.h" -#include "base58.h" +#include "sync.h" +#include class CScript; @@ -24,17 +24,17 @@ public: virtual bool AddKey(const CKey& key) =0; // Check whether a key corresponding to a given address is present in the store. - virtual bool HaveKey(const CBitcoinAddress &address) const =0; - virtual bool GetKey(const CBitcoinAddress &address, CKey& keyOut) const =0; - virtual void GetKeys(std::set &setAddress) const =0; - virtual bool GetPubKey(const CBitcoinAddress &address, std::vector& vchPubKeyOut) const; + virtual bool HaveKey(const CKeyID &address) const =0; + virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0; + virtual void GetKeys(std::set &setAddress) const =0; + virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; // Support for BIP 0013 : see https://en.bitcoin.it/wiki/BIP_0013 virtual bool AddCScript(const CScript& redeemScript) =0; - virtual bool HaveCScript(const uint160 &hash) const =0; - virtual bool GetCScript(const uint160 &hash, CScript& redeemScriptOut) const =0; + virtual bool HaveCScript(const CScriptID &hash) const =0; + virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const =0; - virtual bool GetSecret(const CBitcoinAddress &address, CSecret& vchSecret, bool &fCompressed) const + virtual bool GetSecret(const CKeyID &address, CSecret& vchSecret, bool &fCompressed) const { CKey key; if (!GetKey(address, key)) @@ -44,8 +44,8 @@ public: } }; -typedef std::map > KeyMap; -typedef std::map ScriptMap; +typedef std::map > KeyMap; +typedef std::map ScriptMap; /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore @@ -56,7 +56,7 @@ protected: public: bool AddKey(const CKey& key); - bool HaveKey(const CBitcoinAddress &address) const + bool HaveKey(const CKeyID &address) const { bool result; { @@ -65,7 +65,7 @@ public: } return result; } - void GetKeys(std::set &setAddress) const + void GetKeys(std::set &setAddress) const { setAddress.clear(); { @@ -78,7 +78,7 @@ public: } } } - bool GetKey(const CBitcoinAddress &address, CKey &keyOut) const + bool GetKey(const CKeyID &address, CKey &keyOut) const { { LOCK(cs_KeyStore); @@ -93,11 +93,11 @@ public: return false; } virtual bool AddCScript(const CScript& redeemScript); - virtual bool HaveCScript(const uint160 &hash) const; - virtual bool GetCScript(const uint160 &hash, CScript& redeemScriptOut) const; + virtual bool HaveCScript(const CScriptID &hash) const; + virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const; }; -typedef std::map, std::vector > > CryptedKeyMap; +typedef std::map > > CryptedKeyMap; /** Keystore which keeps the private keys encrypted. * It derives from the basic key store, which is used if no encryption is active. @@ -143,22 +143,11 @@ public: return result; } - bool Lock() - { - if (!SetCrypted()) - return false; - - { - LOCK(cs_KeyStore); - vMasterKey.clear(); - } + bool Lock(); - return true; - } - - virtual bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); + virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); bool AddKey(const CKey& key); - bool HaveKey(const CBitcoinAddress &address) const + bool HaveKey(const CKeyID &address) const { { LOCK(cs_KeyStore); @@ -168,9 +157,9 @@ public: } return false; } - bool GetKey(const CBitcoinAddress &address, CKey& keyOut) const; - bool GetPubKey(const CBitcoinAddress &address, std::vector& vchPubKeyOut) const; - void GetKeys(std::set &setAddress) const + bool GetKey(const CKeyID &address, CKey& keyOut) const; + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; + void GetKeys(std::set &setAddress) const { if (!IsCrypted()) { @@ -185,6 +174,11 @@ public: mi++; } } + + /* Wallet status (encrypted, locked) changed. + * Note: Called without locks held. + */ + boost::signals2::signal NotifyStatusChanged; }; #endif diff --git a/src/main.cpp b/src/main.cpp index ff6f2bd..18d85f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,9 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "alert.h" #include "checkpoints.h" #include "db.h" #include "net.h" @@ -37,8 +37,10 @@ static CBigNum bnProofOfWorkLimit(~uint256(0) >> 20); static CBigNum bnProofOfStakeLimit(~uint256(0) >> 24); static CBigNum bnProofOfStakeHardLimit(~uint256(0) >> 30); static CBigNum bnInitialHashTarget(~uint256(0) >> 20); -unsigned int nStakeMinAge = STAKE_MIN_AGE; -int nCoinbaseMaturity = COINBASE_MATURITY_PPC; +unsigned int nStakeMinAge = 60 * 60 * 24 * 30; // minimum age for coin age +unsigned int nStakeMaxAge = 60 * 60 * 24 * 90; // stake age of full weight +unsigned int nStakeTargetSpacing = 10 * 60; // 10-minute block spacing +int nCoinbaseMaturity = 500; CBlockIndex* pindexGenesisBlock = NULL; int nBestHeight = -1; CBigNum bnBestChainTrust = 0; @@ -69,7 +71,6 @@ int64 nHPSTimerStart; int64 nTransactionFee = MIN_TX_FEE; - ////////////////////////////////////////////////////////////////////////////// // // dispatching functions @@ -120,7 +121,7 @@ void static EraseFromWallets(uint256 hash) } // make sure all wallets know about the given transaction, in the given block -void static SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true) +void SyncWithWallets(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fConnect) { if (!fConnect) { @@ -203,7 +204,7 @@ bool AddOrphanTx(const CDataStream& vMsg) // at most 500 megabytes of orphans: if (pvMsg->size() > 5000) { - printf("ignoring large orphan tx (size: %u, hash: %s)\n", pvMsg->size(), hash.ToString().substr(0,10).c_str()); + printf("ignoring large orphan tx (size: %"PRIszu", hash: %s)\n", pvMsg->size(), hash.ToString().substr(0,10).c_str()); delete pvMsg; return false; } @@ -212,7 +213,7 @@ bool AddOrphanTx(const CDataStream& vMsg) BOOST_FOREACH(const CTxIn& txin, tx.vin) mapOrphanTransactionsByPrev[txin.prevout.hash].insert(make_pair(hash, pvMsg)); - printf("stored orphan tx %s (mapsz %u)\n", hash.ToString().substr(0,10).c_str(), + printf("stored orphan tx %s (mapsz %"PRIszu")\n", hash.ToString().substr(0,10).c_str(), mapOrphanTransactions.size()); return true; } @@ -291,6 +292,9 @@ bool CTransaction::ReadFromDisk(COutPoint prevout) bool CTransaction::IsStandard() const { + if (nVersion > CTransaction::CURRENT_VERSION) + return false; + BOOST_FOREACH(const CTxIn& txin, vin) { // Biggest 'standard' txin is a 3-signature 3-of-3 CHECKMULTISIG @@ -301,9 +305,12 @@ bool CTransaction::IsStandard() const if (!txin.scriptSig.IsPushOnly()) return false; } - BOOST_FOREACH(const CTxOut& txout, vout) + BOOST_FOREACH(const CTxOut& txout, vout) { if (!::IsStandard(txout.scriptPubKey)) return false; + if (txout.nValue == 0) + return false; + } return true; } @@ -458,10 +465,10 @@ bool CTransaction::CheckTransaction() const // Check for negative or overflow output values int64 nValueOut = 0; - for (int i = 0; i < vout.size(); i++) + for (unsigned int i = 0; i < vout.size(); i++) { const CTxOut& txout = vout[i]; - if (txout.IsEmpty() && (!IsCoinBase()) && (!IsCoinStake())) + if (txout.IsEmpty() && !IsCoinBase() && !IsCoinStake()) return DoS(100, error("CTransaction::CheckTransaction() : txout empty for user transaction")); // ppcoin: enforce minimum output amount if ((!txout.IsEmpty()) && txout.nValue < MIN_TXOUT_AMOUNT) @@ -497,6 +504,55 @@ bool CTransaction::CheckTransaction() const return true; } +int64 CTransaction::GetMinFee(unsigned int nBlockSize, bool fAllowFree, + enum GetMinFee_mode mode) const +{ + // Base fee is either MIN_TX_FEE or MIN_RELAY_TX_FEE + int64 nBaseFee = (mode == GMF_RELAY) ? MIN_RELAY_TX_FEE : MIN_TX_FEE; + + unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION); + unsigned int nNewBlockSize = nBlockSize + nBytes; + int64 nMinFee = (1 + (int64)nBytes / 1000) * nBaseFee; + + if (fAllowFree) + { + if (nBlockSize == 1) + { + // Transactions under 10K are free + // (about 4500 BTC if made of 50 BTC inputs) + if (nBytes < 10000) + nMinFee = 0; + } + else + { + // Free transaction area + if (nNewBlockSize < 27000) + nMinFee = 0; + } + } + + // To limit dust spam, require MIN_TX_FEE/MIN_RELAY_TX_FEE if any output is less than 0.01 + if (nMinFee < nBaseFee) + { + BOOST_FOREACH(const CTxOut& txout, vout) + if (txout.nValue < CENT) + nMinFee = nBaseFee; + } + + // Raise the price as the block approaches full + if (nBlockSize != 1 && nNewBlockSize >= MAX_BLOCK_SIZE_GEN/2) + { + if (nNewBlockSize >= MAX_BLOCK_SIZE_GEN) + return MAX_MONEY; + nMinFee *= MAX_BLOCK_SIZE_GEN / (MAX_BLOCK_SIZE_GEN - nNewBlockSize); + } + + if (!MoneyRange(nMinFee)) + nMinFee = MAX_MONEY; + return nMinFee; +} + + bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs) { @@ -509,6 +565,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) return tx.DoS(100, error("CTxMemPool::accept() : coinbase as individual tx")); + // ppcoin: coinstake is also only valid in a block, not as a loose transaction if (tx.IsCoinStake()) return tx.DoS(100, error("CTxMemPool::accept() : coinstake as individual tx")); @@ -571,7 +628,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, return error("CTxMemPool::accept() : FetchInputs found invalid tx %s", hash.ToString().substr(0,10).c_str()); if (pfMissingInputs) *pfMissingInputs = true; - return error("CTxMemPool::accept() : FetchInputs failed %s", hash.ToString().substr(0,10).c_str()); + return false; } // Check for non-standard pay-to-script-hash in inputs @@ -586,12 +643,15 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Don't accept it if it can't get into a block - if (nFees < tx.GetMinFee(1000, false, GMF_RELAY)) - return error("CTxMemPool::accept() : not enough fees"); + int64 txMinFee = tx.GetMinFee(1000, false, GMF_RELAY); + if (nFees < txMinFee) + return error("CTxMemPool::accept() : not enough fees %s, %"PRI64d" < %"PRI64d, + hash.ToString().c_str(), + nFees, txMinFee); // Continuously rate-limit free transactions // This mitigates 'penny-flooding' -- sending thousands of free transactions just to - // be annoying or make other's transactions take longer to confirm. + // be annoying or make others' transactions take longer to confirm. if (nFees < MIN_RELAY_TX_FEE) { static CCriticalSection cs; @@ -630,7 +690,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, printf("CTxMemPool::accept() : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); remove(*ptxOld); } - addUnchecked(tx); + addUnchecked(hash, tx); } ///// are we sure this is ok when loading transactions or restoring block txes @@ -638,7 +698,9 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, if (ptxOld) EraseFromWallets(ptxOld->GetHash()); - printf("CTxMemPool::accept() : accepted %s\n", hash.ToString().substr(0,10).c_str()); + printf("CTxMemPool::accept() : accepted %s (poolsz %"PRIszu")\n", + hash.ToString().substr(0,10).c_str(), + mapTx.size()); return true; } @@ -647,14 +709,11 @@ bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMi return mempool.accept(txdb, *this, fCheckInputs, pfMissingInputs); } -bool CTxMemPool::addUnchecked(CTransaction &tx) +bool CTxMemPool::addUnchecked(const uint256& hash, CTransaction &tx) { - printf("addUnchecked(): size %lu\n", mapTx.size()); // Add to memory pool without checking anything. Don't call this directly, // call CTxMemPool::accept to properly check the transaction first. { - LOCK(cs); - uint256 hash = tx.GetHash(); mapTx[hash] = tx; for (unsigned int i = 0; i < tx.vin.size(); i++) mapNextTx[tx.vin[i].prevout] = CInPoint(&mapTx[hash], i); @@ -681,6 +740,27 @@ bool CTxMemPool::remove(CTransaction &tx) return true; } +void CTxMemPool::clear() +{ + LOCK(cs); + mapTx.clear(); + mapNextTx.clear(); + ++nTransactionsUpdated; +} + +void CTxMemPool::queryHashes(std::vector& vtxid) +{ + vtxid.clear(); + + LOCK(cs); + vtxid.reserve(mapTx.size()); + for (map::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi) + vtxid.push_back((*mi).first); +} + + + + int CMerkleTx::GetDepthInMainChain(CBlockIndex* &pindexRet) const { if (hashBlock == 0 || nIndex == -1) @@ -735,6 +815,8 @@ bool CMerkleTx::AcceptToMemoryPool() return AcceptToMemoryPool(txdb); } + + bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) { @@ -803,11 +885,36 @@ bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock) return false; } + + + + + + + ////////////////////////////////////////////////////////////////////////////// // // CBlock and CBlockIndex // +static CBlockIndex* pblockindexFBBHLast; +CBlockIndex* FindBlockByHeight(int nHeight) +{ + CBlockIndex *pblockindex; + if (nHeight < nBestHeight / 2) + pblockindex = pindexGenesisBlock; + else + pblockindex = pindexBest; + if (pblockindexFBBHLast && abs(nHeight - pblockindex->nHeight) > abs(nHeight - pblockindexFBBHLast->nHeight)) + pblockindex = pblockindexFBBHLast; + while (pblockindex->nHeight > nHeight) + pblockindex = pblockindex->pprev; + while (pblockindex->nHeight < nHeight) + pblockindex = pblockindex->pnext; + pblockindexFBBHLast = pblockindex; + return pblockindex; +} + bool CBlock::ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions) { if (!fReadTransactions) @@ -886,7 +993,7 @@ int64 GetProofOfStakeReward(int64 nCoinAge) } static const int64 nTargetTimespan = 7 * 24 * 60 * 60; // one week -static const int64 nTargetSpacingWorkMax = 12 * STAKE_TARGET_SPACING; // 2-hour +static const int64 nTargetSpacingWorkMax = 12 * nStakeTargetSpacing; // 2-hour // // minimum amount of work that could possibly be required nTime after @@ -929,7 +1036,7 @@ unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fP bnTargetLimit = bnProofOfStakeHardLimit; else { - if(pindexLast->nHeight + 1 > 15000) + if(fTestNet || (pindexLast->nHeight + 1 > 15000)) bnTargetLimit = bnProofOfStakeLimit; else if(pindexLast->nHeight + 1 > 14060) bnTargetLimit = bnProofOfStakeHardLimit; @@ -952,7 +1059,7 @@ unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fP // ppcoin: retarget with exponential moving toward target spacing CBigNum bnNew; bnNew.SetCompact(pindexPrev->nBits); - int64 nTargetSpacing = fProofOfStake? STAKE_TARGET_SPACING : min(nTargetSpacingWorkMax, (int64) STAKE_TARGET_SPACING * (1 + pindexLast->nHeight - pindexPrev->nHeight)); + int64 nTargetSpacing = fProofOfStake? nStakeTargetSpacing : min(nTargetSpacingWorkMax, (int64) nStakeTargetSpacing * (1 + pindexLast->nHeight - pindexPrev->nHeight)); int64 nInterval = nTargetTimespan / nTargetSpacing; bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing); bnNew /= ((nInterval + 1) * nTargetSpacing); @@ -1006,11 +1113,16 @@ void static InvalidChainFound(CBlockIndex* pindexNew) { bnBestInvalidTrust = pindexNew->bnChainTrust; CTxDB().WriteBestInvalidTrust(bnBestInvalidTrust); - MainFrameRepaint(); + uiInterface.NotifyBlocksChanged(); } - printf("InvalidChainFound: invalid block=%s height=%d trust=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, CBigNum(pindexNew->bnChainTrust).ToString().c_str()); - printf("InvalidChainFound: current best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(bnBestChainTrust).ToString().c_str()); - // ppcoin: should not enter safe mode for longer invalid chain + + printf("InvalidChainFound: invalid block=%s height=%d trust=%s date=%s\n", + pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, + pindexNew->bnChainTrust.ToString().c_str(), DateTimeStrFormat("%x %H:%M:%S", + pindexNew->GetBlockTime()).c_str()); + printf("InvalidChainFound: current best=%s height=%d trust=%s date=%s\n", + hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainTrust.ToString().c_str(), + DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); } void CBlock::UpdateTime(const CBlockIndex* pindexPrev) @@ -1057,7 +1169,7 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb) // Remove transaction from index // This can fail if a duplicate of this transaction was in a chain that got // reorganized away. This is only possible if this transaction was completely - // spent, so erasing it would be a no-op anway. + // spent, so erasing it would be a no-op anyway. txdb.EraseTxIndex(*this); return true; @@ -1120,7 +1232,7 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map& mapTes } } - // Make sure all prevout.n's are valid: + // Make sure all prevout.n indexes are valid: for (unsigned int i = 0; i < vin.size(); i++) { const COutPoint prevout = vin[i].prevout; @@ -1132,7 +1244,7 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map& mapTes // Revisit this if/when transaction replacement is implemented and allows // adding inputs: fInvalid = true; - return DoS(100, error("FetchInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); + return DoS(100, error("FetchInputs() : %s prevout.n out of range %d %"PRIszu" %"PRIszu" prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); } } @@ -1201,13 +1313,13 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, CTransaction& txPrev = inputs[prevout.hash].second; if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) - return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); + return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %"PRIszu" %"PRIszu" prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); - // If prev is coinbase/coinstake, check that it's matured + // If prev is coinbase or coinstake, check that it's matured if (txPrev.IsCoinBase() || txPrev.IsCoinStake()) for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < nCoinbaseMaturity; pindex = pindex->pprev) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) - return error("ConnectInputs() : tried to spend coinbase/coinstake at depth %d", pindexBlock->nHeight - pindex->nHeight); + return error("ConnectInputs() : tried to spend %s at depth %d", txPrev.IsCoinBase() ? "coinbase" : "coinstake", pindexBlock->nHeight - pindex->nHeight); // ppcoin: check transaction timestamp if (txPrev.nTime > nTime) @@ -1284,6 +1396,7 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, // ppcoin: enforce transaction fees for every block if (nTxFee < GetMinFee()) return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(GetMinFee()).c_str(), FormatMoney(nTxFee).c_str())) : false; + nFees += nTxFee; if (!MoneyRange(nFees)) return DoS(100, error("ConnectInputs() : nFees out of range")); @@ -1367,10 +1480,10 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } -bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) +bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) { // Check it again in case a previous version let a bad block in - if (!CheckBlock()) + if (!CheckBlock(!fJustCheck, !fJustCheck)) return false; // Do not allow blocks that contain transactions which 'overwrite' older transactions, @@ -1380,23 +1493,22 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) // being sent to another address. // See BIP30 and http://r6.ca/blog/20120206T005236Z.html for more information. // This logic is not necessary for memory pool transactions, as AcceptToMemoryPool - // already refuses previously-known transaction id's entirely. - BOOST_FOREACH(CTransaction& tx, vtx) - { - CTxIndex txindexOld; - if (txdb.ReadTxIndex(tx.GetHash(), txindexOld)) - { - BOOST_FOREACH(CDiskTxPos &pos, txindexOld.vSpent) - if (pos.IsNull()) - return false; - } - } - - // BIP16 always active - bool fStrictPayToScriptHash = true; + // already refuses previously-known transaction ids entirely. + // This rule was originally applied all blocks whose timestamp was after March 15, 2012, 0:00 UTC. + // Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the + // two in the chain that violate it. This prevents exploiting the issue against nodes in their + // initial block download. + bool fEnforceBIP30 = true; // Always active in NovaCoin + bool fStrictPayToScriptHash = true; // Always active in NovaCoin //// issue here: it doesn't know the version - unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); + unsigned int nTxPos; + if (fJustCheck) + // FetchInputs treats CDiskTxPos(1,1,1) as a special "refer to memorypool" indicator + // Since we're just checking the block and not actually connecting it, it might not (and probably shouldn't) be on the disk to get the transaction from + nTxPos = 1; + else + nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); map mapQueuedChanges; int64 nFees = 0; @@ -1405,12 +1517,24 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) unsigned int nSigOps = 0; BOOST_FOREACH(CTransaction& tx, vtx) { + uint256 hashTx = tx.GetHash(); + + if (fEnforceBIP30) { + CTxIndex txindexOld; + if (txdb.ReadTxIndex(hashTx, txindexOld)) { + BOOST_FOREACH(CDiskTxPos &pos, txindexOld.vSpent) + if (pos.IsNull()) + return false; + } + } + nSigOps += tx.GetLegacySigOpCount(); if (nSigOps > MAX_BLOCK_SIGOPS) return DoS(100, error("ConnectBlock() : too many sigops")); CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); - nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); + if (!fJustCheck) + nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); MapPrevTx mapInputs; if (tx.IsCoinBase()) @@ -1442,7 +1566,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return false; } - mapQueuedChanges[tx.GetHash()] = CTxIndex(posThisTx, tx.vout.size()); + mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size()); } // ppcoin: track money supply and mint amount info @@ -1451,6 +1575,14 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) if (!txdb.WriteBlockIndex(CDiskBlockIndex(pindex))) return error("Connect() : WriteBlockIndex for pindex failed"); + // ppcoin: fees are not collected by miners as in bitcoin + // ppcoin: fees are destroyed to compensate the entire network + if (fDebug && GetBoolArg("-printcreation")) + printf("ConnectBlock() : destroy=%s nFees=%"PRI64d"\n", FormatMoney(nFees).c_str(), nFees); + + if (fJustCheck) + return true; + // Write queued txindex changes for (map::iterator mi = mapQueuedChanges.begin(); mi != mapQueuedChanges.end(); ++mi) { @@ -1458,11 +1590,6 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return error("ConnectBlock() : UpdateTxIndex failed"); } - // ppcoin: fees are not collected by miners as in bitcoin - // ppcoin: fees are destroyed to compensate the entire network - if (fDebug && GetBoolArg("-printcreation")) - printf("ConnectBlock() : destroy=%s nFees=%"PRI64d"\n", FormatMoney(nFees).c_str(), nFees); - // Update block index on disk without changing it in memory. // The memory index structure will be changed after the db commits. if (pindex->pprev) @@ -1470,7 +1597,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) CDiskBlockIndex blockindexPrev(pindex->pprev); blockindexPrev.hashNext = pindex->GetBlockHash(); if (!txdb.WriteBlockIndex(blockindexPrev)) - return error("ConnectBlock() : WriteBlockIndex for blockindexPrev failed"); + return error("ConnectBlock() : WriteBlockIndex failed"); } // Watch for transactions paying to me @@ -1480,7 +1607,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } -bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) +bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { printf("REORGANIZE\n"); @@ -1509,8 +1636,8 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) vConnect.push_back(pindex); reverse(vConnect.begin(), vConnect.end()); - printf("REORGANIZE: Disconnect %i blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str()); - printf("REORGANIZE: Connect %i blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str()); + printf("REORGANIZE: Disconnect %"PRIszu" blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str()); + printf("REORGANIZE: Connect %"PRIszu" blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str()); // Disconnect shorter branch vector vResurrect; @@ -1539,7 +1666,6 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) if (!block.ConnectBlock(txdb, pindex)) { // Invalid block - txdb.TxnAbort(); return error("Reorganize() : ConnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str()); } @@ -1578,14 +1704,6 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) } -static void -runCommand(std::string strCommand) -{ - int nErr = ::system(strCommand.c_str()); - if (nErr) - printf("runCommand error: system(%s) returned %d\n", strCommand.c_str(), nErr); -} - // Called from inside SetBestChain: attaches a block to the new best chain being built bool CBlock::SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew) { @@ -1647,7 +1765,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) } if (!vpindexSecondary.empty()) - printf("Postponing %i reconnects\n", vpindexSecondary.size()); + printf("Postponing %"PRIszu" reconnects\n", vpindexSecondary.size()); // Switch to new best branch if (!Reorganize(txdb, pindexIntermediate)) @@ -1657,7 +1775,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) return error("SetBestChain() : Reorganize failed"); } - // Connect futher blocks + // Connect further blocks BOOST_REVERSE_FOREACH(CBlockIndex *pindex, vpindexSecondary) { CBlock block; @@ -1687,11 +1805,32 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) // New best block hashBestChain = hash; pindexBest = pindexNew; + pblockindexFBBHLast = NULL; nBestHeight = pindexBest->nHeight; bnBestChainTrust = pindexNew->bnChainTrust; nTimeBestReceived = GetTime(); nTransactionsUpdated++; - printf("SetBestChain: new best=%s height=%d trust=%s moneysupply=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainTrust.ToString().c_str(), FormatMoney(pindexBest->nMoneySupply).c_str()); + printf("SetBestChain: new best=%s height=%d trust=%s date=%s\n", + hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainTrust.ToString().c_str(), + DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); + + // Check the version of the last 100 blocks to see if we need to upgrade: + if (!fIsInitialDownload) + { + int nUpgraded = 0; + const CBlockIndex* pindex = pindexBest; + for (int i = 0; i < 100 && pindex != NULL; i++) + { + if (pindex->nVersion > CBlock::CURRENT_VERSION) + ++nUpgraded; + pindex = pindex->pprev; + } + if (nUpgraded > 0) + printf("SetBestChain: %d of last 100 blocks above version %d\n", nUpgraded, CBlock::CURRENT_VERSION); + if (nUpgraded > 100/2) + // strMiscWarning is read by GetWarnings(), called by Qt and the JSON-RPC code to warn the user: + strMiscWarning = _("Warning: This version is obsolete, upgrade required!"); + } std::string strCmd = GetArg("-blocknotify", ""); @@ -1704,7 +1843,6 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) return true; } - // ppcoin: total coin age spent in transaction, in the unit of coin-days. // Only those coins meeting minimum age requirement counts. As those // transactions not in main chain are not currently indexed so we @@ -1741,7 +1879,7 @@ bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT; if (fDebug && GetBoolArg("-printcoinage")) - printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str()); + printf("coin age nValueIn=%"PRI64d" nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str()); } CBigNum bnCoinDay = bnCentSecond * CENT / COIN / (24 * 60 * 60); @@ -1773,7 +1911,6 @@ bool CBlock::GetCoinAge(uint64& nCoinAge) const return true; } - bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) { // Check for duplicate @@ -1785,7 +1922,6 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this); if (!pindexNew) return error("AddToBlockIndex() : new CBlockIndex failed"); - pindexNew->phashBlock = &hash; map::iterator miPrev = mapBlockIndex.find(hashPrevBlock); if (miPrev != mapBlockIndex.end()) @@ -1848,14 +1984,14 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) hashPrevBestCoinBase = vtx[0].GetHash(); } - MainFrameRepaint(); + uiInterface.NotifyBlocksChanged(); return true; } -bool CBlock::CheckBlock() const +bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot) const { // These are checks that are independent of context // that can be verified before saving an orphan block. @@ -1865,7 +2001,7 @@ bool CBlock::CheckBlock() const return DoS(100, error("CheckBlock() : size limits failed")); // Check proof of work matches claimed amount - if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits)) + if (fCheckPOW && IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits)) return DoS(50, error("CheckBlock() : proof of work failed")); // Check timestamp @@ -1880,7 +2016,7 @@ bool CBlock::CheckBlock() const return DoS(100, error("CheckBlock() : more than one coinbase")); // ppcoin: only the second transaction can be the optional coinstake - for (int i = 2; i < vtx.size(); i++) + for (unsigned int i = 2; i < vtx.size(); i++) if (vtx[i].IsCoinStake()) return DoS(100, error("CheckBlock() : coinstake in wrong position")); @@ -1894,7 +2030,7 @@ bool CBlock::CheckBlock() const // Check coinstake timestamp if (IsProofOfStake() && !CheckCoinStakeTimestamp(GetBlockTime(), (int64)vtx[1].nTime)) - return DoS(50, error("CheckBlock() : coinstake timestamp violation nTimeBlock=%u nTimeTx=%u", GetBlockTime(), vtx[1].nTime)); + return DoS(50, error("CheckBlock() : coinstake timestamp violation nTimeBlock=%"PRI64d" nTimeTx=%u", GetBlockTime(), vtx[1].nTime)); // Check coinbase reward if (vtx[0].GetValueOut() > (IsProofOfWork()? (GetProofOfWorkReward(nBits) - vtx[0].GetMinFee() + MIN_TX_FEE) : 0)) @@ -1907,6 +2043,7 @@ bool CBlock::CheckBlock() const { if (!tx.CheckTransaction()) return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed")); + // ppcoin: check transaction timestamp if (GetBlockTime() < (int64)tx.nTime) return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp")); @@ -1930,8 +2067,8 @@ bool CBlock::CheckBlock() const if (nSigOps > MAX_BLOCK_SIGOPS) return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount")); - // Check merkleroot - if (hashMerkleRoot != BuildMerkleTree()) + // Check merkle root + if (fCheckMerkleRoot && hashMerkleRoot != BuildMerkleTree()) return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch")); // ppcoin: check block signature @@ -1957,7 +2094,7 @@ bool CBlock::AcceptBlock() // Check proof-of-work or proof-of-stake if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake())) - return DoS(100, error("AcceptBlock() : incorrect proof-of-work/proof-of-stake")); + return DoS(100, error("AcceptBlock() : incorrect %s", IsProofOfWork() ? "proof-of-work" : "proof-of-stake")); // Check timestamp against prev if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime()) @@ -1968,18 +2105,19 @@ bool CBlock::AcceptBlock() if (!tx.IsFinal(nHeight, GetBlockTime())) return DoS(10, error("AcceptBlock() : contains a non-final transaction")); - // Check that the block chain matches the known block chain up to a hardened checkpoint + // Check that the block chain matches the known block chain up to a checkpoint if (!Checkpoints::CheckHardened(nHeight, hash)) - return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight)); + return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lock-in at %d", nHeight)); // ppcoin: check that the block satisfies synchronized checkpoint if (!Checkpoints::CheckSync(hash, pindexPrev)) return error("AcceptBlock() : rejected by synchronized checkpoint"); // Reject block.nVersion < 3 blocks since 95% threshold on mainNet and always on testNet: - if (nVersion < 3 && (nHeight > 14060)) + if (nVersion < 3 && ((!fTestNet && nHeight > 14060) || (fTestNet && nHeight > 0))) return error("CheckBlock() : rejected nVersion < 3 block"); + // Enforce rule that the coinbase starts with serialized block height CScript expect = CScript() << nHeight; if (!std::equal(expect.begin(), expect.end(), vtx[0].vin[0].scriptSig.begin())) return DoS(100, error("AcceptBlock() : block height mismatch in coinbase")); @@ -2010,6 +2148,18 @@ bool CBlock::AcceptBlock() return true; } +bool CBlockIndex::IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired, unsigned int nToCheck) +{ + unsigned int nFound = 0; + for (unsigned int i = 0; i < nToCheck && nFound < nRequired && pstart != NULL; i++) + { + if (pstart->nVersion >= minVersion) + ++nFound; + pstart = pstart->pprev; + } + return (nFound >= nRequired); +} + bool ProcessBlock(CNode* pfrom, CBlock* pblock) { // Check for duplicate @@ -2051,7 +2201,6 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) bnNewBlock.SetCompact(pblock->nBits); CBigNum bnRequired; bnRequired.SetCompact(ComputeMinWork(GetLastBlockIndex(pcheckpoint, pblock->IsProofOfStake())->nBits, deltaTime)); - if (bnNewBlock > bnRequired) { if (pfrom) @@ -2135,7 +2284,7 @@ bool CBlock::SignBlock(const CKeyStore& keystore) if(!IsProofOfStake()) { - for(int i = 0; i < vtx[0].vout.size(); i++) + for(unsigned int i = 0; i < vtx[0].vout.size(); i++) { const CTxOut& txout = vtx[0].vout[i]; @@ -2213,7 +2362,7 @@ bool CBlock::CheckBlockSignature() const } else { - for(int i = 0; i < vtx[0].vout.size(); i++) + for(unsigned int i = 0; i < vtx[0].vout.size(); i++) { const CTxOut& txout = vtx[0].vout[i]; @@ -2243,25 +2392,31 @@ bool CheckDiskSpace(uint64 nAdditionalBytes) { uint64 nFreeBytesAvailable = filesystem::space(GetDataDir()).available; - // Check for 15MB because database could create another 10MB log file at any time - if (nFreeBytesAvailable < (uint64)15000000 + nAdditionalBytes) + // Check for nMinDiskSpace bytes (currently 50MB) + if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes) { fShutdown = true; - string strMessage = _("Warning: Disk space is low"); + string strMessage = _("Warning: Disk space is low!"); strMiscWarning = strMessage; printf("*** %s\n", strMessage.c_str()); - ThreadSafeMessageBox(strMessage, "NovaCoin", wxOK | wxICON_EXCLAMATION | wxMODAL); + uiInterface.ThreadSafeMessageBox(strMessage, "NovaCoin", CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); StartShutdown(); return false; } return true; } +static filesystem::path BlockFilePath(unsigned int nFile) +{ + string strBlockFn = strprintf("blk%04u.dat", nFile); + return GetDataDir() / strBlockFn; +} + FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode) { - if (nFile == -1) + if ((nFile < 1) || (nFile == (unsigned int) -1)) return NULL; - FILE* file = fopen((GetDataDir() / strprintf("blk%04d.dat", nFile)).string().c_str(), pszMode); + FILE* file = fopen(BlockFilePath(nFile).string().c_str(), pszMode); if (!file) return NULL; if (nBlockPos != 0 && !strchr(pszMode, 'a') && !strchr(pszMode, 'w')) @@ -2287,8 +2442,8 @@ FILE* AppendBlockFile(unsigned int& nFileRet) return NULL; if (fseek(file, 0, SEEK_END) != 0) return NULL; - // FAT32 filesize max 4GB, fseek and ftell max 2GB, so we must stay under 2GB - if (ftell(file) < 0x7F000000 - MAX_SIZE) + // FAT32 file size max 4GB, fseek and ftell max 2GB, so we must stay under 2GB + if (ftell(file) < (long)(0x7F000000 - MAX_SIZE)) { nFileRet = nCurrentBlockFile; return file; @@ -2302,14 +2457,17 @@ bool LoadBlockIndex(bool fAllowNew) { if (fTestNet) { + pchMessageStart[0] = 0xcd; + pchMessageStart[1] = 0xf2; + pchMessageStart[2] = 0xc0; + pchMessageStart[3] = 0xef; + + hashGenesisBlock = hashGenesisBlockTestNet; nStakeMinAge = 60 * 60 * 24; // test net min age is 1 day nCoinbaseMaturity = 60; } - printf("%s Network: genesis=0x%s nBitsLimit=0x%08x nBitsInitial=0x%08x nStakeMinAge=%d nCoinbaseMaturity=%d nModifierInterval=%d\n", - fTestNet? "Test" : "NovaCoin", hashGenesisBlock.ToString().substr(0, 20).c_str(), bnProofOfWorkLimit.GetCompact(), bnInitialHashTarget.GetCompact(), nStakeMinAge, nCoinbaseMaturity, nModifierInterval); - // // Load block index // @@ -2326,6 +2484,13 @@ bool LoadBlockIndex(bool fAllowNew) if (!fAllowNew) return false; + // Genesis Block: + // CBlock(hash=000000000019d6, ver=1, hashPrevBlock=00000000000000, hashMerkleRoot=4a5e1e, nTime=1231006505, nBits=1d00ffff, nNonce=2083236893, vtx=1) + // CTransaction(hash=4a5e1e, ver=1, vin.size=1, vout.size=1, nLockTime=0) + // CTxIn(COutPoint(000000, -1), coinbase 04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73) + // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) + // vMerkleTree: 4a5e1e + // Genesis block const char* pszTimestamp = "https://bitcointalk.org/index.php?topic=134179.msg1502196#msg1502196"; CTransaction txNew; @@ -2387,7 +2552,7 @@ bool LoadBlockIndex(bool fAllowNew) void PrintBlockTree() { - // precompute tree structure + // pre-compute tree structure map > mapNext; for (map::iterator mi = mapBlockIndex.begin(); mi != mapBlockIndex.end(); ++mi) { @@ -2430,19 +2595,19 @@ void PrintBlockTree() // print item CBlock block; block.ReadFromDisk(pindex); - printf("%d (%u,%u) %s %08lx %s mint %7s tx %d", + printf("%d (%u,%u) %s %08x %s mint %7s tx %"PRIszu"", pindex->nHeight, pindex->nFile, pindex->nBlockPos, block.GetHash().ToString().c_str(), block.nBits, - DateTimeStrFormat(block.GetBlockTime()).c_str(), + DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(), FormatMoney(pindex->nMint).c_str(), block.vtx.size()); PrintWallets(block); - // put the main timechain first + // put the main time-chain first vector& vNext = mapNext[pindex]; for (unsigned int i = 0; i < vNext.size(); i++) { @@ -2459,6 +2624,65 @@ void PrintBlockTree() } } +bool LoadExternalBlockFile(FILE* fileIn) +{ + int64 nStart = GetTimeMillis(); + + int nLoaded = 0; + { + LOCK(cs_main); + try { + CAutoFile blkdat(fileIn, SER_DISK, CLIENT_VERSION); + unsigned int nPos = 0; + while (nPos != (unsigned int)-1 && blkdat.good() && !fRequestShutdown) + { + unsigned char pchData[65536]; + do { + fseek(blkdat, nPos, SEEK_SET); + int nRead = fread(pchData, 1, sizeof(pchData), blkdat); + if (nRead <= 8) + { + nPos = (unsigned int)-1; + break; + } + void* nFind = memchr(pchData, pchMessageStart[0], nRead+1-sizeof(pchMessageStart)); + if (nFind) + { + if (memcmp(nFind, pchMessageStart, sizeof(pchMessageStart))==0) + { + nPos += ((unsigned char*)nFind - pchData) + sizeof(pchMessageStart); + break; + } + nPos += ((unsigned char*)nFind - pchData) + 1; + } + else + nPos += sizeof(pchData) - sizeof(pchMessageStart) + 1; + } while(!fRequestShutdown); + if (nPos == (unsigned int)-1) + break; + fseek(blkdat, nPos, SEEK_SET); + unsigned int nSize; + blkdat >> nSize; + if (nSize > 0 && nSize <= MAX_BLOCK_SIZE) + { + CBlock block; + blkdat >> block; + if (ProcessBlock(NULL,&block)) + { + nLoaded++; + nPos += 4 + nSize; + } + } + } + } + catch (std::exception &e) { + printf("%s() : Deserialize or I/O error caught during load\n", + __PRETTY_FUNCTION__); + } + } + printf("Loaded %i blocks from external file in %"PRI64d"ms\n", nLoaded, GetTimeMillis() - nStart); + return nLoaded > 0; +} @@ -2473,10 +2697,10 @@ void PrintBlockTree() // CAlert // -map mapAlerts; -CCriticalSection cs_mapAlerts; +extern map mapAlerts; +extern CCriticalSection cs_mapAlerts; -static string strMintMessage = _("Info: Minting suspended due to locked wallet."); +static string strMintMessage = "Info: Minting suspended due to locked wallet."; static string strMintWarning; string GetWarnings(string strFor) @@ -2484,15 +2708,17 @@ string GetWarnings(string strFor) int nPriority = 0; string strStatusBar; string strRPC; + if (GetBoolArg("-testsafemode")) strRPC = "test"; - +/* // ppcoin: wallet lock warning for minting if (strMintWarning != "") { nPriority = 0; strStatusBar = strMintWarning; } +*/ // Misc warnings like out of disk space and clock is wrong if (strMiscWarning != "") @@ -2540,53 +2766,6 @@ string GetWarnings(string strFor) return "error"; } -bool CAlert::ProcessAlert() -{ - if (!CheckSignature()) - return false; - if (!IsInEffect()) - return false; - - { - LOCK(cs_mapAlerts); - // Cancel previous alerts - for (map::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();) - { - const CAlert& alert = (*mi).second; - if (Cancels(alert)) - { - printf("cancelling alert %d\n", alert.nID); - mapAlerts.erase(mi++); - } - else if (!alert.IsInEffect()) - { - printf("expiring alert %d\n", alert.nID); - mapAlerts.erase(mi++); - } - else - mi++; - } - - // Check if this alert has been cancelled - BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) - { - const CAlert& alert = item.second; - if (alert.Cancels(*this)) - { - printf("alert already cancelled by %d\n", alert.nID); - return false; - } - } - - // Add to mapAlerts - mapAlerts.insert(make_pair(GetHash(), *this)); - } - - printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); - MainFrameRepaint(); - return true; -} - @@ -2627,14 +2806,17 @@ bool static AlreadyHave(CTxDB& txdb, const CInv& inv) +// The message start string is designed to be unlikely to occur in normal data. +// The characters are rarely used upper ASCII, not valid as UTF-8, and produce +// a large 4-byte int at any alignment. +unsigned char pchMessageStart[4] = { 0xe4, 0xe8, 0xe9, 0xe5 }; + bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { - static map > mapReuseKey; + static map mapReuseKey; RandAddSeedPerfmon(); - if (fDebug) { - printf("%s ", DateTimeStrFormat(GetTime()).c_str()); - printf("received: %s (%d bytes)\n", strCommand.c_str(), vRecv.size()); - } + if (fDebug) + printf("received: %s (%"PRIszu" bytes)\n", strCommand.c_str(), vRecv.size()); if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0) { printf("dropmessagestest DROPPING RECV MESSAGE\n"); @@ -2677,6 +2859,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!vRecv.empty()) vRecv >> pfrom->nStartingHeight; + if (pfrom->fInbound && addrMe.IsRoutable()) + { + pfrom->addrLocal = addrMe; + SeenLocal(addrMe); + } + // Disconnect if we connected to ourself if (nNonce == nLocalHostNonce && nNonce > 1) { @@ -2704,16 +2892,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!pfrom->fInbound) { // Advertise our address - if (!fNoListen && !fUseProxy && addrLocalHost.IsRoutable() && - !IsInitialBlockDownload()) + if (!fNoListen && !IsInitialBlockDownload()) { - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - pfrom->PushAddress(addr); + CAddress addr = GetLocalAddress(&pfrom->addr); + if (addr.IsRoutable()) + pfrom->PushAddress(addr); } // Get recent addresses - if (pfrom->nVersion >= CADDR_TIME_VERSION || addrman.size() < 1000) + if (pfrom->fOneShot || pfrom->nVersion >= CADDR_TIME_VERSION || addrman.size() < 1000) { pfrom->PushMessage("getaddr"); pfrom->fGetAddr = true; @@ -2729,7 +2916,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Ask the first connected node for block updates static int nAskedForBlocks = 0; - if (!pfrom->fClient && + if (!pfrom->fClient && !pfrom->fOneShot && + (pfrom->nStartingHeight > (nBestHeight - 144)) && (pfrom->nVersion < NOBLKS_VERSION_START || pfrom->nVersion >= NOBLKS_VERSION_END) && (nAskedForBlocks < 1 || vNodes.size() <= 1)) @@ -2754,7 +2942,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->fSuccessfullyConnected = true; - printf("version message: version %d, blocks=%d\n", pfrom->nVersion, pfrom->nStartingHeight); + printf("receive version message: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString().c_str(), addrFrom.ToString().c_str(), pfrom->addr.ToString().c_str()); cPeerBlockCounts.input(pfrom->nStartingHeight); @@ -2789,22 +2977,21 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (vAddr.size() > 1000) { pfrom->Misbehaving(20); - return error("message addr size() = %d", vAddr.size()); + return error("message addr size() = %"PRIszu"", vAddr.size()); } // Store the new addresses + vector vAddrOk; int64 nNow = GetAdjustedTime(); int64 nSince = nNow - 10 * 60; BOOST_FOREACH(CAddress& addr, vAddr) { if (fShutdown) return true; - // ignore IPv6 for now, since it isn't implemented anyway - if (!addr.IsIPv4()) - continue; if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) addr.nTime = nNow - 5 * 24 * 60 * 60; pfrom->AddAddressKnown(addr); + bool fReachable = IsReachable(addr); if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) { // Relay to a limited number of other nodes @@ -2815,7 +3002,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) static uint256 hashSalt; if (hashSalt == 0) hashSalt = GetRandHash(); - int64 hashAddr = addr.GetHash(); + uint64 hashAddr = addr.GetHash(); uint256 hashRand = hashSalt ^ (hashAddr<<32) ^ ((GetTime()+hashAddr)/(24*60*60)); hashRand = Hash(BEGIN(hashRand), END(hashRand)); multimap mapMix; @@ -2829,15 +3016,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) hashKey = Hash(BEGIN(hashKey), END(hashKey)); mapMix.insert(make_pair(hashKey, pnode)); } - int nRelayNodes = 2; + int nRelayNodes = fReachable ? 2 : 1; // limited relaying of addresses outside our network(s) for (multimap::iterator mi = mapMix.begin(); mi != mapMix.end() && nRelayNodes-- > 0; ++mi) ((*mi).second)->PushAddress(addr); } } + // Do not store addresses outside our network + if (fReachable) + vAddrOk.push_back(addr); } - addrman.Add(vAddr, pfrom->addr, 2 * 60 * 60); + addrman.Add(vAddrOk, pfrom->addr, 2 * 60 * 60); if (vAddr.size() < 1000) pfrom->fGetAddr = false; + if (pfrom->fOneShot) + pfrom->fDisconnect = true; } @@ -2845,10 +3037,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { vector vInv; vRecv >> vInv; - if (vInv.size() > 50000) + if (vInv.size() > MAX_INV_SZ) { pfrom->Misbehaving(20); - return error("message inv size() = %d", vInv.size()); + return error("message inv size() = %"PRIszu"", vInv.size()); } // find last block in inv vector @@ -2880,7 +3072,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // In case we are on a very long side-chain, it is possible that we already have // the last block in an inv bundle sent in response to getblocks. Try to detect // this situation and push another getblocks to continue. - std::vector vGetData(1,inv); pfrom->PushGetBlocks(mapBlockIndex[inv.hash], uint256(0)); if (fDebug) printf("force request: %s\n", inv.ToString().c_str()); @@ -2896,17 +3087,21 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { vector vInv; vRecv >> vInv; - if (vInv.size() > 50000) + if (vInv.size() > MAX_INV_SZ) { pfrom->Misbehaving(20); - return error("message getdata size() = %d", vInv.size()); + return error("message getdata size() = %"PRIszu"", vInv.size()); } + if (fDebugNet || (vInv.size() != 1)) + printf("received getdata (%"PRIszu" invsz)\n", vInv.size()); + BOOST_FOREACH(const CInv& inv, vInv) { if (fShutdown) return true; - printf("received getdata for: %s\n", inv.ToString().c_str()); + if (fDebugNet || (vInv.size() == 1)) + printf("received getdata for: %s\n", inv.ToString().c_str()); if (inv.type == MSG_BLOCK) { @@ -2921,9 +3116,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Trigger them to send a getblocks request for the next batch of inventory if (inv.hash == pfrom->hashContinue) { - // Bypass PushInventory, this must send even if redundant, - // and we want it right after the last block so they don't - // wait for other stuff first. // ppcoin: send latest proof-of-work block to allow the // download node to accept as orphan (proof-of-stake // block might be rejected by stake connection check) @@ -2937,11 +3129,24 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (inv.IsKnownType()) { // Send stream from relay memory + bool pushed = false; { LOCK(cs_mapRelay); map::iterator mi = mapRelay.find(inv); - if (mi != mapRelay.end()) + if (mi != mapRelay.end()) { pfrom->PushMessage(inv.GetCommand(), (*mi).second); + pushed = true; + } + } + if (!pushed && inv.type == MSG_TX) { + LOCK(mempool.cs); + if (mempool.exists(inv.hash)) { + CTransaction tx = mempool.lookup(inv.hash); + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << tx; + pfrom->PushMessage("tx", ss); + } } } @@ -2963,14 +3168,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Send the rest of the chain if (pindex) pindex = pindex->pnext; - int nLimit = 500 + locator.GetDistanceBack(); - unsigned int nBytes = 0; + int nLimit = 500; printf("getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,20).c_str(), nLimit); for (; pindex; pindex = pindex->pnext) { if (pindex->GetBlockHash() == hashStop) { - printf(" getblocks stopping at %d %s (%u bytes)\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str(), nBytes); + printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str()); // ppcoin: tell downloading node about the latest block if it's // without risk being rejected due to stake connection check if (hashStop != hashBestChain && pindex->GetBlockTime() + nStakeMinAge > pindexBest->GetBlockTime()) @@ -2978,20 +3182,30 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) break; } pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash())); - CBlock block; - block.ReadFromDisk(pindex, true); - nBytes += block.GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION); - if (--nLimit <= 0 || nBytes >= SendBufferSize()/2) + if (--nLimit <= 0) { // When this block is requested, we'll send an inv that'll make them // getblocks the next batch of inventory. - printf(" getblocks stopping at limit %d %s (%u bytes)\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str(), nBytes); + printf(" getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,20).c_str()); pfrom->hashContinue = pindex->GetBlockHash(); break; } } } + else if (strCommand == "checkpoint") + { + CSyncCheckpoint checkpoint; + vRecv >> checkpoint; + if (checkpoint.ProcessSyncCheckpoint(pfrom)) + { + // Relay + pfrom->hashCheckpointKnown = checkpoint.hashCheckpoint; + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + checkpoint.RelayTo(pnode); + } + } else if (strCommand == "getheaders") { @@ -3124,6 +3338,22 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } + else if (strCommand == "mempool") + { + std::vector vtxid; + mempool.queryHashes(vtxid); + vector vInv; + for (unsigned int i = 0; i < vtxid.size(); i++) { + CInv inv(MSG_TX, vtxid[i]); + vInv.push_back(inv); + if (i == (MAX_INV_SZ - 1)) + break; + } + if (vInv.size() > 0) + pfrom->PushMessage("inv", vInv); + } + + else if (strCommand == "checkorder") { uint256 hashReply; @@ -3198,32 +3428,31 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) CAlert alert; vRecv >> alert; - if (alert.ProcessAlert()) + uint256 alertHash = alert.GetHash(); + if (pfrom->setKnown.count(alertHash) == 0) { - // Relay - pfrom->setKnown.insert(alert.GetHash()); + if (alert.ProcessAlert()) { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - alert.RelayTo(pnode); + // Relay + pfrom->setKnown.insert(alertHash); + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + alert.RelayTo(pnode); + } + } + else { + // Small DoS penalty so peers that send us lots of + // duplicate/expired/invalid-signature/whatever alerts + // eventually get banned. + // This isn't a Misbehaving(100) (immediate ban) because the + // peer might be an older or different implementation with + // a different signature key, etc. + pfrom->Misbehaving(10); } } } - else if (strCommand == "checkpoint") - { - CSyncCheckpoint checkpoint; - vRecv >> checkpoint; - - if (checkpoint.ProcessSyncCheckpoint(pfrom)) - { - // Relay - pfrom->hashCheckpointKnown = checkpoint.hashCheckpoint; - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - checkpoint.RelayTo(pnode); - } - } else { @@ -3257,19 +3486,12 @@ bool ProcessMessages(CNode* pfrom) // (x) data // - unsigned char pchMessageStart[4]; - GetMessageStart(pchMessageStart); - static int64 nTimeLastPrintMessageStart = 0; - if (fDebug && GetBoolArg("-printmessagestart") && nTimeLastPrintMessageStart + 30 < GetAdjustedTime()) - { - string strMessageStart((const char *)pchMessageStart, sizeof(pchMessageStart)); - vector vchMessageStart(strMessageStart.begin(), strMessageStart.end()); - printf("ProcessMessages : AdjustedTime=%"PRI64d" MessageStart=%s\n", GetAdjustedTime(), HexStr(vchMessageStart).c_str()); - nTimeLastPrintMessageStart = GetAdjustedTime(); - } - loop { + // Don't bother if send buffer is too full to respond anyway + if (pfrom->vSend.size() >= SendBufferSize()) + break; + // Scan for message start CDataStream::iterator pstart = search(vRecv.begin(), vRecv.end(), BEGIN(pchMessageStart), END(pchMessageStart)); int nHeaderSize = vRecv.GetSerializeSize(CMessageHeader()); @@ -3283,7 +3505,7 @@ bool ProcessMessages(CNode* pfrom) break; } if (pstart - vRecv.begin() > 0) - printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", pstart - vRecv.begin()); + printf("\n\nPROCESSMESSAGE SKIPPED %"PRIpdd" BYTES\n\n", pstart - vRecv.begin()); vRecv.erase(vRecv.begin(), pstart); // Read header @@ -3341,12 +3563,12 @@ bool ProcessMessages(CNode* pfrom) { if (strstr(e.what(), "end of data")) { - // Allow exceptions from underlength message on vRecv + // Allow exceptions from under-length message on vRecv printf("ProcessMessages(%s, %u bytes) : Exception '%s' caught, normally caused by a message being shorter than its stated length\n", strCommand.c_str(), nMessageSize, e.what()); } else if (strstr(e.what(), "size too large")) { - // Allow exceptions from overlong size + // Allow exceptions from over-long size printf("ProcessMessages(%s, %u bytes) : Exception '%s' caught\n", strCommand.c_str(), nMessageSize, e.what()); } else @@ -3403,11 +3625,11 @@ bool SendMessages(CNode* pto, bool fSendTrickle) pnode->setAddrKnown.clear(); // Rebroadcast our address - if (!fNoListen && !fUseProxy && addrLocalHost.IsRoutable()) + if (!fNoListen) { - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - pnode->PushAddress(addr); + CAddress addr = GetLocalAddress(&pnode->addr); + if (addr.IsRoutable()) + pnode->PushAddress(addr); } } } @@ -3510,15 +3732,16 @@ bool SendMessages(CNode* pto, bool fSendTrickle) const CInv& inv = (*pto->mapAskFor.begin()).second; if (!AlreadyHave(txdb, inv)) { - printf("sending getdata: %s\n", inv.ToString().c_str()); + if (fDebugNet) + printf("sending getdata: %s\n", inv.ToString().c_str()); vGetData.push_back(inv); if (vGetData.size() >= 1000) { pto->PushMessage("getdata", vGetData); vGetData.clear(); } + mapAlreadyAskedFor[inv] = nNow; } - mapAlreadyAskedFor[inv] = nNow; pto->mapAskFor.erase(pto->mapAskFor.begin()); } if (!vGetData.empty()) @@ -3589,16 +3812,18 @@ public: CTransaction* ptx; set setDependsOn; double dPriority; + double dFeePerKb; COrphan(CTransaction* ptxIn) { ptx = ptxIn; - dPriority = 0; + dPriority = dFeePerKb = 0; } void print() const { - printf("COrphan(hash=%s, dPriority=%.1f)\n", ptx->GetHash().ToString().substr(0,10).c_str(), dPriority); + printf("COrphan(hash=%s, dPriority=%.1f, dFeePerKb=%.1f)\n", + ptx->GetHash().ToString().substr(0,10).c_str(), dPriority, dFeePerKb); BOOST_FOREACH(uint256 hash, setDependsOn) printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str()); } @@ -3608,6 +3833,30 @@ public: uint64 nLastBlockTx = 0; uint64 nLastBlockSize = 0; int64 nLastCoinStakeSearchInterval = 0; + +// We want to sort transactions by priority and fee, so: +typedef boost::tuple TxPriority; +class TxPriorityCompare +{ + bool byFee; +public: + TxPriorityCompare(bool _byFee) : byFee(_byFee) { } + bool operator()(const TxPriority& a, const TxPriority& b) + { + if (byFee) + { + if (a.get<1>() == b.get<1>()) + return a.get<0>() < b.get<0>(); + return a.get<1>() < b.get<1>(); + } + else + { + if (a.get<0>() == b.get<0>()) + return a.get<1>() < b.get<1>(); + return a.get<0>() < b.get<0>(); + } + } +}; // CreateNewBlock: // fProofOfStake: try (best effort) to make a proof-of-stake block @@ -3630,6 +3879,32 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) // Add our coinbase tx as first transaction pblock->vtx.push_back(txNew); + + + // Largest block you're willing to create: + unsigned int nBlockMaxSize = GetArg("-blockmaxsize", MAX_BLOCK_SIZE_GEN/2); + // Limit to betweeen 1K and MAX_BLOCK_SIZE-1K for sanity: + nBlockMaxSize = std::max((unsigned int)1000, std::min((unsigned int)(MAX_BLOCK_SIZE-1000), nBlockMaxSize)); + + // How much of the block should be dedicated to high-priority transactions, + // included regardless of the fees they pay + unsigned int nBlockPrioritySize = GetArg("-blockprioritysize", 27000); + nBlockPrioritySize = std::min(nBlockMaxSize, nBlockPrioritySize); + + // Minimum block size you want to create; block will be filled with free transactions + // until there are no more or the block reaches this size: + unsigned int nBlockMinSize = GetArg("-blockminsize", 0); + nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize); + + // Fee-per-kilobyte amount considered the same as "free" + // Be careful setting this: if you set it to zero then + // a transaction spammer can cheaply fill blocks using + // 1-satoshi-fee transactions. It should be set above the real + // cost to you of processing a transaction. + int64 nMinTxFee = MIN_TX_FEE; + if (mapArgs.count("-mintxfee")) + ParseMoney(mapArgs["-mintxfee"], nMinTxFee); + // ppcoin: if coinstake available add coinstake tx static int64 nLastCoinStakeSearchTime = GetAdjustedTime(); // only initialized at startup CBlockIndex* pindexPrev = pindexBest; @@ -3662,12 +3937,16 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) int64 nFees = 0; { LOCK2(cs_main, mempool.cs); + CBlockIndex* pindexPrev = pindexBest; CTxDB txdb("r"); // Priority order to process transactions list vOrphan; // list memory doesn't move map > mapDependers; - multimap mapPriority; + + // This vector will be sorted into a priority queue: + vector vecPriority; + vecPriority.reserve(mempool.mapTx.size()); for (map::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi) { CTransaction& tx = (*mi).second; @@ -3676,6 +3955,8 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) COrphan* porphan = NULL; double dPriority = 0; + int64 nTotalIn = 0; + bool fMissingInputs = false; BOOST_FOREACH(const CTxIn& txin, tx.vin) { // Read prev transaction @@ -3683,6 +3964,19 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) CTxIndex txindex; if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) { + // This should never happen; all transactions in the memory + // pool should connect to either transactions in the chain + // or other transactions in the memory pool. + if (!mempool.mapTx.count(txin.prevout.hash)) + { + printf("ERROR: mempool transaction missing input\n"); + if (fDebug) assert("mempool transaction missing input" == 0); + fMissingInputs = true; + if (porphan) + vOrphan.pop_back(); + break; + } + // Has to wait for dependencies if (!porphan) { @@ -3692,34 +3986,33 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) } mapDependers[txin.prevout.hash].push_back(porphan); porphan->setDependsOn.insert(txin.prevout.hash); + nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue; continue; } int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; + nTotalIn += nValueIn; - // Read block header int nConf = txindex.GetDepthInMainChain(); - dPriority += (double)nValueIn * nConf; - - if (fDebug && GetBoolArg("-printpriority")) - printf("priority nValueIn=%-12"PRI64d" nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority); } + if (fMissingInputs) continue; // Priority is sum(valuein * age) / txsize - dPriority /= ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); + unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); + dPriority /= nTxSize; - if (porphan) - porphan->dPriority = dPriority; - else - mapPriority.insert(make_pair(-dPriority, &(*mi).second)); + // This is a more accurate fee-per-kilobyte than is used by the client code, because the + // client code rounds up the size to the nearest 1K. That's good, because it gives an + // incentive to create smaller transactions. + double dFeePerKb = double(nTotalIn-tx.GetValueOut()) / (double(nTxSize)/1000.0); - if (fDebug && GetBoolArg("-printpriority")) + if (porphan) { - printf("priority %-20.1f %s\n%s", dPriority, tx.GetHash().ToString().substr(0,10).c_str(), tx.ToString().c_str()); - if (porphan) - porphan->print(); - printf("\n"); + porphan->dPriority = dPriority; + porphan->dFeePerKb = dFeePerKb; } + else + vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &(*mi).second)); } // Collect transactions into block @@ -3727,15 +4020,24 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) uint64 nBlockSize = 1000; uint64 nBlockTx = 0; int nBlockSigOps = 100; - while (!mapPriority.empty()) + bool fSortedByFee = (nBlockPrioritySize <= 0); + + TxPriorityCompare comparer(fSortedByFee); + std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); + + while (!vecPriority.empty()) { - // Take highest priority transaction off priority queue - CTransaction& tx = *(*mapPriority.begin()).second; - mapPriority.erase(mapPriority.begin()); + // Take highest priority transaction off the priority queue: + double dPriority = vecPriority.front().get<0>(); + double dFeePerKb = vecPriority.front().get<1>(); + CTransaction& tx = *(vecPriority.front().get<2>()); + + std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer); + vecPriority.pop_back(); // Size limits unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); - if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN) + if (nBlockSize + nTxSize >= nBlockMaxSize) continue; // Legacy limits on sigOps: @@ -3750,6 +4052,20 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) // ppcoin: simplify transaction fee - allow free = false int64 nMinFee = tx.GetMinFee(nBlockSize, false, GMF_BLOCK); + // Skip free transactions if we're past the minimum block size: + if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize)) + continue; + + // Prioritize by fee once past the priority size or we run out of high-priority + // transactions: + if (!fSortedByFee && + ((nBlockSize + nTxSize >= nBlockPrioritySize) || (dPriority < COIN * 144 / 250))) + { + fSortedByFee = true; + comparer = TxPriorityCompare(fSortedByFee); + std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); + } + // Connecting shouldn't fail due to dependency on other memory pool transactions // because we're already processing them in order of dependency map mapTestPoolTmp(mapTestPool); @@ -3778,6 +4094,12 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) nBlockSigOps += nTxSigOps; nFees += nTxFees; + if (fDebug && GetBoolArg("-printpriority")) + { + printf("priority %.1f feeperkb %.1f txid %s\n", + dPriority, dFeePerKb, tx.GetHash().ToString().c_str()); + } + // Add transactions that depend on this one to the priority queue uint256 hash = tx.GetHash(); if (mapDependers.count(hash)) @@ -3788,7 +4110,10 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) { porphan->setDependsOn.erase(hash); if (porphan->setDependsOn.empty()) - mapPriority.insert(make_pair(-porphan->dPriority, porphan->ptx)); + { + vecPriority.push_back(TxPriority(porphan->dPriority, porphan->dFeePerKb, porphan->ptx)); + std::push_heap(vecPriority.begin(), vecPriority.end(), comparer); + } } } } @@ -3796,23 +4121,21 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) nLastBlockTx = nBlockTx; nLastBlockSize = nBlockSize; - if (fDebug && GetBoolArg("-printpriority")) - printf("CreateNewBlock(): total size %lu\n", nBlockSize); - + printf("CreateNewBlock(): total size %"PRI64u"\n", nBlockSize); + + if (pblock->IsProofOfWork()) + pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits); + + // Fill in header + pblock->hashPrevBlock = pindexPrev->GetBlockHash(); + if (pblock->IsProofOfStake()) + pblock->nTime = pblock->vtx[1].nTime; //same as coinstake timestamp + pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime()); + pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift); + if (pblock->IsProofOfWork()) + pblock->UpdateTime(pindexPrev); + pblock->nNonce = 0; } - if (pblock->IsProofOfWork()) - pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits); - - // Fill in header - pblock->hashPrevBlock = pindexPrev->GetBlockHash(); - pblock->hashMerkleRoot = pblock->BuildMerkleTree(); - if (pblock->IsProofOfStake()) - pblock->nTime = pblock->vtx[1].nTime; //same as coinstake timestamp - pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime()); - pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift); - if (pblock->IsProofOfWork()) - pblock->UpdateTime(pindexPrev); - pblock->nNonce = 0; return pblock.release(); } @@ -3828,10 +4151,8 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& hashPrevBlock = pblock->hashPrevBlock; } ++nExtraNonce; - unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2 pblock->vtx[0].vin[0].scriptSig = (CScript() << nHeight << CBigNum(nExtraNonce)) + COINBASE_FLAGS; - assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100); pblock->hashMerkleRoot = pblock->BuildMerkleTree(); @@ -3841,7 +4162,7 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1) { // - // Prebuild hash buffers + // Pre-build hash buffers // struct { @@ -3896,7 +4217,6 @@ bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey) printf("BitcoinMiner:\n"); printf("new block found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); pblock->print(); - printf("%s ", DateTimeStrFormat(GetTime()).c_str()); printf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue).c_str()); // Found a solution @@ -3935,6 +4255,9 @@ void BitcoinMiner(CWallet *pwallet, bool fProofOfStake) printf("CPUMiner started for proof-of-%s\n", fProofOfStake? "stake" : "work"); SetThreadPriority(THREAD_PRIORITY_LOWEST); + // Make this thread recognisable as the mining thread + RenameThread("bitcoin-miner"); + // Each thread has its own key and counter CReserveKey reservekey(pwallet); unsigned int nExtraNonce = 0; @@ -3968,7 +4291,6 @@ void BitcoinMiner(CWallet *pwallet, bool fProofOfStake) auto_ptr pblock(CreateNewBlock(pwallet, fProofOfStake)); if (!pblock.get()) return; - IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce); if (fProofOfStake) @@ -3991,11 +4313,11 @@ void BitcoinMiner(CWallet *pwallet, bool fProofOfStake) continue; } - printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size()); - + printf("Running BitcoinMiner with %"PRIszu" transactions in block (%u bytes)\n", pblock->vtx.size(), + ::GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION)); // - // Prebuild hash buffers + // Pre-build hash buffers // char pmidstatebuf[32+16]; char* pmidstate = alignup<16>(pmidstatebuf); char pdatabuf[128+16]; char* pdata = alignup<16>(pdatabuf); @@ -4041,10 +4363,11 @@ void BitcoinMiner(CWallet *pwallet, bool fProofOfStake) assert(result == pblock->GetHash()); if (!pblock->SignBlock(*pwalletMain)) { - strMintWarning = strMintMessage; +// strMintWarning = strMintMessage; break; } strMintWarning = ""; + SetThreadPriority(THREAD_PRIORITY_NORMAL); CheckWork(pblock.get(), *pwalletMain, reservekey); SetThreadPriority(THREAD_PRIORITY_LOWEST); @@ -4075,7 +4398,6 @@ void BitcoinMiner(CWallet *pwallet, bool fProofOfStake) if (GetTime() - nLogTime > 30 * 60) { nLogTime = GetTime(); - printf("%s ", DateTimeStrFormat(GetTime()).c_str()); printf("hashmeter %3d CPUs %6.0f khash/s\n", vnThreadsRunning[THREAD_MINER], dHashesPerSec/1000.0); } } @@ -4103,6 +4425,7 @@ void BitcoinMiner(CWallet *pwallet, bool fProofOfStake) pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift); pblock->UpdateTime(pindexPrev); nBlockTime = ByteReverse(pblock->nTime); + if (pblock->GetBlockTime() >= (int64)pblock->vtx[0].nTime + nMaxClockDrift) break; // need to update coinbase timestamp } @@ -4154,8 +4477,8 @@ void GenerateBitcoins(bool fGenerate, CWallet* pwallet) printf("Starting %d BitcoinMiner threads\n", nAddThreads); for (int i = 0; i < nAddThreads; i++) { - if (!CreateThread(ThreadBitcoinMiner, pwallet)) - printf("Error: CreateThread(ThreadBitcoinMiner) failed\n"); + if (!NewThread(ThreadBitcoinMiner, pwallet)) + printf("Error: NewThread(ThreadBitcoinMiner) failed\n"); Sleep(10); } } diff --git a/src/main.h b/src/main.h index 17de9ed..736ff5a 100644 --- a/src/main.h +++ b/src/main.h @@ -1,20 +1,16 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_MAIN_H #define BITCOIN_MAIN_H #include "bignum.h" +#include "sync.h" #include "net.h" #include "script.h" #include "scrypt_mine.h" -#ifdef WIN32 -#include /* for _commit */ -#endif - #include class CWallet; @@ -33,18 +29,15 @@ static const unsigned int MAX_BLOCK_SIZE = 1000000; static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2; static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50; static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100; +static const unsigned int MAX_INV_SZ = 50000; static const int64 MIN_TX_FEE = CENT; static const int64 MIN_RELAY_TX_FEE = CENT; static const int64 MAX_MONEY = 2000000000 * COIN; static const int64 MAX_MINT_PROOF_OF_WORK = 100 * COIN; static const int64 MIN_TXOUT_AMOUNT = MIN_TX_FEE; inline bool MoneyRange(int64 nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); } -static const int COINBASE_MATURITY_PPC = 500; // Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp. -static const int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC -static const int STAKE_TARGET_SPACING = 10 * 60; // 10-minute block spacing -static const int STAKE_MIN_AGE = 60 * 60 * 24 * 30; // minimum age for coin age -static const int STAKE_MAX_AGE = 60 * 60 * 24 * 90; // stake age of full weight +static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC #ifdef USE_UPNP static const int fHaveUPnP = true; @@ -68,9 +61,9 @@ extern CCriticalSection cs_main; extern std::map mapBlockIndex; extern std::set > setStakeSeen; extern uint256 hashGenesisBlock; +extern CBlockIndex* pindexGenesisBlock; extern unsigned int nStakeMinAge; extern int nCoinbaseMaturity; -extern CBlockIndex* pindexGenesisBlock; extern int nBestHeight; extern CBigNum bnBestChainTrust; extern CBigNum bnBestInvalidTrust; @@ -86,13 +79,14 @@ extern int64 nHPSTimerStart; extern int64 nTimeBestReceived; extern CCriticalSection cs_setpwalletRegistered; extern std::set setpwalletRegistered; +extern unsigned char pchMessageStart[4]; extern std::map mapOrphanBlocks; // Settings extern int64 nTransactionFee; - - +// Minimum disk space required - used in CheckDiskSpace() +static const uint64 nMinDiskSpace = 52428800; class CReserveKey; @@ -101,14 +95,17 @@ class CTxIndex; void RegisterWallet(CWallet* pwalletIn); void UnregisterWallet(CWallet* pwalletIn); +void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true); bool ProcessBlock(CNode* pfrom, CBlock* pblock); bool CheckDiskSpace(uint64 nAdditionalBytes=0); FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode="rb"); FILE* AppendBlockFile(unsigned int& nFileRet); bool LoadBlockIndex(bool fAllowNew=true); void PrintBlockTree(); +CBlockIndex* FindBlockByHeight(int nHeight); bool ProcessMessages(CNode* pfrom); bool SendMessages(CNode* pto, bool fSendTrickle); +bool LoadExternalBlockFile(FILE* fileIn); void GenerateBitcoins(bool fGenerate, CWallet* pwallet); CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake=false); void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce); @@ -133,6 +130,9 @@ void ResendWalletTransactions(); + + + bool GetWalletFile(CWallet* pwallet, std::string &strWalletFileOut); /** Position on disk for a particular transaction. */ @@ -156,8 +156,8 @@ public: } IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); ) - void SetNull() { nFile = -1; nBlockPos = 0; nTxPos = 0; } - bool IsNull() const { return (nFile == -1); } + void SetNull() { nFile = (unsigned int) -1; nBlockPos = 0; nTxPos = 0; } + bool IsNull() const { return (nFile == (unsigned int) -1); } friend bool operator==(const CDiskTxPos& a, const CDiskTxPos& b) { @@ -171,12 +171,13 @@ public: return !(a == b); } + std::string ToString() const { if (IsNull()) return "null"; else - return strprintf("(nFile=%d, nBlockPos=%d, nTxPos=%d)", nFile, nBlockPos, nTxPos); + return strprintf("(nFile=%u, nBlockPos=%u, nTxPos=%u)", nFile, nBlockPos, nTxPos); } void print() const @@ -196,8 +197,8 @@ public: CInPoint() { SetNull(); } CInPoint(CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; } - void SetNull() { ptx = NULL; n = -1; } - bool IsNull() const { return (ptx == NULL && n == -1); } + void SetNull() { ptx = NULL; n = (unsigned int) -1; } + bool IsNull() const { return (ptx == NULL && n == (unsigned int) -1); } }; @@ -212,8 +213,8 @@ public: COutPoint() { SetNull(); } COutPoint(uint256 hashIn, unsigned int nIn) { hash = hashIn; n = nIn; } IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); ) - void SetNull() { hash = 0; n = -1; } - bool IsNull() const { return (hash == 0 && n == -1); } + void SetNull() { hash = 0; n = (unsigned int) -1; } + bool IsNull() const { return (hash == 0 && n == (unsigned int) -1); } friend bool operator<(const COutPoint& a, const COutPoint& b) { @@ -232,7 +233,7 @@ public: std::string ToString() const { - return strprintf("COutPoint(%s, %d)", hash.ToString().substr(0,10).c_str(), n); + return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10).c_str(), n); } void print() const @@ -428,6 +429,7 @@ typedef std::map > MapPrevTx; class CTransaction { public: + static const int CURRENT_VERSION=1; int nVersion; unsigned int nTime; std::vector vin; @@ -455,7 +457,7 @@ public: void SetNull() { - nVersion = 1; + nVersion = CTransaction::CURRENT_VERSION; nTime = GetAdjustedTime(); vin.clear(); vout.clear(); @@ -588,53 +590,7 @@ public: return dPriority > COIN * 144 / 250; } - int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=false, enum GetMinFee_mode mode=GMF_BLOCK) const - { - // Base fee is either MIN_TX_FEE or MIN_RELAY_TX_FEE - int64 nBaseFee = (mode == GMF_RELAY) ? MIN_RELAY_TX_FEE : MIN_TX_FEE; - - unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION); - unsigned int nNewBlockSize = nBlockSize + nBytes; - int64 nMinFee = (1 + (int64)nBytes / 1000) * nBaseFee; - - if (fAllowFree) - { - if (nBlockSize == 1) - { - // Transactions under 10K are free - // (about 4500bc if made of 50bc inputs) - if (nBytes < 10000) - nMinFee = 0; - } - else - { - // Free transaction area - if (nNewBlockSize < 27000) - nMinFee = 0; - } - } - - // To limit dust spam, require MIN_TX_FEE/MIN_RELAY_TX_FEE if any output is less than 0.01 - if (nMinFee < nBaseFee) - { - BOOST_FOREACH(const CTxOut& txout, vout) - if (txout.nValue < CENT) - nMinFee = nBaseFee; - } - - // Raise the price as the block approaches full - if (nBlockSize != 1 && nNewBlockSize >= MAX_BLOCK_SIZE_GEN/2) - { - if (nNewBlockSize >= MAX_BLOCK_SIZE_GEN) - return MAX_MONEY; - nMinFee *= MAX_BLOCK_SIZE_GEN / (MAX_BLOCK_SIZE_GEN - nNewBlockSize); - } - - if (!MoneyRange(nMinFee)) - nMinFee = MAX_MONEY; - return nMinFee; - } - + int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=false, enum GetMinFee_mode mode=GMF_BLOCK) const; bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL) { @@ -677,7 +633,6 @@ public: return !(a == b); } - std::string ToStringShort() const { std::string str; @@ -689,7 +644,7 @@ public: { std::string str; str += IsCoinBase()? "Coinbase" : (IsCoinStake()? "Coinstake" : "CTransaction"); - str += strprintf("(hash=%s, nTime=%d, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%d)\n", + str += strprintf("(hash=%s, nTime=%d, ver=%d, vin.size=%"PRIszu", vout.size=%"PRIszu", nLockTime=%d)\n", GetHash().ToString().substr(0,10).c_str(), nTime, nVersion, @@ -858,6 +813,7 @@ public: return !(a == b); } int GetDepthInMainChain() const; + }; @@ -878,6 +834,7 @@ class CBlock { public: // header + static const int CURRENT_VERSION=3; int nVersion; uint256 hashPrevBlock; uint256 hashMerkleRoot; @@ -928,7 +885,7 @@ public: void SetNull() { - nVersion = 3; + nVersion = CBlock::CURRENT_VERSION; hashPrevBlock = 0; hashMerkleRoot = 0; nTime = 0; @@ -982,7 +939,7 @@ public: printf("GetStakeEntropyBit: hashSig=%s", hashSig.ToString().c_str()); hashSig >>= 159; // take the first bit of the hash if (fDebug && GetBoolArg("-printstakemodifier")) - printf(" entropybit=%d\n", hashSig.Get64()); + printf(" entropybit=%"PRI64d"\n", hashSig.Get64()); return hashSig.Get64(); } @@ -1070,8 +1027,6 @@ public: return error("CBlock::WriteToDisk() : AppendBlockFile failed"); // Write index header - unsigned char pchMessageStart[4]; - GetMessageStart(pchMessageStart, true); unsigned int nSize = fileout.GetSerializeSize(*this); fileout << FLATDATA(pchMessageStart) << nSize; @@ -1085,13 +1040,7 @@ public: // Flush stdio buffers and commit to disk before returning fflush(fileout); if (!IsInitialBlockDownload() || (nBestHeight+1) % 500 == 0) - { -#ifdef WIN32 - _commit(_fileno(fileout)); -#else - fsync(fileno(fileout)); -#endif - } + FileCommit(fileout); return true; } @@ -1126,7 +1075,7 @@ public: void print() const { - printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%d, vchBlockSig=%s)\n", + printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%"PRIszu", vchBlockSig=%s)\n", GetHash().ToString().substr(0,20).c_str(), nVersion, hashPrevBlock.ToString().substr(0,20).c_str(), @@ -1147,11 +1096,11 @@ public: bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex); - bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex); + bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck=false); bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true); bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew); bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos); - bool CheckBlock() const; + bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true) const; bool AcceptBlock(); bool GetCoinAge(uint64& nCoinAge) const; // ppcoin: calculate total coin age spent in block bool SignBlock(const CKeyStore& keystore); @@ -1183,6 +1132,7 @@ public: unsigned int nBlockPos; CBigNum bnChainTrust; // ppcoin: trust score of block chain int nHeight; + int64 nMint; int64 nMoneySupply; @@ -1209,7 +1159,6 @@ public: unsigned int nBits; unsigned int nNonce; - CBlockIndex() { phashBlock = NULL; @@ -1311,21 +1260,6 @@ public: return true; } - bool EraseBlockFromDisk() - { - // Open history file - CAutoFile fileout = CAutoFile(OpenBlockFile(nFile, nBlockPos, "rb+"), SER_DISK, CLIENT_VERSION); - if (!fileout) - return false; - - // Overwrite with empty null block - CBlock block; - block.SetNull(); - fileout << block; - - return true; - } - enum { nMedianTimeSpan=11 }; int64 GetMedianTimePast() const @@ -1354,6 +1288,14 @@ public: return pindex->GetMedianTimePast(); } + /** + * Returns true if there are nRequired or more blocks of minVersion or above + * in the last nToCheck blocks, starting at pstart and going backwards. + */ + static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, + unsigned int nRequired, unsigned int nToCheck); + + bool IsProofOfWork() const { return !(nFlags & BLOCK_PROOF_OF_STAKE); @@ -1396,7 +1338,7 @@ public: std::string ToString() const { - return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d nHeight=%d, nMint=%s, nMoneySupply=%s, nFlags=(%s)(%d)(%s), nStakeModifier=%016"PRI64x", nStakeModifierChecksum=%08x, hashProofOfStake=%s, prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)", + return strprintf("CBlockIndex(nprev=%p, pnext=%p, nFile=%u, nBlockPos=%-6d nHeight=%d, nMint=%s, nMoneySupply=%s, nFlags=(%s)(%d)(%s), nStakeModifier=%016"PRI64x", nStakeModifierChecksum=%08x, hashProofOfStake=%s, prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)", pprev, pnext, nFile, nBlockPos, nHeight, FormatMoney(nMint).c_str(), FormatMoney(nMoneySupply).c_str(), GeneratedStakeModifier() ? "MOD" : "-", GetStakeEntropyBit(), IsProofOfStake()? "PoS" : "PoW", @@ -1640,207 +1582,6 @@ public: - -/** Alerts are for notifying old versions if they become too obsolete and - * need to upgrade. The message is displayed in the status bar. - * Alert messages are broadcast as a vector of signed data. Unserializing may - * not read the entire buffer if the alert is for a newer version, but older - * versions can still relay the original data. - */ -class CUnsignedAlert -{ -public: - int nVersion; - int64 nRelayUntil; // when newer nodes stop relaying to newer nodes - int64 nExpiration; - int nID; - int nCancel; - std::set setCancel; - int nMinVer; // lowest version inclusive - int nMaxVer; // highest version inclusive - std::set setSubVer; // empty matches all - int nPriority; - - // Actions - std::string strComment; - std::string strStatusBar; - std::string strReserved; - - IMPLEMENT_SERIALIZE - ( - READWRITE(this->nVersion); - nVersion = this->nVersion; - READWRITE(nRelayUntil); - READWRITE(nExpiration); - READWRITE(nID); - READWRITE(nCancel); - READWRITE(setCancel); - READWRITE(nMinVer); - READWRITE(nMaxVer); - READWRITE(setSubVer); - READWRITE(nPriority); - - READWRITE(strComment); - READWRITE(strStatusBar); - READWRITE(strReserved); - ) - - void SetNull() - { - nVersion = 1; - nRelayUntil = 0; - nExpiration = 0; - nID = 0; - nCancel = 0; - setCancel.clear(); - nMinVer = 0; - nMaxVer = 0; - setSubVer.clear(); - nPriority = 0; - - strComment.clear(); - strStatusBar.clear(); - strReserved.clear(); - } - - std::string ToString() const - { - std::string strSetCancel; - BOOST_FOREACH(int n, setCancel) - strSetCancel += strprintf("%d ", n); - std::string strSetSubVer; - BOOST_FOREACH(std::string str, setSubVer) - strSetSubVer += "\"" + str + "\" "; - return strprintf( - "CAlert(\n" - " nVersion = %d\n" - " nRelayUntil = %"PRI64d"\n" - " nExpiration = %"PRI64d"\n" - " nID = %d\n" - " nCancel = %d\n" - " setCancel = %s\n" - " nMinVer = %d\n" - " nMaxVer = %d\n" - " setSubVer = %s\n" - " nPriority = %d\n" - " strComment = \"%s\"\n" - " strStatusBar = \"%s\"\n" - ")\n", - nVersion, - nRelayUntil, - nExpiration, - nID, - nCancel, - strSetCancel.c_str(), - nMinVer, - nMaxVer, - strSetSubVer.c_str(), - nPriority, - strComment.c_str(), - strStatusBar.c_str()); - } - - void print() const - { - printf("%s", ToString().c_str()); - } -}; - -/** An alert is a combination of a serialized CUnsignedAlert and a signature. */ -class CAlert : public CUnsignedAlert -{ -public: - std::vector vchMsg; - std::vector vchSig; - - CAlert() - { - SetNull(); - } - - IMPLEMENT_SERIALIZE - ( - READWRITE(vchMsg); - READWRITE(vchSig); - ) - - void SetNull() - { - CUnsignedAlert::SetNull(); - vchMsg.clear(); - vchSig.clear(); - } - - bool IsNull() const - { - return (nExpiration == 0); - } - - uint256 GetHash() const - { - return SerializeHash(*this); - } - - bool IsInEffect() const - { - return (GetAdjustedTime() < nExpiration); - } - - bool Cancels(const CAlert& alert) const - { - if (!IsInEffect()) - return false; // this was a no-op before 31403 - return (alert.nID <= nCancel || setCancel.count(alert.nID)); - } - - bool AppliesTo(int nVersion, std::string strSubVerIn) const - { - // TODO: rework for client-version-embedded-in-strSubVer ? - return (IsInEffect() && - nMinVer <= nVersion && nVersion <= nMaxVer && - (setSubVer.empty() || setSubVer.count(strSubVerIn))); - } - - bool AppliesToMe() const - { - return AppliesTo(PROTOCOL_VERSION, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector())); - } - - bool RelayTo(CNode* pnode) const - { - if (!IsInEffect()) - return false; - // returns true if wasn't already contained in the set - if (pnode->setKnown.insert(GetHash()).second) - { - if (AppliesTo(pnode->nVersion, pnode->strSubVer) || - AppliesToMe() || - GetAdjustedTime() < nRelayUntil) - { - pnode->PushMessage("alert", *this); - return true; - } - } - return false; - } - - bool CheckSignature() - { - CKey key; - if (!key.SetPubKey(ParseHex("043fa441fd4203d03f5df2b75ea14e36f20d39f43e7a61aa7552ab9bcd7ecb0e77a3be4585b13fcdaa22ef6e51f1ff6f2929bec2494385b086fb86610e33193195"))) - return error("CAlert::CheckSignature() : SetPubKey failed"); - if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) - return error("CAlert::CheckSignature() : verify signature failed"); - - // Now unserialize the data - CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); - sMsg >> *(CUnsignedAlert*)this; - return true; - } - - bool ProcessAlert(); -}; - class CTxMemPool { public: @@ -1850,8 +1591,10 @@ public: bool accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs); - bool addUnchecked(CTransaction &tx); + bool addUnchecked(const uint256& hash, CTransaction &tx); bool remove(CTransaction &tx); + void clear(); + void queryHashes(std::vector& vtxid); unsigned long size() { diff --git a/src/makefile.bsd b/src/makefile.bsd index 58b541d..eec1a3e 100644 --- a/src/makefile.bsd +++ b/src/makefile.bsd @@ -1,15 +1,13 @@ # Copyright (c) 2009-2010 Satoshi Nakamoto -# Copyright (c) 2011-2012 The PPCoin developers # Distributed under the MIT/X11 software license, see the accompanying -# file license.txt or http://www.opensource.org/licenses/mit-license.php. +# file COPYING or http://www.opensource.org/licenses/mit-license.php. -USE_UPNP:=- +USE_UPNP:=0 +USE_IPV6:=1 + +LINK:=$(CXX) DEFS=-DBOOST_SPIRIT_THREADSAFE -BOOST_INCLUDE_PATH=/usr/local/include -BDB_INCLUDE_PATH=/usr/local/include/db48 -BOOST_LIB_PATH=/usr/local/lib -BDB_LIB_PATH=/usr/local/lib/db48 DEFS += $(addprefix -I,$(CURDIR) $(CURDIR)/obj $(BOOST_INCLUDE_PATH) $(BDB_INCLUDE_PATH) $(OPENSSL_INCLUDE_PATH)) LIBS = $(addprefix -L,$(BOOST_LIB_PATH) $(BDB_LIB_PATH) $(OPENSSL_LIB_PATH)) @@ -47,9 +45,14 @@ ifneq (${USE_UPNP}, -) DEFS += -DUSE_UPNP=$(USE_UPNP) endif +ifneq (${USE_IPV6}, -) + DEFS += -DUSE_IPV6=$(USE_IPV6) +endif + LIBS+= \ -Wl,-B$(LMODE2) \ -l z \ + -l dl \ -l pthread @@ -71,13 +74,14 @@ LIBS+= \ # Make some important things such as the global offset table read only as soon as # the dynamic linker is finished building it. This will prevent overwriting of addresses # which would later be jumped to. - HARDENING+=-Wl,-z,relro -Wl,-z,now + LDHARDENING+=-Wl,-z,relro -Wl,-z,now # Build position independent code to take advantage of Address Space Layout Randomization # offered by some kernels. # see doc/build-unix.txt for more information. ifdef PIE - HARDENING+=-fPIE -pie + HARDENING+=-fPIE + LDHARDENING+=-pie endif # -D_FORTIFY_SOURCE=2 does some checking for potentially exploitable code patterns in @@ -87,11 +91,18 @@ LIBS+= \ DEBUGFLAGS=-g -CXXFLAGS=-O2 -msse2 -xCXXFLAGS=-pthread -Wall -Wextra -Wno-sign-compare -Wno-invalid-offsetof -Wno-unused-parameter -Wformat -Wformat-security \ + +# CXXFLAGS can be specified on the make command line, so we use xCXXFLAGS that only +# adds some defaults in front. Unfortunately, CXXFLAGS=... $(CXXFLAGS) does not work. +xCXXFLAGS=-O0 -msse2 -pthread -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter \ $(DEBUGFLAGS) $(DEFS) $(HARDENING) $(CXXFLAGS) +# LDFLAGS can be specified on the make command line, so we use xLDFLAGS that only +# adds some defaults in front. Unfortunately, LDFLAGS=... $(LDFLAGS) does not work. +xLDFLAGS=$(LDHARDENING) $(LDFLAGS) + OBJS= \ + obj/alert.o \ obj/version.o \ obj/checkpoints.o \ obj/netbase.o \ @@ -107,7 +118,13 @@ OBJS= \ obj/protocol.o \ obj/bitcoinrpc.o \ obj/rpcdump.o \ + obj/rpcnet.o \ + obj/rpcmining.o \ + obj/rpcwallet.o \ + obj/rpcblockchain.o \ + obj/rpcrawtransaction.o \ obj/script.o \ + obj/sync.o \ obj/util.o \ obj/wallet.o \ obj/walletdb.o \ @@ -118,8 +135,12 @@ OBJS= \ obj/scrypt-x86.o \ obj/scrypt-x86_64.o + all: novacoind +test check: test_novacoin FORCE + ./test_novacoin + # auto-generated dependencies: -include obj/*.P -include obj-test/*.P @@ -129,33 +150,33 @@ obj/build.h: FORCE version.cpp: obj/build.h DEFS += -DHAVE_BUILD_INFO -obj/%.o: %.cpp - $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< - @cp $(@:%.o=%.d) $(@:%.o=%.P); \ - sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ - -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ - rm -f $(@:%.o=%.d) - obj/scrypt-x86.o: scrypt-x86.S $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< obj/scrypt-x86_64.o: scrypt-x86_64.S $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< +obj/%.o: %.cpp + $(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< + @cp $(@:%.o=%.d) $(@:%.o=%.P); \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ + rm -f $(@:%.o=%.d) + novacoind: $(OBJS:obj/%=obj/%) - $(CXX) $(xCXXFLAGS) -rdynamic -o $@ $^ $(LDFLAGS) $(LIBS) + $(LINK) $(xCXXFLAGS) -o $@ $^ $(xLDFLAGS) $(LIBS) TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) obj-test/%.o: test/%.cpp - $(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -MMD -o $@ $< + $(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< @cp $(@:%.o=%.d) $(@:%.o=%.P); \ sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ rm -f $(@:%.o=%.d) test_novacoin: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) - $(CXX) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-B$(LMODE) -lboost_unit_test_framework $(LDFLAGS) $(LIBS) + $(LINK) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-B$(LMODE) -lboost_unit_test_framework $(xLDFLAGS) $(LIBS) clean: -rm -f novacoind test_novacoin @@ -163,6 +184,6 @@ clean: -rm -f obj-test/*.o -rm -f obj/*.P -rm -f obj-test/*.P - -rm -f src/build.h + -rm -f obj/build.h FORCE: diff --git a/src/makefile.linux-mingw b/src/makefile.linux-mingw index 25be29f..2013486 100644 --- a/src/makefile.linux-mingw +++ b/src/makefile.linux-mingw @@ -1,50 +1,62 @@ # Copyright (c) 2009-2010 Satoshi Nakamoto # Distributed under the MIT/X11 software license, see the accompanying -# file license.txt or http://www.opensource.org/licenses/mit-license.php. +# file COPYING or http://www.opensource.org/licenses/mit-license.php. DEPSDIR:=/usr/i586-mingw32msvc USE_UPNP:=0 +USE_IPV6:=1 INCLUDEPATHS= \ - -I"$(DEPSDIR)/boost_1_47_0" \ - -I"$(DEPSDIR)/db-4.8.30.NC/build_unix" \ - -I"$(DEPSDIR)/openssl-1.0.1b/include" \ - -I"$(DEPSDIR)" \ + -I"$(CURDIR)" \ -I"$(CURDIR)"/obj \ + -I"$(DEPSDIR)/boost_1_50_0" \ + -I"$(DEPSDIR)/db-4.8.30.NC/build_unix" \ + -I"$(DEPSDIR)/openssl-1.0.1c/include" \ + -I"$(DEPSDIR)" LIBPATHS= \ - -L"$(DEPSDIR)/boost_1_47_0/stage/lib" \ + -L"$(DEPSDIR)/boost_1_50_0/stage/lib" \ -L"$(DEPSDIR)/db-4.8.30.NC/build_unix" \ - -L"$(DEPSDIR)/openssl-1.0.1b" + -L"$(DEPSDIR)/openssl-1.0.1c" LIBS= \ -l boost_system-mt-s \ -l boost_filesystem-mt-s \ -l boost_program_options-mt-s \ -l boost_thread_win32-mt-s \ + -l boost_chrono-mt-s \ -l db_cxx \ -l ssl \ -l crypto DEFS=-D_MT -DWIN32 -D_WINDOWS -DBOOST_THREAD_USE_LIB -DBOOST_SPIRIT_THREADSAFE DEBUGFLAGS=-g -CFLAGS=-O2 -msse2 -w -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) +CFLAGS=-O2 -msse2 -w -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) +LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat TESTDEFS = -DTEST_DATA_DIR=$(abspath test/data) -ifdef USE_UPNP +ifndef USE_UPNP + override USE_UPNP = - +endif +ifneq (${USE_UPNP}, -) LIBPATHS += -L"$(DEPSDIR)/miniupnpc" LIBS += -l miniupnpc -l iphlpapi DEFS += -DSTATICLIB -DUSE_UPNP=$(USE_UPNP) endif -LIBS += -l mingwthrd -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l shlwapi +ifneq (${USE_IPV6}, -) + DEFS += -DUSE_IPV6=$(USE_IPV6) +endif + +LIBS += -l mingwthrd -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l mswsock -l shlwapi # TODO: make the mingw builds smarter about dependencies, like the linux/osx builds are HEADERS = $(wildcard *.h) OBJS= \ + obj/alert.o \ obj/version.o \ obj/checkpoints.o \ obj/netbase.o \ @@ -60,7 +72,13 @@ OBJS= \ obj/protocol.o \ obj/bitcoinrpc.o \ obj/rpcdump.o \ + obj/rpcnet.o \ + obj/rpcmining.o \ + obj/rpcwallet.o \ + obj/rpcblockchain.o \ + obj/rpcrawtransaction.o \ obj/script.o \ + obj/sync.o \ obj/util.o \ obj/wallet.o \ obj/walletdb.o \ @@ -81,14 +99,8 @@ DEFS += -DHAVE_BUILD_INFO obj/%.o: %.cpp $(HEADERS) i586-mingw32msvc-g++ -c $(CFLAGS) -o $@ $< -obj/scrypt-x86.o: scrypt-x86.S - i586-mingw32msvc-g++ -c $(CFLAGS) -MMD -o $@ $< - -obj/scrypt-x86_64.o: scrypt-x86_64.S - i586-mingw32msvc-g++ -c $(CFLAGS) -MMD -o $@ $< - -novacoind.exe: $(OBJS:obj/%=obj/%) - i586-mingw32msvc-g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) +bitcoind.exe: $(OBJS:obj/%=obj/%) + i586-mingw32msvc-g++ $(CFLAGS) $(LDFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) @@ -96,14 +108,19 @@ obj-test/%.o: test/%.cpp $(HEADERS) i586-mingw32msvc-g++ -c $(TESTDEFS) $(CFLAGS) -o $@ $< test_novacoin.exe: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) - i586-mingw32msvc-g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework $(LIBS) + i586-mingw32msvc-g++ $(CFLAGS) $(LDFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework-mt-s $(LIBS) + +obj/scrypt-x86.o: scrypt-x86.S + i586-mingw32msvc-g++ -c $(CFLAGS) -MMD -o $@ $< +obj/scrypt-x86_64.o: scrypt-x86_64.S + i586-mingw32msvc-g++ -c $(CFLAGS) -MMD -o $@ $< clean: -rm -f obj/*.o -rm -f novacoind.exe -rm -f obj-test/*.o -rm -f test_novacoin.exe - -rm -f src/build.h + -rm -f obj/build.h FORCE: diff --git a/src/makefile.mingw b/src/makefile.mingw index d7f2f53..b28088d 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -1,104 +1,58 @@ # Copyright (c) 2009-2010 Satoshi Nakamoto -# Copyright (c) 2012 The PPCoin developers # Distributed under the MIT/X11 software license, see the accompanying -# file license.txt or http://www.opensource.org/licenses/mit-license.php. - -# Windows commandline build procedure: -# - Install MinGW following http://www.mingw.org/wiki/Getting_Started. -# Install with the C++ and MSYS options checked -# -# - Add/edit 'MAKE' environment variable with value '/c/MinGW32/bin/mingw32-make.exe' -# -# - Build openssl library version: 1.0.1b -# download from http://www.openssl.org/source/ -# Extract to c:\openssl-1.0.1b-mgw -# In MinGW MSYS: -# ./config -# make -# -# - Build Berkeley DB library version: 4.8.30.NC -# download from http://www.oracle.com/technology/software/products/berkeley-db/index.html -# Extract to c:\db-4.8.30.NC-mgw -# In MinGW MSYS: -# cd build_unix -# sh ../dist/configure --disable-replication --enable-mingw --enable-cxx --prefix=/usr/local -# Edit db.h@113 in build_unix -# from -# typedef pthread_t db_threadid_t; -# to -# typedef u_int32_t db_threadid_t; -# Then -# make -# -# - Build Boost C++ library version: 1.47.0 -# download from http://www.boost.org/users/download/ -# Extract to c:\boost-1.47.0-mgw -# Install Boost.Build: -# cd tools\build\v2 -# bootstrap.bat -# b2 install --prefix=BOOST_BUILD_INSTALL_DIR -# Add BOOST_BUILD_INSTALL_DIR to your PATH system environment variable -# Build boost library in MSDOS: -# cd c:\boost-1.47.0-mgw -# bjam toolset=gcc --build-type=complete stage -# -# - Build Miniupnp library version: 1.6 -# download from http://miniupnp.free.fr/files/ -# file name is miniupnpc-1.6.20120509.tar.gz -# Extract to c:\miniupnpc-1.6-mgw -# In MinGW MSYS shell: -# cd /c/miniupnpc-1.6-mgw -# make -f Makefile.mingw -# mkdir miniupnpc -# cp *.h miniupnpc/ -# -# - Build novacoind.exe -# in MinGW MSYS -# cd novacoin/src -# make novacoind.exe -f makefile.mingw USE_UPNP=1 -# -# +# file COPYING or http://www.opensource.org/licenses/mit-license.php. USE_UPNP:=0 +USE_IPV6:=1 INCLUDEPATHS= \ - -I"C:\boost-1.47.0-mgw" \ + -I"C:\boost-1.50.0-mgw" \ -I"C:\db-4.8.30.NC-mgw\build_unix" \ - -I"C:\openssl-1.0.1b-mgw\include" + -I"C:\openssl-1.0.1c-mgw\include" LIBPATHS= \ - -L"C:\boost-1.47.0-mgw\stage\lib" \ + -L"C:\boost-1.50.0-mgw\stage\lib" \ -L"C:\db-4.8.30.NC-mgw\build_unix" \ - -L"C:\openssl-1.0.1b-mgw" + -L"C:\openssl-1.0.1c-mgw" LIBS= \ - -l boost_system-mgw46-mt-s-1_47 \ - -l boost_filesystem-mgw46-mt-s-1_47 \ - -l boost_program_options-mgw46-mt-s-1_47 \ - -l boost_thread-mgw46-mt-s-1_47 \ + -l boost_system-mgw45-mt-s-1_50 \ + -l boost_filesystem-mgw45-mt-s-1_50 \ + -l boost_program_options-mgw45-mt-s-1_50 \ + -l boost_thread-mgw45-mt-s-1_50 \ + -l boost_chrono-mgw45-mt-s-1_50 \ -l db_cxx \ -l ssl \ -l crypto DEFS=-DWIN32 -D_WINDOWS -DBOOST_THREAD_USE_LIB -DBOOST_SPIRIT_THREADSAFE DEBUGFLAGS=-g -CFLAGS=-mthreads -msse2 -O2 -w -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) +CFLAGS=-mthreads -O2 -msse2 -w -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) +LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat TESTDEFS = -DTEST_DATA_DIR=$(abspath test/data) -ifdef USE_UPNP +ifndef USE_UPNP + override USE_UPNP = - +endif +ifneq (${USE_UPNP}, -) INCLUDEPATHS += -I"C:\miniupnpc-1.6-mgw" LIBPATHS += -L"C:\miniupnpc-1.6-mgw" LIBS += -l miniupnpc -l iphlpapi DEFS += -DSTATICLIB -DUSE_UPNP=$(USE_UPNP) endif -LIBS += -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l shlwapi +ifneq (${USE_IPV6}, -) + DEFS += -DUSE_IPV6=$(USE_IPV6) +endif + +LIBS += -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l mswsock -l shlwapi # TODO: make the mingw builds smarter about dependencies, like the linux/osx builds are HEADERS = $(wildcard *.h) OBJS= \ + obj/alert.o \ obj/version.o \ obj/checkpoints.o \ obj/netbase.o \ @@ -114,7 +68,13 @@ OBJS= \ obj/protocol.o \ obj/bitcoinrpc.o \ obj/rpcdump.o \ + obj/rpcnet.o \ + obj/rpcmining.o \ + obj/rpcwallet.o \ + obj/rpcblockchain.o \ + obj/rpcrawtransaction.o \ obj/script.o \ + obj/sync.o \ obj/util.o \ obj/wallet.o \ obj/walletdb.o \ @@ -125,9 +85,11 @@ OBJS= \ obj/scrypt-x86.o \ obj/scrypt-x86_64.o - all: novacoind.exe +test check: test_novacoin.exe FORCE + test_novacoin.exe + obj/%.o: %.cpp $(HEADERS) g++ -c $(CFLAGS) -o $@ $< @@ -138,18 +100,19 @@ obj/scrypt-x86_64.o: scrypt-x86_64.S $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< novacoind.exe: $(OBJS:obj/%=obj/%) - g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) + g++ $(CFLAGS) $(LDFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) obj-test/%.o: test/%.cpp $(HEADERS) g++ -c $(TESTDEFS) $(CFLAGS) -o $@ $< -test_novacoin.exe: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) - g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework $(LIBS) +test_bitcoin.exe: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) + g++ $(CFLAGS) $(LDFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework $(LIBS) clean: -del /Q novacoind test_novacoin -del /Q obj\* -del /Q obj-test\* - -del /Q build.h + +FORCE: diff --git a/src/makefile.osx b/src/makefile.osx index 8a7c960..c699be5 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -1,7 +1,7 @@ # -*- mode: Makefile; -*- # Copyright (c) 2011 Bitcoin Developers # Distributed under the MIT/X11 software license, see the accompanying -# file license.txt or http://www.opensource.org/licenses/mit-license.php. +# file COPYING or http://www.opensource.org/licenses/mit-license.php. # Mac OS X makefile for bitcoin # Originally by Laszlo Hanyecz (solar@heliacal.net) @@ -20,6 +20,7 @@ LIBPATHS= \ -L"$(DEPSDIR)/lib/db48" USE_UPNP:=1 +USE_IPV6:=1 LIBS= -dead_strip @@ -65,10 +66,11 @@ CFLAGS = -g -msse2 endif # ppc doesn't work because we don't support big-endian -CFLAGS += -Wextra -Wno-sign-compare -Wno-invalid-offsetof -Wformat-security \ +CFLAGS += -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter \ $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) OBJS= \ + obj/alert.o \ obj/version.o \ obj/checkpoints.o \ obj/netbase.o \ @@ -84,7 +86,13 @@ OBJS= \ obj/protocol.o \ obj/bitcoinrpc.o \ obj/rpcdump.o \ + obj/rpcnet.o \ + obj/rpcmining.o \ + obj/rpcwallet.o \ + obj/rpcblockchain.o \ + obj/rpcrawtransaction.o \ obj/script.o \ + obj/sync.o \ obj/util.o \ obj/wallet.o \ obj/walletdb.o \ @@ -95,7 +103,10 @@ OBJS= \ obj/scrypt-x86.o \ obj/scrypt-x86_64.o -ifdef USE_UPNP +ifndef USE_UPNP + override USE_UPNP = - +endif +ifneq (${USE_UPNP}, -) DEFS += -DUSE_UPNP=$(USE_UPNP) ifdef STATIC LIBS += $(DEPSDIR)/lib/libminiupnpc.a @@ -104,8 +115,15 @@ else endif endif +ifneq (${USE_IPV6}, -) + DEFS += -DUSE_IPV6=$(USE_IPV6) +endif + all: novacoind +test check: test_novacoin FORCE + ./test_novacoin + # auto-generated dependencies: -include obj/*.P -include obj-test/*.P @@ -116,7 +134,7 @@ version.cpp: obj/build.h DEFS += -DHAVE_BUILD_INFO obj/%.o: %.cpp - $(CXX) -c $(CFLAGS) -MMD -o $@ $< + $(CXX) -c $(CFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< @cp $(@:%.o=%.d) $(@:%.o=%.P); \ sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ @@ -134,7 +152,7 @@ novacoind: $(OBJS:obj/%=obj/%) TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) obj-test/%.o: test/%.cpp - $(CXX) -c $(TESTDEFS) $(CFLAGS) -MMD -o $@ $< + $(CXX) -c $(TESTDEFS) $(CFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< @cp $(@:%.o=%.d) $(@:%.o=%.P); \ sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ @@ -149,6 +167,6 @@ clean: -rm -f obj-test/*.o -rm -f obj/*.P -rm -f obj-test/*.P - -rm -f src/build.h + -rm -f obj/build.h FORCE: diff --git a/src/makefile.unix b/src/makefile.unix index 684585f..0af2807 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -1,9 +1,11 @@ # Copyright (c) 2009-2010 Satoshi Nakamoto -# Copyright (c) 2011-2012 The PPCoin developers # Distributed under the MIT/X11 software license, see the accompanying -# file license.txt or http://www.opensource.org/licenses/mit-license.php. +# file COPYING or http://www.opensource.org/licenses/mit-license.php. USE_UPNP:=0 +USE_IPV6:=1 + +LINK:=$(CXX) DEFS=-DBOOST_SPIRIT_THREADSAFE @@ -42,6 +44,10 @@ ifneq (${USE_UPNP}, -) DEFS += -DUSE_UPNP=$(USE_UPNP) endif +ifneq (${USE_IPV6}, -) + DEFS += -DUSE_IPV6=$(USE_IPV6) +endif + LIBS+= \ -Wl,-B$(LMODE2) \ -l z \ @@ -67,13 +73,14 @@ LIBS+= \ # Make some important things such as the global offset table read only as soon as # the dynamic linker is finished building it. This will prevent overwriting of addresses # which would later be jumped to. - HARDENING+=-Wl,-z,relro -Wl,-z,now + LDHARDENING+=-Wl,-z,relro -Wl,-z,now # Build position independent code to take advantage of Address Space Layout Randomization # offered by some kernels. # see doc/build-unix.txt for more information. ifdef PIE - HARDENING+=-fPIE -pie + HARDENING+=-fPIE + LDHARDENING+=-pie endif # -D_FORTIFY_SOURCE=2 does some checking for potentially exploitable code patterns in @@ -83,11 +90,18 @@ LIBS+= \ DEBUGFLAGS=-g -CXXFLAGS=-O2 -msse2 -xCXXFLAGS=-pthread -Wall -Wextra -Wno-sign-compare -Wno-invalid-offsetof -Wno-unused-parameter -Wformat -Wformat-security \ + +# CXXFLAGS can be specified on the make command line, so we use xCXXFLAGS that only +# adds some defaults in front. Unfortunately, CXXFLAGS=... $(CXXFLAGS) does not work. +xCXXFLAGS=-O0 -msse2 -pthread -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter \ $(DEBUGFLAGS) $(DEFS) $(HARDENING) $(CXXFLAGS) +# LDFLAGS can be specified on the make command line, so we use xLDFLAGS that only +# adds some defaults in front. Unfortunately, LDFLAGS=... $(LDFLAGS) does not work. +xLDFLAGS=$(LDHARDENING) $(LDFLAGS) + OBJS= \ + obj/alert.o \ obj/version.o \ obj/checkpoints.o \ obj/netbase.o \ @@ -103,7 +117,13 @@ OBJS= \ obj/protocol.o \ obj/bitcoinrpc.o \ obj/rpcdump.o \ + obj/rpcnet.o \ + obj/rpcmining.o \ + obj/rpcwallet.o \ + obj/rpcblockchain.o \ + obj/rpcrawtransaction.o \ obj/script.o \ + obj/sync.o \ obj/util.o \ obj/wallet.o \ obj/walletdb.o \ @@ -114,8 +134,12 @@ OBJS= \ obj/scrypt-x86.o \ obj/scrypt-x86_64.o + all: novacoind +test check: test_novacoin FORCE + ./test_novacoin + # auto-generated dependencies: -include obj/*.P -include obj-test/*.P @@ -125,33 +149,33 @@ obj/build.h: FORCE version.cpp: obj/build.h DEFS += -DHAVE_BUILD_INFO -obj/%.o: %.cpp - $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< - @cp $(@:%.o=%.d) $(@:%.o=%.P); \ - sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ - -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ - rm -f $(@:%.o=%.d) - obj/scrypt-x86.o: scrypt-x86.S $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< obj/scrypt-x86_64.o: scrypt-x86_64.S $(CXX) -c $(xCXXFLAGS) -MMD -o $@ $< +obj/%.o: %.cpp + $(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< + @cp $(@:%.o=%.d) $(@:%.o=%.P); \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ + rm -f $(@:%.o=%.d) + novacoind: $(OBJS:obj/%=obj/%) - $(CXX) $(xCXXFLAGS) -rdynamic -o $@ $^ $(LDFLAGS) $(LIBS) + $(LINK) $(xCXXFLAGS) -o $@ $^ $(xLDFLAGS) $(LIBS) TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) obj-test/%.o: test/%.cpp - $(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -MMD -o $@ $< + $(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $< @cp $(@:%.o=%.d) $(@:%.o=%.P); \ sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ rm -f $(@:%.o=%.d) test_novacoin: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) - $(CXX) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-B$(LMODE) -lboost_unit_test_framework $(LDFLAGS) $(LIBS) + $(LINK) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-B$(LMODE) -lboost_unit_test_framework $(xLDFLAGS) $(LIBS) clean: -rm -f novacoind test_novacoin @@ -159,6 +183,6 @@ clean: -rm -f obj-test/*.o -rm -f obj/*.P -rm -f obj-test/*.P - -rm -f src/build.h + -rm -f obj/build.h FORCE: diff --git a/src/mruset.h b/src/mruset.h index ad2e160..a527351 100644 --- a/src/mruset.h +++ b/src/mruset.h @@ -51,7 +51,7 @@ public: size_type max_size(size_type s) { if (s) - while (queue.size() >= s) + while (queue.size() > s) { set.erase(queue.front()); queue.pop_front(); diff --git a/src/net.cpp b/src/net.cpp index f87acbd..007f49d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -36,23 +35,30 @@ void ThreadOpenAddedConnections2(void* parg); void ThreadMapPort2(void* parg); #endif void ThreadDNSAddressSeed2(void* parg); -bool OpenNetworkConnection(const CAddress& addrConnect, bool fUseGrant = true); +bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false); +struct LocalServiceInfo { + int nScore; + int nPort; +}; // // Global state variables // bool fClient = false; -bool fAllowDNS = false; -static bool fUseUPnP = false; +bool fDiscover = true; +bool fUseUPnP = false; uint64 nLocalServices = (fClient ? 0 : NODE_NETWORK); -CAddress addrLocalHost(CService("0.0.0.0", 0), nLocalServices); -CAddress addrSeenByPeer(CService("0.0.0.0", 0), nLocalServices); +static CCriticalSection cs_mapLocalHost; +static map mapLocalHost; +static bool vfReachable[NET_MAX] = {}; +static bool vfLimited[NET_MAX] = {}; static CNode* pnodeLocalHost = NULL; +CAddress addrSeenByPeer(CService("0.0.0.0", 0), nLocalServices); uint64 nLocalHostNonce = 0; array vnThreadsRunning; -static SOCKET hListenSocket = INVALID_SOCKET; +static std::vector vhListenSocket; CAddrMan addrman; vector vNodes; @@ -62,12 +68,20 @@ deque > vRelayExpiration; CCriticalSection cs_mapRelay; map mapAlreadyAskedFor; +static deque vOneShots; +CCriticalSection cs_vOneShots; set setservAddNodeAddresses; CCriticalSection cs_setservAddNodeAddresses; static CSemaphore *semOutbound = NULL; +void AddOneShot(string strDest) +{ + LOCK(cs_vOneShots); + vOneShots.push_back(strDest); +} + unsigned short GetListenPort() { return (unsigned short)(GetArg("-port", GetDefaultPort())); @@ -84,7 +98,44 @@ void CNode::PushGetBlocks(CBlockIndex* pindexBegin, uint256 hashEnd) PushMessage("getblocks", CBlockLocator(pindexBegin), hashEnd); } +// find 'best' local address for a particular peer +bool GetLocal(CService& addr, const CNetAddr *paddrPeer) +{ + if (fNoListen) + return false; + int nBestScore = -1; + int nBestReachability = -1; + { + LOCK(cs_mapLocalHost); + for (map::iterator it = mapLocalHost.begin(); it != mapLocalHost.end(); it++) + { + int nScore = (*it).second.nScore; + int nReachability = (*it).first.GetReachabilityFrom(paddrPeer); + if (nReachability > nBestReachability || (nReachability == nBestReachability && nScore > nBestScore)) + { + addr = CService((*it).first, (*it).second.nPort); + nBestReachability = nReachability; + nBestScore = nScore; + } + } + } + return nBestScore >= 0; +} + +// get best local address for a particular peer as a CAddress +CAddress GetLocalAddress(const CNetAddr *paddrPeer) +{ + CAddress ret(CService("0.0.0.0",0),0); + CService addr; + if (GetLocal(addr, paddrPeer)) + { + ret = CAddress(addr); + ret.nServices = nLocalServices; + ret.nTime = GetAdjustedTime(); + } + return ret; +} bool RecvLine(SOCKET hSocket, string& strLine) { @@ -137,7 +188,117 @@ bool RecvLine(SOCKET hSocket, string& strLine) } } +// used when scores of local addresses may have changed +// pushes better local address to peers +void static AdvertizeLocal() +{ + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + { + if (pnode->fSuccessfullyConnected) + { + CAddress addrLocal = GetLocalAddress(&pnode->addr); + if (addrLocal.IsRoutable() && (CService)addrLocal != (CService)pnode->addrLocal) + { + pnode->PushAddress(addrLocal); + pnode->addrLocal = addrLocal; + } + } + } +} + +void SetReachable(enum Network net, bool fFlag) +{ + LOCK(cs_mapLocalHost); + vfReachable[net] = fFlag; + if (net == NET_IPV6 && fFlag) + vfReachable[NET_IPV4] = true; +} + +// learn a new local address +bool AddLocal(const CService& addr, int nScore) +{ + if (!addr.IsRoutable()) + return false; + if (!fDiscover && nScore < LOCAL_MANUAL) + return false; + + if (IsLimited(addr)) + return false; + + printf("AddLocal(%s,%i)\n", addr.ToString().c_str(), nScore); + + { + LOCK(cs_mapLocalHost); + bool fAlready = mapLocalHost.count(addr) > 0; + LocalServiceInfo &info = mapLocalHost[addr]; + if (!fAlready || nScore >= info.nScore) { + info.nScore = nScore + (fAlready ? 1 : 0); + info.nPort = addr.GetPort(); + } + SetReachable(addr.GetNetwork()); + } + + AdvertizeLocal(); + + return true; +} + +bool AddLocal(const CNetAddr &addr, int nScore) +{ + return AddLocal(CService(addr, GetListenPort()), nScore); +} + +/** Make a particular network entirely off-limits (no automatic connects to it) */ +void SetLimited(enum Network net, bool fLimited) +{ + if (net == NET_UNROUTABLE) + return; + LOCK(cs_mapLocalHost); + vfLimited[net] = fLimited; +} + +bool IsLimited(enum Network net) +{ + LOCK(cs_mapLocalHost); + return vfLimited[net]; +} + +bool IsLimited(const CNetAddr &addr) +{ + return IsLimited(addr.GetNetwork()); +} + +/** vote for a local address */ +bool SeenLocal(const CService& addr) +{ + { + LOCK(cs_mapLocalHost); + if (mapLocalHost.count(addr) == 0) + return false; + mapLocalHost[addr].nScore++; + } + + AdvertizeLocal(); + + return true; +} + +/** check whether a given address is potentially local */ +bool IsLocal(const CService& addr) +{ + LOCK(cs_mapLocalHost); + return mapLocalHost.count(addr) > 0; +} + +/** check whether a given address is in a network we can probably connect to */ +bool IsReachable(const CNetAddr& addr) +{ + LOCK(cs_mapLocalHost); + enum Network net = addr.GetNetwork(); + return vfReachable[net] && !vfLimited[net]; +} bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const char* pszKeyword, CNetAddr& ipRet) { @@ -192,15 +353,12 @@ bool GetMyExternalIP(CNetAddr& ipRet) const char* pszGet; const char* pszKeyword; - if (fNoListen||fUseProxy) - return false; - for (int nLookup = 0; nLookup <= 1; nLookup++) for (int nHost = 1; nHost <= 2; nHost++) { // We should be phasing out our use of sites like these. If we need // replacements, we should ask for volunteers to put this simple - // php file on their webserver that prints the client IP: + // php file on their web server that prints the client IP: // if (nHost == 1) { @@ -250,33 +408,14 @@ bool GetMyExternalIP(CNetAddr& ipRet) void ThreadGetMyExternalIP(void* parg) { - // Wait for IRC to get it first - if (GetBoolArg("-irc", true)) - { - for (int i = 0; i < 2 * 60; i++) - { - Sleep(1000); - if (fGotExternalIP || fShutdown) - return; - } - } + // Make this thread recognisable as the external IP detection thread + RenameThread("bitcoin-ext-ip"); - // Fallback in case IRC fails to get it + CNetAddr addrLocalHost; if (GetMyExternalIP(addrLocalHost)) { printf("GetMyExternalIP() returned %s\n", addrLocalHost.ToStringIP().c_str()); - if (addrLocalHost.IsRoutable()) - { - // If we already connected to a few before we had our IP, go back and addr them. - // setAddrKnown automatically filters any duplicate sends. - CAddress addr(addrLocalHost); - addr.nTime = GetAdjustedTime(); - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - pnode->PushAddress(addr); - } - } + AddLocal(addrLocalHost, LOCAL_HTTP); } } @@ -306,6 +445,15 @@ CNode* FindNode(const CNetAddr& ip) return NULL; } +CNode* FindNode(std::string addrName) +{ + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if (pnode->addrName == addrName) + return (pnode); + return NULL; +} + CNode* FindNode(const CService& addr) { { @@ -317,52 +465,56 @@ CNode* FindNode(const CService& addr) return NULL; } -CNode* ConnectNode(CAddress addrConnect, int64 nTimeout) +CNode* ConnectNode(CAddress addrConnect, const char *pszDest, int64 nTimeout) { - if ((CNetAddr)addrConnect == (CNetAddr)addrLocalHost) - return NULL; + if (pszDest == NULL) { + if (IsLocal(addrConnect)) + return NULL; - // Look for an existing connection - CNode* pnode = FindNode((CService)addrConnect); - if (pnode) - { - if (nTimeout != 0) - pnode->AddRef(nTimeout); - else - pnode->AddRef(); - return pnode; + // Look for an existing connection + CNode* pnode = FindNode((CService)addrConnect); + if (pnode) + { + if (nTimeout != 0) + pnode->AddRef(nTimeout); + else + pnode->AddRef(); + return pnode; + } } + /// debug print printf("trying connection %s lastseen=%.1fhrs\n", - addrConnect.ToString().c_str(), - (double)(addrConnect.nTime - GetAdjustedTime())/3600.0); - - addrman.Attempt(addrConnect); + pszDest ? pszDest : addrConnect.ToString().c_str(), + pszDest ? 0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0); // Connect SOCKET hSocket; - if (ConnectSocket(addrConnect, hSocket)) + if (pszDest ? ConnectSocketByName(addrConnect, hSocket, pszDest, GetDefaultPort()) : ConnectSocket(addrConnect, hSocket)) { + addrman.Attempt(addrConnect); + /// debug print - printf("connected %s\n", addrConnect.ToString().c_str()); + printf("connected %s\n", pszDest ? pszDest : addrConnect.ToString().c_str()); - // Set to nonblocking + // Set to non-blocking #ifdef WIN32 u_long nOne = 1; if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) - printf("ConnectSocket() : ioctlsocket nonblocking setting failed, error %d\n", WSAGetLastError()); + printf("ConnectSocket() : ioctlsocket non-blocking setting failed, error %d\n", WSAGetLastError()); #else if (fcntl(hSocket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR) - printf("ConnectSocket() : fcntl nonblocking setting failed, error %d\n", errno); + printf("ConnectSocket() : fcntl non-blocking setting failed, error %d\n", errno); #endif // Add node - CNode* pnode = new CNode(hSocket, addrConnect, false); + CNode* pnode = new CNode(hSocket, addrConnect, pszDest ? pszDest : "", false); if (nTimeout != 0) pnode->AddRef(nTimeout); else pnode->AddRef(); + { LOCK(cs_vNodes); vNodes.push_back(pnode); @@ -382,9 +534,7 @@ void CNode::CloseSocketDisconnect() fDisconnect = true; if (hSocket != INVALID_SOCKET) { - if (fDebug) - printf("%s ", DateTimeStrFormat(GetTime()).c_str()); - printf("disconnecting node %s\n", addr.ToString().c_str()); + printf("disconnecting node %s\n", addrName.c_str()); closesocket(hSocket); hSocket = INVALID_SOCKET; vRecv.clear(); @@ -400,9 +550,10 @@ void CNode::PushVersion() { /// when NTP implemented, change to just nTime = GetAdjustedTime() int64 nTime = (fInbound ? GetAdjustedTime() : GetTime()); - CAddress addrYou = (fUseProxy ? CAddress(CService("0.0.0.0",0)) : addr); - CAddress addrMe = (fUseProxy || !addrLocalHost.IsRoutable() ? CAddress(CService("0.0.0.0",0)) : addrLocalHost); + CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr) ? addr : CAddress(CService("0.0.0.0",0))); + CAddress addrMe = GetLocalAddress(&addr); RAND_bytes((unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce)); + printf("send version message: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", PROTOCOL_VERSION, nBestHeight, addrMe.ToString().c_str(), addrYou.ToString().c_str(), addr.ToString().c_str()); PushMessage("version", PROTOCOL_VERSION, nLocalServices, nTime, addrYou, addrMe, nLocalHostNonce, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector()), nBestHeight); } @@ -439,7 +590,7 @@ bool CNode::Misbehaving(int howmuch) { if (addr.IsLocal()) { - printf("Warning: local node %s misbehaving\n", addr.ToString().c_str()); + printf("Warning: Local node %s misbehaving (delta: %d)!\n", addrName.c_str(), howmuch); return false; } @@ -447,19 +598,36 @@ bool CNode::Misbehaving(int howmuch) if (nMisbehavior >= GetArg("-banscore", 100)) { int64 banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban + printf("Misbehaving: %s (%d -> %d) DISCONNECTING\n", addr.ToString().c_str(), nMisbehavior-howmuch, nMisbehavior); { LOCK(cs_setBanned); if (setBanned[addr] < banTime) setBanned[addr] = banTime; } CloseSocketDisconnect(); - printf("Disconnected %s for misbehavior (score=%d)\n", addr.ToString().c_str(), nMisbehavior); return true; - } + } else + printf("Misbehaving: %s (%d -> %d)\n", addr.ToString().c_str(), nMisbehavior-howmuch, nMisbehavior); return false; } - +#undef X +#define X(name) stats.name = name +void CNode::copyStats(CNodeStats &stats) +{ + X(nServices); + X(nLastSend); + X(nLastRecv); + X(nTimeConnected); + X(addrName); + X(nVersion); + X(strSubVer); + X(fInbound); + X(nReleaseTime); + X(nStartingHeight); + X(nMisbehavior); +} +#undef X @@ -472,7 +640,9 @@ bool CNode::Misbehaving(int howmuch) void ThreadSocketHandler(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadSocketHandler(parg)); + // Make this thread recognisable as the networking thread + RenameThread("bitcoin-net"); + try { vnThreadsRunning[THREAD_SOCKETHANDLER]++; @@ -486,7 +656,7 @@ void ThreadSocketHandler(void* parg) vnThreadsRunning[THREAD_SOCKETHANDLER]--; throw; // support pthread_cancel() } - printf("ThreadSocketHandler exiting\n"); + printf("ThreadSocketHandler exited\n"); } void ThreadSocketHandler2(void* parg) @@ -512,9 +682,8 @@ void ThreadSocketHandler2(void* parg) // remove from vNodes vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); - if (pnode->fHasGrant) - semOutbound->post(); - pnode->fHasGrant = false; + // release outbound grant (if any) + pnode->grantOutbound.Release(); // close socket and cleanup pnode->CloseSocketDisconnect(); @@ -564,7 +733,7 @@ void ThreadSocketHandler2(void* parg) if (vNodes.size() != nPrevNodeCount) { nPrevNodeCount = vNodes.size(); - MainFrameRepaint(); + uiInterface.NotifyNumConnectionsChanged(vNodes.size()); } @@ -582,10 +751,13 @@ void ThreadSocketHandler2(void* parg) FD_ZERO(&fdsetSend); FD_ZERO(&fdsetError); SOCKET hSocketMax = 0; + bool have_fds = false; - if(hListenSocket != INVALID_SOCKET) + BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) { FD_SET(hListenSocket, &fdsetRecv); - hSocketMax = max(hSocketMax, hListenSocket); + hSocketMax = max(hSocketMax, hListenSocket); + have_fds = true; + } { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) @@ -595,6 +767,7 @@ void ThreadSocketHandler2(void* parg) FD_SET(pnode->hSocket, &fdsetRecv); FD_SET(pnode->hSocket, &fdsetError); hSocketMax = max(hSocketMax, pnode->hSocket); + have_fds = true; { TRY_LOCK(pnode->cs_vSend, lockSend); if (lockSend && !pnode->vSend.empty()) @@ -604,15 +777,16 @@ void ThreadSocketHandler2(void* parg) } vnThreadsRunning[THREAD_SOCKETHANDLER]--; - int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, &fdsetError, &timeout); + int nSelect = select(have_fds ? hSocketMax + 1 : 0, + &fdsetRecv, &fdsetSend, &fdsetError, &timeout); vnThreadsRunning[THREAD_SOCKETHANDLER]++; if (fShutdown) return; if (nSelect == SOCKET_ERROR) { - int nErr = WSAGetLastError(); - if (hSocketMax != INVALID_SOCKET) + if (have_fds) { + int nErr = WSAGetLastError(); printf("socket select error %d\n", nErr); for (unsigned int i = 0; i <= hSocketMax; i++) FD_SET(i, &fdsetRecv); @@ -626,16 +800,22 @@ void ThreadSocketHandler2(void* parg) // // Accept new connections // + BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) if (hListenSocket != INVALID_SOCKET && FD_ISSET(hListenSocket, &fdsetRecv)) { - struct sockaddr_in sockaddr; +#ifdef USE_IPV6 + struct sockaddr_storage sockaddr; +#else + struct sockaddr sockaddr; +#endif socklen_t len = sizeof(sockaddr); SOCKET hSocket = accept(hListenSocket, (struct sockaddr*)&sockaddr, &len); CAddress addr; int nInbound = 0; if (hSocket != INVALID_SOCKET) - addr = CAddress(sockaddr); + if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) + printf("Warning: Unknown socket family\n"); { LOCK(cs_vNodes); @@ -646,8 +826,9 @@ void ThreadSocketHandler2(void* parg) if (hSocket == INVALID_SOCKET) { - if (WSAGetLastError() != WSAEWOULDBLOCK) - printf("socket error accept failed: %d\n", WSAGetLastError()); + int nErr = WSAGetLastError(); + if (nErr != WSAEWOULDBLOCK) + printf("socket error accept failed: %d\n", nErr); } else if (nInbound >= GetArg("-maxconnections", 125) - MAX_OUTBOUND_CONNECTIONS) { @@ -665,7 +846,7 @@ void ThreadSocketHandler2(void* parg) else { printf("accepted connection %s\n", addr.ToString().c_str()); - CNode* pnode = new CNode(hSocket, addr, true); + CNode* pnode = new CNode(hSocket, addr, "", true); pnode->AddRef(); { LOCK(cs_vNodes); @@ -705,7 +886,7 @@ void ThreadSocketHandler2(void* parg) if (nPos > ReceiveBufferSize()) { if (!pnode->fDisconnect) - printf("socket recv flood control disconnect (%d bytes)\n", vRecv.size()); + printf("socket recv flood control disconnect (%"PRIszu" bytes)\n", vRecv.size()); pnode->CloseSocketDisconnect(); } else { @@ -769,11 +950,6 @@ void ThreadSocketHandler2(void* parg) pnode->CloseSocketDisconnect(); } } - if (vSend.size() > SendBufferSize()) { - if (!pnode->fDisconnect) - printf("socket send flood control disconnect (%d bytes)\n", vSend.size()); - pnode->CloseSocketDisconnect(); - } } } } @@ -823,7 +999,9 @@ void ThreadSocketHandler2(void* parg) #ifdef USE_UPNP void ThreadMapPort(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadMapPort(parg)); + // Make this thread recognisable as the UPnP thread + RenameThread("bitcoin-UPnP"); + try { vnThreadsRunning[THREAD_UPNP]++; @@ -837,16 +1015,14 @@ void ThreadMapPort(void* parg) vnThreadsRunning[THREAD_UPNP]--; PrintException(NULL, "ThreadMapPort()"); } - printf("ThreadMapPort exiting\n"); + printf("ThreadMapPort exited\n"); } void ThreadMapPort2(void* parg) { printf("ThreadMapPort started\n"); - char port[6]; - sprintf(port, "%d", GetListenPort()); - + std::string port = strprintf("%u", GetListenPort()); const char * multicastif = 0; const char * minissdpdpath = 0; struct UPNPDev * devlist = 0; @@ -868,8 +1044,7 @@ void ThreadMapPort2(void* parg) r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); if (r == 1) { - if (!addrLocalHost.IsRoutable()) - { + if (fDiscover) { char externalIPAddress[40]; r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); if(r != UPNPCOMMAND_SUCCESS) @@ -879,9 +1054,7 @@ void ThreadMapPort2(void* parg) if(externalIPAddress[0]) { printf("UPnP: ExternalIPAddress = %s\n", externalIPAddress); - CAddress addrExternalFromUPnP(CService(externalIPAddress, 0), nLocalServices); - if (addrExternalFromUPnP.IsRoutable()) - addrLocalHost = addrExternalFromUPnP; + AddLocal(CNetAddr(externalIPAddress), LOCAL_UPNP); } else printf("UPnP: GetExternalIPAddress failed.\n"); @@ -892,23 +1065,23 @@ void ThreadMapPort2(void* parg) #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port, port, lanaddr, strDesc.c_str(), "TCP", 0); + port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0); #else /* miniupnpc 1.6 */ r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port, port, lanaddr, strDesc.c_str(), "TCP", 0, "0"); + port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); #endif if(r!=UPNPCOMMAND_SUCCESS) printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", - port, port, lanaddr, r, strupnperror(r)); + port.c_str(), port.c_str(), lanaddr, r, strupnperror(r)); else printf("UPnP Port Mapping successful.\n"); int i = 1; loop { if (fShutdown || !fUseUPnP) { - r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port, "TCP", 0); + r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); printf("UPNP_DeletePortMapping() returned : %d\n", r); freeUPNPDevlist(devlist); devlist = 0; FreeUPNPUrls(&urls); @@ -919,16 +1092,16 @@ void ThreadMapPort2(void* parg) #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port, port, lanaddr, strDesc.c_str(), "TCP", 0); + port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0); #else /* miniupnpc 1.6 */ r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port, port, lanaddr, strDesc.c_str(), "TCP", 0, "0"); + port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); #endif if(r!=UPNPCOMMAND_SUCCESS) printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", - port, port, lanaddr, r, strupnperror(r)); + port.c_str(), port.c_str(), lanaddr, r, strupnperror(r)); else printf("UPnP Port Mapping successful.\n");; } @@ -948,20 +1121,16 @@ void ThreadMapPort2(void* parg) } } -void MapPort(bool fMapPort) +void MapPort() { - if (fUseUPnP != fMapPort) - { - fUseUPnP = fMapPort; - } if (fUseUPnP && vnThreadsRunning[THREAD_UPNP] < 1) { - if (!CreateThread(ThreadMapPort, NULL)) + if (!NewThread(ThreadMapPort, NULL)) printf("Error: ThreadMapPort(ThreadMapPort) failed\n"); } } #else -void MapPort(bool /* unused fMapPort */) +void MapPort() { // Intentionally left blank. } @@ -979,15 +1148,15 @@ void MapPort(bool /* unused fMapPort */) // Each pair gives a source name and a seed name. // The first name is used as information source for addrman. // The second name should resolve to a list of seed addresses. -// testnet dns seed begins with 't', all else are ppcoin dns seeds. static const char *strDNSSeed[][2] = { - {"seed", "seed.ppcoin.net"}, - {"tnseed", "tnseed.ppcoin.net"}, + {"novacoin.su", "seed.novacoin.su"}, }; void ThreadDNSAddressSeed(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadDNSAddressSeed(parg)); + // Make this thread recognisable as the DNS seeding thread + RenameThread("bitcoin-dnsseed"); + try { vnThreadsRunning[THREAD_DNSSEED]++; @@ -1001,7 +1170,7 @@ void ThreadDNSAddressSeed(void* parg) vnThreadsRunning[THREAD_DNSSEED]--; throw; // support pthread_cancel() } - printf("ThreadDNSAddressSeed exiting\n"); + printf("ThreadDNSAddressSeed exited\n"); } void ThreadDNSAddressSeed2(void* parg) @@ -1009,28 +1178,29 @@ void ThreadDNSAddressSeed2(void* parg) printf("ThreadDNSAddressSeed started\n"); int found = 0; - if (true /*!fTestNet*/) // ppcoin enables dns seeding with testnet too + if (!fTestNet) { printf("Loading addresses from DNS seeds (could take a while)\n"); for (unsigned int seed_idx = 0; seed_idx < ARRAYLEN(strDNSSeed); seed_idx++) { - if (fTestNet && strDNSSeed[seed_idx][1][0] != 't') continue; - if ((!fTestNet) && strDNSSeed[seed_idx][1][0] == 't') continue; - - vector vaddr; - vector vAdd; - if (LookupHost(strDNSSeed[seed_idx][1], vaddr)) - { - BOOST_FOREACH(CNetAddr& ip, vaddr) + if (HaveNameProxy()) { + AddOneShot(strDNSSeed[seed_idx][1]); + } else { + vector vaddr; + vector vAdd; + if (LookupHost(strDNSSeed[seed_idx][1], vaddr)) { - int nOneDay = 24*3600; - CAddress addr = CAddress(CService(ip, GetDefaultPort())); - addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay); // use a random age between 3 and 7 days old - vAdd.push_back(addr); - found++; + BOOST_FOREACH(CNetAddr& ip, vaddr) + { + int nOneDay = 24*3600; + CAddress addr = CAddress(CService(ip, GetDefaultPort())); + addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay); // use a random age between 3 and 7 days old + vAdd.push_back(addr); + found++; + } } + addrman.Add(vAdd, CNetAddr(strDNSSeed[seed_idx][0], true)); } - addrman.Add(vAdd, CNetAddr(strDNSSeed[seed_idx][0], true)); } } @@ -1055,8 +1225,13 @@ unsigned int pnSeed[] = void DumpAddresses() { + int64 nStart = GetTimeMillis(); + CAddrDB adb; - adb.WriteAddrman(addrman); + adb.Write(addrman); + + printf("Flushed %d addresses to peers.dat %"PRI64d"ms\n", + addrman.size(), GetTimeMillis() - nStart); } void ThreadDumpAddress2(void* parg) @@ -1074,7 +1249,9 @@ void ThreadDumpAddress2(void* parg) void ThreadDumpAddress(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadDumpAddress(parg)); + // Make this thread recognisable as the address dumping thread + RenameThread("bitcoin-adrdump"); + try { ThreadDumpAddress2(parg); @@ -1082,12 +1259,14 @@ void ThreadDumpAddress(void* parg) catch (std::exception& e) { PrintException(&e, "ThreadDumpAddress()"); } - printf("ThreadDumpAddress exiting\n"); + printf("ThreadDumpAddress exited\n"); } void ThreadOpenConnections(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadOpenConnections(parg)); + // Make this thread recognisable as the connection opening thread + RenameThread("bitcoin-opencon"); + try { vnThreadsRunning[THREAD_OPENCONNECTIONS]++; @@ -1101,7 +1280,46 @@ void ThreadOpenConnections(void* parg) vnThreadsRunning[THREAD_OPENCONNECTIONS]--; PrintException(NULL, "ThreadOpenConnections()"); } - printf("ThreadOpenConnections exiting\n"); + printf("ThreadOpenConnections exited\n"); +} + +void static ProcessOneShot() +{ + string strDest; + { + LOCK(cs_vOneShots); + if (vOneShots.empty()) + return; + strDest = vOneShots.front(); + vOneShots.pop_front(); + } + CAddress addr; + CSemaphoreGrant grant(*semOutbound, true); + if (grant) { + if (!OpenNetworkConnection(addr, &grant, strDest.c_str(), true)) + AddOneShot(strDest); + } +} + +// ppcoin: stake minter thread +void static ThreadStakeMinter(void* parg) +{ + printf("ThreadStakeMinter started\n"); + CWallet* pwallet = (CWallet*)parg; + try + { + vnThreadsRunning[THREAD_MINTER]++; + BitcoinMiner(pwallet, true); + vnThreadsRunning[THREAD_MINTER]--; + } + catch (std::exception& e) { + vnThreadsRunning[THREAD_MINTER]--; + PrintException(&e, "ThreadStakeMinter()"); + } catch (...) { + vnThreadsRunning[THREAD_MINTER]--; + PrintException(NULL, "ThreadStakeMinter()"); + } + printf("ThreadStakeMinter exiting, %d threads remaining\n", vnThreadsRunning[THREAD_MINTER]); } void ThreadOpenConnections2(void* parg) @@ -1109,15 +1327,15 @@ void ThreadOpenConnections2(void* parg) printf("ThreadOpenConnections started\n"); // Connect to specific addresses - if (mapArgs.count("-connect")) + if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0) { for (int64 nLoop = 0;; nLoop++) { + ProcessOneShot(); BOOST_FOREACH(string strAddr, mapMultiArgs["-connect"]) { - CAddress addr(CService(strAddr, GetDefaultPort(), fAllowDNS)); - if (addr.IsValid()) - OpenNetworkConnection(addr, false); + CAddress addr; + OpenNetworkConnection(addr, NULL, strAddr.c_str()); for (int i = 0; i < 10 && i < nLoop; i++) { Sleep(500); @@ -1125,6 +1343,7 @@ void ThreadOpenConnections2(void* parg) return; } } + Sleep(500); } } @@ -1132,6 +1351,8 @@ void ThreadOpenConnections2(void* parg) int64 nStart = GetTime(); loop { + ProcessOneShot(); + vnThreadsRunning[THREAD_OPENCONNECTIONS]--; Sleep(500); vnThreadsRunning[THREAD_OPENCONNECTIONS]++; @@ -1140,14 +1361,13 @@ void ThreadOpenConnections2(void* parg) vnThreadsRunning[THREAD_OPENCONNECTIONS]--; - semOutbound->wait(); + CSemaphoreGrant grant(*semOutbound); vnThreadsRunning[THREAD_OPENCONNECTIONS]++; if (fShutdown) return; // Add seed nodes if IRC isn't working - bool fTOR = (fUseProxy && addrProxy.GetPort() == 9050); - if (addrman.size()==0 && (GetTime() - nStart > 60 || fTOR) && !fTestNet) + if (addrman.size()==0 && (GetTime() - nStart > 60) && !fTestNet) { std::vector vAdd; for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++) @@ -1171,16 +1391,17 @@ void ThreadOpenConnections2(void* parg) // CAddress addrConnect; - // Only connect to one address per a.b.?.? range. + // Only connect out to one peer per network group (/16 for IPv4). // Do this here so we don't have to critsect vNodes inside mapAddresses critsect. int nOutbound = 0; set > setConnected; { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) { - setConnected.insert(pnode->addr.GetGroup()); - if (!pnode->fInbound) + if (!pnode->fInbound) { + setConnected.insert(pnode->addr.GetGroup()); nOutbound++; + } } } @@ -1193,10 +1414,18 @@ void ThreadOpenConnections2(void* parg) CAddress addr = addrman.Select(10 + min(nOutbound,8)*10); // if we selected an invalid address, restart - if (!addr.IsIPv4() || !addr.IsValid() || setConnected.count(addr.GetGroup()) || addr == addrLocalHost) + if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr)) break; + // If we didn't find an appropriate destination after trying 100 addresses fetched from addrman, + // stop this loop, and let the outer loop run again (which sleeps, adds seed nodes, recalculates + // already-connected network ranges, ...) before trying new addrman addresses. nTries++; + if (nTries > 100) + break; + + if (IsLimited(addr)) + continue; // only consider very recently tried nodes after 30 failed attempts if (nANow - addr.nLastTry < 600 && nTries < 30) @@ -1211,15 +1440,15 @@ void ThreadOpenConnections2(void* parg) } if (addrConnect.IsValid()) - OpenNetworkConnection(addrConnect); - else - semOutbound->post(); + OpenNetworkConnection(addrConnect, &grant); } } void ThreadOpenAddedConnections(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadOpenAddedConnections(parg)); + // Make this thread recognisable as the connection opening thread + RenameThread("bitcoin-opencon"); + try { vnThreadsRunning[THREAD_ADDEDCONNECTIONS]++; @@ -1233,7 +1462,7 @@ void ThreadOpenAddedConnections(void* parg) vnThreadsRunning[THREAD_ADDEDCONNECTIONS]--; PrintException(NULL, "ThreadOpenAddedConnections()"); } - printf("ThreadOpenAddedConnections exiting\n"); + printf("ThreadOpenAddedConnections exited\n"); } void ThreadOpenAddedConnections2(void* parg) @@ -1243,11 +1472,26 @@ void ThreadOpenAddedConnections2(void* parg) if (mapArgs.count("-addnode") == 0) return; + if (HaveNameProxy()) { + while(!fShutdown) { + BOOST_FOREACH(string& strAddNode, mapMultiArgs["-addnode"]) { + CAddress addr; + CSemaphoreGrant grant(*semOutbound); + OpenNetworkConnection(addr, &grant, strAddNode.c_str()); + Sleep(500); + } + vnThreadsRunning[THREAD_ADDEDCONNECTIONS]--; + Sleep(120000); // Retry every 2 minutes + vnThreadsRunning[THREAD_ADDEDCONNECTIONS]++; + } + return; + } + vector > vservAddressesToAdd(0); BOOST_FOREACH(string& strAddNode, mapMultiArgs["-addnode"]) { vector vservNode(0); - if(Lookup(strAddNode.c_str(), vservNode, GetDefaultPort(), fAllowDNS, 0)) + if(Lookup(strAddNode.c_str(), vservNode, GetDefaultPort(), fNameLookup, 0)) { vservAddressesToAdd.push_back(vservNode); { @@ -1261,7 +1505,7 @@ void ThreadOpenAddedConnections2(void* parg) { vector > vservConnectAddresses = vservAddressesToAdd; // Attempt to connect to each IP for each addnode entry until at least one is successful per addnode entry - // (keeping in mind that addnode entries can have many IPs if fAllowDNS) + // (keeping in mind that addnode entries can have many IPs if fNameLookup) { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) @@ -1276,8 +1520,8 @@ void ThreadOpenAddedConnections2(void* parg) } BOOST_FOREACH(vector& vserv, vservConnectAddresses) { - semOutbound->wait(); - OpenNetworkConnection(CAddress(*(vserv.begin()))); + CSemaphoreGrant grant(*semOutbound); + OpenNetworkConnection(CAddress(*(vserv.begin())), &grant); Sleep(500); if (fShutdown) return; @@ -1292,38 +1536,34 @@ void ThreadOpenAddedConnections2(void* parg) } } -bool static ReleaseGrant(bool fUseGrant) { - if (fUseGrant) - semOutbound->post(); - return false; -} - -// only call this function when semOutbound has been waited for -bool OpenNetworkConnection(const CAddress& addrConnect, bool fUseGrant) +// if successful, this moves the passed grant to the constructed node +bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound, const char *strDest, bool fOneShot) { // // Initiate outbound network connection // if (fShutdown) return false; - if ((CNetAddr)addrConnect == (CNetAddr)addrLocalHost || !addrConnect.IsIPv4() || - FindNode((CNetAddr)addrConnect) || CNode::IsBanned(addrConnect)) - return ReleaseGrant(fUseGrant); + if (!strDest) + if (IsLocal(addrConnect) || + FindNode((CNetAddr)addrConnect) || CNode::IsBanned(addrConnect) || + FindNode(addrConnect.ToStringIPPort().c_str())) + return false; + if (strDest && FindNode(strDest)) + return false; vnThreadsRunning[THREAD_OPENCONNECTIONS]--; - CNode* pnode = ConnectNode(addrConnect); + CNode* pnode = ConnectNode(addrConnect, strDest); vnThreadsRunning[THREAD_OPENCONNECTIONS]++; if (fShutdown) return false; if (!pnode) - return ReleaseGrant(fUseGrant); - if (pnode->fHasGrant) { - // node already has connection grant, release the one that was passed to us - ReleaseGrant(fUseGrant); - } else { - pnode->fHasGrant = fUseGrant; - } + return false; + if (grantOutbound) + grantOutbound->MoveTo(pnode->grantOutbound); pnode->fNetworkNode = true; + if (fOneShot) + pnode->fOneShot = true; return true; } @@ -1337,7 +1577,9 @@ bool OpenNetworkConnection(const CAddress& addrConnect, bool fUseGrant) void ThreadMessageHandler(void* parg) { - IMPLEMENT_RANDOMIZE_STACK(ThreadMessageHandler(parg)); + // Make this thread recognisable as the message handling thread + RenameThread("bitcoin-msghand"); + try { vnThreadsRunning[THREAD_MESSAGEHANDLER]++; @@ -1351,7 +1593,7 @@ void ThreadMessageHandler(void* parg) vnThreadsRunning[THREAD_MESSAGEHANDLER]--; PrintException(NULL, "ThreadMessageHandler()"); } - printf("ThreadMessageHandler exiting\n"); + printf("ThreadMessageHandler exited\n"); } void ThreadMessageHandler2(void* parg) @@ -1412,37 +1654,15 @@ void ThreadMessageHandler2(void* parg) } } -// ppcoin: stake minter thread -void static ThreadStakeMinter(void* parg) -{ - printf("ThreadStakeMinter started\n"); - CWallet* pwallet = (CWallet*)parg; - try - { - vnThreadsRunning[THREAD_MINTER]++; - BitcoinMiner(pwallet, true); - vnThreadsRunning[THREAD_MINTER]--; - } - catch (std::exception& e) { - vnThreadsRunning[THREAD_MINTER]--; - PrintException(&e, "ThreadStakeMinter()"); - } catch (...) { - vnThreadsRunning[THREAD_MINTER]--; - PrintException(NULL, "ThreadStakeMinter()"); - } - printf("ThreadStakeMinter exiting, %d threads remaining\n", vnThreadsRunning[THREAD_MINTER]); -} - -bool BindListenPort(string& strError) +bool BindListenPort(const CService &addrBind, string& strError) { strError = ""; int nOne = 1; - addrLocalHost.SetPort(GetListenPort()); #ifdef WIN32 // Initialize Windows Sockets @@ -1457,7 +1677,20 @@ bool BindListenPort(string& strError) #endif // Create socket for listening for incoming connections - hListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#ifdef USE_IPV6 + struct sockaddr_storage sockaddr; +#else + struct sockaddr sockaddr; +#endif + socklen_t len = sizeof(sockaddr); + if (!addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)) + { + strError = strprintf("Error: bind address family for %s not supported", addrBind.ToString().c_str()); + printf("%s\n", strError.c_str()); + return false; + } + + SOCKET hListenSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (hListenSocket == INVALID_SOCKET) { strError = strprintf("Error: Couldn't open socket for incoming connections (socket returned error %d)", WSAGetLastError()); @@ -1476,8 +1709,9 @@ bool BindListenPort(string& strError) setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (void*)&nOne, sizeof(int)); #endif + #ifdef WIN32 - // Set to nonblocking, incoming connections will also inherit this + // Set to non-blocking, incoming connections will also inherit this if (ioctlsocket(hListenSocket, FIONBIO, (u_long*)&nOne) == SOCKET_ERROR) #else if (fcntl(hListenSocket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR) @@ -1488,24 +1722,33 @@ bool BindListenPort(string& strError) return false; } - // The sockaddr_in structure specifies the address family, - // IP address, and port for the socket that is being bound - struct sockaddr_in sockaddr; - memset(&sockaddr, 0, sizeof(sockaddr)); - sockaddr.sin_family = AF_INET; - sockaddr.sin_addr.s_addr = INADDR_ANY; // bind to all IPs on this computer - sockaddr.sin_port = htons(GetListenPort()); - if (::bind(hListenSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR) +#ifdef USE_IPV6 + // some systems don't have IPV6_V6ONLY but are always v6only; others do have the option + // and enable it by default or not. Try to enable it, if possible. + if (addrBind.IsIPv6()) { +#ifdef IPV6_V6ONLY + setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&nOne, sizeof(int)); +#endif +#ifdef WIN32 + int nProtLevel = 10 /* PROTECTION_LEVEL_UNRESTRICTED */; + int nParameterId = 23 /* IPV6_PROTECTION_LEVEl */; + // this call is allowed to fail + setsockopt(hListenSocket, IPPROTO_IPV6, nParameterId, (const char*)&nProtLevel, sizeof(int)); +#endif + } +#endif + + if (::bind(hListenSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) - strError = strprintf(_("Unable to bind to port %d on this computer. NovaCoin is probably already running."), ntohs(sockaddr.sin_port)); + strError = strprintf(_("Unable to bind to %s on this computer. NovaCoin is probably already running."), addrBind.ToString().c_str()); else - strError = strprintf("Error: Unable to bind to port %d on this computer (bind returned error %d)", ntohs(sockaddr.sin_port), nErr); + strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %d, %s)"), addrBind.ToString().c_str(), nErr, strerror(nErr)); printf("%s\n", strError.c_str()); return false; } - printf("Bound to port %d\n", ntohs(sockaddr.sin_port)); + printf("Bound to %s\n", addrBind.ToString().c_str()); // Listen for incoming connections if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) @@ -1515,30 +1758,21 @@ bool BindListenPort(string& strError) return false; } + vhListenSocket.push_back(hListenSocket); + + if (addrBind.IsRoutable() && fDiscover) + AddLocal(addrBind, LOCAL_BIND); + return true; } -void StartNode(void* parg) +void static Discover() { - if (semOutbound == NULL) { - // initialize semaphore - int nMaxOutbound = min(MAX_OUTBOUND_CONNECTIONS, (int)GetArg("-maxconnections", 125)); - semOutbound = new CSemaphore(nMaxOutbound); - } - -#ifdef USE_UPNP -#if USE_UPNP - fUseUPnP = GetBoolArg("-upnp", true); -#else - fUseUPnP = GetBoolArg("-upnp", false); -#endif -#endif - - if (pnodeLocalHost == NULL) - pnodeLocalHost = new CNode(INVALID_SOCKET, CAddress(CService("127.0.0.1", 0), nLocalServices)); + if (!fDiscover) + return; #ifdef WIN32 - // Get local host ip + // Get local host IP char pszHostName[1000] = ""; if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR) { @@ -1547,11 +1781,7 @@ void StartNode(void* parg) { BOOST_FOREACH (const CNetAddr &addr, vaddr) { - if (!addr.IsLocal()) - { - addrLocalHost.SetIP(addr); - break; - } + AddLocal(addr, LOCAL_IF); } } } @@ -1566,44 +1796,48 @@ void StartNode(void* parg) if ((ifa->ifa_flags & IFF_UP) == 0) continue; if (strcmp(ifa->ifa_name, "lo") == 0) continue; if (strcmp(ifa->ifa_name, "lo0") == 0) continue; - char pszIP[100]; if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr); - if (inet_ntop(ifa->ifa_addr->sa_family, (void*)&(s4->sin_addr), pszIP, sizeof(pszIP)) != NULL) - printf("ipv4 %s: %s\n", ifa->ifa_name, pszIP); - - // Take the first IP that isn't loopback 127.x.x.x - CAddress addr(CService(s4->sin_addr, GetListenPort()), nLocalServices); - if (addr.IsValid() && !addr.IsLocal()) - { - addrLocalHost = addr; - break; - } + CNetAddr addr(s4->sin_addr); + if (AddLocal(addr, LOCAL_IF)) + printf("IPv4 %s: %s\n", ifa->ifa_name, addr.ToString().c_str()); } +#ifdef USE_IPV6 else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr); - if (inet_ntop(ifa->ifa_addr->sa_family, (void*)&(s6->sin6_addr), pszIP, sizeof(pszIP)) != NULL) - printf("ipv6 %s: %s\n", ifa->ifa_name, pszIP); + CNetAddr addr(s6->sin6_addr); + if (AddLocal(addr, LOCAL_IF)) + printf("IPv6 %s: %s\n", ifa->ifa_name, addr.ToString().c_str()); } +#endif } freeifaddrs(myaddrs); } #endif - printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str()); - if (fUseProxy || mapArgs.count("-connect") || fNoListen) - { - // Proxies can't take incoming connections - addrLocalHost.SetIP(CNetAddr("0.0.0.0")); - printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str()); - } - else - { - CreateThread(ThreadGetMyExternalIP, NULL); + // Don't use external IPv4 discovery, when -onlynet="IPv6" + if (!IsLimited(NET_IPV4)) + NewThread(ThreadGetMyExternalIP, NULL); +} + +void StartNode(void* parg) +{ + // Make this thread recognisable as the startup thread + RenameThread("bitcoin-start"); + + if (semOutbound == NULL) { + // initialize semaphore + int nMaxOutbound = min(MAX_OUTBOUND_CONNECTIONS, (int)GetArg("-maxconnections", 125)); + semOutbound = new CSemaphore(nMaxOutbound); } + if (pnodeLocalHost == NULL) + pnodeLocalHost = new CNode(INVALID_SOCKET, CAddress(CService("127.0.0.1", 0), nLocalServices)); + + Discover(); + // // Start threads // @@ -1612,8 +1846,8 @@ void StartNode(void* parg) if (!GetBoolArg("-dnsseed", true)) printf("DNS seeding disabled\n"); else - if (!CreateThread(ThreadDNSAddressSeed, NULL)) - printf("Error: CreateThread(ThreadDNSAddressSeed) failed\n"); + if (!NewThread(ThreadDNSAddressSeed, NULL)) + printf("Error: NewThread(ThreadDNSAddressSeed) failed\n"); */ if (!GetBoolArg("-dnsseed", false)) @@ -1622,39 +1856,39 @@ void StartNode(void* parg) printf("DNS seeding NYI\n"); // Map ports with UPnP - if (fHaveUPnP) - MapPort(fUseUPnP); + if (fUseUPnP) + MapPort(); // Get addresses from IRC and advertise ours - if (!CreateThread(ThreadIRCSeed, NULL)) - printf("Error: CreateThread(ThreadIRCSeed) failed\n"); + if (!NewThread(ThreadIRCSeed, NULL)) + printf("Error: NewThread(ThreadIRCSeed) failed\n"); // Send and receive from sockets, accept connections - if (!CreateThread(ThreadSocketHandler, NULL)) - printf("Error: CreateThread(ThreadSocketHandler) failed\n"); + if (!NewThread(ThreadSocketHandler, NULL)) + printf("Error: NewThread(ThreadSocketHandler) failed\n"); // Initiate outbound connections from -addnode - if (!CreateThread(ThreadOpenAddedConnections, NULL)) - printf("Error: CreateThread(ThreadOpenAddedConnections) failed\n"); + if (!NewThread(ThreadOpenAddedConnections, NULL)) + printf("Error: NewThread(ThreadOpenAddedConnections) failed\n"); // Initiate outbound connections - if (!CreateThread(ThreadOpenConnections, NULL)) - printf("Error: CreateThread(ThreadOpenConnections) failed\n"); + if (!NewThread(ThreadOpenConnections, NULL)) + printf("Error: NewThread(ThreadOpenConnections) failed\n"); // Process messages - if (!CreateThread(ThreadMessageHandler, NULL)) - printf("Error: CreateThread(ThreadMessageHandler) failed\n"); + if (!NewThread(ThreadMessageHandler, NULL)) + printf("Error: NewThread(ThreadMessageHandler) failed\n"); // Dump network addresses - if (!CreateThread(ThreadDumpAddress, NULL)) - printf("Error; CreateThread(ThreadDumpAddress) failed\n"); + if (!NewThread(ThreadDumpAddress, NULL)) + printf("Error; NewThread(ThreadDumpAddress) failed\n"); + + // ppcoin: mint proof-of-stake blocks in the background + if (!NewThread(ThreadStakeMinter, pwalletMain)) + printf("Error: NewThread(ThreadStakeMinter) failed\n"); // Generate coins in the background GenerateBitcoins(GetBoolArg("-gen", false), pwalletMain); - - // ppcoin: mint proof-of-stake blocks in the background - if (!CreateThread(ThreadStakeMinter, pwalletMain)) - printf("Error: CreateThread(ThreadStakeMinter) failed\n"); } bool StopNode() @@ -1681,13 +1915,16 @@ bool StopNode() if (vnThreadsRunning[THREAD_OPENCONNECTIONS] > 0) printf("ThreadOpenConnections still running\n"); if (vnThreadsRunning[THREAD_MESSAGEHANDLER] > 0) printf("ThreadMessageHandler still running\n"); if (vnThreadsRunning[THREAD_MINER] > 0) printf("ThreadBitcoinMiner still running\n"); - if (vnThreadsRunning[THREAD_RPCSERVER] > 0) printf("ThreadRPCServer still running\n"); - if (fHaveUPnP && vnThreadsRunning[THREAD_UPNP] > 0) printf("ThreadMapPort still running\n"); + if (vnThreadsRunning[THREAD_RPCLISTENER] > 0) printf("ThreadRPCListener still running\n"); + if (vnThreadsRunning[THREAD_RPCHANDLER] > 0) printf("ThreadsRPCServer still running\n"); +#ifdef USE_UPNP + if (vnThreadsRunning[THREAD_UPNP] > 0) printf("ThreadMapPort still running\n"); +#endif if (vnThreadsRunning[THREAD_DNSSEED] > 0) printf("ThreadDNSAddressSeed still running\n"); if (vnThreadsRunning[THREAD_ADDEDCONNECTIONS] > 0) printf("ThreadOpenAddedConnections still running\n"); if (vnThreadsRunning[THREAD_DUMPADDRESS] > 0) printf("ThreadDumpAddresses still running\n"); if (vnThreadsRunning[THREAD_MINTER] > 0) printf("ThreadStakeMinter still running\n"); - while (vnThreadsRunning[THREAD_MESSAGEHANDLER] > 0 || vnThreadsRunning[THREAD_RPCSERVER] > 0) + while (vnThreadsRunning[THREAD_MESSAGEHANDLER] > 0 || vnThreadsRunning[THREAD_RPCHANDLER] > 0) Sleep(20); Sleep(50); DumpAddresses(); @@ -1706,9 +1943,10 @@ public: BOOST_FOREACH(CNode* pnode, vNodes) if (pnode->hSocket != INVALID_SOCKET) closesocket(pnode->hSocket); - if (hListenSocket != INVALID_SOCKET) - if (closesocket(hListenSocket) == SOCKET_ERROR) - printf("closesocket(hListenSocket) failed with error %d\n", WSAGetLastError()); + BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) + if (hListenSocket != INVALID_SOCKET) + if (closesocket(hListenSocket) == SOCKET_ERROR) + printf("closesocket(hListenSocket) failed with error %d\n", WSAGetLastError()); #ifdef WIN32 // Shutdown Windows Sockets diff --git a/src/net.h b/src/net.h index 661cc33..16cd95f 100644 --- a/src/net.h +++ b/src/net.h @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_NET_H @@ -20,7 +19,6 @@ #include "protocol.h" #include "addrman.h" -class CAddrDB; class CRequestTracker; class CNode; class CBlockIndex; @@ -28,22 +26,50 @@ extern int nBestHeight; -inline unsigned int ReceiveBufferSize() { return 1000*GetArg("-maxreceivebuffer", 10*1000); } -inline unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 10*1000); } +inline unsigned int ReceiveBufferSize() { return 1000*GetArg("-maxreceivebuffer", 5*1000); } +inline unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 1*1000); } +void AddOneShot(std::string strDest); bool RecvLine(SOCKET hSocket, std::string& strLine); bool GetMyExternalIP(CNetAddr& ipRet); void AddressCurrentlyConnected(const CService& addr); CNode* FindNode(const CNetAddr& ip); CNode* FindNode(const CService& ip); -CNode* ConnectNode(CAddress addrConnect, int64 nTimeout=0); -void MapPort(bool fMapPort); -bool BindListenPort(std::string& strError=REF(std::string())); +CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL, int64 nTimeout=0); +void MapPort(); +unsigned short GetListenPort(); +bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string())); void StartNode(void* parg); bool StopNode(); enum { + LOCAL_NONE, // unknown + LOCAL_IF, // address a local interface listens on + LOCAL_BIND, // address explicit bound to + LOCAL_UPNP, // address reported by UPnP + LOCAL_IRC, // address reported by IRC (deprecated) + LOCAL_HTTP, // address reported by whatismyip.com and similar + LOCAL_MANUAL, // address explicitly specified (-externalip=) + + LOCAL_MAX +}; + +void SetLimited(enum Network net, bool fLimited = true); +bool IsLimited(enum Network net); +bool IsLimited(const CNetAddr& addr); +bool AddLocal(const CService& addr, int nScore = LOCAL_NONE); +bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE); +bool SeenLocal(const CService& addr); +bool IsLocal(const CService& addr); +bool GetLocal(CService &addr, const CNetAddr *paddrPeer = NULL); +bool IsReachable(const CNetAddr &addr); +void SetReachable(enum Network net, bool fFlag = true); +CAddress GetLocalAddress(const CNetAddr *paddrPeer = NULL); + + +enum +{ MSG_TX = 1, MSG_BLOCK, }; @@ -74,22 +100,23 @@ enum threadId THREAD_OPENCONNECTIONS, THREAD_MESSAGEHANDLER, THREAD_MINER, - THREAD_RPCSERVER, + THREAD_RPCLISTENER, THREAD_UPNP, THREAD_DNSSEED, THREAD_ADDEDCONNECTIONS, THREAD_DUMPADDRESS, + THREAD_RPCHANDLER, THREAD_MINTER, THREAD_MAX }; extern bool fClient; -extern bool fAllowDNS; +extern bool fDiscover; +extern bool fUseUPnP; extern uint64 nLocalServices; -extern CAddress addrLocalHost; -extern CAddress addrSeenByPeer; extern uint64 nLocalHostNonce; +extern CAddress addrSeenByPeer; extern boost::array vnThreadsRunning; extern CAddrMan addrman; @@ -103,6 +130,24 @@ extern std::map mapAlreadyAskedFor; +class CNodeStats +{ +public: + uint64 nServices; + int64 nLastSend; + int64 nLastRecv; + int64 nTimeConnected; + std::string addrName; + int nVersion; + std::string strSubVer; + bool fInbound; + int64 nReleaseTime; + int nStartingHeight; + int nMisbehavior; +}; + + + /** Information about a peer */ @@ -123,19 +168,22 @@ public: int nHeaderStart; unsigned int nMessageStart; CAddress addr; + std::string addrName; + CService addrLocal; int nVersion; std::string strSubVer; + bool fOneShot; bool fClient; bool fInbound; bool fNetworkNode; bool fSuccessfullyConnected; bool fDisconnect; - bool fHasGrant; // whether to call semOutbound.post() at disconnect + CSemaphoreGrant grantOutbound; protected: int nRefCount; // Denial-of-service detection/prevention - // Key is ip address, value is banned-until-time + // Key is IP address, value is banned-until-time static std::map setBanned; static CCriticalSection cs_setBanned; int nMisbehavior; @@ -162,7 +210,7 @@ public: CCriticalSection cs_inventory; std::multimap mapAskFor; - CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn=false) : vSend(SER_NETWORK, MIN_PROTO_VERSION), vRecv(SER_NETWORK, MIN_PROTO_VERSION) + CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : vSend(SER_NETWORK, MIN_PROTO_VERSION), vRecv(SER_NETWORK, MIN_PROTO_VERSION) { nServices = 0; hSocket = hSocketIn; @@ -173,10 +221,11 @@ public: nHeaderStart = -1; nMessageStart = -1; addr = addrIn; + addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; nVersion = 0; strSubVer = ""; + fOneShot = false; fClient = false; // set by version message - fHasGrant = false; fInbound = fInboundIn; fNetworkNode = false; fSuccessfullyConnected = false; @@ -270,7 +319,8 @@ public: // We're using mapAskFor as a priority queue, // the key is the earliest time the request can be sent int64& nRequestTime = mapAlreadyAskedFor[inv]; - printf("askfor %s %"PRI64d"\n", inv.ToString().c_str(), nRequestTime); + if (fDebugNet) + printf("askfor %s %"PRI64d" (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str()); // Make sure not to reuse time indexes to keep things in the same order int64 nNow = (GetTime() - 1) * 1000000; @@ -294,10 +344,8 @@ public: nHeaderStart = vSend.size(); vSend << CMessageHeader(pszCommand, 0); nMessageStart = vSend.size(); - if (fDebug) { - printf("%s ", DateTimeStrFormat(GetTime()).c_str()); + if (fDebug) printf("sending: %s ", pszCommand); - } } void AbortMessage() @@ -327,14 +375,14 @@ public: // Set the size unsigned int nSize = vSend.size() - nMessageStart; - memcpy((char*)&vSend[nHeaderStart] + offsetof(CMessageHeader, nMessageSize), &nSize, sizeof(nSize)); + memcpy((char*)&vSend[nHeaderStart] + CMessageHeader::MESSAGE_SIZE_OFFSET, &nSize, sizeof(nSize)); // Set the checksum uint256 hash = Hash(vSend.begin() + nMessageStart, vSend.end()); unsigned int nChecksum = 0; memcpy(&nChecksum, &hash, sizeof(nChecksum)); - assert(nMessageStart - nHeaderStart >= offsetof(CMessageHeader, nChecksum) + sizeof(nChecksum)); - memcpy((char*)&vSend[nHeaderStart] + offsetof(CMessageHeader, nChecksum), &nChecksum, sizeof(nChecksum)); + assert(nMessageStart - nHeaderStart >= CMessageHeader::CHECKSUM_OFFSET + sizeof(nChecksum)); + memcpy((char*)&vSend[nHeaderStart] + CMessageHeader::CHECKSUM_OFFSET, &nChecksum, sizeof(nChecksum)); if (fDebug) { printf("(%d bytes)\n", nSize); @@ -591,6 +639,7 @@ public: static void ClearBanned(); // needed for unit testing static bool IsBanned(CNetAddr ip); bool Misbehaving(int howmuch); // 1 == a little, 100 == a lot + void copyStats(CNodeStats &stats); }; diff --git a/src/netbase.cpp b/src/netbase.cpp index 6cb8aa8..95d6493 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -5,26 +5,68 @@ #include "netbase.h" #include "util.h" +#include "sync.h" #ifndef WIN32 #include #endif #include "strlcpy.h" +#include // for to_lower() using namespace std; // Settings -int fUseProxy = false; -CService addrProxy("127.0.0.1",9050); +static proxyType proxyInfo[NET_MAX]; +static proxyType nameproxyInfo; +static CCriticalSection cs_proxyInfos; int nConnectTimeout = 5000; - +bool fNameLookup = false; static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; +enum Network ParseNetwork(std::string net) { + boost::to_lower(net); + if (net == "ipv4") return NET_IPV4; + if (net == "ipv6") return NET_IPV6; + if (net == "tor") return NET_TOR; + if (net == "i2p") return NET_I2P; + return NET_UNROUTABLE; +} + +void SplitHostPort(std::string in, int &portOut, std::string &hostOut) { + size_t colon = in.find_last_of(':'); + // if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator + bool fHaveColon = colon != in.npos; + bool fBracketed = fHaveColon && (in[0]=='[' && in[colon-1]==']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe + bool fMultiColon = fHaveColon && (in.find_last_of(':',colon-1) != in.npos); + if (fHaveColon && (colon==0 || fBracketed || !fMultiColon)) { + char *endp = NULL; + int n = strtol(in.c_str() + colon + 1, &endp, 10); + if (endp && *endp == 0 && n >= 0) { + in = in.substr(0, colon); + if (n > 0 && n < 0x10000) + portOut = n; + } + } + if (in.size()>0 && in[0] == '[' && in[in.size()-1] == ']') + hostOut = in.substr(1, in.size()-2); + else + hostOut = in; +} + bool static LookupIntern(const char *pszName, std::vector& vIP, unsigned int nMaxSolutions, bool fAllowLookup) { vIP.clear(); + + { + CNetAddr addr; + if (addr.SetSpecial(std::string(pszName))) { + vIP.push_back(addr); + return true; + } + } + struct addrinfo aiHint; memset(&aiHint, 0, sizeof(struct addrinfo)); @@ -33,19 +75,17 @@ bool static LookupIntern(const char *pszName, std::vector& vIP, unsign #ifdef WIN32 # ifdef USE_IPV6 aiHint.ai_family = AF_UNSPEC; - aiHint.ai_flags = fAllowLookup ? 0 : AI_NUMERICHOST; # else aiHint.ai_family = AF_INET; - aiHint.ai_flags = fAllowLookup ? 0 : AI_NUMERICHOST; # endif + aiHint.ai_flags = fAllowLookup ? 0 : AI_NUMERICHOST; #else # ifdef USE_IPV6 aiHint.ai_family = AF_UNSPEC; - aiHint.ai_flags = AI_ADDRCONFIG | (fAllowLookup ? 0 : AI_NUMERICHOST); # else aiHint.ai_family = AF_INET; - aiHint.ai_flags = AI_ADDRCONFIG | (fAllowLookup ? 0 : AI_NUMERICHOST); # endif + aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST; #endif struct addrinfo *aiRes = NULL; int nErr = getaddrinfo(pszName, NULL, &aiHint, &aiRes); @@ -103,36 +143,11 @@ bool Lookup(const char *pszName, std::vector& vAddr, int portDefault, if (pszName[0] == 0) return false; int port = portDefault; - char psz[256]; - char *pszHost = psz; - strlcpy(psz, pszName, sizeof(psz)); - char* pszColon = strrchr(psz+1,':'); - char *pszPortEnd = NULL; - int portParsed = pszColon ? strtoul(pszColon+1, &pszPortEnd, 10) : 0; - if (pszColon && pszPortEnd && pszPortEnd[0] == 0) - { - if (psz[0] == '[' && pszColon[-1] == ']') - { - pszHost = psz+1; - pszColon[-1] = 0; - } - else - pszColon[0] = 0; - if (port >= 0 && port <= USHRT_MAX) - port = portParsed; - } - else - { - if (psz[0] == '[' && psz[strlen(psz)-1] == ']') - { - pszHost = psz+1; - psz[strlen(psz)-1] = 0; - } - - } + std::string hostname = ""; + SplitHostPort(std::string(pszName), port, hostname); std::vector vIP; - bool fRet = LookupIntern(pszHost, vIP, nMaxSolutions, fAllowLookup); + bool fRet = LookupIntern(hostname.c_str(), vIP, nMaxSolutions, fAllowLookup); if (!fRet) return false; vAddr.resize(vIP.size()); @@ -156,11 +171,169 @@ bool LookupNumeric(const char *pszName, CService& addr, int portDefault) return Lookup(pszName, addr, portDefault, false); } -bool ConnectSocket(const CService &addrDest, SOCKET& hSocketRet, int nTimeout) +bool static Socks4(const CService &addrDest, SOCKET& hSocket) +{ + printf("SOCKS4 connecting %s\n", addrDest.ToString().c_str()); + if (!addrDest.IsIPv4()) + { + closesocket(hSocket); + return error("Proxy destination is not IPv4"); + } + char pszSocks4IP[] = "\4\1\0\0\0\0\0\0user"; + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + if (!addrDest.GetSockAddr((struct sockaddr*)&addr, &len) || addr.sin_family != AF_INET) + { + closesocket(hSocket); + return error("Cannot get proxy destination address"); + } + memcpy(pszSocks4IP + 2, &addr.sin_port, 2); + memcpy(pszSocks4IP + 4, &addr.sin_addr, 4); + char* pszSocks4 = pszSocks4IP; + int nSize = sizeof(pszSocks4IP); + + int ret = send(hSocket, pszSocks4, nSize, MSG_NOSIGNAL); + if (ret != nSize) + { + closesocket(hSocket); + return error("Error sending to proxy"); + } + char pchRet[8]; + if (recv(hSocket, pchRet, 8, 0) != 8) + { + closesocket(hSocket); + return error("Error reading proxy response"); + } + if (pchRet[1] != 0x5a) + { + closesocket(hSocket); + if (pchRet[1] != 0x5b) + printf("ERROR: Proxy returned error %d\n", pchRet[1]); + return false; + } + printf("SOCKS4 connected %s\n", addrDest.ToString().c_str()); + return true; +} + +bool static Socks5(string strDest, int port, SOCKET& hSocket) +{ + printf("SOCKS5 connecting %s\n", strDest.c_str()); + if (strDest.size() > 255) + { + closesocket(hSocket); + return error("Hostname too long"); + } + char pszSocks5Init[] = "\5\1\0"; + char *pszSocks5 = pszSocks5Init; + ssize_t nSize = sizeof(pszSocks5Init) - 1; + + ssize_t ret = send(hSocket, pszSocks5, nSize, MSG_NOSIGNAL); + if (ret != nSize) + { + closesocket(hSocket); + return error("Error sending to proxy"); + } + char pchRet1[2]; + if (recv(hSocket, pchRet1, 2, 0) != 2) + { + closesocket(hSocket); + return error("Error reading proxy response"); + } + if (pchRet1[0] != 0x05 || pchRet1[1] != 0x00) + { + closesocket(hSocket); + return error("Proxy failed to initialize"); + } + string strSocks5("\5\1"); + strSocks5 += '\000'; strSocks5 += '\003'; + strSocks5 += static_cast(std::min((int)strDest.size(), 255)); + strSocks5 += strDest; + strSocks5 += static_cast((port >> 8) & 0xFF); + strSocks5 += static_cast((port >> 0) & 0xFF); + ret = send(hSocket, strSocks5.c_str(), strSocks5.size(), MSG_NOSIGNAL); + if (ret != (ssize_t)strSocks5.size()) + { + closesocket(hSocket); + return error("Error sending to proxy"); + } + char pchRet2[4]; + if (recv(hSocket, pchRet2, 4, 0) != 4) + { + closesocket(hSocket); + return error("Error reading proxy response"); + } + if (pchRet2[0] != 0x05) + { + closesocket(hSocket); + return error("Proxy failed to accept request"); + } + if (pchRet2[1] != 0x00) + { + closesocket(hSocket); + switch (pchRet2[1]) + { + case 0x01: return error("Proxy error: general failure"); + case 0x02: return error("Proxy error: connection not allowed"); + case 0x03: return error("Proxy error: network unreachable"); + case 0x04: return error("Proxy error: host unreachable"); + case 0x05: return error("Proxy error: connection refused"); + case 0x06: return error("Proxy error: TTL expired"); + case 0x07: return error("Proxy error: protocol error"); + case 0x08: return error("Proxy error: address type not supported"); + default: return error("Proxy error: unknown"); + } + } + if (pchRet2[2] != 0x00) + { + closesocket(hSocket); + return error("Error: malformed proxy response"); + } + char pchRet3[256]; + switch (pchRet2[3]) + { + case 0x01: ret = recv(hSocket, pchRet3, 4, 0) != 4; break; + case 0x04: ret = recv(hSocket, pchRet3, 16, 0) != 16; break; + case 0x03: + { + ret = recv(hSocket, pchRet3, 1, 0) != 1; + if (ret) + return error("Error reading from proxy"); + int nRecv = pchRet3[0]; + ret = recv(hSocket, pchRet3, nRecv, 0) != nRecv; + break; + } + default: closesocket(hSocket); return error("Error: malformed proxy response"); + } + if (ret) + { + closesocket(hSocket); + return error("Error reading from proxy"); + } + if (recv(hSocket, pchRet3, 2, 0) != 2) + { + closesocket(hSocket); + return error("Error reading from proxy"); + } + printf("SOCKS5 connected %s\n", strDest.c_str()); + return true; +} + +bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRet, int nTimeout) { hSocketRet = INVALID_SOCKET; - SOCKET hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#ifdef USE_IPV6 + struct sockaddr_storage sockaddr; +#else + struct sockaddr sockaddr; +#endif + socklen_t len = sizeof(sockaddr); + if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { + printf("Cannot connect to %s: unsupported network\n", addrConnect.ToString().c_str()); + return false; + } + + SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (hSocket == INVALID_SOCKET) return false; #ifdef SO_NOSIGPIPE @@ -168,13 +341,6 @@ bool ConnectSocket(const CService &addrDest, SOCKET& hSocketRet, int nTimeout) setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)); #endif - bool fProxy = (fUseProxy && addrDest.IsRoutable()); - struct sockaddr_in sockaddr; - if (fProxy) - addrProxy.GetSockAddr(&sockaddr); - else - addrDest.GetSockAddr(&sockaddr); - #ifdef WIN32 u_long fNonblock = 1; if (ioctlsocket(hSocket, FIONBIO, &fNonblock) == SOCKET_ERROR) @@ -187,8 +353,7 @@ bool ConnectSocket(const CService &addrDest, SOCKET& hSocketRet, int nTimeout) return false; } - - if (connect(hSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR) + if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) { // WSAEINVAL is here because some legacy version of winsock uses it if (WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINVAL) @@ -258,37 +423,123 @@ bool ConnectSocket(const CService &addrDest, SOCKET& hSocketRet, int nTimeout) return false; } - if (fProxy) - { - printf("proxy connecting %s\n", addrDest.ToString().c_str()); - char pszSocks4IP[] = "\4\1\0\0\0\0\0\0user"; - struct sockaddr_in addr; - addrDest.GetSockAddr(&addr); - memcpy(pszSocks4IP + 2, &addr.sin_port, 2); - memcpy(pszSocks4IP + 4, &addr.sin_addr, 4); - char* pszSocks4 = pszSocks4IP; - int nSize = sizeof(pszSocks4IP); + hSocketRet = hSocket; + return true; +} - int ret = send(hSocket, pszSocks4, nSize, MSG_NOSIGNAL); - if (ret != nSize) - { - closesocket(hSocket); - return error("Error sending to proxy"); - } - char pchRet[8]; - if (recv(hSocket, pchRet, 8, 0) != 8) - { - closesocket(hSocket); - return error("Error reading proxy response"); - } - if (pchRet[1] != 0x5a) - { - closesocket(hSocket); - if (pchRet[1] != 0x5b) - printf("ERROR: Proxy returned error %d\n", pchRet[1]); +bool SetProxy(enum Network net, CService addrProxy, int nSocksVersion) { + assert(net >= 0 && net < NET_MAX); + if (nSocksVersion != 0 && nSocksVersion != 4 && nSocksVersion != 5) + return false; + if (nSocksVersion != 0 && !addrProxy.IsValid()) + return false; + LOCK(cs_proxyInfos); + proxyInfo[net] = std::make_pair(addrProxy, nSocksVersion); + return true; +} + +bool GetProxy(enum Network net, proxyType &proxyInfoOut) { + assert(net >= 0 && net < NET_MAX); + LOCK(cs_proxyInfos); + if (!proxyInfo[net].second) + return false; + proxyInfoOut = proxyInfo[net]; + return true; +} + +bool SetNameProxy(CService addrProxy, int nSocksVersion) { + if (nSocksVersion != 0 && nSocksVersion != 5) + return false; + if (nSocksVersion != 0 && !addrProxy.IsValid()) + return false; + LOCK(cs_proxyInfos); + nameproxyInfo = std::make_pair(addrProxy, nSocksVersion); + return true; +} + +bool GetNameProxy(proxyType &nameproxyInfoOut) { + LOCK(cs_proxyInfos); + if (!nameproxyInfo.second) + return false; + nameproxyInfoOut = nameproxyInfo; + return true; +} + +bool HaveNameProxy() { + LOCK(cs_proxyInfos); + return nameproxyInfo.second != 0; +} + +bool IsProxy(const CNetAddr &addr) { + LOCK(cs_proxyInfos); + for (int i = 0; i < NET_MAX; i++) { + if (proxyInfo[i].second && (addr == (CNetAddr)proxyInfo[i].first)) + return true; + } + return false; +} + +bool ConnectSocket(const CService &addrDest, SOCKET& hSocketRet, int nTimeout) +{ + proxyType proxy; + + // no proxy needed + if (!GetProxy(addrDest.GetNetwork(), proxy)) + return ConnectSocketDirectly(addrDest, hSocketRet, nTimeout); + + SOCKET hSocket = INVALID_SOCKET; + + // first connect to proxy server + if (!ConnectSocketDirectly(proxy.first, hSocket, nTimeout)) + return false; + + // do socks negotiation + switch (proxy.second) { + case 4: + if (!Socks4(addrDest, hSocket)) return false; - } - printf("proxy connected %s\n", addrDest.ToString().c_str()); + break; + case 5: + if (!Socks5(addrDest.ToStringIP(), addrDest.GetPort(), hSocket)) + return false; + break; + default: + return false; + } + + hSocketRet = hSocket; + return true; +} + +bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest, int portDefault, int nTimeout) +{ + string strDest; + int port = portDefault; + SplitHostPort(string(pszDest), port, strDest); + + SOCKET hSocket = INVALID_SOCKET; + + proxyType nameproxy; + GetNameProxy(nameproxy); + + CService addrResolved(CNetAddr(strDest, fNameLookup && !nameproxy.second), port); + if (addrResolved.IsValid()) { + addr = addrResolved; + return ConnectSocket(addr, hSocketRet, nTimeout); + } + addr = CService("0.0.0.0:0"); + if (!nameproxy.second) + return false; + if (!ConnectSocketDirectly(nameproxy.first, hSocket, nTimeout)) + return false; + + switch(nameproxy.second) { + default: + case 4: return false; + case 5: + if (!Socks5(strDest, port, hSocket)) + return false; + break; } hSocketRet = hSocket; @@ -305,6 +556,32 @@ void CNetAddr::SetIP(const CNetAddr& ipIn) memcpy(ip, ipIn.ip, sizeof(ip)); } +static const unsigned char pchOnionCat[] = {0xFD,0x87,0xD8,0x7E,0xEB,0x43}; +static const unsigned char pchGarliCat[] = {0xFD,0x60,0xDB,0x4D,0xDD,0xB5}; + +bool CNetAddr::SetSpecial(const std::string &strName) +{ + if (strName.size()>6 && strName.substr(strName.size() - 6, 6) == ".onion") { + std::vector vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str()); + if (vchAddr.size() != 16-sizeof(pchOnionCat)) + return false; + memcpy(ip, pchOnionCat, sizeof(pchOnionCat)); + for (unsigned int i=0; i<16-sizeof(pchOnionCat); i++) + ip[i + sizeof(pchOnionCat)] = vchAddr[i]; + return true; + } + if (strName.size()>11 && strName.substr(strName.size() - 11, 11) == ".oc.b32.i2p") { + std::vector vchAddr = DecodeBase32(strName.substr(0, strName.size() - 11).c_str()); + if (vchAddr.size() != 16-sizeof(pchGarliCat)) + return false; + memcpy(ip, pchOnionCat, sizeof(pchGarliCat)); + for (unsigned int i=0; i<16-sizeof(pchGarliCat); i++) + ip[i + sizeof(pchGarliCat)] = vchAddr[i]; + return true; + } + return false; +} + CNetAddr::CNetAddr() { Init(); @@ -339,7 +616,7 @@ CNetAddr::CNetAddr(const std::string &strIp, bool fAllowLookup) *this = vIP[0]; } -int CNetAddr::GetByte(int n) const +unsigned int CNetAddr::GetByte(int n) const { return ip[15-n]; } @@ -349,11 +626,16 @@ bool CNetAddr::IsIPv4() const return (memcmp(ip, pchIPv4, sizeof(pchIPv4)) == 0); } +bool CNetAddr::IsIPv6() const +{ + return (!IsIPv4() && !IsTor() && !IsI2P()); +} + bool CNetAddr::IsRFC1918() const { return IsIPv4() && ( - GetByte(3) == 10 || - (GetByte(3) == 192 && GetByte(2) == 168) || + GetByte(3) == 10 || + (GetByte(3) == 192 && GetByte(2) == 168) || (GetByte(3) == 172 && (GetByte(2) >= 16 && GetByte(2) <= 31))); } @@ -405,6 +687,16 @@ bool CNetAddr::IsRFC4843() const return (GetByte(15) == 0x20 && GetByte(14) == 0x01 && GetByte(13) == 0x00 && (GetByte(12) & 0xF0) == 0x10); } +bool CNetAddr::IsTor() const +{ + return (memcmp(ip, pchOnionCat, sizeof(pchOnionCat)) == 0); +} + +bool CNetAddr::IsI2P() const +{ + return (memcmp(ip, pchGarliCat, sizeof(pchGarliCat)) == 0); +} + bool CNetAddr::IsLocal() const { // IPv4 loopback @@ -427,7 +719,7 @@ bool CNetAddr::IsMulticast() const bool CNetAddr::IsValid() const { - // Clean up 3-byte shifted addresses caused by garbage in size field + // Cleanup 3-byte shifted addresses caused by garbage in size field // of addr messages from versions before 0.2.9 checksum. // Two consecutive addr messages look like this: // header20 vectorlen3 addr26 addr26 addr26 header20 vectorlen3 addr26 addr26 addr26... @@ -463,11 +755,44 @@ bool CNetAddr::IsValid() const bool CNetAddr::IsRoutable() const { - return IsValid() && !(IsRFC1918() || IsRFC3927() || IsRFC4862() || IsRFC4193() || IsRFC4843() || IsLocal()); + return IsValid() && !(IsRFC1918() || IsRFC3927() || IsRFC4862() || (IsRFC4193() && !IsTor() && !IsI2P()) || IsRFC4843() || IsLocal()); +} + +enum Network CNetAddr::GetNetwork() const +{ + if (!IsRoutable()) + return NET_UNROUTABLE; + + if (IsIPv4()) + return NET_IPV4; + + if (IsTor()) + return NET_TOR; + + if (IsI2P()) + return NET_I2P; + + return NET_IPV6; } std::string CNetAddr::ToStringIP() const { + if (IsTor()) + return EncodeBase32(&ip[6], 10) + ".onion"; + if (IsI2P()) + return EncodeBase32(&ip[6], 10) + ".oc.b32.i2p"; + CService serv(*this, 0); +#ifdef USE_IPV6 + struct sockaddr_storage sockaddr; +#else + struct sockaddr sockaddr; +#endif + socklen_t socklen = sizeof(sockaddr); + if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) { + char name[1025] = ""; + if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), NULL, 0, NI_NUMERICHOST)) + return std::string(name); + } if (IsIPv4()) return strprintf("%u.%u.%u.%u", GetByte(3), GetByte(2), GetByte(1), GetByte(0)); else @@ -519,44 +844,56 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const std::vector CNetAddr::GetGroup() const { std::vector vchRet; - int nClass = 0; // 0=IPv6, 1=IPv4, 254=local, 255=unroutable + int nClass = NET_IPV6; int nStartByte = 0; int nBits = 16; // all local addresses belong to the same group if (IsLocal()) { - nClass = 254; + nClass = 255; nBits = 0; } // all unroutable addresses belong to the same group if (!IsRoutable()) { - nClass = 255; + nClass = NET_UNROUTABLE; nBits = 0; } // for IPv4 addresses, '1' + the 16 higher-order bits of the IP // includes mapped IPv4, SIIT translated IPv4, and the well-known prefix else if (IsIPv4() || IsRFC6145() || IsRFC6052()) { - nClass = 1; + nClass = NET_IPV4; nStartByte = 12; } - // for 6to4 tunneled addresses, use the encapsulated IPv4 address + // for 6to4 tunnelled addresses, use the encapsulated IPv4 address else if (IsRFC3964()) { - nClass = 1; + nClass = NET_IPV4; nStartByte = 2; } - // for Teredo-tunneled IPv6 addresses, use the encapsulated IPv4 address + // for Teredo-tunnelled IPv6 addresses, use the encapsulated IPv4 address else if (IsRFC4380()) { - vchRet.push_back(1); + vchRet.push_back(NET_IPV4); vchRet.push_back(GetByte(3) ^ 0xFF); vchRet.push_back(GetByte(2) ^ 0xFF); return vchRet; } + else if (IsTor()) + { + nClass = NET_TOR; + nStartByte = 6; + nBits = 4; + } + else if (IsI2P()) + { + nClass = NET_I2P; + nStartByte = 6; + nBits = 4; + } // for he.net, use /36 groups else if (GetByte(15) == 0x20 && GetByte(14) == 0x11 && GetByte(13) == 0x04 && GetByte(12) == 0x70) nBits = 36; @@ -577,10 +914,10 @@ std::vector CNetAddr::GetGroup() const return vchRet; } -int64 CNetAddr::GetHash() const +uint64 CNetAddr::GetHash() const { uint256 hash = Hash(&ip[0], &ip[16]); - int64 nRet; + uint64 nRet; memcpy(&nRet, &hash, sizeof(nRet)); return nRet; } @@ -590,6 +927,84 @@ void CNetAddr::print() const printf("CNetAddr(%s)\n", ToString().c_str()); } +// private extensions to enum Network, only returned by GetExtNetwork, +// and only used in GetReachabilityFrom +static const int NET_UNKNOWN = NET_MAX + 0; +static const int NET_TEREDO = NET_MAX + 1; +int static GetExtNetwork(const CNetAddr *addr) +{ + if (addr == NULL) + return NET_UNKNOWN; + if (addr->IsRFC4380()) + return NET_TEREDO; + return addr->GetNetwork(); +} + +/** Calculates a metric for how reachable (*this) is from a given partner */ +int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const +{ + enum Reachability { + REACH_UNREACHABLE, + REACH_DEFAULT, + REACH_TEREDO, + REACH_IPV6_WEAK, + REACH_IPV4, + REACH_IPV6_STRONG, + REACH_PRIVATE + }; + + if (!IsRoutable()) + return REACH_UNREACHABLE; + + int ourNet = GetExtNetwork(this); + int theirNet = GetExtNetwork(paddrPartner); + bool fTunnel = IsRFC3964() || IsRFC6052() || IsRFC6145(); + + switch(theirNet) { + case NET_IPV4: + switch(ourNet) { + default: return REACH_DEFAULT; + case NET_IPV4: return REACH_IPV4; + } + case NET_IPV6: + switch(ourNet) { + default: return REACH_DEFAULT; + case NET_TEREDO: return REACH_TEREDO; + case NET_IPV4: return REACH_IPV4; + case NET_IPV6: return fTunnel ? REACH_IPV6_WEAK : REACH_IPV6_STRONG; // only prefer giving our IPv6 address if it's not tunnelled + } + case NET_TOR: + switch(ourNet) { + default: return REACH_DEFAULT; + case NET_IPV4: return REACH_IPV4; // Tor users can connect to IPv4 as well + case NET_TOR: return REACH_PRIVATE; + } + case NET_I2P: + switch(ourNet) { + default: return REACH_DEFAULT; + case NET_I2P: return REACH_PRIVATE; + } + case NET_TEREDO: + switch(ourNet) { + default: return REACH_DEFAULT; + case NET_TEREDO: return REACH_TEREDO; + case NET_IPV6: return REACH_IPV6_WEAK; + case NET_IPV4: return REACH_IPV4; + } + case NET_UNKNOWN: + case NET_UNROUTABLE: + default: + switch(ourNet) { + default: return REACH_DEFAULT; + case NET_TEREDO: return REACH_TEREDO; + case NET_IPV6: return REACH_IPV6_WEAK; + case NET_IPV4: return REACH_IPV4; + case NET_I2P: return REACH_PRIVATE; // assume connections from unroutable addresses are + case NET_TOR: return REACH_PRIVATE; // either from Tor/I2P, or don't care about our address + } + } +} + void CService::Init() { port = 0; @@ -626,6 +1041,22 @@ CService::CService(const struct sockaddr_in6 &addr) : CNetAddr(addr.sin6_addr), } #endif +bool CService::SetSockAddr(const struct sockaddr *paddr) +{ + switch (paddr->sa_family) { + case AF_INET: + *this = CService(*(const struct sockaddr_in*)paddr); + return true; +#ifdef USE_IPV6 + case AF_INET6: + *this = CService(*(const struct sockaddr_in6*)paddr); + return true; +#endif + default: + return false; + } +} + CService::CService(const char *pszIpPort, bool fAllowLookup) { Init(); @@ -678,29 +1109,36 @@ bool operator<(const CService& a, const CService& b) return (CNetAddr)a < (CNetAddr)b || ((CNetAddr)a == (CNetAddr)b && a.port < b.port); } -bool CService::GetSockAddr(struct sockaddr_in* paddr) const +bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const { - if (!IsIPv4()) - return false; - memset(paddr, 0, sizeof(struct sockaddr_in)); - if (!GetInAddr(&paddr->sin_addr)) - return false; - paddr->sin_family = AF_INET; - paddr->sin_port = htons(port); - return true; -} - + if (IsIPv4()) { + if (*addrlen < (socklen_t)sizeof(struct sockaddr_in)) + return false; + *addrlen = sizeof(struct sockaddr_in); + struct sockaddr_in *paddrin = (struct sockaddr_in*)paddr; + memset(paddrin, 0, *addrlen); + if (!GetInAddr(&paddrin->sin_addr)) + return false; + paddrin->sin_family = AF_INET; + paddrin->sin_port = htons(port); + return true; + } #ifdef USE_IPV6 -bool CService::GetSockAddr6(struct sockaddr_in6* paddr) const -{ - memset(paddr, 0, sizeof(struct sockaddr_in6)); - if (!GetIn6Addr(&paddr->sin6_addr)) - return false; - paddr->sin6_family = AF_INET6; - paddr->sin6_port = htons(port); - return true; -} + if (IsIPv6()) { + if (*addrlen < (socklen_t)sizeof(struct sockaddr_in6)) + return false; + *addrlen = sizeof(struct sockaddr_in6); + struct sockaddr_in6 *paddrin6 = (struct sockaddr_in6*)paddr; + memset(paddrin6, 0, *addrlen); + if (!GetIn6Addr(&paddrin6->sin6_addr)) + return false; + paddrin6->sin6_family = AF_INET6; + paddrin6->sin6_port = htons(port); + return true; + } #endif + return false; +} std::vector CService::GetKey() const { @@ -714,12 +1152,16 @@ std::vector CService::GetKey() const std::string CService::ToStringPort() const { - return strprintf(":%i", port); + return strprintf("%u", port); } std::string CService::ToStringIPPort() const { - return ToStringIP() + ToStringPort(); + if (IsIPv4() || IsTor() || IsI2P()) { + return ToStringIP() + ":" + ToStringPort(); + } else { + return "[" + ToStringIP() + "]:" + ToStringPort(); + } } std::string CService::ToString() const diff --git a/src/netbase.h b/src/netbase.h index 5bf3fdd..0a29d4f 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -17,6 +17,20 @@ extern int nConnectTimeout; #undef SetPort #endif +enum Network +{ + NET_UNROUTABLE, + NET_IPV4, + NET_IPV6, + NET_TOR, + NET_I2P, + + NET_MAX, +}; + +extern int nConnectTimeout; +extern bool fNameLookup; + /** IP address (IPv6, or IPv4 using mapped IPv6 range (::FFFF:0:0/96)) */ class CNetAddr { @@ -30,27 +44,33 @@ class CNetAddr explicit CNetAddr(const std::string &strIp, bool fAllowLookup = false); void Init(); void SetIP(const CNetAddr& ip); + bool SetSpecial(const std::string &strName); // for Tor and I2P addresses bool IsIPv4() const; // IPv4 mapped address (::FFFF:0:0/96, 0.0.0.0/0) + bool IsIPv6() const; // IPv6 address (not mapped IPv4, not Tor/I2P) bool IsRFC1918() const; // IPv4 private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12) bool IsRFC3849() const; // IPv6 documentation address (2001:0DB8::/32) bool IsRFC3927() const; // IPv4 autoconfig (169.254.0.0/16) - bool IsRFC3964() const; // IPv6 6to4 tunneling (2002::/16) + bool IsRFC3964() const; // IPv6 6to4 tunnelling (2002::/16) bool IsRFC4193() const; // IPv6 unique local (FC00::/15) - bool IsRFC4380() const; // IPv6 Teredo tunneling (2001::/32) + bool IsRFC4380() const; // IPv6 Teredo tunnelling (2001::/32) bool IsRFC4843() const; // IPv6 ORCHID (2001:10::/28) bool IsRFC4862() const; // IPv6 autoconfig (FE80::/64) bool IsRFC6052() const; // IPv6 well-known prefix (64:FF9B::/96) bool IsRFC6145() const; // IPv6 IPv4-translated address (::FFFF:0:0:0/96) + bool IsTor() const; + bool IsI2P() const; bool IsLocal() const; bool IsRoutable() const; bool IsValid() const; bool IsMulticast() const; + enum Network GetNetwork() const; std::string ToString() const; std::string ToStringIP() const; - int GetByte(int n) const; - int64 GetHash() const; + unsigned int GetByte(int n) const; + uint64 GetHash() const; bool GetInAddr(struct in_addr* pipv4Addr) const; std::vector GetGroup() const; + int GetReachabilityFrom(const CNetAddr *paddrPartner = NULL) const; void print() const; #ifdef USE_IPV6 @@ -86,7 +106,8 @@ class CService : public CNetAddr void Init(); void SetPort(unsigned short portIn); unsigned short GetPort() const; - bool GetSockAddr(struct sockaddr_in* paddr) const; + bool GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const; + bool SetSockAddr(const struct sockaddr* paddr); friend bool operator==(const CService& a, const CService& b); friend bool operator!=(const CService& a, const CService& b); friend bool operator<(const CService& a, const CService& b); @@ -98,7 +119,6 @@ class CService : public CNetAddr #ifdef USE_IPV6 CService(const struct in6_addr& ipv6Addr, unsigned short port); - bool GetSockAddr6(struct sockaddr_in6* paddr) const; CService(const struct sockaddr_in6& addr); #endif @@ -113,15 +133,21 @@ class CService : public CNetAddr ) }; +typedef std::pair proxyType; + +enum Network ParseNetwork(std::string net); +void SplitHostPort(std::string in, int &portOut, std::string &hostOut); +bool SetProxy(enum Network net, CService addrProxy, int nSocksVersion = 5); +bool GetProxy(enum Network net, proxyType &proxyInfoOut); +bool IsProxy(const CNetAddr &addr); +bool SetNameProxy(CService addrProxy, int nSocksVersion = 5); +bool HaveNameProxy(); bool LookupHost(const char *pszName, std::vector& vIP, unsigned int nMaxSolutions = 0, bool fAllowLookup = true); bool LookupHostNumeric(const char *pszName, std::vector& vIP, unsigned int nMaxSolutions = 0); bool Lookup(const char *pszName, CService& addr, int portDefault = 0, bool fAllowLookup = true); bool Lookup(const char *pszName, std::vector& vAddr, int portDefault = 0, bool fAllowLookup = true, unsigned int nMaxSolutions = 0); bool LookupNumeric(const char *pszName, CService& addr, int portDefault = 0); bool ConnectSocket(const CService &addr, SOCKET& hSocketRet, int nTimeout = nConnectTimeout); - -// Settings -extern int fUseProxy; -extern CService addrProxy; +bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest, int portDefault = 0, int nTimeout = nConnectTimeout); #endif diff --git a/src/noui.cpp b/src/noui.cpp index 6d98468..db25f2d 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -3,42 +3,26 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "ui_interface.h" +#include "init.h" +#include "bitcoinrpc.h" #include -#include "init.h" -int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) +static int noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { printf("%s: %s\n", caption.c_str(), message.c_str()); fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); return 4; } -bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) +static bool noui_ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) { return true; } -void MainFrameRepaint() -{ -} - -void AddressBookRepaint() -{ -} - -void InitMessage(const std::string &message) +void noui_connect() { + // Connect bitcoind signal handlers + uiInterface.ThreadSafeMessageBox.connect(noui_ThreadSafeMessageBox); + uiInterface.ThreadSafeAskFee.connect(noui_ThreadSafeAskFee); } - -std::string _(const char* psz) -{ - return psz; -} - -void QueueShutdown() -{ - // Without UI, Shutdown can simply be started in a new thread - CreateThread(Shutdown, NULL); -} - diff --git a/src/protocol.cpp b/src/protocol.cpp index e33ede5..d6e340e 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2013 NovaCoin Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -12,21 +11,6 @@ # include #endif -// The message start string is designed to be unlikely to occur in normal data. -// The characters are rarely used upper ascii, not valid as UTF-8, and produce -// a large 4-byte int at any alignment. - -static unsigned char pchMessageStartTestNew[4] = { 0xcd, 0xf2, 0xc0, 0xef }; -static unsigned char pchMessageStartPPCoin[4] = { 0xe4, 0xe8, 0xe9, 0xe5 }; - -void GetMessageStart(unsigned char pchMessageStart[], bool fPersistent) -{ - if (fTestNet) - memcpy(pchMessageStart, pchMessageStartTestNew, sizeof(pchMessageStartTestNew)); - else - memcpy(pchMessageStart, pchMessageStartPPCoin, sizeof(pchMessageStartPPCoin)); -} - static const char* ppszTypeName[] = { "ERROR", @@ -36,7 +20,7 @@ static const char* ppszTypeName[] = CMessageHeader::CMessageHeader() { - GetMessageStart(pchMessageStart); + memcpy(pchMessageStart, ::pchMessageStart, sizeof(pchMessageStart)); memset(pchCommand, 0, sizeof(pchCommand)); pchCommand[1] = 1; nMessageSize = -1; @@ -45,7 +29,7 @@ CMessageHeader::CMessageHeader() CMessageHeader::CMessageHeader(const char* pszCommand, unsigned int nMessageSizeIn) { - GetMessageStart(pchMessageStart); + memcpy(pchMessageStart, ::pchMessageStart, sizeof(pchMessageStart)); strncpy(pchCommand, pszCommand, COMMAND_SIZE); nMessageSize = nMessageSizeIn; nChecksum = 0; @@ -62,9 +46,7 @@ std::string CMessageHeader::GetCommand() const bool CMessageHeader::IsValid() const { // Check start string - unsigned char pchMessageStartProtocol[4]; - GetMessageStart(pchMessageStartProtocol); - if (memcmp(pchMessageStart, pchMessageStartProtocol, sizeof(pchMessageStart)) != 0) + if (memcmp(pchMessageStart, ::pchMessageStart, sizeof(pchMessageStart)) != 0) return false; // Check the command string for errors diff --git a/src/protocol.h b/src/protocol.h index ed2ca59..d771406 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -15,20 +15,15 @@ #include #include "uint256.h" -#define PPCOIN_PORT 7777 -#define RPC_PORT 8344 -#define TESTNET_PORT 17777 - extern bool fTestNet; - -void GetMessageStart(unsigned char pchMessageStart[], bool fPersistent = false); - static inline unsigned short GetDefaultPort(const bool testnet = fTestNet) { - return testnet ? TESTNET_PORT : PPCOIN_PORT; + return testnet ? 17777 : 7777; } +extern unsigned char pchMessageStart[4]; + /** Message header. * (4) message start. * (12) command. @@ -54,8 +49,16 @@ class CMessageHeader // TODO: make private (improves encapsulation) public: - enum { COMMAND_SIZE=12 }; - unsigned char pchMessageStart[4]; + enum { + MESSAGE_START_SIZE=sizeof(::pchMessageStart), + COMMAND_SIZE=12, + MESSAGE_SIZE_SIZE=sizeof(int), + CHECKSUM_SIZE=sizeof(int), + + MESSAGE_SIZE_OFFSET=MESSAGE_START_SIZE+COMMAND_SIZE, + CHECKSUM_OFFSET=MESSAGE_SIZE_OFFSET+MESSAGE_SIZE_SIZE + }; + char pchMessageStart[MESSAGE_START_SIZE]; char pchCommand[COMMAND_SIZE]; unsigned int nMessageSize; unsigned int nChecksum; diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 02454c5..e20358c 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -2,6 +2,7 @@ #include "ui_addressbookpage.h" #include "addresstablemodel.h" +#include "optionsmodel.h" #include "bitcoingui.h" #include "editaddressdialog.h" #include "csvmodelwriter.h" @@ -20,12 +21,13 @@ AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) : QDialog(parent), ui(new Ui::AddressBookPage), model(0), + optionsModel(0), mode(mode), tab(tab) { ui->setupUi(this); -#ifdef Q_WS_MAC // Icons on push buttons are very uncommon on Mac +#ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac ui->newAddressButton->setIcon(QIcon()); ui->copyToClipboard->setIcon(QIcon()); ui->deleteButton->setIcon(QIcon()); @@ -58,25 +60,38 @@ AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) : ui->signMessage->setVisible(true); break; } - ui->tableView->setTabKeyNavigation(false); - ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu); // Context menu actions - QAction *copyAddressAction = new QAction(tr("Copy address"), this); - QAction *copyLabelAction = new QAction(tr("Copy label"), this); - QAction *editAction = new QAction(tr("Edit"), this); - deleteAction = new QAction(tr("Delete"), this); - + QAction *copyLabelAction = new QAction(tr("Copy &Label"), this); + QAction *copyAddressAction = new QAction(ui->copyToClipboard->text(), this); + QAction *editAction = new QAction(tr("&Edit"), this); + QAction *showQRCodeAction = new QAction(ui->showQRCode->text(), this); + QAction *signMessageAction = new QAction(ui->signMessage->text(), this); + QAction *verifyMessageAction = new QAction(ui->verifyMessage->text(), this); + deleteAction = new QAction(ui->deleteButton->text(), this); + + // Build context menu contextMenu = new QMenu(); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(editAction); - contextMenu->addAction(deleteAction); - + if(tab == SendingTab) + contextMenu->addAction(deleteAction); + contextMenu->addSeparator(); + contextMenu->addAction(showQRCodeAction); + if(tab == ReceivingTab) + contextMenu->addAction(signMessageAction); + else if(tab == SendingTab) + contextMenu->addAction(verifyMessageAction); + + // Connect signals for context menu actions connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(on_copyToClipboard_clicked())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(onCopyLabelAction())); connect(editAction, SIGNAL(triggered()), this, SLOT(onEditAction())); connect(deleteAction, SIGNAL(triggered()), this, SLOT(on_deleteButton_clicked())); + connect(showQRCodeAction, SIGNAL(triggered()), this, SLOT(on_showQRCode_clicked())); + connect(signMessageAction, SIGNAL(triggered()), this, SLOT(on_signMessage_clicked())); + connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(on_verifyMessage_clicked())); connect(ui->tableView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); @@ -98,6 +113,8 @@ void AddressBookPage::setModel(AddressTableModel *model) proxyModel = new QSortFilterProxyModel(this); proxyModel->setSourceModel(model); proxyModel->setDynamicSortFilter(true); + proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); switch(tab) { case ReceivingTab: @@ -123,9 +140,18 @@ void AddressBookPage::setModel(AddressTableModel *model) connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged())); + // Select row for newly created address + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(selectNewAddress(QModelIndex,int,int))); + selectionChanged(); } +void AddressBookPage::setOptionsModel(OptionsModel *optionsModel) +{ + this->optionsModel = optionsModel; +} + void AddressBookPage::on_copyToClipboard_clicked() { GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address); @@ -166,10 +192,22 @@ void AddressBookPage::on_signMessage_clicked() addr = address.toString(); } - QObject *qoGUI = parent()->parent(); - BitcoinGUI *gui = qobject_cast(qoGUI); - if (gui) - gui->gotoMessagePage(addr); + emit signMessage(addr); +} + +void AddressBookPage::on_verifyMessage_clicked() +{ + QTableView *table = ui->tableView; + QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); + QString addr; + + foreach (QModelIndex index, indexes) + { + QVariant address = index.data(); + addr = address.toString(); + } + + emit verifyMessage(addr); } void AddressBookPage::on_newAddressButton_clicked() @@ -179,20 +217,11 @@ void AddressBookPage::on_newAddressButton_clicked() EditAddressDialog dlg( tab == SendingTab ? EditAddressDialog::NewSendingAddress : - EditAddressDialog::NewReceivingAddress); + EditAddressDialog::NewReceivingAddress, this); dlg.setModel(model); if(dlg.exec()) { - // Select row for newly created address - QString address = dlg.getAddress(); - QModelIndexList lst = proxyModel->match(proxyModel->index(0, - AddressTableModel::Address, QModelIndex()), - Qt::EditRole, address, 1, Qt::MatchExactly); - if(!lst.isEmpty()) - { - ui->tableView->setFocus(); - ui->tableView->selectRow(lst.at(0).row()); - } + newAddressToSelect = dlg.getAddress(); } } @@ -226,6 +255,8 @@ void AddressBookPage::selectionChanged() deleteAction->setEnabled(true); ui->signMessage->setEnabled(false); ui->signMessage->setVisible(false); + ui->verifyMessage->setEnabled(true); + ui->verifyMessage->setVisible(true); break; case ReceivingTab: // Deleting receiving addresses, however, is not allowed @@ -234,6 +265,8 @@ void AddressBookPage::selectionChanged() deleteAction->setEnabled(false); ui->signMessage->setEnabled(true); ui->signMessage->setVisible(true); + ui->verifyMessage->setEnabled(false); + ui->verifyMessage->setVisible(false); break; } ui->copyToClipboard->setEnabled(true); @@ -245,6 +278,7 @@ void AddressBookPage::selectionChanged() ui->showQRCode->setEnabled(false); ui->copyToClipboard->setEnabled(false); ui->signMessage->setEnabled(false); + ui->verifyMessage->setEnabled(false); } } @@ -310,6 +344,9 @@ void AddressBookPage::on_showQRCode_clicked() QString address = index.data().toString(), label = index.sibling(index.row(), 0).data(Qt::EditRole).toString(); QRCodeDialog *dialog = new QRCodeDialog(address, label, tab == ReceivingTab, this); + if(optionsModel) + dialog->setModel(optionsModel); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } #endif @@ -323,3 +360,15 @@ void AddressBookPage::contextualMenu(const QPoint &point) contextMenu->exec(QCursor::pos()); } } + +void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int end) +{ + QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent)); + if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect)) + { + // Select row of newly created address, once + ui->tableView->setFocus(); + ui->tableView->selectRow(idx.row()); + newAddressToSelect.clear(); + } +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index b2cf2db..df87486 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -7,12 +7,14 @@ namespace Ui { class AddressBookPage; } class AddressTableModel; +class OptionsModel; QT_BEGIN_NAMESPACE class QTableView; class QItemSelection; class QSortFilterProxyModel; class QMenu; +class QModelIndex; QT_END_NAMESPACE /** Widget that shows a list of sending or receiving addresses. @@ -36,6 +38,7 @@ public: ~AddressBookPage(); void setModel(AddressTableModel *model); + void setOptionsModel(OptionsModel *optionsModel); const QString &getReturnValue() const { return returnValue; } public slots: @@ -45,12 +48,14 @@ public slots: private: Ui::AddressBookPage *ui; AddressTableModel *model; + OptionsModel *optionsModel; Mode mode; Tabs tab; QString returnValue; QSortFilterProxyModel *proxyModel; QMenu *contextMenu; QAction *deleteAction; + QString newAddressToSelect; private slots: void on_deleteButton_clicked(); @@ -58,6 +63,7 @@ private slots: /** Copy address of currently selected address entry to clipboard */ void on_copyToClipboard_clicked(); void on_signMessage_clicked(); + void on_verifyMessage_clicked(); void selectionChanged(); void on_showQRCode_clicked(); /** Spawn contextual menu (right mouse menu) for address book entry */ @@ -67,6 +73,13 @@ private slots: void onCopyLabelAction(); /** Edit currently selected address entry */ void onEditAction(); + + /** New entry/entries were added to address table */ + void selectNewAddress(const QModelIndex &parent, int begin, int end); + +signals: + void signMessage(QString addr); + void verifyMessage(QString addr); }; #endif // ADDRESSBOOKDIALOG_H diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 7b95f51..e65d391 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -3,6 +3,7 @@ #include "walletmodel.h" #include "wallet.h" +#include "base58.h" #include #include @@ -26,27 +27,43 @@ struct AddressTableEntry type(type), label(label), address(address) {} }; +struct AddressTableEntryLessThan +{ + bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const + { + return a.address < b.address; + } + bool operator()(const AddressTableEntry &a, const QString &b) const + { + return a.address < b; + } + bool operator()(const QString &a, const AddressTableEntry &b) const + { + return a < b.address; + } +}; + // Private implementation class AddressTablePriv { public: CWallet *wallet; QList cachedAddressTable; + AddressTableModel *parent; - AddressTablePriv(CWallet *wallet): - wallet(wallet) {} + AddressTablePriv(CWallet *wallet, AddressTableModel *parent): + wallet(wallet), parent(parent) {} void refreshAddressTable() { cachedAddressTable.clear(); - { LOCK(wallet->cs_wallet); - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, std::string)& item, wallet->mapAddressBook) + BOOST_FOREACH(const PAIRTYPE(CTxDestination, std::string)& item, wallet->mapAddressBook) { const CBitcoinAddress& address = item.first; const std::string& strName = item.second; - bool fMine = wallet->HaveKey(address); + bool fMine = IsMine(*wallet, address.Get()); cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending, QString::fromStdString(strName), QString::fromStdString(address.ToString()))); @@ -54,6 +71,53 @@ public: } } + void updateEntry(const QString &address, const QString &label, bool isMine, int status) + { + // Find address / label in model + QList::iterator lower = qLowerBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + QList::iterator upper = qUpperBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + int lowerIndex = (lower - cachedAddressTable.begin()); + int upperIndex = (upper - cachedAddressTable.begin()); + bool inModel = (lower != upper); + AddressTableEntry::Type newEntryType = isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending; + + switch(status) + { + case CT_NEW: + if(inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n"); + break; + } + parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); + cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address)); + parent->endInsertRows(); + break; + case CT_UPDATED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n"); + break; + } + lower->type = newEntryType; + lower->label = label; + parent->emitDataChanged(lowerIndex); + break; + case CT_DELETED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n"); + break; + } + parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); + cachedAddressTable.erase(lower, upper); + parent->endRemoveRows(); + break; + } + } + int size() { return cachedAddressTable.size(); @@ -76,7 +140,7 @@ AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) : QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0) { columns << tr("Label") << tr("Address"); - priv = new AddressTablePriv(wallet); + priv = new AddressTablePriv(wallet, this); priv->refreshAddressTable(); } @@ -157,7 +221,7 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu switch(index.column()) { case Label: - wallet->SetAddressBookName(rec->address.toStdString(), value.toString().toStdString()); + wallet->SetAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get(), value.toString().toStdString()); rec->label = value.toString(); break; case Address: @@ -173,16 +237,13 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu { LOCK(wallet->cs_wallet); // Remove old entry - wallet->DelAddressBookName(rec->address.toStdString()); + wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); // Add new entry with new address - wallet->SetAddressBookName(value.toString().toStdString(), rec->label.toStdString()); + wallet->SetAddressBookName(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString()); } - - rec->address = value.toString(); } break; } - emit dataChanged(index, index); return true; } @@ -232,12 +293,10 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex & pa } } -void AddressTableModel::update() +void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, int status) { // Update address book model from Bitcoin core - beginResetModel(); - priv->refreshAddressTable(); - endResetModel(); + priv->updateEntry(address, label, isMine, status); } QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) @@ -257,7 +316,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con // Check for duplicate addresses { LOCK(wallet->cs_wallet); - if(wallet->mapAddressBook.count(strAddress)) + if(wallet->mapAddressBook.count(CBitcoinAddress(strAddress).Get())) { editStatus = DUPLICATE_ADDRESS; return QString(); @@ -274,13 +333,13 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - std::vector newKey; + CPubKey newKey; if(!wallet->GetKeyFromPool(newKey, true)) { editStatus = KEY_GENERATION_FAILURE; return QString(); } - strAddress = CBitcoinAddress(newKey).ToString(); + strAddress = CBitcoinAddress(newKey.GetID()).ToString(); } else { @@ -289,7 +348,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con // Add entry { LOCK(wallet->cs_wallet); - wallet->SetAddressBookName(strAddress, strLabel); + wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); } return QString::fromStdString(strAddress); } @@ -306,7 +365,7 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex & paren } { LOCK(wallet->cs_wallet); - wallet->DelAddressBookName(rec->address.toStdString()); + wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); } return true; } @@ -318,7 +377,7 @@ QString AddressTableModel::labelForAddress(const QString &address) const { LOCK(wallet->cs_wallet); CBitcoinAddress address_parsed(address.toStdString()); - std::map::iterator mi = wallet->mapAddressBook.find(address_parsed); + std::map::iterator mi = wallet->mapAddressBook.find(address_parsed.Get()); if (mi != wallet->mapAddressBook.end()) { return QString::fromStdString(mi->second); @@ -341,3 +400,7 @@ int AddressTableModel::lookupAddress(const QString &address) const } } +void AddressTableModel::emitDataChanged(int idx) +{ + emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex())); +} diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 7fd07cf..42974e3 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -74,13 +74,18 @@ private: QStringList columns; EditStatus editStatus; + /** Notify listeners that data changed. */ + void emitDataChanged(int index); + signals: void defaultAddressChanged(const QString &address); public slots: - /* Update address list from core. Invalidates any indices. + /* Update address list from core. */ - void update(); + void updateEntry(const QString &address, const QString &label, bool isMine, int status); + + friend class AddressTablePriv; }; #endif // ADDRESSTABLEMODEL_H diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 31e4040..1ae43c1 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -24,7 +24,6 @@ AskPassphraseDialog::AskPassphraseDialog(Mode mode, QWidget *parent) : ui->passEdit1->installEventFilter(this); ui->passEdit2->installEventFilter(this); ui->passEdit3->installEventFilter(this); - ui->capsLabel->clear(); switch(mode) { @@ -99,7 +98,7 @@ void AskPassphraseDialog::accept() break; } QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm wallet encryption"), - tr("WARNING: If you encrypt your wallet and lose your passphrase, you will LOSE ALL OF YOUR BITCOINS!\nAre you sure you wish to encrypt your wallet?"), + tr("Warning: If you encrypt your wallet and lose your passphrase, you will LOSE ALL OF YOUR COINS!") + "

" + tr("Are you sure you wish to encrypt your wallet?"), QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Cancel); if(retval == QMessageBox::Yes) @@ -109,7 +108,16 @@ void AskPassphraseDialog::accept() if(model->setWalletEncrypted(true, newpass1)) { QMessageBox::warning(this, tr("Wallet encrypted"), - tr("Bitcoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer.")); + "" + + tr("NovaCoin will close now to finish the encryption process. " + "Remember that encrypting your wallet cannot fully protect " + "your coins from being stolen by malware infecting your computer.") + + "

" + + tr("IMPORTANT: Any previous backups you have made of your wallet file " + "should be replaced with the newly generated, encrypted wallet file. " + "For security reasons, previous backups of the unencrypted wallet file " + "will become useless as soon as you start using the new, encrypted wallet.") + + "
"); QApplication::quit(); } else @@ -158,7 +166,7 @@ void AskPassphraseDialog::accept() if(model->changePassphrase(oldpass, newpass1)) { QMessageBox::information(this, tr("Wallet encrypted"), - tr("Wallet passphrase was succesfully changed.")); + tr("Wallet passphrase was successfully changed.")); QDialog::accept(); // Success } else @@ -178,7 +186,7 @@ void AskPassphraseDialog::accept() void AskPassphraseDialog::textChanged() { - // Validate input, set Ok button to enabled when accepable + // Validate input, set Ok button to enabled when acceptable bool acceptable = false; switch(mode) { @@ -205,7 +213,7 @@ bool AskPassphraseDialog::event(QEvent *event) fCapsLock = !fCapsLock; } if (fCapsLock) { - ui->capsLabel->setText(tr("Warning: The Caps Lock key is on.")); + ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); } else { ui->capsLabel->clear(); } @@ -213,9 +221,9 @@ bool AskPassphraseDialog::event(QEvent *event) return QWidget::event(event); } -bool AskPassphraseDialog::eventFilter(QObject *, QEvent *event) +bool AskPassphraseDialog::eventFilter(QObject *object, QEvent *event) { - /* Detect Caps Lock. + /* Detect Caps Lock. * There is no good OS-independent way to check a key state in Qt, but we * can detect Caps Lock by checking for the following condition: * Shift key is down and the result is a lower case character, or @@ -229,12 +237,12 @@ bool AskPassphraseDialog::eventFilter(QObject *, QEvent *event) bool fShift = (ke->modifiers() & Qt::ShiftModifier) != 0; if ((fShift && psz->isLower()) || (!fShift && psz->isUpper())) { fCapsLock = true; - ui->capsLabel->setText(tr("Warning: The Caps Lock key is on.")); + ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); } else if (psz->isLetter()) { fCapsLock = false; ui->capsLabel->clear(); } } } - return false; + return QDialog::eventFilter(object, event); } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 59e9f6d..796b48a 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -6,6 +6,7 @@ #include "walletmodel.h" #include "optionsmodel.h" #include "guiutil.h" +#include "guiconstants.h" #include "init.h" #include "ui_interface.h" @@ -19,8 +20,6 @@ #include #include -#include - #if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED) #define _BITCOIN_QT_PLUGINS_INCLUDED #define __INSURE__ @@ -35,15 +34,13 @@ Q_IMPORT_PLUGIN(qtaccessiblewidgets) // Need a global reference for the notifications to find the GUI static BitcoinGUI *guiref; static QSplashScreen *splashref; -static WalletModel *walletmodel; -static ClientModel *clientmodel; -int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) +static void ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { // Message from network thread if(guiref) { - bool modal = (style & wxMODAL); + bool modal = (style & CClientUIInterface::MODAL); // in case of modal message, use blocking connection to wait for user to click OK QMetaObject::invokeMethod(guiref, "error", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, @@ -56,10 +53,9 @@ int ThreadSafeMessageBox(const std::string& message, const std::string& caption, printf("%s: %s\n", caption.c_str(), message.c_str()); fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); } - return 4; } -bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) +static bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) { if(!guiref) return false; @@ -74,7 +70,7 @@ bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) return payFee; } -void ThreadSafeHandleURI(const std::string& strURI) +static void ThreadSafeHandleURI(const std::string& strURI) { if(!guiref) return; @@ -83,21 +79,7 @@ void ThreadSafeHandleURI(const std::string& strURI) Q_ARG(QString, QString::fromStdString(strURI))); } -void MainFrameRepaint() -{ - if(clientmodel) - QMetaObject::invokeMethod(clientmodel, "update", Qt::QueuedConnection); - if(walletmodel) - QMetaObject::invokeMethod(walletmodel, "update", Qt::QueuedConnection); -} - -void AddressBookRepaint() -{ - if(walletmodel) - QMetaObject::invokeMethod(walletmodel, "updateAddressList", Qt::QueuedConnection); -} - -void InitMessage(const std::string &message) +static void InitMessage(const std::string &message) { if(splashref) { @@ -106,7 +88,7 @@ void InitMessage(const std::string &message) } } -void QueueShutdown() +static void QueueShutdown() { QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); } @@ -114,7 +96,7 @@ void QueueShutdown() /* Translate string to current locale using Qt. */ -std::string _(const char* psz) +static std::string Translate(const char* psz) { return QCoreApplication::translate("bitcoin-core", psz).toStdString(); } @@ -124,38 +106,15 @@ std::string _(const char* psz) static void handleRunawayException(std::exception *e) { PrintExceptionContinue(e, "Runaway exception"); - QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occured. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + QString::fromStdString(strMiscWarning)); + QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. NovaCoin can no longer continue safely and will quit.") + QString("\n\n") + QString::fromStdString(strMiscWarning)); exit(1); } -#ifdef WIN32 -#define strncasecmp strnicmp -#endif #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { -#if !defined(MAC_OSX) && !defined(WIN32) -// TODO: implement qtipcserver.cpp for Mac and Windows - // Do this early as we don't want to bother initializing if we are just calling IPC - for (int i = 1; i < argc; i++) - { - if (strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0) - { - const char *strURI = argv[i]; - try { - boost::interprocess::message_queue mq(boost::interprocess::open_only, BITCOINURI_QUEUE_NAME); - if(mq.try_send(strURI, strlen(strURI), 0)) - exit(0); - else - break; - } - catch (boost::interprocess::interprocess_exception &ex) { - break; - } - } - } -#endif + ipcScanRelay(argc, argv); // Internal string conversion is all UTF-8 QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); @@ -164,13 +123,19 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(bitcoin); QApplication app(argc, argv); + // Install global event filter that makes sure that long tooltips can be word-wrapped + app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); + // Command-line options take precedence: ParseParameters(argc, argv); // ... then bitcoin.conf: if (!boost::filesystem::is_directory(GetDataDir(false))) { - fprintf(stderr, "Error: Specified directory does not exist\n"); + // This message can not be translated, as translation is not initialized yet + // (which not yet possible because lang=XX can be overridden in bitcoin.conf in the data directory) + QMessageBox::critical(0, "NovaCoin", + QString("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"]))); return 1; } ReadConfigFile(mapArgs, mapMultiArgs); @@ -178,7 +143,7 @@ int main(int argc, char *argv[]) // Application identification (must be set before OptionsModel is initialized, // as it is used to locate QSettings) app.setOrganizationName("NovaCoin"); - app.setOrganizationDomain("novacoin.ru"); + app.setOrganizationDomain("novacoin.su"); if(GetBoolArg("-testnet")) // Separate UI settings for testnet app.setApplicationName("NovaCoin-Qt-testnet"); else @@ -187,32 +152,50 @@ int main(int argc, char *argv[]) // ... then GUI settings: OptionsModel optionsModel; - // Get desired locale ("en_US") from command line or system locale + // Get desired locale (e.g. "de_DE") from command line or use system locale QString lang_territory = QString::fromStdString(GetArg("-lang", QLocale::system().name().toStdString())); - // Load language files for configured locale: - // - First load the translator for the base language, without territory - // - Then load the more specific locale translator QString lang = lang_territory; + // Convert to "de" only by truncating "_DE" + lang.truncate(lang_territory.lastIndexOf('_')); - lang.truncate(lang_territory.lastIndexOf('_')); // "en" QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; + // Load language files for configured locale: + // - First load the translator for the base language, without territory + // - Then load the more specific locale translator - qtTranslatorBase.load(QLibraryInfo::location(QLibraryInfo::TranslationsPath) + "/qt_" + lang); - if (!qtTranslatorBase.isEmpty()) + // Load e.g. qt_de.qm + if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) app.installTranslator(&qtTranslatorBase); - qtTranslator.load(QLibraryInfo::location(QLibraryInfo::TranslationsPath) + "/qt_" + lang_territory); - if (!qtTranslator.isEmpty()) + // Load e.g. qt_de_DE.qm + if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) app.installTranslator(&qtTranslator); - translatorBase.load(":/translations/"+lang); - if (!translatorBase.isEmpty()) + // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc) + if (translatorBase.load(lang, ":/translations/")) app.installTranslator(&translatorBase); - translator.load(":/translations/"+lang_territory); - if (!translator.isEmpty()) + // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc) + if (translator.load(lang_territory, ":/translations/")) app.installTranslator(&translator); + // Subscribe to global signals from core + uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox); + uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee); + uiInterface.ThreadSafeHandleURI.connect(ThreadSafeHandleURI); + uiInterface.InitMessage.connect(InitMessage); + uiInterface.QueueShutdown.connect(QueueShutdown); + uiInterface.Translate.connect(Translate); + + // Show help message immediately after parsing command-line options (for "-lang") and setting locale, + // but before showing splash screen. + if (mapArgs.count("-?") || mapArgs.count("--help")) + { + GUIUtil::HelpMessageBox help; + help.showOrPrint(); + return 1; + } + QSplashScreen splash(QPixmap(":/images/splash"), 0); if (GetBoolArg("-splash", true) && !GetBoolArg("-min")) { @@ -227,9 +210,13 @@ int main(int argc, char *argv[]) try { + // Regenerate startup link, to fix links to old versions + if (GUIUtil::GetStartOnSystemStartup()) + GUIUtil::SetStartOnSystemStartup(true); + BitcoinGUI window; guiref = &window; - if(AppInit2(argc, argv)) + if(AppInit2()) { { // Put this in a block, so that the Model objects are cleaned up before @@ -241,9 +228,7 @@ int main(int argc, char *argv[]) splash.finish(&window); ClientModel clientModel(&optionsModel); - clientmodel = &clientModel; WalletModel walletModel(pwalletMain, &optionsModel); - walletmodel = &walletModel; window.setClientModel(&clientModel); window.setWalletModel(&walletModel); @@ -258,37 +243,17 @@ int main(int argc, char *argv[]) window.show(); } - // Place this here as guiref has to be defined if we dont want to lose URIs - ipcInit(); + // Place this here as guiref has to be defined if we don't want to lose URIs + ipcInit(argc, argv); -#if !defined(MAC_OSX) && !defined(WIN32) -// TODO: implement qtipcserver.cpp for Mac and Windows - - // Check for URI in argv - for (int i = 1; i < argc; i++) - { - if (strlen(argv[i]) > 7 && strncasecmp(argv[i], "bitcoin:", 8) == 0) - { - const char *strURI = argv[i]; - try { - boost::interprocess::message_queue mq(boost::interprocess::open_only, BITCOINURI_QUEUE_NAME); - mq.try_send(strURI, strlen(strURI), 0); - } - catch (boost::interprocess::interprocess_exception &ex) { - } - } - } -#endif app.exec(); window.hide(); window.setClientModel(0); window.setWalletModel(0); guiref = 0; - clientmodel = 0; - walletmodel = 0; } - // Shutdown the core and it's threads, but don't exit Bitcoin-Qt here + // Shutdown the core and its threads, but don't exit Bitcoin-Qt here Shutdown(NULL); } else diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index 0df1eab..f5ea25c 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -1,6 +1,6 @@ - res/icons/novacoin.png + res/icons/novacoin-128.png res/icons/address-book.png res/icons/quit.png res/icons/send.png @@ -22,8 +22,8 @@ res/icons/editpaste.png res/icons/editcopy.png res/icons/add.png - res/icons/bitcoin_testnet.png - res/icons/toolbar_testnet.png + res/icons/novacoin-128.png + res/icons/novacoin-16.png res/icons/edit.png res/icons/history.png res/icons/overview.png @@ -38,11 +38,12 @@ res/icons/lock_open.png res/icons/key.png res/icons/filesave.png + res/icons/qrcode.png + res/icons/debugwindow.png res/images/about.png res/images/splash2.jpg - res/images/qrcode.png res/movies/update_spinner.mng diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index c804ad0..5136ea0 100644 --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -5,8 +5,8 @@ This is: - All numbers except for '0' - - All uppercase letters except for 'I' and 'O' - - All lowercase letters except for 'l' + - All upper-case letters except for 'I' and 'O' + - All lower-case letters except for 'l' User friendly Base58 input can map - 'l' and 'I' to '1' @@ -25,17 +25,11 @@ QValidator::State BitcoinAddressValidator::validate(QString &input, int &pos) co { bool removeChar = false; QChar ch = input.at(idx); - // Transform characters that are visually close + // Corrections made are very conservative on purpose, to avoid + // users unexpectedly getting away with typos that would normally + // be detected, and thus sending to the wrong address. switch(ch.unicode()) { - case 'l': - case 'I': - input[idx] = QChar('1'); - break; - case '0': - case 'O': - input[idx] = QChar('o'); - break; // Qt categorizes these as "Other_Format" not "Separator_Space" case 0x200B: // ZERO WIDTH SPACE case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index ead8bdb..66792e0 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -31,7 +31,7 @@ public: /** Make field empty and ready for new input. */ void clear(); - /** Qt messes up the tab chain by default in some cases (issue http://bugreports.qt.nokia.com/browse/QTBUG-10907), + /** Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project.org/browse/QTBUG-10907), in these cases we have to set it up manually. */ QWidget *setupTabChain(QWidget *prev); @@ -40,7 +40,7 @@ signals: void textChanged(); protected: - /** Intercept focus-in event and ',' keypresses */ + /** Intercept focus-in event and ',' key presses */ bool eventFilter(QObject *object, QEvent *event); private: diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index d03e741..8aeab44 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1,16 +1,14 @@ /* - * Qt4 ppcoin GUI. + * Qt4 bitcoin GUI. * * W.J. van der Laan 2011-2012 * The Bitcoin Developers 2011-2012 - * The PPCoin Developers 2011-2012 - * The NovaCoin Developers 2011-2012 */ #include "bitcoingui.h" #include "transactiontablemodel.h" #include "addressbookpage.h" #include "sendcoinsdialog.h" -#include "messagepage.h" +#include "signverifymessagedialog.h" #include "optionsdialog.h" #include "aboutdialog.h" #include "clientmodel.h" @@ -26,8 +24,9 @@ #include "askpassphrasedialog.h" #include "notificator.h" #include "guiutil.h" +#include "rpcconsole.h" -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC #include "macdockiconhandler.h" #endif @@ -52,9 +51,9 @@ #include #include #include - #include #include +#include #include @@ -66,11 +65,13 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): changePassphraseAction(0), aboutQtAction(0), trayIcon(0), - notificator(0) + notificator(0), + rpcConsole(0) { resize(850, 550); - setWindowTitle(tr("NovaCoin Wallet")); -#ifndef Q_WS_MAC + setWindowTitle(tr("NovaCoin") + " - " + tr("Wallet")); +#ifndef Q_OS_MAC + qApp->setWindowIcon(QIcon(":icons/bitcoin")); setWindowIcon(QIcon(":icons/bitcoin")); #else setUnifiedTitleAndToolBarOnMac(true); @@ -106,7 +107,7 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): sendCoinsPage = new SendCoinsDialog(this); - messagePage = new MessagePage(this); + signVerifyMessageDialog = new SignVerifyMessageDialog(this); centralWidget = new QStackedWidget(this); centralWidget->addWidget(overviewPage); @@ -114,9 +115,6 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): centralWidget->addWidget(addressBookPage); centralWidget->addWidget(receiveCoinsPage); centralWidget->addWidget(sendCoinsPage); -#ifdef FIRST_CLASS_MESSAGING - centralWidget->addWidget(messagePage); -#endif setCentralWidget(centralWidget); // Create status bar @@ -148,6 +146,15 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): progressBar->setAlignment(Qt::AlignCenter); progressBar->setVisible(false); + // Override style sheet for progress bar for styles that have a segmented progress bar, + // as they make the text unreadable (workaround for issue #1071) + // See https://qt-project.org/doc/qt-4.8/gallery.html + QString curStyle = qApp->style()->metaObject()->className(); + if(curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle") + { + progressBar->setStyleSheet("QProgressBar { background-color: #e8e8e8; border: 1px solid grey; border-radius: 7px; padding: 1px; text-align: center; } QProgressBar::chunk { background: QLinearGradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #FF8000, stop: 1 orange); border-radius: 7px; margin: 0px; }"); + } + statusBar()->addWidget(progressBarLabel); statusBar()->addWidget(progressBar); statusBar()->addPermanentWidget(frameBlocks); @@ -156,16 +163,27 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): // Clicking on a transaction on the overview page simply sends you to transaction history page connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage())); + connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex))); - // Doubleclicking on a transaction on the transaction history page shows details + // Double-clicking on a transaction on the transaction history page shows details connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails())); + rpcConsole = new RPCConsole(this); + connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show())); + + // Clicking on "Verify Message" in the address book sends you to the verify message tab + connect(addressBookPage, SIGNAL(verifyMessage(QString)), this, SLOT(gotoVerifyMessageTab(QString))); + // Clicking on "Sign Message" in the receive coins page sends you to the sign message tab + connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString))); + gotoOverviewPage(); } BitcoinGUI::~BitcoinGUI() { -#ifdef Q_WS_MAC + if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) + trayIcon->hide(); +#ifdef Q_OS_MAC delete appMenuBar; #endif } @@ -180,6 +198,18 @@ void BitcoinGUI::createActions() overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); tabGroup->addAction(overviewAction); + sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this); + sendCoinsAction->setToolTip(tr("Send coins to a NovaCoin address")); + sendCoinsAction->setCheckable(true); + sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); + tabGroup->addAction(sendCoinsAction); + + receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this); + receiveCoinsAction->setToolTip(tr("Show the list of addresses for receiving payments")); + receiveCoinsAction->setCheckable(true); + receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); + tabGroup->addAction(receiveCoinsAction); + historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this); historyAction->setToolTip(tr("Browse transaction history")); historyAction->setCheckable(true); @@ -192,76 +222,61 @@ void BitcoinGUI::createActions() addressBookAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5)); tabGroup->addAction(addressBookAction); - receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this); - receiveCoinsAction->setToolTip(tr("Show the list of addresses for receiving payments")); - receiveCoinsAction->setCheckable(true); - receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); - tabGroup->addAction(receiveCoinsAction); - - sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this); - sendCoinsAction->setToolTip(tr("Send coins to a NovaCoin address")); - sendCoinsAction->setCheckable(true); - sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); - tabGroup->addAction(sendCoinsAction); - - messageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message"), this); - messageAction->setToolTip(tr("Prove you control an address")); -#ifdef FIRST_CLASS_MESSAGING - messageAction->setCheckable(true); -#endif - tabGroup->addAction(messageAction); - connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage())); + connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); + connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); connect(addressBookAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage())); - connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); - connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); - connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); - connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); - connect(messageAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); - connect(messageAction, SIGNAL(triggered()), this, SLOT(gotoMessagePage())); quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this); quitAction->setToolTip(tr("Quit application")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); quitAction->setMenuRole(QAction::QuitRole); - aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About %1").arg(qApp->applicationName()), this); + aboutAction = new QAction(QIcon(":/icons/bitcoin"), tr("&About NovaCoin"), this); aboutAction->setToolTip(tr("Show information about NovaCoin")); aboutAction->setMenuRole(QAction::AboutRole); - aboutQtAction = new QAction(tr("About &Qt"), this); + aboutQtAction = new QAction(QIcon(":/trolltech/qmessagebox/images/qtlogo-64.png"), tr("About &Qt"), this); aboutQtAction->setToolTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this); optionsAction->setToolTip(tr("Modify configuration options for NovaCoin")); optionsAction->setMenuRole(QAction::PreferencesRole); - toggleHideAction = new QAction(QIcon(":/icons/bitcoin"), tr("Show/Hide &NovaCoin"), this); - toggleHideAction->setToolTip(tr("Show or hide the NovaCoin window")); - exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this); - exportAction->setToolTip(tr("Export the data in the current tab to a file")); - encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet"), this); + toggleHideAction = new QAction(QIcon(":/icons/bitcoin"), tr("&Show / Hide"), this); + encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this); encryptWalletAction->setToolTip(tr("Encrypt or decrypt wallet")); encryptWalletAction->setCheckable(true); - backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet"), this); + backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet..."), this); backupWalletAction->setToolTip(tr("Backup wallet to another location")); - changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase"), this); + changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase..."), this); changePassphraseAction->setToolTip(tr("Change the passphrase used for wallet encryption")); + signMessageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message..."), this); + verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this); + + exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this); + exportAction->setToolTip(tr("Export the data in the current tab to a file")); + openRPCConsoleAction = new QAction(QIcon(":/icons/debugwindow"), tr("&Debug window"), this); + openRPCConsoleAction->setToolTip(tr("Open debugging and diagnostic console")); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); - connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked())); connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked())); connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); + connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked())); connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden())); connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool))); connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet())); connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase())); + connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab())); + connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab())); } void BitcoinGUI::createMenuBar() { -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC // Create a decoupled menu bar on Mac which stays even if the window is closed appMenuBar = new QMenuBar(); #else @@ -273,9 +288,8 @@ void BitcoinGUI::createMenuBar() QMenu *file = appMenuBar->addMenu(tr("&File")); file->addAction(backupWalletAction); file->addAction(exportAction); -#ifndef FIRST_CLASS_MESSAGING - file->addAction(messageAction); -#endif + file->addAction(signMessageAction); + file->addAction(verifyMessageAction); file->addSeparator(); file->addAction(quitAction); @@ -286,6 +300,8 @@ void BitcoinGUI::createMenuBar() settings->addAction(optionsAction); QMenu *help = appMenuBar->addMenu(tr("&Help")); + help->addAction(openRPCConsoleAction); + help->addSeparator(); help->addAction(aboutAction); help->addAction(aboutQtAction); } @@ -299,9 +315,6 @@ void BitcoinGUI::createToolBars() toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); toolbar->addAction(addressBookAction); -#ifdef FIRST_CLASS_MESSAGING - toolbar->addAction(messageAction); -#endif QToolBar *toolbar2 = addToolBar(tr("Actions toolbar")); toolbar2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); @@ -313,31 +326,39 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) this->clientModel = clientModel; if(clientModel) { + // Replace some strings and icons, when using the testnet if(clientModel->isTestNet()) { - QString title_testnet = windowTitle() + QString(" ") + tr("[testnet]"); - setWindowTitle(title_testnet); -#ifndef Q_WS_MAC + setWindowTitle(windowTitle() + QString(" ") + tr("[testnet]")); +#ifndef Q_OS_MAC + qApp->setWindowIcon(QIcon(":icons/bitcoin_testnet")); setWindowIcon(QIcon(":icons/bitcoin_testnet")); #else MacDockIconHandler::instance()->setIcon(QIcon(":icons/bitcoin_testnet")); #endif if(trayIcon) { - trayIcon->setToolTip(title_testnet); + trayIcon->setToolTip(tr("NovaCoin client") + QString(" ") + tr("[testnet]")); trayIcon->setIcon(QIcon(":/icons/toolbar_testnet")); + toggleHideAction->setIcon(QIcon(":/icons/toolbar_testnet")); } + + aboutAction->setIcon(QIcon(":/icons/toolbar_testnet")); } // Keep up to date with client setNumConnections(clientModel->getNumConnections()); connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); - setNumBlocks(clientModel->getNumBlocks()); - connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int))); + setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers()); + connect(clientModel, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); // Report errors from network/worker thread - connect(clientModel, SIGNAL(error(QString,QString, bool)), this, SLOT(error(QString,QString,bool))); + connect(clientModel, SIGNAL(error(QString,QString,bool)), this, SLOT(error(QString,QString,bool))); + + rpcConsole->setClientModel(clientModel); + addressBookPage->setOptionsModel(clientModel->getOptionsModel()); + receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel()); } } @@ -356,12 +377,12 @@ void BitcoinGUI::setWalletModel(WalletModel *walletModel) addressBookPage->setModel(walletModel->getAddressTableModel()); receiveCoinsPage->setModel(walletModel->getAddressTableModel()); sendCoinsPage->setModel(walletModel); - messagePage->setModel(walletModel); + signVerifyMessageDialog->setModel(walletModel); setEncryptionStatus(walletModel->getEncryptionStatus()); connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int))); - // Balloon popup for new transaction + // Balloon pop-up for new transaction connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(incomingTransaction(QModelIndex,int,int))); @@ -373,7 +394,7 @@ void BitcoinGUI::setWalletModel(WalletModel *walletModel) void BitcoinGUI::createTrayIcon() { QMenu *trayIconMenu; -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC trayIcon = new QSystemTrayIcon(this); trayIconMenu = new QMenu(this); trayIcon->setContextMenu(trayIconMenu); @@ -391,55 +412,33 @@ void BitcoinGUI::createTrayIcon() // Configuration of the tray icon (or dock icon) icon menu trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); - trayIconMenu->addAction(messageAction); -#ifndef FIRST_CLASS_MESSAGING - trayIconMenu->addSeparator(); -#endif - trayIconMenu->addAction(receiveCoinsAction); trayIconMenu->addAction(sendCoinsAction); + trayIconMenu->addAction(receiveCoinsAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(signMessageAction); + trayIconMenu->addAction(verifyMessageAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(optionsAction); -#ifndef Q_WS_MAC // This is built-in on Mac + trayIconMenu->addAction(openRPCConsoleAction); +#ifndef Q_OS_MAC // This is built-in on Mac trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif - notificator = new Notificator(tr("novacoin-qt"), trayIcon); + notificator = new Notificator(qApp->applicationName(), trayIcon); } -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { if(reason == QSystemTrayIcon::Trigger) { - // Click on system tray icon triggers "show/hide bitcoin" + // Click on system tray icon triggers show/hide of the main window toggleHideAction->trigger(); } } #endif -void BitcoinGUI::toggleHidden() -{ - // activateWindow() (sometimes) helps with keyboard focus on Windows - if (isHidden()) - { - show(); - activateWindow(); - } - else if (isMinimized()) - { - showNormal(); - activateWindow(); - } - else if (GUIUtil::isObscured(this)) - { - raise(); - activateWindow(); - } - else - hide(); -} - void BitcoinGUI::optionsClicked() { if(!clientModel || !clientModel->getOptionsModel()) @@ -471,9 +470,9 @@ void BitcoinGUI::setNumConnections(int count) labelConnectionsIcon->setToolTip(tr("%n active connection(s) to NovaCoin network", "", count)); } -void BitcoinGUI::setNumBlocks(int count) +void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks) { - // don't show / hide progressBar and it's label if we have no connection(s) to the network + // don't show / hide progress bar and its label if we have no connection to the network if (!clientModel || clientModel->getNumConnections() == 0) { progressBarLabel->setVisible(false); @@ -482,7 +481,7 @@ void BitcoinGUI::setNumBlocks(int count) return; } - int nTotalBlocks = clientModel->getNumBlocksOfPeers(); + QString strStatusBarWarnings = clientModel->getStatusBarWarnings(); QString tooltip; if(count < nTotalBlocks) @@ -490,7 +489,7 @@ void BitcoinGUI::setNumBlocks(int count) int nRemainingBlocks = nTotalBlocks - count; float nPercentageDone = count / (nTotalBlocks * 0.01f); - if (clientModel->getStatusBarWarnings() == "") + if (strStatusBarWarnings.isEmpty()) { progressBarLabel->setText(tr("Synchronizing with network...")); progressBarLabel->setVisible(true); @@ -499,30 +498,28 @@ void BitcoinGUI::setNumBlocks(int count) progressBar->setValue(count); progressBar->setVisible(true); } - else - { - progressBarLabel->setText(clientModel->getStatusBarWarnings()); - progressBarLabel->setVisible(true); - progressBar->setVisible(false); - } + tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2); } else { - if (clientModel->getStatusBarWarnings() == "") + if (strStatusBarWarnings.isEmpty()) progressBarLabel->setVisible(false); - else - { - progressBarLabel->setText(clientModel->getStatusBarWarnings()); - progressBarLabel->setVisible(true); - } + progressBar->setVisible(false); tooltip = tr("Downloaded %1 blocks of transaction history.").arg(count); } - QDateTime now = QDateTime::currentDateTime(); + // Override progressBarLabel text and hide progress bar, when we have warnings to display + if (!strStatusBarWarnings.isEmpty()) + { + progressBarLabel->setText(strStatusBarWarnings); + progressBarLabel->setVisible(true); + progressBar->setVisible(false); + } + QDateTime lastBlockDate = clientModel->getLastBlockDate(); - int secs = lastBlockDate.secsTo(now); + int secs = lastBlockDate.secsTo(QDateTime::currentDateTime()); QString text; // Represent time from last generated block in human readable text @@ -550,22 +547,29 @@ void BitcoinGUI::setNumBlocks(int count) // Set icon state: spinning if catching up, tick otherwise if(secs < 90*60 && count >= nTotalBlocks) { - tooltip = tr("Up to date") + QString(".\n") + tooltip; + tooltip = tr("Up to date") + QString(".
") + tooltip; labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); + + overviewPage->showOutOfSyncWarning(false); } else { - tooltip = tr("Catching up...") + QString("\n") + tooltip; + tooltip = tr("Catching up...") + QString("
") + tooltip; labelBlocksIcon->setMovie(syncIconMovie); syncIconMovie->start(); + + overviewPage->showOutOfSyncWarning(true); } if(!text.isEmpty()) { - tooltip += QString("\n"); + tooltip += QString("
"); tooltip += tr("Last received block was generated %1.").arg(text); } + // Don't word-wrap this (fixed-width) tooltip + tooltip = QString("") + tooltip + QString(""); + labelBlocksIcon->setToolTip(tooltip); progressBarLabel->setToolTip(tooltip); progressBar->setToolTip(tooltip); @@ -585,7 +589,7 @@ void BitcoinGUI::error(const QString &title, const QString &message, bool modal) void BitcoinGUI::changeEvent(QEvent *e) { QMainWindow::changeEvent(e); -#ifndef Q_WS_MAC // Ignored on Mac +#ifndef Q_OS_MAC // Ignored on Mac if(e->type() == QEvent::WindowStateChange) { if(clientModel && clientModel->getOptionsModel()->getMinimizeToTray()) @@ -605,7 +609,7 @@ void BitcoinGUI::closeEvent(QCloseEvent *event) { if(clientModel) { -#ifndef Q_WS_MAC // Ignored on Mac +#ifndef Q_OS_MAC // Ignored on Mac if(!clientModel->getOptionsModel()->getMinimizeToTray() && !clientModel->getOptionsModel()->getMinimizeOnClose()) { @@ -624,7 +628,7 @@ void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee) "Do you want to pay the fee?").arg( BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nFeeRequired)); QMessageBox::StandardButton retval = QMessageBox::question( - this, tr("Sending..."), strMessage, + this, tr("Confirm transaction fee"), strMessage, QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes); *payFee = (retval == QMessageBox::Yes); } @@ -712,24 +716,22 @@ void BitcoinGUI::gotoSendCoinsPage() disconnect(exportAction, SIGNAL(triggered()), 0, 0); } -void BitcoinGUI::gotoMessagePage() +void BitcoinGUI::gotoSignMessageTab(QString addr) { -#ifdef FIRST_CLASS_MESSAGING - messageAction->setChecked(true); - centralWidget->setCurrentWidget(messagePage); + // call show() in showTab_SM() + signVerifyMessageDialog->showTab_SM(true); - exportAction->setEnabled(false); - disconnect(exportAction, SIGNAL(triggered()), 0, 0); -#else - messagePage->show(); - messagePage->setFocus(); -#endif + if(!addr.isEmpty()) + signVerifyMessageDialog->setAddress_SM(addr); } -void BitcoinGUI::gotoMessagePage(QString addr) +void BitcoinGUI::gotoVerifyMessageTab(QString addr) { - gotoMessagePage(); - messagePage->setAddress(addr); + // call show() in showTab_VM() + signVerifyMessageDialog->showTab_VM(true); + + if(!addr.isEmpty()) + signVerifyMessageDialog->setAddress_VM(addr); } void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) @@ -743,12 +745,19 @@ void BitcoinGUI::dropEvent(QDropEvent *event) { if(event->mimeData()->hasUrls()) { - gotoSendCoinsPage(); + int nValidUrisFound = 0; QList uris = event->mimeData()->urls(); foreach(const QUrl &uri, uris) { - sendCoinsPage->handleURI(uri.toString()); + if (sendCoinsPage->handleURI(uri.toString())) + nValidUrisFound++; } + + // if valid URIs were found + if (nValidUrisFound) + gotoSendCoinsPage(); + else + notificator->notify(Notificator::Warning, tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid NovaCoin address or malformed URI parameters.")); } event->acceptProposedAction(); @@ -756,13 +765,14 @@ void BitcoinGUI::dropEvent(QDropEvent *event) void BitcoinGUI::handleURI(QString strURI) { - gotoSendCoinsPage(); - sendCoinsPage->handleURI(strURI); - - if(!isActiveWindow()) - activateWindow(); - - showNormalIfMinimized(); + // URI has to be valid + if (sendCoinsPage->handleURI(strURI)) + { + showNormalIfMinimized(); + gotoSendCoinsPage(); + } + else + notificator->notify(Notificator::Warning, tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid NovaCoin address or malformed URI parameters.")); } void BitcoinGUI::setEncryptionStatus(int status) @@ -837,10 +847,29 @@ void BitcoinGUI::unlockWallet() } } -void BitcoinGUI::showNormalIfMinimized() +void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) { - if(!isVisible()) // Show, if hidden + // activateWindow() (sometimes) helps with keyboard focus on Windows + if (isHidden()) + { show(); - if(isMinimized()) // Unminimize, if minimized + activateWindow(); + } + else if (isMinimized()) + { showNormal(); + activateWindow(); + } + else if (GUIUtil::isObscured(this)) + { + raise(); + activateWindow(); + } + else if(fToggleHidden) + hide(); +} + +void BitcoinGUI::toggleHidden() +{ + showNormalIfMinimized(true); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 2cce8d3..c67e887 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -11,8 +11,9 @@ class TransactionView; class OverviewPage; class AddressBookPage; class SendCoinsDialog; -class MessagePage; +class SignVerifyMessageDialog; class Notificator; +class RPCConsole; QT_BEGIN_NAMESPACE class QLabel; @@ -45,7 +46,7 @@ public: functionality. */ void setWalletModel(WalletModel *walletModel); - + protected: void changeEvent(QEvent *e); void closeEvent(QCloseEvent *event); @@ -63,7 +64,7 @@ private: AddressBookPage *addressBookPage; AddressBookPage *receiveCoinsPage; SendCoinsDialog *sendCoinsPage; - MessagePage *messagePage; + SignVerifyMessageDialog *signVerifyMessageDialog; QLabel *labelEncryptionIcon; QLabel *labelConnectionsIcon; @@ -77,7 +78,8 @@ private: QAction *quitAction; QAction *sendCoinsAction; QAction *addressBookAction; - QAction *messageAction; + QAction *signMessageAction; + QAction *verifyMessageAction; QAction *aboutAction; QAction *receiveCoinsAction; QAction *optionsAction; @@ -87,16 +89,18 @@ private: QAction *backupWalletAction; QAction *changePassphraseAction; QAction *aboutQtAction; + QAction *openRPCConsoleAction; QSystemTrayIcon *trayIcon; Notificator *notificator; TransactionView *transactionView; + RPCConsole *rpcConsole; QMovie *syncIconMovie; /** Create the main UI actions. */ void createActions(); - /** Create the menu bar and submenus. */ + /** Create the menu bar and sub-menus. */ void createMenuBar(); /** Create the toolbars */ void createToolBars(); @@ -107,7 +111,7 @@ public slots: /** Set number of connections shown in the UI */ void setNumConnections(int count); /** Set number of blocks shown in the UI */ - void setNumBlocks(int count); + void setNumBlocks(int count, int nTotalBlocks); /** Set the encryption status as shown in the UI. @param[in] status current encryption status @see WalletModel::EncryptionStatus @@ -119,7 +123,7 @@ public slots: /** Asks the user whether to pay the transaction fee or to cancel the transaction. It is currently not possible to pass a return value to another thread through BlockingQueuedConnection, so an indirected pointer is used. - http://bugreports.qt.nokia.com/browse/QTBUG-10440 + https://bugreports.qt-project.org/browse/QTBUG-10440 @param[in] nFeeRequired the required fee @param[out] payFee true to pay the fee, false to not pay the fee @@ -127,9 +131,6 @@ public slots: void askFee(qint64 nFeeRequired, bool *payFee); void handleURI(QString strURI); - void gotoMessagePage(); - void gotoMessagePage(QString); - private slots: /** Switch to overview (home) page */ void gotoOverviewPage(); @@ -142,11 +143,16 @@ private slots: /** Switch to send coins page */ void gotoSendCoinsPage(); + /** Show Sign/Verify Message dialog and switch to sign message tab */ + void gotoSignMessageTab(QString addr = ""); + /** Show Sign/Verify Message dialog and switch to verify message tab */ + void gotoVerifyMessageTab(QString addr = ""); + /** Show configuration dialog */ void optionsClicked(); /** Show about dialog */ void aboutClicked(); -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC /** Handle tray icon clicked */ void trayIconActivated(QSystemTrayIcon::ActivationReason reason); #endif @@ -161,12 +167,12 @@ private slots: void backupWallet(); /** Change encrypted wallet passphrase */ void changePassphrase(); - /** Ask for pass phrase to unlock wallet temporarily */ + /** Ask for passphrase to unlock wallet temporarily */ void unlockWallet(); - /** Show window if hidden, unminimize when minimized */ - void showNormalIfMinimized(); - /** Hide window if visible, show if hidden */ + /** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */ + void showNormalIfMinimized(bool fToggleHidden = false); + /** simply calls showNormalIfMinimized(true) for use in SLOT() macro */ void toggleHidden(); }; diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index 696e347..e96d8dd 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -5,130 +5,173 @@ #else #define UNUSED #endif -static const char UNUSED *bitcoin_strings[] = {QT_TRANSLATE_NOOP("bitcoin-core", "" -"Unable to bind to port %d on this computer. NovaCoin is probably already " +static const char UNUSED *bitcoin_strings[] = { +QT_TRANSLATE_NOOP("bitcoin-core", "" +"%s, you must set a rpcpassword in the configuration file:\n" +" %s\n" +"It is recommended you use the following random password:\n" +"rpcuser=bitcoinrpc\n" +"rpcpassword=%s\n" +"(you do not need to remember this password)\n" +"If the file does not exist, create it with owner-readable-only file " +"permissions.\n"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:" +"@STRENGTH)"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"An error occurred while setting up the RPC port %u for listening on IPv4: %s"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"An error occurred while setting up the RPC port %u for listening on IPv6, " +"falling back to IPv4: %s"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Cannot obtain a lock on data directory %s. NovaCoin is probably already " "running."), -QT_TRANSLATE_NOOP("bitcoin-core", "Warning: Disk space is low "), -QT_TRANSLATE_NOOP("bitcoin-core", "NovaCoin version"), -QT_TRANSLATE_NOOP("bitcoin-core", "Usage:"), -QT_TRANSLATE_NOOP("bitcoin-core", "Send command to -server or bitcoind"), -QT_TRANSLATE_NOOP("bitcoin-core", "List commands"), -QT_TRANSLATE_NOOP("bitcoin-core", "Get help for a command"), -QT_TRANSLATE_NOOP("bitcoin-core", "Options:"), -QT_TRANSLATE_NOOP("bitcoin-core", "Specify configuration file (default: bitcoin.conf)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Specify pid file (default: bitcoind.pid)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Generate coins"), -QT_TRANSLATE_NOOP("bitcoin-core", "Don't generate coins"), -QT_TRANSLATE_NOOP("bitcoin-core", "Start minimized"), -QT_TRANSLATE_NOOP("bitcoin-core", "Show splash screen on startup (default: 1)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Specify data directory"), -QT_TRANSLATE_NOOP("bitcoin-core", "Set database cache size in megabytes (default: 25)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Set database disk log size in megabytes (default: 100)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Specify connection timeout (in milliseconds)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Connect through socks4 proxy"), -QT_TRANSLATE_NOOP("bitcoin-core", "Allow DNS lookups for addnode and connect"), -QT_TRANSLATE_NOOP("bitcoin-core", "Listen for connections on (default: 8333 or testnet: 18333)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Maintain at most connections to peers (default: 125)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Add a node to connect to and attempt to keep the connection open"), -QT_TRANSLATE_NOOP("bitcoin-core", "Connect only to the specified node"), -QT_TRANSLATE_NOOP("bitcoin-core", "Find peers using internet relay chat (default: 0)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Accept connections from outside (default: 1)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Set language, for example \"de_DE\" (default: system locale)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Find peers using DNS lookup (default: 1)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Threshold for disconnecting misbehaving peers (default: 100)"), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Number of seconds to keep misbehaving peers from reconnecting (default: " -"86400)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Maximum per-connection receive buffer, *1000 bytes (default: 10000)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Maximum per-connection send buffer, *1000 bytes (default: 10000)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Use Universal Plug and Play to map the listening port (default: 1)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Use Universal Plug and Play to map the listening port (default: 0)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Fee per KB to add to transactions you send"), -QT_TRANSLATE_NOOP("bitcoin-core", "Accept command line and JSON-RPC commands"), -QT_TRANSLATE_NOOP("bitcoin-core", "Run in the background as a daemon and accept commands"), -QT_TRANSLATE_NOOP("bitcoin-core", "Use the test network"), -QT_TRANSLATE_NOOP("bitcoin-core", "Output extra debugging information"), -QT_TRANSLATE_NOOP("bitcoin-core", "Prepend debug output with timestamp"), -QT_TRANSLATE_NOOP("bitcoin-core", "Send trace/debug info to console instead of debug.log file"), -QT_TRANSLATE_NOOP("bitcoin-core", "Send trace/debug info to debugger"), -QT_TRANSLATE_NOOP("bitcoin-core", "Username for JSON-RPC connections"), -QT_TRANSLATE_NOOP("bitcoin-core", "Password for JSON-RPC connections"), -QT_TRANSLATE_NOOP("bitcoin-core", "Listen for JSON-RPC connections on (default: 8332)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Allow JSON-RPC connections from specified IP address"), -QT_TRANSLATE_NOOP("bitcoin-core", "Send commands to node running on (default: 127.0.0.1)"), +"Detach block and address databases. Increases shutdown time (default: 0)"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error initializing database environment %s! To recover, BACKUP THAT " +"DIRECTORY, then remove everything from it except for wallet.dat."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: The transaction was rejected. This might happen if some of the coins " +"in your wallet were already spent, such as if you used a copy of wallet.dat " +"and coins were spent in the copy but not marked as spent here."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: This transaction requires a transaction fee of at least %s because of " +"its amount, complexity, or use of recently received funds "), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Wallet unlocked for block minting only, unable to create transaction."), QT_TRANSLATE_NOOP("bitcoin-core", "" "Execute command when the best block changes (%s in cmd is replaced by block " "hash)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Upgrade wallet to latest format"), -QT_TRANSLATE_NOOP("bitcoin-core", "Set key pool size to (default: 100)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Rescan the block chain for missing wallet transactions"), -QT_TRANSLATE_NOOP("bitcoin-core", "How many blocks to check at startup (default: 2500, 0 = all)"), -QT_TRANSLATE_NOOP("bitcoin-core", "How thorough the block verification is (0-6, default: 1)"), QT_TRANSLATE_NOOP("bitcoin-core", "" -"\n" -"SSL options: (see the Bitcoin Wiki for SSL setup instructions)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Use OpenSSL (https) for JSON-RPC connections"), -QT_TRANSLATE_NOOP("bitcoin-core", "Server certificate file (default: server.cert)"), -QT_TRANSLATE_NOOP("bitcoin-core", "Server private key (default: server.pem)"), +"Listen for JSON-RPC connections on (default: 8344 or testnet: 18344)"), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:" -"@STRENGTH)"), -QT_TRANSLATE_NOOP("bitcoin-core", "This help message"), -QT_TRANSLATE_NOOP("bitcoin-core", "Usage"), +"Number of seconds to keep misbehaving peers from reconnecting (default: " +"86400)"), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Cannot obtain a lock on data directory %s. NovaCoin is probably already " -"running."), -QT_TRANSLATE_NOOP("bitcoin-core", "NovaCoin"), -QT_TRANSLATE_NOOP("bitcoin-core", "Loading addresses..."), -QT_TRANSLATE_NOOP("bitcoin-core", "Error loading addr.dat"), -QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index..."), -QT_TRANSLATE_NOOP("bitcoin-core", "Error loading blkindex.dat"), -QT_TRANSLATE_NOOP("bitcoin-core", "Loading wallet..."), -QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat: Wallet corrupted"), -QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat: Wallet requires newer version of Bitcoin"), -QT_TRANSLATE_NOOP("bitcoin-core", "Wallet needed to be rewritten: restart Bitcoin to complete"), -QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat"), -QT_TRANSLATE_NOOP("bitcoin-core", "Cannot downgrade wallet"), -QT_TRANSLATE_NOOP("bitcoin-core", "Cannot initialize keypool"), -QT_TRANSLATE_NOOP("bitcoin-core", "Cannot write default address"), -QT_TRANSLATE_NOOP("bitcoin-core", "Rescanning..."), -QT_TRANSLATE_NOOP("bitcoin-core", "Done loading"), -QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -proxy address"), -QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -paytxfee="), +"Set maximum size of high-priority/low-fee transactions in bytes (default: " +"27000)"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Unable to bind to %s on this computer. NovaCoin is probably already running."), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Warning: -paytxfee is set very high. This is the transaction fee you will " +"Warning: -paytxfee is set very high! This is the transaction fee you will " "pay if you send a transaction."), -QT_TRANSLATE_NOOP("bitcoin-core", "Error: CreateThread(StartNode) failed"), -QT_TRANSLATE_NOOP("bitcoin-core", "To use the %s option"), QT_TRANSLATE_NOOP("bitcoin-core", "" -"%s, you must set a rpcpassword in the configuration file:\n" -" %s\n" -"It is recommended you use the following random password:\n" -"rpcuser=bitcoinrpc\n" -"rpcpassword=%s\n" -"(you do not need to remember this password)\n" -"If the file does not exist, create it with owner-readable-only file " -"permissions.\n"), -QT_TRANSLATE_NOOP("bitcoin-core", "Error"), -QT_TRANSLATE_NOOP("bitcoin-core", "An error occured while setting up the RPC port %i for listening: %s"), +"Warning: Please check that your computer's date and time are correct! If " +"your clock is wrong NovaCoin will not work properly."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Warning: error reading wallet.dat! All keys read correctly, but transaction " +"data or address book entries might be missing or incorrect."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Warning: wallet.dat corrupt, data salvaged! Original wallet.dat saved as " +"wallet.{timestamp}.bak in %s; if your balance or transactions are incorrect " +"you should restore from a backup."), QT_TRANSLATE_NOOP("bitcoin-core", "" "You must set rpcpassword= in the configuration file:\n" "%s\n" "If the file does not exist, create it with owner-readable-only file " "permissions."), -QT_TRANSLATE_NOOP("bitcoin-core", "" -"Warning: Please check that your computer's date and time are correct. If " -"your clock is wrong Bitcoin will not work properly."), -QT_TRANSLATE_NOOP("bitcoin-core", "Error: Wallet locked, unable to create transaction "), -QT_TRANSLATE_NOOP("bitcoin-core", "" -"Error: This transaction requires a transaction fee of at least %s because of " -"its amount, complexity, or use of recently received funds "), +QT_TRANSLATE_NOOP("bitcoin-core", "Accept command line and JSON-RPC commands"), +QT_TRANSLATE_NOOP("bitcoin-core", "Accept connections from outside (default: 1 if no -proxy or -connect)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Add a node to connect to and attempt to keep the connection open"), +QT_TRANSLATE_NOOP("bitcoin-core", "Allow DNS lookups for -addnode, -seednode and -connect"), +QT_TRANSLATE_NOOP("bitcoin-core", "Allow JSON-RPC connections from specified IP address"), +QT_TRANSLATE_NOOP("bitcoin-core", "Attempt to recover private keys from a corrupt wallet.dat"), +QT_TRANSLATE_NOOP("bitcoin-core", "Bind to given address. Use [host]:port notation for IPv6"), +QT_TRANSLATE_NOOP("bitcoin-core", "Block creation options:"), +QT_TRANSLATE_NOOP("bitcoin-core", "Cannot downgrade wallet"), +QT_TRANSLATE_NOOP("bitcoin-core", "Cannot initialize keypool"), +QT_TRANSLATE_NOOP("bitcoin-core", "Cannot resolve -bind address: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Cannot resolve -externalip address: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Cannot write default address"), +QT_TRANSLATE_NOOP("bitcoin-core", "Connect only to the specified node(s)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Connect through socks proxy"), +QT_TRANSLATE_NOOP("bitcoin-core", "Connect to a node to retrieve peer addresses, and disconnect"), +QT_TRANSLATE_NOOP("bitcoin-core", "Discover own IP address (default: 1 when listening and no -externalip)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Don't generate coins"), +QT_TRANSLATE_NOOP("bitcoin-core", "Done loading"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error loading blkindex.dat"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat: Wallet corrupted"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet.dat: Wallet requires newer version of NovaCoin"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Transaction creation failed "), -QT_TRANSLATE_NOOP("bitcoin-core", "Sending..."), -QT_TRANSLATE_NOOP("bitcoin-core", "" -"Error: The transaction was rejected. This might happen if some of the coins " -"in your wallet were already spent, such as if you used a copy of wallet.dat " -"and coins were spent in the copy but not marked as spent here."), -QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Wallet locked, unable to create transaction "), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: could not start node"), +QT_TRANSLATE_NOOP("bitcoin-core", "Failed to listen on any port. Use -listen=0 if you want this."), +QT_TRANSLATE_NOOP("bitcoin-core", "Fee per KB to add to transactions you send"), +QT_TRANSLATE_NOOP("bitcoin-core", "Find peers using DNS lookup (default: 0)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Find peers using internet relay chat (default: 1)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Generate coins"), +QT_TRANSLATE_NOOP("bitcoin-core", "Get help for a command"), +QT_TRANSLATE_NOOP("bitcoin-core", "How many blocks to check at startup (default: 2500, 0 = all)"), +QT_TRANSLATE_NOOP("bitcoin-core", "How thorough the block verification is (0-6, default: 1)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Importing blockchain data file."), +QT_TRANSLATE_NOOP("bitcoin-core", "Importing bootstrap blockchain data file."), +QT_TRANSLATE_NOOP("bitcoin-core", "Imports blocks from external blk000?.dat file"), QT_TRANSLATE_NOOP("bitcoin-core", "Insufficient funds"), -}; +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -proxy address: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -tor address: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -paytxfee=: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -reservebalance="), +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount"), +QT_TRANSLATE_NOOP("bitcoin-core", "List commands"), +QT_TRANSLATE_NOOP("bitcoin-core", "Listen for connections on (default: 7777 or testnet: 17777)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Loading addresses..."), +QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index..."), +QT_TRANSLATE_NOOP("bitcoin-core", "Loading wallet..."), +QT_TRANSLATE_NOOP("bitcoin-core", "Maintain at most connections to peers (default: 125)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Maximum per-connection receive buffer, *1000 bytes (default: 5000)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Maximum per-connection send buffer, *1000 bytes (default: 1000)"), +QT_TRANSLATE_NOOP("bitcoin-core", "NovaCoin version"), +QT_TRANSLATE_NOOP("bitcoin-core", "NovaCoin"), +QT_TRANSLATE_NOOP("bitcoin-core", "Only connect to nodes in network (IPv4, IPv6 or Tor)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Options:"), +QT_TRANSLATE_NOOP("bitcoin-core", "Output extra debugging information. Implies all other -debug* options"), +QT_TRANSLATE_NOOP("bitcoin-core", "Output extra network debugging information"), +QT_TRANSLATE_NOOP("bitcoin-core", "Password for JSON-RPC connections"), +QT_TRANSLATE_NOOP("bitcoin-core", "Prepend debug output with timestamp"), +QT_TRANSLATE_NOOP("bitcoin-core", "Rescan the block chain for missing wallet transactions"), +QT_TRANSLATE_NOOP("bitcoin-core", "Rescanning..."), +QT_TRANSLATE_NOOP("bitcoin-core", "Run in the background as a daemon and accept commands"), +QT_TRANSLATE_NOOP("bitcoin-core", "SSL options: (see the Bitcoin Wiki for SSL setup instructions)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Select the version of socks proxy to use (4-5, default: 5)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Send command to -server or novacoind"), +QT_TRANSLATE_NOOP("bitcoin-core", "Send commands to node running on (default: 127.0.0.1)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Send trace/debug info to console instead of debug.log file"), +QT_TRANSLATE_NOOP("bitcoin-core", "Send trace/debug info to debugger"), +QT_TRANSLATE_NOOP("bitcoin-core", "Sending..."), +QT_TRANSLATE_NOOP("bitcoin-core", "Server certificate file (default: server.cert)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Server private key (default: server.pem)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Set database cache size in megabytes (default: 25)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Set database disk log size in megabytes (default: 100)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Set key pool size to (default: 100)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Set maximum block size in bytes (default: 250000)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Set minimum block size in bytes (default: 0)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Shrink debug.log file on client startup (default: 1 when no -debug)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Specify configuration file (default: novacoin.conf)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Specify connection timeout in milliseconds (default: 5000)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Specify data directory"), +QT_TRANSLATE_NOOP("bitcoin-core", "Specify pid file (default: novacoind.pid)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Specify your own public address"), +QT_TRANSLATE_NOOP("bitcoin-core", "This help message"), +QT_TRANSLATE_NOOP("bitcoin-core", "Threshold for disconnecting misbehaving peers (default: 100)"), +QT_TRANSLATE_NOOP("bitcoin-core", "To use the %s option"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer (bind returned error %d, %s)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unable to sign checkpoint, wrong checkpointkey?\n"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unknown -socks proxy version requested: %i"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unknown network specified in -onlynet: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Upgrade wallet to latest format"), +QT_TRANSLATE_NOOP("bitcoin-core", "Usage:"), +QT_TRANSLATE_NOOP("bitcoin-core", "Use OpenSSL (https) for JSON-RPC connections"), +QT_TRANSLATE_NOOP("bitcoin-core", "Use UPnP to map the listening port (default: 0)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Use UPnP to map the listening port (default: 1 when listening)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Use proxy to reach tor hidden services (default: same as -proxy)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Use the test network"), +QT_TRANSLATE_NOOP("bitcoin-core", "Username for JSON-RPC connections"), +QT_TRANSLATE_NOOP("bitcoin-core", "Verifying database integrity..."), +QT_TRANSLATE_NOOP("bitcoin-core", "Wallet needed to be rewritten: restart NovaCoin to complete"), +QT_TRANSLATE_NOOP("bitcoin-core", "Warning: Disk space is low!"), +QT_TRANSLATE_NOOP("bitcoin-core", "Warning: This version is obsolete, upgrade required!"), +QT_TRANSLATE_NOOP("bitcoin-core", "wallet.dat corrupt, salvage failed"), +}; \ No newline at end of file diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index bd67423..863f291 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -67,9 +67,9 @@ int BitcoinUnits::amountDigits(int unit) { switch(unit) { - case BTC: return 10; // 21,000,000 (# digits, without commas) - case mBTC: return 13; // 21,000,000,000 - case uBTC: return 16; // 21,000,000,000,000 + case BTC: return 8; // 21,000,000 (# digits, without commas) + case mBTC: return 11; // 21,000,000,000 + case uBTC: return 14; // 21,000,000,000,000 default: return 0; } } @@ -99,7 +99,7 @@ QString BitcoinUnits::format(int unit, qint64 n, bool fPlus) QString quotient_str = QString::number(quotient); QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0'); - // Right-trim excess 0's after the decimal point + // Right-trim excess zeros after the decimal point int nTrim = 0; for (int i = remainder_str.size()-1; i>=2 && (remainder_str.at(i) == '0'); --i) ++nTrim; diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h index 18fa36a..9b7c9e1 100644 --- a/src/qt/bitcoinunits.h +++ b/src/qt/bitcoinunits.h @@ -5,7 +5,7 @@ #include /** Bitcoin unit definitions. Encapsulates parsing and formatting - and serves as list model for dropdown selection boxes. + and serves as list model for drop-down selection boxes. */ class BitcoinUnits: public QAbstractListModel { @@ -26,7 +26,7 @@ public: //! Unit conversion and formatting ///@{ - //! Get list of units, for dropdown box + //! Get list of units, for drop-down box static QList availableUnits(); //! Is unit ID valid? static bool valid(int unit); @@ -49,7 +49,7 @@ public: ///@} //! @name AbstractListModel implementation - //! List model for unit dropdown selection box. + //! List model for unit drop-down selection box. ///@{ enum RoleIndex { /** Unit identifier */ diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index cb602ce..b820d16 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -4,15 +4,32 @@ #include "addresstablemodel.h" #include "transactiontablemodel.h" +#include "alert.h" #include "main.h" +#include "ui_interface.h" #include +#include + +static const int64 nClientStartupTime = GetTime(); ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) : QObject(parent), optionsModel(optionsModel), - cachedNumConnections(0), cachedNumBlocks(0) + cachedNumBlocks(0), cachedNumBlocksOfPeers(0), pollTimer(0) { numBlocksAtStartup = -1; + + pollTimer = new QTimer(this); + pollTimer->setInterval(MODEL_UPDATE_DELAY); + pollTimer->start(); + connect(pollTimer, SIGNAL(timeout()), this, SLOT(updateTimer())); + + subscribeToCoreSignals(); +} + +ClientModel::~ClientModel() +{ + unsubscribeFromCoreSignals(); } int ClientModel::getNumConnections() const @@ -36,27 +53,44 @@ QDateTime ClientModel::getLastBlockDate() const return QDateTime::fromTime_t(pindexBest->GetBlockTime()); } -void ClientModel::update() +void ClientModel::updateTimer() { - int newNumConnections = getNumConnections(); + // Some quantities (such as number of blocks) change so fast that we don't want to be notified for each change. + // Periodically check and update with a timer. int newNumBlocks = getNumBlocks(); - QString newStatusBar = getStatusBarWarnings(); + int newNumBlocksOfPeers = getNumBlocksOfPeers(); + + if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers) + { + cachedNumBlocks = newNumBlocks; + cachedNumBlocksOfPeers = newNumBlocksOfPeers; + + emit numBlocksChanged(newNumBlocks, newNumBlocksOfPeers); + } +} + +void ClientModel::updateNumConnections(int numConnections) +{ + emit numConnectionsChanged(numConnections); +} - if(cachedNumConnections != newNumConnections) - emit numConnectionsChanged(newNumConnections); - if(cachedNumBlocks != newNumBlocks || cachedStatusBar != newStatusBar) +void ClientModel::updateAlert(const QString &hash, int status) +{ + // Show error message notification for new alert + if(status == CT_NEW) { - // Simply emit a numBlocksChanged for now in case the status message changes, - // so that the view updates the status bar. - // TODO: It should send a notification. - // (However, this might generate looped notifications and needs to be thought through and tested carefully) - // error(tr("Network Alert"), newStatusBar); - emit numBlocksChanged(newNumBlocks); + uint256 hash_256; + hash_256.SetHex(hash.toStdString()); + CAlert alert = CAlert::getAlertByHash(hash_256); + if(!alert.IsNull()) + { + emit error(tr("Network Alert"), QString::fromStdString(alert.strStatusBar), false); + } } - cachedNumConnections = newNumConnections; - cachedNumBlocks = newNumBlocks; - cachedStatusBar = newStatusBar; + // Emit a numBlocksChanged when the status message changes, + // so that the view recomputes and updates the status bar. + emit numBlocksChanged(getNumBlocks(), getNumBlocksOfPeers()); } bool ClientModel::isTestNet() const @@ -93,3 +127,51 @@ QString ClientModel::formatBuildDate() const { return QString::fromStdString(CLIENT_DATE); } + +QString ClientModel::clientName() const +{ + return QString::fromStdString(CLIENT_NAME); +} + +QString ClientModel::formatClientStartupTime() const +{ + return QDateTime::fromTime_t(nClientStartupTime).toString(); +} + +// Handlers for core signals +static void NotifyBlocksChanged(ClientModel *clientmodel) +{ + // This notification is too frequent. Don't trigger a signal. + // Don't remove it, though, as it might be useful later. +} + +static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) +{ + // Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections); + QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, + Q_ARG(int, newNumConnections)); +} + +static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status); + QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void ClientModel::subscribeToCoreSignals() +{ + // Connect signals to client + uiInterface.NotifyBlocksChanged.connect(boost::bind(NotifyBlocksChanged, this)); + uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1)); + uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2)); +} + +void ClientModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from client + uiInterface.NotifyBlocksChanged.disconnect(boost::bind(NotifyBlocksChanged, this)); + uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1)); + uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2)); +} diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 8e7431a..70d816b 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -10,6 +10,7 @@ class CWallet; QT_BEGIN_NAMESPACE class QDateTime; +class QTimer; QT_END_NAMESPACE /** Model for Bitcoin network client. */ @@ -18,6 +19,7 @@ class ClientModel : public QObject Q_OBJECT public: explicit ClientModel(OptionsModel *optionsModel, QObject *parent = 0); + ~ClientModel(); OptionsModel *getOptionsModel(); @@ -38,27 +40,32 @@ public: QString formatFullVersion() const; QString formatBuildDate() const; + QString clientName() const; + QString formatClientStartupTime() const; private: OptionsModel *optionsModel; - int cachedNumConnections; int cachedNumBlocks; - QString cachedStatusBar; + int cachedNumBlocksOfPeers; int numBlocksAtStartup; + QTimer *pollTimer; + + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); signals: void numConnectionsChanged(int count); - void numBlocksChanged(int count); + void numBlocksChanged(int count, int countOfPeers); //! Asynchronous error notification void error(const QString &title, const QString &message, bool modal); public slots: - -private slots: - void update(); + void updateTimer(); + void updateNumConnections(int numConnections); + void updateAlert(const QString &hash, int status); }; #endif // CLIENTMODEL_H diff --git a/src/qt/forms/aboutdialog.ui b/src/qt/forms/aboutdialog.ui index 08af8ce..e9c47cf 100644 --- a/src/qt/forms/aboutdialog.ui +++ b/src/qt/forms/aboutdialog.ui @@ -22,9 +22,6 @@ 0 - - - :/images/about @@ -49,6 +46,9 @@ + + IBeamCursor + <b>NovaCoin</b> version @@ -59,12 +59,12 @@ + + IBeamCursor + 0.3.666-beta - - Qt::RichText - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse @@ -86,13 +86,28 @@ + + + IBeamCursor + + + Copyright © 2009-2012 The NovaCoin developers + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + IBeamCursor + - Copyright © 2009-2012 NovaCoin Developers - + This is experimental software. -Distributed under the MIT/X11 software license, see the accompanying file license.txt or http://www.opensource.org/licenses/mit-license.php. +Distributed under the MIT/X11 software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php. This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard. diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index 219744a..3d23543 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -6,8 +6,8 @@ 0 0 - 627 - 347 + 760 + 380 @@ -17,10 +17,10 @@ - These are your NovaCoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. + These are your NovaCoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. - Qt::AutoText + Qt::PlainText true @@ -29,9 +29,15 @@ + + Qt::CustomContextMenu + Double-click to edit address or label + + false + true @@ -57,7 +63,7 @@ Create a new address - &New Address... + &New Address @@ -71,7 +77,7 @@ Copy the currently selected address to the system clipboard - &Copy to Clipboard + &Copy Address @@ -86,17 +92,17 @@ - :/images/qrcode:/images/qrcode + :/icons/qrcode:/icons/qrcode - Sign a message to prove you own this address + Sign a message to prove you own a NovaCoin address - &Sign Message + Sign &Message @@ -105,9 +111,23 @@ + + + Verify a message to ensure it was signed with a specified NovaCoin address + + + &Verify Message + + + + :/icons/transaction_0:/icons/transaction_0 + + + + - Delete the currently selected address from the list. Only sending addresses can be deleted. + Delete the currently selected address from the list &Delete diff --git a/src/qt/forms/askpassphrasedialog.ui b/src/qt/forms/askpassphrasedialog.ui index ff3a771..2516904 100644 --- a/src/qt/forms/askpassphrasedialog.ui +++ b/src/qt/forms/askpassphrasedialog.ui @@ -23,7 +23,7 @@ - Dialog + Passphrase Dialog @@ -41,57 +41,58 @@ QFormLayout::AllNonFixedFieldsGrow - + Enter passphrase - + QLineEdit::Password - + New passphrase - + QLineEdit::Password - + Repeat new passphrase - + QLineEdit::Password - + - - #capsLabel { - font: bold; -} + + + 75 + true + - TextLabel + Qt::AlignCenter diff --git a/src/qt/forms/messagepage.ui b/src/qt/forms/messagepage.ui deleted file mode 100644 index 09ba212..0000000 --- a/src/qt/forms/messagepage.ui +++ /dev/null @@ -1,170 +0,0 @@ - - - MessagePage - - - - 0 - 0 - 627 - 380 - - - - Message - - - - - - You can sign messages with your addresses to prove you own them. Be careful not to sign anything vague, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to. - - - Qt::AutoText - - - true - - - - - - - 0 - - - - - The address to sign the message with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - - - 34 - - - - - - - Choose adress from address book - - - - - - - :/icons/address-book:/icons/address-book - - - Alt+A - - - false - - - false - - - - - - - Paste address from clipboard - - - - - - - :/icons/editpaste:/icons/editpaste - - - Alt+P - - - false - - - - - - - - - Enter the message you want to sign here - - - - - - - - true - - - - Click "Sign Message" to get signature - - - true - - - - - - - - - Sign a message to prove you own this address - - - &Sign Message - - - - :/icons/edit:/icons/edit - - - - - - - Copy the current signature to the system clipboard - - - &Copy to Clipboard - - - - :/icons/editcopy:/icons/editcopy - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - QValidatedLineEdit - QLineEdit -
qvalidatedlineedit.h
-
-
- - - - -
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui new file mode 100644 index 0000000..d1284b6 --- /dev/null +++ b/src/qt/forms/optionsdialog.ui @@ -0,0 +1,460 @@ + + + OptionsDialog + + + + 0 + 0 + 540 + 380 + + + + Options + + + true + + + + + + QTabWidget::North + + + 0 + + + + &Main + + + + + + Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. + + + Qt::PlainText + + + true + + + + + + + + + Pay transaction &fee + + + Qt::PlainText + + + transactionFee + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Automatically start NovaCoin after logging in to the system. + + + &Start NovaCoin on system login + + + + + + + Detach block and address databases at shutdown. This means they can be moved to another data directory, but it slows down shutdown. The wallet is always detached. + + + &Detach databases at shutdown + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + &Network + + + + + + Automatically open the NovaCoin client port on the router. This only works when your router supports UPnP and it is enabled. + + + Map port using &UPnP + + + + + + + Connect to the NovaCoin network through a SOCKS proxy (e.g. when connecting through Tor). + + + &Connect through SOCKS proxy: + + + + + + + + + Proxy &IP: + + + Qt::PlainText + + + proxyIp + + + + + + + + 140 + 16777215 + + + + IP address of the proxy (e.g. 127.0.0.1) + + + + + + + &Port: + + + Qt::PlainText + + + proxyPort + + + + + + + + 55 + 16777215 + + + + Port of the proxy (e.g. 9050) + + + + + + + SOCKS &Version: + + + Qt::PlainText + + + socksVersion + + + + + + + SOCKS version of the proxy (e.g. 5) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + &Window + + + + + + Show only a tray icon after minimizing the window. + + + &Minimize to the tray instead of the taskbar + + + + + + + Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. + + + M&inimize on close + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + &Display + + + + + + + + User Interface &language: + + + Qt::PlainText + + + lang + + + + + + + The user interface language can be set here. This setting will take effect after restarting NovaCoin. + + + + + + + + + + + &Unit to show amounts in: + + + Qt::PlainText + + + unit + + + + + + + Choose the default subdivision unit to show in the interface and when sending coins. + + + + + + + + + Whether to show NovaCoin addresses in the transaction list or not. + + + &Display addresses in transaction list + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + 75 + true + + + + + + + Qt::PlainText + + + true + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + &OK + + + + + + + &Cancel + + + false + + + + + + + &Apply + + + false + + + + + + + + + + BitcoinAmountField + QSpinBox +
bitcoinamountfield.h
+
+ + QValueComboBox + QComboBox +
qvaluecombobox.h
+
+ + QValidatedLineEdit + QLineEdit +
qvalidatedlineedit.h
+
+
+ + +
diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index dd543c4..4a59ade 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -1,178 +1,328 @@ - - - OverviewPage - - - - 0 - 0 - 552 - 342 - - - - Form - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - QFormLayout::AllNonFixedFieldsGrow - - - 12 - - - 12 - - - - - Balance: - - - - - - - 0 NVC - - - - - - - Number of transactions: - - - - - - - 0 - - - - - - - Unconfirmed: - - - - - - - 0 NVC - - - - - - - Stake: - - - - - - - 0 NVC - - - - - - - - 11 - 75 - true - - - - Wallet - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - <b>Recent transactions</b> - - - - - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - + + + OverviewPage + + + + 0 + 0 + 573 + 342 + + + + Form + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + 11 + 75 + true + + + + Wallet + + + + + + + The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. + + + QLabel { color: red; } + + + (out of sync) + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 12 + + + 12 + + + + + Balance: + + + + + + + + 75 + true + + + + IBeamCursor + + + Your current balance + + + 0 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Stake: + + + + + + + + 75 + true + + + + IBeamCursor + + + Total of coins that was staked, and do not yet count toward the current balance + + + 0 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Unconfirmed: + + + + + + + + 75 + true + + + + IBeamCursor + + + Total of transactions that have yet to be confirmed, and do not yet count toward the current balance + + + 0 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Immature: + + + + + + + + 75 + true + + + + Mined balance that has not yet matured + + + 0 NVC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Number of transactions: + + + + + + + Total number of transactions in wallet + + + 0 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + <b>Recent transactions</b> + + + + + + + The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. + + + QLabel { color: red; } + + + (out of sync) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QListView { background: transparent; } + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoSelection + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/src/qt/forms/qrcodedialog.ui b/src/qt/forms/qrcodedialog.ui index 714b1d6..52e9db3 100644 --- a/src/qt/forms/qrcodedialog.ui +++ b/src/qt/forms/qrcodedialog.ui @@ -6,12 +6,12 @@ 0 0 - 320 - 404 + 340 + 530
- Dialog + QR Code Dialog @@ -28,8 +28,8 @@ 300
- - QR Code + + Qt::PlainText Qt::AlignCenter @@ -40,133 +40,122 @@
+ + + + 0 + 0 + + + + + 0 + 50 + + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + - - - - - - - true - - - Request Payment - - - - - - - - - - 0 - 0 - - - - Amount: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - lnReqAmount - - - - - - - false - - - - 60 - 0 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - BTC - - - lnReqAmount - - - - - - + + + true + + + Request Payment + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Label: + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + lnLabel + + - - - - - - Label: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - lnLabel - - - - - - - - 100 - 0 - - - - - - - - Message: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - lnMessage - - - - - - - - 100 - 0 - - - - - + + + + + + + Message: + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + lnMessage + + + + + + + + + + + 0 + 0 + + + + Amount: + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + lnReqAmount + + + + + + + false + + + + 80 + 0 + + + - + @@ -194,6 +183,13 @@ + + + BitcoinAmountField + QSpinBox +
bitcoinamountfield.h
+
+
diff --git a/src/qt/forms/rpcconsole.ui b/src/qt/forms/rpcconsole.ui new file mode 100644 index 0000000..a36c553 --- /dev/null +++ b/src/qt/forms/rpcconsole.ui @@ -0,0 +1,456 @@ + + + RPCConsole + + + + 0 + 0 + 740 + 450 + + + + NovaCoin - Debug window + + + + + + 0 + + + + &Information + + + + 12 + + + + + + 75 + true + + + + NovaCoin Core + + + + + + + Client name + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Client version + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Using OpenSSL version + + + 10 + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Build date + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Startup time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 75 + true + + + + Network + + + + + + + Number of connections + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + On testnet + + + + + + + false + + + + + + + + + + + 75 + true + + + + Block chain + + + + + + + Current number of blocks + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Estimated total blocks + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last block time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + 75 + true + + + + Debug log file + + + + + + + Open the NovaCoin debug log file from the current data directory. This can take a few seconds for large log files. + + + &Open + + + false + + + + + + + + 75 + true + + + + Command-line options + + + + + + + Show the NovaCoin-Qt help message to get a list with possible NovaCoin command-line options. + + + &Show + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + &Console + + + + 3 + + + + + + 0 + 100 + + + + true + + + false + + + 2 + + + + + + + 3 + + + + + > + + + + + + + + + + + 24 + 24 + + + + Clear console + + + + + + + :/icons/remove:/icons/remove + + + Ctrl+L + + + false + + + + + + + + + + + + + + + + diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 04cf404..6e17565 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -64,12 +64,15 @@ Send to multiple recipients at once - &Add recipient... + Add &Recipient :/icons/add:/icons/add + + false +
@@ -84,7 +87,7 @@ Remove all transaction fields - Clear all + Clear &All @@ -93,6 +96,9 @@ 300 + + false + @@ -109,6 +115,9 @@ + + IBeamCursor + 123.456 BTC @@ -144,7 +153,7 @@ Confirm the send action - &Send + S&end diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index e718212..22a3f8f 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -90,7 +90,7 @@ - The address to send the payment to (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L) 34 diff --git a/src/qt/forms/signverifymessagedialog.ui b/src/qt/forms/signverifymessagedialog.ui new file mode 100644 index 0000000..7d13c83 --- /dev/null +++ b/src/qt/forms/signverifymessagedialog.ui @@ -0,0 +1,386 @@ + + + SignVerifyMessageDialog + + + + 0 + 0 + 700 + 380 + + + + Signatures - Sign / Verify a Message + + + true + + + + + + 0 + + + + &Sign Message + + + + + + You can sign messages with your addresses to prove you own them. Be careful not to sign anything vague, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to. + + + Qt::PlainText + + + true + + + + + + + 0 + + + + + The address to sign the message with (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L) + + + 34 + + + + + + + Choose an address from the address book + + + + + + + :/icons/address-book:/icons/address-book + + + Alt+A + + + false + + + + + + + Paste address from clipboard + + + + + + + :/icons/editpaste:/icons/editpaste + + + Alt+P + + + false + + + + + + + + + Enter the message you want to sign here + + + + + + + 0 + + + + + + true + + + + true + + + + + + + Copy the current signature to the system clipboard + + + + + + + :/icons/editcopy:/icons/editcopy + + + false + + + + + + + + + + + Sign the message to prove you own this NovaCoin address + + + &Sign Message + + + + :/icons/edit:/icons/edit + + + false + + + + + + + Reset all sign message fields + + + Clear &All + + + + :/icons/remove:/icons/remove + + + false + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + 75 + true + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + + + &Verify Message + + + + + + Enter the signing address, message (ensure you copy line breaks, spaces, tabs, etc. exactly) and signature below to verify the message. Be careful not to read more into the signature than what is in the signed message itself, to avoid being tricked by a man-in-the-middle attack. + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + 0 + + + + + The address the message was signed with (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L) + + + 34 + + + + + + + Choose an address from the address book + + + + + + + :/icons/address-book:/icons/address-book + + + Alt+A + + + false + + + + + + + + + + + + + + + + + Verify the message to ensure it was signed with the specified NovaCoin address + + + &Verify Message + + + + :/icons/transaction_0:/icons/transaction_0 + + + false + + + + + + + Reset all verify message fields + + + Clear &All + + + + :/icons/remove:/icons/remove + + + false + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + 75 + true + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + + + + + + + QValidatedLineEdit + QLineEdit +
qvalidatedlineedit.h
+
+
+ + + + +
diff --git a/src/qt/forms/transactiondescdialog.ui b/src/qt/forms/transactiondescdialog.ui index 9a9f6db..b38dffc 100644 --- a/src/qt/forms/transactiondescdialog.ui +++ b/src/qt/forms/transactiondescdialog.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 620 + 250
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 0cb5075..405ba39 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -4,10 +4,10 @@ /* Milliseconds between model updates */ static const int MODEL_UPDATE_DELAY = 500; -/* Maximum passphrase length */ +/* AskPassphraseDialog -- Maximum passphrase length */ static const int MAX_PASSPHRASE_SIZE = 1024; -/* Size of icons in status bar */ +/* BitcoinGUI -- Size of icons in status bar */ static const int STATUSBAR_ICONSIZE = 16; /* Invalid field background style */ @@ -20,4 +20,15 @@ static const int STATUSBAR_ICONSIZE = 16; /* Transaction list -- bare address (without label) */ #define COLOR_BAREADDRESS QColor(140, 140, 140) +/* Tooltips longer than this (in characters) are converted into rich text, + so that they can be word-wrapped. + */ +static const int TOOLTIP_WRAP_THRESHOLD = 80; + +/* Maximum allowed URI length */ +static const int MAX_URI_LENGTH = 255; + +/* QRCodeDialog -- size of exported QR Code image */ +#define EXPORT_IMAGE_SIZE 256 + #endif // GUICONSTANTS_H diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index f1e8a5f..bc10f1c 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -2,6 +2,8 @@ #include "bitcoinaddressvalidator.h" #include "walletmodel.h" #include "bitcoinunits.h" +#include "util.h" +#include "init.h" #include #include @@ -17,6 +19,27 @@ #include #include +#include +#include + +#ifdef WIN32 +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0501 +#ifdef _WIN32_IE +#undef _WIN32_IE +#endif +#define _WIN32_IE 0x0501 +#define WIN32_LEAN_AND_MEAN 1 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include "shlwapi.h" +#include "shlobj.h" +#include "shellapi.h" +#endif + namespace GUIUtil { QString dateTimeStr(const QDateTime &date) @@ -102,7 +125,7 @@ bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) // Convert bitcoin:// to bitcoin: // // Cannot handle this later, because bitcoin:// will cause Qt to see the part after // as host, - // which will lowercase it (and thus invalidate the address). + // which will lower-case it (and thus invalidate the address). if(uri.startsWith("bitcoin://")) { uri.replace(0, 10, "bitcoin:"); @@ -199,19 +222,235 @@ Qt::ConnectionType blockingGUIThreadConnection() bool checkPoint(const QPoint &p, const QWidget *w) { - QWidget *atW = qApp->widgetAt(w->mapToGlobal(p)); - if(!atW) return false; - return atW->topLevelWidget() == w; + QWidget *atW = qApp->widgetAt(w->mapToGlobal(p)); + if (!atW) return false; + return atW->topLevelWidget() == w; } bool isObscured(QWidget *w) { + return !(checkPoint(QPoint(0, 0), w) + && checkPoint(QPoint(w->width() - 1, 0), w) + && checkPoint(QPoint(0, w->height() - 1), w) + && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) + && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); +} + +void openDebugLogfile() +{ + boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; + + /* Open debug.log with the associated application */ + if (boost::filesystem::exists(pathDebug)) + QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(pathDebug.string()))); +} + +ToolTipToRichTextFilter::ToolTipToRichTextFilter(int size_threshold, QObject *parent) : + QObject(parent), size_threshold(size_threshold) +{ + +} + +bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) +{ + if(evt->type() == QEvent::ToolTipChange) + { + QWidget *widget = static_cast(obj); + QString tooltip = widget->toolTip(); + if(tooltip.size() > size_threshold && !tooltip.startsWith("") && !Qt::mightBeRichText(tooltip)) + { + // Prefix to make sure Qt detects this as rich text + // Escape the current message as HTML and replace \n by
+ tooltip = "" + HtmlEscape(tooltip, true); + widget->setToolTip(tooltip); + return true; + } + } + return QObject::eventFilter(obj, evt); +} + +#ifdef WIN32 +boost::filesystem::path static StartupShortcutPath() +{ + return GetSpecialFolderPath(CSIDL_STARTUP) / "NovaCoin.lnk"; +} + +bool GetStartOnSystemStartup() +{ + // check for Bitcoin.lnk + return boost::filesystem::exists(StartupShortcutPath()); +} + +bool SetStartOnSystemStartup(bool fAutoStart) +{ + // If the shortcut exists already, remove it for updating + boost::filesystem::remove(StartupShortcutPath()); + + if (fAutoStart) + { + CoInitialize(NULL); + + // Get a pointer to the IShellLink interface. + IShellLink* psl = NULL; + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, IID_IShellLink, + reinterpret_cast(&psl)); + + if (SUCCEEDED(hres)) + { + // Get the current executable path + TCHAR pszExePath[MAX_PATH]; + GetModuleFileName(NULL, pszExePath, sizeof(pszExePath)); + + TCHAR pszArgs[5] = TEXT("-min"); + + // Set the path to the shortcut target + psl->SetPath(pszExePath); + PathRemoveFileSpec(pszExePath); + psl->SetWorkingDirectory(pszExePath); + psl->SetShowCmd(SW_SHOWMINNOACTIVE); + psl->SetArguments(pszArgs); + + // Query IShellLink for the IPersistFile interface for + // saving the shortcut in persistent storage. + IPersistFile* ppf = NULL; + hres = psl->QueryInterface(IID_IPersistFile, + reinterpret_cast(&ppf)); + if (SUCCEEDED(hres)) + { + WCHAR pwsz[MAX_PATH]; + // Ensure that the string is ANSI. + MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); + // Save the link by calling IPersistFile::Save. + hres = ppf->Save(pwsz, TRUE); + ppf->Release(); + psl->Release(); + CoUninitialize(); + return true; + } + psl->Release(); + } + CoUninitialize(); + return false; + } + return true; +} + +#elif defined(LINUX) - return !(checkPoint(QPoint(0, 0), w) - && checkPoint(QPoint(w->width() - 1, 0), w) - && checkPoint(QPoint(0, w->height() - 1), w) - && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) - && checkPoint(QPoint(w->width()/2, w->height()/2), w)); +// Follow the Desktop Application Autostart Spec: +// http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html + +boost::filesystem::path static GetAutostartDir() +{ + namespace fs = boost::filesystem; + + char* pszConfigHome = getenv("XDG_CONFIG_HOME"); + if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; + char* pszHome = getenv("HOME"); + if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; + return fs::path(); +} + +boost::filesystem::path static GetAutostartFilePath() +{ + return GetAutostartDir() / "novacoin.desktop"; +} + +bool GetStartOnSystemStartup() +{ + boost::filesystem::ifstream optionFile(GetAutostartFilePath()); + if (!optionFile.good()) + return false; + // Scan through file for "Hidden=true": + std::string line; + while (!optionFile.eof()) + { + getline(optionFile, line); + if (line.find("Hidden") != std::string::npos && + line.find("true") != std::string::npos) + return false; + } + optionFile.close(); + + return true; +} + +bool SetStartOnSystemStartup(bool fAutoStart) +{ + if (!fAutoStart) + boost::filesystem::remove(GetAutostartFilePath()); + else + { + char pszExePath[MAX_PATH+1]; + memset(pszExePath, 0, sizeof(pszExePath)); + if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)-1) == -1) + return false; + + boost::filesystem::create_directories(GetAutostartDir()); + + boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc); + if (!optionFile.good()) + return false; + // Write a bitcoin.desktop file to the autostart directory: + optionFile << "[Desktop Entry]\n"; + optionFile << "Type=Application\n"; + optionFile << "Name=NovaCoin\n"; + optionFile << "Exec=" << pszExePath << " -min\n"; + optionFile << "Terminal=false\n"; + optionFile << "Hidden=false\n"; + optionFile.close(); + } + return true; +} +#else + +// TODO: OSX startup stuff; see: +// https://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPSystemStartup/Articles/CustomLogin.html + +bool GetStartOnSystemStartup() { return false; } +bool SetStartOnSystemStartup(bool fAutoStart) { return false; } + +#endif + +HelpMessageBox::HelpMessageBox(QWidget *parent) : + QMessageBox(parent) +{ + header = tr("NovaCoin-Qt") + " " + tr("version") + " " + + QString::fromStdString(FormatFullVersion()) + "\n\n" + + tr("Usage:") + "\n" + + " novacoin-qt [" + tr("command-line options") + "] " + "\n"; + + coreOptions = QString::fromStdString(HelpMessage()); + + uiOptions = tr("UI options") + ":\n" + + " -lang= " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" + + " -min " + tr("Start minimized") + "\n" + + " -splash " + tr("Show splash screen on startup (default: 1)") + "\n"; + + setWindowTitle(tr("NovaCoin-Qt")); + setTextFormat(Qt::PlainText); + // setMinimumWidth is ignored for QMessageBox so put in non-breaking spaces to make it wider. + setText(header + QString(QChar(0x2003)).repeated(50)); + setDetailedText(coreOptions + "\n" + uiOptions); +} + +void HelpMessageBox::printToConsole() +{ + // On other operating systems, the expected action is to print the message to the console. + QString strUsage = header + "\n" + coreOptions + "\n" + uiOptions; + fprintf(stdout, "%s", strUsage.toStdString().c_str()); +} + +void HelpMessageBox::showOrPrint() +{ +#if defined(WIN32) + // On Windows, show a message box, as there is no stderr/stdout in windowed applications + exec(); +#else + // On other operating systems, print help text to console + printToConsole(); +#endif } } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index ea1a479..db100bd 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -2,6 +2,8 @@ #define GUIUTIL_H #include +#include +#include QT_BEGIN_NAMESPACE class QFont; @@ -21,14 +23,14 @@ namespace GUIUtil QString dateTimeStr(const QDateTime &datetime); QString dateTimeStr(qint64 nTime); - // Render bitcoin addresses in monospace font + // Render Bitcoin addresses in monospace font QFont bitcoinAddressFont(); // Set up widgets for address and amounts void setupAddressWidget(QLineEdit *widget, QWidget *parent); void setupAmountWidget(QLineEdit *widget, QWidget *parent); - // Parse "bitcoin:" URI into recipient object, return true on succesful parsing + // Parse "bitcoin:" URI into recipient object, return true on successful parsing // See Bitcoin URI definition discussion here: https://bitcointalk.org/index.php?topic=33490.0 bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); @@ -45,7 +47,7 @@ namespace GUIUtil */ void copyEntryData(QAbstractItemView *view, int column, int role=Qt::EditRole); - /** Get save file name, mimics QFileDialog::getSaveFileName, except that it appends a default suffix + /** Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when no suffix is provided by the user. @param[in] parent Parent window (or 0) @@ -69,6 +71,50 @@ namespace GUIUtil // Determine whether a widget is hidden behind other windows bool isObscured(QWidget *w); + // Open debug.log + void openDebugLogfile(); + + /** Qt event filter that intercepts ToolTipChange events, and replaces the tooltip with a rich text + representation if needed. This assures that Qt can word-wrap long tooltip messages. + Tooltips longer than the provided size threshold (in characters) are wrapped. + */ + class ToolTipToRichTextFilter : public QObject + { + Q_OBJECT + + public: + explicit ToolTipToRichTextFilter(int size_threshold, QObject *parent = 0); + + protected: + bool eventFilter(QObject *obj, QEvent *evt); + + private: + int size_threshold; + }; + + bool GetStartOnSystemStartup(); + bool SetStartOnSystemStartup(bool fAutoStart); + + /** Help message for Bitcoin-Qt, shown with --help. */ + class HelpMessageBox : public QMessageBox + { + Q_OBJECT + + public: + HelpMessageBox(QWidget *parent = 0); + + /** Show message box or print help message to standard output, based on operating system. */ + void showOrPrint(); + + /** Print help message to console */ + void printToConsole(); + + private: + QString header; + QString coreOptions; + QString uiOptions; + }; + } // namespace GUIUtil #endif // GUIUTIL_H diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index a8e0ce7..9ee3cb8 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -5,405 +5,402 @@ AboutDialog - + About NovaCoin - + About NovaCoin - + <b>NovaCoin</b> version - + <b>NovaCoin</b> version - - Copyright © 2009-2012 NovaCoin Developers - + + Copyright © 2009-2012 The NovaCoin developers + Copyright © 2009-2012 The NovaCoin developers + + + + This is experimental software. -Distributed under the MIT/X11 software license, see the accompanying file license.txt or http://www.opensource.org/licenses/mit-license.php. +Distributed under the MIT/X11 software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php. This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard. - + +This is experimental software. + +Distributed under the MIT/X11 software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php. + +This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard. AddressBookPage - + Address Book - - - - - These are your NovaCoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. - + Address Book - + Double-click to edit address or label - + Double-click to edit address or label - + Create a new address - + Create a new address - - &New Address... - + + Copy the currently selected address to the system clipboard + Copy the currently selected address to the system clipboard - - Copy the currently selected address to the system clipboard - + + &New Address + &New Address - - &Copy to Clipboard - + + These are your NovaCoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. + These are your NovaCoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. + + + + &Copy Address + &Copy Address - + Show &QR Code - + Show &QR Code - - Sign a message to prove you own this address - + + Sign a message to prove you own a NovaCoin address + Sign a message to prove you own a NovaCoin address - - &Sign Message + + Sign &Message - - Delete the currently selected address from the list. Only sending addresses can be deleted. + + Delete the currently selected address from the list - - &Delete - + + Verify a message to ensure it was signed with a specified NovaCoin address + Verify a message to ensure it was signed with a specified NovaCoin address - - Copy address - + + &Verify Message + &Verify Message - - Copy label - + + &Delete + &Delete - - Edit - + + Copy &Label + Copy &Label - - Delete - + + &Edit + &Edit - + Export Address Book Data - + Export Address Book Data - + Comma separated file (*.csv) - + Comma separated file (*.csv) - + Error exporting - + Error exporting - + Could not write to file %1. - + Could not write to file %1. AddressTableModel - + Label - + Label - + Address - + Address - + (no label) - + (no label) AskPassphraseDialog - - Dialog - + + Passphrase Dialog + Passphrase Dialog - - - TextLabel - - - - + Enter passphrase - + Enter passphrase - + New passphrase - + New passphrase - + Repeat new passphrase - + Repeat new passphrase - + Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>10 or more random characters</b>, or <b>eight or more words</b>. - + Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>10 or more random characters</b>, or <b>eight or more words</b>. - + Encrypt wallet - + Encrypt wallet - + This operation needs your wallet passphrase to unlock the wallet. - + This operation needs your wallet passphrase to unlock the wallet. - + Unlock wallet - + Unlock wallet - + This operation needs your wallet passphrase to decrypt the wallet. - + This operation needs your wallet passphrase to decrypt the wallet. - + Decrypt wallet - + Decrypt wallet - + Change passphrase - + Change passphrase - + Enter the old and new passphrase to the wallet. - + Enter the old and new passphrase to the wallet. - + Confirm wallet encryption - + Confirm wallet encryption - - WARNING: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR COINS</b>! -Are you sure you wish to encrypt your wallet? - + + Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>! + Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>! - - - Wallet encrypted - + + Are you sure you wish to encrypt your wallet? + Are you sure you wish to encrypt your wallet? - - NovaCoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your NovaCoins from being stolen by malware infecting your computer. + + IMPORTANT: Any previous backups you have made of your wallet file should be replaced with the newly generated, encrypted wallet file. For security reasons, previous backups of the unencrypted wallet file will become useless as soon as you start using the new, encrypted wallet. - - - Warning: The Caps Lock key is on. - + + + Warning: The Caps Lock key is on! + Warning: The Caps Lock key is on! + + + + + Wallet encrypted + Wallet encrypted + + + + NovaCoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer. + NovaCoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer. - - - - + + + + Wallet encryption failed - + Wallet encryption failed - + Wallet encryption failed due to an internal error. Your wallet was not encrypted. - + Wallet encryption failed due to an internal error. Your wallet was not encrypted. - - + + The supplied passphrases do not match. - + The supplied passphrases do not match. - + Wallet unlock failed - + Wallet unlock failed - - - + + + The passphrase entered for the wallet decryption was incorrect. - + The passphrase entered for the wallet decryption was incorrect. - + Wallet decryption failed - + Wallet decryption failed - - Wallet passphrase was succesfully changed. - + + Wallet passphrase was successfully changed. + Wallet passphrase was successfully changed. BitcoinGUI - - NovaCoin Wallet - - - - - Show/Hide &NovaCoin - + + Sign &message... + Sign &message... - + Synchronizing with network... - + Synchronizing with network... - + &Overview - + &Overview - + Show general overview of wallet - + Show general overview of wallet - + &Transactions - + &Transactions - + Browse transaction history - + Browse transaction history - + &Address Book - + &Address Book - + Edit the list of stored addresses and labels - + Edit the list of stored addresses and labels - + &Receive coins - + &Receive coins - + Show the list of addresses for receiving payments - + Show the list of addresses for receiving payments - + &Send coins - - - - - Send coins to a NovaCoin address - + &Send coins - - Sign &message - - - - - Prove you control an address - - - - + E&xit - + E&xit - + Quit application - - - - - &About %1 - + Quit application - + Show information about NovaCoin - + Show information about NovaCoin - + About &Qt - + About &Qt - + Show information about Qt - + Show information about Qt - + &Options... - + &Options... - - Modify configuration options for NovaCoin - + + &Encrypt Wallet... + &Encrypt Wallet... + + + + &Backup Wallet... + &Backup Wallet... + + + + &Change Passphrase... + &Change Passphrase... - + ~%n block(s) remaining ~%n block remaining @@ -411,97 +408,119 @@ Are you sure you wish to encrypt your wallet? - + Downloaded %1 of %2 blocks of transaction history (%3% done). - + Downloaded %1 of %2 blocks of transaction history (%3% done). - + &Export... - + &Export... - - Show or hide the NovaCoin window - + + Send coins to a NovaCoin address + Send coins to a NovaCoin address - - Export the data in the current tab to a file - + + Modify configuration options for NovaCoin + Modify configuration options for NovaCoin - - &Encrypt Wallet - + + Export the data in the current tab to a file + Export the data in the current tab to a file - + Encrypt or decrypt wallet - + Encrypt or decrypt wallet - - &Backup Wallet - + + Backup wallet to another location + Backup wallet to another location - - Backup wallet to another location - + + Change the passphrase used for wallet encryption + Change the passphrase used for wallet encryption - - &Change Passphrase - + + &Debug window + &Debug window - - Change the passphrase used for wallet encryption - + + Open debugging and diagnostic console + Open debugging and diagnostic console + + + + &Verify message... + &Verify message... + + + + NovaCoin + NovaCoin + + + + Wallet + Wallet + + + + &About NovaCoin + &About NovaCoin + + + + &Show / Hide + &Show / Hide - + &File - + &File - + &Settings - + &Settings - + &Help - + &Help - + Tabs toolbar - + Tabs toolbar - + Actions toolbar - + Actions toolbar - + + [testnet] - + [testnet] - + + NovaCoin client - - - - - novacoin-qt - + NovaCoin client - + %n active connection(s) to NovaCoin network %n active connection to NovaCoin network @@ -509,12 +528,12 @@ Are you sure you wish to encrypt your wallet? - + Downloaded %1 blocks of transaction history. - + Downloaded %1 blocks of transaction history. - + %n second(s) ago %n second ago @@ -522,7 +541,7 @@ Are you sure you wish to encrypt your wallet? - + %n minute(s) ago %n minute ago @@ -530,7 +549,7 @@ Are you sure you wish to encrypt your wallet? - + %n hour(s) ago %n hour ago @@ -538,7 +557,7 @@ Are you sure you wish to encrypt your wallet? - + %n day(s) ago %n day ago @@ -546,839 +565,1265 @@ Are you sure you wish to encrypt your wallet? - + Up to date - + Up to date - + Catching up... - + Catching up... - + Last received block was generated %1. - + Last received block was generated %1. - + This transaction is over the size limit. You can still send it for a fee of %1, which goes to the nodes that process your transaction and helps to support the network. Do you want to pay the fee? - - Sending... - + + Confirm transaction fee + Confirm transaction fee - + Sent transaction - + Sent transaction - + Incoming transaction - + Incoming transaction - + Date: %1 Amount: %2 Type: %3 Address: %4 - + Date: %1 +Amount: %2 +Type: %3 +Address: %4 + + + + + + URI handling + URI handling + + + + + URI can not be parsed! This can be caused by an invalid NovaCoin address or malformed URI parameters. + URI can not be parsed! This can be caused by an invalid NovaCoin address or malformed URI parameters. - + Wallet is <b>encrypted</b> and currently <b>unlocked</b> - + Wallet is <b>encrypted</b> and currently <b>unlocked</b> - + Wallet is <b>encrypted</b> and currently <b>locked</b> - + Wallet is <b>encrypted</b> and currently <b>locked</b> - + Backup Wallet - + Backup Wallet - + Wallet Data (*.dat) - + Wallet Data (*.dat) - + Backup Failed - + Backup Failed - + There was an error trying to save the wallet data to the new location. - - - - - DisplayOptionsPage - - - &Unit to show amounts in: - + There was an error trying to save the wallet data to the new location. - - Choose the default subdivision unit to show in the interface, and when sending coins - + + A fatal error occurred. NovaCoin can no longer continue safely and will quit. + A fatal error occurred. NovaCoin can no longer continue safely and will quit. + + + ClientModel - - Display addresses in transaction list - + + Network Alert + Network Alert EditAddressDialog - + Edit Address - + Edit Address - + &Label - + &Label - + The label associated with this address book entry - + The label associated with this address book entry - + &Address - + &Address - + The address associated with this address book entry. This can only be modified for sending addresses. - + The address associated with this address book entry. This can only be modified for sending addresses. - + New receiving address - + New receiving address - + New sending address - + New sending address - + Edit receiving address - + Edit receiving address - + Edit sending address - + Edit sending address - + The entered address "%1" is already in the address book. - + The entered address "%1" is already in the address book. - + The entered address "%1" is not a valid NovaCoin address. - + The entered address "%1" is not a valid NovaCoin address. - + Could not unlock wallet. - + Could not unlock wallet. - + New key generation failed. - + New key generation failed. - MainOptionsPage + GUIUtil::HelpMessageBox - - &Start NovaCoin on window system startup - + + + NovaCoin-Qt + NovaCoin-Qt - - Automatically start NovaCoin after the computer is turned on - + + version + version - - &Minimize to the tray instead of the taskbar - + + Usage: + Usage: - - Show only a tray icon after minimizing the window - + + command-line options + command-line options - - Map port using &UPnP - + + UI options + UI options - - Automatically open the NovaCoin client port on the router. This only works when your router supports UPnP and it is enabled. - + + Set language, for example "de_DE" (default: system locale) + Set language, for example "de_DE" (default: system locale) - - M&inimize on close - + + Start minimized + Start minimized - - Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. - + + Show splash screen on startup (default: 1) + Show splash screen on startup (default: 1) + + + OptionsDialog - - &Connect through SOCKS4 proxy: - + + Options + Options - - Connect to the Bitcon network through a SOCKS4 proxy (e.g. when connecting through Tor) - + + &Main + &Main - - Proxy &IP: - + + Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. + Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. - - IP address of the proxy (e.g. 127.0.0.1) - + + Pay transaction &fee + Pay transaction &fee - - &Port: - + + Automatically start NovaCoin after logging in to the system. + Automatically start NovaCoin after logging in to the system. - - Port of the proxy (e.g. 1234) - + + &Start NovaCoin on system login + &Start NovaCoin on system login - - Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. - + + Detach block and address databases at shutdown. This means they can be moved to another data directory, but it slows down shutdown. The wallet is always detached. + Detach block and address databases at shutdown. This means they can be moved to another data directory, but it slows down shutdown. The wallet is always detached. - - Pay transaction &fee - + + &Detach databases at shutdown + &Detach databases at shutdown - - Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. - + + &Network + &Network - - - MessagePage - - Message - + + Automatically open the NovaCoin client port on the router. This only works when your router supports UPnP and it is enabled. + Automatically open the NovaCoin client port on the router. This only works when your router supports UPnP and it is enabled. - - You can sign messages with your addresses to prove you own them. Be careful not to sign anything vague, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to. - + + Map port using &UPnP + Map port using &UPnP - - The address to sign the message with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - + + Connect to the NovaCoin network through a SOCKS proxy (e.g. when connecting through Tor). + Connect to the NovaCoin network through a SOCKS proxy (e.g. when connecting through Tor). - - Choose adress from address book - + + &Connect through SOCKS proxy: + &Connect through SOCKS proxy: - - Alt+A - + + Proxy &IP: + Proxy &IP: - - Paste address from clipboard - + + IP address of the proxy (e.g. 127.0.0.1) + IP address of the proxy (e.g. 127.0.0.1) - - Alt+P - + + &Port: + &Port: - - Enter the message you want to sign here - + + Port of the proxy (e.g. 9050) + Port of the proxy (e.g. 9050) - - Click "Sign Message" to get signature - + + SOCKS &Version: + SOCKS &Version: - - Sign a message to prove you own this address - + + SOCKS version of the proxy (e.g. 5) + SOCKS version of the proxy (e.g. 5) - - &Sign Message - + + &Window + &Window - - Copy the currently selected address to the system clipboard - + + Show only a tray icon after minimizing the window. + Show only a tray icon after minimizing the window. - - &Copy to Clipboard - + + &Minimize to the tray instead of the taskbar + &Minimize to the tray instead of the taskbar - - - - Error signing - + + Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. + Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. - - %1 is not a valid address. - + + M&inimize on close + M&inimize on close - - Private key for %1 is not available. - + + &Display + &Display - - Sign failed - + + User Interface &language: + User Interface &language: - - - OptionsDialog - - Main - + + The user interface language can be set here. This setting will take effect after restarting NovaCoin. + The user interface language can be set here. This setting will take effect after restarting NovaCoin. - - Display - + + &Unit to show amounts in: + &Unit to show amounts in: - - Options - + + Choose the default subdivision unit to show in the interface and when sending coins. + Choose the default subdivision unit to show in the interface and when sending coins. + + + + Whether to show NovaCoin addresses in the transaction list or not. + Whether to show NovaCoin addresses in the transaction list or not. + + + + &Display addresses in transaction list + &Display addresses in transaction list + + + + &OK + &OK + + + + &Cancel + &Cancel + + + + &Apply + &Apply + + + + default + default + + + + + Warning + Warning + + + + + This setting will take effect after restarting NovaCoin. + This setting will take effect after restarting NovaCoin. + + + + The supplied proxy address is invalid. + The supplied proxy address is invalid. OverviewPage - + Form - + Form - - Balance: - + + + The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. + The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. - - 123.456 BTC - + + Balance: + Balance: - + Number of transactions: - + Number of transactions: - - 0 - + + Unconfirmed: + Unconfirmed: - - Unconfirmed: - + + Wallet + Wallet - - 0 BTC - + + Immature: + Immature: - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Wallet</span></p></body></html> - + + Mined balance that has not yet matured + Mined balance that has not yet matured - + <b>Recent transactions</b> - + <b>Recent transactions</b> - + Your current balance - + Your current balance - + Total of transactions that have yet to be confirmed, and do not yet count toward the current balance - + Total of transactions that have yet to be confirmed, and do not yet count toward the current balance - + Total number of transactions in wallet - + Total number of transactions in wallet + + + + + out of sync + out of sync QRCodeDialog - - Dialog - + + QR Code Dialog + QR Code Dialog - - QR Code - - - - + Request Payment - + Request Payment - + Amount: - + Amount: - - BTC - - - - + Label: - + Label: - + Message: - + Message: - + &Save As... - + &Save As... + + + + Error encoding URI into QR Code. + Error encoding URI into QR Code. + + + + The entered amount is invalid, please check. + The entered amount is invalid, please check. - + Resulting URI too long, try to reduce the text for label / message. - + Resulting URI too long, try to reduce the text for label / message. - - Save Image... - + + Save QR Code + Save QR Code - + PNG Images (*.png) - + PNG Images (*.png) + + + + RPCConsole + + + Client name + Client name + + + + + + + + + + + + + N/A + N/A + + + + Client version + Client version + + + + &Information + &Information + + + + Using OpenSSL version + Using OpenSSL version + + + + Startup time + Startup time + + + + Network + Network + + + + Number of connections + Number of connections + + + + On testnet + On testnet + + + + Block chain + Block chain + + + + Current number of blocks + Current number of blocks + + + + Estimated total blocks + Estimated total blocks + + + + Last block time + Last block time + + + + &Open + &Open + + + + Command-line options + Command-line options + + + + Show the NovaCoin-Qt help message to get a list with possible NovaCoin command-line options. + Show the NovaCoin-Qt help message to get a list with possible NovaCoin command-line options. + + + + &Show + &Show + + + + &Console + &Console + + + + Build date + Build date + + + + NovaCoin - Debug window + NovaCoin - Debug window + + + + NovaCoin Core + NovaCoin Core + + + + Debug log file + Debug log file + + + + Open the NovaCoin debug log file from the current data directory. This can take a few seconds for large log files. + Open the NovaCoin debug log file from the current data directory. This can take a few seconds for large log files. + + + + Clear console + Clear console + + + + Welcome to the NovaCoin RPC console. + Welcome to the NovaCoin RPC console. + + + + Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen. + Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen. + + + + Type <b>help</b> for an overview of available commands. + Type <b>help</b> for an overview of available commands. SendCoinsDialog - - - - - - - - + + + + + + + + Send Coins - + Send Coins - + Send to multiple recipients at once - + Send to multiple recipients at once - - &Add recipient... + + Add &Recipient - + Remove all transaction fields - + Remove all transaction fields - - Clear all - + + Clear &All + Clear &All - + Balance: - + Balance: - + 123.456 BTC - + 123.456 BTC - + Confirm the send action - + Confirm the send action - - &Send + + S&end - + <b>%1</b> to %2 (%3) - + <b>%1</b> to %2 (%3) - + Confirm send coins - + Confirm send coins - + Are you sure you want to send %1? - + Are you sure you want to send %1? - + and - + and - - The recepient address is not valid, please recheck. - + + The recipient address is not valid, please recheck. + The recipient address is not valid, please recheck. - + The amount to pay must be larger than 0. - + The amount to pay must be larger than 0. - - Amount exceeds your balance - + + The amount exceeds your balance. + The amount exceeds your balance. - - Total exceeds your balance when the %1 transaction fee is included - + + The total exceeds your balance when the %1 transaction fee is included. + The total exceeds your balance when the %1 transaction fee is included. - - Duplicate address found, can only send to each address once in one send operation - + + Duplicate address found, can only send to each address once per send operation. + Duplicate address found, can only send to each address once per send operation. - - Error: Transaction creation failed - + + Error: Transaction creation failed. + Error: Transaction creation failed. - - Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. - + + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. SendCoinsEntry - + Form - + Form - + A&mount: - + A&mount: - + Pay &To: - + Pay &To: - - + + Enter a label for this address to add it to your address book - + Enter a label for this address to add it to your address book - + &Label: - + &Label: - + The address to send the payment to (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - + Choose address from address book - + Choose address from address book - + Alt+A - + Alt+A - + Paste address from clipboard - + Paste address from clipboard - + Alt+P - + Alt+P - + Remove this recipient - + Remove this recipient - + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - TransactionDesc + SignVerifyMessageDialog - - Open for %1 blocks - + + Signatures - Sign / Verify a Message + Signatures - Sign / Verify a Message + + + + + &Sign Message + &Sign Message + + + + You can sign messages with your addresses to prove you own them. Be careful not to sign anything vague, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to. + You can sign messages with your addresses to prove you own them. Be careful not to sign anything vague, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to. + + + + The address to sign the message with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + The address to sign the message with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + + + Choose an address from the address book + Choose an address from the address book + + + + + Alt+A + Alt+A + + + + Paste address from clipboard + Paste address from clipboard + + + + Alt+P + Alt+P + + + + Enter the message you want to sign here + Enter the message you want to sign here + + + + Copy the current signature to the system clipboard + Copy the current signature to the system clipboard + + + + Sign the message to prove you own this NovaCoin address + Sign the message to prove you own this NovaCoin address + + + + Reset all sign message fields + Reset all sign message fields + + + + + Clear &All + Clear &All + + + + + &Verify Message + &Verify Message + + + + Enter the signing address, message (ensure you copy line breaks, spaces, tabs, etc. exactly) and signature below to verify the message. Be careful not to read more into the signature than what is in the signed message itself, to avoid being tricked by a man-in-the-middle attack. + Enter the signing address, message (ensure you copy line breaks, spaces, tabs, etc. exactly) and signature below to verify the message. Be careful not to read more into the signature than what is in the signed message itself, to avoid being tricked by a man-in-the-middle attack. + + + + The address the message was signed with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + The address the message was signed with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + + Verify the message to ensure it was signed with the specified NovaCoin address + Verify the message to ensure it was signed with the specified NovaCoin address + + + + Reset all verify message fields + Reset all verify message fields + + + + + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + + Click "Sign Message" to generate signature + Click "Sign Message" to generate signature + + + + Enter NovaCoin signature + Enter NovaCoin signature + + + + + The entered address is invalid. + The entered address is invalid. + + + + + + + Please check the address and try again. + Please check the address and try again. + + + + + The entered address does not refer to a key. + The entered address does not refer to a key. + + + + Wallet unlock was cancelled. + Wallet unlock was cancelled. + + + + Private key for the entered address is not available. + Private key for the entered address is not available. + + + + Message signing failed. + Message signing failed. + + + + Message signed. + Message signed. + + + + The signature could not be decoded. + The signature could not be decoded. - + + + Please check the signature and try again. + Please check the signature and try again. + + + + The signature did not match the message digest. + The signature did not match the message digest. + + + + Message verification failed. + Message verification failed. + + + + Message verified. + Message verified. + + + + TransactionDesc + + Open until %1 - + Open until %1 + + + + Open for %n block(s) + + Open for %n block + Open for %n blocks + - - %1/offline? - + + %1/offline + %1/offline - + %1/unconfirmed - + %1/unconfirmed - + %1 confirmations - + %1 confirmations - - <b>Status:</b> - + + Status + Status + + + + , broadcast through %n node(s) + + , broadcast through %n node + , broadcast through %n nodes + - - , has not been successfully broadcast yet - + + Date + Date - - , broadcast through %1 node - + + Source + Source - - , broadcast through %1 nodes - + + Generated + Generated - - <b>Date:</b> - + + + From + From - - <b>Source:</b> Generated<br> - + + + + To + To - - - <b>From:</b> - + + + own address + own address - - unknown - + + label + label - - - - <b>To:</b> - + + + + + + Credit + Credit + + + + matures in %n more block(s) + + matures in %n more block + matures in %n more blocks + - - (yours, label: - + + not accepted + not accepted - - (yours) - + + + + + Debit + Debit - - - - - <b>Credit:</b> - + + Transaction fee + Transaction fee - - (%1 matures in %2 more blocks) - + + Net amount + Net amount - - (not accepted) - + + Message + Message - - - - <b>Debit:</b> - + + Comment + Comment - - <b>Transaction fee:</b> - + + Transaction ID + Transaction ID - - <b>Net amount:</b> - + + Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours. + Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours. - - Message: - + + Debug information + Debug information - - Comment: - + + Transaction + Transaction - - Transaction ID: - + + Inputs + Inputs - - Generated coins must wait 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to "not accepted" and not be spendable. This may occasionally happen if another node generates a block within a few seconds of yours. - + + Amount + Amount + + + + true + true + + + + false + false + + + + , has not been successfully broadcast yet + , has not been successfully broadcast yet + + + + unknown + unknown TransactionDescDialog - + Transaction details - + Transaction details - + This pane shows a detailed description of the transaction - + This pane shows a detailed description of the transaction TransactionTableModel - + Date - + Date - + Type - + Type - + Address - + Address - + Amount - + Amount - + Open for %n block(s) Open for %n block @@ -1386,716 +1831,865 @@ p, li { white-space: pre-wrap; } - + Open until %1 - + Open until %1 - + Offline (%1 confirmations) - + Offline (%1 confirmations) - + Unconfirmed (%1 of %2 confirmations) - + Unconfirmed (%1 of %2 confirmations) - + Confirmed (%1 confirmations) - + Confirmed (%1 confirmations) - - Mined balance will be available in %n more blocks + + Mined balance will be available when it matures in %n more block(s) - Mined balance will be available in %n more block - Mined balance will be available in %n more blocks + Mined balance will be available when it matures in %n more block + Mined balance will be available when it matures in %n more blocks - + This block was not received by any other nodes and will probably not be accepted! - + This block was not received by any other nodes and will probably not be accepted! - + Generated but not accepted - + Generated but not accepted - + Received with - + Received with - + Received from - + Received from - + Sent to - + Sent to - + Payment to yourself - + Payment to yourself - + Mined - + Mined - + (n/a) - + (n/a) - + Transaction status. Hover over this field to show number of confirmations. - + Transaction status. Hover over this field to show number of confirmations. - + Date and time that the transaction was received. - + Date and time that the transaction was received. - + Type of transaction. - + Type of transaction. - + Destination address of transaction. - + Destination address of transaction. - + Amount removed from or added to balance. - + Amount removed from or added to balance. TransactionView - - + + All - + All - + Today - + Today - + This week - + This week - + This month - + This month - + Last month - + Last month - + This year - + This year - + Range... - + Range... - + Received with - + Received with - + Sent to - + Sent to - + To yourself - + To yourself - + Mined - + Mined - + Other - + Other - + Enter address or label to search - + Enter address or label to search - + Min amount - + Min amount - + Copy address - + Copy address - + Copy label - + Copy label - + Copy amount - + Copy amount - + Edit label - + Edit label - - Show details... - + + Show transaction details + Show transaction details - + Export Transaction Data - + Export Transaction Data - + Comma separated file (*.csv) - + Comma separated file (*.csv) - + Confirmed - + Confirmed - + Date - + Date - + Type - + Type - + Label - + Label - + Address - + Address - + Amount - + Amount - + ID - + ID - + Error exporting - + Error exporting - + Could not write to file %1. - + Could not write to file %1. - + Range: - + Range: - + to - + to WalletModel - + Sending... - + Sending... bitcoin-core - + NovaCoin version - + NovaCoin version - + Usage: - + Usage: - - Send command to -server or novacoind - + + Send command to -server or bitcoind + Send command to -server or bitcoind - + List commands - + List commands - + Get help for a command - + Get help for a command - + Options: - + Options: - + Specify configuration file (default: novacoin.conf) - + Specify configuration file (default: novacoin.conf) - + Specify pid file (default: novacoind.pid) - + Specify pid file (default: novacoind.pid) - + Generate coins - + Generate coins - + Don't generate coins - + Don't generate coins - - Start minimized - + + Specify data directory + Specify data directory - - Show splash screen on startup (default: 1) - + + Set database cache size in megabytes (default: 25) + Set database cache size in megabytes (default: 25) - - Specify data directory - + + Set database disk log size in megabytes (default: 100) + Set database disk log size in megabytes (default: 100) - - Set database cache size in megabytes (default: 25) - + + Listen for connections on <port> (default: 8333 or testnet: 18333) + Listen for connections on <port> (default: 8333 or testnet: 18333) - - Set database disk log size in megabytes (default: 100) - + + Maintain at most <n> connections to peers (default: 125) + Maintain at most <n> connections to peers (default: 125) - - Specify connection timeout (in milliseconds) - + + Connect to a node to retrieve peer addresses, and disconnect + Connect to a node to retrieve peer addresses, and disconnect + + + + Specify your own public address + Specify your own public address + + + + Bind to given address. Use [host]:port notation for IPv6 + Bind to given address. Use [host]:port notation for IPv6 + + + + Threshold for disconnecting misbehaving peers (default: 100) + Threshold for disconnecting misbehaving peers (default: 100) - - Connect through socks4 proxy + + Number of seconds to keep misbehaving peers from reconnecting (default: 86400) + Number of seconds to keep misbehaving peers from reconnecting (default: 86400) + + + + An error occurred while setting up the RPC port %u for listening on IPv4: %s - - Allow DNS lookups for addnode and connect + + Detach block and address databases. Increases shutdown time (default: 0) + Detach block and address databases. Increases shutdown time (default: 0) + + + + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. - - Listen for connections on <port> (default: 8333 or testnet: 18333) + + Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds - - Maintain at most <n> connections to peers (default: 125) + + Listen for JSON-RPC connections on <port> (default: 8344 or testnet: 18344) - - Connect only to the specified node + + Accept command line and JSON-RPC commands + Accept command line and JSON-RPC commands + + + + Error: Transaction creation failed - - Threshold for disconnecting misbehaving peers (default: 100) + + Error: Wallet locked, unable to create transaction - - Number of seconds to keep misbehaving peers from reconnecting (default: 86400) + + Importing blockchain data file. - - Maximum per-connection receive buffer, <n>*1000 bytes (default: 10000) + + Importing bootstrap blockchain data file. - - Maximum per-connection send buffer, <n>*1000 bytes (default: 10000) + + Run in the background as a daemon and accept commands + Run in the background as a daemon and accept commands + + + + Use the test network + Use the test network + + + + Accept connections from outside (default: 1 if no -proxy or -connect) + Accept connections from outside (default: 1 if no -proxy or -connect) + + + + An error occurred while setting up the RPC port %u for listening on IPv6, falling back to IPv4: %s - - Accept command line and JSON-RPC commands + + Error initializing database environment %s! To recover, BACKUP THAT DIRECTORY, then remove everything from it except for wallet.dat. - - Run in the background as a daemon and accept commands + + Set maximum size of high-priority/low-fee transactions in bytes (default: 27000) + Set maximum size of high-priority/low-fee transactions in bytes (default: 27000) + + + + Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction. + Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction. + + + + Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade. + Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade. + + + + Warning: Please check that your computer's date and time are correct! If your clock is wrong NovaCoin will not work properly. + Warning: Please check that your computer's date and time are correct! If your clock is wrong NovaCoin will not work properly. + + + + Warning: error reading wallet.dat! All keys read correctly, but transaction data or address book entries might be missing or incorrect. - - Use the test network + + Warning: wallet.dat corrupt, data salvaged! Original wallet.dat saved as wallet.{timestamp}.bak in %s; if your balance or transactions are incorrect you should restore from a backup. - - Output extra debugging information + + Attempt to recover private keys from a corrupt wallet.dat - + + Block creation options: + Block creation options: + + + + Connect only to the specified node(s) + Connect only to the specified node(s) + + + + Discover own IP address (default: 1 when listening and no -externalip) + Discover own IP address (default: 1 when listening and no -externalip) + + + + Failed to listen on any port. Use -listen=0 if you want this. + Failed to listen on any port. Use -listen=0 if you want this. + + + + Find peers using DNS lookup (default: 0) + Find peers using DNS lookup (default: 0) + + + + Invalid -tor address: '%s' + Invalid -tor address: '%s' + + + + Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000) + Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000) + + + + Maximum per-connection send buffer, <n>*1000 bytes (default: 1000) + Maximum per-connection send buffer, <n>*1000 bytes (default: 1000) + + + + Only connect to nodes in network <net> (IPv4, IPv6 or Tor) + Only connect to nodes in network <net> (IPv4, IPv6 or Tor) + + + + Output extra debugging information. Implies all other -debug* options + Output extra debugging information. Implies all other -debug* options + + + + Output extra network debugging information + Output extra network debugging information + + + Prepend debug output with timestamp - + Prepend debug output with timestamp - + + SSL options: (see the Bitcoin Wiki for SSL setup instructions) + SSL options: (see the Bitcoin Wiki for SSL setup instructions) + + + + Select the version of socks proxy to use (4-5, default: 5) + Select the version of socks proxy to use (4-5, default: 5) + + + Send trace/debug info to console instead of debug.log file - + Send trace/debug info to console instead of debug.log file - + Send trace/debug info to debugger - + Send trace/debug info to debugger + + + + Set maximum block size in bytes (default: 250000) + Set maximum block size in bytes (default: 250000) + + + + Set minimum block size in bytes (default: 0) + Set minimum block size in bytes (default: 0) + + + + Shrink debug.log file on client startup (default: 1 when no -debug) + Shrink debug.log file on client startup (default: 1 when no -debug) + + + + Specify connection timeout in milliseconds (default: 5000) + Specify connection timeout in milliseconds (default: 5000) + + + + Use UPnP to map the listening port (default: 0) + Use UPnP to map the listening port (default: 0) + + + + Use UPnP to map the listening port (default: 1 when listening) + Use UPnP to map the listening port (default: 1 when listening) + + + + Use proxy to reach tor hidden services (default: same as -proxy) + Use proxy to reach tor hidden services (default: same as -proxy) - + Username for JSON-RPC connections - + Username for JSON-RPC connections - - Password for JSON-RPC connections + + Verifying database integrity... - - Listen for JSON-RPC connections on <port> (default: 8332) + + Warning: Disk space is low! + Warning: Disk space is low! + + + + Warning: This version is obsolete, upgrade required! + Warning: This version is obsolete, upgrade required! + + + + wallet.dat corrupt, salvage failed - + + Password for JSON-RPC connections + Password for JSON-RPC connections + + + Allow JSON-RPC connections from specified IP address - + Allow JSON-RPC connections from specified IP address - + Send commands to node running on <ip> (default: 127.0.0.1) - + Send commands to node running on <ip> (default: 127.0.0.1) - + Execute command when the best block changes (%s in cmd is replaced by block hash) - + Execute command when the best block changes (%s in cmd is replaced by block hash) - + Upgrade wallet to latest format - + Upgrade wallet to latest format - + Set key pool size to <n> (default: 100) - + Set key pool size to <n> (default: 100) - + Rescan the block chain for missing wallet transactions - + Rescan the block chain for missing wallet transactions - + How many blocks to check at startup (default: 2500, 0 = all) - + How many blocks to check at startup (default: 2500, 0 = all) - + How thorough the block verification is (0-6, default: 1) - + How thorough the block verification is (0-6, default: 1) - - -SSL options: (see the Bitcoin Wiki for SSL setup instructions) - + + Imports blocks from external blk000?.dat file + Imports blocks from external blk000?.dat file - + Use OpenSSL (https) for JSON-RPC connections - + Use OpenSSL (https) for JSON-RPC connections - + Server certificate file (default: server.cert) - + Server certificate file (default: server.cert) - + Server private key (default: server.pem) - + Server private key (default: server.pem) - + Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) - + Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) - + This help message - - - - - Usage - + This help message - + Cannot obtain a lock on data directory %s. NovaCoin is probably already running. - + Cannot obtain a lock on data directory %s. NovaCoin is probably already running. - + NovaCoin - + NovaCoin - - Loading addresses... - + + Unable to bind to %s on this computer (bind returned error %d, %s) + Unable to bind to %s on this computer (bind returned error %d, %s) - - Error loading addr.dat - + + Connect through socks proxy + Connect through socks proxy - + + Allow DNS lookups for -addnode, -seednode and -connect + Allow DNS lookups for -addnode, -seednode and -connect + + + + Loading addresses... + Loading addresses... + + + Error loading blkindex.dat - + Error loading blkindex.dat - + Error loading wallet.dat: Wallet corrupted - + Error loading wallet.dat: Wallet corrupted - + Error loading wallet.dat: Wallet requires newer version of NovaCoin - + Error loading wallet.dat: Wallet requires newer version of NovaCoin - + Wallet needed to be rewritten: restart NovaCoin to complete - + Wallet needed to be rewritten: restart NovaCoin to complete - + Error loading wallet.dat - + Error loading wallet.dat - - Error: Wallet locked, unable to create transaction - + + Invalid -proxy address: '%s' + Invalid -proxy address: '%s' - - Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds - + + Unknown network specified in -onlynet: '%s' + Unknown network specified in -onlynet: '%s' - - Error: Transaction creation failed - - - - - Sending... - + + Unknown -socks proxy version requested: %i + Unknown -socks proxy version requested: %i - - Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. - + + Cannot resolve -bind address: '%s' + Cannot resolve -bind address: '%s' - - Invalid amount - + + Cannot resolve -externalip address: '%s' + Cannot resolve -externalip address: '%s' - - Insufficient funds - + + Invalid amount for -paytxfee=<amount>: '%s' + Invalid amount for -paytxfee=<amount>: '%s' - - Loading block index... - + + Error: could not start node + Error: could not start node - - Add a node to connect to and attempt to keep the connection open - + + Sending... + Sending... - - Find peers using internet relay chat (default: 0) - + + Invalid amount + Invalid amount - - Accept connections from outside (default: 1) - + + Insufficient funds + Insufficient funds - - Set language, for example "de_DE" (default: system locale) - + + Loading block index... + Loading block index... - - Find peers using DNS lookup (default: 1) - + + Add a node to connect to and attempt to keep the connection open + Add a node to connect to and attempt to keep the connection open - - Use Universal Plug and Play to map the listening port (default: 1) - + + Unable to bind to %s on this computer. NovaCoin is probably already running. + Unable to bind to %s on this computer. NovaCoin is probably already running. - - Use Universal Plug and Play to map the listening port (default: 0) - + + Find peers using internet relay chat (default: 1) + Find peers using internet relay chat (default: 1) - + Fee per KB to add to transactions you send - + Fee per KB to add to transactions you send - + Loading wallet... - + Loading wallet... - + Cannot downgrade wallet - + Cannot downgrade wallet - + Cannot initialize keypool - + Cannot initialize keypool - + Cannot write default address - + Cannot write default address - + Rescanning... - + Rescanning... - + Done loading - + Done loading - - Invalid -proxy address - - - - - Invalid amount for -paytxfee=<amount> - - - - - Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction. - - - - - Error: CreateThread(StartNode) failed - - - - - Warning: Disk space is low - - - - - Unable to bind to port %d on this computer. NovaCoin is probably already running. - - - - + To use the %s option - + To use the %s option - + %s, you must set a rpcpassword in the configuration file: %s It is recommended you use the following random password: @@ -2104,29 +2698,28 @@ rpcpassword=%s (you do not need to remember this password) If the file does not exist, create it with owner-readable-only file permissions. - + %s, you must set a rpcpassword in the configuration file: + %s +It is recommended you use the following random password: +rpcuser=bitcoinrpc +rpcpassword=%s +(you do not need to remember this password) +If the file does not exist, create it with owner-readable-only file permissions. + - + Error - - - - - An error occured while setting up the RPC port %i for listening: %s - + Error - + You must set rpcpassword=<password> in the configuration file: %s If the file does not exist, create it with owner-readable-only file permissions. - - - - - Warning: Please check that your computer's date and time are correct. If your clock is wrong NovaCoin will not work properly. - + You must set rpcpassword=<password> in the configuration file: +%s +If the file does not exist, create it with owner-readable-only file permissions. diff --git a/src/qt/locale/bitcoin_ru.ts b/src/qt/locale/bitcoin_ru.ts index 17c70f1..d0c5e89 100644 --- a/src/qt/locale/bitcoin_ru.ts +++ b/src/qt/locale/bitcoin_ru.ts @@ -1,134 +1,139 @@ - + + + UTF-8 AboutDialog - + About NovaCoin О NovaCoin - + <b>NovaCoin</b> version <b>NovaCoin</b> версия - - Copyright © 2009-2012 NovaCoin Developers - + + Copyright © 2009-2012 The NovaCoin developers + Все права защищены © 2009-2012 Разработчики NovaCoin + + + + This is experimental software. -Distributed under the MIT/X11 software license, see the accompanying file license.txt or http://www.opensource.org/licenses/mit-license.php. +Distributed under the MIT/X11 software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php. This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard. - Все права защищены © 2009-2012 Разработчики NovaCoin - + Это экспериментальная программа. Распространяется на правах лицензии MIT/X11, см. файл license.txt или http://www.opensource.org/licenses/mit-license.php. -Этот продукт включате ПО, разработанное OpenSSL Project для использования в OpenSSL Toolkit (http://www.openssl.org/) и криптографическое ПО, написанное Eric Young (eay@cryptsoft.com) и ПО для работы с UPnP, написанное Thomas Bernard. +Этот продукт включает ПО, разработанное OpenSSL Project для использования в OpenSSL Toolkit (http://www.openssl.org/) и криптографическое ПО, написанное Eric Young (eay@cryptsoft.com) и ПО для работы с UPnP, написанное Thomas Bernard. AddressBookPage - + Address Book Адресная книга - - These are your NovaCoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. - Здесь перечислены Ваши адреса для получения платежей. Вы можете использовать их для того, чтобы давать разным людям разные адреса и таким образом иметь возможность отслеживать кто и сколько Вам платил, а так же поддерживать бо́льшую анонимность.. - - - + Double-click to edit address or label Для того, чтобы изменить адрес или метку давжды кликните по изменяемому объекту - + Create a new address Создать новый адрес - - &New Address... - &Создать адрес... - - - + Copy the currently selected address to the system clipboard Копировать текущий выделенный адрес в буфер обмена - - &Copy to Clipboard - &Kопировать + + &New Address + &Новый адрес + + + + These are your NovaCoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. + Это Ваши адреса для получения платежей. Вы можете дать разные адреса отправителям, чтобы отслеживать, кто именно вам платит. + + + + &Copy Address + &Копировать адрес - + Show &QR Code Показать &QR код - - Sign a message to prove you own this address - Подпишите сообщение для доказательства + + Sign a message to prove you own a NovaCoin address + Подписать сообщение, чтобы доказать владение адресом NovaCoin - - &Sign Message + + Sign &Message &Подписать сообщение - - Delete the currently selected address from the list. Only sending addresses can be deleted. - Удалить выделенный адрес из списка (могут быть удалены только записи из адресной книги). + + Delete the currently selected address from the list + Удалить выбранный адрес из списка - - &Delete - &Удалить + + Verify a message to ensure it was signed with a specified NovaCoin address + Проверить сообщение, чтобы убедиться, что оно было подписано указанным адресом NovaCoin - - Copy address - Копировать адрес + + &Verify Message + &Проверить сообщение - - Copy label - Копировать метку + + &Delete + &Удалить - - Edit - Правка + + Copy &Label + Копировать &метку - - Delete - Удалить + + &Edit + &Правка - + Export Address Book Data Экспортировать адресную книгу - + Comma separated file (*.csv) Текст, разделённый запятыми (*.csv) - + Error exporting Ошибка экспорта - + Could not write to file %1. Невозможно записать в файл %1. @@ -136,17 +141,17 @@ This product includes software developed by the OpenSSL Project for use in the O AddressTableModel - + Label Метка - + Address Адрес - + (no label) [нет метки] @@ -154,427 +159,454 @@ This product includes software developed by the OpenSSL Project for use in the O AskPassphraseDialog - - Dialog - Dialog - - - - - TextLabel - TextLabel + + Passphrase Dialog + Диалог ввода пароля - + Enter passphrase Введите пароль - + New passphrase Новый пароль - + Repeat new passphrase Повторите новый пароль - + Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>10 or more random characters</b>, or <b>eight or more words</b>. Введите новый пароль для бумажника. <br/> Пожалуйста, используйте фразы из <b>10 или более случайных символов,</b> или <b>восьми и более слов.</b> - + Encrypt wallet Зашифровать бумажник - + This operation needs your wallet passphrase to unlock the wallet. Для выполнения операции требуется пароль вашего бумажника. - + Unlock wallet Разблокировать бумажник - + This operation needs your wallet passphrase to decrypt the wallet. Для выполнения операции требуется пароль вашего бумажника. - + Decrypt wallet Расшифровать бумажник - + Change passphrase Сменить пароль - + Enter the old and new passphrase to the wallet. Введите старый и новый пароль для бумажника. - + Confirm wallet encryption Подтвердите шифрование бумажника - - WARNING: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR COINS</b>! -Are you sure you wish to encrypt your wallet? - ВНИМАНИЕ: Если вы зашифруете бумажник и потеряете свой ​​пароль, вы <b>ПОТЕРЯЕТЕ ВСЕ ВАШИ МОНЕТЫ!</b> -Вы действительно хотите зашифровать ваш бумажник? + + Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>! + Внимание: если вы зашифруете бумажник и потеряете пароль, вы <b>ПОТЕРЯЕТЕ ВСЕ ВАШИ БИТКОИНЫ</b>! - - + + Are you sure you wish to encrypt your wallet? + Вы уверены, что хотите зашифровать ваш бумажник? + + + + IMPORTANT: Any previous backups you have made of your wallet file should be replaced with the newly generated, encrypted wallet file. For security reasons, previous backups of the unencrypted wallet file will become useless as soon as you start using the new, encrypted wallet. + ВАЖНО: все предыдущие резервные копии вашего кошелька должны быть заменены новым зашифрованным файлом. В целях безопасности предыдущие резервные копии нешифрованного кошелька станут бесполезны, как только вы начнёте использовать новый шифрованный кошелёк. + + + + + Warning: The Caps Lock key is on! + Внимание: Caps Lock включен! + + + + Wallet encrypted Бумажник зашифрован - - NovaCoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your NovaCoins from being stolen by malware infecting your computer. + + NovaCoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer. Сейчас программа закроется для завершения процесса шифрования. Помните, что шифрование вашего бумажника не может полностью защитить ваши биткоины от кражи с помощью инфицирования вашего компьютера вредоносным ПО. - - - Warning: The Caps Lock key is on. - Внимание: Caps Lock включен. - - - - - - + + + + Wallet encryption failed Не удалось зашифровать бумажник - + Wallet encryption failed due to an internal error. Your wallet was not encrypted. Шифрование бумажника не удалось из-за внутренней ошибки. Ваш бумажник не был зашифрован. - - + + The supplied passphrases do not match. Введённые пароли не совпадают. - + Wallet unlock failed Разблокировка бумажника не удалась - - - + + + The passphrase entered for the wallet decryption was incorrect. Указанный пароль не подходит. - + Wallet decryption failed Расшифрование бумажника не удалось - - Wallet passphrase was succesfully changed. + + Wallet passphrase was successfully changed. Пароль бумажника успешно изменён. BitcoinGUI - - NovaCoin Wallet - NovaCoin-бумажник - - - - Show/Hide &NovaCoin - Показать/Спрятать бумажник + + Sign &message... + &Подписать сообщение - - + Synchronizing with network... Синхронизация с сетью... - - Block chain synchronization in progress - Идёт синхронизация цепочки блоков - - - + &Overview О&бзор - + Show general overview of wallet Показать общий обзор действий с бумажником - + &Transactions &Транзакции - + Browse transaction history Показать историю транзакций - + &Address Book &Адресная книга - + Edit the list of stored addresses and labels Изменить список сохранённых адресов и меток к ним - + &Receive coins &Получение монет - + Show the list of addresses for receiving payments Показать список адресов для получения платежей - + &Send coins Отп&равка монет - - Send coins to a NovaCoin address - Отправить монеты на указанный адрес - - - - Sign &message - Подписать &сообщение - - - - Prove you control an address - Доказать, что вы владеете адресом - - - + E&xit В&ыход - + Quit application Закрыть приложение - - &About %1 - &О %1 - - - + Show information about NovaCoin Показать информацию о NovaCoin'е - + About &Qt О &Qt - + Show information about Qt Показать информацию о Qt - + &Options... Оп&ции... - - Modify configuration options for NovaCoin - Изменить настройки + + &Encrypt Wallet... + &Зашифровать бумажник + + + + &Backup Wallet... + &Сделать резервную копию бумажника - - Open &NovaCoin - &Показать бумажник + + &Change Passphrase... + &Изменить пароль + + + + ~%n block(s) remaining + + остался ~%n блок + осталось ~%n блоков + осталось ~%n блоков + - - Show the NovaCoin window - Показать окно бумажника + + Downloaded %1 of %2 blocks of transaction history (%3% done). + Загружено %1 из %2 блоков истории операций (%3% завершено). - + &Export... &Экспорт... - - Export the data in the current tab to a file - + + Send coins to a NovaCoin address + Отправить монеты на указанный адрес NovaCoin - - &Encrypt Wallet - &Зашифровать бумажник + + Modify configuration options for NovaCoin + Изменить параметры конфигурации NovaCoin - + + Export the data in the current tab to a file + Экспортировать данные из вкладки в файл + + + Encrypt or decrypt wallet Зашифровать или расшифровать бумажник - - &Backup Wallet - Скопировать кошелек + + Backup wallet to another location + Сделать резервную копию бумажника в другом месте - - Backup wallet to another location - Скопировать кошелек в другое место + + Change the passphrase used for wallet encryption + Изменить пароль шифрования бумажника - - &Change Passphrase - &Изменить пароль + + &Debug window + &Окно отладки - - Change the passphrase used for wallet encryption - Изменить пароль шифрования бумажника + + Open debugging and diagnostic console + Открыть консоль отладки и диагностики - + + &Verify message... + &Проверить сообщение... + + + + NovaCoin + NovaCoin + + + + Wallet + Бумажник + + + + &About NovaCoin + &О NovaCoin + + + + &Show / Hide + &Показать / Скрыть + + + &File &Файл - + &Settings &Настройки - + &Help &Помощь - + Tabs toolbar Панель вкладок - + Actions toolbar Панель действий - + + [testnet] [тестовая сеть] - - novacoin-qt - novacoin-qt + + + NovaCoin client + NovaCoin клиент - + %n active connection(s) to NovaCoin network - %n активное соединение с сетью%n активных соединений с сетью%n активных соединений с сетью - - - - Downloaded %1 of %2 blocks of transaction history. - Загружено %1 из %2 блоков истории транзакций. - - - - Downloaded %1 of %2 blocks of transaction history (%3% done). - Загружено %1 из %2 блоков истории транзакций (%3% завершено). + + %n активное соединение с сетью + %n активных соединений с сетью + %n активных соединений с сетью + - + Downloaded %1 blocks of transaction history. Загружено %1 блоков истории транзакций. - + %n second(s) ago - %n секунду назад%n секунды назад%n секунд назад + + %n секунду назад + %n секунды назад + %n секунд назад + - + %n minute(s) ago - %n минуту назад%n минуты назад%n минут назад + + %n минуту назад + %n минуты назад + %n минут назад + - + %n hour(s) ago - %n час назад%n часа назад%n часов назад + + %n час назад + %n часа назад + %n часов назад + - + %n day(s) ago - %n день назад%n дня назад%n дней назад + + %n день назад + %n дня назад + %n дней назад + - + Up to date Синхронизированно - + Catching up... Синхронизируется... - + Last received block was generated %1. Последний полученный блок был сгенерирован %1. - + This transaction is over the size limit. You can still send it for a fee of %1, which goes to the nodes that process your transaction and helps to support the network. Do you want to pay the fee? - Данная транзакция превышает предельно допустимый размер. Но Вы можете всё равно совершить ей, добавив комиссию в %1, которая отправится тем узлам, которые обработают Вашу транзакцию и поможет поддержать сеть. Вы хотите добавить комиссию? + Данная транзакция превышает предельно допустимый размер. Но Вы можете всё равно совершить её, добавив комиссию в %1, которая отправится тем узлам, которые обработают Вашу транзакцию, и поможет поддержать сеть. Вы хотите добавить комиссию? - - Sending... - Отправка... + + Confirm transaction fee + Подтвердите комиссию - + Sent transaction Исходящая транзакция - + Incoming transaction Входящая транзакция - + Date: %1 Amount: %2 Type: %3 @@ -587,776 +619,1192 @@ Address: %4 - + + + URI handling + Обработка URI + + + + + URI can not be parsed! This can be caused by an invalid NovaCoin address or malformed URI parameters. + Не удалось обработать URI! Это может быть связано с неверным адресом NovaCoin или неправильными параметрами URI. + + + Wallet is <b>encrypted</b> and currently <b>unlocked</b> Бумажник <b>зашифрован</b> и в настоящее время <b>разблокирован</b> - + Wallet is <b>encrypted</b> and currently <b>locked</b> Бумажник <b>зашифрован</b> и в настоящее время <b>заблокирован</b> - + Backup Wallet - Скопировать кошелек + Сделать резервную копию бумажника - + Wallet Data (*.dat) - Данные Кошелька (*.dat) + Данные бумажника (*.dat) - + Backup Failed - + Резервное копирование не удалось - + There was an error trying to save the wallet data to the new location. - - - - - DisplayOptionsPage - - - &Unit to show amounts in: - &Измерять монеты в: + При попытке сохранения данных бумажника в новое место произошла ошибка. - - Choose the default subdivision unit to show in the interface, and when sending coins - Единица измерения количества монет при отображении и при отправке + + A fatal error occurred. NovaCoin can no longer continue safely and will quit. + Произошла неисправимая ошибка. NovaCoin не может безопасно продолжать работу и будет закрыт. + + + ClientModel - - Display addresses in transaction list - Показывать адреса в списке транзакций + + Network Alert + Сетевая Тревога EditAddressDialog - + Edit Address Изменить адрес - + &Label &Метка - + The label associated with this address book entry Метка, связанная с данной записью - + &Address &Адрес - + The address associated with this address book entry. This can only be modified for sending addresses. Адрес, связанный с данной записью. - + New receiving address Новый адрес для получения - + New sending address Новый адрес для отправки - + Edit receiving address Изменение адреса для получения - + Edit sending address Изменение адреса для отправки - + The entered address "%1" is already in the address book. Введённый адрес «%1» уже находится в адресной книге. - + The entered address "%1" is not a valid NovaCoin address. - Введённый адрес «%1» не является правильным NovaCoin-адресом. + Введённый адрес "%1" не является правильным NovaCoin-адресом. - + Could not unlock wallet. Не удается разблокировать бумажник. - + New key generation failed. Генерация нового ключа не удалась. - MainOptionsPage + GUIUtil::HelpMessageBox - - &Start NovaCoin on window system startup - &Запускать бумажник при входе в систему + + + NovaCoin-Qt + NovaCoin-Qt - - Automatically start NovaCoin after the computer is turned on - Автоматически запускать бумажник, когда включается компьютер + + version + версия - - &Minimize to the tray instead of the taskbar - &Cворачивать в системный лоток вместо панели задач + + Usage: + Использование: - - Show only a tray icon after minimizing the window - Показывать только иконку в системном лотке при сворачивании окна + + command-line options + параметры командной строки - - Map port using &UPnP - Пробросить порт через &UPnP + + UI options + Опции интерфейса - - Automatically open the NovaCoin client port on the router. This only works when your router supports UPnP and it is enabled. - Автоматически открыть порт для NovaCoin-клиента на роутере. Работает ТОЛЬКО если Ваш роутер поддерживает UPnP и данная функция включена. + + Set language, for example "de_DE" (default: system locale) + Выберите язык, например "de_DE" (по умолчанию: как в системе) - - M&inimize on close - С&ворачивать вместо закрытия + + Start minimized + Запускать свёрнутым - - Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. - Сворачивать вместо закрытия. Если данная опция будет выбрана — приложение закроется только после выбора соответствующего пункта в меню. + + Show splash screen on startup (default: 1) + Показывать сплэш при запуске (по умолчанию: 1) + + + OptionsDialog - - &Connect through SOCKS4 proxy: - &Подключаться через SOCKS4 прокси: + + Options + Опции - - Connect to the Bitcon network through a SOCKS4 proxy (e.g. when connecting through Tor) - Подключаться к сети NovaCoin через SOCKS4 прокси (например, при использовании Tor) + + &Main + &Главная - - Proxy &IP: - &IP Прокси: + + Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. + Опциональная комиссия за каждый КБ транзакции, которая позволяет быть уверенным, что Ваша транзакция будет обработана быстро. Большинство транзакций занимают 1КБ. Рекомендуется комиссия 0.01. - - IP address of the proxy (e.g. 127.0.0.1) - IP-адрес прокси (например 127.0.0.1) + + Pay transaction &fee + Заплатить ко&миссию - - &Port: - По&рт: + + Automatically start NovaCoin after logging in to the system. + Автоматически запускать NovaCoin после входа в систему - - Port of the proxy (e.g. 1234) - Порт прокси-сервера (например 1234) + + &Start NovaCoin on system login + &Запускать NovaCoin при входе в систему - - Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. - Опциональная комиссия за каждый КБ транзакции, которая позволяет быть уверенным, что Ваша транзакция будет обработано быстро. Большинство транзакций занимают 1КБ. Рекомендуется комиссия 0.01. + + Detach block and address databases at shutdown. This means they can be moved to another data directory, but it slows down shutdown. The wallet is always detached. + Отключить базы данных блоков и адресов при выходе. Это означает, что их можно будет переместить в другой каталог данных, но завершение работы будет медленнее. Бумажник всегда отключается. - - Pay transaction &fee - Добавлять ко&миссию + + &Detach databases at shutdown + &Отключать базы данных при выходе - - Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended. - Опциональная комиссия за каждый КБ транзакции, которая позволяет быть уверенным, что Ваша транзакция будет обработано быстро. Большинство транзакций занимают 1КБ. Рекомендуется комиссия 0.01. + + &Network + &Сеть - - - MessagePage - - Message - Сообщение + + Automatically open the NovaCoin client port on the router. This only works when your router supports UPnP and it is enabled. + Автоматически открыть порт для NovaCoin-клиента на роутере. Работает только если Ваш роутер поддерживает UPnP, и данная функция включена. - - You can sign messages with your addresses to prove you own them. Be careful not to sign anything vague, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to. - Вы можете подписать сообщение ключом от своего адреса, чтобы доказать владенгие им. Будьте осторожны и не подписывайте текст, не относящийся лично к вам. Подписывайте только текст, с которым вы полностью согласны и который можете однозначно интерпретировать. + + Map port using &UPnP + Пробросить порт через &UPnP - - The address to send the payment to (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - Адрес получателя платежа (например 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + Connect to the NovaCoin network through a SOCKS proxy (e.g. when connecting through Tor). + Подключаться к сети NovaCoin через прокси SOCKS (например, при подключении через Tor). - - Choose adress from address book - Выбрать адрес из адресной книги + + &Connect through SOCKS proxy: + &Подключаться через SOCKS прокси: - - Alt+A - Alt+A + + Proxy &IP: + &IP Прокси: - - Paste address from clipboard - Вставить адрес из буфера обмена + + IP address of the proxy (e.g. 127.0.0.1) + IP-адрес прокси (например 127.0.0.1) - - Alt+P - Alt+P + + &Port: + По&рт: - - Enter the message you want to sign here - Введите сообщение для подписи + + Port of the proxy (e.g. 9050) + Порт прокси-сервера (например, 9050) - - Click "Sign Message" to get signature - Для создания подписи нажмите на "Подписать сообщение" + + SOCKS &Version: + &Версия SOCKS: - - Sign a message to prove you own this address - Подпишите сообщение для доказательства + + SOCKS version of the proxy (e.g. 5) + Версия SOCKS-прокси (например, 5) - - &Sign Message - &Подписать сообщение + + &Window + &Окно - - Copy the currently selected address to the system clipboard - Копировать текущий выделенный адрес в буфер обмена + + Show only a tray icon after minimizing the window. + Показывать только иконку в системном лотке после сворачивания окна. - - &Copy to Clipboard - &Kопировать + + &Minimize to the tray instead of the taskbar + &Cворачивать в системный лоток вместо панели задач - - - - Error signing - Ошибка создания подписи + + Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. + Сворачивать вместо закрытия. Если данная опция будет выбрана — приложение закроется только после выбора соответствующего пункта в меню. - - %1 is not a valid address. - %1 не является правильным адресом. + + M&inimize on close + С&ворачивать при закрытии - - Private key for %1 is not available. - Секретный ключ для %1 не доступен + + &Display + О&тображение - - Sign failed - Подписание не удалось. + + User Interface &language: + &Язык интерфейса: - - - OptionsDialog - - Main - Основное + + The user interface language can be set here. This setting will take effect after restarting NovaCoin. + Здесь можно выбрать язык интерфейса. Настройки вступят в силу после перезапуска NovaCoin. - - Display - Отображение + + &Unit to show amounts in: + &Отображать суммы в единицах: - - Options - Опции + + Choose the default subdivision unit to show in the interface and when sending coins. + Выберите единицу измерения монет при отображении и отправке. + + + + Whether to show NovaCoin addresses in the transaction list or not. + Показывать ли адреса NovaCoin в списке транзакций. + + + + &Display addresses in transaction list + &Показывать адреса в списке транзакций + + + + &OK + О&К + + + + &Cancel + &Отмена + + + + &Apply + &Применить + + + + default + по умолчанию + + + + + Warning + Внимание + + + + + This setting will take effect after restarting NovaCoin. + Эта настройка вступит в силу после перезапуска NovaCoin + + + + The supplied proxy address is invalid. + Адрес прокси неверен. OverviewPage - + Form Форма - - Wallet - Бумажник + + + The displayed information may be out of date. Your wallet automatically synchronizes with the NovaCoin network after a connection is established, but this process has not completed yet. + Отображаемая информация может быть устаревшей. Ваш бумажник автоматически синхронизируется с сетью NovaCoin после подключения, но этот процесс пока не завершён. - + Balance: Баланс: - - Stake: - Доля: - - - - 123.456 BTC - 123.456 BTC - - - + Number of transactions: Количество транзакций: - - 0 - 0 - - - + Unconfirmed: Не подтверждено: - - 0 BTC - 0 BTC + + Wallet + Бумажник - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Wallet</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Бумажник</span></p></body></html> + + Immature: + Незрелые: - + + Mined balance that has not yet matured + Баланс добытых монет, который ещё не созрел + + + <b>Recent transactions</b> <b>Последние транзакции</b> - + Your current balance Ваш текущий баланс - + Total of transactions that have yet to be confirmed, and do not yet count toward the current balance Общая сумма всех транзакций, которые до сих пор не подтверждены, и до сих пор не учитываются в текущем балансе - + Total number of transactions in wallet Общее количество транзакций в Вашем бумажнике + + + + out of sync + не синхронизировано + QRCodeDialog - - Dialog - Dialog + + QR Code Dialog + Диалог QR-кода - - QR Code - QR код - - - + Request Payment Запросить платёж - + Amount: Количество: - - BTC - BTC - - - + Label: Метка: - + Message: Сообщение: - + &Save As... &Сохранить как... - - Save Image... - Сохранить изображение... + + Error encoding URI into QR Code. + Ошибка кодирования URI в QR-код - + + The entered amount is invalid, please check. + Введено неверное количество, проверьте ещё раз. + + + + Resulting URI too long, try to reduce the text for label / message. + Получившийся URI слишком длинный, попробуйте сократить текст метки / сообщения. + + + + Save QR Code + Сохранить QR-код + + + PNG Images (*.png) - + PNG Изображения (*.png) + + + + RPCConsole + + + Client name + Имя клиента + + + + + + + + + + + + + N/A + Н/Д + + + + Client version + Версия клиента + + + + &Information + &Информация + + + + Using OpenSSL version + Используется версия OpenSSL + + + + Startup time + Время запуска + + + + Network + Сеть + + + + Number of connections + Число подключений + + + + On testnet + В тестовой сети + + + + Block chain + Цепь блоков + + + + Current number of blocks + Текущее число блоков + + + + Estimated total blocks + Расчётное число блоков + + + + Last block time + Время последнего блока + + + + &Open + &Открыть + + + + Command-line options + Параметры командной строки + + + + Show the NovaCoin-Qt help message to get a list with possible NovaCoin command-line options. + Показать помощь по NovaCoin-Qt, чтобы получить список доступных параметров командной строки. + + + + &Show + &Показать + + + + &Console + Консоль + + + + Build date + Дата сборки + + + + NovaCoin - Debug window + NovaCoin - Окно отладки + + + + NovaCoin Core + Ядро NovaCoin + + + + Debug log file + Отладочный лог-файл + + + + Open the NovaCoin debug log file from the current data directory. This can take a few seconds for large log files. + Открыть отладочный лог-файл NovaCoin из текущего каталога данных. Это может занять несколько секунд для больших лог-файлов. + + + + Clear console + Очистить консоль + + + + Welcome to the NovaCoin RPC console. + Добро пожаловать в RPC-консоль NovaCoin. + + + + Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen. + Используйте стрелки вверх и вниз для просмотра истории и <b>Ctrl-L</b> для очистки экрана. + + + + Type <b>help</b> for an overview of available commands. + Напишите <b>help</b> для просмотра доступных команд. SendCoinsDialog - - - - - - - - + + + + + + + + Send Coins Отправка - + Send to multiple recipients at once Отправить нескольким получателям одновременно - - &Add recipient... - &Добавить получателя... + + Add &Recipient + &Добавить получателя - + Remove all transaction fields Удалить все поля транзакции - - Clear all - Очистить всё + + Clear &All + Очистить &всё - + Balance: Баланс: - + 123.456 BTC 123.456 BTC - + Confirm the send action Подтвердить отправку - - &Send + + S&end &Отправить - + <b>%1</b> to %2 (%3) <b>%1</b> адресату %2 (%3) - + Confirm send coins Подтвердите отправку монет - + Are you sure you want to send %1? Вы уверены, что хотите отправить %1? - + and и - - The recepient address is not valid, please recheck. + + The recipient address is not valid, please recheck. Адрес получателя неверный, пожалуйста, перепроверьте. - + The amount to pay must be larger than 0. Количество монет для отправки должно быть больше 0. - - Amount exceeds your balance + + The amount exceeds your balance. Количество отправляемых монет превышает Ваш баланс - - Total exceeds your balance when the %1 transaction fee is included - Сумма превысит Ваш баланс, если комиссия в %1 будет добавлена к транзакции + + The total exceeds your balance when the %1 transaction fee is included. + Сумма превысит Ваш баланс, если комиссия в размере %1 будет добавлена к транзакции - - Duplicate address found, can only send to each address once in one send operation + + Duplicate address found, can only send to each address once per send operation. Обнаружен дублирующийся адрес. Отправка на один и тот же адрес возможна только один раз за одну операцию отправки - - Error: Transaction creation failed - Ошибка: Создание транзакции не удалось + + Error: Transaction creation failed. + Ошибка: не удалось создать транзакцию. - - Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. - Ошибка: В транзакции отказано. Такое может произойти, если некоторые монеты уже были потрачены, например, если Вы используете одну копию бумажника (wallet.dat), а монеты были потрачены из другой копии, но не были отмечены как потраченные в этой. Или в случае кражи (компрометации) Вашего бумажника. + + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. + Ошибка: В транзакции отказано. Такое может произойти, если некоторые монеты уже были потрачены, например, если Вы используете одну копию файла wallet.dat, а монеты были потрачены из другой копии, но не были отмечены как потраченные в этой. SendCoinsEntry - + Form Форма - + A&mount: Ко&личество: - + Pay &To: Полу&чатель: - - + + Enter a label for this address to add it to your address book Введите метку для данного адреса (для добавления в адресную книгу) - + &Label: &Метка: - + The address to send the payment to (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) Адрес получателя платежа (например 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - + Choose address from address book Выберите адрес из адресной книги - + Alt+A Alt+A - + Paste address from clipboard Вставить адрес из буфера обмена - + Alt+P Alt+P - + Remove this recipient Удалить этого получателя - + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) Введите NovaCoin-адрес (например 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) - TransactionDesc + SignVerifyMessageDialog + + + Signatures - Sign / Verify a Message + Подписи - подписать/проверить сообщение + + + + + &Sign Message + &Подписать сообщение + + + + You can sign messages with your addresses to prove you own them. Be careful not to sign anything vague, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to. + Вы можете подписывать сообщения своими адресами, чтобы доказать владение ими. Будьте осторожны, не подписывайте что-то неопределённое, так как фишинговые атаки могут обманным путём заставить вас подписать нежелательные сообщения. Подписывайте только те сообщения, с которыми вы согласны вплоть до мелочей. + + + + The address to sign the message with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + Адрес, которым вы хотите подписать сообщение (напр. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + + + Choose an address from the address book + Выберите адрес из адресной книги + + + + + Alt+A + Alt+A + + + + Paste address from clipboard + Вставить адрес из буфера обмена + + + + Alt+P + Alt+P + + + + Enter the message you want to sign here + Введите сообщение для подписи + + + + Copy the current signature to the system clipboard + Скопировать текущую подпись в системный буфер обмена + + + + Sign the message to prove you own this NovaCoin address + Подписать сообщение, чтобы доказать владение адресом NovaCoin + + + + Reset all sign message fields + Сбросить значения всех полей подписывания сообщений + + + + + Clear &All + Очистить &всё + + + + + &Verify Message + &Проверить сообщение + + + + Enter the signing address, message (ensure you copy line breaks, spaces, tabs, etc. exactly) and signature below to verify the message. Be careful not to read more into the signature than what is in the signed message itself, to avoid being tricked by a man-in-the-middle attack. + Введите ниже адрес для подписи, сообщение (убедитесь, что переводы строк, пробелы, табы и т.п. в точности скопированы) и подпись, чтобы проверить сообщение. Убедитесь, что не скопировали лишнего в подпись, по сравнению с самим подписываемым сообщением, чтобы не стать жертвой атаки "man-in-the-middle". + + + + The address the message was signed with (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + Адрес, которым было подписано сообщение (напр. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + + Verify the message to ensure it was signed with the specified NovaCoin address + Проверить сообщение, чтобы убедиться, что оно было подписано указанным адресом NovaCoin + + + + Reset all verify message fields + Сбросить все поля проверки сообщения + + + + + Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + Введите адрес NovaCoin (напр. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5) + + + + Click "Sign Message" to generate signature + Нажмите "Подписать сообщение" для создания подписи + + + + Enter NovaCoin signature + Введите подпись NovaCoin + + + + + The entered address is invalid. + Введённый адрес неверен + + + + + + + Please check the address and try again. + Пожалуйста, проверьте адрес и попробуйте ещё раз. + + + + + The entered address does not refer to a key. + Введённый адрес не связан с ключом + + + + Wallet unlock was cancelled. + Разблокировка бумажника была отменена. + + + + Private key for the entered address is not available. + Для введённого адреса недоступен закрытый ключ + + + + Message signing failed. + Не удалось подписать сообщение + + + + Message signed. + Сообщение подписано + - - Open for %1 blocks - Открыто до получения %1 блоков + + The signature could not be decoded. + Подпись не может быть раскодирована. - + + + Please check the signature and try again. + Пожалуйста, проверьте подпись и попробуйте ещё раз. + + + + The signature did not match the message digest. + Подпись не соответствует отпечатку сообщения. + + + + Message verification failed. + Проверка сообщения не удалась. + + + + Message verified. + Сообщение проверено. + + + + TransactionDesc + + Open until %1 Открыто до %1 + + + Open for %n block(s) + + Открыто для %n блока + Открыто для %n блоков + Открыто для %n блоков + + - - %1/offline? - %1/оффлайн? + + %1/offline + %1/отключен - + %1/unconfirmed %1/не подтверждено - + %1 confirmations %1 подтверждений - - <b>Status:</b> - <b>Статус:</b> + + Status + Статус + + + + , broadcast through %n node(s) + + , разослано через %n узел + , разослано через %n узла + , разослано через %n узлов + - - , has not been successfully broadcast yet - , ещё не было успешно разослано + + Date + Дата - - , broadcast through %1 node - , разослано через %1 узел + + Source + Источник - - , broadcast through %1 nodes - , разослано через %1 узлов + + Generated + Сгенерированно - - <b>Date:</b> - <b>Дата:</b> + + + From + От - - <b>Source:</b> Generated<br> - <b>Источник:</b> [сгенерированно]<br> + + + + To + Для - - - <b>From:</b> - <b>Отправитель:</b> + + + own address + свой адрес - - unknown - неизвестно + + label + метка - - - - <b>To:</b> - <b>Получатель:</b> + + + + + + Credit + Кредит + + + + matures in %n more block(s) + + будет доступно через %n блок + будет доступно через %n блока + будет доступно через %n блоков + - - (yours, label: - (Ваш, метка: + + not accepted + не принято - - (yours) - (ваш) + + + + + Debit + Дебет - - - - - <b>Credit:</b> - <b>Кредит:</b> + + Transaction fee + Комиссия - - (%1 matures in %2 more blocks) - (%1 станет доступно через %2 блоков) + + Net amount + Чистая сумма - - (not accepted) - (не принято) + + Message + Сообщение - - - - <b>Debit:</b> - <b>Дебет:</b> + + Comment + Комментарий: - - <b>Transaction fee:</b> - <b>Комиссия:</b> + + Transaction ID + ID транзакции - - <b>Net amount:</b> - <b>Общая сумма:</b> + + Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours. + Сгенерированные монеты должны подождать 120 блоков, прежде чем они могут быть потрачены. Когда Вы сгенерировали этот блок, он был отправлен в сеть для добавления в цепочку блоков. Если данная процедура не удастся, статус изменится на «не подтверждено», и монеты будут недействительны. Это иногда происходит в случае, если другой узел сгенерирует блок на несколько секунд раньше вас. - - Message: - Сообщение: + + Debug information + Отладочная информация - - Comment: - Комментарий: + + Transaction + Транзакция - - Transaction ID: - Идентификатор транзакции: + + Inputs + Входы - - Generated coins must wait 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to "not accepted" and not be spendable. This may occasionally happen if another node generates a block within a few seconds of yours. - Сгенерированные монеты должны подождать 120 блоков прежде, чем они смогут быть отправлены. Когда Вы сгенерировали этот блок он был отправлен в сеть, чтобы он был добавлен к цепочке блоков. Если данная процедура не удастся, статус изменится на «не подтверждено» и монеты будут непередаваемыми. Такое может случайно происходить в случае, если другой узел сгенерирует блок на несколько секунд раньше. + + Amount + Количество + + + + true + истина + + + + false + ложь + + + + , has not been successfully broadcast yet + , ещё не было успешно разослано + + + + unknown + неизвестно TransactionDescDialog - + Transaction details Детали транзакции - + This pane shows a detailed description of the transaction Данный диалог показывает детализированную статистику по выбранной транзакции @@ -1364,117 +1812,125 @@ p, li { white-space: pre-wrap; } TransactionTableModel - + Date Дата - + Type Тип - + Address Адрес - + Amount Количество - + Open for %n block(s) - Открыто для %n блокаОткрыто для %n блоковОткрыто для %n блоков + + Открыто для %n блока + Открыто для %n блоков + Открыто для %n блоков + - + Open until %1 Открыто до %1 - + Offline (%1 confirmations) Оффлайн (%1 подтверждений) - + Unconfirmed (%1 of %2 confirmations) Не подтверждено (%1 из %2 подтверждений) - + Confirmed (%1 confirmations) Подтверждено (%1 подтверждений) - - Mined balance will be available in %n more blocks - Добытыми монетами можно будет воспользоваться через %n блокДобытыми монетами можно будет воспользоваться через %n блокаДобытыми монетами можно будет воспользоваться через %n блоков + + Mined balance will be available when it matures in %n more block(s) + + Добытыми монетами можно будет воспользоваться через %n блок + Добытыми монетами можно будет воспользоваться через %n блока + Добытыми монетами можно будет воспользоваться через %n блоков + - + This block was not received by any other nodes and will probably not be accepted! Этот блок не был получен другими узлами и, возможно, не будет принят! - + Generated but not accepted Сгенерированно, но не подтверждено - + Received with Получено - + Received from Получено от - + Sent to Отправлено - + Payment to yourself Отправлено себе - + Mined Добыто - + (n/a) [не доступно] - + Transaction status. Hover over this field to show number of confirmations. Статус транзакции. Подведите курсор к нужному полю для того, чтобы увидеть количество подтверждений. - + Date and time that the transaction was received. Дата и время, когда транзакция была получена. - + Type of transaction. Тип транзакции. - + Destination address of transaction. Адрес назначения транзакции. - + Amount removed from or added to balance. Сумма, добавленная, или снятая с баланса. @@ -1482,163 +1938,163 @@ p, li { white-space: pre-wrap; } TransactionView - - + + All Все - + Today Сегодня - + This week На этой неделе - + This month В этом месяце - + Last month За последний месяц - + This year В этом году - + Range... Промежуток... - + Received with Получено на - + Sent to Отправлено на - + To yourself Отправленные себе - + Mined Добытые - + Other Другое - + Enter address or label to search Введите адрес или метку для поиска - + Min amount Мин. сумма - + Copy address Копировать адрес - + Copy label Копировать метку - + Copy amount Скопировать сумму - + Edit label Изменить метку - - Show details... - Показать детали... + + Show transaction details + Показать подробности транзакции - + Export Transaction Data Экспортировать данные транзакций - + Comma separated file (*.csv) - Текс, разделённый запятыми (*.csv) + Текст, разделённый запятыми (*.csv) - + Confirmed Подтверждено - + Date Дата - + Type Тип - + Label Метка - + Address Адрес - + Amount Количество - + ID ID - + Error exporting Ошибка экспорта - + Could not write to file %1. Невозможно записать в файл %1. - + Range: Промежуток от: - + to до @@ -1646,7 +2102,7 @@ p, li { white-space: pre-wrap; } WalletModel - + Sending... Отправка.... @@ -1654,347 +2110,629 @@ p, li { white-space: pre-wrap; } bitcoin-core - + NovaCoin version Версия - + Usage: Использование: - - Send command to -server or novacoind - Отправить команду на -server или novacoind + + Send command to -server or bitcoind + Отправить команду на -server или bitcoind - + List commands Список команд - + Get help for a command Получить помощь по команде - + Options: Опции: - - Specify configuration file (default: bitcoin.conf) - Указать конфигурационный файл (по умолчанию: bitcoin.conf) + + Specify configuration file (default: novacoin.conf) + Указать конфигурационный файл (по умолчанию: novacoin.conf) - + Specify pid file (default: novacoind.pid) Указать pid-файл (по умолчанию: novacoin.pid) - + Generate coins Генерировать монеты - + Don't generate coins Не генерировать монеты - - Start minimized - Запускать свёрнутым - - - + Specify data directory Укажите каталог данных - - Specify connection timeout (in milliseconds) - Укажите таймаут соединения (в миллисекундах) - - - - Connect through socks4 proxy - Подключаться через socks4 прокси + + Set database cache size in megabytes (default: 25) + Установить размер кэша базы данных в мегабайтах (по умолчанию: 25) - - Allow DNS lookups for addnode and connect - Разрешить обращения к DNS для addnode и подключения + + Set database disk log size in megabytes (default: 100) + Установить размер лога базы данных в мегабайтах (по умолчанию: 100) - + Listen for connections on <port> (default: 8333 or testnet: 18333) Принимать входящие подключения на <port> (по умолчанию: 8333 или 18333 в тестовой сети) - + Maintain at most <n> connections to peers (default: 125) Поддерживать не более <n> подключений к узлам (по умолчанию: 125) - - Add a node to connect to - Добавить узел для подключения - - - - Connect only to the specified node - Подключаться только к указанному узлу + + Connect to a node to retrieve peer addresses, and disconnect + Подключиться к узлу, чтобы получить список адресов других участников и отключиться - - Don't accept connections from outside - Не принимать входящие подключения + + Specify your own public address + Укажите ваш собственный публичный адрес - - Don't bootstrap list of peers using DNS - Не получать начальный список узлов через DNS + + Bind to given address. Use [host]:port notation for IPv6 + Привязаться (bind) к указанному адресу. Используйте запись вида [хост]:порт для IPv6 - + Threshold for disconnecting misbehaving peers (default: 100) Порог для отключения неправильно ведущих себя узлов (по умолчанию: 100) - + Number of seconds to keep misbehaving peers from reconnecting (default: 86400) Число секунд блокирования неправильно ведущих себя узлов (по умолчанию: 86400) - - Maximum per-connection receive buffer, <n>*1000 bytes (default: 10000) - Максимальный размер буфера приёма на соединение, <n>*1000 байт (по умолчанию: 10000) + + An error occurred while setting up the RPC port %u for listening on IPv4: %s + Произошла ошибка при открытии RPC-порта %u для прослушивания на IPv4: %s + + + + An error occurred while setting up the RPC port %u for listening on IPv6, falling back to IPv4: %s + Произошла ошибка при открытии на прослушивание IPv6 RCP-порта %u, возвращаемся к IPv4: %s + + + + Detach block and address databases. Increases shutdown time (default: 0) + Отключить базы данных блоков и адресов. Увеличивает время завершения работы (по умолчанию: 0) - - Maximum per-connection send buffer, <n>*1000 bytes (default: 10000) - Максимальный размер буфера отправки на соединение, <n>*1000 байт (по умолчанию: 10000) + + Error initializing database environment %s! To recover, BACKUP THAT DIRECTORY, then remove everything from it except for wallet.dat. + - - Don't attempt to use UPnP to map the listening port - Не пытаться использовать UPnP для назначения входящего порта + + Listen for JSON-RPC connections on <port> (default: 8344 or testnet: 18344) + Прослушивать подключения JSON-RPC на <порту> (по умолчанию: 8344 или для testnet: 18344) - - Attempt to use UPnP to map the listening port - Пытаться использовать UPnP для назначения входящего порта + + Warning: error reading wallet.dat! All keys read correctly, but transaction data or address book entries might be missing or incorrect. + - - Fee per kB to add to transactions you send - Комиссия на Кб, добавляемая к вашим переводам + + Warning: wallet.dat corrupt, data salvaged! Original wallet.dat saved as wallet.{timestamp}.bak in %s; if your balance or transactions are incorrect you should restore from a backup. + - + Accept command line and JSON-RPC commands Принимать командную строку и команды JSON-RPC - + + Attempt to recover private keys from a corrupt wallet.dat + + + + + Importing blockchain data file. + Импортируется файл цепи блоков. + + + + Importing bootstrap blockchain data file. + Импортируется bootstrap-файл цепи блоков. + + + Run in the background as a daemon and accept commands Запускаться в фоне как демон и принимать команды - + Use the test network Использовать тестовую сеть - - Output extra debugging information - Выводить дополнительную отладочную информацию + + Accept connections from outside (default: 1 if no -proxy or -connect) + Принимать подключения извне (по умолчанию: 1, если не используется -proxy или -connect) + + + + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. + Ошибка: В транзакции отказано. Такое может произойти, если некоторые монеты уже были потрачены, например, если Вы используете одну копию файла wallet.dat, а монеты были потрачены из другой копии, но не были отмечены как потраченные в этой. + + + + Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds + Ошибка: эта транзакция требует комиссию в размере как минимум %s из-за её объёма, сложности или использования недавно полученных средств + + + + Set maximum size of high-priority/low-fee transactions in bytes (default: 27000) + Максимальный размер высокоприоритетных/низкокомиссионных транзакций в байтах (по умолчанию: 27000) + + + + Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction. + Внимание: установлено очень большое значение -paytxfee. Это комиссия, которую вы заплатите при проведении транзакции. + + + + Warning: Displayed transactions may not be correct! You may need to upgrade, or other nodes may need to upgrade. + Внимание: отображаемые транзакции могут быть некорректны! Вам или другим узлам, возможно, следует обновиться. + + + + Warning: Please check that your computer's date and time are correct! If your clock is wrong NovaCoin will not work properly. + Внимание: убедитесь, что дата и время на Вашем компьютере выставлены верно. Если Ваши часы идут неправильно, NovaCoin будет работать некорректно. + + + + Block creation options: + Параметры создания блоков: + + + + Connect only to the specified node(s) + Подключаться только к указанному узлу(ам) + + + + Discover own IP address (default: 1 when listening and no -externalip) + Определить свой IP (по умолчанию: 1 при прослушивании и если не используется -externalip) + + + + Error: Transaction creation failed + Ошибка: Создание транзакции не удалось + + + + Error: Wallet locked, unable to create transaction + Ошибка: бумажник заблокирован, невозможно создать транзакцию + + + + Failed to listen on any port. Use -listen=0 if you want this. + Не удалось начать прослушивание на порту. Используйте -listen=0 если вас это устраивает. + + + + Find peers using DNS lookup (default: 0 unless -connect) + Искать узлы с помощью DNS (по умолчанию: 0) + + + + Invalid -tor address: '%s' + Неверный адрес -tor: '%s' + + + + Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000) + Максимальный размер буфера приёма на соединение, <n>*1000 байт (по умолчанию: 5000) - + + Maximum per-connection send buffer, <n>*1000 bytes (default: 1000) + Максимальный размер буфера отправки на соединение, <n>*1000 байт (по умолчанию: 1000) + + + + Only connect to nodes in network <net> (IPv4, IPv6 or Tor) + Подключаться только к узлам из сети <net> (IPv4, IPv6 или Tor) + + + + Output extra debugging information. Implies all other -debug* options + Выводить больше отладочной информации. Включает все остальные опции -debug* + + + + Output extra network debugging information + Выводить дополнительную сетевую отладочную информацию + + + Prepend debug output with timestamp Дописывать отметки времени к отладочному выводу - + + SSL options: (see the Bitcoin Wiki for SSL setup instructions) + +Параметры SSL: (см. Bitcoin Wiki для инструкций по настройке SSL) + + + + Select the version of socks proxy to use (4-5, default: 5) + Выберите версию SOCKS-прокси (4-5, по умолчанию: 5) + + + Send trace/debug info to console instead of debug.log file Выводить информацию трассировки/отладки на консоль вместо файла debug.log - + Send trace/debug info to debugger Отправлять информацию трассировки/отладки в отладчик - + + Set maximum block size in bytes (default: 250000) + Максимальный размер блока в байтах (по умолчанию: 250000) + + + + Set minimum block size in bytes (default: 0) + Минимальный размер блока в байтах (по умолчанию: 0) + + + + Shrink debug.log file on client startup (default: 1 when no -debug) + Сжимать файл debug.log при запуске клиента (по умолчанию: 1, если нет -debug) + + + + Specify connection timeout in milliseconds (default: 5000) + Таймаут соединения в миллисекундах (по умолчанию: 5000) + + + + Use UPnP to map the listening port (default: 0) + Использовать UPnP для проброса порта (по умолчанию: 0) + + + + Use UPnP to map the listening port (default: 1 when listening) + Использовать UPnP для проброса порта (по умолчанию: 1, если используется прослушивание) + + + + Use proxy to reach tor hidden services (default: same as -proxy) + Использовать прокси для скрытых сервисов (по умолчанию: тот же, что и в -proxy) + + + Username for JSON-RPC connections Имя для подключений JSON-RPC - - Password for JSON-RPC connections - Пароль для подключений JSON-RPC + + Verifying database integrity... + + + + + Warning: Disk space is low! + Внимание: мало места на диске! + + + + Warning: This version is obsolete, upgrade required! + Внимание: эта версия устарела, требуется обновление! - - Listen for JSON-RPC connections on <port> (default: 8332) - Ожидать подключения JSON-RPC на <порт> (по умолчанию: 8332) + + wallet.dat corrupt, salvage failed + - + + Password for JSON-RPC connections + Пароль для подключений JSON-RPC + + + Allow JSON-RPC connections from specified IP address Разрешить подключения JSON-RPC с указанного IP - + Send commands to node running on <ip> (default: 127.0.0.1) Посылать команды узлу, запущенному на <ip> (по умолчанию: 127.0.0.1) - + + Execute command when the best block changes (%s in cmd is replaced by block hash) + Выполнить команду, когда появляется новый блок (%s в команде заменяется на хэш блока) + + + + Upgrade wallet to latest format + Обновить бумажник до последнего формата + + + Set key pool size to <n> (default: 100) Установить размер запаса ключей в <n> (по умолчанию: 100) - + Rescan the block chain for missing wallet transactions - Перепроверить цепь блоков на предмет отсутствующих в кошельке транзакций + Перепроверить цепь блоков на предмет отсутствующих в бумажнике транзакций - - -SSL options: (see the Bitcoin Wiki for SSL setup instructions) - -Параметры SSL: (см. Bitcoin Wiki для инструкций по настройке SSL) + + How many blocks to check at startup (default: 2500, 0 = all) + Сколько блоков проверять при запуске (по умолчанию: 2500, 0 = все) + + + + How thorough the block verification is (0-6, default: 1) + Насколько тщательно проверять блоки (0-6, по умолчанию: 1) - + + Imports blocks from external blk000?.dat file + Импортировать блоки из внешнего файла blk000?.dat + + + Use OpenSSL (https) for JSON-RPC connections Использовать OpenSSL (https) для подключений JSON-RPC - + Server certificate file (default: server.cert) Файл серверного сертификата (по умолчанию: server.cert) - + Server private key (default: server.pem) Приватный ключ сервера (по умолчанию: server.pem) - + Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) Разрешённые алгоритмы (по умолчанию: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) - + This help message Эта справка - + Cannot obtain a lock on data directory %s. NovaCoin is probably already running. Невозможно установить блокировку на рабочую директорию %s. Возможно, бумажник уже запущен. - - Loading addresses... - Загрузка адресов... + + NovaCoin + NovaCoin - - Error loading addr.dat - Ошибка загрузки addr.dat + + Unable to bind to %s on this computer (bind returned error %d, %s) + Невозможно привязаться к %s на этом компьютере (bind вернул ошибку %d, %s) - + + Connect through socks proxy + Подключаться через socks прокси + + + + Allow DNS lookups for -addnode, -seednode and -connect + Разрешить поиск в DNS для -addnode, -seednode и -connect + + + + Loading addresses... + Загрузка адресов... + + + Error loading blkindex.dat Ошибка чтения blkindex.dat - + Error loading wallet.dat: Wallet corrupted Ошибка загрузки wallet.dat: Бумажник поврежден - + Error loading wallet.dat: Wallet requires newer version of NovaCoin Ошибка загрузки wallet.dat: бумажник требует более новую версию NovaCoin - + Wallet needed to be rewritten: restart NovaCoin to complete Необходимо перезаписать бумажник, перезапустите NovaCoin для завершения операции. - + Error loading wallet.dat Ошибка при загрузке wallet.dat - + + Invalid -proxy address: '%s' + Неверный адрес -proxy: '%s' + + + + Unknown network specified in -onlynet: '%s' + В параметре -onlynet указана неизвестная сеть: '%s' + + + + Unknown -socks proxy version requested: %i + В параметре -socks запрошена неизвестная версия: %i + + + + Cannot resolve -bind address: '%s' + Не удаётся разрешить адрес в параметре -bind: '%s' + + + + Cannot resolve -externalip address: '%s' + Не удаётся разрешить адрес в параметре -externalip: '%s' + + + + Invalid amount for -paytxfee=<amount>: '%s' + Неверное количество в параметре -paytxfee=<кол-во>: '%s' + + + + Error: could not start node + Ошибка: не удалось запустить узел + + + + Sending... + Отправка... + + + + Invalid amount + Неверное количество + + + + Insufficient funds + Недостаточно монет + + + Loading block index... Загрузка индекса блоков... - - Loading wallet... - Загрузка бумажника... + + Add a node to connect to and attempt to keep the connection open + Добавить узел для подключения и пытаться поддерживать соединение открытым - - Rescanning... - Сканирование... + + Unable to bind to %s on this computer. NovaCoin is probably already running. + Невозможно привязаться к %s на этом компьютере. Возможно, NovaCoin уже работает. - - Done loading - Загрузка завершена + + Find peers using internet relay chat (default: 1) + Найти участников через IRC (по умолчанию: 1) + + + + Fee per KB to add to transactions you send + Комиссия на килобайт, добавляемая к вашим транзакциям + + + + Loading wallet... + Загрузка бумажника... - - Invalid -proxy address - Ошибка в адресе прокси + + Cannot downgrade wallet + Не удаётся понизить версию бумажника - - Invalid amount for -paytxfee=<amount> - Ошибка в сумме комиссии + + Cannot initialize keypool + Не удаётся инициализировать массив ключей - - Warning: -paytxfee is set very high. This is the transaction fee you will pay if you send a transaction. - ВНИМАНИЕ: Установлена слишком большая комиссия (-paytxfee=). Данный параметр отвечает за комиссию, которую Вы будете добавлять к сумме при осуществлении транзакций. + + Cannot write default address + Не удаётся записать адрес по умолчанию - - Error: CreateThread(StartNode) failed - Ошибка: Созданиние потока (запуск узла) не удался + + Rescanning... + Сканирование... + + + + Done loading + Загрузка завершена - - Warning: Disk space is low - ВНИМАНИЕ: На диске заканчивается свободное пространство + + To use the %s option + Чтобы использовать опцию %s - - Unable to bind to port %d on this computer. NovaCoin is probably already running. - Невозможно забиндить порт %d на данном компьютере. Возможно, бумажник ужк запущен. + + %s, you must set a rpcpassword in the configuration file: + %s +It is recommended you use the following random password: +rpcuser=bitcoinrpc +rpcpassword=%s +(you do not need to remember this password) +If the file does not exist, create it with owner-readable-only file permissions. + + %s, вы должны установить опцию rpcpassword в конфигурационном файле: + %s +Рекомендуется использовать следующий случайный пароль: +rpcuser=bitcoinrpc +rpcpassword=%s +(вам не нужно запоминать этот пароль) +Если файл не существует, создайте его и установите права доступа только для владельца. + - - Warning: Please check that your computer's date and time are correct. If your clock is wrong NovaCoin will not work properly. - ВНИМАНИЕ: Проверьте дату и время, установленные на Вашем компьютере. Если Ваши часы идут не правильно NovaCoin может наботать не корректно. + + Error + Ошибка - - beta - бета + + You must set rpcpassword=<password> in the configuration file: +%s +If the file does not exist, create it with owner-readable-only file permissions. + Вы должны установить rpcpassword=<password> в конфигурационном файле: +%s +Если файл не существует, создайте его и установите права доступа только для владельца. - \ No newline at end of file + diff --git a/src/qt/macdockiconhandler.h b/src/qt/macdockiconhandler.h index d02c148..2092fb2 100644 --- a/src/qt/macdockiconhandler.h +++ b/src/qt/macdockiconhandler.h @@ -6,7 +6,12 @@ class QMenu; class QIcon; class QWidget; -class objc_object; + +#ifdef __OBJC__ +@class DockIconClickEventHandler; +#else +class DockIconClickEventHandler; +#endif /** Macintosh-specific dock icon handler. */ @@ -31,7 +36,7 @@ public slots: private: MacDockIconHandler(); - objc_object *m_dockIconClickEventHandler; + DockIconClickEventHandler *m_dockIconClickEventHandler; QWidget *m_dummyWidget; QMenu *m_dockMenu; }; diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index df56e69..7568440 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -1,8 +1,8 @@ #include "macdockiconhandler.h" -#include -#include +#include +#include extern void qt_mac_set_dock_menu(QMenu*); diff --git a/src/qt/messagepage.cpp b/src/qt/messagepage.cpp deleted file mode 100644 index 18bb64f..0000000 --- a/src/qt/messagepage.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include - -#include "main.h" -#include "wallet.h" -#include "init.h" -#include "util.h" - -#include "messagepage.h" -#include "ui_messagepage.h" - -#include "addressbookpage.h" -#include "guiutil.h" -#include "walletmodel.h" - -MessagePage::MessagePage(QWidget *parent) : - QDialog(parent), - ui(new Ui::MessagePage) -{ - ui->setupUi(this); - - GUIUtil::setupAddressWidget(ui->signFrom, this); -} - -MessagePage::~MessagePage() -{ - delete ui; -} - -void MessagePage::setModel(WalletModel *model) -{ - this->model = model; -} - -void MessagePage::setAddress(QString addr) -{ - ui->signFrom->setText(addr); - ui->message->setFocus(); -} - -void MessagePage::on_pasteButton_clicked() -{ - setAddress(QApplication::clipboard()->text()); -} - -void MessagePage::on_addressBookButton_clicked() -{ - AddressBookPage dlg(AddressBookPage::ForSending, AddressBookPage::ReceivingTab, this); - dlg.setModel(model->getAddressTableModel()); - if(dlg.exec()) - { - setAddress(dlg.getReturnValue()); - } -} - -void MessagePage::on_copyToClipboard_clicked() -{ - QApplication::clipboard()->setText(ui->signature->text()); -} - -void MessagePage::on_signMessage_clicked() -{ - QString address = ui->signFrom->text(); - - CBitcoinAddress addr(address.toStdString()); - if (!addr.IsValid()) - { - QMessageBox::critical(this, tr("Error signing"), tr("%1 is not a valid address.").arg(address), - QMessageBox::Abort, QMessageBox::Abort); - return; - } - - WalletModel::UnlockContext ctx(model->requestUnlock()); - if(!ctx.isValid()) - { - // Unlock wallet was cancelled - return; - } - - CKey key; - if (!pwalletMain->GetKey(addr, key)) - { - QMessageBox::critical(this, tr("Error signing"), tr("Private key for %1 is not available.").arg(address), - QMessageBox::Abort, QMessageBox::Abort); - return; - } - - CDataStream ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << ui->message->document()->toPlainText().toStdString(); - - std::vector vchSig; - if (!key.SignCompact(Hash(ss.begin(), ss.end()), vchSig)) - { - QMessageBox::critical(this, tr("Error signing"), tr("Sign failed"), - QMessageBox::Abort, QMessageBox::Abort); - } - - ui->signature->setText(QString::fromStdString(EncodeBase64(&vchSig[0], vchSig.size()))); - ui->signature->setFont(GUIUtil::bitcoinAddressFont()); -} diff --git a/src/qt/messagepage.h b/src/qt/messagepage.h deleted file mode 100644 index 55e6228..0000000 --- a/src/qt/messagepage.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef MESSAGEPAGE_H -#define MESSAGEPAGE_H - -#include - -namespace Ui { - class MessagePage; -} -class WalletModel; - -QT_BEGIN_NAMESPACE -QT_END_NAMESPACE - -class MessagePage : public QDialog -{ - Q_OBJECT - -public: - explicit MessagePage(QWidget *parent = 0); - ~MessagePage(); - - void setModel(WalletModel *model); - - void setAddress(QString); - -private: - Ui::MessagePage *ui; - WalletModel *model; - -private slots: - void on_pasteButton_clicked(); - void on_addressBookButton_clicked(); - - void on_signMessage_clicked(); - void on_copyToClipboard_clicked(); -}; - -#endif // MESSAGEPAGE_H diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index e668079..8028190 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -16,7 +16,7 @@ #include #endif -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC #include extern bool qt_mac_execute_apple_script(const QString &script, AEDesc *ret); #endif @@ -46,7 +46,7 @@ Notificator::Notificator(const QString &programName, QSystemTrayIcon *trayicon, mode = Freedesktop; } #endif -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC // Check if Growl is installed (based on Qt's tray icon implementation) CFURLRef cfurl; OSStatus status = LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator, CFSTR("growlTicket"), kLSRolesAll, 0, &cfurl); @@ -82,7 +82,7 @@ public: static int metaType(); - // Image to variant that can be marshaled over DBus + // Image to variant that can be marshalled over DBus static QVariant toVariant(const QImage &img); private: @@ -225,7 +225,7 @@ void Notificator::notifySystray(Class cls, const QString &title, const QString & } // Based on Qt's tray icon implementation -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC void Notificator::notifyGrowl(Class cls, const QString &title, const QString &text, const QIcon &icon) { const QString script( @@ -285,7 +285,7 @@ void Notificator::notify(Class cls, const QString &title, const QString &text, c case QSystemTray: notifySystray(cls, title, text, icon, millisTimeout); break; -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC case Growl12: case Growl13: notifyGrowl(cls, title, text, icon); @@ -294,7 +294,7 @@ void Notificator::notify(Class cls, const QString &title, const QString &text, c default: if(cls == Critical) { - // Fall back to old fashioned popup dialog if critical and no other notification available + // Fall back to old fashioned pop-up dialog if critical and no other notification available QMessageBox::critical(parent, title, text, QMessageBox::Ok, QMessageBox::Ok); } break; diff --git a/src/qt/notificator.h b/src/qt/notificator.h index 0271c26..abb4710 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -27,7 +27,7 @@ public: { Information, /**< Informational message */ Warning, /**< Notify user of potential problem */ - Critical /**< An error occured */ + Critical /**< An error occurred */ }; public slots: @@ -61,7 +61,7 @@ private: void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); #endif void notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC void notifyGrowl(Class cls, const QString &title, const QString &text, const QIcon &icon); #endif }; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index f6720a6..777e046 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -1,297 +1,256 @@ #include "optionsdialog.h" -#include "optionsmodel.h" -#include "bitcoinamountfield.h" -#include "monitoreddatamapper.h" -#include "guiutil.h" -#include "bitcoinunits.h" -#include "qvaluecombobox.h" +#include "ui_optionsdialog.h" -#include -#include -#include -#include -#include +#include "bitcoinunits.h" +#include "monitoreddatamapper.h" +#include "netbase.h" +#include "optionsmodel.h" -#include -#include -#include +#include #include -#include +#include +#include +#include #include -#include - -/* First page of options */ -class MainOptionsPage : public QWidget -{ - Q_OBJECT -public: - explicit MainOptionsPage(QWidget *parent=0); - - void setMapper(MonitoredDataMapper *mapper); -private: - QCheckBox *bitcoin_at_startup; -#ifndef Q_WS_MAC - QCheckBox *minimize_to_tray; -#endif - QCheckBox *map_port_upnp; -#ifndef Q_WS_MAC - QCheckBox *minimize_on_close; -#endif - QCheckBox *connect_socks4; - QCheckBox *detach_database; - QLineEdit *proxy_ip; - QLineEdit *proxy_port; - BitcoinAmountField *fee_edit; - -signals: - -public slots: - -}; - -class DisplayOptionsPage : public QWidget -{ - Q_OBJECT -public: - explicit DisplayOptionsPage(QWidget *parent=0); - void setMapper(MonitoredDataMapper *mapper); -private: - QValueComboBox *unit; - QCheckBox *display_addresses; -signals: - -public slots: - -}; - -#include "optionsdialog.moc" - -OptionsDialog::OptionsDialog(QWidget *parent): - QDialog(parent), contents_widget(0), pages_widget(0), - model(0), main_page(0), display_page(0) +OptionsDialog::OptionsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::OptionsDialog), + model(0), + mapper(0), + fRestartWarningDisplayed_Proxy(false), + fRestartWarningDisplayed_Lang(false), + fProxyIpValid(true) { - contents_widget = new QListWidget(); - contents_widget->setMaximumWidth(128); + ui->setupUi(this); - pages_widget = new QStackedWidget(); - pages_widget->setMinimumWidth(300); + /* Network elements init */ +#ifndef USE_UPNP + ui->mapPortUpnp->setEnabled(false); +#endif - QListWidgetItem *item_main = new QListWidgetItem(tr("Main")); - contents_widget->addItem(item_main); - main_page = new MainOptionsPage(this); - pages_widget->addWidget(main_page); + ui->proxyIp->setEnabled(false); + ui->proxyPort->setEnabled(false); + ui->proxyPort->setValidator(new QIntValidator(1, 65535, this)); - QListWidgetItem *item_display = new QListWidgetItem(tr("Display")); - contents_widget->addItem(item_display); - display_page = new DisplayOptionsPage(this); - pages_widget->addWidget(display_page); + ui->socksVersion->setEnabled(false); + ui->socksVersion->addItem("5", 5); + ui->socksVersion->addItem("4", 4); + ui->socksVersion->setCurrentIndex(0); - contents_widget->setCurrentRow(0); + connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->proxyIp, SLOT(setEnabled(bool))); + connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->proxyPort, SLOT(setEnabled(bool))); + connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->socksVersion, SLOT(setEnabled(bool))); + connect(ui->connectSocks, SIGNAL(clicked(bool)), this, SLOT(showRestartWarning_Proxy())); - QHBoxLayout *main_layout = new QHBoxLayout(); - main_layout->addWidget(contents_widget); - main_layout->addWidget(pages_widget, 1); + ui->proxyIp->installEventFilter(this); - QVBoxLayout *layout = new QVBoxLayout(); - layout->addLayout(main_layout); + /* Window elements init */ +#ifdef Q_OS_MAC + ui->tabWindow->setVisible(false); +#endif - QDialogButtonBox *buttonbox = new QDialogButtonBox(); - buttonbox->setStandardButtons(QDialogButtonBox::Apply|QDialogButtonBox::Ok|QDialogButtonBox::Cancel); - apply_button = buttonbox->button(QDialogButtonBox::Apply); - layout->addWidget(buttonbox); + /* Display elements init */ + QDir translations(":translations"); + ui->lang->addItem(QString("(") + tr("default") + QString(")"), QVariant("")); + foreach(const QString &langStr, translations.entryList()) + { + QLocale locale(langStr); + + /** check if the locale name consists of 2 parts (language_country) */ + if(langStr.contains("_")) + { +#if QT_VERSION >= 0x040800 + /** display language strings as "native language - native country (locale name)", e.g. "Deutsch - Deutschland (de)" */ + ui->lang->addItem(locale.nativeLanguageName() + QString(" - ") + locale.nativeCountryName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); +#else + /** display language strings as "language - country (locale name)", e.g. "German - Germany (de)" */ + ui->lang->addItem(QLocale::languageToString(locale.language()) + QString(" - ") + QLocale::countryToString(locale.country()) + QString(" (") + langStr + QString(")"), QVariant(langStr)); +#endif + } + else + { +#if QT_VERSION >= 0x040800 + /** display language strings as "native language (locale name)", e.g. "Deutsch (de)" */ + ui->lang->addItem(locale.nativeLanguageName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); +#else + /** display language strings as "language (locale name)", e.g. "German (de)" */ + ui->lang->addItem(QLocale::languageToString(locale.language()) + QString(" (") + langStr + QString(")"), QVariant(langStr)); +#endif + } + } - setLayout(layout); - setWindowTitle(tr("Options")); + ui->unit->setModel(new BitcoinUnits(this)); /* Widget-to-option mapper */ mapper = new MonitoredDataMapper(this); mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); mapper->setOrientation(Qt::Vertical); + /* enable apply button when data modified */ - connect(mapper, SIGNAL(viewModified()), this, SLOT(enableApply())); + connect(mapper, SIGNAL(viewModified()), this, SLOT(enableApplyButton())); /* disable apply button when new data loaded */ - connect(mapper, SIGNAL(currentIndexChanged(int)), this, SLOT(disableApply())); + connect(mapper, SIGNAL(currentIndexChanged(int)), this, SLOT(disableApplyButton())); + /* setup/change UI elements when proxy IP is invalid/valid */ + connect(this, SIGNAL(proxyIpValid(QValidatedLineEdit *, bool)), this, SLOT(handleProxyIpValid(QValidatedLineEdit *, bool))); +} - /* Event bindings */ - connect(contents_widget, SIGNAL(currentRowChanged(int)), this, SLOT(changePage(int))); - connect(buttonbox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(okClicked())); - connect(buttonbox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(cancelClicked())); - connect(buttonbox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(applyClicked())); +OptionsDialog::~OptionsDialog() +{ + delete ui; } void OptionsDialog::setModel(OptionsModel *model) { this->model = model; - mapper->setModel(model); - main_page->setMapper(mapper); - display_page->setMapper(mapper); + if(model) + { + connect(model, SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + + mapper->setModel(model); + setMapper(); + mapper->toFirst(); + } + + /* update the display unit, to not use the default ("BTC") */ + updateDisplayUnit(); + + /* warn only when language selection changes by user action (placed here so init via mapper doesn't trigger this) */ + connect(ui->lang, SIGNAL(valueChanged()), this, SLOT(showRestartWarning_Lang())); - mapper->toFirst(); + /* disable apply button after settings are loaded as there is nothing to save */ + disableApplyButton(); } -void OptionsDialog::changePage(int index) +void OptionsDialog::setMapper() { - pages_widget->setCurrentIndex(index); + /* Main */ + mapper->addMapping(ui->transactionFee, OptionsModel::Fee); + mapper->addMapping(ui->bitcoinAtStartup, OptionsModel::StartAtStartup); + mapper->addMapping(ui->detachDatabases, OptionsModel::DetachDatabases); + + /* Network */ + mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP); + + mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse); + mapper->addMapping(ui->proxyIp, OptionsModel::ProxyIP); + mapper->addMapping(ui->proxyPort, OptionsModel::ProxyPort); + mapper->addMapping(ui->socksVersion, OptionsModel::ProxySocksVersion); + + /* Window */ +#ifndef Q_OS_MAC + mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); + mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose); +#endif + + /* Display */ + mapper->addMapping(ui->lang, OptionsModel::Language); + mapper->addMapping(ui->unit, OptionsModel::DisplayUnit); + mapper->addMapping(ui->displayAddresses, OptionsModel::DisplayAddresses); } -void OptionsDialog::okClicked() +void OptionsDialog::enableApplyButton() { - mapper->submit(); - accept(); + ui->applyButton->setEnabled(true); } -void OptionsDialog::cancelClicked() +void OptionsDialog::disableApplyButton() { - reject(); + ui->applyButton->setEnabled(false); } -void OptionsDialog::applyClicked() +void OptionsDialog::enableSaveButtons() { - mapper->submit(); - apply_button->setEnabled(false); + /* prevent enabling of the save buttons when data modified, if there is an invalid proxy address present */ + if(fProxyIpValid) + setSaveButtonState(true); } -void OptionsDialog::enableApply() +void OptionsDialog::disableSaveButtons() { - apply_button->setEnabled(true); + setSaveButtonState(false); } -void OptionsDialog::disableApply() +void OptionsDialog::setSaveButtonState(bool fState) { - apply_button->setEnabled(false); + ui->applyButton->setEnabled(fState); + ui->okButton->setEnabled(fState); } -MainOptionsPage::MainOptionsPage(QWidget *parent): - QWidget(parent) +void OptionsDialog::on_okButton_clicked() { - QVBoxLayout *layout = new QVBoxLayout(); - - bitcoin_at_startup = new QCheckBox(tr("&Start NovaCoin on window system startup")); - bitcoin_at_startup->setToolTip(tr("Automatically start NovaCoin after the computer is turned on")); - layout->addWidget(bitcoin_at_startup); - -#ifndef Q_WS_MAC - minimize_to_tray = new QCheckBox(tr("&Minimize to the tray instead of the taskbar")); - minimize_to_tray->setToolTip(tr("Show only a tray icon after minimizing the window")); - layout->addWidget(minimize_to_tray); - - minimize_on_close = new QCheckBox(tr("M&inimize on close")); - minimize_on_close->setToolTip(tr("Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu.")); - layout->addWidget(minimize_on_close); -#endif - - map_port_upnp = new QCheckBox(tr("Map port using &UPnP")); - map_port_upnp->setToolTip(tr("Automatically open the NovaCoin client port on the router. This only works when your router supports UPnP and it is enabled.")); - layout->addWidget(map_port_upnp); - - connect_socks4 = new QCheckBox(tr("&Connect through SOCKS4 proxy:")); - connect_socks4->setToolTip(tr("Connect to the Bitcon network through a SOCKS4 proxy (e.g. when connecting through Tor)")); - layout->addWidget(connect_socks4); - - QHBoxLayout *proxy_hbox = new QHBoxLayout(); - proxy_hbox->addSpacing(18); - QLabel *proxy_ip_label = new QLabel(tr("Proxy &IP: ")); - proxy_hbox->addWidget(proxy_ip_label); - proxy_ip = new QLineEdit(); - proxy_ip->setMaximumWidth(140); - proxy_ip->setEnabled(false); - proxy_ip->setValidator(new QRegExpValidator(QRegExp("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"), this)); - proxy_ip->setToolTip(tr("IP address of the proxy (e.g. 127.0.0.1)")); - proxy_ip_label->setBuddy(proxy_ip); - proxy_hbox->addWidget(proxy_ip); - QLabel *proxy_port_label = new QLabel(tr("&Port: ")); - proxy_hbox->addWidget(proxy_port_label); - proxy_port = new QLineEdit(); - proxy_port->setMaximumWidth(55); - proxy_port->setValidator(new QIntValidator(0, 65535, this)); - proxy_port->setEnabled(false); - proxy_port->setToolTip(tr("Port of the proxy (e.g. 1234)")); - proxy_port_label->setBuddy(proxy_port); - proxy_hbox->addWidget(proxy_port); - proxy_hbox->addStretch(1); - - layout->addLayout(proxy_hbox); - QLabel *fee_help = new QLabel(tr("Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended.")); - fee_help->setWordWrap(true); - layout->addWidget(fee_help); - - QHBoxLayout *fee_hbox = new QHBoxLayout(); - fee_hbox->addSpacing(18); - QLabel *fee_label = new QLabel(tr("Pay transaction &fee")); - fee_hbox->addWidget(fee_label); - fee_edit = new BitcoinAmountField(); - - fee_label->setBuddy(fee_edit); - fee_hbox->addWidget(fee_edit); - fee_hbox->addStretch(1); - - layout->addLayout(fee_hbox); - - detach_database = new QCheckBox(tr("Detach databases at shutdown")); - detach_database->setToolTip(tr("Detach block and address databases at shutdown. This means they can be moved to another data directory, but it slows down shutdown. The wallet is always detached.")); - layout->addWidget(detach_database); - - layout->addStretch(1); // Extra space at bottom - - setLayout(layout); - - connect(connect_socks4, SIGNAL(toggled(bool)), proxy_ip, SLOT(setEnabled(bool))); - connect(connect_socks4, SIGNAL(toggled(bool)), proxy_port, SLOT(setEnabled(bool))); - -#ifndef USE_UPNP - map_port_upnp->setDisabled(true); -#endif + mapper->submit(); + accept(); } -void MainOptionsPage::setMapper(MonitoredDataMapper *mapper) +void OptionsDialog::on_cancelButton_clicked() { - // Map model to widgets - mapper->addMapping(bitcoin_at_startup, OptionsModel::StartAtStartup); -#ifndef Q_WS_MAC - mapper->addMapping(minimize_to_tray, OptionsModel::MinimizeToTray); -#endif - mapper->addMapping(map_port_upnp, OptionsModel::MapPortUPnP); -#ifndef Q_WS_MAC - mapper->addMapping(minimize_on_close, OptionsModel::MinimizeOnClose); -#endif - mapper->addMapping(connect_socks4, OptionsModel::ConnectSOCKS4); - mapper->addMapping(proxy_ip, OptionsModel::ProxyIP); - mapper->addMapping(proxy_port, OptionsModel::ProxyPort); - mapper->addMapping(fee_edit, OptionsModel::Fee); - mapper->addMapping(detach_database, OptionsModel::DetachDatabases); + reject(); } -DisplayOptionsPage::DisplayOptionsPage(QWidget *parent): - QWidget(parent) +void OptionsDialog::on_applyButton_clicked() { - QVBoxLayout *layout = new QVBoxLayout(); - - QHBoxLayout *unit_hbox = new QHBoxLayout(); - unit_hbox->addSpacing(18); - QLabel *unit_label = new QLabel(tr("&Unit to show amounts in: ")); - unit_hbox->addWidget(unit_label); - unit = new QValueComboBox(this); - unit->setModel(new BitcoinUnits(this)); - unit->setToolTip(tr("Choose the default subdivision unit to show in the interface, and when sending coins")); - - unit_label->setBuddy(unit); - unit_hbox->addWidget(unit); + mapper->submit(); + disableApplyButton(); +} - layout->addLayout(unit_hbox); +void OptionsDialog::showRestartWarning_Proxy() +{ + if(!fRestartWarningDisplayed_Proxy) + { + QMessageBox::warning(this, tr("Warning"), tr("This setting will take effect after restarting NovaCoin."), QMessageBox::Ok); + fRestartWarningDisplayed_Proxy = true; + } +} - display_addresses = new QCheckBox(tr("&Display addresses in transaction list"), this); - display_addresses->setToolTip(tr("Whether to show NovaCoin addresses in the transaction list")); - layout->addWidget(display_addresses); +void OptionsDialog::showRestartWarning_Lang() +{ + if(!fRestartWarningDisplayed_Lang) + { + QMessageBox::warning(this, tr("Warning"), tr("This setting will take effect after restarting NovaCoin."), QMessageBox::Ok); + fRestartWarningDisplayed_Lang = true; + } +} - layout->addStretch(); +void OptionsDialog::updateDisplayUnit() +{ + if(model) + { + /* Update transactionFee with the current unit */ + ui->transactionFee->setDisplayUnit(model->getDisplayUnit()); + } +} - setLayout(layout); +void OptionsDialog::handleProxyIpValid(QValidatedLineEdit *object, bool fState) +{ + // this is used in a check before re-enabling the save buttons + fProxyIpValid = fState; + + if(fProxyIpValid) + { + enableSaveButtons(); + ui->statusLabel->clear(); + } + else + { + disableSaveButtons(); + object->setValid(fProxyIpValid); + ui->statusLabel->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel->setText(tr("The supplied proxy address is invalid.")); + } } -void DisplayOptionsPage::setMapper(MonitoredDataMapper *mapper) +bool OptionsDialog::eventFilter(QObject *object, QEvent *event) { - mapper->addMapping(unit, OptionsModel::DisplayUnit); - mapper->addMapping(display_addresses, OptionsModel::DisplayAddresses); + if(event->type() == QEvent::FocusOut) + { + if(object == ui->proxyIp) + { + CService addr; + /* Check proxyIp for a valid IPv4/IPv6 address and emit the proxyIpValid signal */ + emit proxyIpValid(ui->proxyIp, LookupNumeric(ui->proxyIp->text().toStdString().c_str(), addr)); + } + } + return QDialog::eventFilter(object, event); } diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 9e1f87c..18469f5 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -3,51 +3,58 @@ #include -QT_BEGIN_NAMESPACE -class QStackedWidget; -class QListWidget; -class QListWidgetItem; -class QPushButton; -QT_END_NAMESPACE +namespace Ui { +class OptionsDialog; +} class OptionsModel; -class MainOptionsPage; -class DisplayOptionsPage; class MonitoredDataMapper; +class QValidatedLineEdit; /** Preferences dialog. */ class OptionsDialog : public QDialog { Q_OBJECT + public: - explicit OptionsDialog(QWidget *parent=0); + explicit OptionsDialog(QWidget *parent = 0); + ~OptionsDialog(); void setModel(OptionsModel *model); + void setMapper(); -signals: - -public slots: - /** Change the current page to \a index. */ - void changePage(int index); +protected: + bool eventFilter(QObject *object, QEvent *event); private slots: - void okClicked(); - void cancelClicked(); - void applyClicked(); - void enableApply(); - void disableApply(); + /* enable only apply button */ + void enableApplyButton(); + /* disable only apply button */ + void disableApplyButton(); + /* enable apply button and OK button */ + void enableSaveButtons(); + /* disable apply button and OK button */ + void disableSaveButtons(); + /* set apply button and OK button state (enabled / disabled) */ + void setSaveButtonState(bool fState); + void on_okButton_clicked(); + void on_cancelButton_clicked(); + void on_applyButton_clicked(); + + void showRestartWarning_Proxy(); + void showRestartWarning_Lang(); + void updateDisplayUnit(); + void handleProxyIpValid(QValidatedLineEdit *object, bool fState); + +signals: + void proxyIpValid(QValidatedLineEdit *object, bool fValid); private: - QListWidget *contents_widget; - QStackedWidget *pages_widget; + Ui::OptionsDialog *ui; OptionsModel *model; MonitoredDataMapper *mapper; - QPushButton *apply_button; - - // Pages - MainOptionsPage *main_page; - DisplayOptionsPage *display_page; - - void setupMainPage(); + bool fRestartWarningDisplayed_Proxy; + bool fRestartWarningDisplayed_Lang; + bool fProxyIpValid; }; #endif // OPTIONSDIALOG_H diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 5bba308..756fe61 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -4,6 +4,7 @@ #include "init.h" #include "walletdb.h" +#include "guiutil.h" OptionsModel::OptionsModel(QObject *parent) : QAbstractListModel(parent) @@ -11,25 +12,54 @@ OptionsModel::OptionsModel(QObject *parent) : Init(); } +bool static ApplyProxySettings() +{ + QSettings settings; + CService addrProxy(settings.value("addrProxy", "127.0.0.1:9050").toString().toStdString()); + int nSocksVersion(settings.value("nSocksVersion", 5).toInt()); + if (!settings.value("fUseProxy", false).toBool()) { + addrProxy = CService(); + nSocksVersion = 0; + return false; + } + if (nSocksVersion && !addrProxy.IsValid()) + return false; + if (!IsLimited(NET_IPV4)) + SetProxy(NET_IPV4, addrProxy, nSocksVersion); + if (nSocksVersion > 4) { +#ifdef USE_IPV6 + if (!IsLimited(NET_IPV6)) + SetProxy(NET_IPV6, addrProxy, nSocksVersion); +#endif + SetNameProxy(addrProxy, nSocksVersion); + } + return true; +} + void OptionsModel::Init() { QSettings settings; - // These are QT-only settings: + // These are Qt-only settings: nDisplayUnit = settings.value("nDisplayUnit", BitcoinUnits::BTC).toInt(); bDisplayAddresses = settings.value("bDisplayAddresses", false).toBool(); fMinimizeToTray = settings.value("fMinimizeToTray", false).toBool(); fMinimizeOnClose = settings.value("fMinimizeOnClose", false).toBool(); nTransactionFee = settings.value("nTransactionFee").toLongLong(); + language = settings.value("language", "").toString(); - // These are shared with core bitcoin; we want + // These are shared with core Bitcoin; we want // command-line options to override the GUI settings: if (settings.contains("fUseUPnP")) SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool()); if (settings.contains("addrProxy") && settings.value("fUseProxy").toBool()) SoftSetArg("-proxy", settings.value("addrProxy").toString().toStdString()); + if (settings.contains("nSocksVersion") && settings.value("fUseProxy").toBool()) + SoftSetArg("-socks", settings.value("nSocksVersion").toString().toStdString()); if (settings.contains("detachDB")) SoftSetBoolArg("-detachdb", settings.value("detachDB").toBool()); + if (!language.isEmpty()) + SoftSetArg("-lang", language.toStdString()); } bool OptionsModel::Upgrade() @@ -71,20 +101,21 @@ bool OptionsModel::Upgrade() CAddress addrProxyAddress; if (walletdb.ReadSetting("addrProxy", addrProxyAddress)) { - addrProxy = addrProxyAddress; - settings.setValue("addrProxy", addrProxy.ToStringIPPort().c_str()); + settings.setValue("addrProxy", addrProxyAddress.ToStringIPPort().c_str()); walletdb.EraseSetting("addrProxy"); } } catch (std::ios_base::failure &e) { // 0.6.0rc1 saved this as a CService, which causes failure when parsing as a CAddress + CService addrProxy; if (walletdb.ReadSetting("addrProxy", addrProxy)) { settings.setValue("addrProxy", addrProxy.ToStringIPPort().c_str()); walletdb.EraseSetting("addrProxy"); } } + ApplyProxySettings(); Init(); return true; @@ -104,19 +135,31 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const switch(index.row()) { case StartAtStartup: - return QVariant(GetStartOnSystemStartup()); + return QVariant(GUIUtil::GetStartOnSystemStartup()); case MinimizeToTray: return QVariant(fMinimizeToTray); case MapPortUPnP: return settings.value("fUseUPnP", GetBoolArg("-upnp", true)); case MinimizeOnClose: return QVariant(fMinimizeOnClose); - case ConnectSOCKS4: + case ProxyUse: return settings.value("fUseProxy", false); - case ProxyIP: - return QVariant(QString::fromStdString(addrProxy.ToStringIP())); - case ProxyPort: - return QVariant(addrProxy.GetPort()); + case ProxyIP: { + proxyType proxy; + if (GetProxy(NET_IPV4, proxy)) + return QVariant(QString::fromStdString(proxy.first.ToStringIP())); + else + return QVariant(QString::fromStdString("127.0.0.1")); + } + case ProxyPort: { + proxyType proxy; + if (GetProxy(NET_IPV4, proxy)) + return QVariant(proxy.first.GetPort()); + else + return QVariant(9050); + } + case ProxySocksVersion: + return settings.value("nSocksVersion", 5); case Fee: return QVariant(nTransactionFee); case DisplayUnit: @@ -124,7 +167,9 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const case DisplayAddresses: return QVariant(bDisplayAddresses); case DetachDatabases: - return QVariant(fDetachDB); + return QVariant(bitdb.GetDetach()); + case Language: + return settings.value("language", ""); default: return QVariant(); } @@ -141,78 +186,78 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in switch(index.row()) { case StartAtStartup: - successful = SetStartOnSystemStartup(value.toBool()); + successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); break; case MinimizeToTray: fMinimizeToTray = value.toBool(); settings.setValue("fMinimizeToTray", fMinimizeToTray); break; case MapPortUPnP: - { - bool bUseUPnP = value.toBool(); - settings.setValue("fUseUPnP", bUseUPnP); - MapPort(bUseUPnP); - } + fUseUPnP = value.toBool(); + settings.setValue("fUseUPnP", fUseUPnP); + MapPort(); break; case MinimizeOnClose: fMinimizeOnClose = value.toBool(); settings.setValue("fMinimizeOnClose", fMinimizeOnClose); break; - case ConnectSOCKS4: - fUseProxy = value.toBool(); - settings.setValue("fUseProxy", fUseProxy); - break; - case ProxyIP: - { - // Use CAddress to parse and check IP - CNetAddr addr(value.toString().toStdString()); - if (addr.IsValid()) - { - addrProxy.SetIP(addr); - settings.setValue("addrProxy", addrProxy.ToStringIPPort().c_str()); - } - else - { - successful = false; - } - } - break; - case ProxyPort: - { - int nPort = atoi(value.toString().toAscii().data()); - if (nPort > 0 && nPort < std::numeric_limits::max()) - { - addrProxy.SetPort(nPort); - settings.setValue("addrProxy", addrProxy.ToStringIPPort().c_str()); - } - else - { - successful = false; - } - } + case ProxyUse: + settings.setValue("fUseProxy", value.toBool()); + ApplyProxySettings(); break; - case Fee: { + case ProxyIP: { + proxyType proxy; + proxy.first = CService("127.0.0.1", 9050); + GetProxy(NET_IPV4, proxy); + + CNetAddr addr(value.toString().toStdString()); + proxy.first.SetIP(addr); + settings.setValue("addrProxy", proxy.first.ToStringIPPort().c_str()); + successful = ApplyProxySettings(); + } + break; + case ProxyPort: { + proxyType proxy; + proxy.first = CService("127.0.0.1", 9050); + GetProxy(NET_IPV4, proxy); + + proxy.first.SetPort(value.toInt()); + settings.setValue("addrProxy", proxy.first.ToStringIPPort().c_str()); + successful = ApplyProxySettings(); + } + break; + case ProxySocksVersion: { + proxyType proxy; + proxy.second = 5; + GetProxy(NET_IPV4, proxy); + + proxy.second = value.toInt(); + settings.setValue("nSocksVersion", proxy.second); + successful = ApplyProxySettings(); + } + break; + case Fee: nTransactionFee = value.toLongLong(); settings.setValue("nTransactionFee", nTransactionFee); - } break; - case DisplayUnit: { - int unit = value.toInt(); - nDisplayUnit = unit; + case DisplayUnit: + nDisplayUnit = value.toInt(); settings.setValue("nDisplayUnit", nDisplayUnit); - emit displayUnitChanged(unit); - } + emit displayUnitChanged(nDisplayUnit); break; - case DisplayAddresses: { + case DisplayAddresses: bDisplayAddresses = value.toBool(); settings.setValue("bDisplayAddresses", bDisplayAddresses); - } break; case DetachDatabases: { - fDetachDB = value.toBool(); + bool fDetachDB = value.toBool(); + bitdb.SetDetach(fDetachDB); settings.setValue("detachDB", fDetachDB); } break; + case Language: + settings.setValue("language", value); + break; default: break; } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index da4e86f..34724ad 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -3,8 +3,8 @@ #include -/** Interface from QT to configuration data structure for bitcoin client. - To QT, the options are presented as a list with the different options +/** Interface from Qt to configuration data structure for Bitcoin client. + To Qt, the options are presented as a list with the different options laid out vertically. This can be changed to a tree once the settings become sufficiently complex. @@ -12,21 +12,24 @@ class OptionsModel : public QAbstractListModel { Q_OBJECT + public: explicit OptionsModel(QObject *parent = 0); enum OptionID { - StartAtStartup, // bool - MinimizeToTray, // bool - MapPortUPnP, // bool - MinimizeOnClose, // bool - ConnectSOCKS4, // bool - ProxyIP, // QString - ProxyPort, // QString - Fee, // qint64 - DisplayUnit, // BitcoinUnits::Unit - DisplayAddresses, // bool - DetachDatabases, // bool + StartAtStartup, // bool + MinimizeToTray, // bool + MapPortUPnP, // bool + MinimizeOnClose, // bool + ProxyUse, // bool + ProxyIP, // QString + ProxyPort, // int + ProxySocksVersion, // int + Fee, // qint64 + DisplayUnit, // BitcoinUnits::Unit + DisplayAddresses, // bool + DetachDatabases, // bool + Language, // QString OptionIDRowCount, }; @@ -45,16 +48,17 @@ public: bool getMinimizeOnClose(); int getDisplayUnit(); bool getDisplayAddresses(); + QString getLanguage() { return language; } + private: int nDisplayUnit; bool bDisplayAddresses; bool fMinimizeToTray; bool fMinimizeOnClose; + QString language; + signals: void displayUnitChanged(int unit); - -public slots: - }; #endif // OPTIONSMODEL_H diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 7c1481a..e864b7f 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -95,36 +95,32 @@ OverviewPage::OverviewPage(QWidget *parent) : currentBalance(-1), currentStake(0), currentUnconfirmedBalance(-1), - txdelegate(new TxViewDelegate()) + currentImmatureBalance(-1), + txdelegate(new TxViewDelegate()), + filter(0) { ui->setupUi(this); - // Balance: - ui->labelBalance->setFont(QFont("Monospace", -1, QFont::Bold)); - ui->labelBalance->setToolTip(tr("Your current balance")); - ui->labelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard); - - // ppcoin: stake: - ui->labelStake->setFont(QFont("Monospace", -1, QFont::Bold)); - ui->labelStake->setToolTip(tr("Your current stake")); - ui->labelStake->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard); - - // Unconfirmed balance: - ui->labelUnconfirmed->setFont(QFont("Monospace", -1, QFont::Bold)); - ui->labelUnconfirmed->setToolTip(tr("Total of transactions that have yet to be confirmed, and do not yet count toward the current balance")); - ui->labelUnconfirmed->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard); - - ui->labelNumTransactions->setToolTip(tr("Total number of transactions in wallet")); - // Recent transactions - ui->listTransactions->setStyleSheet("QListView { background:transparent }"); ui->listTransactions->setItemDelegate(txdelegate); ui->listTransactions->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE)); - ui->listTransactions->setSelectionMode(QAbstractItemView::NoSelection); ui->listTransactions->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2)); ui->listTransactions->setAttribute(Qt::WA_MacShowFocusRect, false); - connect(ui->listTransactions, SIGNAL(clicked(QModelIndex)), this, SIGNAL(transactionClicked(QModelIndex))); + connect(ui->listTransactions, SIGNAL(clicked(QModelIndex)), this, SLOT(handleTransactionClicked(QModelIndex))); + + // init "out of sync" warning labels + ui->labelWalletStatus->setText("(" + tr("out of sync") + ")"); + ui->labelTransactionsStatus->setText("(" + tr("out of sync") + ")"); + + // start with displaying the "out of sync" warnings + showOutOfSyncWarning(true); +} + +void OverviewPage::handleTransactionClicked(const QModelIndex &index) +{ + if(filter) + emit transactionClicked(filter->mapToSource(index)); } OverviewPage::~OverviewPage() @@ -132,15 +128,23 @@ OverviewPage::~OverviewPage() delete ui; } -void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance) +void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance) { int unit = model->getOptionsModel()->getDisplayUnit(); currentBalance = balance; currentStake = stake; currentUnconfirmedBalance = unconfirmedBalance; + currentImmatureBalance = immatureBalance; ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance)); ui->labelStake->setText(BitcoinUnits::formatWithUnit(unit, stake)); ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance)); + ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, immatureBalance)); + + // only show immature (newly mined) balance if it's non-zero, so as not to complicate things + // for the non-mining users + bool showImmature = immatureBalance != 0; + ui->labelImmature->setVisible(showImmature); + ui->labelImmatureText->setVisible(showImmature); } void OverviewPage::setNumTransactions(int count) @@ -151,37 +155,49 @@ void OverviewPage::setNumTransactions(int count) void OverviewPage::setModel(WalletModel *model) { this->model = model; - if(model) + if(model && model->getOptionsModel()) { // Set up transaction list - TransactionFilterProxy *filter = new TransactionFilterProxy(); + filter = new TransactionFilterProxy(); filter->setSourceModel(model->getTransactionTableModel()); filter->setLimit(NUM_ITEMS); filter->setDynamicSortFilter(true); filter->setSortRole(Qt::EditRole); - filter->sort(TransactionTableModel::Date, Qt::DescendingOrder); + filter->sort(TransactionTableModel::Status, Qt::DescendingOrder); ui->listTransactions->setModel(filter); ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress); // Keep up to date with wallet - setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance()); - connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64))); + setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance()); + connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64))); setNumTransactions(model->getNumTransactions()); connect(model, SIGNAL(numTransactionsChanged(int)), this, SLOT(setNumTransactions(int))); - connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(displayUnitChanged())); + connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); } + + // update the display unit, to not use the default ("BTC") + updateDisplayUnit(); } -void OverviewPage::displayUnitChanged() +void OverviewPage::updateDisplayUnit() { - if(!model || !model->getOptionsModel()) - return; - if(currentBalance != -1) - setBalance(currentBalance, currentStake, currentUnconfirmedBalance); + if(model && model->getOptionsModel()) + { + if(currentBalance != -1) + setBalance(currentBalance, model->getStake(), currentUnconfirmedBalance, currentImmatureBalance); + + // Update txdelegate->unit with the current unit + txdelegate->unit = model->getOptionsModel()->getDisplayUnit(); - txdelegate->unit = model->getOptionsModel()->getDisplayUnit(); - ui->listTransactions->update(); + ui->listTransactions->update(); + } +} + +void OverviewPage::showOutOfSyncWarning(bool fShow) +{ + ui->labelWalletStatus->setVisible(fShow); + ui->labelTransactionsStatus->setVisible(fShow); } diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 0d07160..59fbb1d 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -12,6 +12,7 @@ namespace Ui { } class WalletModel; class TxViewDelegate; +class TransactionFilterProxy; /** Overview ("home") page widget */ class OverviewPage : public QWidget @@ -23,9 +24,10 @@ public: ~OverviewPage(); void setModel(WalletModel *model); + void showOutOfSyncWarning(bool fShow); public slots: - void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance); + void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance); void setNumTransactions(int count); signals: @@ -37,11 +39,14 @@ private: qint64 currentBalance; qint64 currentStake; qint64 currentUnconfirmedBalance; + qint64 currentImmatureBalance; TxViewDelegate *txdelegate; + TransactionFilterProxy *filter; private slots: - void displayUnitChanged(); + void updateDisplayUnit(); + void handleTransactionClicked(const QModelIndex &index); }; #endif // OVERVIEWPAGE_H diff --git a/src/qt/qrcodedialog.cpp b/src/qt/qrcodedialog.cpp index 2a428fb..ca94975 100644 --- a/src/qt/qrcodedialog.cpp +++ b/src/qt/qrcodedialog.cpp @@ -1,29 +1,34 @@ #include "qrcodedialog.h" #include "ui_qrcodedialog.h" + +#include "bitcoinunits.h" +#include "guiconstants.h" #include "guiutil.h" +#include "optionsmodel.h" #include #include -#include #include -#define EXPORT_IMAGE_SIZE 256 - QRCodeDialog::QRCodeDialog(const QString &addr, const QString &label, bool enableReq, QWidget *parent) : - QDialog(parent), ui(new Ui::QRCodeDialog), address(addr) + QDialog(parent), + ui(new Ui::QRCodeDialog), + model(0), + address(addr) { ui->setupUi(this); + setWindowTitle(QString("%1").arg(address)); - setAttribute(Qt::WA_DeleteOnClose); ui->chkReqPayment->setVisible(enableReq); - ui->lnReqAmount->setVisible(enableReq); ui->lblAmount->setVisible(enableReq); - ui->lblBTC->setVisible(enableReq); + ui->lnReqAmount->setVisible(enableReq); ui->lnLabel->setText(label); + ui->btnSaveAs->setEnabled(false); + genCode(); } @@ -32,6 +37,17 @@ QRCodeDialog::~QRCodeDialog() delete ui; } +void QRCodeDialog::setModel(OptionsModel *model) +{ + this->model = model; + + if (model) + connect(model, SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + + // update the display unit, to not use the default ("BTC") + updateDisplayUnit(); +} + void QRCodeDialog::genCode() { QString uri = getURI(); @@ -58,26 +74,34 @@ void QRCodeDialog::genCode() } } QRcode_free(code); + ui->lblQRCode->setPixmap(QPixmap::fromImage(myImage).scaled(300, 300)); + + ui->outUri->setPlainText(uri); } - else - ui->lblQRCode->setText(tr("Resulting URI too long, try to reduce the text for label / message.")); } QString QRCodeDialog::getURI() { QString ret = QString("bitcoin:%1").arg(address); - int paramCount = 0; - if (ui->chkReqPayment->isChecked() && !ui->lnReqAmount->text().isEmpty()) + + ui->outUri->clear(); + + if (ui->chkReqPayment->isChecked()) { - bool ok = false; - ui->lnReqAmount->text().toDouble(&ok); - if (ok) + if (ui->lnReqAmount->validate()) { - ret += QString("?amount=%1").arg(ui->lnReqAmount->text()); + // even if we allow a non BTC unit input in lnReqAmount, we generate the URI with BTC as unit (as defined in BIP21) + ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC, ui->lnReqAmount->value())); paramCount++; } + else + { + ui->btnSaveAs->setEnabled(false); + ui->lblQRCode->setText(tr("The entered amount is invalid, please check.")); + return QString(""); + } } if (!ui->lnLabel->text().isEmpty()) @@ -94,36 +118,54 @@ QString QRCodeDialog::getURI() paramCount++; } - // limit URI length to 255 chars, to prevent a DoS against the QR-Code dialog - if (ret.length() < 256) - return ret; - else + // limit URI length to prevent a DoS against the QR-Code dialog + if (ret.length() > MAX_URI_LENGTH) + { + ui->btnSaveAs->setEnabled(false); + ui->lblQRCode->setText(tr("Resulting URI too long, try to reduce the text for label / message.")); return QString(""); + } + + ui->btnSaveAs->setEnabled(true); + return ret; } -void QRCodeDialog::on_lnReqAmount_textChanged(const QString &arg1) +void QRCodeDialog::on_lnReqAmount_textChanged() { genCode(); } -void QRCodeDialog::on_lnLabel_textChanged(const QString &arg1) +void QRCodeDialog::on_lnLabel_textChanged() { genCode(); } -void QRCodeDialog::on_lnMessage_textChanged(const QString &arg1) +void QRCodeDialog::on_lnMessage_textChanged() { genCode(); } void QRCodeDialog::on_btnSaveAs_clicked() { - QString fn = GUIUtil::getSaveFileName(this, tr("Save Image..."), QString(), tr("PNG Images (*.png)")); + QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Images (*.png)")); if (!fn.isEmpty()) myImage.scaled(EXPORT_IMAGE_SIZE, EXPORT_IMAGE_SIZE).save(fn); } -void QRCodeDialog::on_chkReqPayment_toggled(bool) +void QRCodeDialog::on_chkReqPayment_toggled(bool fChecked) { + if (!fChecked) + // if chkReqPayment is not active, don't display lnReqAmount as invalid + ui->lnReqAmount->setValid(true); + genCode(); } + +void QRCodeDialog::updateDisplayUnit() +{ + if (model) + { + // Update lnReqAmount with the current unit + ui->lnReqAmount->setDisplayUnit(model->getDisplayUnit()); + } +} diff --git a/src/qt/qrcodedialog.h b/src/qt/qrcodedialog.h index ad06116..c55c34b 100644 --- a/src/qt/qrcodedialog.h +++ b/src/qt/qrcodedialog.h @@ -7,6 +7,7 @@ namespace Ui { class QRCodeDialog; } +class OptionsModel; class QRCodeDialog : public QDialog { @@ -16,22 +17,25 @@ public: explicit QRCodeDialog(const QString &addr, const QString &label, bool enableReq, QWidget *parent = 0); ~QRCodeDialog(); + void setModel(OptionsModel *model); + private slots: - void on_lnReqAmount_textChanged(const QString &arg1); - void on_lnLabel_textChanged(const QString &arg1); - void on_lnMessage_textChanged(const QString &arg1); + void on_lnReqAmount_textChanged(); + void on_lnLabel_textChanged(); + void on_lnMessage_textChanged(); void on_btnSaveAs_clicked(); + void on_chkReqPayment_toggled(bool fChecked); - void on_chkReqPayment_toggled(bool checked); + void updateDisplayUnit(); private: Ui::QRCodeDialog *ui; - QImage myImage; - - QString getURI(); + OptionsModel *model; QString address; + QImage myImage; void genCode(); + QString getURI(); }; #endif // QRCODEDIALOG_H diff --git a/src/qt/qtipcserver.cpp b/src/qt/qtipcserver.cpp index 102ac0f..b26c375 100644 --- a/src/qt/qtipcserver.cpp +++ b/src/qt/qtipcserver.cpp @@ -1,76 +1,139 @@ // Copyright (c) 2009-2012 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying -// file license.txt or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include -#include -#include -#include +#include +#if defined(WIN32) && BOOST_VERSION == 104900 +#define BOOST_INTERPROCESS_HAS_WINDOWS_KERNEL_BOOTTIME +#define BOOST_INTERPROCESS_HAS_KERNEL_BOOTTIME +#endif +#include "qtipcserver.h" +#include "guiconstants.h" #include "ui_interface.h" #include "util.h" -#include "qtipcserver.h" +#include +#include +#include +#include + +#if defined(WIN32) && (!defined(BOOST_INTERPROCESS_HAS_WINDOWS_KERNEL_BOOTTIME) || !defined(BOOST_INTERPROCESS_HAS_KERNEL_BOOTTIME) || BOOST_VERSION < 104900) +#warning Compiling without BOOST_INTERPROCESS_HAS_WINDOWS_KERNEL_BOOTTIME and BOOST_INTERPROCESS_HAS_KERNEL_BOOTTIME uncommented in boost/interprocess/detail/tmp_dir_helpers.hpp or using a boost version before 1.49 may have unintended results see svn.boost.org/trac/boost/ticket/5392 +#endif + +using namespace boost; using namespace boost::interprocess; using namespace boost::posix_time; -using namespace boost; -using namespace std; -void ipcShutdown() +#if defined MAC_OSX || defined __FreeBSD__ +// URI handling not implemented on OSX yet + +void ipcScanRelay(int argc, char *argv[]) { } +void ipcInit(int argc, char *argv[]) { } + +#else + +static void ipcThread2(void* pArg); + +static bool ipcScanCmd(int argc, char *argv[], bool fRelay) { - message_queue::remove(BITCOINURI_QUEUE_NAME); + // Check for URI in argv + bool fSent = false; + for (int i = 1; i < argc; i++) + { + if (boost::algorithm::istarts_with(argv[i], "bitcoin:")) + { + const char *strURI = argv[i]; + try { + boost::interprocess::message_queue mq(boost::interprocess::open_only, BITCOINURI_QUEUE_NAME); + if (mq.try_send(strURI, strlen(strURI), 0)) + fSent = true; + else if (fRelay) + break; + } + catch (boost::interprocess::interprocess_exception &ex) { + // don't log the "file not found" exception, because that's normal for + // the first start of the first instance + if (ex.get_error_code() != boost::interprocess::not_found_error || !fRelay) + { + printf("main() - boost interprocess exception #%d: %s\n", ex.get_error_code(), ex.what()); + break; + } + } + } + } + return fSent; +} + +void ipcScanRelay(int argc, char *argv[]) +{ + if (ipcScanCmd(argc, argv, true)) + exit(0); +} + +static void ipcThread(void* pArg) +{ + // Make this thread recognisable as the GUI-IPC thread + RenameThread("bitcoin-gui-ipc"); + + try + { + ipcThread2(pArg); + } + catch (std::exception& e) { + PrintExceptionContinue(&e, "ipcThread()"); + } catch (...) { + PrintExceptionContinue(NULL, "ipcThread()"); + } + printf("ipcThread exited\n"); } -void ipcThread(void* parg) +static void ipcThread2(void* pArg) { - message_queue* mq = (message_queue*)parg; - char strBuf[257]; - size_t nSize; - unsigned int nPriority; + printf("ipcThread started\n"); + + message_queue* mq = (message_queue*)pArg; + char buffer[MAX_URI_LENGTH + 1] = ""; + size_t nSize = 0; + unsigned int nPriority = 0; + loop { ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(100); - if(mq->timed_receive(&strBuf, sizeof(strBuf), nSize, nPriority, d)) + if (mq->timed_receive(&buffer, sizeof(buffer), nSize, nPriority, d)) { - ThreadSafeHandleURI(std::string(strBuf, nSize)); + uiInterface.ThreadSafeHandleURI(std::string(buffer, nSize)); Sleep(1000); } + if (fShutdown) - { - ipcShutdown(); break; - } } - ipcShutdown(); + + // Remove message queue + message_queue::remove(BITCOINURI_QUEUE_NAME); + // Cleanup allocated memory + delete mq; } -void ipcInit() +void ipcInit(int argc, char *argv[]) { -#ifdef MAC_OSX - // TODO: implement bitcoin: URI handling the Mac Way - return; -#endif -#ifdef WIN32 - // TODO: THOROUGHLY test boost::interprocess fix, - // and make sure there are no Windows argument-handling exploitable - // problems. - return; -#endif + message_queue* mq = NULL; + char buffer[MAX_URI_LENGTH + 1] = ""; + size_t nSize = 0; + unsigned int nPriority = 0; - message_queue* mq; - char strBuf[257]; - size_t nSize; - unsigned int nPriority; try { - mq = new message_queue(open_or_create, BITCOINURI_QUEUE_NAME, 2, 256); + mq = new message_queue(open_or_create, BITCOINURI_QUEUE_NAME, 2, MAX_URI_LENGTH); // Make sure we don't lose any bitcoin: URIs for (int i = 0; i < 2; i++) { ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(1); - if(mq->timed_receive(&strBuf, sizeof(strBuf), nSize, nPriority, d)) + if (mq->timed_receive(&buffer, sizeof(buffer), nSize, nPriority, d)) { - ThreadSafeHandleURI(std::string(strBuf, nSize)); + uiInterface.ThreadSafeHandleURI(std::string(buffer, nSize)); } else break; @@ -78,13 +141,22 @@ void ipcInit() // Make sure only one bitcoin instance is listening message_queue::remove(BITCOINURI_QUEUE_NAME); - mq = new message_queue(open_or_create, BITCOINURI_QUEUE_NAME, 2, 256); + delete mq; + + mq = new message_queue(open_or_create, BITCOINURI_QUEUE_NAME, 2, MAX_URI_LENGTH); } catch (interprocess_exception &ex) { + printf("ipcInit() - boost interprocess exception #%d: %s\n", ex.get_error_code(), ex.what()); return; } - if (!CreateThread(ipcThread, mq)) + + if (!NewThread(ipcThread, mq)) { delete mq; + return; } + + ipcScanCmd(argc, argv, false); } + +#endif diff --git a/src/qt/qtipcserver.h b/src/qt/qtipcserver.h index fcff10d..cccf200 100644 --- a/src/qt/qtipcserver.h +++ b/src/qt/qtipcserver.h @@ -1,4 +1,10 @@ +#ifndef QTIPCSERVER_H +#define QTIPCSERVER_H + +// Define Bitcoin-Qt message queue name #define BITCOINURI_QUEUE_NAME "BitcoinURI" -void ipcInit(); -void ipcShutdown(); +void ipcScanRelay(int argc, char *argv[]); +void ipcInit(int argc, char *argv[]); + +#endif // QTIPCSERVER_H diff --git a/src/qt/qvaluecombobox.cpp b/src/qt/qvaluecombobox.cpp index c0ad8c1..d7ce3d0 100644 --- a/src/qt/qvaluecombobox.cpp +++ b/src/qt/qvaluecombobox.cpp @@ -6,12 +6,12 @@ QValueComboBox::QValueComboBox(QWidget *parent) : connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(handleSelectionChanged(int))); } -int QValueComboBox::value() const +QVariant QValueComboBox::value() const { - return itemData(currentIndex(), role).toInt(); + return itemData(currentIndex(), role); } -void QValueComboBox::setValue(int value) +void QValueComboBox::setValue(const QVariant &value) { setCurrentIndex(findData(value, role)); } diff --git a/src/qt/qvaluecombobox.h b/src/qt/qvaluecombobox.h index 11f342d..1a47bb6 100644 --- a/src/qt/qvaluecombobox.h +++ b/src/qt/qvaluecombobox.h @@ -2,19 +2,20 @@ #define QVALUECOMBOBOX_H #include +#include /* QComboBox that can be used with QDataWidgetMapper to select ordinal values from a model. */ class QValueComboBox : public QComboBox { Q_OBJECT - Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged USER true) + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged USER true) public: explicit QValueComboBox(QWidget *parent = 0); - int value() const; - void setValue(int value); + QVariant value() const; + void setValue(const QVariant &value); - /** Specify model role to use as ordinal value */ + /** Specify model role to use as ordinal value (defaults to Qt::UserRole) */ void setRole(int role); signals: diff --git a/src/qt/res/bitcoin-qt.rc b/src/qt/res/bitcoin-qt.rc index dd301a2..3ee63cc 100644 --- a/src/qt/res/bitcoin-qt.rc +++ b/src/qt/res/bitcoin-qt.rc @@ -1 +1,37 @@ -IDI_ICON1 ICON DISCARDABLE "icons/novacoin.ico" +IDI_ICON1 ICON DISCARDABLE "icons/bitcoin.ico" + +#include // needed for VERSIONINFO +#include "../../clientversion.h" // holds the needed client version information + +#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_REVISION,CLIENT_VERSION_BUILD +#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_REVISION) "." STRINGIZE(CLIENT_VERSION_BUILD) +#define VER_FILEVERSION VER_PRODUCTVERSION +#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" // U.S. English - multilingual (hex) + BEGIN + VALUE "CompanyName", "NovaCoin" + VALUE "FileDescription", "NovaCoin-Qt (OSS GUI client for NovaCoin)" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", "novacoin-qt" + VALUE "LegalCopyright", "2009-2012 The Bitcoin developers, 2012-2013 The NovaCoin & PPCoin developers" + VALUE "LegalTrademarks1", "Distributed under the MIT/X11 software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php." + VALUE "OriginalFilename", "novacoin-qt.exe" + VALUE "ProductName", "NovaCoin-Qt" + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1252 // language neutral - multilingual (decimal) + END +END diff --git a/src/qt/res/icons/debugwindow.png b/src/qt/res/icons/debugwindow.png new file mode 100644 index 0000000000000000000000000000000000000000..1712adf0e719fa1d8865fd31d2907b4736e6f0e7 GIT binary patch literal 5402 zcmW;QcQn=i9|!Quy{-}WitMZsN!gNXUL!>K+L<@mWRG$Y*IpsBP?EjN3SI6^vR5_{ zW$(?k?(cJd@5gz*U+293eV_9>@AHb$(^03RVW$CsKy;cKs`^0XA%z|a+&9+>8-N0} z(Nb3>{{K*$9{&b9)E*j7yg?vo3sT5>I`SJyO+OE$15kPXpQzL991!T5zoshE&~bJ% z^UU99bo5x!Wm8M1pIWAo?9CM~N&`JNFUEWrB;L3nmZIFujQa|EvaJ4XM~dV)(Yqg6 zOZRF`8*itPD@W8@&=_sCKp53D!ylRPFlcCWcFm$I4lk1)x4pOwU5+}QX$|~2^=#ww z@_A+B(mU_(#8;Ppf`2@haLe+XrfvK-P_Hn3>MC(OskL$Ih~Y0=2}yBn69Azh^cWCP zYvfe+%NY4h9YC4STmV2i2LKTHut%J!2rB)lqFP-i_)uaVx0)hmg>gtAPd5bHHEZim z&hD5y&#(1Hlb;Rq7T=5Fmn?TQ9jYc@LysPpZd3SHjbe?D*(Fn@0c~{{7m9J3Tz)Ko z)A|aKm<+Nf9#?&NM1pWd-E(=e?*6D$2f!R4ea zUwlt~tU}_!61$FtYU)& zO@pP+aAcV5jCzY{4=GS0OwZ;9^r%1OY_SuOy0m&Lk0v(OKl4rh~d zYS>~HifxQPz8Pc{oAI}RUiSy%uIlfDFJH><3iS}wqB3N^{hnWpB{GazxKv7wTMSsF zHs1EW+U2zQYpS3Q{L+vsP2JsVL30^UT{LB|S%UJfYo4;iSJszpI#&~)6Q?hwCx$ni zjwBsVubLK>8;l7i4KGN_&@I3kRklF?-6~aO8jMmnAd_ULTgq#66voKd>AW@Iv5$Rg z!~Y@?4QNZ70I*7OIm!Kc0WmZmHLj!ZZD=x|p+i>XnhajSl=BF6VsecJ?>JIiD!s~8 zq_1!@X(*dcR{05MQ?!-)5M8LGcM*wh!_ISEM9Ir(X3=#+3=(dufY8^LyBgZET=O=I~Oj z4Bg+P2YfKH{mc$I`c@VIrRT%!s}3L*mIfIcErUmZq?bYar%Q*62F;ov0=!T(LU1CW zdA714Y&XF@P;|W)Q3G2D+d~p~qA&AWSKxd3a)P>;-#pTtbSVGN%@dYNI!TNRkR*u( zS{d+0!I3^%&@|od3A2Vt(=Uo-kv%nqmE?eYE^3=-)*F$yMHAsxF9OrauTuc#N*Bu; z5kBy-2mClhs-N8<0X(F2z<>>LE-iuHDb6Kl?J@m{=@OISr#ygRRklNNc^#?Pl@u$v z#}mU$`IuwWPM$(oy|;g(muF>JB9B<7o;kvI_AdezxwB_=+>8A8Uz?XzjE*^1x6}Z@ zGDbRS2KdvQL)mqIl$2S5@lDgIU+K`74d2XX=~DlD$Oe%Qdymu={g5@#x6I?l*bp;4 z7uZL5w=bpFQWa=TP}pfco4MFR4b(eK*M#2wFAql_DGD*|)=wvQ-+{V^AWC1CenORM zrbY>}r zk5I0GtNo{BH*<1{g#hRxfyBdUpg=z3D5P$5%K16PG8u`eZl*vlV@N^*U0`=nAPvH( zXw8tmAkQ#Wx#xR&#JwxL4SWE|FRsUE$<6;f;EhqgRp_@_0aV1Pld1&b6xCtaI^{UL zQVz8Kg{I#3-)de3f1pe^ec!R|sa;8KhIpbZ{5qJr=z9J;MqTkp-DD#Ga zZ7gmiE5HDL@QM2(1m=?qF4lB@^A56WM=~hfY8l)d*!e>6UqO5Z&*Ek3nZXvN6!Nhi zBTpu0FU(%W#up%U!gx-72@##{^Q*bAC!_HR6%P5S%dY5j4#9}wGf}h?)+8amg9t}X zNP127B5Em9#vZnEe0X*mME{2cK-p-pl`4z$OXaldSTfIB8hdB%;ug`}mzE_Y9X3^T zR1!3IK0+wjHxTXOpX39hCwr{skX#E$6s7D0SyR?lVKXwT?K!MLhPIK?8@|K^xymSZ zHLM8(ARlaRGG&qeBYgUD_N17Vp0OEE7|eKku@_UF2rO=hus z!ht4dmYe={ILC()?6x28rbeJ(Z&jA)2j7j=LYoF7gq5zUT?>8U%AD1ngylnP9x-&@ z?FU4zU-}Z8;N><r|=@d1iPJ5(@) z$cGPBKjmEYhwN?lwn`9nb|}xnF7POUNlib72c&Te4aj?6EP(M$aIv5liDhYzLj~Cb zpyD@fij&i|u}4bLhBYm(y)Q$<7Hk?#h25A3UB$C{`FT>b#gVnw?y2=GEp$={dytv6 z4DBVPyDK>-j&PO8D=ijZPXpiJWw=3R^VXCohPqyw3Ml|WM zDCghNXDY$W$TWNVEr*my)fFBUQc;PA_3|*XbGrq8-F;dtZ;q}%x_f=f)3M_tXOg$% zvSa^$o#r_W2zRMk$Dus6IA)&y*qXn0i`_>X*31V54yc33wK;}=){>#Kuzj=8>x;M5 zkq1mcYSt*Ze{Ap!uwDjdQWxjvw6kUCuwd$XUVdRDVpcZfnsWFp&w6wnX>&-S1a~#f_9n zff`+9dm{i7gabo|hu+pr73gN%*(`Qf50a&6ncX`~uWG=x$H%d;NZ_U%pV!`3J|~>) z?=JINT^D)HqH-|5(l!)@pWNK=^9w`&l>hfWG@nOMg9gVr|Jp-6Bx`x8@#UPGe?#*$ zK4=)gXd81k`PN+3{y1S>$@gn}1PGc+>WE)g`-5I=wRv-69^8e+wMlb^D*QW*UzdO({tOXb_xp!%6{xTN zhf*t#W(qC)V104Ew(L=PV6ob$Uz}+yR$;@=mmQwsf$OdoFQ;`R8lDwRDN+#D_s5s3 zqU00@2KMp6-vN0tGBI5?UovU=G?G9ycvn#loR9R(V}H?qy$>W{2Lp!y>!(a#dk+{$91HJai-r2u8o`>Y)C5h#?XC@a;aKS`g16`hgF z=-^T(Ara)q(VQ>?&8r~6#Y_L|d!f<&2!QEa12|X9_0-4}E9$?>zoZ(;7ze2I?Mn@e z%fay-Bsi16b`$y!EC5Qu4*k~!*TKsf{USNm80|Nj;?yUR?kCV*3yJYw3Px#9MoI(M z*vDckDW~}glY!V0WusymRL+y7#_b(WsLzdyB>HVyuXXQdjsJ>RXI+8;S#$ z`x}Z`DOzT30Abo~sc+*uR)c4UvsK5zoL_WpKXfXP-L<(I$K&+)*SmZvoOt@@c>A~_ ziJaBGk3paGhU}WOGeXVEnakR9%}R_*nTj`^o2Ov-A1E8qt0O{dOoucl?xz4{(g%41 zvJM~PE>{eRh@dc)GEOi8Cs=Yc-}l36g4T;&!8yYXj++2p zPd)&oDc$ehrWuX=Gm^m|S{A{?arz>;8Lg$l>l47-N=~t~AK+$XFYSdRZ-J z3Jd;dy~2WN9kE^$p(yJwty#nUnRDNfmV{eukT>ImK|=QJ<*X2HC;)S=3keUqf)2&v ztUzqZ@fr^y!RbdewI2%bxHd2xE=Mcqt-qAtOJ-D1YRD9=1i2BiaM5*^a;(yQnR+33 z#(V!BfKI=uz(;Ep`dP94^4b~WkE0HNifa%+{)b;X0O{%p5K}dO2vGKU3gA_(C}yhT zX;J`iz41-~@QTX=IBzf|$7)ch245rmx1gS6m<%A(OnJk9>oIvle}(rt?&AHK{4tv6 zb=<$d4~pGNv)25)A=gtW7m?LP69yZCxe52DLJ&p$CEk7A0*m|r%rt2ZLmNI#4{cIy zm66QFd;Waz4A zTfi$R$MK3=+{cM+bz|dZ)-H5o5+pn(-DzI8qUUYCvcmp8*)4y;{32ET<+aY6iE?^X zCF3J5sO@_H>1O1tz;?99>as?{C<)u~KbU%)i?TP|0I<(~rr>BHJlVyyg$IdXn`rVof~1e1YQnk`lT zJlW4O4!<+!ir+dHzN7x^Z?pYlHq_deV3c9)8@k}sHTOZEUZ7 z#ItDyKD`vRN&KQd`3V>N3&4=gLW1+W-r2dFNjN z$JOdWL=9m0o7J)XOqQ?vuZJ5fXYIjq`-~*CrUfOzA&QsdFQHjCU*`86(I zI6~X%VL^#xS|n`R>R)=Jof})BhtK@V_n|o}->N2)P-PmyaFkuOxGD9eG|AlZX=AW3 zU%CL`X6c8D;QG@isu$;{gv0&hSsM3<2J(6AA|S$5+X+COWf-#f1R zpRTEQc&;m~|46Xl23Pv`!?22&F;y3v110;H0GVxBft-|n&EN$?M4HYmKKaxGy{2J5hWq@&Spn;7d!AIpR-Z0+D0qA^H<`Xs&L5>g6V` z?9dr&wo@Nqj;)w0K(AUMPCOO2IwmbH^bfU0MZ}ua)Z${9nZ_L=$q!BpZR1>%XPOc{ z9PI3D^Z-eR z4~K6*QYd6DQFBg~n=v%&N&CtAEPlns{_PX{^ocIN`kEZ{_p<<(;lbf}W`Q5np5L<> zbmNNJhXbq(ct06zL~Q~NDU*Xq*3O<3)#y0n7#riWF)|e1BUijH8ZRxZi61y9+=hu% z;q=PU9DUsUwplxBr`X5Zo5MdGi(P&68B=DG)Y}r+r&JwWPCnE(pr-8JQpHS3x}vmqd<7ZWFJG$IH#IZgte~wngu^I@;W*ZQxa0FnRp#@`Zuz zh@!@wlKe5QD=Bkz+~M3hu6Z1h?-(P?FD{9w8w=kCcl~(VlG1&7&BI?mREdAZKU_o?Y#A|EiwFDrPD;EV<7z8gfWbow>g|Pf<^&GamN|KfZrd{Q%SJic}3>m zl67zMt5FNEi@XtFXzF9&Ztvs&)XNS8dNCy=A~N37+as)_b5~=uN8}MD=>7!Ml-{5l RSZ9Jjnrb?#mC7j0{{UqMhiCu* literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/qrcode.png b/src/qt/res/icons/qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d97174b39197ce4f457640f948212edf109451 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRF%}28J29*~C-V}>VGHmHasB`Q zKad%E=yDxMI?3DJg`tC0)&t1lEbxddW?Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00FxI00FxJI_%@(00007bV*G`2ipb= z5fvp6?)N|d00WCjL_t(I%WYGCOq6vTem>vtd+)vH9dO5iaB&cD{&>+$rl$R{AF~v< zim{Q&EN0nSH51Ef(=z8;rMcD?P1{iH*IZ@g%sEI#WrNfT%rM%E>o^XO1MWES;DF=( z@$C=NYU}gY^T+c%d$!N>5dP;>RdqCM!h@0{eU4=sl)O69UHPfwB!FZvs9C!H50|b1 zAy%WLq|ep7?S=zhC$DB)0ug~Tf@ulphKX1#_C?wI$*pVr14FgV2@3$&jCeg7w|35L z+gT9FPP0{Wn;qaX!lN|Wc(&_gLYMW(7{z@)-@0?J6oy<1Tlx6PGl;$xS(hb;*p*#Pr7qzUa`MQ7%7pQOiesf`~4l$$>|OW0KR=8 z?~sj?1B?Tb0=KVNKGg8XYp1#$7lBIzVD@L5{ZD~OAPI2IBIll}x$oRxZ&t9HGIzGu z$sZQn4oC_F2n&mH7WuQ4TQp6(+PdeiQ=dGy^s%b_-TSs5JX2|rOyDx|ZVaqilA)Ed z=U3;gCB{KS05J$=$E3+o{}Y`*yk*%!k2_=e()qp}FRxAC&=_=_xH2h%U=ldzc=Wb{ z2P8hrQ!?`p1|otWCb|YD`vHVw2_yC0&zBA`W@NtkO`=7cuV%VkN`R~hSYRm1wih!1 zHW0(VOlTw$ZU+$GP;t*oo}7H6_N$IJ09+V&B2ca>78hpJe7w7=9l-2WyIMCB%~XdG&=Z>;mOlD1+O)%82rvs;u>bP9y67gr zcA@-cBeQ>a^moNB2l^t>E&vLEabcMdLV&}<(X;V0q^c@!+LGCrlkL4vk`=l##YaM+ z(C+A%*|hE0cnW|HzyL7Pu)Vb2?_nE-sf%z=sHePcyiA(s7%<;EG4$fA)ho_}81pEy z_u$1r-;))0e*W}O+q>)6tSILs7TLMTb=ZVUn7Yt)opv43_No}~<6VO(sJU`%G#>r! zrsA9`fFpz}d;14RKRB)-aeYh=Et72u zvE{FCTvF%H&01iAff$D+AQ*u-2?Tp1eQ$l)xi9KX9R2z@hX1oh0H`drQ|DBX`&Mu7 z?a$tuTb|+IeoKJG#*NsAr-xfw;`u);7}LVXE@SGN<}WN5Z895_*YyAZ002ovPDHLk FV1n@J>}UW0 diff --git a/src/qt/res/icons/toolbar_testnet.png b/src/qt/res/icons/toolbar_testnet.png deleted file mode 100644 index 90ed6d99f43f1bbffc1e7e16c8fa068a77a79130..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1037 zcmV+o1oHcdP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00FxI00FxJI_%@(00007bV*G`2ipc7 z5d|(c?xT$W00WIlL_t(I%WYF@Y?EaeJpi&#&jlIVVq^a}MEuMo|>!_HU_Hwz*49%b;j%YWP^a^E`mq(2#2B`oCCO zT7=ku^73)lp6~Np+*xcZCmVl5kKG7&9F1B4z!u5RV0ZgK zS#x0dM0$?2RpJgXvSBJnv)%23=aW&pertlJeLmmQ2Yx9UkzMSe>KEMX?gfgXxSIpZ z!;+2560=j^8aCNC=pyySc>rUXjWQAYNEKTpurSLF&|IFn_s*5AQl#`NC2?+ zhy3G`9Zm)gP7dUHitXo)|FKWK;Oqyk6M&qZpZi~7!~r;v;zH`S17(MgJZWxZ8yY>? zZm+bNSu(hA0)kLlk+;h0m)EMQdh1Ac^@X=SUGw~*hT(%PZ}-%bun`ju6fP@W@2pf; zvrRkvk2B&R1`rXLB|}dz^qio+rjz%r^kk&3D_`T=wQ+aamTT?KZ{jmzh!F?p98Wx3 z_>?3|p7O(;&P?}^q9}Y zymR}e+40$%b4dU?00Y3(zVB9_@cP+vre%oW*zoA#y15$Z9_J17tB=AvU)*zlAAzxy z40~qa<4Iq`%MX7MeD&84Yd2TbG09rR9U{*z!)1yEp&JX-x>s#>D|6#~a59Nyid~zT zz4?2|%De_JffXe_#huAZ1_EP$RaX1Aai{KOlCYaX7lx@*>!I#uEj0T{pbgQxWvbj{ zw>f3D?xlAtkNNVmDw2P-AU7@ampZyhTu=6YuOPqfI!UlQN-8Z?`00000NkvXX Hu0mjf7RKiu diff --git a/src/qt/res/images/qrcode.png b/src/qt/res/images/qrcode.png deleted file mode 100644 index c89a49bbceba56e1901487b970645dd9eea95608..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5993 zcmW+)2RxMjAAgKETxWDfHrYGbgmm_0hh$ztx*w7ia%7!7>XN-fLPE>PF4>*S$dqxehShp#DFh~hg$j; zFc|FTjPW%1mBvf&)Jzh+|@k(Z}-%Fks^w z*fnV=!eFo`v}i-^+M2vd{5u&@%e!}FigSDE3rS7xC6W&@517R=?JD|a*+$%%&yhJm z+T<`w8a}>=WKEd<#%5CC@J~a{Zy;E+uG$<`N*v+DQn-#0XzxC$8SIvP_SA00LKP2Fwz zZDL^&;lvi@l#q}BC8woDJ6U5e1$MJ}dAw_)KVILOTV9TF8rh!vLY7xhK;9-3rnxmP zEk^i;J6(0?7c|5>+_>J5t@cQkm)B86+uWRZ+p{t9^U>kqM8l(+cke8Pp01C7{M{Tz zVQOk>R<;`_-PC-KTO&l`X_tcMWJAy&kA}<2%D2P_&j|!B_27fms?U~X7PsGFyZZZ$ z_4TXkWx120xp#JU6gIw=vCB$12ERd3_YJaw>RfVOZ zoE#+;Rl$v|-Q8zh&N_vRZ88d(SLNmJ%BF*j*+ySJFU`z^xnUJJCciq%Cg@y(93hJa zlB0}Q2qK<00JSz+N2l6&wyH^i-4qw)6WpZm-;dOw-cxIm!JT zRA!Npn>$4!`EE_OWMyTs-ln7a+#P%Y@|bKW8(!7Vwk+SJ+ww zBvw{RHL!|quZ=W3y29}6V5IsETa=_tE&hNI6%{o&XbRyS8e?4?3|vBOrTY8&5cKp_ zm6en4J$Yme!otFMa3`1fzkdDt;>8PI#Ekbx5{Y#4=FQxYBhkr`iHU3ip)TmpMp04G z?(S|_7+FJkUEN;V>m}CMWc#YBvN9S3A~7lH3+wvY8Yd4=QjWQ~d7X{q1AIw|YjAL| zu`zx1prg#t&``tCN;%|pdgMDRb!ujkdqnA2Gx{SpT+7QHnx36yVqu|zctQ^N2nMH{ zxW9Ce(9>m5dKO~LqPaE|9`5esp^ZU*3iMS}Rb7wv`?6KbEZnWE(yS#qHDXct2x@w6 z238h!_Smt@aJR36^78U25<;!(Ki_Y=X7+Yssf$LT`HT5ADFCe-gIKqDICOLFf#DtUe6E; zh&_R7J1a9&HBJ}-hquc}T)A=q`sJ<6^d>$&e%U}x)w0S)-w>bVz-zChYKO&2KxVuoyZy@l^(E<-aK{bsK$qcgfe;x#-QCftHs1(o#J!0=ee3 zYh;VyRZ+;Usp>n7{{ zle*_Oz)Pu6{Cw}Z=>75f_3ITC6$iC;c6KPL7ib}O-09x%Wq5aQ@4WX%?~lP$R6eya zF*MmmuGQ8e$k?>Bv{5Xm1v4>%N;U#wgGZ&MvD%;v4e4_G|A4i%1*4%^URy)Sxz~NF zujiPG02#_Q8YU8nLqh@#FfA>ufwvhM8Ar#*zA9YbvC`62O-`c!u}T~cCHbaV{MXqZ8y;?xKkr06+`QfvHsb{z1YuI2L=WJCxB|5o1M+b%uMkH2nT7JnK8*e)5=d)ifEhp zH9uchTWkBtXL&x&K_~1qOZlPt!F2e+!NK(O^aWuf-oN;)9sn{E6BA^b4VskhB;F{e znVA`^LT0;ESIWJ*F;-KF)Qf_Bnz4~vT)Zn#WqZvRExe0GNl>e%o!!LY@5KujE*Kgb zUWSW{h-fnL6Aa2s-x(o*?biiO{L2HA~WXi?({9CoJUaho* zQ{&03{6pp(;6@W^zu zP;P(Kt5-*$|LLLb=aPo{`Yke83q|tl;opSWD~2gFLqgP}oC03+aB##qjhvkx$7qDU zFw5v&80{7Dc7`qKmRg8?e>qAbV?yyWno_o69SUNa(rjgM`Y84K~#>V!z zTuJLbk3cA7_OisCYkN~xCR#I!tr=yYU}k1^sTl>}yZpTvMFoZG6y~e7V5(pAy7>G1 zkM++?PlrnH4wadMi+Xz{1qC~>3a)pr8jjwR8+-TP&mQn+EH^jz%R#s8a;w31%mZg< zXIEEY(OA^9m(sOs*D%#=7iGaIN{Gy~=LS9YcX!*{+hLJfD%oJy#>xsUbb5LUdcmgR zd5dF2bTlYOiqP=?uMW%uA0LqQGFvGtF@lqm6Oy(#Kfk57_XdATd}87c66wZ`8|Ufi zhe;%5W#x|c_VG`?-R^Z#x8KFL%?y!9WX%T$A38AAHWhhh2uC&+Ztg_OJ5~`4desaF zBwb4AZJ7cXUxS`R1cenQ6~^~EKH;$$K}1SQM|FB}G1@;@`60kE>{zX+yu5E))cJtp zPrj>aYHE`~n~nAUo8--axfTB#tM%WU#N+W?s($ea31phOy3`O7iIi|?;^YbV^)+R6 zxC(c&{YB{MJ>tt7Uxmmaknq>90j*Gm+SU6B3JX(cK7M>EoE!WEhTu`hR$C9ySG;~b z4tUCdPo~+?YkGV4Z?s?NGqb*puVUijm*H*Qe6SCfE{R^A<#c)}6XvF)-~2o5!w7S7 z&2N7-?xZhsF&!9q8@N}`+;-FPhU%|hy)HF6h1Up4>1^Pf18#Caugq)*WM^lK1OsCH z;U*_4D(bc~QSUzo|EDrC6_dVdIymqoefe2;pEXrnPC!!fM&TeRU|t&CgwfGaJefjf z#h}|!V31qZqu(qN)(`}zngjeq74FpX-~F4SV@o}JR}>Xu}h+D0(c+Eh$< z1^S`;{Y&B)2I-U4F4HV8FSmE;M#&qe9_{RSBjHTjUBbC9dKbpW$3cFEhleRbGc#{{ zcu1}M=i%XT|NhTRvsV$ zJ6Ii*R8)W(`Y|*#Uhhv2DRCP8{~q^!edP->hM*=oda9%2 zH;d_}zhd;O88%`D#dFLGtekbE?HllZ&^(16wWEz(Y!t&CK1D^v?^Ct+b?)3L5Zyd2 zUMgAj1xrt)v7teLiN;_*H8nLWJNvHeEl_nk27P^f2h&keR0%r24;OQ(DJg@uTBzT9 zPFac(gpIO5=pY78PEOG8?CkC`Yz>W#+Wd^P-ocL_1Av!ciQAm0Pa_a~PYxb@wC$Vm z0?oa!x%t_h^eQheFU>x%?A<#*Z*Qi*mM$&}TU%bXwpl>4t&qz9tc?<EUf-KYw14l|4B*Nv3!A^rVK2jg1AwpgiJot<8F>py3O&i?_F| zfg3mj=y<@`(NR%AZm)so`mNhQFS4>wR85VIkLNlQLPA1-o&>K5Kt3@sF()Sn>@X;P z`|4F}2j=kbkdLCyog`K`SZY>w5Ud{EmZB$+#C;V!lpY4NuJ>C9mf#}1bFA(@D1+oC zUpgu04*}B#SSxA$n>2Eqhh%GuGjcQn8YX6DPn_6dWxYnum$9?dfsn>||SxhO`6Wr8K>nA_V|5|@Ge!zut)%`7i3udlDSm0H{m z00-^u?cLkkv#PRBz~lAM=mwuA zzRI)OoTj|8Qb!^+ww`G0rZ*#($XZ61qG^VCW(FGZLJUs|)1P+nf;6Ii(0ZN2=PXJf zJ?|awudn{7FuZZ2(QBr)>#1;ecQ*w&xtN$3I3w}K`tt1T&e`#1;R`Vh4P4;hu_x}B<0u={Txmr`$4>y`+S=#rJIkx8j13K+8We*H zbo(3gLe<~n-aXN$Es%EqrS|7`x>29jgydiXT+`bD?N?+u+qs&7%0wU&_@`ZbURuKh z<$!Q=ggBTeZ#R%@;O~}KhXWF8z zDs34VLU%aYq3AY77$p!%6%{vl891*7T7dKg1mtyHgj1=<8Wal&3YJ;C%F5yl)8tL- z<^iUU0p{)PEdvMO18R;ujQ8F;``C1znd%WX?WP#Prb4IRy)L5zGvmt#S%2^XM48v? za)CAyl$|Ua-0-;sV+;zU9!_;#1o`RHCtw)LN$%>on;RRGMB?>lDz#mJj4{=vBzJ;= zypqz=+}zx+U#L`ue3PZs)zy`im4yW%ByIhtPn=v_ky`n^F4y~lFc>Wnr1wYLk&NfG zSHfQm^i;O{^@`}nY~yzMfg~ed$Pnf)G8_C+Wo+NXzdzjqI%MZ-%B3_ZI~D&8PW2#w z6FG#CqvaAz^@pM&RI%CJyLapA>c}+x{1l?w01w;(bUWdYH|X~E?c0Ok5Pkiwmb$(>;bB%2E#mM*B;$%!`!`OS013GugM06#Q1Ov^ zsxqo;FP{b2=~b`?`}MfXu|EzD-VJQDl}d__MOC72>6>M(QoO3lO zIEP&Oq(2hH-x3|yF}q9&!0w-M-yR0h0N#uSQoxRY+}=*9UZE%O)}|tfN5iGsT1rZ)G^NGSONq1ET1{R)ayA$c8uyZqN~G4cFZFX# zmq#*=*xBzWvb@b47}=xnyY?-HQO{pc??F^s}+&&snl|_J$pFtDUjf*T6P{4vFh9Ff#J^@4rUhRZCyrxu3oxyCx59}SXEPHsq+pWrCNTv$8|jy{NVefhlDwqQ`=jf1??#vNcOkW*R_D4=!EdIskBDx-2d%?E3TFe*r6O z7ca&<)bN~C108B1Hh7+$5-5vZHtD_Ex;mQ3)~U1o_XfqX;A((j(3`v6EEri2R@c6) zLvWiEzsbfRR+2SiK|ukYWDNle?)`B*kEgiqUEL2w`KQ9i%3#C+C~Cj|eB zX-R6J`py@VugHrRKl-lz0KtFxQb>)dthDq4m{b`VAvQkaVF-je*n>vH1A)El=;#nJ zR`FR%HYg4>JL>2yop89TSX@h|#x6Lkjo~wsi`|5(8IvLzwjvfr5l|?oLDx@8K$mF- zpQoeaU}w*0+68tElt&>2+UMaM0EM<7ZGKar!@;YAwp|ZH@!#|G{7cL|v$%CDJvsS} z*NoQpgv8QPk%DoJz+Jrcib~M_Ys+$ioW>bd=nGQg;& zqhF%tWn^TOm1CrGVo(LZ%=*mtxB>TT z#a33>=+qYMjmSF!TZ6w#+$IqWW4d3|9 zV7~JgSi@4SFnf9)^{n*t^wd;uz~HL>8%S1GFk=9V;df5I`f*Q73!j7px^R#|0|;XO zrGfk_u6+@ak$e05fP2os;O-jLAK>DairQ4}hO~mr%CRRf#Y8A|jEs!bWZFSZO-*c3 z%X>#aSao*l2n!2m*;fq{mwkkVOOlfr1YX1VQ@&yqRF#!4!lOV0&dx#_?QzHZ|IONf zDGREE2<9V$aF#gjs$npPTj1g1`VWgmSp^L&0?gMF(7L0mTl*JhjBbsy64VPwaYR^Q zm{~vBErj{X4!kOiLpNZ4@vMcmcxJ=w!{oR z!i6|xq7_p8!s@5pA zM@v;vTIHwj_jlgspL_4Q_kKR-Ip;a|`P?aqHc6I?j~AZ-O!~TTT>u#w82|==rLYv^ zc>n-%a*E6Nze5QGP>_*RQPW(!2EdpAWaN|-6jby;Y6=SAWepiQ1%Q%S0LUVvWnxFg z$|i_=LCr2J_cSb}sJLW8NEl*jrj0VU_wL>2xOQ7-`MZNpcxu`u64xcgCC`5>|AV5$ zQvolN63hVdOJHgW;C}^|Av24B2_-8VyP#}}j2#XrbX)61(Zw9#$|VXpGX*n11MovH zYpjVhPH%7}me-LiHeZS;1OhzO2a|2YMG9PbSZwxr5S+q>&gkJTo z*X_h%rk@0}=i*ltEvKrxClA7Mw(-TM#aCR*LZUZfu6G%KJ`JTa8CyFVSef=Nrvc=* zQ=CS=vL}u_{BY)YFjfLxr)r(j(E6cco0NN|u}MSe-AW6@(?ZPjo4&*k>PdPf6F;Hx zdo(_h1(wT6)=ij>B0`|&{x@Rwg74DulCpH`A6&$#xULhb%ZlSz_u9y)xVXedFS6hu z{Hjyq&Y5h0^F;AMR6)o&?~gRD{M<9uO;HYstn5Izo@`h>SWkI-F9F}iiPR*?Xd)!? zXFhZZ<&5Kh?hoC3zPnESyzBzNc={&p?sj+oxmPH0eUFnqyreXp9JWbQ>U#dNFV5JI zkmRjN*6+=5jAPm`yK_l+5AP}R_VC?!&;RdCB*#l=+^HOp1PLme^ft79Nx!EYa03H{&}}S^PtZBK^z@8)yqlo zx3-okV!~u-UyLKd=c==pIlpqU2#;%{enYs&2vhr@1ZO(~zxA4-DkRe<&j`FlJ*T$ZRrL4k4lH&+GN8|<*T?0 zz!d(>UQFrd_PGpDEul;zaZ0B)i*CB95=e(om=SjQK>NJshdW|1fLQ0l=xkOjXSfJlKhlE@%uwXp7>MRsrWsKhe)f%qWIMF)SaK?KkffSE zY@LHLhMU-tkZ&;J-g7CScXSBs{|S`bo~&5Zo#qf|SRcP!5K(Rl@!8a*vI>vE0!?0D;_Cbs<-fK4fM6@n`TzJ z9mHTy|6VYz4<=lk39pFwn8d%jj79B;uZ5#=%M1aWsdo#mn?gc;cwsw}3dh#)w~Fua z2n0!Tp|<3l!?2YMWIV||T4H4*P2d`G#`U-dj4VF)g(d#akPl5+`{lnq@c}0a*i`;5 z{=>V%r(X^O;AhRSSEZYR*KXHdTTh}5YWc~n_e zsYGAXbvNh$@*nJo#;N&xiWt+WGm238n1xxAd2{pa^4ZXwp=xH|zPO(c%y3Y6 z$5RK-hb`Izt53{ws}j8SM*BO6ow98HJFtX)#@AtZe^S@G#dz+5zI-`Ws(a=QHyn%0 ze{~cDDcrRCLI?2@9kgMI?C?H5N?dlE;pIQK1AVEz=@OkmeBy3|u$P3N$Nr8#_GqH| zXDst4vg-%uCM)_O<#+aSM-Zf3y45Tqozfj|7wib~q_$hRlRTEi9UOZ0Dj*kJT&+_n zhLKC~11*m^$M@A9gcM1(&B8>hxFz+WWp@04RJNiMbNy$AR6K?Jj&4uUs8Su94EQDk za$U6GXM%$4T`6@Z6GWV)Nz=Ed1t=(-5{M3q2y}on&XlAZFZw1ZRgtr50-+f!DR_oB zXV_?6<#=A7iidV`d+Z!JEdn-+7Sq?0kS&59^)9ujH^RlZRNf=T>)ctH;RM1hW&;z|FQn%~xe!CMD))@(oG_yy!eiFUeN zB*#MZJIeVB0Moln${)jXKFl_E`x4}J(??wqEDnhkU9%sdM+rWJ3Fq`b>t=)NaLQ^K zWS+gpg9i`~f|w`S^)H^Vk4PKF8tk&dEx!2}eY4i|Qn?8Bwq~wmK!-#RoWu$8q)0Lk6Hn!IDf&!AcL%quFDo`smFkCLsz_K~JAh}1jH zi3FV4_L3>(deWou+ib6MaivYEv1;np^4bADj_f3iyco()`q?#Nf0^{!NTZ4WO~cB$F`XpW)c4<|>aM=BY3 zstrJNFp^f`ELSviFoX~JvF4_-$jzPU__ACNXk>6o3-7J3=Yk7qbie@$6f~KtDgl}Q zNY-I^gKo|utQoJKDri>mP`?7`0(-GKs3JQg<`|lV<{=or@9QYc>tRS?3s5LBH)y}7 zsolIg%j{IC+!&jUa;Ygab04=*c}^WJ_jOsAJ)){>;#zH;eg$#T6SQOc)*^pvE8ylL zB_B;%3NE9@snUD1WujfT#;o`ddm>*okzOKF<@%;{(hvXl-SUhv*cpz`s0t-AgHZ05 zg5jmG+vnd?=a=IXP<}QRlJuRC^aw)zZsEh$H>O`^+W>`(S%$T3lY9Hc#@h^~V{EGS zjk`|UKTG~;w|6;6tb$Lq`ik_&fX?I=$l4FJZLO+zv;OGVzV8t0-+}&goo`U)ir>w1 zyl*uI#uslvJsWlQFg^67Tx{|e$Htn48Y`*ozJf%ImQW5ux!3Z?|gZW$NVvbdvSTd@lBPxWg;;d4k?tco^uzG{|n+eUO4Y~!RwTjT)+NPopd80P&%2}GU za!k=pbgtbDL0gM<6GJO6{L#h@%Bl9TT0WJxO5e`AQt#PDat1%kq*-aHjRB|4 z&BH-@#f;E#FVWCSc{*y`%+F5y{`fZ$0upICIQLKxon|X<@PxjuK;jDj$Z+Ky&3Six zai>`r9BdBY`eFwgP45ng5?%du#E9Gf0U{V#s*k}e8F$H79sRqEiL)=w0buf3_WQfQ zcLF!l^&6TcC56eG;>aHna#O6V6DsOlknqwUk)_vonpM6O!gdQ!N_drO+)E_kzRn7o zD6d%via3IN0FtQe3}$wwRdalCy$S#HILW}#Riwi6{_gdGKBoc~C|zNeyCgV{4NOrx zAJ}KTrnI4#ZL632(&tv8*OGsg0jpu=BHMUBDe`88sf;|Tk;16dMYM-)GpxH}s)glM zOhtt+@y?=`3;K@s*98BuryfbW$9|#5lml%RmT}r|v)Hj=VZ+n(Y*>>A%% zRB_SUs(oEa?UmaogUShA0GQu<%euGEHZ_8yXPSru;Cg_ow|IjTLn{0i`x7(wT-EK` z`YKm9d4ft`PDzd5rl7FT+k&Gf#kxEif%x}(u>jlC--N$^{BWL7e(-&t`z0OPv*uWU zN`{IyR>)i!$CPT0*2c0Y;c@Jf#S-c>wgrJ|;hDP6>x!dgX4d+!>`uH7Qfgf!8I*EjjX%Z+L<*K*&C1iuo3aCl}o@;>4mzFT6+?4WGx zc(JP~qRL#zT6fS*M@!LtIyLghjKBoKkJ$xs?A=hv5P^e@QsIrU_|=|gK|IZJP>AMN zi4SbI>Mmv{^;Qz~vmirVd*={C=_y23!1OwR z%!TWBQ6TAIspTYTL74G%I(e!h$YVF*y)Hw!5JRd#;zvTUkD|7C17468Ic z(l|+rEiAdksJfPbZxSax-rY?se_>q?AxP!B&KL)0b+WFm-xBK3Yh^z zn}J__z%MId4`jI?VE%f4fYNsq&K5O#=hobyBMJWd@n8==;+*ok9C!7#m6PN=8@HC( ztV}1hd6umM`Rt--ksnvLnDMw~1^w|Lp!dL0!>f-G7%Cg>1`D z9IC3XRWodp!@MS~FZW|laHkaD&SgAff_Ee^VC=3m611^EXD_-PMqgBLO{e~r`rjZ z28q*RUjVc%JqNXaWYU!4SfRMK1n&0hiZOSPOblQcM{#$vkYmSf<8AZ;r`1Dld^u*y zlyf^~&UCjv)+|55lj(Z`eAdsr726SM%&23#Atbo{dy%g{P9ugT^2T|Y@|40X2Rf9! zol9riJq)4SEX<%cBLaab59HDDt(|=F&#rnu>TE_mL*u~b0oSs!xF*EE?)F*t;S)#( z`ca&oRmuGbpXN`z%~`BOC?F7Uo`$s^O0R15ePq-9=4nB?SnqvPgN`eR>HqVG@Z)C;&fXC;HI8 z-dl`NE*>JvzpORUvf2&0UVBF+@N;S&a^fDAhy2qhgLql4Q1iamPDf9a|(wbt`xOI*^Udt(CW)dRvz_6?Q@j_`ircc68f z5!4uo$M+S2M^NA>9AidQWfJDr|VB)#M(=zU7Sj!A|*-=zs=6&f=Y1t;2Zc z1l2d6p=|~7H&{*KqnN@{M@0p*r(^dZm2E}Ou&%vPJQNel1_Eo;XtCx++YkB`avZ`L*pa+&^I!M zb;vmEarid7RIijsWXOzv-r470I$l4<^98vIH1LkO#k5*qw)O9yt#YL!j&?qzt(!VN zdDTUGs-Turd3by?STOSB*4i%#c;)<;4>w?!jX3G)?|_^h(_?xd8l92os+fMnGn@v& z7Cpl}?yaLL5Owh~MRw3_hA)T!o+`>|&6NH>UA}(AC_81QWzJ&?@k-|T6E!O&Ui#c{ zSWn&$v>ScvfcuL6Q&t~~^C!LDN{0Dd%}PBYdMJB`Mk5w?X2~*6ber1JoL@Dv`^-q%=(3(ki9Exim%TC@!9I zh|@49Nv17(6`**V#{p`Hi_8;BaH&j^reN8}$<;0$-l3D8 zr0zjL`(|5@ROg^Xz@9YgH&=fOsaNzHyfd=D7al-C71KRTgzVRNSTayDVC1jVyxi3P z8Vc5P%zEtje2bVt*4aB;{E8-a>9NGG=d$F)01?E~3|M#k>Ewn>J3! z5fqLg3%9Cyz8NI84OEODW}Lrt=n3f7UddXU&r~#n!WRJ5Wer$6=V;dEnboQ04efr;GE~2y1k8{?;xW?)r7mM0` zVBDbN1wi8xuG0T?`Cm#dahD}_Ntd#EN?S?s(3rA`CYL04AZcL63A zkXGj(mZgQ-651y&uf>aQ9gqf!O5?&jHrrlr8-_e=8<^my3pBlvvnQisdZT+!=2CqJ WI03s3rch~_QA9y4W`4$t+5ZDbs+>Fk literal 5587 zcmbVQXIN89x84vS)I)t#2+4HQK%)9n__nS=C8uBml3~)wQOGgW!qM`yG zP%eNx4rl_jAQ~DP5G|#nrKP0mnBq zA0G?`W8)MM65to&<%jW~I-#OK!E|5!x}&peaOlU+;gMgXW3zME`GrN?((=mo&h8$5|KRZG_>>nF z0Q#HOznT3HUaS;e)D&50z^A;Zr~@cBh?R!+qBPxE4Fj-^7n^`g1U>tWq_-cN83bhw zw>WIQ2N=%@$<1EcKBe}X+5e7M&&fbyT=JCokB<>n#->REhG7mq z;dws>-Mg?lHTHL(Sdals0qGA8e^x&-poTm;V`#k+d^yR01A`h_WCq|Gi0W;+q^P>D zf)#y=<=P4Fsy((GoV!aJo^ReP3>%?ftdH1DIvPeP7Mx-^5;XHRfOFRyHv5!{jM|jd zX$bSs{aBcJ;yMo>8K8r&O=v$nc(;ahB?BlZAx_-)#DfST13OoX1>3&e5-lvf?R$il z%WEIIW%@^L8R+QNga6~B$AjN#MK?&)f;nk1 z4BnMOI!BjMDXS5#FWZ=)Oa_##$9z>ti?Go&Lc^Xl@(UU0zfJ}OFKdd7BbQ)gASv1~ z1^w-T4D?fxD1%^+Ap7*8{^xgvdRct);WV+yP}TZ2*qAZJ1AOfp$vQ}`@gK^ijV=`O zFhZQzVBm=Eb@YBnBq~IPAL6yVdWQ_OSik?$zYk#wz8;Jn8ctkSRzrFsAO75E*r}U# zrr@LqPsLNGop3}b84zIX_uz1h=c-PPa>5Y0_LwKS1mEypf;vjx1Q0YX0q$ZK;cE5G7=)omME;=>GCd?0$taT~gTw4)*d5tkivhW% zg@}6-Y~$k6_rfGYtInR!KK2?w7cihh2rTNdr_OYN9aL z=^8Q2!s{0=DJ@3+{0G-4%!#DV!D8z4qi8kC67VG2nX9rO~!1?ZYQ`9KZhFT3K4=^)+CQIe?*9MDY8#P z^mmbt5;_-!&KmKGb(Fek-%0jd!6YSqgLrMOn>R2U+BcpzrKOq(PTHCKwpEpq?J*hV zAbd&6LBb!Ibg9QrCk7&6wV*ug)-to$&~Z4IZN0s#KmZ>Z{PB;WepbJ>#%GV@_sKPz zu{#Yp0NWzj)?7Bt2P~`gZ@M};ky4YOg%XgT`AXoe%vC!7B&+*QShkfsuALQ=rnz7%F zSZ_0t%E9G871`HFGI*c^o0vw7Bq9-hsy*Of_z%(RF$wW-Mb>2EVX+q@%y<n+yZFNNYyylf`1he`~rCbHAtMdo!I4*v@0~m70Zhet~^OV%l z9_f~IDz)p$&v&7&&L7KQ$v}mXZTbD_+RA}=hr*3e=eo5#p-7NdL?oQY#%I^|&#QP< zv8roSOyP9eEP5BO*-x<6HW>AzG%Vo}9YLF(^q0J~U1x)BYj3~eLT1CP@IMaeL4N*p z{_96R5$&|WU;Q>)e}*}{d3tzhiL#+SqNr>#=vUUAUs{sCYHjuS*$Wt_Ec5`G#-3}t zfjornybHhF1Mi>G7i@8b z=kXFeaA3+ORuO%WC7jcfclm1tA|prdE{&8#v}$NHQ0vO|=9Fh6*U*a`Su zA?isQims*{+P-a>^$b!CT)n}T9cjutW6;HUN%C4aWKiE5jQJZ$Daq9Rv~?sV@SN0 zK!3+GI2&osAfu+GIPYU%e^#cJA9rj}SJ-(JJP)Bg$CR8Bb?`AfkWY*zFR5{S*YBWPU1f%M910n(gw+>~2C&@wYeHt>OGMIiZm*v;lx}sr<<=FB zPe*ZY=C(@MzV$s))ji5YFIz&R^ZK7OORm{RPq=laq~Pbii<-oxT0cTfc>l~7;l8q5 zQTK2Vf2#I<1btRXG)w>Ukl*PC|Uq`@wnWZZDoNE<@ z63~SgcpP8O0O+BJPBNe}0uvkP`Lj1W#uak7iHPpxW_(}aUSIsRNoYrQ!*RNAN)zrFjoRd4}4nAHt= z-ZcN({t1p&TR&Ej;KbcvkQvm6c#Om^EpSQ;QggYrD>b+KEXNz3<2IDJscsh1;{v1YP$g2D!F(sklUud& zdI-MQv{NWcIAZ@cOrmxNTvH)mCYKZ6=T>HbT(`-tY@hS){mP4sma`0(UW_3Ff!I~E z!4*lf&<`!vSN)`6FGx$YihxDB3tFV{mBP;QVWrDtzgtz*AjlN$3Om$}U)X`qt zyRxtNz`P?le1^ZSsFWlnJV73p6m3162&i#kDc0X8@VYw#DDP-pH!*7IRDJqN%zLHp zc(wkaWkA{eyx}VMMDydHA9p-_&LR$vQKt#S`V6g3^{R4qGS$D9m*Q_M` zr^9fBFz4aX8r{&|L19lH@5@8rK-DGfMmyoJw+#<+?J74B6HR!6?48nDj7p-u7`8v`Phb6lQerjmhbOqfpj^z19Ok-!_`gaVZvrDe;{oQ*JJuMy<;^t|g5A6D;~ zYL_`$Dp0+Uq5fZR^&UyW2E$be@s&;Q|ogF0{o53Sfjop z$p7aNM~UNnHsq^(w3~=Pwd;A`aEC&8*E=NBmGt&{gwS@#qE;tP=wHvLzHjv+3H zwq!oE&my{OS2%NNLtC6MGYmpk^F;aFqZi3gz@@X34y zr%2B!LR{-;$IzJI%}3)yUKhM#Ru64cEvP-40R14ks^0m)e!4ZfeOh0k7~*q{{WAg^VPl` z0M?3zC1P*%CUewZBTTv|tczY{0Q2Vwr&?*97fQo#AmlUg)^4i5et_zcU zE_?1M0+*Oo<`7!UHi=}_O*be!nKnYhu-a9jQDZ!MSDTtu716v#OmAwB&<)~=;{qZ@ zXrrXt`=9%ClvX3!q#m6>#{@ zT&h5RKTf^0imc~fVg z6s-Iehe+lAFJWmYBxR6D8C1qP<+7AGDf^fo`{wFGkkO4v<^EIpY56YFhV=3>rg|LU zZXt~5wng6{FI6PMc`8&FAJ4cSs318XC`n&s470r|tt6jgq%G7es)0qX1VQB=+G$jTr(N=*AI=uvx?}L}lnBK1}w$)!JRr6SEng;ncQZCaJWp zoglh~7~~TxHm>nYOK8LtbgK|GD-|7s40(*G$bh$`SEkz#ZTT>$FP5_mn!9`LN|cIz zZ!jIHW_!{Y!dEi6aEO@wc;H?!vV1g-8SZsq;!p=X!^8A%Jx?%8&7 z6O-$3p)BK>Y+w2HvjqA6k&Q_!X5XWg(nhFnDh<>3cTkI@+Y}h-BLtWHZsHuHO(>@0 zy#6i9mV%8Q78$Kg=~~D*D9e1z@|%l_kMJ_d{CXi?#FAvSd@$#qgjS` zwfB0@o1oI8^FGEkXYJ>5E7W(N+EUIuGXLIR1pZ?Jx5>ca9_7#C2cDJ;5P~U5#Y!-c zEHdUytgDxh$&b-1pbJ;X&6=ucR;YiVp-^uXBe*7mJX7y62cplX=us zkqQY+6dU!9yaw(ql%6~b9>gfr8$Xit zb_7I(ljB6Gxf-#Hzmwso{}|zC$pA@JXeK8%u?*%?Lk4nCZ;O_4>D5L($bc$*dq{iy zG8w=Xkd&Q+MX43>OnxeqL?+>N4Wz&ozEDA#X7rW@#{vD%O{yv2>BD6*z*x;+;Rs)- zAt@tUKE3nDmq>pP?x%}%IfvXGCj)%&StH?34=LU$$4)~e4qXKg^Gv>aN9wuNyVkqY zzZdB9{7fetP!6CFhbAWe_<~pubKoPf`rjb-p;F3W5e7-{rIa-{>+7BJ$A`m(+#X>_ zlsX4nKoA!hz6{6x4j`j%s+#S)T^Rr^F-KxNej`lN|68#4`0AZMQ33>A`mvr!ppZc8 z8K)c6s0tBTbR=rZG}X^!bucU<`Umz4;kbe&XJ=7rShdFK%hA*x%Oz3juG6XdPv9H} Si?r*WUh6l_-i5=-qyGhMQ{4>! diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp new file mode 100644 index 0000000..ad8497c --- /dev/null +++ b/src/qt/rpcconsole.cpp @@ -0,0 +1,437 @@ +#include "rpcconsole.h" +#include "ui_rpcconsole.h" + +#include "clientmodel.h" +#include "bitcoinrpc.h" +#include "guiutil.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +// TODO: make it possible to filter out categories (esp debug messages when implemented) +// TODO: receive errors and debug messages through ClientModel + +const int CONSOLE_SCROLLBACK = 50; +const int CONSOLE_HISTORY = 50; + +const QSize ICON_SIZE(24, 24); + +const struct { + const char *url; + const char *source; +} ICON_MAPPING[] = { + {"cmd-request", ":/icons/tx_input"}, + {"cmd-reply", ":/icons/tx_output"}, + {"cmd-error", ":/icons/tx_output"}, + {"misc", ":/icons/tx_inout"}, + {NULL, NULL} +}; + +/* Object for executing console RPC commands in a separate thread. +*/ +class RPCExecutor: public QObject +{ + Q_OBJECT +public slots: + void start(); + void request(const QString &command); +signals: + void reply(int category, const QString &command); +}; + +#include "rpcconsole.moc" + +void RPCExecutor::start() +{ + // Nothing to do +} + +/** + * Split shell command line into a list of arguments. Aims to emulate \c bash and friends. + * + * - Arguments are delimited with whitespace + * - Extra whitespace at the beginning and end and between arguments will be ignored + * - Text can be "double" or 'single' quoted + * - The backslash \c \ is used as escape character + * - Outside quotes, any character can be escaped + * - Within double quotes, only escape \c " and backslashes before a \c " or another backslash + * - Within single quotes, no escaping is possible and no special interpretation takes place + * + * @param[out] args Parsed arguments will be appended to this list + * @param[in] strCommand Command line to split + */ +bool parseCommandLine(std::vector &args, const std::string &strCommand) +{ + enum CmdParseState + { + STATE_EATING_SPACES, + STATE_ARGUMENT, + STATE_SINGLEQUOTED, + STATE_DOUBLEQUOTED, + STATE_ESCAPE_OUTER, + STATE_ESCAPE_DOUBLEQUOTED + } state = STATE_EATING_SPACES; + std::string curarg; + foreach(char ch, strCommand) + { + switch(state) + { + case STATE_ARGUMENT: // In or after argument + case STATE_EATING_SPACES: // Handle runs of whitespace + switch(ch) + { + case '"': state = STATE_DOUBLEQUOTED; break; + case '\'': state = STATE_SINGLEQUOTED; break; + case '\\': state = STATE_ESCAPE_OUTER; break; + case ' ': case '\n': case '\t': + if(state == STATE_ARGUMENT) // Space ends argument + { + args.push_back(curarg); + curarg.clear(); + } + state = STATE_EATING_SPACES; + break; + default: curarg += ch; state = STATE_ARGUMENT; + } + break; + case STATE_SINGLEQUOTED: // Single-quoted string + switch(ch) + { + case '\'': state = STATE_ARGUMENT; break; + default: curarg += ch; + } + break; + case STATE_DOUBLEQUOTED: // Double-quoted string + switch(ch) + { + case '"': state = STATE_ARGUMENT; break; + case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break; + default: curarg += ch; + } + break; + case STATE_ESCAPE_OUTER: // '\' outside quotes + curarg += ch; state = STATE_ARGUMENT; + break; + case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text + if(ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself + curarg += ch; state = STATE_DOUBLEQUOTED; + break; + } + } + switch(state) // final state + { + case STATE_EATING_SPACES: + return true; + case STATE_ARGUMENT: + args.push_back(curarg); + return true; + default: // ERROR to end in one of the other states + return false; + } +} + +void RPCExecutor::request(const QString &command) +{ + std::vector args; + if(!parseCommandLine(args, command.toStdString())) + { + emit reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); + return; + } + if(args.empty()) + return; // Nothing to do + try + { + std::string strPrint; + // Convert argument list to JSON objects in method-dependent way, + // and pass it along with the method name to the dispatcher. + json_spirit::Value result = tableRPC.execute( + args[0], + RPCConvertValues(args[0], std::vector(args.begin() + 1, args.end()))); + + // Format result reply + if (result.type() == json_spirit::null_type) + strPrint = ""; + else if (result.type() == json_spirit::str_type) + strPrint = result.get_str(); + else + strPrint = write_string(result, true); + + emit reply(RPCConsole::CMD_REPLY, QString::fromStdString(strPrint)); + } + catch (json_spirit::Object& objError) + { + try // Nice formatting for standard-format error + { + int code = find_value(objError, "code").get_int(); + std::string message = find_value(objError, "message").get_str(); + emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")"); + } + catch(std::runtime_error &) // raised when converting to invalid type, i.e. missing code or message + { // Show raw JSON object + emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(write_string(json_spirit::Value(objError), false))); + } + } + catch (std::exception& e) + { + emit reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what())); + } +} + +RPCConsole::RPCConsole(QWidget *parent) : + QDialog(parent), + ui(new Ui::RPCConsole), + historyPtr(0) +{ + ui->setupUi(this); + +#ifndef Q_OS_MAC + ui->openDebugLogfileButton->setIcon(QIcon(":/icons/export")); + ui->showCLOptionsButton->setIcon(QIcon(":/icons/options")); +#endif + + // Install event filter for up and down arrow + ui->lineEdit->installEventFilter(this); + ui->messagesWidget->installEventFilter(this); + + connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); + + // set OpenSSL version label + ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION)); + + startExecutor(); + + clear(); +} + +RPCConsole::~RPCConsole() +{ + emit stopExecutor(); + delete ui; +} + +bool RPCConsole::eventFilter(QObject* obj, QEvent *event) +{ + if(event->type() == QEvent::KeyPress) // Special key handling + { + QKeyEvent *keyevt = static_cast(event); + int key = keyevt->key(); + Qt::KeyboardModifiers mod = keyevt->modifiers(); + switch(key) + { + case Qt::Key_Up: if(obj == ui->lineEdit) { browseHistory(-1); return true; } break; + case Qt::Key_Down: if(obj == ui->lineEdit) { browseHistory(1); return true; } break; + case Qt::Key_PageUp: /* pass paging keys to messages widget */ + case Qt::Key_PageDown: + if(obj == ui->lineEdit) + { + QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt)); + return true; + } + break; + default: + // Typing in messages widget brings focus to line edit, and redirects key there + // Exclude most combinations and keys that emit no text, except paste shortcuts + if(obj == ui->messagesWidget && ( + (!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) || + ((mod & Qt::ControlModifier) && key == Qt::Key_V) || + ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert))) + { + ui->lineEdit->setFocus(); + QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); + return true; + } + } + } + return QDialog::eventFilter(obj, event); +} + +void RPCConsole::setClientModel(ClientModel *model) +{ + this->clientModel = model; + if(model) + { + // Subscribe to information, replies, messages, errors + connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); + connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); + + // Provide initial values + ui->clientVersion->setText(model->formatFullVersion()); + ui->clientName->setText(model->clientName()); + ui->buildDate->setText(model->formatBuildDate()); + ui->startupTime->setText(model->formatClientStartupTime()); + + setNumConnections(model->getNumConnections()); + ui->isTestNet->setChecked(model->isTestNet()); + + setNumBlocks(model->getNumBlocks(), model->getNumBlocksOfPeers()); + } +} + +static QString categoryClass(int category) +{ + switch(category) + { + case RPCConsole::CMD_REQUEST: return "cmd-request"; break; + case RPCConsole::CMD_REPLY: return "cmd-reply"; break; + case RPCConsole::CMD_ERROR: return "cmd-error"; break; + default: return "misc"; + } +} + +void RPCConsole::clear() +{ + ui->messagesWidget->clear(); + ui->lineEdit->clear(); + ui->lineEdit->setFocus(); + + // Add smoothly scaled icon images. + // (when using width/height on an img, Qt uses nearest instead of linear interpolation) + for(int i=0; ICON_MAPPING[i].url; ++i) + { + ui->messagesWidget->document()->addResource( + QTextDocument::ImageResource, + QUrl(ICON_MAPPING[i].url), + QImage(ICON_MAPPING[i].source).scaled(ICON_SIZE, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + + // Set default style sheet + ui->messagesWidget->document()->setDefaultStyleSheet( + "table { }" + "td.time { color: #808080; padding-top: 3px; } " + "td.message { font-family: Monospace; font-size: 12px; } " + "td.cmd-request { color: #006060; } " + "td.cmd-error { color: red; } " + "b { color: #006060; } " + ); + + message(CMD_REPLY, (tr("Welcome to the NovaCoin RPC console.") + "
" + + tr("Use up and down arrows to navigate history, and Ctrl-L to clear screen.") + "
" + + tr("Type help for an overview of available commands.")), true); +} + +void RPCConsole::message(int category, const QString &message, bool html) +{ + QTime time = QTime::currentTime(); + QString timeString = time.toString(); + QString out; + out += ""; + out += ""; + out += "
" + timeString + ""; + if(html) + out += message; + else + out += GUIUtil::HtmlEscape(message, true); + out += "
"; + ui->messagesWidget->append(out); +} + +void RPCConsole::setNumConnections(int count) +{ + ui->numberOfConnections->setText(QString::number(count)); +} + +void RPCConsole::setNumBlocks(int count, int countOfPeers) +{ + ui->numberOfBlocks->setText(QString::number(count)); + ui->totalBlocks->setText(QString::number(countOfPeers)); + if(clientModel) + { + // If there is no current number available display N/A instead of 0, which can't ever be true + ui->totalBlocks->setText(clientModel->getNumBlocksOfPeers() == 0 ? tr("N/A") : QString::number(clientModel->getNumBlocksOfPeers())); + ui->lastBlockTime->setText(clientModel->getLastBlockDate().toString()); + } +} + +void RPCConsole::on_lineEdit_returnPressed() +{ + QString cmd = ui->lineEdit->text(); + ui->lineEdit->clear(); + + if(!cmd.isEmpty()) + { + message(CMD_REQUEST, cmd); + emit cmdRequest(cmd); + // Truncate history from current position + history.erase(history.begin() + historyPtr, history.end()); + // Append command to history + history.append(cmd); + // Enforce maximum history size + while(history.size() > CONSOLE_HISTORY) + history.removeFirst(); + // Set pointer to end of history + historyPtr = history.size(); + // Scroll console view to end + scrollToEnd(); + } +} + +void RPCConsole::browseHistory(int offset) +{ + historyPtr += offset; + if(historyPtr < 0) + historyPtr = 0; + if(historyPtr > history.size()) + historyPtr = history.size(); + QString cmd; + if(historyPtr < history.size()) + cmd = history.at(historyPtr); + ui->lineEdit->setText(cmd); +} + +void RPCConsole::startExecutor() +{ + QThread* thread = new QThread; + RPCExecutor *executor = new RPCExecutor(); + executor->moveToThread(thread); + + // Notify executor when thread started (in executor thread) + connect(thread, SIGNAL(started()), executor, SLOT(start())); + // Replies from executor object must go to this object + connect(executor, SIGNAL(reply(int,QString)), this, SLOT(message(int,QString))); + // Requests from this object must go to executor + connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString))); + // On stopExecutor signal + // - queue executor for deletion (in execution thread) + // - quit the Qt event loop in the execution thread + connect(this, SIGNAL(stopExecutor()), executor, SLOT(deleteLater())); + connect(this, SIGNAL(stopExecutor()), thread, SLOT(quit())); + // Queue the thread for deletion (in this thread) when it is finished + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + // Default implementation of QThread::run() simply spins up an event loop in the thread, + // which is what we want. + thread->start(); +} + +void RPCConsole::on_tabWidget_currentChanged(int index) +{ + if(ui->tabWidget->widget(index) == ui->tab_console) + { + ui->lineEdit->setFocus(); + } +} + +void RPCConsole::on_openDebugLogfileButton_clicked() +{ + GUIUtil::openDebugLogfile(); +} + +void RPCConsole::scrollToEnd() +{ + QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar(); + scrollbar->setValue(scrollbar->maximum()); +} + +void RPCConsole::on_showCLOptionsButton_clicked() +{ + GUIUtil::HelpMessageBox help; + help.exec(); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h new file mode 100644 index 0000000..3c38b4b --- /dev/null +++ b/src/qt/rpcconsole.h @@ -0,0 +1,66 @@ +#ifndef RPCCONSOLE_H +#define RPCCONSOLE_H + +#include + +namespace Ui { + class RPCConsole; +} +class ClientModel; + +/** Local Bitcoin RPC console. */ +class RPCConsole: public QDialog +{ + Q_OBJECT + +public: + explicit RPCConsole(QWidget *parent = 0); + ~RPCConsole(); + + void setClientModel(ClientModel *model); + + enum MessageClass { + MC_ERROR, + MC_DEBUG, + CMD_REQUEST, + CMD_REPLY, + CMD_ERROR + }; + +protected: + virtual bool eventFilter(QObject* obj, QEvent *event); + +private slots: + void on_lineEdit_returnPressed(); + void on_tabWidget_currentChanged(int index); + /** open the debug.log from the current datadir */ + void on_openDebugLogfileButton_clicked(); + /** display messagebox with program parameters (same as bitcoin-qt --help) */ + void on_showCLOptionsButton_clicked(); + +public slots: + void clear(); + void message(int category, const QString &message, bool html = false); + /** Set number of connections shown in the UI */ + void setNumConnections(int count); + /** Set number of blocks shown in the UI */ + void setNumBlocks(int count, int countOfPeers); + /** Go forward or back in history */ + void browseHistory(int offset); + /** Scroll console view to end */ + void scrollToEnd(); +signals: + // For RPC command executor + void stopExecutor(); + void cmdRequest(const QString &command); + +private: + Ui::RPCConsole *ui; + ClientModel *clientModel; + QStringList history; + int historyPtr; + + void startExecutor(); +}; + +#endif // RPCCONSOLE_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 25b125e..4c728a5 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -7,6 +7,7 @@ #include "sendcoinsentry.h" #include "guiutil.h" #include "askpassphrasedialog.h" +#include "base58.h" #include #include @@ -20,7 +21,7 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : { ui->setupUi(this); -#ifdef Q_WS_MAC // Icons on push buttons are very uncommon on Mac +#ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac ui->addButton->setIcon(QIcon()); ui->clearButton->setIcon(QIcon()); ui->sendButton->setIcon(QIcon()); @@ -46,10 +47,11 @@ void SendCoinsDialog::setModel(WalletModel *model) entry->setModel(model); } } - if(model) + if(model && model->getOptionsModel()) { - setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance()); - connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64))); + setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance()); + connect(model, SIGNAL(balanceChanged(qint64, qint64, quint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64))); + connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); } } @@ -120,7 +122,7 @@ void SendCoinsDialog::on_sendButton_clicked() { case WalletModel::InvalidAddress: QMessageBox::warning(this, tr("Send Coins"), - tr("The recepient address is not valid, please recheck."), + tr("The recipient address is not valid, please recheck."), QMessageBox::Ok, QMessageBox::Ok); break; case WalletModel::InvalidAmount: @@ -130,28 +132,28 @@ void SendCoinsDialog::on_sendButton_clicked() break; case WalletModel::AmountExceedsBalance: QMessageBox::warning(this, tr("Send Coins"), - tr("Amount exceeds your balance"), + tr("The amount exceeds your balance."), QMessageBox::Ok, QMessageBox::Ok); break; case WalletModel::AmountWithFeeExceedsBalance: QMessageBox::warning(this, tr("Send Coins"), - tr("Total exceeds your balance when the %1 transaction fee is included"). + tr("The total exceeds your balance when the %1 transaction fee is included."). arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendstatus.fee)), QMessageBox::Ok, QMessageBox::Ok); break; case WalletModel::DuplicateAddress: QMessageBox::warning(this, tr("Send Coins"), - tr("Duplicate address found, can only send to each address once in one send operation"), + tr("Duplicate address found, can only send to each address once per send operation."), QMessageBox::Ok, QMessageBox::Ok); break; case WalletModel::TransactionCreationFailed: QMessageBox::warning(this, tr("Send Coins"), - tr("Error: Transaction creation failed "), + tr("Error: Transaction creation failed."), QMessageBox::Ok, QMessageBox::Ok); break; case WalletModel::TransactionCommitFailed: QMessageBox::warning(this, tr("Send Coins"), - tr("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."), + tr("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."), QMessageBox::Ok, QMessageBox::Ok); break; case WalletModel::Aborted: // User aborted, nothing to do @@ -202,7 +204,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry() ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint()); QCoreApplication::instance()->processEvents(); QScrollBar* bar = ui->scrollArea->verticalScrollBar(); - if (bar) + if(bar) bar->setSliderPosition(bar->maximum()); return entry; } @@ -245,7 +247,7 @@ QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) { - if (!fNewRecipientAllowed) + if(!fNewRecipientAllowed) return; SendCoinsEntry *entry = 0; @@ -266,24 +268,39 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) entry->setValue(rv); } - -void SendCoinsDialog::handleURI(const QString &uri) +bool SendCoinsDialog::handleURI(const QString &uri) { SendCoinsRecipient rv; - if(!GUIUtil::parseBitcoinURI(uri, &rv)) + // URI has to be valid + if (GUIUtil::parseBitcoinURI(uri, &rv)) { - return; + CBitcoinAddress address(rv.address.toStdString()); + if (!address.IsValid()) + return false; + pasteEntry(rv); + return true; } - pasteEntry(rv); + + return false; } -void SendCoinsDialog::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance) +void SendCoinsDialog::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance) { Q_UNUSED(stake); Q_UNUSED(unconfirmedBalance); + Q_UNUSED(immatureBalance); if(!model || !model->getOptionsModel()) return; int unit = model->getOptionsModel()->getDisplayUnit(); ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance)); } + +void SendCoinsDialog::updateDisplayUnit() +{ + if(model && model->getOptionsModel()) + { + // Update labelBalance with the current balance and the current unit + ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->getBalance())); + } +} diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index a37bcdb..32bb61a 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -25,12 +25,12 @@ public: void setModel(WalletModel *model); - /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue http://bugreports.qt.nokia.com/browse/QTBUG-10907). + /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project.org/browse/QTBUG-10907). */ QWidget *setupTabChain(QWidget *prev); void pasteEntry(const SendCoinsRecipient &rv); - void handleURI(const QString &uri); + bool handleURI(const QString &uri); public slots: void clear(); @@ -38,7 +38,7 @@ public slots: void accept(); SendCoinsEntry *addEntry(); void updateRemoveEnabled(); - void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance); + void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance); private: Ui::SendCoinsDialog *ui; @@ -47,8 +47,8 @@ private: private slots: void on_sendButton_clicked(); - void removeEntry(SendCoinsEntry* entry); + void updateDisplayUnit(); }; #endif // SENDCOINSDIALOG_H diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 8563179..cd18619 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -17,13 +17,13 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) : { ui->setupUi(this); -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC ui->payToLayout->setSpacing(4); #endif - #if QT_VERSION >= 0x040700 - ui->payTo->setPlaceholderText(tr("Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5)")); + /* Do not move this to the XML file, Qt before 4.7 will choke on it */ ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book")); + ui->payTo->setPlaceholderText(tr("Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5)")); #endif setFocusPolicy(Qt::TabFocus); setFocusProxy(ui->payTo); @@ -68,6 +68,10 @@ void SendCoinsEntry::on_payTo_textChanged(const QString &address) void SendCoinsEntry::setModel(WalletModel *model) { this->model = model; + + if(model && model->getOptionsModel()) + connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + clear(); } @@ -82,10 +86,8 @@ void SendCoinsEntry::clear() ui->addAsLabel->clear(); ui->payAmount->clear(); ui->payTo->setFocus(); - if(model && model->getOptionsModel()) - { - ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - } + // update the display unit, to not use the default ("BTC") + updateDisplayUnit(); } void SendCoinsEntry::on_deleteButton_clicked() @@ -160,3 +162,11 @@ void SendCoinsEntry::setFocus() ui->payTo->setFocus(); } +void SendCoinsEntry::updateDisplayUnit() +{ + if(model && model->getOptionsModel()) + { + // Update payAmount with the current unit + ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); + } +} diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index cdbf893..0ac14c1 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -27,7 +27,7 @@ public: void setValue(const SendCoinsRecipient &value); - /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue http://bugreports.qt.nokia.com/browse/QTBUG-10907). + /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project.org/browse/QTBUG-10907). */ QWidget *setupTabChain(QWidget *prev); @@ -45,6 +45,7 @@ private slots: void on_payTo_textChanged(const QString &address); void on_addressBookButton_clicked(); void on_pasteButton_clicked(); + void updateDisplayUnit(); private: Ui::SendCoinsEntry *ui; diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp new file mode 100644 index 0000000..18154eb --- /dev/null +++ b/src/qt/signverifymessagedialog.cpp @@ -0,0 +1,274 @@ +#include "signverifymessagedialog.h" +#include "ui_signverifymessagedialog.h" + +#include "addressbookpage.h" +#include "base58.h" +#include "guiutil.h" +#include "init.h" +#include "main.h" +#include "optionsmodel.h" +#include "walletmodel.h" +#include "wallet.h" + +#include +#include + +#include + +SignVerifyMessageDialog::SignVerifyMessageDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SignVerifyMessageDialog), + model(0) +{ + ui->setupUi(this); + +#if (QT_VERSION >= 0x040700) + /* Do not move this to the XML file, Qt before 4.7 will choke on it */ + ui->addressIn_SM->setPlaceholderText(tr("Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5)")); + ui->signatureOut_SM->setPlaceholderText(tr("Click \"Sign Message\" to generate signature")); + + ui->addressIn_VM->setPlaceholderText(tr("Enter a NovaCoin address (e.g. 4Zo1ga6xuKuQ7JV7M9rGDoxdbYwV5zgQJ5)")); + ui->signatureIn_VM->setPlaceholderText(tr("Enter NovaCoin signature")); +#endif + + GUIUtil::setupAddressWidget(ui->addressIn_SM, this); + GUIUtil::setupAddressWidget(ui->addressIn_VM, this); + + ui->addressIn_SM->installEventFilter(this); + ui->messageIn_SM->installEventFilter(this); + ui->signatureOut_SM->installEventFilter(this); + ui->addressIn_VM->installEventFilter(this); + ui->messageIn_VM->installEventFilter(this); + ui->signatureIn_VM->installEventFilter(this); + + ui->signatureOut_SM->setFont(GUIUtil::bitcoinAddressFont()); + ui->signatureIn_VM->setFont(GUIUtil::bitcoinAddressFont()); +} + +SignVerifyMessageDialog::~SignVerifyMessageDialog() +{ + delete ui; +} + +void SignVerifyMessageDialog::setModel(WalletModel *model) +{ + this->model = model; +} + +void SignVerifyMessageDialog::setAddress_SM(QString address) +{ + ui->addressIn_SM->setText(address); + ui->messageIn_SM->setFocus(); +} + +void SignVerifyMessageDialog::setAddress_VM(QString address) +{ + ui->addressIn_VM->setText(address); + ui->messageIn_VM->setFocus(); +} + +void SignVerifyMessageDialog::showTab_SM(bool fShow) +{ + ui->tabWidget->setCurrentIndex(0); + + if (fShow) + this->show(); +} + +void SignVerifyMessageDialog::showTab_VM(bool fShow) +{ + ui->tabWidget->setCurrentIndex(1); + if (fShow) + this->show(); +} + +void SignVerifyMessageDialog::on_addressBookButton_SM_clicked() +{ + if (model && model->getAddressTableModel()) + { + AddressBookPage dlg(AddressBookPage::ForSending, AddressBookPage::ReceivingTab, this); + dlg.setModel(model->getAddressTableModel()); + if (dlg.exec()) + { + setAddress_SM(dlg.getReturnValue()); + } + } +} + +void SignVerifyMessageDialog::on_pasteButton_SM_clicked() +{ + setAddress_SM(QApplication::clipboard()->text()); +} + +void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() +{ + /* Clear old signature to ensure users don't get confused on error with an old signature displayed */ + ui->signatureOut_SM->clear(); + + CBitcoinAddress addr(ui->addressIn_SM->text().toStdString()); + if (!addr.IsValid()) + { + ui->addressIn_SM->setValid(false); + ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_SM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); + return; + } + CKeyID keyID; + if (!addr.GetKeyID(keyID)) + { + ui->addressIn_SM->setValid(false); + ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_SM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); + return; + } + + WalletModel::UnlockContext ctx(model->requestUnlock()); + if (!ctx.isValid()) + { + ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_SM->setText(tr("Wallet unlock was cancelled.")); + return; + } + + CKey key; + if (!pwalletMain->GetKey(keyID, key)) + { + ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_SM->setText(tr("Private key for the entered address is not available.")); + return; + } + + CDataStream ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << ui->messageIn_SM->document()->toPlainText().toStdString(); + + std::vector vchSig; + if (!key.SignCompact(Hash(ss.begin(), ss.end()), vchSig)) + { + ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_SM->setText(QString("") + tr("Message signing failed.") + QString("")); + return; + } + + ui->statusLabel_SM->setStyleSheet("QLabel { color: green; }"); + ui->statusLabel_SM->setText(QString("") + tr("Message signed.") + QString("")); + + ui->signatureOut_SM->setText(QString::fromStdString(EncodeBase64(&vchSig[0], vchSig.size()))); +} + +void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked() +{ + QApplication::clipboard()->setText(ui->signatureOut_SM->text()); +} + +void SignVerifyMessageDialog::on_clearButton_SM_clicked() +{ + ui->addressIn_SM->clear(); + ui->messageIn_SM->clear(); + ui->signatureOut_SM->clear(); + ui->statusLabel_SM->clear(); + + ui->addressIn_SM->setFocus(); +} + +void SignVerifyMessageDialog::on_addressBookButton_VM_clicked() +{ + if (model && model->getAddressTableModel()) + { + AddressBookPage dlg(AddressBookPage::ForSending, AddressBookPage::SendingTab, this); + dlg.setModel(model->getAddressTableModel()); + if (dlg.exec()) + { + setAddress_VM(dlg.getReturnValue()); + } + } +} + +void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked() +{ + CBitcoinAddress addr(ui->addressIn_VM->text().toStdString()); + if (!addr.IsValid()) + { + ui->addressIn_VM->setValid(false); + ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_VM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); + return; + } + CKeyID keyID; + if (!addr.GetKeyID(keyID)) + { + ui->addressIn_VM->setValid(false); + ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_VM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); + return; + } + + bool fInvalid = false; + std::vector vchSig = DecodeBase64(ui->signatureIn_VM->text().toStdString().c_str(), &fInvalid); + + if (fInvalid) + { + ui->signatureIn_VM->setValid(false); + ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_VM->setText(tr("The signature could not be decoded.") + QString(" ") + tr("Please check the signature and try again.")); + return; + } + + CDataStream ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << ui->messageIn_VM->document()->toPlainText().toStdString(); + + CKey key; + if (!key.SetCompactSignature(Hash(ss.begin(), ss.end()), vchSig)) + { + ui->signatureIn_VM->setValid(false); + ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_VM->setText(tr("The signature did not match the message digest.") + QString(" ") + tr("Please check the signature and try again.")); + return; + } + + if (!(CBitcoinAddress(key.GetPubKey().GetID()) == addr)) + { + ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_VM->setText(QString("") + tr("Message verification failed.") + QString("")); + return; + } + + ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }"); + ui->statusLabel_VM->setText(QString("") + tr("Message verified.") + QString("")); +} + +void SignVerifyMessageDialog::on_clearButton_VM_clicked() +{ + ui->addressIn_VM->clear(); + ui->signatureIn_VM->clear(); + ui->messageIn_VM->clear(); + ui->statusLabel_VM->clear(); + + ui->addressIn_VM->setFocus(); +} + +bool SignVerifyMessageDialog::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::FocusIn) + { + if (ui->tabWidget->currentIndex() == 0) + { + /* Clear status message on focus change */ + ui->statusLabel_SM->clear(); + + /* Select generated signature */ + if (object == ui->signatureOut_SM) + { + ui->signatureOut_SM->selectAll(); + return true; + } + } + else if (ui->tabWidget->currentIndex() == 1) + { + /* Clear status message on focus change */ + ui->statusLabel_VM->clear(); + } + } + return QDialog::eventFilter(object, event); +} diff --git a/src/qt/signverifymessagedialog.h b/src/qt/signverifymessagedialog.h new file mode 100644 index 0000000..5569c8b --- /dev/null +++ b/src/qt/signverifymessagedialog.h @@ -0,0 +1,49 @@ +#ifndef SIGNVERIFYMESSAGEDIALOG_H +#define SIGNVERIFYMESSAGEDIALOG_H + +#include + +namespace Ui { + class SignVerifyMessageDialog; +} +class WalletModel; + +QT_BEGIN_NAMESPACE +QT_END_NAMESPACE + +class SignVerifyMessageDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SignVerifyMessageDialog(QWidget *parent = 0); + ~SignVerifyMessageDialog(); + + void setModel(WalletModel *model); + void setAddress_SM(QString address); + void setAddress_VM(QString address); + + void showTab_SM(bool fShow); + void showTab_VM(bool fShow); + +protected: + bool eventFilter(QObject *object, QEvent *event); + +private: + Ui::SignVerifyMessageDialog *ui; + WalletModel *model; + +private slots: + /* sign message */ + void on_addressBookButton_SM_clicked(); + void on_pasteButton_SM_clicked(); + void on_signMessageButton_SM_clicked(); + void on_copySignatureButton_SM_clicked(); + void on_clearButton_SM_clicked(); + /* verify message */ + void on_addressBookButton_VM_clicked(); + void on_verifyMessageButton_VM_clicked(); + void on_clearButton_VM_clicked(); +}; + +#endif // SIGNVERIFYMESSAGEDIALOG_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 5b11e39..af2d358 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -6,6 +6,11 @@ // This is all you need to run all the tests int main(int argc, char *argv[]) { + bool fInvalid = false; + URITests test1; - QTest::qExec(&test1); + if (QTest::qExec(&test1) != 0) + fInvalid = true; + + return fInvalid; } diff --git a/src/qt/test/uritests.cpp b/src/qt/test/uritests.cpp index 70c20be..4662f5e 100644 --- a/src/qt/test/uritests.cpp +++ b/src/qt/test/uritests.cpp @@ -47,7 +47,7 @@ void URITests::uriTests() uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100&label=Wikipedia Example")); QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); - QVERIFY(rv.amount == 10000000000); + QVERIFY(rv.amount == 10000000000LL); QVERIFY(rv.label == QString("Wikipedia Example")); uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?message=Wikipedia Example Address")); @@ -59,7 +59,7 @@ void URITests::uriTests() QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.label == QString()); - // We currently dont implement the message paramenter (ok, yea, we break spec...) + // We currently don't implement the message parameter (ok, yea, we break spec...) uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-message=Wikipedia Example Address")); QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index b03a520..5aa2aa9 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -7,17 +7,14 @@ #include "wallet.h" #include "db.h" #include "ui_interface.h" - -#include - -using namespace std; +#include "base58.h" QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) { if (!wtx.IsFinal()) { if (wtx.nLockTime < LOCKTIME_THRESHOLD) - return tr("Open for %1 blocks").arg(nBestHeight - wtx.nLockTime); + return tr("Open for %n block(s)", "", nBestHeight - wtx.nLockTime); else return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.nLockTime)); } @@ -25,7 +22,7 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) { int nDepth = wtx.GetDepthInMainChain(); if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) - return tr("%1/offline?").arg(nDepth); + return tr("%1/offline").arg(nDepth); else if (nDepth < 6) return tr("%1/unconfirmed").arg(nDepth); else @@ -47,33 +44,30 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) int64 nDebit = wtx.GetDebit(); int64 nNet = nCredit - nDebit; - strHTML += tr("Status: ") + FormatTxStatus(wtx); + strHTML += "" + tr("Status") + ": " + FormatTxStatus(wtx); int nRequests = wtx.GetRequestCount(); if (nRequests != -1) { if (nRequests == 0) strHTML += tr(", has not been successfully broadcast yet"); - else if (nRequests == 1) - strHTML += tr(", broadcast through %1 node").arg(nRequests); - else - strHTML += tr(", broadcast through %1 nodes").arg(nRequests); + else if (nRequests > 0) + strHTML += tr(", broadcast through %n node(s)", "", nRequests); } strHTML += "
"; - strHTML += tr("Date: ") + (nTime ? GUIUtil::dateTimeStr(nTime) : QString("")) + "
"; + strHTML += "" + tr("Date") + ": " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "
"; // // From // if (wtx.IsCoinBase()) { - strHTML += tr("Source: Generated
"); + strHTML += "" + tr("Source") + ": " + tr("Generated") + "
"; } - else if (!wtx.mapValue["from"].empty()) + else if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty()) { // Online transaction - if (!wtx.mapValue["from"].empty()) - strHTML += tr("From: ") + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "
"; + strHTML += "" + tr("From") + ": " + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "
"; } else { @@ -85,18 +79,18 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) { if (wallet->IsMine(txout)) { - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address)) + CTxDestination address; + if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) { if (wallet->mapAddressBook.count(address)) { - strHTML += tr("From: ") + tr("unknown") + "
"; - strHTML += tr("To: "); - strHTML += GUIUtil::HtmlEscape(address.ToString()); + strHTML += "" + tr("From") + ": " + tr("unknown") + "
"; + strHTML += "" + tr("To") + ": "; + strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); if (!wallet->mapAddressBook[address].empty()) - strHTML += tr(" (yours, label: ") + GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + ")"; + strHTML += " (" + tr("own address") + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + ")"; else - strHTML += tr(" (yours)"); + strHTML += " (" + tr("own address") + ")"; strHTML += "
"; } } @@ -109,14 +103,14 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // // To // - string strAddress; - if (!wtx.mapValue["to"].empty()) + if (wtx.mapValue.count("to") && !wtx.mapValue["to"].empty()) { // Online transaction - strAddress = wtx.mapValue["to"]; - strHTML += tr("To: "); - if (wallet->mapAddressBook.count(strAddress) && !wallet->mapAddressBook[strAddress].empty()) - strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[strAddress]) + " "; + std::string strAddress = wtx.mapValue["to"]; + strHTML += "" + tr("To") + ": "; + CTxDestination dest = CBitcoinAddress(strAddress).Get(); + if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].empty()) + strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest]) + " "; strHTML += GUIUtil::HtmlEscape(strAddress) + "
"; } @@ -131,13 +125,11 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) int64 nUnmatured = 0; BOOST_FOREACH(const CTxOut& txout, wtx.vout) nUnmatured += wallet->GetCredit(txout); - strHTML += tr("Credit: "); + strHTML += "" + tr("Credit") + ": "; if (wtx.IsInMainChain()) - strHTML += tr("(%1 matures in %2 more blocks)") - .arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nUnmatured)) - .arg(wtx.GetBlocksToMaturity()); + strHTML += BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")"; else - strHTML += tr("(not accepted)"); + strHTML += "(" + tr("not accepted") + ")"; strHTML += "
"; } else if (nNet > 0) @@ -145,7 +137,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // // Credit // - strHTML += tr("Credit: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nNet) + "
"; + strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nNet) + "
"; } else { @@ -167,21 +159,21 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) if (wallet->IsMine(txout)) continue; - if (wtx.mapValue["to"].empty()) + if (!wtx.mapValue.count("to") || wtx.mapValue["to"].empty()) { // Offline transaction - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, address)) + CTxDestination address; + if (ExtractDestination(txout.scriptPubKey, address)) { - strHTML += tr("To: "); + strHTML += "" + tr("To") + ": "; if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty()) strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " "; - strHTML += GUIUtil::HtmlEscape(address.ToString()); + strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); strHTML += "
"; } } - strHTML += tr("Debit: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -txout.nValue) + "
"; + strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -txout.nValue) + "
"; } if (fAllToMe) @@ -189,13 +181,13 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // Payment to self int64 nChange = wtx.GetChange(); int64 nValue = nCredit - nChange; - strHTML += tr("Debit: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -nValue) + "
"; - strHTML += tr("Credit: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nValue) + "
"; + strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -nValue) + "
"; + strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nValue) + "
"; } int64 nTxFee = nDebit - wtx.GetValueOut(); if (nTxFee > 0) - strHTML += tr("Transaction fee: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,-nTxFee) + "
"; + strHTML += "" + tr("Transaction fee") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -nTxFee) + "
"; } else { @@ -204,27 +196,27 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // BOOST_FOREACH(const CTxIn& txin, wtx.vin) if (wallet->IsMine(txin)) - strHTML += tr("Debit: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,-wallet->GetDebit(txin)) + "
"; + strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -wallet->GetDebit(txin)) + "
"; BOOST_FOREACH(const CTxOut& txout, wtx.vout) if (wallet->IsMine(txout)) - strHTML += tr("Credit: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,wallet->GetCredit(txout)) + "
"; + strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, wallet->GetCredit(txout)) + "
"; } } - strHTML += tr("Net amount: ") + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,nNet, true) + "
"; + strHTML += "" + tr("Net amount") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nNet, true) + "
"; // // Message // - if (!wtx.mapValue["message"].empty()) - strHTML += QString("
") + tr("Message:") + "
" + GUIUtil::HtmlEscape(wtx.mapValue["message"], true) + "
"; - if (!wtx.mapValue["comment"].empty()) - strHTML += QString("
") + tr("Comment:") + "
" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "
"; + if (wtx.mapValue.count("message") && !wtx.mapValue["message"].empty()) + strHTML += "
" + tr("Message") + ":
" + GUIUtil::HtmlEscape(wtx.mapValue["message"], true) + "
"; + if (wtx.mapValue.count("comment") && !wtx.mapValue["comment"].empty()) + strHTML += "
" + tr("Comment") + ":
" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "
"; - strHTML += QString("") + tr("Transaction ID:") + " " + wtx.GetHash().ToString().c_str() + "
"; + strHTML += "" + tr("Transaction ID") + ": " + wtx.GetHash().ToString().c_str() + "
"; if (wtx.IsCoinBase()) - strHTML += QString("
") + tr("Generated coins must wait 520 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.") + "
"; + strHTML += "
" + tr("Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.") + "
"; if (wtx.IsCoinStake()) strHTML += QString("
") + tr("Staked coins must wait 520 blocks before they can return to balance and be spent. When you generated this proof-of-stake block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be a valid stake. This may occasionally happen if another node generates a proof-of-stake block within a few seconds of yours.") + "
"; @@ -233,20 +225,20 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // if (fDebug) { - strHTML += "

Debug information

"; + strHTML += "

" + tr("Debug information") + "

"; BOOST_FOREACH(const CTxIn& txin, wtx.vin) if(wallet->IsMine(txin)) - strHTML += "Debit: " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,-wallet->GetDebit(txin)) + "
"; + strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -wallet->GetDebit(txin)) + "
"; BOOST_FOREACH(const CTxOut& txout, wtx.vout) if(wallet->IsMine(txout)) - strHTML += "Credit: " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,wallet->GetCredit(txout)) + "
"; + strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, wallet->GetCredit(txout)) + "
"; - strHTML += "
Transaction:
"; + strHTML += "
" + tr("Transaction") + ":
"; strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true); CTxDB txdb("r"); // To fetch source txouts - strHTML += "
Inputs:"; + strHTML += "
" + tr("Inputs") + ":"; strHTML += "
    "; { @@ -262,19 +254,20 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) { strHTML += "
  • "; const CTxOut &vout = prev.vout[prevout.n]; - CBitcoinAddress address; - if (ExtractAddress(vout.scriptPubKey, address)) + CTxDestination address; + if (ExtractDestination(vout.scriptPubKey, address)) { if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty()) strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " "; - strHTML += QString::fromStdString(address.ToString()); + strHTML += QString::fromStdString(CBitcoinAddress(address).ToString()); } - strHTML = strHTML + " Amount=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,vout.nValue); - strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) ? "true" : "false") + "
  • "; + strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, vout.nValue); + strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) ? tr("true") : tr("false")) + ""; } } } } + strHTML += "
"; } diff --git a/src/qt/transactionfilterproxy.h b/src/qt/transactionfilterproxy.h index 8d6829d..30b9858 100644 --- a/src/qt/transactionfilterproxy.h +++ b/src/qt/transactionfilterproxy.h @@ -23,7 +23,7 @@ public: void setDateRange(const QDateTime &from, const QDateTime &to); void setAddressPrefix(const QString &addrPrefix); /** - @note Type filter takes a bitfield created with TYPE() or ALL_TYPES + @note Type filter takes a bit field created with TYPE() or ALL_TYPES */ void setTypeFilter(quint32 modes); void setMinAmount(qint64 minimum); diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 1998040..4c30719 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -1,6 +1,7 @@ #include "transactionrecord.h" #include "wallet.h" +#include "base58.h" /* Return positive answer if transaction should be shown in list. */ @@ -8,18 +9,8 @@ bool TransactionRecord::showTransaction(const CWalletTx &wtx) { if (wtx.IsCoinBase()) { - // Don't show generated coin until confirmed by at least one block after it - // so we don't get the user's hopes up until it looks like it's probably accepted. - // - // It is not an error when generated blocks are not accepted. By design, - // some percentage of blocks, like 10% or more, will end up not accepted. - // This is the normal mechanism by which the network copes with latency. - // - // We display regular transactions right away before any confirmation - // because they can always get into some block eventually. Generated coins - // are special because if their block is not accepted, they are not valid. - // - if (wtx.GetDepthInMainChain() < 2) + // Ensures we show generated coins / mined transactions at depth 1 + if (!wtx.IsInMainChain()) { return false; } @@ -40,118 +31,111 @@ QList TransactionRecord::decomposeTransaction(const CWallet * uint256 hash = wtx.GetHash(); std::map mapValue = wtx.mapValue; - if (showTransaction(wtx)) + if (nNet > 0 || wtx.IsCoinBase()) { - if (wtx.IsCoinStake()) // ppcoin: coinstake transaction - { - parts.append(TransactionRecord(hash, nTime, TransactionRecord::StakeMint, "", -nDebit, wtx.GetValueOut())); - } - else if (nNet > 0 || wtx.IsCoinBase()) + // + // Credit + // + BOOST_FOREACH(const CTxOut& txout, wtx.vout) { - // - // Credit - // - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if(wallet->IsMine(txout)) { - if(wallet->IsMine(txout)) + TransactionRecord sub(hash, nTime); + CTxDestination address; + sub.idx = parts.size(); // sequence number + sub.credit = txout.nValue; + if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) + { + // Received by Bitcoin Address + sub.type = TransactionRecord::RecvWithAddress; + sub.address = CBitcoinAddress(address).ToString(); + } + else + { + // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction + sub.type = TransactionRecord::RecvFromOther; + sub.address = mapValue["from"]; + } + if (wtx.IsCoinBase()) { - TransactionRecord sub(hash, nTime); - CBitcoinAddress address; - sub.idx = parts.size(); // sequence number - sub.credit = txout.nValue; - if (wtx.IsCoinBase()) - { - // Generated - sub.type = TransactionRecord::Generated; - } - else if (ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address)) - { - // Received by Bitcoin Address - sub.type = TransactionRecord::RecvWithAddress; - sub.address = address.ToString(); - } - else - { - // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction - sub.type = TransactionRecord::RecvFromOther; - sub.address = mapValue["from"]; - } - - parts.append(sub); + // Generated + sub.type = TransactionRecord::Generated; } + + parts.append(sub); } } - else + } + else + { + bool fAllFromMe = true; + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + fAllFromMe = fAllFromMe && wallet->IsMine(txin); + + bool fAllToMe = true; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + fAllToMe = fAllToMe && wallet->IsMine(txout); + + if (fAllFromMe && fAllToMe) { - bool fAllFromMe = true; - BOOST_FOREACH(const CTxIn& txin, wtx.vin) - fAllFromMe = fAllFromMe && wallet->IsMine(txin); + // Payment to self + int64 nChange = wtx.GetChange(); - bool fAllToMe = true; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - fAllToMe = fAllToMe && wallet->IsMine(txout); + parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", + -(nDebit - nChange), nCredit - nChange)); + } + else if (fAllFromMe) + { + // + // Debit + // + int64 nTxFee = nDebit - wtx.GetValueOut(); - if (fAllFromMe && fAllToMe) + for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { - // Payment to self - int64 nChange = wtx.GetChange(); + const CTxOut& txout = wtx.vout[nOut]; + TransactionRecord sub(hash, nTime); + sub.idx = parts.size(); - parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", - -(nDebit - nChange), nCredit - nChange)); - } - else if (fAllFromMe) - { - // - // Debit - // - int64 nTxFee = nDebit - wtx.GetValueOut(); + if(wallet->IsMine(txout)) + { + // Ignore parts sent to self, as this is usually the change + // from a transaction sent back to our own address. + continue; + } - for (int nOut = 0; nOut < wtx.vout.size(); nOut++) + CTxDestination address; + if (ExtractDestination(txout.scriptPubKey, address)) { - const CTxOut& txout = wtx.vout[nOut]; - TransactionRecord sub(hash, nTime); - sub.idx = parts.size(); - - if(wallet->IsMine(txout)) - { - // Ignore parts sent to self, as this is usually the change - // from a transaction sent back to our own address. - continue; - } - - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, address)) - { - // Sent to Bitcoin Address - sub.type = TransactionRecord::SendToAddress; - sub.address = address.ToString(); - } - else - { - // Sent to IP, or other non-address transaction like OP_EVAL - sub.type = TransactionRecord::SendToOther; - sub.address = mapValue["to"]; - } - - int64 nValue = txout.nValue; - /* Add fee to first output */ - if (nTxFee > 0) - { - nValue += nTxFee; - nTxFee = 0; - } - sub.debit = -nValue; - - parts.append(sub); + // Sent to Bitcoin Address + sub.type = TransactionRecord::SendToAddress; + sub.address = CBitcoinAddress(address).ToString(); } + else + { + // Sent to IP, or other non-address transaction like OP_EVAL + sub.type = TransactionRecord::SendToOther; + sub.address = mapValue["to"]; + } + + int64 nValue = txout.nValue; + /* Add fee to first output */ + if (nTxFee > 0) + { + nValue += nTxFee; + nTxFee = 0; + } + sub.debit = -nValue; + + parts.append(sub); } - else - { - // - // Mixed debit transaction, can't break down payees - // - parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0)); - } + } + else + { + // + // Mixed debit transaction, can't break down payees + // + parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0)); } } @@ -208,7 +192,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) } // For generated transactions, determine maturity - if(type == TransactionRecord::Generated || type == TransactionRecord::StakeMint) + if(type == TransactionRecord::Generated) { int64 nCredit = wtx.GetCredit(true); if (nCredit == 0) diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 8d4ab03..db06374 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -68,8 +68,7 @@ public: SendToOther, RecvWithAddress, RecvFromOther, - SendToSelf, - StakeMint + SendToSelf }; /** Number of confirmation needed for transaction */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 9c3b80e..847c9e9 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -9,6 +9,7 @@ #include "bitcoinunits.h" #include "wallet.h" +#include "ui_interface.h" #include #include @@ -66,15 +67,14 @@ public: */ void refreshWallet() { -#ifdef WALLET_UPDATE_DEBUG - qDebug() << "refreshWallet"; -#endif + OutputDebugStringF("refreshWallet\n"); cachedWallet.clear(); { LOCK(wallet->cs_wallet); for(std::map::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it) { - cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); + if(TransactionRecord::showTransaction(it->second)) + cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); } } } @@ -82,49 +82,55 @@ public: /* Update our model of the wallet incrementally, to synchronize our model of the wallet with that of the core. - Call with list of hashes of transactions that were added, removed or changed. + Call with transaction that was added, removed or changed. */ - void updateWallet(const QList &updated) + void updateWallet(const uint256 &hash, int status) { - // Walk through updated transactions, update model as needed. -#ifdef WALLET_UPDATE_DEBUG - qDebug() << "updateWallet"; -#endif - // Sort update list, and iterate through it in reverse, so that model updates - // can be emitted from end to beginning (so that earlier updates will not influence - // the indices of latter ones). - QList updated_sorted = updated; - qSort(updated_sorted); - + OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status); { LOCK(wallet->cs_wallet); - for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx) + + // Find transaction in wallet + std::map::iterator mi = wallet->mapWallet.find(hash); + bool inWallet = mi != wallet->mapWallet.end(); + + // Find bounds of this transaction in model + QList::iterator lower = qLowerBound( + cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); + QList::iterator upper = qUpperBound( + cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); + int lowerIndex = (lower - cachedWallet.begin()); + int upperIndex = (upper - cachedWallet.begin()); + bool inModel = (lower != upper); + + // Determine whether to show transaction or not + bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second)); + + if(status == CT_UPDATED) { - const uint256 &hash = updated_sorted.at(update_idx); - // Find transaction in wallet - std::map::iterator mi = wallet->mapWallet.find(hash); - bool inWallet = mi != wallet->mapWallet.end(); - // Find bounds of this transaction in model - QList::iterator lower = qLowerBound( - cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - QList::iterator upper = qUpperBound( - cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - int lowerIndex = (lower - cachedWallet.begin()); - int upperIndex = (upper - cachedWallet.begin()); - - // Determine if transaction is in model already - bool inModel = false; - if(lower != upper) - { - inModel = true; - } + if(showTransaction && !inModel) + status = CT_NEW; /* Not in model, but want to show, treat as new */ + if(!showTransaction && inModel) + status = CT_DELETED; /* In model, but want to hide, treat as deleted */ + } -#ifdef WALLET_UPDATE_DEBUG - qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel - << lowerIndex << "-" << upperIndex; -#endif + OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n", + inWallet, inModel, lowerIndex, upperIndex, showTransaction, status); - if(inWallet && !inModel) + switch(status) + { + case CT_NEW: + if(inModel) + { + OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n"); + break; + } + if(!inWallet) + { + OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n"); + break; + } + if(showTransaction) { // Added -- insert at the right position QList toInsert = @@ -141,17 +147,22 @@ public: parent->endInsertRows(); } } - else if(!inWallet && inModel) - { - // Removed -- remove entire transaction from table - parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); - cachedWallet.erase(lower, upper); - parent->endRemoveRows(); - } - else if(inWallet && inModel) + break; + case CT_DELETED: + if(!inModel) { - // Updated -- nothing to do, status update will take care of this + OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n"); + break; } + // Removed -- remove entire transaction from table + parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); + cachedWallet.erase(lower, upper); + parent->endRemoveRows(); + break; + case CT_UPDATED: + // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for + // visible transactions. + break; } } } @@ -209,15 +220,18 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren QAbstractTableModel(parent), wallet(wallet), walletModel(parent), - priv(new TransactionTablePriv(wallet, this)) + priv(new TransactionTablePriv(wallet, this)), + cachedNumBlocks(0) { columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount"); priv->refreshWallet(); QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(update())); + connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations())); timer->start(MODEL_UPDATE_DELAY); + + connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); } TransactionTableModel::~TransactionTableModel() @@ -225,29 +239,23 @@ TransactionTableModel::~TransactionTableModel() delete priv; } -void TransactionTableModel::update() +void TransactionTableModel::updateTransaction(const QString &hash, int status) { - QList updated; + uint256 updated; + updated.SetHex(hash.toStdString()); - // Check if there are changes to wallet map - { - TRY_LOCK(wallet->cs_wallet, lockWallet); - if (lockWallet && !wallet->vWalletUpdated.empty()) - { - BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated) - { - updated.append(hash); - } - wallet->vWalletUpdated.clear(); - } - } + priv->updateWallet(updated, status); +} - if(!updated.empty()) +void TransactionTableModel::updateConfirmations() +{ + if(nBestHeight != cachedNumBlocks) { - priv->updateWallet(updated); - - // Status (number of confirmations) and (possibly) description - // columns changed for all rows. + cachedNumBlocks = nBestHeight; + // Blocks came in since last poll. + // Invalidate status (number of confirmations) and (possibly) description + // for all rows. Qt is smart enough to only actually request the data for the + // visible rows. emit dataChanged(index(0, Status), index(priv->size()-1, Status)); emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress)); } @@ -287,13 +295,12 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); break; } - if(wtx->type == TransactionRecord::Generated || wtx->type == TransactionRecord::StakeMint) + if(wtx->type == TransactionRecord::Generated) { switch(wtx->status.maturity) { case TransactionStatus::Immature: - status += "\n" + tr("Mined balance will be available in %n more blocks", "", - wtx->status.matures_in); + status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in); break; case TransactionStatus::Mature: break; @@ -354,8 +361,6 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const return tr("Payment to yourself"); case TransactionRecord::Generated: return tr("Mined"); - case TransactionRecord::StakeMint: - return tr("Mint by stake"); default: return QString(); } @@ -387,11 +392,11 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b return QString::fromStdString(wtx->address); case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: + case TransactionRecord::Generated: return lookupAddress(wtx->address, tooltip); case TransactionRecord::SendToOther: return QString::fromStdString(wtx->address); case TransactionRecord::SendToSelf: - case TransactionRecord::Generated: default: return tr("(n/a)"); } @@ -404,13 +409,13 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const { case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: + case TransactionRecord::Generated: { QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address)); if(label.isEmpty()) return COLOR_BAREADDRESS; } break; case TransactionRecord::SendToSelf: - case TransactionRecord::Generated: return COLOR_BAREADDRESS; default: break; @@ -621,3 +626,8 @@ QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex } } +void TransactionTableModel::updateDisplayUnit() +{ + // emit dataChanged to update Amount column with the current unit + emit dataChanged(index(0, Amount), index(priv->size()-1, Amount)); +} diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index db88a06..fd321ce 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -60,6 +60,7 @@ private: WalletModel *walletModel; QStringList columns; TransactionTablePriv *priv; + int cachedNumBlocks; QString lookupAddress(const std::string &address, bool tooltip) const; QVariant addressColor(const TransactionRecord *wtx) const; @@ -72,8 +73,10 @@ private: QVariant txStatusDecoration(const TransactionRecord *wtx) const; QVariant txAddressDecoration(const TransactionRecord *wtx) const; -private slots: - void update(); +public slots: + void updateTransaction(const QString &hash, int status); + void updateConfirmations(); + void updateDisplayUnit(); friend class TransactionTablePriv; }; diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 70045a1..7acf5de 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -38,7 +38,7 @@ TransactionView::TransactionView(QWidget *parent) : QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0,0,0,0); -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC hlayout->setSpacing(5); hlayout->addSpacing(26); #else @@ -47,7 +47,7 @@ TransactionView::TransactionView(QWidget *parent) : #endif dateWidget = new QComboBox(this); -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC dateWidget->setFixedWidth(121); #else dateWidget->setFixedWidth(120); @@ -62,7 +62,7 @@ TransactionView::TransactionView(QWidget *parent) : hlayout->addWidget(dateWidget); typeWidget = new QComboBox(this); -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC typeWidget->setFixedWidth(121); #else typeWidget->setFixedWidth(120); @@ -75,22 +75,23 @@ TransactionView::TransactionView(QWidget *parent) : TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); - typeWidget->addItem(tr("Mint by stake"), TransactionFilterProxy::TYPE(TransactionRecord::StakeMint)); typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); hlayout->addWidget(typeWidget); addressWidget = new QLineEdit(this); #if QT_VERSION >= 0x040700 + /* Do not move this to the XML file, Qt before 4.7 will choke on it */ addressWidget->setPlaceholderText(tr("Enter address or label to search")); #endif hlayout->addWidget(addressWidget); amountWidget = new QLineEdit(this); #if QT_VERSION >= 0x040700 + /* Do not move this to the XML file, Qt before 4.7 will choke on it */ amountWidget->setPlaceholderText(tr("Min amount")); #endif -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC amountWidget->setFixedWidth(97); #else amountWidget->setFixedWidth(100); @@ -109,7 +110,7 @@ TransactionView::TransactionView(QWidget *parent) : vlayout->setSpacing(0); int width = view->verticalScrollBar()->sizeHint().width(); // Cover scroll bar width with spacing -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC hlayout->addSpacing(width+2); #else hlayout->addSpacing(width); @@ -126,7 +127,7 @@ TransactionView::TransactionView(QWidget *parent) : QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); QAction *editLabelAction = new QAction(tr("Edit label"), this); - QAction *showDetailsAction = new QAction(tr("Show details..."), this); + QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); contextMenu = new QMenu(); contextMenu->addAction(copyAddressAction); @@ -159,6 +160,8 @@ void TransactionView::setModel(WalletModel *model) transactionProxyModel = new TransactionFilterProxy(this); transactionProxyModel->setSourceModel(model->getTransactionTableModel()); transactionProxyModel->setDynamicSortFilter(true); + transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setSortRole(Qt::EditRole); @@ -202,7 +205,7 @@ void TransactionView::chooseDate(int idx) TransactionFilterProxy::MAX_DATE); break; case ThisWeek: { - // Find last monday + // Find last Monday QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1)); transactionProxyModel->setDateRange( QDateTime(startOfWeek), @@ -416,3 +419,13 @@ void TransactionView::dateRangeChanged() QDateTime(dateFrom->date()), QDateTime(dateTo->date()).addDays(1)); } + +void TransactionView::focusTransaction(const QModelIndex &idx) +{ + if(!transactionProxyModel) + return; + QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx); + transactionView->scrollTo(targetIdx); + transactionView->setCurrentIndex(targetIdx); + transactionView->setFocus(); +} diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index bc6e1e4..4ade3ec 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -75,6 +75,7 @@ public slots: void changedPrefix(const QString &prefix); void changedAmount(const QString &amount); void exportClicked(); + void focusTransaction(const QModelIndex&); }; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e3e68b3..2dd321b 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -7,17 +7,33 @@ #include "ui_interface.h" #include "wallet.h" #include "walletdb.h" // for BackupWallet +#include "base58.h" #include +#include WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), transactionTableModel(0), - cachedBalance(0), cachedUnconfirmedBalance(0), cachedNumTransactions(0), - cachedEncryptionStatus(Unencrypted) + cachedBalance(0), cachedStake(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), + cachedNumTransactions(0), + cachedEncryptionStatus(Unencrypted), + cachedNumBlocks(0) { addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(wallet, this); + + // This timer will be fired repeatedly to update the balance + pollTimer = new QTimer(this); + connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged())); + pollTimer->start(MODEL_UPDATE_DELAY); + + subscribeToCoreSignals(); +} + +WalletModel::~WalletModel() +{ + unsubscribeFromCoreSignals(); } qint64 WalletModel::getBalance() const @@ -25,14 +41,19 @@ qint64 WalletModel::getBalance() const return wallet->GetBalance(); } +qint64 WalletModel::getUnconfirmedBalance() const +{ + return wallet->GetUnconfirmedBalance(); +} + qint64 WalletModel::getStake() const { return wallet->GetStake(); } -qint64 WalletModel::getUnconfirmedBalance() const +qint64 WalletModel::getImmatureBalance() const { - return wallet->GetUnconfirmedBalance(); + return wallet->GetImmatureBalance(); } int WalletModel::getNumTransactions() const @@ -45,30 +66,61 @@ int WalletModel::getNumTransactions() const return numTransactions; } -void WalletModel::update() +void WalletModel::updateStatus() +{ + EncryptionStatus newEncryptionStatus = getEncryptionStatus(); + + if(cachedEncryptionStatus != newEncryptionStatus) + emit encryptionStatusChanged(newEncryptionStatus); +} + +void WalletModel::pollBalanceChanged() +{ + if(nBestHeight != cachedNumBlocks) + { + // Balance and number of transactions might have changed + cachedNumBlocks = nBestHeight; + checkBalanceChanged(); + } +} + +void WalletModel::checkBalanceChanged() { qint64 newBalance = getBalance(); + qint64 newStake = getStake(); qint64 newUnconfirmedBalance = getUnconfirmedBalance(); - int newNumTransactions = getNumTransactions(); - EncryptionStatus newEncryptionStatus = getEncryptionStatus(); + qint64 newImmatureBalance = getImmatureBalance(); + + if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance) + { + cachedBalance = newBalance; + cachedStake = newStake; + cachedUnconfirmedBalance = newUnconfirmedBalance; + cachedImmatureBalance = newImmatureBalance; + emit balanceChanged(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance); + } +} - if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance) - emit balanceChanged(newBalance, getStake(), newUnconfirmedBalance); +void WalletModel::updateTransaction(const QString &hash, int status) +{ + if(transactionTableModel) + transactionTableModel->updateTransaction(hash, status); + // Balance and number of transactions might have changed + checkBalanceChanged(); + + int newNumTransactions = getNumTransactions(); if(cachedNumTransactions != newNumTransactions) + { + cachedNumTransactions = newNumTransactions; emit numTransactionsChanged(newNumTransactions); - - if(cachedEncryptionStatus != newEncryptionStatus) - emit encryptionStatusChanged(newEncryptionStatus); - - cachedBalance = newBalance; - cachedUnconfirmedBalance = newUnconfirmedBalance; - cachedNumTransactions = newNumTransactions; + } } -void WalletModel::updateAddressList() +void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, int status) { - addressTableModel->update(); + if(addressTableModel) + addressTableModel->updateEntry(address, label, isMine, status); } bool WalletModel::validateAddress(const QString &address) @@ -127,7 +179,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QListcs_wallet); - std::map::iterator mi = wallet->mapAddressBook.find(strAddress); + std::map::iterator mi = wallet->mapAddressBook.find(dest); // Check if we have a new address or an updated label if (mi == wallet->mapAddressBook.end() || mi->second != strLabel) { - wallet->SetAddressBookName(strAddress, strLabel); + wallet->SetAddressBookName(dest, strLabel); } } } @@ -251,6 +304,47 @@ bool WalletModel::backupWallet(const QString &filename) return BackupWallet(*wallet, filename.toLocal8Bit().data()); } +// Handlers for core signals +static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) +{ + OutputDebugStringF("NotifyKeyStoreStatusChanged\n"); + QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); +} + +static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status) +{ + OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", CBitcoinAddress(address).ToString().c_str(), label.c_str(), isMine, status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(CBitcoinAddress(address).ToString())), + Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(bool, isMine), + Q_ARG(int, status)); +} + +static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status); + QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void WalletModel::subscribeToCoreSignals() +{ + // Connect signals to wallet + wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + +void WalletModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from wallet + wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + // WalletModel::UnlockContext implementation WalletModel::UnlockContext WalletModel::requestUnlock() { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 11df41e..324d7f3 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -10,6 +10,10 @@ class AddressTableModel; class TransactionTableModel; class CWallet; +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + class SendCoinsRecipient { public: @@ -24,6 +28,7 @@ class WalletModel : public QObject Q_OBJECT public: explicit WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent = 0); + ~WalletModel(); enum StatusCode // Returned by sendCoins { @@ -52,6 +57,7 @@ public: qint64 getBalance() const; qint64 getStake() const; qint64 getUnconfirmedBalance() const; + qint64 getImmatureBalance() const; int getNumTransactions() const; EncryptionStatus getEncryptionStatus() const; @@ -115,13 +121,22 @@ private: // Cache some values to be able to detect changes qint64 cachedBalance; + qint64 cachedStake; qint64 cachedUnconfirmedBalance; + qint64 cachedImmatureBalance; qint64 cachedNumTransactions; EncryptionStatus cachedEncryptionStatus; + int cachedNumBlocks; + + QTimer *pollTimer; + + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); + void checkBalanceChanged(); signals: // Signal that balance in wallet changed - void balanceChanged(qint64 balance, qint64 stake, qint64 unconfirmedBalance); + void balanceChanged(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance); // Number of transactions in wallet changed void numTransactionsChanged(int count); @@ -138,8 +153,14 @@ signals: void error(const QString &title, const QString &message, bool modal); public slots: - void update(); - void updateAddressList(); + /* Wallet status might have changed */ + void updateStatus(); + /* New transaction, or transaction changed status */ + void updateTransaction(const QString &hash, int status); + /* New, updated or removed address book entry */ + void updateAddressBook(const QString &address, const QString &label, bool isMine, int status); + /* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */ + void pollBalanceChanged(); }; diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp new file mode 100644 index 0000000..09e81b0 --- /dev/null +++ b/src/rpcblockchain.cpp @@ -0,0 +1,232 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "main.h" +#include "bitcoinrpc.h" + +using namespace json_spirit; +using namespace std; + +extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, json_spirit::Object& entry); + +double GetDifficulty(const CBlockIndex* blockindex) +{ + // Floating point number that is a multiple of the minimum difficulty, + // minimum difficulty = 1.0. + if (blockindex == NULL) + { + if (pindexBest == NULL) + return 1.0; + else + blockindex = GetLastBlockIndex(pindexBest, false); + } + + int nShift = (blockindex->nBits >> 24) & 0xff; + + double dDiff = + (double)0x0000ffff / (double)(blockindex->nBits & 0x00ffffff); + + while (nShift < 29) + { + dDiff *= 256.0; + nShift++; + } + while (nShift > 29) + { + dDiff /= 256.0; + nShift--; + } + + return dDiff; +} + + +Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fPrintTransactionDetail) +{ + Object result; + result.push_back(Pair("hash", block.GetHash().GetHex())); + CMerkleTx txGen(block.vtx[0]); + txGen.SetMerkleBranch(&block); + result.push_back(Pair("confirmations", (int)txGen.GetDepthInMainChain())); + result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); + result.push_back(Pair("height", blockindex->nHeight)); + result.push_back(Pair("version", block.nVersion)); + result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex())); + result.push_back(Pair("mint", ValueFromAmount(blockindex->nMint))); + result.push_back(Pair("time", (boost::int64_t)block.GetBlockTime())); + result.push_back(Pair("nonce", (boost::uint64_t)block.nNonce)); + result.push_back(Pair("bits", HexBits(block.nBits))); + result.push_back(Pair("difficulty", GetDifficulty(blockindex))); + + if (blockindex->pprev) + result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); + if (blockindex->pnext) + result.push_back(Pair("nextblockhash", blockindex->pnext->GetBlockHash().GetHex())); + + result.push_back(Pair("flags", strprintf("%s%s", blockindex->IsProofOfStake()? "proof-of-stake" : "proof-of-work", blockindex->GeneratedStakeModifier()? " stake-modifier": ""))); + result.push_back(Pair("proofhash", blockindex->IsProofOfStake()? blockindex->hashProofOfStake.GetHex() : blockindex->GetBlockHash().GetHex())); + result.push_back(Pair("entropybit", (int)blockindex->GetStakeEntropyBit())); + result.push_back(Pair("modifier", strprintf("%016"PRI64x, blockindex->nStakeModifier))); + result.push_back(Pair("modifierchecksum", strprintf("%08x", blockindex->nStakeModifierChecksum))); + Array txinfo; + BOOST_FOREACH (const CTransaction& tx, block.vtx) + { + if (fPrintTransactionDetail) + { + Object entry; + + entry.push_back(Pair("txid", tx.GetHash().GetHex())); + TxToJSON(tx, 0, entry); + + txinfo.push_back(entry); + } + else + txinfo.push_back(tx.GetHash().GetHex()); + } + + result.push_back(Pair("tx", txinfo)); + result.push_back(Pair("signature", HexStr(block.vchBlockSig.begin(), block.vchBlockSig.end()))); + + return result; +} + + +Value getblockcount(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getblockcount\n" + "Returns the number of blocks in the longest block chain."); + + return nBestHeight; +} + + +Value getdifficulty(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getdifficulty\n" + "Returns the difficulty as a multiple of the minimum difficulty."); + + Object obj; + obj.push_back(Pair("proof-of-work", GetDifficulty())); + obj.push_back(Pair("proof-of-stake", GetDifficulty(GetLastBlockIndex(pindexBest, true)))); + obj.push_back(Pair("search-interval", (int)nLastCoinStakeSearchInterval)); + return obj; +} + + +Value settxfee(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 1 || AmountFromValue(params[0]) < MIN_TX_FEE) + throw runtime_error( + "settxfee \n" + " is a real and is rounded to the nearest 0.01"); + + nTransactionFee = AmountFromValue(params[0]); + nTransactionFee = (nTransactionFee / CENT) * CENT; // round to cent + + return true; +} + +Value getrawmempool(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getrawmempool\n" + "Returns all transaction ids in memory pool."); + + vector vtxid; + mempool.queryHashes(vtxid); + + Array a; + BOOST_FOREACH(const uint256& hash, vtxid) + a.push_back(hash.ToString()); + + return a; +} + +Value getblockhash(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getblockhash \n" + "Returns hash of block in best-block-chain at ."); + + int nHeight = params[0].get_int(); + if (nHeight < 0 || nHeight > nBestHeight) + throw runtime_error("Block number out of range."); + + CBlockIndex* pblockindex = FindBlockByHeight(nHeight); + return pblockindex->phashBlock->GetHex(); +} + +Value getblock(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getblock [txinfo]\n" + "txinfo optional to print more detailed tx info\n" + "Returns details of a block with given block-hash."); + + std::string strHash = params[0].get_str(); + uint256 hash(strHash); + + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + CBlock block; + CBlockIndex* pblockindex = mapBlockIndex[hash]; + block.ReadFromDisk(pblockindex, true); + + return blockToJSON(block, pblockindex, params.size() > 1 ? params[1].get_bool() : false); +} + +Value getblockbynumber(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getblock [txinfo]\n" + "txinfo optional to print more detailed tx info\n" + "Returns details of a block with given block-number."); + + int nHeight = params[0].get_int(); + if (nHeight < 0 || nHeight > nBestHeight) + throw runtime_error("Block number out of range."); + + CBlock block; + CBlockIndex* pblockindex = mapBlockIndex[hashBestChain]; + while (pblockindex->nHeight > nHeight) + pblockindex = pblockindex->pprev; + + uint256 hash = *pblockindex->phashBlock; + + pblockindex = mapBlockIndex[hash]; + block.ReadFromDisk(pblockindex, true); + + return blockToJSON(block, pblockindex, params.size() > 1 ? params[1].get_bool() : false); +} + +// ppcoin: get information of sync-checkpoint +Value getcheckpoint(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getcheckpoint\n" + "Show info of synchronized checkpoint.\n"); + + Object result; + CBlockIndex* pindexCheckpoint; + + result.push_back(Pair("synccheckpoint", Checkpoints::hashSyncCheckpoint.ToString().c_str())); + pindexCheckpoint = mapBlockIndex[Checkpoints::hashSyncCheckpoint]; + result.push_back(Pair("height", pindexCheckpoint->nHeight)); + result.push_back(Pair("timestamp", DateTimeStrFormat(pindexCheckpoint->GetBlockTime()).c_str())); + if (mapArgs.count("-checkpointkey")) + result.push_back(Pair("checkpointmaster", true)); + + return result; +} diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 864d950..e287742 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -1,26 +1,19 @@ // Copyright (c) 2009-2012 Bitcoin Developers -// Copyright (c) 2012-2013 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "init.h" // for pwalletMain #include "bitcoinrpc.h" #include "ui_interface.h" +#include "base58.h" #include -#include "json/json_spirit_reader_template.h" -#include "json/json_spirit_writer_template.h" -#include "json/json_spirit_utils.h" - #define printf OutputDebugStringF -// using namespace boost::asio; using namespace json_spirit; using namespace std; -extern Object JSONRPCError(int code, const string& message); - class CTxDump { public: @@ -53,16 +46,15 @@ Value importprivkey(const Array& params, bool fHelp) CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strSecret); - if (!fGood) throw JSONRPCError(-5,"Invalid private key"); + if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); if (fWalletUnlockMintOnly) // ppcoin: no importprivkey in mint-only mode - throw JSONRPCError(-102, "Wallet is unlocked for minting only."); + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Wallet is unlocked for minting only."); CKey key; bool fCompressed; CSecret secret = vchSecret.GetSecret(fCompressed); key.SetSecret(secret, fCompressed); - CBitcoinAddress vchAddress = CBitcoinAddress(key.GetPubKey()); - + CKeyID vchAddress = key.GetPubKey().GetID(); { LOCK2(cs_main, pwalletMain->cs_wallet); @@ -70,14 +62,12 @@ Value importprivkey(const Array& params, bool fHelp) pwalletMain->SetAddressBookName(vchAddress, strLabel); if (!pwalletMain->AddKey(key)) - throw JSONRPCError(-4,"Error adding key to wallet"); + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true); pwalletMain->ReacceptWalletTransactions(); } - MainFrameRepaint(); - return Value::null; } @@ -91,12 +81,15 @@ Value dumpprivkey(const Array& params, bool fHelp) string strAddress = params[0].get_str(); CBitcoinAddress address; if (!address.SetString(strAddress)) - throw JSONRPCError(-5, "Invalid novacoin address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid NovaCoin address"); if (fWalletUnlockMintOnly) // ppcoin: no dumpprivkey in mint-only mode - throw JSONRPCError(-102, "Wallet is unlocked for minting only."); + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Wallet is unlocked for minting only."); + CKeyID keyID; + if (!address.GetKeyID(keyID)) + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); CSecret vchSecret; bool fCompressed; - if (!pwalletMain->GetSecret(address, vchSecret, fCompressed)) - throw JSONRPCError(-4,"Private key for address " + strAddress + " is not known"); + if (!pwalletMain->GetSecret(keyID, vchSecret, fCompressed)) + throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); return CBitcoinSecret(vchSecret, fCompressed).ToString(); } diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp new file mode 100644 index 0000000..103b933 --- /dev/null +++ b/src/rpcmining.cpp @@ -0,0 +1,516 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "main.h" +#include "db.h" +#include "init.h" +#include "bitcoinrpc.h" + +using namespace json_spirit; +using namespace std; + +Value getgenerate(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getgenerate\n" + "Returns true or false."); + + return GetBoolArg("-gen"); +} + + +Value setgenerate(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "setgenerate [genproclimit]\n" + " is true or false to turn generation on or off.\n" + "Generation is limited to [genproclimit] processors, -1 is unlimited."); + + bool fGenerate = true; + if (params.size() > 0) + fGenerate = params[0].get_bool(); + + if (params.size() > 1) + { + int nGenProcLimit = params[1].get_int(); + mapArgs["-genproclimit"] = itostr(nGenProcLimit); + if (nGenProcLimit == 0) + fGenerate = false; + } + mapArgs["-gen"] = (fGenerate ? "1" : "0"); + + GenerateBitcoins(fGenerate, pwalletMain); + return Value::null; +} + + +Value gethashespersec(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "gethashespersec\n" + "Returns a recent hashes per second performance measurement while generating."); + + if (GetTimeMillis() - nHPSTimerStart > 8000) + return (boost::int64_t)0; + return (boost::int64_t)dHashesPerSec; +} + + +Value getmininginfo(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getmininginfo\n" + "Returns an object containing mining-related information."); + + Object obj; + obj.push_back(Pair("blocks", (int)nBestHeight)); + obj.push_back(Pair("currentblocksize",(uint64_t)nLastBlockSize)); + obj.push_back(Pair("currentblocktx",(uint64_t)nLastBlockTx)); + obj.push_back(Pair("difficulty", (double)GetDifficulty())); + obj.push_back(Pair("errors", GetWarnings("statusbar"))); + obj.push_back(Pair("generate", GetBoolArg("-gen"))); + obj.push_back(Pair("genproclimit", (int)GetArg("-genproclimit", -1))); + obj.push_back(Pair("hashespersec", gethashespersec(params, false))); + obj.push_back(Pair("pooledtx", (uint64_t)mempool.size())); + obj.push_back(Pair("testnet", fTestNet)); + return obj; +} + +Value getworkex(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "getworkex [data, coinbase]\n" + "If [data, coinbase] is not specified, returns extended work data.\n" + ); + + if (vNodes.empty()) + throw JSONRPCError(-9, "NovaCoin is not connected!"); + + if (IsInitialBlockDownload()) + throw JSONRPCError(-10, "NovaCoin is downloading blocks..."); + + typedef map > mapNewBlock_t; + static mapNewBlock_t mapNewBlock; + static vector vNewBlock; + static CReserveKey reservekey(pwalletMain); + + if (params.size() == 0) + { + // Update block + static unsigned int nTransactionsUpdatedLast; + static CBlockIndex* pindexPrev; + static int64 nStart; + static CBlock* pblock; + if (pindexPrev != pindexBest || + (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)) + { + if (pindexPrev != pindexBest) + { + // Deallocate old blocks since they're obsolete now + mapNewBlock.clear(); + BOOST_FOREACH(CBlock* pblock, vNewBlock) + delete pblock; + vNewBlock.clear(); + } + nTransactionsUpdatedLast = nTransactionsUpdated; + pindexPrev = pindexBest; + nStart = GetTime(); + + // Create new block + pblock = CreateNewBlock(pwalletMain); + if (!pblock) + throw JSONRPCError(-7, "Out of memory"); + vNewBlock.push_back(pblock); + } + + // Update nTime + pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + pblock->nNonce = 0; + + // Update nExtraNonce + static unsigned int nExtraNonce = 0; + IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); + + // Save + mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig); + + // Prebuild hash buffers + char pmidstate[32]; + char pdata[128]; + char phash1[64]; + FormatHashBuffers(pblock, pmidstate, pdata, phash1); + + uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); + + CTransaction coinbaseTx = pblock->vtx[0]; + std::vector merkle = pblock->GetMerkleBranch(0); + + Object result; + result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata)))); + result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget)))); + + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << coinbaseTx; + result.push_back(Pair("coinbase", HexStr(ssTx.begin(), ssTx.end()))); + + Array merkle_arr; + + BOOST_FOREACH(uint256 merkleh, merkle) { + merkle_arr.push_back(HexStr(BEGIN(merkleh), END(merkleh))); + } + + result.push_back(Pair("merkle", merkle_arr)); + + + return result; + } + else + { + // Parse parameters + vector vchData = ParseHex(params[0].get_str()); + vector coinbase; + + if(params.size() == 2) + coinbase = ParseHex(params[1].get_str()); + + if (vchData.size() != 128) + throw JSONRPCError(-8, "Invalid parameter"); + + CBlock* pdata = (CBlock*)&vchData[0]; + + // Byte reverse + for (int i = 0; i < 128/4; i++) + ((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]); + + // Get saved block + if (!mapNewBlock.count(pdata->hashMerkleRoot)) + return false; + CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first; + + pblock->nTime = pdata->nTime; + pblock->nNonce = pdata->nNonce; + + if(coinbase.size() == 0) + pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second; + else + CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - HACK! + + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + + if (!pblock->SignBlock(*pwalletMain)) + throw JSONRPCError(-100, "Unable to sign block, wallet locked?"); + + return CheckWork(pblock, *pwalletMain, reservekey); + } +} + + +Value getwork(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getwork [data]\n" + "If [data] is not specified, returns formatted hash data to work on:\n" + " \"midstate\" : precomputed hash state after hashing the first half of the data (DEPRECATED)\n" // deprecated + " \"data\" : block data\n" + " \"hash1\" : formatted hash buffer for second hash (DEPRECATED)\n" // deprecated + " \"target\" : little endian hash target\n" + "If [data] is specified, tries to solve the block and returns true if it was successful."); + + if (vNodes.empty()) + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "NovaCoin is not connected!"); + + if (IsInitialBlockDownload()) + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "NovaCoin is downloading blocks..."); + + typedef map > mapNewBlock_t; + static mapNewBlock_t mapNewBlock; // FIXME: thread safety + static vector vNewBlock; + static CReserveKey reservekey(pwalletMain); + + if (params.size() == 0) + { + // Update block + static unsigned int nTransactionsUpdatedLast; + static CBlockIndex* pindexPrev; + static int64 nStart; + static CBlock* pblock; + if (pindexPrev != pindexBest || + (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)) + { + if (pindexPrev != pindexBest) + { + // Deallocate old blocks since they're obsolete now + mapNewBlock.clear(); + BOOST_FOREACH(CBlock* pblock, vNewBlock) + delete pblock; + vNewBlock.clear(); + } + + // Clear pindexPrev so future getworks make a new block, despite any failures from here on + pindexPrev = NULL; + + // Store the pindexBest used before CreateNewBlock, to avoid races + nTransactionsUpdatedLast = nTransactionsUpdated; + CBlockIndex* pindexPrevNew = pindexBest; + nStart = GetTime(); + + // Create new block + pblock = CreateNewBlock(pwalletMain); + if (!pblock) + throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); + vNewBlock.push_back(pblock); + + // Need to update only after we know CreateNewBlock succeeded + pindexPrev = pindexPrevNew; + } + + // Update nTime + pblock->UpdateTime(pindexPrev); + pblock->nNonce = 0; + + // Update nExtraNonce + static unsigned int nExtraNonce = 0; + IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); + + // Save + mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig); + + // Pre-build hash buffers + char pmidstate[32]; + char pdata[128]; + char phash1[64]; + FormatHashBuffers(pblock, pmidstate, pdata, phash1); + + uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); + + Object result; + result.push_back(Pair("midstate", HexStr(BEGIN(pmidstate), END(pmidstate)))); // deprecated + result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata)))); + result.push_back(Pair("hash1", HexStr(BEGIN(phash1), END(phash1)))); // deprecated + result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget)))); + return result; + } + else + { + // Parse parameters + vector vchData = ParseHex(params[0].get_str()); + if (vchData.size() != 128) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); + CBlock* pdata = (CBlock*)&vchData[0]; + + // Byte reverse + for (int i = 0; i < 128/4; i++) + ((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]); + + // Get saved block + if (!mapNewBlock.count(pdata->hashMerkleRoot)) + return false; + CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first; + + pblock->nTime = pdata->nTime; + pblock->nNonce = pdata->nNonce; + pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second; + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + + if (!pblock->SignBlock(*pwalletMain)) + throw JSONRPCError(-100, "Unable to sign block, wallet locked?"); + + return CheckWork(pblock, *pwalletMain, reservekey); + } +} + + +Value getblocktemplate(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getblocktemplate [params]\n" + "Returns data needed to construct a block to work on:\n" + " \"version\" : block version\n" + " \"previousblockhash\" : hash of current highest block\n" + " \"transactions\" : contents of non-coinbase transactions that should be included in the next block\n" + " \"coinbaseaux\" : data that should be included in coinbase\n" + " \"coinbasevalue\" : maximum allowable input to coinbase transaction, including the generation award and transaction fees\n" + " \"target\" : hash target\n" + " \"mintime\" : minimum timestamp appropriate for next block\n" + " \"curtime\" : current timestamp\n" + " \"mutable\" : list of ways the block template may be changed\n" + " \"noncerange\" : range of valid nonces\n" + " \"sigoplimit\" : limit of sigops in blocks\n" + " \"sizelimit\" : limit of block size\n" + " \"bits\" : compressed target of next block\n" + " \"height\" : height of the next block\n" + "See https://en.bitcoin.it/wiki/BIP_0022 for full specification."); + + std::string strMode = "template"; + if (params.size() > 0) + { + const Object& oparam = params[0].get_obj(); + const Value& modeval = find_value(oparam, "mode"); + if (modeval.type() == str_type) + strMode = modeval.get_str(); + else if (modeval.type() == null_type) + { + /* Do nothing */ + } + else + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); + } + + if (strMode != "template") + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); + + if (vNodes.empty()) + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "NovaCoin is not connected!"); + + if (IsInitialBlockDownload()) + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "NovaCoin is downloading blocks..."); + + static CReserveKey reservekey(pwalletMain); + + // Update block + static unsigned int nTransactionsUpdatedLast; + static CBlockIndex* pindexPrev; + static int64 nStart; + static CBlock* pblock; + if (pindexPrev != pindexBest || + (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 5)) + { + // Clear pindexPrev so future calls make a new block, despite any failures from here on + pindexPrev = NULL; + + // Store the pindexBest used before CreateNewBlock, to avoid races + nTransactionsUpdatedLast = nTransactionsUpdated; + CBlockIndex* pindexPrevNew = pindexBest; + nStart = GetTime(); + + // Create new block + if(pblock) + { + delete pblock; + pblock = NULL; + } + pblock = CreateNewBlock(pwalletMain); + if (!pblock) + throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); + + // Need to update only after we know CreateNewBlock succeeded + pindexPrev = pindexPrevNew; + } + + // Update nTime + pblock->UpdateTime(pindexPrev); + pblock->nNonce = 0; + + Array transactions; + map setTxIndex; + int i = 0; + CTxDB txdb("r"); + BOOST_FOREACH (CTransaction& tx, pblock->vtx) + { + uint256 txHash = tx.GetHash(); + setTxIndex[txHash] = i++; + + if (tx.IsCoinBase() || tx.IsCoinStake()) + continue; + + Object entry; + + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << tx; + entry.push_back(Pair("data", HexStr(ssTx.begin(), ssTx.end()))); + + entry.push_back(Pair("hash", txHash.GetHex())); + + MapPrevTx mapInputs; + map mapUnused; + bool fInvalid = false; + if (tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid)) + { + entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(mapInputs) - tx.GetValueOut()))); + + Array deps; + BOOST_FOREACH (MapPrevTx::value_type& inp, mapInputs) + { + if (setTxIndex.count(inp.first)) + deps.push_back(setTxIndex[inp.first]); + } + entry.push_back(Pair("depends", deps)); + + int64_t nSigOps = tx.GetLegacySigOpCount(); + nSigOps += tx.GetP2SHSigOpCount(mapInputs); + entry.push_back(Pair("sigops", nSigOps)); + } + + transactions.push_back(entry); + } + + Object aux; + aux.push_back(Pair("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end()))); + + uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256(); + + static Array aMutable; + if (aMutable.empty()) + { + aMutable.push_back("time"); + aMutable.push_back("transactions"); + aMutable.push_back("prevblock"); + } + + Object result; + result.push_back(Pair("version", pblock->nVersion)); + result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex())); + result.push_back(Pair("transactions", transactions)); + result.push_back(Pair("coinbaseaux", aux)); + result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue)); + result.push_back(Pair("target", hashTarget.GetHex())); + result.push_back(Pair("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1)); + result.push_back(Pair("mutable", aMutable)); + result.push_back(Pair("noncerange", "00000000ffffffff")); + result.push_back(Pair("sigoplimit", (int64_t)MAX_BLOCK_SIGOPS)); + result.push_back(Pair("sizelimit", (int64_t)MAX_BLOCK_SIZE)); + result.push_back(Pair("curtime", (int64_t)pblock->nTime)); + result.push_back(Pair("bits", HexBits(pblock->nBits))); + result.push_back(Pair("height", (int64_t)(pindexPrev->nHeight+1))); + + return result; +} + +Value submitblock(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "submitblock [optional-params-obj]\n" + "[optional-params-obj] parameter is currently ignored.\n" + "Attempts to submit new block to network.\n" + "See https://en.bitcoin.it/wiki/BIP_0022 for full specification."); + + vector blockData(ParseHex(params[0].get_str())); + CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION); + CBlock block; + try { + ssBlock >> block; + } + catch (std::exception &e) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); + } + + if (!block.SignBlock(*pwalletMain)) + throw JSONRPCError(-100, "Unable to sign block, wallet locked?"); + + bool fAccepted = ProcessBlock(NULL, &block); + if (!fAccepted) + return "rejected"; + + return Value::null; +} + diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp new file mode 100644 index 0000000..04ea310 --- /dev/null +++ b/src/rpcnet.cpp @@ -0,0 +1,136 @@ +// Copyright (c) 2009-2012 Bitcoin Developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "net.h" +#include "bitcoinrpc.h" +#include "alert.h" +#include "wallet.h" +#include "db.h" +#include "walletdb.h" + +using namespace json_spirit; +using namespace std; + +Value getconnectioncount(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getconnectioncount\n" + "Returns the number of connections to other nodes."); + + LOCK(cs_vNodes); + return (int)vNodes.size(); +} + +static void CopyNodeStats(std::vector& vstats) +{ + vstats.clear(); + + LOCK(cs_vNodes); + vstats.reserve(vNodes.size()); + BOOST_FOREACH(CNode* pnode, vNodes) { + CNodeStats stats; + pnode->copyStats(stats); + vstats.push_back(stats); + } +} + +Value getpeerinfo(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getpeerinfo\n" + "Returns data about each connected network node."); + + vector vstats; + CopyNodeStats(vstats); + + Array ret; + + BOOST_FOREACH(const CNodeStats& stats, vstats) { + Object obj; + + obj.push_back(Pair("addr", stats.addrName)); + obj.push_back(Pair("services", strprintf("%08"PRI64x, stats.nServices))); + obj.push_back(Pair("lastsend", (boost::int64_t)stats.nLastSend)); + obj.push_back(Pair("lastrecv", (boost::int64_t)stats.nLastRecv)); + obj.push_back(Pair("conntime", (boost::int64_t)stats.nTimeConnected)); + obj.push_back(Pair("version", stats.nVersion)); + obj.push_back(Pair("subver", stats.strSubVer)); + obj.push_back(Pair("inbound", stats.fInbound)); + obj.push_back(Pair("releasetime", (boost::int64_t)stats.nReleaseTime)); + obj.push_back(Pair("startingheight", stats.nStartingHeight)); + obj.push_back(Pair("banscore", stats.nMisbehavior)); + + ret.push_back(obj); + } + + return ret; +} + +extern CCriticalSection cs_mapAlerts; +extern map mapAlerts; + +// ppcoin: send alert. +// There is a known deadlock situation with ThreadMessageHandler +// ThreadMessageHandler: holds cs_vSend and acquiring cs_main in SendMessages() +// ThreadRPCServer: holds cs_main and acquiring cs_vSend in alert.RelayTo()/PushMessage()/BeginMessage() +Value sendalert(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 6) + throw runtime_error( + "sendalert [cancelupto]\n" + " is the alert text message\n" + " is hex string of alert master private key\n" + " is the minimum applicable internal client version\n" + " is the maximum applicable internal client version\n" + " is integer priority number\n" + " is the alert id\n" + "[cancelupto] cancels all alert id's up to this number\n" + "Returns true or false."); + + CAlert alert; + CKey key; + + alert.strStatusBar = params[0].get_str(); + alert.nMinVer = params[2].get_int(); + alert.nMaxVer = params[3].get_int(); + alert.nPriority = params[4].get_int(); + alert.nID = params[5].get_int(); + if (params.size() > 6) + alert.nCancel = params[6].get_int(); + alert.nVersion = PROTOCOL_VERSION; + alert.nRelayUntil = GetAdjustedTime() + 365*24*60*60; + alert.nExpiration = GetAdjustedTime() + 365*24*60*60; + + CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION); + sMsg << (CUnsignedAlert)alert; + alert.vchMsg = vector(sMsg.begin(), sMsg.end()); + + vector vchPrivKey = ParseHex(params[1].get_str()); + key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash + if (!key.Sign(Hash(alert.vchMsg.begin(), alert.vchMsg.end()), alert.vchSig)) + throw runtime_error( + "Unable to sign alert, check private key?\n"); + if(!alert.ProcessAlert()) + throw runtime_error( + "Failed to process alert.\n"); + // Relay alert + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + alert.RelayTo(pnode); + } + + Object result; + result.push_back(Pair("strStatusBar", alert.strStatusBar)); + result.push_back(Pair("nVersion", alert.nVersion)); + result.push_back(Pair("nMinVer", alert.nMinVer)); + result.push_back(Pair("nMaxVer", alert.nMaxVer)); + result.push_back(Pair("nPriority", alert.nPriority)); + result.push_back(Pair("nID", alert.nID)); + if (alert.nCancel > 0) + result.push_back(Pair("nCancel", alert.nCancel)); + return result; +} diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp new file mode 100644 index 0000000..fdc5426 --- /dev/null +++ b/src/rpcrawtransaction.cpp @@ -0,0 +1,532 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include "base58.h" +#include "bitcoinrpc.h" +#include "db.h" +#include "init.h" +#include "main.h" +#include "net.h" +#include "wallet.h" + +using namespace std; +using namespace boost; +using namespace boost::assign; +using namespace json_spirit; + +void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out) +{ + txnouttype type; + vector addresses; + int nRequired; + + out.push_back(Pair("asm", scriptPubKey.ToString())); + out.push_back(Pair("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); + + if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) + { + out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD))); + return; + } + + out.push_back(Pair("reqSigs", nRequired)); + out.push_back(Pair("type", GetTxnOutputType(type))); + + Array a; + BOOST_FOREACH(const CTxDestination& addr, addresses) + a.push_back(CBitcoinAddress(addr).ToString()); + out.push_back(Pair("addresses", a)); +} + +void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) +{ + entry.push_back(Pair("txid", tx.GetHash().GetHex())); + entry.push_back(Pair("version", tx.nVersion)); + entry.push_back(Pair("time", (boost::int64_t)tx.nTime)); + entry.push_back(Pair("locktime", (boost::int64_t)tx.nLockTime)); + Array vin; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + Object in; + if (tx.IsCoinBase()) + in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + else + { + in.push_back(Pair("txid", txin.prevout.hash.GetHex())); + in.push_back(Pair("vout", (boost::int64_t)txin.prevout.n)); + Object o; + o.push_back(Pair("asm", txin.scriptSig.ToString())); + o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + in.push_back(Pair("scriptSig", o)); + } + in.push_back(Pair("sequence", (boost::int64_t)txin.nSequence)); + vin.push_back(in); + } + entry.push_back(Pair("vin", vin)); + Array vout; + for (unsigned int i = 0; i < tx.vout.size(); i++) + { + const CTxOut& txout = tx.vout[i]; + Object out; + out.push_back(Pair("value", ValueFromAmount(txout.nValue))); + out.push_back(Pair("n", (boost::int64_t)i)); + Object o; + ScriptPubKeyToJSON(txout.scriptPubKey, o); + out.push_back(Pair("scriptPubKey", o)); + vout.push_back(out); + } + entry.push_back(Pair("vout", vout)); + + if (hashBlock != 0) + { + entry.push_back(Pair("blockhash", hashBlock.GetHex())); + map::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) + { + CBlockIndex* pindex = (*mi).second; + if (pindex->IsInMainChain()) + { + entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight)); + entry.push_back(Pair("time", (boost::int64_t)pindex->nTime)); + entry.push_back(Pair("blocktime", (boost::int64_t)pindex->nTime)); + } + else + entry.push_back(Pair("confirmations", 0)); + } + } +} + +Value getrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getrawtransaction [verbose=0]\n" + "If verbose=0, returns a string that is\n" + "serialized, hex-encoded data for .\n" + "If verbose is non-zero, returns an Object\n" + "with information about ."); + + uint256 hash; + hash.SetHex(params[0].get_str()); + + bool fVerbose = false; + if (params.size() > 1) + fVerbose = (params[1].get_int() != 0); + + CTransaction tx; + uint256 hashBlock = 0; + if (!GetTransaction(hash, tx, hashBlock)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); + + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << tx; + string strHex = HexStr(ssTx.begin(), ssTx.end()); + + if (!fVerbose) + return strHex; + + Object result; + result.push_back(Pair("hex", strHex)); + TxToJSON(tx, hashBlock, result); + return result; +} + +Value listunspent(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 3) + throw runtime_error( + "listunspent [minconf=1] [maxconf=9999999] [\"address\",...]\n" + "Returns array of unspent transaction outputs\n" + "with between minconf and maxconf (inclusive) confirmations.\n" + "Optionally filtered to only include txouts paid to specified addresses.\n" + "Results are an array of Objects, each of which has:\n" + "{txid, vout, scriptPubKey, amount, confirmations}"); + + RPCTypeCheck(params, list_of(int_type)(int_type)(array_type)); + + int nMinDepth = 1; + if (params.size() > 0) + nMinDepth = params[0].get_int(); + + int nMaxDepth = 9999999; + if (params.size() > 1) + nMaxDepth = params[1].get_int(); + + set setAddress; + if (params.size() > 2) + { + Array inputs = params[2].get_array(); + BOOST_FOREACH(Value& input, inputs) + { + CBitcoinAddress address(input.get_str()); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid NovaCoin address: ")+input.get_str()); + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+input.get_str()); + setAddress.insert(address); + } + } + + Array results; + vector vecOutputs; + pwalletMain->AvailableCoins(vecOutputs, false); + BOOST_FOREACH(const COutput& out, vecOutputs) + { + if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) + continue; + + if(setAddress.size()) + { + CTxDestination address; + if(!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) + continue; + + if (!setAddress.count(address)) + continue; + } + + int64 nValue = out.tx->vout[out.i].nValue; + const CScript& pk = out.tx->vout[out.i].scriptPubKey; + Object entry; + entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); + entry.push_back(Pair("vout", out.i)); + entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end()))); + entry.push_back(Pair("amount",ValueFromAmount(nValue))); + entry.push_back(Pair("confirmations",out.nDepth)); + results.push_back(entry); + } + + return results; +} + +Value createrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error( + "createrawtransaction [{\"txid\":txid,\"vout\":n},...] {address:amount,...}\n" + "Create a transaction spending given inputs\n" + "(array of objects containing transaction id and output number),\n" + "sending to given address(es).\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."); + + RPCTypeCheck(params, list_of(array_type)(obj_type)); + + Array inputs = params[0].get_array(); + Object sendTo = params[1].get_obj(); + + CTransaction rawTx; + + BOOST_FOREACH(Value& input, inputs) + { + const Object& o = input.get_obj(); + + const Value& txid_v = find_value(o, "txid"); + if (txid_v.type() != str_type) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing txid key"); + string txid = txid_v.get_str(); + if (!IsHex(txid)) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); + + const Value& vout_v = find_value(o, "vout"); + if (vout_v.type() != int_type) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); + int nOutput = vout_v.get_int(); + if (nOutput < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); + + CTxIn in(COutPoint(uint256(txid), nOutput)); + rawTx.vin.push_back(in); + } + + set setAddress; + BOOST_FOREACH(const Pair& s, sendTo) + { + CBitcoinAddress address(s.name_); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid NovaCoin address: ")+s.name_); + + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+s.name_); + setAddress.insert(address); + + CScript scriptPubKey; + scriptPubKey.SetDestination(address.Get()); + int64 nAmount = AmountFromValue(s.value_); + + CTxOut out(nAmount, scriptPubKey); + rawTx.vout.push_back(out); + } + + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << rawTx; + return HexStr(ss.begin(), ss.end()); +} + +Value decoderawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "decoderawtransaction \n" + "Return a JSON object representing the serialized, hex-encoded transaction."); + + RPCTypeCheck(params, list_of(str_type)); + + vector txData(ParseHex(params[0].get_str())); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + try { + ssData >> tx; + } + catch (std::exception &e) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + Object result; + TxToJSON(tx, 0, result); + + return result; +} + +Value signrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 4) + throw runtime_error( + "signrawtransaction [{\"txid\":txid,\"vout\":n,\"scriptPubKey\":hex},...] [,...] [sighashtype=\"ALL\"]\n" + "Sign inputs for raw transaction (serialized, hex-encoded).\n" + "Second optional argument (may be null) is an array of previous transaction outputs that\n" + "this transaction depends on but may not yet be in the blockchain.\n" + "Third optional argument (may be null) is an array of base58-encoded private\n" + "keys that, if given, will be the only keys used to sign the transaction.\n" + "Fourth optional argument is a string that is one of six values; ALL, NONE, SINGLE or\n" + "ALL|ANYONECANPAY, NONE|ANYONECANPAY, SINGLE|ANYONECANPAY.\n" + "Returns json object with keys:\n" + " hex : raw transaction with signature(s) (hex-encoded string)\n" + " complete : 1 if transaction has a complete set of signature (0 if not)" + + HelpRequiringPassphrase()); + + RPCTypeCheck(params, list_of(str_type)(array_type)(array_type)(str_type), true); + + vector txData(ParseHex(params[0].get_str())); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + vector txVariants; + while (!ssData.empty()) + { + try { + CTransaction tx; + ssData >> tx; + txVariants.push_back(tx); + } + catch (std::exception &e) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + } + + if (txVariants.empty()) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transaction"); + + // mergedTx will end up with all the signatures; it + // starts as a clone of the rawtx: + CTransaction mergedTx(txVariants[0]); + bool fComplete = true; + + // Fetch previous transactions (inputs): + map mapPrevOut; + for (unsigned int i = 0; i < mergedTx.vin.size(); i++) + { + CTransaction tempTx; + MapPrevTx mapPrevTx; + CTxDB txdb("r"); + map unused; + bool fInvalid; + + // FetchInputs aborts on failure, so we go one at a time. + tempTx.vin.push_back(mergedTx.vin[i]); + tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid); + + // Copy results into mapPrevOut: + BOOST_FOREACH(const CTxIn& txin, tempTx.vin) + { + const uint256& prevHash = txin.prevout.hash; + if (mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size()>txin.prevout.n) + mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey; + } + } + + // Add previous txouts given in the RPC call: + if (params.size() > 1 && params[1].type() != null_type) + { + Array prevTxs = params[1].get_array(); + BOOST_FOREACH(Value& p, prevTxs) + { + if (p.type() != obj_type) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}"); + + Object prevOut = p.get_obj(); + + RPCTypeCheck(prevOut, map_list_of("txid", str_type)("vout", int_type)("scriptPubKey", str_type)); + + string txidHex = find_value(prevOut, "txid").get_str(); + if (!IsHex(txidHex)) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "txid must be hexadecimal"); + uint256 txid; + txid.SetHex(txidHex); + + int nOut = find_value(prevOut, "vout").get_int(); + if (nOut < 0) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive"); + + string pkHex = find_value(prevOut, "scriptPubKey").get_str(); + if (!IsHex(pkHex)) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "scriptPubKey must be hexadecimal"); + vector pkData(ParseHex(pkHex)); + CScript scriptPubKey(pkData.begin(), pkData.end()); + + COutPoint outpoint(txid, nOut); + if (mapPrevOut.count(outpoint)) + { + // Complain if scriptPubKey doesn't match + if (mapPrevOut[outpoint] != scriptPubKey) + { + string err("Previous output scriptPubKey mismatch:\n"); + err = err + mapPrevOut[outpoint].ToString() + "\nvs:\n"+ + scriptPubKey.ToString(); + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err); + } + } + else + mapPrevOut[outpoint] = scriptPubKey; + } + } + + bool fGivenKeys = false; + CBasicKeyStore tempKeystore; + if (params.size() > 2 && params[2].type() != null_type) + { + fGivenKeys = true; + Array keys = params[2].get_array(); + BOOST_FOREACH(Value k, keys) + { + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(k.get_str()); + if (!fGood) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,"Invalid private key"); + CKey key; + bool fCompressed; + CSecret secret = vchSecret.GetSecret(fCompressed); + key.SetSecret(secret, fCompressed); + tempKeystore.AddKey(key); + } + } + else + EnsureWalletIsUnlocked(); + + const CKeyStore& keystore = (fGivenKeys ? tempKeystore : *pwalletMain); + + int nHashType = SIGHASH_ALL; + if (params.size() > 3 && params[3].type() != null_type) + { + static map mapSigHashValues = + boost::assign::map_list_of + (string("ALL"), int(SIGHASH_ALL)) + (string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)) + (string("NONE"), int(SIGHASH_NONE)) + (string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)) + (string("SINGLE"), int(SIGHASH_SINGLE)) + (string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)) + ; + string strHashType = params[3].get_str(); + if (mapSigHashValues.count(strHashType)) + nHashType = mapSigHashValues[strHashType]; + else + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); + } + + bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); + + // Sign what we can: + for (unsigned int i = 0; i < mergedTx.vin.size(); i++) + { + CTxIn& txin = mergedTx.vin[i]; + if (mapPrevOut.count(txin.prevout) == 0) + { + fComplete = false; + continue; + } + const CScript& prevPubKey = mapPrevOut[txin.prevout]; + + txin.scriptSig.clear(); + // Only sign SIGHASH_SINGLE if there's a corresponding output: + if (!fHashSingle || (i < mergedTx.vout.size())) + SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); + + // ... and merge in other signatures: + BOOST_FOREACH(const CTransaction& txv, txVariants) + { + txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); + } + if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, true, 0)) + fComplete = false; + } + + Object result; + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << mergedTx; + result.push_back(Pair("hex", HexStr(ssTx.begin(), ssTx.end()))); + result.push_back(Pair("complete", fComplete)); + + return result; +} + +Value sendrawtransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 1) + throw runtime_error( + "sendrawtransaction \n" + "Submits raw transaction (serialized, hex-encoded) to local node and network."); + + RPCTypeCheck(params, list_of(str_type)); + + // parse hex string from parameter + vector txData(ParseHex(params[0].get_str())); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + + // deserialize binary data stream + try { + ssData >> tx; + } + catch (std::exception &e) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + uint256 hashTx = tx.GetHash(); + + // See if the transaction is already in a block + // or in the memory pool: + CTransaction existingTx; + uint256 hashBlock = 0; + if (GetTransaction(hashTx, existingTx, hashBlock)) + { + if (hashBlock != 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("transaction already in block ")+hashBlock.GetHex()); + // Not in block, but already in the memory pool; will drop + // through to re-relay it. + } + else + { + // push to local node + CTxDB txdb("r"); + if (!tx.AcceptToMemoryPool(txdb)) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected"); + + SyncWithWallets(tx, NULL, true); + } + RelayMessage(CInv(MSG_TX, hashTx), tx); + + return hashTx.GetHex(); +} diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp new file mode 100644 index 0000000..475f029 --- /dev/null +++ b/src/rpcwallet.cpp @@ -0,0 +1,1677 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "wallet.h" +#include "walletdb.h" +#include "bitcoinrpc.h" +#include "init.h" +#include "base58.h" + +using namespace json_spirit; +using namespace std; + +int64 nWalletUnlockTime; +static CCriticalSection cs_nWalletUnlockTime; + +extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, json_spirit::Object& entry); + +std::string HelpRequiringPassphrase() +{ + return pwalletMain->IsCrypted() + ? "\nrequires wallet passphrase to be set with walletpassphrase first" + : ""; +} + +void EnsureWalletIsUnlocked() +{ + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + if (fWalletUnlockMintOnly) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet unlocked for block minting only."); +} + +void WalletTxToJSON(const CWalletTx& wtx, Object& entry) +{ + int confirms = wtx.GetDepthInMainChain(); + entry.push_back(Pair("confirmations", confirms)); + if (wtx.IsCoinBase() || wtx.IsCoinStake()) + entry.push_back(Pair("generated", true)); + if (confirms) + { + entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex())); + entry.push_back(Pair("blockindex", wtx.nIndex)); + entry.push_back(Pair("blocktime", (boost::int64_t)(mapBlockIndex[wtx.hashBlock]->nTime))); + } + entry.push_back(Pair("txid", wtx.GetHash().GetHex())); + entry.push_back(Pair("time", (boost::int64_t)wtx.GetTxTime())); + entry.push_back(Pair("timereceived", (boost::int64_t)wtx.nTimeReceived)); + BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) + entry.push_back(Pair(item.first, item.second)); +} + +string AccountFromValue(const Value& value) +{ + string strAccount = value.get_str(); + if (strAccount == "*") + throw JSONRPCError(RPC_WALLET_INVALID_ACCOUNT_NAME, "Invalid account name"); + return strAccount; +} + +Value getinfo(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getinfo\n" + "Returns an object containing various state info."); + + proxyType proxy; + GetProxy(NET_IPV4, proxy); + + Object obj; + obj.push_back(Pair("version", FormatFullVersion())); + obj.push_back(Pair("protocolversion",(int)PROTOCOL_VERSION)); + obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); + obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); + obj.push_back(Pair("newmint", ValueFromAmount(pwalletMain->GetNewMint()))); + obj.push_back(Pair("stake", ValueFromAmount(pwalletMain->GetStake()))); + obj.push_back(Pair("blocks", (int)nBestHeight)); + obj.push_back(Pair("moneysupply", ValueFromAmount(pindexBest->nMoneySupply))); + obj.push_back(Pair("connections", (int)vNodes.size())); + obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string()))); + obj.push_back(Pair("ip", addrSeenByPeer.ToStringIP())); + obj.push_back(Pair("difficulty", (double)GetDifficulty())); + obj.push_back(Pair("testnet", fTestNet)); + obj.push_back(Pair("keypoololdest", (boost::int64_t)pwalletMain->GetOldestKeyPoolTime())); + obj.push_back(Pair("keypoolsize", pwalletMain->GetKeyPoolSize())); + obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee))); + if (pwalletMain->IsCrypted()) + obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime / 1000)); + obj.push_back(Pair("errors", GetWarnings("statusbar"))); + return obj; +} + + + +Value getnewaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getnewaddress [account]\n" + "Returns a new NovaCoin address for receiving payments. " + "If [account] is specified (recommended), it is added to the address book " + "so payments received with the address will be credited to [account]."); + + // Parse the account first so we don't generate a key if there's an error + string strAccount; + if (params.size() > 0) + strAccount = AccountFromValue(params[0]); + + if (!pwalletMain->IsLocked()) + pwalletMain->TopUpKeyPool(); + + // Generate a new key that is added to wallet + CPubKey newKey; + if (!pwalletMain->GetKeyFromPool(newKey, false)) + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + CKeyID keyID = newKey.GetID(); + + pwalletMain->SetAddressBookName(keyID, strAccount); + + return CBitcoinAddress(keyID).ToString(); +} + + +CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) +{ + CWalletDB walletdb(pwalletMain->strWalletFile); + + CAccount account; + walletdb.ReadAccount(strAccount, account); + + bool bKeyUsed = false; + + // Check if the current key has been used + if (account.vchPubKey.IsValid()) + { + CScript scriptPubKey; + scriptPubKey.SetDestination(account.vchPubKey.GetID()); + for (map::iterator it = pwalletMain->mapWallet.begin(); + it != pwalletMain->mapWallet.end() && account.vchPubKey.IsValid(); + ++it) + { + const CWalletTx& wtx = (*it).second; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if (txout.scriptPubKey == scriptPubKey) + bKeyUsed = true; + } + } + + // Generate a new key + if (!account.vchPubKey.IsValid() || bForceNew || bKeyUsed) + { + if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false)) + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + + pwalletMain->SetAddressBookName(account.vchPubKey.GetID(), strAccount); + walletdb.WriteAccount(strAccount, account); + } + + return CBitcoinAddress(account.vchPubKey.GetID()); +} + +Value getaccountaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaccountaddress \n" + "Returns the current NovaCoin address for receiving payments to this account."); + + // Parse the account first so we don't generate a key if there's an error + string strAccount = AccountFromValue(params[0]); + + Value ret; + + ret = GetAccountAddress(strAccount).ToString(); + + return ret; +} + + + +Value setaccount(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "setaccount \n" + "Sets the account associated with the given address."); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid NovaCoin address"); + + + string strAccount; + if (params.size() > 1) + strAccount = AccountFromValue(params[1]); + + // Detect when changing the account of an address that is the 'unused current key' of another account: + if (pwalletMain->mapAddressBook.count(address.Get())) + { + string strOldAccount = pwalletMain->mapAddressBook[address.Get()]; + if (address == GetAccountAddress(strOldAccount)) + GetAccountAddress(strOldAccount, true); + } + + pwalletMain->SetAddressBookName(address.Get(), strAccount); + + return Value::null; +} + + +Value getaccount(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaccount \n" + "Returns the account associated with the given address."); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid NovaCoin address"); + + string strAccount; + map::iterator mi = pwalletMain->mapAddressBook.find(address.Get()); + if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.empty()) + strAccount = (*mi).second; + return strAccount; +} + + +Value getaddressesbyaccount(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getaddressesbyaccount \n" + "Returns the list of addresses for the given account."); + + string strAccount = AccountFromValue(params[0]); + + // Find all addresses that have the given account + Array ret; + BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) + { + const CBitcoinAddress& address = item.first; + const string& strName = item.second; + if (strName == strAccount) + ret.push_back(address.ToString()); + } + return ret; +} + +Value sendtoaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 4) + throw runtime_error( + "sendtoaddress [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001" + + HelpRequiringPassphrase()); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid NovaCoin address"); + + // Amount + int64 nAmount = AmountFromValue(params[1]); + + if (nAmount < MIN_TXOUT_AMOUNT) + throw JSONRPCError(-101, "Send amount too small"); + + // Wallet comments + CWalletTx wtx; + if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty()) + wtx.mapValue["comment"] = params[2].get_str(); + if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) + wtx.mapValue["to"] = params[3].get_str(); + + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + string strError = pwalletMain->SendMoneyToDestination(address.Get(), nAmount, wtx); + if (strError != "") + throw JSONRPCError(RPC_WALLET_ERROR, strError); + + return wtx.GetHash().GetHex(); +} + +Value listaddressgroupings(const Array& params, bool fHelp) +{ + if (fHelp) + throw runtime_error( + "listaddressgroupings\n" + "Lists groups of addresses which have had their common ownership\n" + "made public by common use as inputs or as the resulting change\n" + "in past transactions"); + + Array jsonGroupings; + map balances = pwalletMain->GetAddressBalances(); + BOOST_FOREACH(set grouping, pwalletMain->GetAddressGroupings()) + { + Array jsonGrouping; + BOOST_FOREACH(CTxDestination address, grouping) + { + Array addressInfo; + addressInfo.push_back(CBitcoinAddress(address).ToString()); + addressInfo.push_back(ValueFromAmount(balances[address])); + { + LOCK(pwalletMain->cs_wallet); + if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end()) + addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second); + } + jsonGrouping.push_back(addressInfo); + } + jsonGroupings.push_back(jsonGrouping); + } + return jsonGroupings; +} + +Value signmessage(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error( + "signmessage \n" + "Sign a message with the private key of an address"); + + EnsureWalletIsUnlocked(); + + string strAddress = params[0].get_str(); + string strMessage = params[1].get_str(); + + CBitcoinAddress addr(strAddress); + if (!addr.IsValid()) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + + CKeyID keyID; + if (!addr.GetKeyID(keyID)) + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); + + CKey key; + if (!pwalletMain->GetKey(keyID, key)) + throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); + + CDataStream ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << strMessage; + + vector vchSig; + if (!key.SignCompact(Hash(ss.begin(), ss.end()), vchSig)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); + + return EncodeBase64(&vchSig[0], vchSig.size()); +} + +Value verifymessage(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 3) + throw runtime_error( + "verifymessage \n" + "Verify a signed message"); + + string strAddress = params[0].get_str(); + string strSign = params[1].get_str(); + string strMessage = params[2].get_str(); + + CBitcoinAddress addr(strAddress); + if (!addr.IsValid()) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + + CKeyID keyID; + if (!addr.GetKeyID(keyID)) + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); + + bool fInvalid = false; + vector vchSig = DecodeBase64(strSign.c_str(), &fInvalid); + + if (fInvalid) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); + + CDataStream ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << strMessage; + + CKey key; + if (!key.SetCompactSignature(Hash(ss.begin(), ss.end()), vchSig)) + return false; + + return (key.GetPubKey().GetID() == keyID); +} + + +Value getreceivedbyaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getreceivedbyaddress [minconf=1]\n" + "Returns the total amount received by in transactions with at least [minconf] confirmations."); + + // Bitcoin address + CBitcoinAddress address = CBitcoinAddress(params[0].get_str()); + CScript scriptPubKey; + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid NovaCoin address"); + scriptPubKey.SetDestination(address.Get()); + if (!IsMine(*pwalletMain,scriptPubKey)) + return (double)0.0; + + // Minimum confirmations + int nMinDepth = 1; + if (params.size() > 1) + nMinDepth = params[1].get_int(); + + // Tally + int64 nAmount = 0; + for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + { + const CWalletTx& wtx = (*it).second; + if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) + continue; + + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if (txout.scriptPubKey == scriptPubKey) + if (wtx.GetDepthInMainChain() >= nMinDepth) + nAmount += txout.nValue; + } + + return ValueFromAmount(nAmount); +} + + +void GetAccountAddresses(string strAccount, set& setAddress) +{ + BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& item, pwalletMain->mapAddressBook) + { + const CTxDestination& address = item.first; + const string& strName = item.second; + if (strName == strAccount) + setAddress.insert(address); + } +} + +Value getreceivedbyaccount(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getreceivedbyaccount [minconf=1]\n" + "Returns the total amount received by addresses with in transactions with at least [minconf] confirmations."); + + // Minimum confirmations + int nMinDepth = 1; + if (params.size() > 1) + nMinDepth = params[1].get_int(); + + // Get the set of pub keys assigned to account + string strAccount = AccountFromValue(params[0]); + set setAddress; + GetAccountAddresses(strAccount, setAddress); + + // Tally + int64 nAmount = 0; + for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + { + const CWalletTx& wtx = (*it).second; + if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) + continue; + + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + { + CTxDestination address; + if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address)) + if (wtx.GetDepthInMainChain() >= nMinDepth) + nAmount += txout.nValue; + } + } + + return (double)nAmount / (double)COIN; +} + + +int64 GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMinDepth) +{ + int64 nBalance = 0; + + // Tally wallet transactions + for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + { + const CWalletTx& wtx = (*it).second; + if (!wtx.IsFinal()) + continue; + + int64 nGenerated, nReceived, nSent, nFee; + wtx.GetAccountAmounts(strAccount, nGenerated, nReceived, nSent, nFee); + + if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth) + nBalance += nReceived; + nBalance += nGenerated - nSent - nFee; + } + + // Tally internal accounting entries + nBalance += walletdb.GetAccountCreditDebit(strAccount); + + return nBalance; +} + +int64 GetAccountBalance(const string& strAccount, int nMinDepth) +{ + CWalletDB walletdb(pwalletMain->strWalletFile); + return GetAccountBalance(walletdb, strAccount, nMinDepth); +} + + +Value getbalance(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "getbalance [account] [minconf=1]\n" + "If [account] is not specified, returns the server's total available balance.\n" + "If [account] is specified, returns the balance in the account."); + + if (params.size() == 0) + return ValueFromAmount(pwalletMain->GetBalance()); + + int nMinDepth = 1; + if (params.size() > 1) + nMinDepth = params[1].get_int(); + + if (params[0].get_str() == "*") { + // Calculate total balance a different way from GetBalance() + // (GetBalance() sums up all unspent TxOuts) + // getbalance and getbalance '*' should always return the same number. + int64 nBalance = 0; + for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + { + const CWalletTx& wtx = (*it).second; + if (!wtx.IsFinal()) + continue; + + int64 allGeneratedImmature, allGeneratedMature, allFee; + allGeneratedImmature = allGeneratedMature = allFee = 0; + + string strSentAccount; + list > listReceived; + list > listSent; + wtx.GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount); + if (wtx.GetDepthInMainChain() >= nMinDepth) + { + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listReceived) + nBalance += r.second; + } + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listSent) + nBalance -= r.second; + nBalance -= allFee; + nBalance += allGeneratedMature; + } + return ValueFromAmount(nBalance); + } + + string strAccount = AccountFromValue(params[0]); + + int64 nBalance = GetAccountBalance(strAccount, nMinDepth); + + return ValueFromAmount(nBalance); +} + + +Value movecmd(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 3 || params.size() > 5) + throw runtime_error( + "move [minconf=1] [comment]\n" + "Move from one account in your wallet to another."); + + string strFrom = AccountFromValue(params[0]); + string strTo = AccountFromValue(params[1]); + int64 nAmount = AmountFromValue(params[2]); + + if (nAmount < MIN_TXOUT_AMOUNT) + throw JSONRPCError(-101, "Send amount too small"); + + if (params.size() > 3) + // unused parameter, used to be nMinDepth, keep type-checking it though + (void)params[3].get_int(); + string strComment; + if (params.size() > 4) + strComment = params[4].get_str(); + + CWalletDB walletdb(pwalletMain->strWalletFile); + if (!walletdb.TxnBegin()) + throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); + + int64 nNow = GetAdjustedTime(); + + // Debit + CAccountingEntry debit; + debit.nOrderPos = pwalletMain->IncOrderPosNext(&walletdb); + debit.strAccount = strFrom; + debit.nCreditDebit = -nAmount; + debit.nTime = nNow; + debit.strOtherAccount = strTo; + debit.strComment = strComment; + walletdb.WriteAccountingEntry(debit); + + // Credit + CAccountingEntry credit; + credit.nOrderPos = pwalletMain->IncOrderPosNext(&walletdb); + credit.strAccount = strTo; + credit.nCreditDebit = nAmount; + credit.nTime = nNow; + credit.strOtherAccount = strFrom; + credit.strComment = strComment; + walletdb.WriteAccountingEntry(credit); + + if (!walletdb.TxnCommit()) + throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); + + return true; +} + + +Value sendfrom(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 3 || params.size() > 6) + throw runtime_error( + "sendfrom [minconf=1] [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001" + + HelpRequiringPassphrase()); + + string strAccount = AccountFromValue(params[0]); + CBitcoinAddress address(params[1].get_str()); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid NovaCoin address"); + int64 nAmount = AmountFromValue(params[2]); + + if (nAmount < MIN_TXOUT_AMOUNT) + throw JSONRPCError(-101, "Send amount too small"); + + int nMinDepth = 1; + if (params.size() > 3) + nMinDepth = params[3].get_int(); + + CWalletTx wtx; + wtx.strFromAccount = strAccount; + if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty()) + wtx.mapValue["comment"] = params[4].get_str(); + if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty()) + wtx.mapValue["to"] = params[5].get_str(); + + EnsureWalletIsUnlocked(); + + // Check funds + int64 nBalance = GetAccountBalance(strAccount, nMinDepth); + if (nAmount > nBalance) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); + + // Send + string strError = pwalletMain->SendMoneyToDestination(address.Get(), nAmount, wtx); + if (strError != "") + throw JSONRPCError(RPC_WALLET_ERROR, strError); + + return wtx.GetHash().GetHex(); +} + + +Value sendmany(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 4) + throw runtime_error( + "sendmany {address:amount,...} [minconf=1] [comment]\n" + "amounts are double-precision floating point numbers" + + HelpRequiringPassphrase()); + + string strAccount = AccountFromValue(params[0]); + Object sendTo = params[1].get_obj(); + int nMinDepth = 1; + if (params.size() > 2) + nMinDepth = params[2].get_int(); + + CWalletTx wtx; + wtx.strFromAccount = strAccount; + if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) + wtx.mapValue["comment"] = params[3].get_str(); + + set setAddress; + vector > vecSend; + + int64 totalAmount = 0; + BOOST_FOREACH(const Pair& s, sendTo) + { + CBitcoinAddress address(s.name_); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid NovaCoin address: ")+s.name_); + + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+s.name_); + setAddress.insert(address); + + CScript scriptPubKey; + scriptPubKey.SetDestination(address.Get()); + int64 nAmount = AmountFromValue(s.value_); + + if (nAmount < MIN_TXOUT_AMOUNT) + throw JSONRPCError(-101, "Send amount too small"); + + totalAmount += nAmount; + + vecSend.push_back(make_pair(scriptPubKey, nAmount)); + } + + EnsureWalletIsUnlocked(); + + // Check funds + int64 nBalance = GetAccountBalance(strAccount, nMinDepth); + if (totalAmount > nBalance) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); + + // Send + CReserveKey keyChange(pwalletMain); + int64 nFeeRequired = 0; + bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired); + if (!fCreated) + { + if (totalAmount + nFeeRequired > pwalletMain->GetBalance()) + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction creation failed"); + } + if (!pwalletMain->CommitTransaction(wtx, keyChange)) + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction commit failed"); + + return wtx.GetHash().GetHex(); +} + +Value addmultisigaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 3) + { + string msg = "addmultisigaddress <'[\"key\",\"key\"]'> [account]\n" + "Add a nrequired-to-sign multisignature address to the wallet\"\n" + "each key is a NovaCoin address or hex-encoded public key\n" + "If [account] is specified, assign address to [account]."; + throw runtime_error(msg); + } + + int nRequired = params[0].get_int(); + const Array& keys = params[1].get_array(); + string strAccount; + if (params.size() > 2) + strAccount = AccountFromValue(params[2]); + + // Gather public keys + if (nRequired < 1) + throw runtime_error("a multisignature address must require at least one key to redeem"); + if ((int)keys.size() < nRequired) + throw runtime_error( + strprintf("not enough keys supplied " + "(got %"PRIszu" keys, but need at least %d to redeem)", keys.size(), nRequired)); + std::vector pubkeys; + pubkeys.resize(keys.size()); + for (unsigned int i = 0; i < keys.size(); i++) + { + const std::string& ks = keys[i].get_str(); + + // Case 1: Bitcoin address and we have full public key: + CBitcoinAddress address(ks); + if (address.IsValid()) + { + CKeyID keyID; + if (!address.GetKeyID(keyID)) + throw runtime_error( + strprintf("%s does not refer to a key",ks.c_str())); + CPubKey vchPubKey; + if (!pwalletMain->GetPubKey(keyID, vchPubKey)) + throw runtime_error( + strprintf("no full public key for address %s",ks.c_str())); + if (!vchPubKey.IsValid() || !pubkeys[i].SetPubKey(vchPubKey)) + throw runtime_error(" Invalid public key: "+ks); + } + + // Case 2: hex public key + else if (IsHex(ks)) + { + CPubKey vchPubKey(ParseHex(ks)); + if (!vchPubKey.IsValid() || !pubkeys[i].SetPubKey(vchPubKey)) + throw runtime_error(" Invalid public key: "+ks); + } + else + { + throw runtime_error(" Invalid public key: "+ks); + } + } + + // Construct using pay-to-script-hash: + CScript inner; + inner.SetMultisig(nRequired, pubkeys); + CScriptID innerID = inner.GetID(); + pwalletMain->AddCScript(inner); + + pwalletMain->SetAddressBookName(innerID, strAccount); + return CBitcoinAddress(innerID).ToString(); +} + + +struct tallyitem +{ + int64 nAmount; + int nConf; + tallyitem() + { + nAmount = 0; + nConf = std::numeric_limits::max(); + } +}; + +Value ListReceived(const Array& params, bool fByAccounts) +{ + // Minimum confirmations + int nMinDepth = 1; + if (params.size() > 0) + nMinDepth = params[0].get_int(); + + // Whether to include empty accounts + bool fIncludeEmpty = false; + if (params.size() > 1) + fIncludeEmpty = params[1].get_bool(); + + // Tally + map mapTally; + for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + { + const CWalletTx& wtx = (*it).second; + + if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wtx.IsFinal()) + continue; + + int nDepth = wtx.GetDepthInMainChain(); + if (nDepth < nMinDepth) + continue; + + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + { + CTxDestination address; + if (!ExtractDestination(txout.scriptPubKey, address) || !IsMine(*pwalletMain, address)) + continue; + + tallyitem& item = mapTally[address]; + item.nAmount += txout.nValue; + item.nConf = min(item.nConf, nDepth); + } + } + + // Reply + Array ret; + map mapAccountTally; + BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) + { + const CBitcoinAddress& address = item.first; + const string& strAccount = item.second; + map::iterator it = mapTally.find(address); + if (it == mapTally.end() && !fIncludeEmpty) + continue; + + int64 nAmount = 0; + int nConf = std::numeric_limits::max(); + if (it != mapTally.end()) + { + nAmount = (*it).second.nAmount; + nConf = (*it).second.nConf; + } + + if (fByAccounts) + { + tallyitem& item = mapAccountTally[strAccount]; + item.nAmount += nAmount; + item.nConf = min(item.nConf, nConf); + } + else + { + Object obj; + obj.push_back(Pair("address", address.ToString())); + obj.push_back(Pair("account", strAccount)); + obj.push_back(Pair("amount", ValueFromAmount(nAmount))); + obj.push_back(Pair("confirmations", (nConf == std::numeric_limits::max() ? 0 : nConf))); + ret.push_back(obj); + } + } + + if (fByAccounts) + { + for (map::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it) + { + int64 nAmount = (*it).second.nAmount; + int nConf = (*it).second.nConf; + Object obj; + obj.push_back(Pair("account", (*it).first)); + obj.push_back(Pair("amount", ValueFromAmount(nAmount))); + obj.push_back(Pair("confirmations", (nConf == std::numeric_limits::max() ? 0 : nConf))); + ret.push_back(obj); + } + } + + return ret; +} + +Value listreceivedbyaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "listreceivedbyaddress [minconf=1] [includeempty=false]\n" + "[minconf] is the minimum number of confirmations before payments are included.\n" + "[includeempty] whether to include addresses that haven't received any payments.\n" + "Returns an array of objects containing:\n" + " \"address\" : receiving address\n" + " \"account\" : the account of the receiving address\n" + " \"amount\" : total amount received by the address\n" + " \"confirmations\" : number of confirmations of the most recent transaction included"); + + return ListReceived(params, false); +} + +Value listreceivedbyaccount(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "listreceivedbyaccount [minconf=1] [includeempty=false]\n" + "[minconf] is the minimum number of confirmations before payments are included.\n" + "[includeempty] whether to include accounts that haven't received any payments.\n" + "Returns an array of objects containing:\n" + " \"account\" : the account of the receiving addresses\n" + " \"amount\" : total amount received by addresses with this account\n" + " \"confirmations\" : number of confirmations of the most recent transaction included"); + + return ListReceived(params, true); +} + +void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, Array& ret) +{ + int64 nGeneratedImmature, nGeneratedMature, nFee; + string strSentAccount; + list > listReceived; + list > listSent; + + wtx.GetAmounts(nGeneratedImmature, nGeneratedMature, listReceived, listSent, nFee, strSentAccount); + + bool fAllAccounts = (strAccount == string("*")); + + // Generated blocks assigned to account "" + if ((nGeneratedMature+nGeneratedImmature) != 0 && (fAllAccounts || strAccount == "")) + { + Object entry; + entry.push_back(Pair("account", string(""))); + if (nGeneratedImmature) + { + entry.push_back(Pair("category", wtx.GetDepthInMainChain() ? "immature" : "orphan")); + entry.push_back(Pair("amount", ValueFromAmount(nGeneratedImmature))); + } + else + { + entry.push_back(Pair("category", "generate")); + entry.push_back(Pair("amount", ValueFromAmount(nGeneratedMature))); + } + if (fLong) + WalletTxToJSON(wtx, entry); + ret.push_back(entry); + } + + // Sent + if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) + { + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& s, listSent) + { + Object entry; + entry.push_back(Pair("account", strSentAccount)); + entry.push_back(Pair("address", CBitcoinAddress(s.first).ToString())); + entry.push_back(Pair("category", "send")); + entry.push_back(Pair("amount", ValueFromAmount(-s.second))); + entry.push_back(Pair("fee", ValueFromAmount(-nFee))); + if (fLong) + WalletTxToJSON(wtx, entry); + ret.push_back(entry); + } + } + + // Received + if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) + { + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& r, listReceived) + { + string account; + if (pwalletMain->mapAddressBook.count(r.first)) + account = pwalletMain->mapAddressBook[r.first]; + if (fAllAccounts || (account == strAccount)) + { + Object entry; + entry.push_back(Pair("account", account)); + entry.push_back(Pair("address", CBitcoinAddress(r.first).ToString())); + if (wtx.IsCoinBase()) + { + if (wtx.GetDepthInMainChain() < 1) + entry.push_back(Pair("category", "orphan")); + else if (wtx.GetBlocksToMaturity() > 0) + entry.push_back(Pair("category", "immature")); + else + entry.push_back(Pair("category", "generate")); + } + else + entry.push_back(Pair("category", "receive")); + entry.push_back(Pair("amount", ValueFromAmount(r.second))); + if (fLong) + WalletTxToJSON(wtx, entry); + ret.push_back(entry); + } + } + } +} + +void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, Array& ret) +{ + bool fAllAccounts = (strAccount == string("*")); + + if (fAllAccounts || acentry.strAccount == strAccount) + { + Object entry; + entry.push_back(Pair("account", acentry.strAccount)); + entry.push_back(Pair("category", "move")); + entry.push_back(Pair("time", (boost::int64_t)acentry.nTime)); + entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit))); + entry.push_back(Pair("otheraccount", acentry.strOtherAccount)); + entry.push_back(Pair("comment", acentry.strComment)); + ret.push_back(entry); + } +} + +Value listtransactions(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 3) + throw runtime_error( + "listtransactions [account] [count=10] [from=0]\n" + "Returns up to [count] most recent transactions skipping the first [from] transactions for account [account]."); + + string strAccount = "*"; + if (params.size() > 0) + strAccount = params[0].get_str(); + int nCount = 10; + if (params.size() > 1) + nCount = params[1].get_int(); + int nFrom = 0; + if (params.size() > 2) + nFrom = params[2].get_int(); + + if (nCount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); + if (nFrom < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); + + Array ret; + + std::list acentries; + CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount); + + // iterate backwards until we have nCount items to return: + for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx != 0) + ListTransactions(*pwtx, strAccount, 0, true, ret); + CAccountingEntry *const pacentry = (*it).second.second; + if (pacentry != 0) + AcentryToJSON(*pacentry, strAccount, ret); + + if ((int)ret.size() >= (nCount+nFrom)) break; + } + // ret is newest to oldest + + if (nFrom > (int)ret.size()) + nFrom = ret.size(); + if ((nFrom + nCount) > (int)ret.size()) + nCount = ret.size() - nFrom; + Array::iterator first = ret.begin(); + std::advance(first, nFrom); + Array::iterator last = ret.begin(); + std::advance(last, nFrom+nCount); + + if (last != ret.end()) ret.erase(last, ret.end()); + if (first != ret.begin()) ret.erase(ret.begin(), first); + + std::reverse(ret.begin(), ret.end()); // Return oldest to newest + + return ret; +} + +Value listaccounts(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "listaccounts [minconf=1]\n" + "Returns Object that has account names as keys, account balances as values."); + + int nMinDepth = 1; + if (params.size() > 0) + nMinDepth = params[0].get_int(); + + map mapAccountBalances; + BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& entry, pwalletMain->mapAddressBook) { + if (IsMine(*pwalletMain, entry.first)) // This address belongs to me + mapAccountBalances[entry.second] = 0; + } + + for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + { + const CWalletTx& wtx = (*it).second; + int64 nGeneratedImmature, nGeneratedMature, nFee; + string strSentAccount; + list > listReceived; + list > listSent; + wtx.GetAmounts(nGeneratedImmature, nGeneratedMature, listReceived, listSent, nFee, strSentAccount); + mapAccountBalances[strSentAccount] -= nFee; + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& s, listSent) + mapAccountBalances[strSentAccount] -= s.second; + if (wtx.GetDepthInMainChain() >= nMinDepth) + { + mapAccountBalances[""] += nGeneratedMature; + BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& r, listReceived) + if (pwalletMain->mapAddressBook.count(r.first)) + mapAccountBalances[pwalletMain->mapAddressBook[r.first]] += r.second; + else + mapAccountBalances[""] += r.second; + } + } + + list acentries; + CWalletDB(pwalletMain->strWalletFile).ListAccountCreditDebit("*", acentries); + BOOST_FOREACH(const CAccountingEntry& entry, acentries) + mapAccountBalances[entry.strAccount] += entry.nCreditDebit; + + Object ret; + BOOST_FOREACH(const PAIRTYPE(string, int64)& accountBalance, mapAccountBalances) { + ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second))); + } + return ret; +} + +Value listsinceblock(const Array& params, bool fHelp) +{ + if (fHelp) + throw runtime_error( + "listsinceblock [blockhash] [target-confirmations]\n" + "Get all transactions in blocks since block [blockhash], or all transactions if omitted"); + + CBlockIndex *pindex = NULL; + int target_confirms = 1; + + if (params.size() > 0) + { + uint256 blockId = 0; + + blockId.SetHex(params[0].get_str()); + pindex = CBlockLocator(blockId).GetBlockIndex(); + } + + if (params.size() > 1) + { + target_confirms = params[1].get_int(); + + if (target_confirms < 1) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); + } + + int depth = pindex ? (1 + nBestHeight - pindex->nHeight) : -1; + + Array transactions; + + for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); it++) + { + CWalletTx tx = (*it).second; + + if (depth == -1 || tx.GetDepthInMainChain() < depth) + ListTransactions(tx, "*", 0, true, transactions); + } + + uint256 lastblock; + + if (target_confirms == 1) + { + lastblock = hashBestChain; + } + else + { + int target_height = pindexBest->nHeight + 1 - target_confirms; + + CBlockIndex *block; + for (block = pindexBest; + block && block->nHeight > target_height; + block = block->pprev) { } + + lastblock = block ? block->GetBlockHash() : 0; + } + + Object ret; + ret.push_back(Pair("transactions", transactions)); + ret.push_back(Pair("lastblock", lastblock.GetHex())); + + return ret; +} + +Value gettransaction(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "gettransaction \n" + "Get detailed information about "); + + uint256 hash; + hash.SetHex(params[0].get_str()); + + Object entry; + + if (pwalletMain->mapWallet.count(hash)) + { + const CWalletTx& wtx = pwalletMain->mapWallet[hash]; + + TxToJSON(wtx, 0, entry); + + int64 nCredit = wtx.GetCredit(); + int64 nDebit = wtx.GetDebit(); + int64 nNet = nCredit - nDebit; + int64 nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0); + + entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); + if (wtx.IsFromMe()) + entry.push_back(Pair("fee", ValueFromAmount(nFee))); + + WalletTxToJSON(wtx, entry); + + Array details; + ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details); + entry.push_back(Pair("details", details)); + } + else + { + CTransaction tx; + uint256 hashBlock = 0; + if (GetTransaction(hash, tx, hashBlock)) + { + entry.push_back(Pair("txid", hash.GetHex())); + TxToJSON(tx, 0, entry); + if (hashBlock == 0) + entry.push_back(Pair("confirmations", 0)); + else + { + entry.push_back(Pair("blockhash", hashBlock.GetHex())); + map::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) + { + CBlockIndex* pindex = (*mi).second; + if (pindex->IsInMainChain()) + { + entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight)); + entry.push_back(Pair("txntime", (boost::int64_t)tx.nTime)); + entry.push_back(Pair("time", (boost::int64_t)pindex->nTime)); + } + else + entry.push_back(Pair("confirmations", 0)); + } + } + } + else + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); + } + + return entry; +} + + +Value backupwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "backupwallet \n" + "Safely copies wallet.dat to destination, which can be a directory or a path with filename."); + + string strDest = params[0].get_str(); + if (!BackupWallet(*pwalletMain, strDest)) + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); + + return Value::null; +} + + +Value keypoolrefill(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 0) + throw runtime_error( + "keypoolrefill\n" + "Fills the keypool." + + HelpRequiringPassphrase()); + + EnsureWalletIsUnlocked(); + + pwalletMain->TopUpKeyPool(); + + if (pwalletMain->GetKeyPoolSize() < GetArg("-keypool", 100)) + throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool."); + + return Value::null; +} + + +void ThreadTopUpKeyPool(void* parg) +{ + // Make this thread recognisable as the key-topping-up thread + RenameThread("bitcoin-key-top"); + + pwalletMain->TopUpKeyPool(); +} + +void ThreadCleanWalletPassphrase(void* parg) +{ + // Make this thread recognisable as the wallet relocking thread + RenameThread("bitcoin-lock-wa"); + + int64 nMyWakeTime = GetTimeMillis() + *((int64*)parg) * 1000; + + ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime); + + if (nWalletUnlockTime == 0) + { + nWalletUnlockTime = nMyWakeTime; + + do + { + if (nWalletUnlockTime==0) + break; + int64 nToSleep = nWalletUnlockTime - GetTimeMillis(); + if (nToSleep <= 0) + break; + + LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime); + Sleep(nToSleep); + ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime); + + } while(1); + + if (nWalletUnlockTime) + { + nWalletUnlockTime = 0; + pwalletMain->Lock(); + } + } + else + { + if (nWalletUnlockTime < nMyWakeTime) + nWalletUnlockTime = nMyWakeTime; + } + + LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime); + + delete (int64*)parg; +} + +Value walletpassphrase(const Array& params, bool fHelp) +{ + if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 3)) + throw runtime_error( + "walletpassphrase [mintonly]\n" + "Stores the wallet decryption key in memory for seconds.\n" + "mintonly is optional true/false allowing only block minting."); + if (fHelp) + return true; + if (!pwalletMain->IsCrypted()) + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); + + if (!pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_ALREADY_UNLOCKED, "Error: Wallet is already unlocked, use walletlock first if need to change unlock settings."); + // Note that the walletpassphrase is stored in params[0] which is not mlock()ed + SecureString strWalletPass; + strWalletPass.reserve(100); + // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) + // Alternately, find a way to make params[0] mlock()'d to begin with. + strWalletPass = params[0].get_str().c_str(); + + if (strWalletPass.length() > 0) + { + if (!pwalletMain->Unlock(strWalletPass)) + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } + else + throw runtime_error( + "walletpassphrase \n" + "Stores the wallet decryption key in memory for seconds."); + + NewThread(ThreadTopUpKeyPool, NULL); + int64* pnSleepTime = new int64(params[1].get_int64()); + NewThread(ThreadCleanWalletPassphrase, pnSleepTime); + + // ppcoin: if user OS account compromised prevent trivial sendmoney commands + if (params.size() > 2) + fWalletUnlockMintOnly = params[2].get_bool(); + else + fWalletUnlockMintOnly = false; + + return Value::null; +} + + +Value walletpassphrasechange(const Array& params, bool fHelp) +{ + if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2)) + throw runtime_error( + "walletpassphrasechange \n" + "Changes the wallet passphrase from to ."); + if (fHelp) + return true; + if (!pwalletMain->IsCrypted()) + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); + + // TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string) + // Alternately, find a way to make params[0] mlock()'d to begin with. + SecureString strOldWalletPass; + strOldWalletPass.reserve(100); + strOldWalletPass = params[0].get_str().c_str(); + + SecureString strNewWalletPass; + strNewWalletPass.reserve(100); + strNewWalletPass = params[1].get_str().c_str(); + + if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) + throw runtime_error( + "walletpassphrasechange \n" + "Changes the wallet passphrase from to ."); + + if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + + return Value::null; +} + + +Value walletlock(const Array& params, bool fHelp) +{ + if (pwalletMain->IsCrypted() && (fHelp || params.size() != 0)) + throw runtime_error( + "walletlock\n" + "Removes the wallet encryption key from memory, locking the wallet.\n" + "After calling this method, you will need to call walletpassphrase again\n" + "before being able to call any methods which require the wallet to be unlocked."); + if (fHelp) + return true; + if (!pwalletMain->IsCrypted()) + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); + + { + LOCK(cs_nWalletUnlockTime); + pwalletMain->Lock(); + nWalletUnlockTime = 0; + } + + return Value::null; +} + + +Value encryptwallet(const Array& params, bool fHelp) +{ + if (!pwalletMain->IsCrypted() && (fHelp || params.size() != 1)) + throw runtime_error( + "encryptwallet \n" + "Encrypts the wallet with ."); + if (fHelp) + return true; + if (pwalletMain->IsCrypted()) + throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); + + // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) + // Alternately, find a way to make params[0] mlock()'d to begin with. + SecureString strWalletPass; + strWalletPass.reserve(100); + strWalletPass = params[0].get_str().c_str(); + + if (strWalletPass.length() < 1) + throw runtime_error( + "encryptwallet \n" + "Encrypts the wallet with ."); + + if (!pwalletMain->EncryptWallet(strWalletPass)) + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); + + // BDB seems to have a bad habit of writing old data into + // slack space in .dat files; that is bad if the old data is + // unencrypted private keys. So: + StartShutdown(); + return "wallet encrypted; NovaCoin server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup."; +} + +class DescribeAddressVisitor : public boost::static_visitor +{ +public: + Object operator()(const CNoDestination &dest) const { return Object(); } + + Object operator()(const CKeyID &keyID) const { + Object obj; + CPubKey vchPubKey; + pwalletMain->GetPubKey(keyID, vchPubKey); + obj.push_back(Pair("isscript", false)); + obj.push_back(Pair("pubkey", HexStr(vchPubKey.Raw()))); + obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed())); + return obj; + } + + Object operator()(const CScriptID &scriptID) const { + Object obj; + obj.push_back(Pair("isscript", true)); + CScript subscript; + pwalletMain->GetCScript(scriptID, subscript); + std::vector addresses; + txnouttype whichType; + int nRequired; + ExtractDestinations(subscript, whichType, addresses, nRequired); + obj.push_back(Pair("script", GetTxnOutputType(whichType))); + Array a; + BOOST_FOREACH(const CTxDestination& addr, addresses) + a.push_back(CBitcoinAddress(addr).ToString()); + obj.push_back(Pair("addresses", a)); + if (whichType == TX_MULTISIG) + obj.push_back(Pair("sigsrequired", nRequired)); + return obj; + } +}; + +Value validateaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "validateaddress \n" + "Return information about ."); + + CBitcoinAddress address(params[0].get_str()); + bool isValid = address.IsValid(); + + Object ret; + ret.push_back(Pair("isvalid", isValid)); + if (isValid) + { + CTxDestination dest = address.Get(); + string currentAddress = address.ToString(); + ret.push_back(Pair("address", currentAddress)); + bool fMine = IsMine(*pwalletMain, dest); + ret.push_back(Pair("ismine", fMine)); + if (fMine) { + Object detail = boost::apply_visitor(DescribeAddressVisitor(), dest); + ret.insert(ret.end(), detail.begin(), detail.end()); + } + if (pwalletMain->mapAddressBook.count(dest)) + ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest])); + } + return ret; +} + +// ppcoin: reserve balance from being staked for network protection +Value reservebalance(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "reservebalance [ [amount]]\n" + " is true or false to turn balance reserve on or off.\n" + " is a real and rounded to cent.\n" + "Set reserve amount not participating in network protection.\n" + "If no parameters provided current setting is printed.\n"); + + if (params.size() > 0) + { + bool fReserve = params[0].get_bool(); + if (fReserve) + { + if (params.size() == 1) + throw runtime_error("must provide amount to reserve balance.\n"); + int64 nAmount = AmountFromValue(params[1]); + nAmount = (nAmount / CENT) * CENT; // round to cent + if (nAmount < 0) + throw runtime_error("amount cannot be negative.\n"); + mapArgs["-reservebalance"] = FormatMoney(nAmount).c_str(); + } + else + { + if (params.size() > 1) + throw runtime_error("cannot specify amount to turn off reserve.\n"); + mapArgs["-reservebalance"] = "0"; + } + } + + Object result; + int64 nReserveBalance = 0; + if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) + throw runtime_error("invalid reserve balance amount\n"); + result.push_back(Pair("reserve", (nReserveBalance > 0))); + result.push_back(Pair("amount", ValueFromAmount(nReserveBalance))); + return result; +} + + +// ppcoin: check wallet integrity +Value checkwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 0) + throw runtime_error( + "checkwallet\n" + "Check wallet for integrity.\n"); + + int nMismatchSpent; + int64 nBalanceInQuestion; + pwalletMain->FixSpentCoins(nMismatchSpent, nBalanceInQuestion, true); + Object result; + if (nMismatchSpent == 0) + result.push_back(Pair("wallet check passed", true)); + else + { + result.push_back(Pair("mismatched spent coins", nMismatchSpent)); + result.push_back(Pair("amount in question", ValueFromAmount(nBalanceInQuestion))); + } + return result; +} + + +// ppcoin: repair wallet +Value repairwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 0) + throw runtime_error( + "repairwallet\n" + "Repair wallet if checkwallet reports any problem.\n"); + + int nMismatchSpent; + int64 nBalanceInQuestion; + pwalletMain->FixSpentCoins(nMismatchSpent, nBalanceInQuestion); + Object result; + if (nMismatchSpent == 0) + result.push_back(Pair("wallet check passed", true)); + else + { + result.push_back(Pair("mismatched spent coins", nMismatchSpent)); + result.push_back(Pair("amount affected by repair", ValueFromAmount(nBalanceInQuestion))); + } + return result; +} + +// NovaCoin: resend unconfirmed wallet transactions +Value resendtx(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "resendtx\n" + "Re-send unconfirmed transactions.\n" + ); + + ResendWalletTransactions(); + + return Value::null; +} + +// ppcoin: make a public-private key pair +Value makekeypair(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "makekeypair [prefix]\n" + "Make a public/private key pair.\n" + "[prefix] is optional preferred prefix for the public key.\n"); + + string strPrefix = ""; + if (params.size() > 0) + strPrefix = params[0].get_str(); + + CKey key; + key.MakeNewKey(false); + + CPrivKey vchPrivKey = key.GetPrivKey(); + Object result; + result.push_back(Pair("PrivateKey", HexStr(vchPrivKey.begin(), vchPrivKey.end()))); + result.push_back(Pair("PublicKey", HexStr(key.GetPubKey().Raw()))); + return result; +} diff --git a/src/script.cpp b/src/script.cpp index a3ab8e1..5ef4d5d 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include @@ -14,12 +13,11 @@ using namespace boost; #include "bignum.h" #include "key.h" #include "main.h" +#include "sync.h" #include "util.h" bool CheckSig(vector vchSig, vector vchPubKey, CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); - - static const valtype vchFalse(0); static const valtype vchZero(0); static const valtype vchTrue(1, 1); @@ -53,12 +51,29 @@ bool CastToBool(const valtype& vch) return false; } +// +// WARNING: This does not work as expected for signed integers; the sign-bit +// is left in place as the integer is zero-extended. The correct behavior +// would be to move the most significant bit of the last byte during the +// resize process. MakeSameSize() is currently only used by the disabled +// opcodes OP_AND, OP_OR, and OP_XOR. +// void MakeSameSize(valtype& vch1, valtype& vch2) { // Lengthen the shorter one if (vch1.size() < vch2.size()) + // PATCH: + // +unsigned char msb = vch1[vch1.size()-1]; + // +vch1[vch1.size()-1] &= 0x7f; + // vch1.resize(vch2.size(), 0); + // +vch1[vch1.size()-1] = msb; vch1.resize(vch2.size(), 0); if (vch2.size() < vch1.size()) + // PATCH: + // +unsigned char msb = vch2[vch2.size()-1]; + // +vch2[vch2.size()-1] &= 0x7f; + // vch2.resize(vch1.size(), 0); + // +vch2[vch2.size()-1] = msb; vch2.resize(vch1.size(), 0); } @@ -662,6 +677,11 @@ bool EvalScript(vector >& stack, const CScript& script, co } break; + // + // WARNING: These disabled opcodes exhibit unexpected behavior + // when used on signed integers due to a bug in MakeSameSize() + // [see definition of MakeSameSize() above]. + // case OP_AND: case OP_OR: case OP_XOR: @@ -671,7 +691,7 @@ bool EvalScript(vector >& stack, const CScript& script, co return false; valtype& vch1 = stacktop(-2); valtype& vch2 = stacktop(-1); - MakeSameSize(vch1, vch2); + MakeSameSize(vch1, vch2); // <-- NOT SAFE FOR SIGNED VALUES if (opcode == OP_AND) { for (unsigned int i = 0; i < vch1.size(); i++) @@ -942,7 +962,7 @@ bool EvalScript(vector >& stack, const CScript& script, co // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) int i = 1; - if (stack.size() < i) + if ((int)stack.size() < i) return false; int nKeysCount = CastToBigNum(stacktop(-i)).getint(); @@ -953,7 +973,7 @@ bool EvalScript(vector >& stack, const CScript& script, co return false; int ikey = ++i; i += nKeysCount; - if (stack.size() < i) + if ((int)stack.size() < i) return false; int nSigsCount = CastToBigNum(stacktop(-i)).getint(); @@ -961,7 +981,7 @@ bool EvalScript(vector >& stack, const CScript& script, co return false; int isig = ++i; i += nSigsCount; - if (stack.size() < i) + if ((int)stack.size() < i) return false; // Subset of script starting at the most recent codeseparator @@ -1069,7 +1089,7 @@ uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int } else if ((nHashType & 0x1f) == SIGHASH_SINGLE) { - // Only lockin the txout payee at same index as txin + // Only lock-in the txout payee at same index as txin unsigned int nOut = nIn; if (nOut >= txTmp.vout.size()) { @@ -1126,8 +1146,7 @@ public: return false; } - void - Set(uint256 hash, const std::vector& vchSig, const std::vector& pubKey) + void Set(uint256 hash, const std::vector& vchSig, const std::vector& pubKey) { // DoS prevention: limit cache size to less than 10MB // (~200 bytes per cache entry times 50,000 entries) @@ -1271,7 +1290,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector& multisigdata, const CKeyStore& keystore, uint2 { int nSigned = 0; int nRequired = multisigdata.front()[0]; - for (vector::const_iterator it = multisigdata.begin()+1; it != multisigdata.begin()+multisigdata.size()-1; it++) + for (unsigned int i = 1; i < multisigdata.size()-1 && nSigned < nRequired; i++) { - const valtype& pubkey = *it; - CBitcoinAddress address; - address.SetPubKey(pubkey); - if (Sign1(address, keystore, hash, nHashType, scriptSigRet)) - { + const valtype& pubkey = multisigdata[i]; + CKeyID keyID = CPubKey(pubkey).GetID(); + if (Sign1(keyID, keystore, hash, nHashType, scriptSigRet)) ++nSigned; - if (nSigned == nRequired) break; - } } return nSigned==nRequired; } @@ -1348,7 +1363,7 @@ bool SignN(const vector& multisigdata, const CKeyStore& keystore, uint2 // Sign scriptPubKey with private keys stored in keystore, given transaction hash and hash type. // Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed), // unless whichTypeRet is TX_SCRIPTHASH, in which case scriptSigRet is the redemption script. -// Returns false if scriptPubKey could not be completely satisified. +// Returns false if scriptPubKey could not be completely satisfied. // bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet, txnouttype& whichTypeRet) @@ -1359,22 +1374,22 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash if (!Solver(scriptPubKey, whichTypeRet, vSolutions)) return false; - CBitcoinAddress address; + CKeyID keyID; switch (whichTypeRet) { case TX_NONSTANDARD: return false; case TX_PUBKEY: - address.SetPubKey(vSolutions[0]); - return Sign1(address, keystore, hash, nHashType, scriptSigRet); + keyID = CPubKey(vSolutions[0]).GetID(); + return Sign1(keyID, keystore, hash, nHashType, scriptSigRet); case TX_PUBKEYHASH: - address.SetHash160(uint160(vSolutions[0])); - if (!Sign1(address, keystore, hash, nHashType, scriptSigRet)) + keyID = CKeyID(uint160(vSolutions[0])); + if (!Sign1(keyID, keystore, hash, nHashType, scriptSigRet)) return false; else { - valtype vch; - keystore.GetPubKey(address, vch); + CPubKey vch; + keystore.GetPubKey(keyID, vch); scriptSigRet << vch; } return true; @@ -1435,14 +1450,30 @@ unsigned int HaveKeys(const vector& pubkeys, const CKeyStore& keystore) unsigned int nResult = 0; BOOST_FOREACH(const valtype& pubkey, pubkeys) { - CBitcoinAddress address; - address.SetPubKey(pubkey); - if (keystore.HaveKey(address)) + CKeyID keyID = CPubKey(pubkey).GetID(); + if (keystore.HaveKey(keyID)) ++nResult; } return nResult; } + +class CKeyStoreIsMineVisitor : public boost::static_visitor +{ +private: + const CKeyStore *keystore; +public: + CKeyStoreIsMineVisitor(const CKeyStore *keystoreIn) : keystore(keystoreIn) { } + bool operator()(const CNoDestination &dest) const { return false; } + bool operator()(const CKeyID &keyID) const { return keystore->HaveKey(keyID); } + bool operator()(const CScriptID &scriptID) const { return keystore->HaveCScript(scriptID); } +}; + +bool IsMine(const CKeyStore &keystore, const CTxDestination &dest) +{ + return boost::apply_visitor(CKeyStoreIsMineVisitor(&keystore), dest); +} + bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) { vector vSolutions; @@ -1450,21 +1481,21 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) if (!Solver(scriptPubKey, whichType, vSolutions)) return false; - CBitcoinAddress address; + CKeyID keyID; switch (whichType) { case TX_NONSTANDARD: return false; case TX_PUBKEY: - address.SetPubKey(vSolutions[0]); - return keystore.HaveKey(address); + keyID = CPubKey(vSolutions[0]).GetID(); + return keystore.HaveKey(keyID); case TX_PUBKEYHASH: - address.SetHash160(uint160(vSolutions[0])); - return keystore.HaveKey(address); + keyID = CKeyID(uint160(vSolutions[0])); + return keystore.HaveKey(keyID); case TX_SCRIPTHASH: { CScript subscript; - if (!keystore.GetCScript(uint160(vSolutions[0]), subscript)) + if (!keystore.GetCScript(CScriptID(uint160(vSolutions[0])), subscript)) return false; return IsMine(keystore, subscript); } @@ -1482,7 +1513,7 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) return false; } -bool ExtractAddress(const CScript& scriptPubKey, CBitcoinAddress& addressRet) +bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) { vector vSolutions; txnouttype whichType; @@ -1491,24 +1522,24 @@ bool ExtractAddress(const CScript& scriptPubKey, CBitcoinAddress& addressRet) if (whichType == TX_PUBKEY) { - addressRet.SetPubKey(vSolutions[0]); + addressRet = CPubKey(vSolutions[0]).GetID(); return true; } else if (whichType == TX_PUBKEYHASH) { - addressRet.SetHash160(uint160(vSolutions[0])); + addressRet = CKeyID(uint160(vSolutions[0])); return true; } else if (whichType == TX_SCRIPTHASH) { - addressRet.SetScriptHash160(uint160(vSolutions[0])); + addressRet = CScriptID(uint160(vSolutions[0])); return true; } // Multisig txns have more than one address... return false; } -bool ExtractAddresses(const CScript& scriptPubKey, txnouttype& typeRet, vector& addressRet, int& nRequiredRet) +bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vector& addressRet, int& nRequiredRet) { addressRet.clear(); typeRet = TX_NONSTANDARD; @@ -1521,21 +1552,16 @@ bool ExtractAddresses(const CScript& scriptPubKey, txnouttype& typeRet, vector(subscript); // Append serialized subscript + bool fSolved = + Solver(keystore, subscript, hash2, nHashType, txin.scriptSig, subType) && subType != TX_SCRIPTHASH; + // Append serialized subscript whether or not it is completely signed: + txin.scriptSig << static_cast(subscript); + if (!fSolved) return false; } // Test solution - if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, true, 0)) - return false; - - return true; + return VerifyScript(txin.scriptSig, fromPubKey, txTo, nIn, true, 0); } +bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType) +{ + assert(nIn < txTo.vin.size()); + CTxIn& txin = txTo.vin[nIn]; + assert(txin.prevout.n < txFrom.vout.size()); + assert(txin.prevout.hash == txFrom.GetHash()); + const CTxOut& txout = txFrom.vout[txin.prevout.n]; + + return SignSignature(keystore, txout.scriptPubKey, txTo, nIn, nHashType); +} bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, int nHashType) { @@ -1631,10 +1662,129 @@ bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsig if (txin.prevout.hash != txFrom.GetHash()) return false; - if (!VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, fValidatePayToScriptHash, nHashType)) - return false; + return VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, fValidatePayToScriptHash, nHashType); +} - return true; +static CScript PushAll(const vector& values) +{ + CScript result; + BOOST_FOREACH(const valtype& v, values) + result << v; + return result; +} + +static CScript CombineMultisig(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, + const vector& vSolutions, + vector& sigs1, vector& sigs2) +{ + // Combine all the signatures we've got: + set allsigs; + BOOST_FOREACH(const valtype& v, sigs1) + { + if (!v.empty()) + allsigs.insert(v); + } + BOOST_FOREACH(const valtype& v, sigs2) + { + if (!v.empty()) + allsigs.insert(v); + } + + // Build a map of pubkey -> signature by matching sigs to pubkeys: + assert(vSolutions.size() > 1); + unsigned int nSigsRequired = vSolutions.front()[0]; + unsigned int nPubKeys = vSolutions.size()-2; + map sigs; + BOOST_FOREACH(const valtype& sig, allsigs) + { + for (unsigned int i = 0; i < nPubKeys; i++) + { + const valtype& pubkey = vSolutions[i+1]; + if (sigs.count(pubkey)) + continue; // Already got a sig for this pubkey + + if (CheckSig(sig, pubkey, scriptPubKey, txTo, nIn, 0)) + { + sigs[pubkey] = sig; + break; + } + } + } + // Now build a merged CScript: + unsigned int nSigsHave = 0; + CScript result; result << OP_0; // pop-one-too-many workaround + for (unsigned int i = 0; i < nPubKeys && nSigsHave < nSigsRequired; i++) + { + if (sigs.count(vSolutions[i+1])) + { + result << sigs[vSolutions[i+1]]; + ++nSigsHave; + } + } + // Fill any missing with OP_0: + for (unsigned int i = nSigsHave; i < nSigsRequired; i++) + result << OP_0; + + return result; +} + +static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, + const txnouttype txType, const vector& vSolutions, + vector& sigs1, vector& sigs2) +{ + switch (txType) + { + case TX_NONSTANDARD: + // Don't know anything about this, assume bigger one is correct: + if (sigs1.size() >= sigs2.size()) + return PushAll(sigs1); + return PushAll(sigs2); + case TX_PUBKEY: + case TX_PUBKEYHASH: + // Signatures are bigger than placeholders or empty scripts: + if (sigs1.empty() || sigs1[0].empty()) + return PushAll(sigs2); + return PushAll(sigs1); + case TX_SCRIPTHASH: + if (sigs1.empty() || sigs1.back().empty()) + return PushAll(sigs2); + else if (sigs2.empty() || sigs2.back().empty()) + return PushAll(sigs1); + else + { + // Recur to combine: + valtype spk = sigs1.back(); + CScript pubKey2(spk.begin(), spk.end()); + + txnouttype txType2; + vector > vSolutions2; + Solver(pubKey2, txType2, vSolutions2); + sigs1.pop_back(); + sigs2.pop_back(); + CScript result = CombineSignatures(pubKey2, txTo, nIn, txType2, vSolutions2, sigs1, sigs2); + result << spk; + return result; + } + case TX_MULTISIG: + return CombineMultisig(scriptPubKey, txTo, nIn, vSolutions, sigs1, sigs2); + } + + return CScript(); +} + +CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, + const CScript& scriptSig1, const CScript& scriptSig2) +{ + txnouttype txType; + vector > vSolutions; + Solver(scriptPubKey, txType, vSolutions); + + vector stack1; + EvalScript(stack1, scriptSig1, CTransaction(), 0, 0); + vector stack2; + EvalScript(stack2, scriptSig2, CTransaction(), 0, 0); + + return CombineSignatures(scriptPubKey, txTo, nIn, txType, vSolutions, stack1, stack2); } unsigned int CScript::GetSigOpCount(bool fAccurate) const @@ -1680,7 +1830,7 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const return 0; } - /// ... and return it's opcount: + /// ... and return its opcount: CScript subscript(data.begin(), data.end()); return subscript.GetSigOpCount(true); } @@ -1694,13 +1844,34 @@ bool CScript::IsPayToScriptHash() const this->at(22) == OP_EQUAL); } -void CScript::SetBitcoinAddress(const CBitcoinAddress& address) +class CScriptVisitor : public boost::static_visitor { - this->clear(); - if (address.IsScript()) - *this << OP_HASH160 << address.GetHash160() << OP_EQUAL; - else - *this << OP_DUP << OP_HASH160 << address.GetHash160() << OP_EQUALVERIFY << OP_CHECKSIG; +private: + CScript *script; +public: + CScriptVisitor(CScript *scriptin) { script = scriptin; } + + bool operator()(const CNoDestination &dest) const { + script->clear(); + return false; + } + + bool operator()(const CKeyID &keyID) const { + script->clear(); + *script << OP_DUP << OP_HASH160 << keyID << OP_EQUALVERIFY << OP_CHECKSIG; + return true; + } + + bool operator()(const CScriptID &scriptID) const { + script->clear(); + *script << OP_HASH160 << scriptID << OP_EQUAL; + return true; + } +}; + +void CScript::SetDestination(const CTxDestination& dest) +{ + boost::apply_visitor(CScriptVisitor(this), dest); } void CScript::SetMultisig(int nRequired, const std::vector& keys) @@ -1712,11 +1883,3 @@ void CScript::SetMultisig(int nRequired, const std::vector& keys) *this << key.GetPubKey(); *this << EncodeOP_N(keys.size()) << OP_CHECKMULTISIG; } - -void CScript::SetPayToScriptHash(const CScript& subscript) -{ - assert(!subscript.empty()); - uint160 subscriptHash = Hash160(subscript); - this->clear(); - *this << OP_HASH160 << subscriptHash << OP_EQUAL; -} diff --git a/src/script.h b/src/script.h index 9a19b63..99465cb 100644 --- a/src/script.h +++ b/src/script.h @@ -1,22 +1,22 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef H_BITCOIN_SCRIPT #define H_BITCOIN_SCRIPT -#include "base58.h" - #include #include #include +#include + +#include "keystore.h" +#include "bignum.h" typedef std::vector valtype; class CTransaction; -class CKeyStore; /** Signature hash types/flags */ enum @@ -38,6 +38,20 @@ enum txnouttype TX_MULTISIG, }; +class CNoDestination { +public: + friend bool operator==(const CNoDestination &a, const CNoDestination &b) { return true; } + friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; } +}; + +/** A txout script template with a specific destination. It is either: + * * CNoDestination: no destination set + * * CKeyID: TX_PUBKEYHASH destination + * * CScriptID: TX_SCRIPTHASH destination + * A CTxDestination is the internal data type encoded in a CBitcoinAddress + */ +typedef boost::variant CTxDestination; + const char* GetTxnOutputType(txnouttype t); /** Script opcodes */ @@ -323,6 +337,12 @@ public: return *this; } + CScript& operator<<(const CPubKey& key) + { + std::vector vchKey = key.Raw(); + return (*this) << vchKey; + } + CScript& operator<<(const CBigNum& b) { *this << b.getvch(); @@ -360,7 +380,7 @@ public: { // I'm not sure if this should push the script or concatenate scripts. // If there's ever a use for pushing a script onto a script, delete this member fn - assert(!"warning: pushing a CScript onto a CScript with << is probably not intended, use + to concatenate"); + assert(!"Warning: Pushing a CScript onto a CScript with << is probably not intended, use + to concatenate!"); return *this; } @@ -434,7 +454,7 @@ public: memcpy(&nSize, &pc[0], 4); pc += 4; } - if (end() - pc < nSize) + if (end() - pc < 0 || (unsigned int)(end() - pc) < nSize) return false; if (pvchRet) pvchRet->assign(pc, pc + nSize); @@ -518,13 +538,8 @@ public: } - void SetBitcoinAddress(const CBitcoinAddress& address); - void SetBitcoinAddress(const std::vector& vchPubKey) - { - SetBitcoinAddress(CBitcoinAddress(vchPubKey)); - } + void SetDestination(const CTxDestination& address); void SetMultisig(int nRequired, const std::vector& keys); - void SetPayToScriptHash(const CScript& subscript); void PrintHex() const @@ -559,6 +574,11 @@ public: { printf("%s\n", ToString().c_str()); } + + CScriptID GetID() const + { + return CScriptID(Hash160(*this)); + } }; @@ -570,9 +590,17 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutions); bool IsStandard(const CScript& scriptPubKey); bool IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); -bool ExtractAddress(const CScript& scriptPubKey, CBitcoinAddress& addressRet); -bool ExtractAddresses(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); +bool IsMine(const CKeyStore& keystore, const CTxDestination &dest); +bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); +bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); +bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL); bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL); +bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, + bool fValidatePayToScriptHash, int nHashType); bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, int nHashType); +// Given two sets of signatures for scriptPubKey, possibly with OP_0 placeholders, +// combine them intelligently and return the result. +CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn, const CScript& scriptSig1, const CScript& scriptSig2); + #endif diff --git a/src/serialize.h b/src/serialize.h index ca12051..3b3dcd4 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SERIALIZE_H @@ -155,7 +154,6 @@ template inline void Serialize(Stream& s, bool a, int, int=0) template inline void Unserialize(Stream& s, bool& a, int, int=0) { char f; READDATA(s, f); a=f; } - #ifndef THROW_WITH_STACKTRACE #define THROW_WITH_STACKTRACE(exception) \ { \ @@ -250,7 +248,6 @@ uint64 ReadCompactSize(Stream& is) #define FLATDATA(obj) REF(CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj))) /** Wrapper for serializing arrays and POD. - * There's a clever template way to make arrays serialize normally, but MSVC6 doesn't support it. */ class CFlatData { @@ -816,7 +813,8 @@ public: void insert(iterator it, const_iterator first, const_iterator last) { - if (it == vch.begin() + nReadPos && last - first <= nReadPos) + assert(last - first >= 0); + if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos) { // special case for inserting at the front when there's room nReadPos -= (last - first); @@ -828,7 +826,8 @@ public: void insert(iterator it, std::vector::const_iterator first, std::vector::const_iterator last) { - if (it == vch.begin() + nReadPos && last - first <= nReadPos) + assert(last - first >= 0); + if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos) { // special case for inserting at the front when there's room nReadPos -= (last - first); @@ -841,7 +840,8 @@ public: #if !defined(_MSC_VER) || _MSC_VER >= 1300 void insert(iterator it, const char* first, const char* last) { - if (it == vch.begin() + nReadPos && last - first <= nReadPos) + assert(last - first >= 0); + if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos) { // special case for inserting at the front when there's room nReadPos -= (last - first); @@ -1014,57 +1014,6 @@ public: } }; -#ifdef TESTCDATASTREAM -// VC6sp6 -// CDataStream: -// n=1000 0 seconds -// n=2000 0 seconds -// n=4000 0 seconds -// n=8000 0 seconds -// n=16000 0 seconds -// n=32000 0 seconds -// n=64000 1 seconds -// n=128000 1 seconds -// n=256000 2 seconds -// n=512000 4 seconds -// n=1024000 8 seconds -// n=2048000 16 seconds -// n=4096000 32 seconds -// stringstream: -// n=1000 1 seconds -// n=2000 1 seconds -// n=4000 13 seconds -// n=8000 87 seconds -// n=16000 400 seconds -// n=32000 1660 seconds -// n=64000 6749 seconds -// n=128000 27241 seconds -// n=256000 109804 seconds -#include -int main(int argc, char *argv[]) -{ - vector vch(0xcc, 250); - printf("CDataStream:\n"); - for (int n = 1000; n <= 4500000; n *= 2) - { - CDataStream ss; - time_t nStart = time(NULL); - for (int i = 0; i < n; i++) - ss.write((char*)&vch[0], vch.size()); - printf("n=%-10d %d seconds\n", n, time(NULL) - nStart); - } - printf("stringstream:\n"); - for (int n = 1000; n <= 4500000; n *= 2) - { - stringstream ss; - time_t nStart = time(NULL); - for (int i = 0; i < n; i++) - ss.write((char*)&vch[0], vch.size()); - printf("n=%-10d %d seconds\n", n, time(NULL) - nStart); - } -} -#endif - diff --git a/src/sync.cpp b/src/sync.cpp new file mode 100644 index 0000000..1ac4403 --- /dev/null +++ b/src/sync.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2011-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "sync.h" +#include "util.h" + +#include + +#ifdef DEBUG_LOCKCONTENTION +void PrintLockContention(const char* pszName, const char* pszFile, int nLine) +{ + printf("LOCKCONTENTION: %s\n", pszName); + printf("Locker: %s:%d\n", pszFile, nLine); +} +#endif /* DEBUG_LOCKCONTENTION */ + +#ifdef DEBUG_LOCKORDER +// +// Early deadlock detection. +// Problem being solved: +// Thread 1 locks A, then B, then C +// Thread 2 locks D, then C, then A +// --> may result in deadlock between the two threads, depending on when they run. +// Solution implemented here: +// Keep track of pairs of locks: (A before B), (A before C), etc. +// Complain if any thread tries to lock in a different order. +// + +struct CLockLocation +{ + CLockLocation(const char* pszName, const char* pszFile, int nLine) + { + mutexName = pszName; + sourceFile = pszFile; + sourceLine = nLine; + } + + std::string ToString() const + { + return mutexName+" "+sourceFile+":"+itostr(sourceLine); + } + +private: + std::string mutexName; + std::string sourceFile; + int sourceLine; +}; + +typedef std::vector< std::pair > LockStack; + +static boost::mutex dd_mutex; +static std::map, LockStack> lockorders; +static boost::thread_specific_ptr lockstack; + + +static void potential_deadlock_detected(const std::pair& mismatch, const LockStack& s1, const LockStack& s2) +{ + printf("POTENTIAL DEADLOCK DETECTED\n"); + printf("Previous lock order was:\n"); + BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, s2) + { + if (i.first == mismatch.first) printf(" (1)"); + if (i.first == mismatch.second) printf(" (2)"); + printf(" %s\n", i.second.ToString().c_str()); + } + printf("Current lock order is:\n"); + BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, s1) + { + if (i.first == mismatch.first) printf(" (1)"); + if (i.first == mismatch.second) printf(" (2)"); + printf(" %s\n", i.second.ToString().c_str()); + } +} + +static void push_lock(void* c, const CLockLocation& locklocation, bool fTry) +{ + if (lockstack.get() == NULL) + lockstack.reset(new LockStack); + + if (fDebug) printf("Locking: %s\n", locklocation.ToString().c_str()); + dd_mutex.lock(); + + (*lockstack).push_back(std::make_pair(c, locklocation)); + + if (!fTry) { + BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, (*lockstack)) { + if (i.first == c) break; + + std::pair p1 = std::make_pair(i.first, c); + if (lockorders.count(p1)) + continue; + lockorders[p1] = (*lockstack); + + std::pair p2 = std::make_pair(c, i.first); + if (lockorders.count(p2)) + { + potential_deadlock_detected(p1, lockorders[p2], lockorders[p1]); + break; + } + } + } + dd_mutex.unlock(); +} + +static void pop_lock() +{ + if (fDebug) + { + const CLockLocation& locklocation = (*lockstack).rbegin()->second; + printf("Unlocked: %s\n", locklocation.ToString().c_str()); + } + dd_mutex.lock(); + (*lockstack).pop_back(); + dd_mutex.unlock(); +} + +void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry) +{ + push_lock(cs, CLockLocation(pszName, pszFile, nLine), fTry); +} + +void LeaveCritical() +{ + pop_lock(); +} + +#endif /* DEBUG_LOCKORDER */ diff --git a/src/sync.h b/src/sync.h new file mode 100644 index 0000000..e80efbe --- /dev/null +++ b/src/sync.h @@ -0,0 +1,212 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_SYNC_H +#define BITCOIN_SYNC_H + +#include +#include +#include +#include + + + + +/** Wrapped boost mutex: supports recursive locking, but no waiting */ +typedef boost::recursive_mutex CCriticalSection; + +/** Wrapped boost mutex: supports waiting but not recursive locking */ +typedef boost::mutex CWaitableCriticalSection; + +#ifdef DEBUG_LOCKORDER +void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false); +void LeaveCritical(); +#else +void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {} +void static inline LeaveCritical() {} +#endif + +#ifdef DEBUG_LOCKCONTENTION +void PrintLockContention(const char* pszName, const char* pszFile, int nLine); +#endif + +/** Wrapper around boost::unique_lock */ +template +class CMutexLock +{ +private: + boost::unique_lock lock; +public: + + void Enter(const char* pszName, const char* pszFile, int nLine) + { + if (!lock.owns_lock()) + { + EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex())); +#ifdef DEBUG_LOCKCONTENTION + if (!lock.try_lock()) + { + PrintLockContention(pszName, pszFile, nLine); +#endif + lock.lock(); +#ifdef DEBUG_LOCKCONTENTION + } +#endif + } + } + + void Leave() + { + if (lock.owns_lock()) + { + lock.unlock(); + LeaveCritical(); + } + } + + bool TryEnter(const char* pszName, const char* pszFile, int nLine) + { + if (!lock.owns_lock()) + { + EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true); + lock.try_lock(); + if (!lock.owns_lock()) + LeaveCritical(); + } + return lock.owns_lock(); + } + + CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) : lock(mutexIn, boost::defer_lock) + { + if (fTry) + TryEnter(pszName, pszFile, nLine); + else + Enter(pszName, pszFile, nLine); + } + + ~CMutexLock() + { + if (lock.owns_lock()) + LeaveCritical(); + } + + operator bool() + { + return lock.owns_lock(); + } + + boost::unique_lock &GetLock() + { + return lock; + } +}; + +typedef CMutexLock CCriticalBlock; + +#define LOCK(cs) CCriticalBlock criticalblock(cs, #cs, __FILE__, __LINE__) +#define LOCK2(cs1,cs2) CCriticalBlock criticalblock1(cs1, #cs1, __FILE__, __LINE__),criticalblock2(cs2, #cs2, __FILE__, __LINE__) +#define TRY_LOCK(cs,name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true) + +#define ENTER_CRITICAL_SECTION(cs) \ + { \ + EnterCritical(#cs, __FILE__, __LINE__, (void*)(&cs)); \ + (cs).lock(); \ + } + +#define LEAVE_CRITICAL_SECTION(cs) \ + { \ + (cs).unlock(); \ + LeaveCritical(); \ + } + +class CSemaphore +{ +private: + boost::condition_variable condition; + boost::mutex mutex; + int value; + +public: + CSemaphore(int init) : value(init) {} + + void wait() { + boost::unique_lock lock(mutex); + while (value < 1) { + condition.wait(lock); + } + value--; + } + + bool try_wait() { + boost::unique_lock lock(mutex); + if (value < 1) + return false; + value--; + return true; + } + + void post() { + { + boost::unique_lock lock(mutex); + value++; + } + condition.notify_one(); + } +}; + +/** RAII-style semaphore lock */ +class CSemaphoreGrant +{ +private: + CSemaphore *sem; + bool fHaveGrant; + +public: + void Acquire() { + if (fHaveGrant) + return; + sem->wait(); + fHaveGrant = true; + } + + void Release() { + if (!fHaveGrant) + return; + sem->post(); + fHaveGrant = false; + } + + bool TryAcquire() { + if (!fHaveGrant && sem->try_wait()) + fHaveGrant = true; + return fHaveGrant; + } + + void MoveTo(CSemaphoreGrant &grant) { + grant.Release(); + grant.sem = sem; + grant.fHaveGrant = fHaveGrant; + sem = NULL; + fHaveGrant = false; + } + + CSemaphoreGrant() : sem(NULL), fHaveGrant(false) {} + + CSemaphoreGrant(CSemaphore &sema, bool fTry = false) : sem(&sema), fHaveGrant(false) { + if (fTry) + TryAcquire(); + else + Acquire(); + } + + ~CSemaphoreGrant() { + Release(); + } + + operator bool() { + return fHaveGrant; + } +}; +#endif + diff --git a/src/test/DoS_tests.cpp b/src/test/DoS_tests.cpp index 4ee2e94..7f08dd5 100644 --- a/src/test/DoS_tests.cpp +++ b/src/test/DoS_tests.cpp @@ -34,13 +34,13 @@ BOOST_AUTO_TEST_CASE(DoS_banning) { CNode::ClearBanned(); CAddress addr1(ip(0xa0b0c001)); - CNode dummyNode1(INVALID_SOCKET, addr1, true); + CNode dummyNode1(INVALID_SOCKET, addr1, "", true); dummyNode1.Misbehaving(100); // Should get banned BOOST_CHECK(CNode::IsBanned(addr1)); - BOOST_CHECK(!CNode::IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different ip, not banned + BOOST_CHECK(!CNode::IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned CAddress addr2(ip(0xa0b0c002)); - CNode dummyNode2(INVALID_SOCKET, addr2, true); + CNode dummyNode2(INVALID_SOCKET, addr2, "", true); dummyNode2.Misbehaving(50); BOOST_CHECK(!CNode::IsBanned(addr2)); // 2 not banned yet... BOOST_CHECK(CNode::IsBanned(addr1)); // ... but 1 still should be @@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) CNode::ClearBanned(); mapArgs["-banscore"] = "111"; // because 11 is my favorite number CAddress addr1(ip(0xa0b0c001)); - CNode dummyNode1(INVALID_SOCKET, addr1, true); + CNode dummyNode1(INVALID_SOCKET, addr1, "", true); dummyNode1.Misbehaving(100); BOOST_CHECK(!CNode::IsBanned(addr1)); dummyNode1.Misbehaving(10); @@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) SetMockTime(nStartTime); // Overrides future calls to GetTime() CAddress addr(ip(0xa0b0c001)); - CNode dummyNode(INVALID_SOCKET, addr, true); + CNode dummyNode(INVALID_SOCKET, addr, "", true); dummyNode.Misbehaving(100); BOOST_CHECK(CNode::IsBanned(addr)); @@ -161,7 +161,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vin[0].scriptSig << OP_1; tx.vout.resize(1); tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey()); + tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID()); CDataStream ds(SER_DISK, CLIENT_VERSION); ds << tx; @@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vin[0].prevout.hash = txPrev.GetHash(); tx.vout.resize(1); tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey()); + tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID()); SignSignature(keystore, txPrev, tx, 0); CDataStream ds(SER_DISK, CLIENT_VERSION); @@ -195,9 +195,9 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) CTransaction tx; tx.vout.resize(1); tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey()); + tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID()); tx.vin.resize(500); - for (int j = 0; j < tx.vin.size(); j++) + for (unsigned int j = 0; j < tx.vin.size(); j++) { tx.vin[j].prevout.n = j; tx.vin[j].prevout.hash = txPrev.GetHash(); @@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) SignSignature(keystore, txPrev, tx, 0); // Re-use same signature for other inputs // (they don't have to be valid for this test) - for (int j = 1; j < tx.vin.size(); j++) + for (unsigned int j = 1; j < tx.vin.size(); j++) tx.vin[j].scriptSig = tx.vin[0].scriptSig; CDataStream ds(SER_DISK, CLIENT_VERSION); @@ -244,7 +244,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig) tx.vin[0].scriptSig << OP_1; tx.vout.resize(1); tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey()); + tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID()); CDataStream ds(SER_DISK, CLIENT_VERSION); ds << tx; @@ -255,16 +255,16 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig) CTransaction tx; tx.vout.resize(1); tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey()); + tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID()); tx.vin.resize(NPREV); - for (int j = 0; j < tx.vin.size(); j++) + for (unsigned int j = 0; j < tx.vin.size(); j++) { tx.vin[j].prevout.n = 0; tx.vin[j].prevout.hash = orphans[j].GetHash(); } // Creating signatures primes the cache: boost::posix_time::ptime mst1 = boost::posix_time::microsec_clock::local_time(); - for (int j = 0; j < tx.vin.size(); j++) + for (unsigned int j = 0; j < tx.vin.size(); j++) BOOST_CHECK(SignSignature(keystore, orphans[j], tx, j)); boost::posix_time::ptime mst2 = boost::posix_time::microsec_clock::local_time(); boost::posix_time::time_duration msdiff = mst2 - mst1; @@ -276,8 +276,8 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig) // uncached Verify takes ~250ms, cached Verify takes ~50ms // (for 100 single-signature inputs) mst1 = boost::posix_time::microsec_clock::local_time(); - for (int i = 0; i < 5; i++) - for (int j = 0; j < tx.vin.size(); j++) + for (unsigned int i = 0; i < 5; i++) + for (unsigned int j = 0; j < tx.vin.size(); j++) BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL)); mst2 = boost::posix_time::microsec_clock::local_time(); msdiff = mst2 - mst1; @@ -304,7 +304,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig) CScript oldSig = tx.vin[0].scriptSig; BOOST_CHECK(SignSignature(keystore, orphans[0], tx, 0)); BOOST_CHECK(tx.vin[0].scriptSig != oldSig); - for (int j = 0; j < tx.vin.size(); j++) + for (unsigned int j = 0; j < tx.vin.size(); j++) BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL)); mapArgs.erase("-maxsigcachesize"); diff --git a/src/test/accounting_tests.cpp b/src/test/accounting_tests.cpp new file mode 100644 index 0000000..8ac6572 --- /dev/null +++ b/src/test/accounting_tests.cpp @@ -0,0 +1,123 @@ +#include + +#include + +#include "init.h" +#include "wallet.h" +#include "walletdb.h" + +BOOST_AUTO_TEST_SUITE(accounting_tests) + +static void +GetResults(CWalletDB& walletdb, std::map& results) +{ + std::list aes; + + results.clear(); + BOOST_CHECK(walletdb.ReorderTransactions(pwalletMain) == DB_LOAD_OK); + walletdb.ListAccountCreditDebit("", aes); + BOOST_FOREACH(CAccountingEntry& ae, aes) + { + results[ae.nOrderPos] = ae; + } +} + +BOOST_AUTO_TEST_CASE(acc_orderupgrade) +{ + CWalletDB walletdb(pwalletMain->strWalletFile); + std::vector vpwtx; + CWalletTx wtx; + CAccountingEntry ae; + std::map results; + + ae.strAccount = ""; + ae.nCreditDebit = 1; + ae.nTime = 1333333333; + ae.strOtherAccount = "b"; + ae.strComment = ""; + walletdb.WriteAccountingEntry(ae); + + wtx.mapValue["comment"] = "z"; + pwalletMain->AddToWallet(wtx); + vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]); + vpwtx[0]->nTimeReceived = (unsigned int)1333333335; + vpwtx[0]->nOrderPos = -1; + + ae.nTime = 1333333336; + ae.strOtherAccount = "c"; + walletdb.WriteAccountingEntry(ae); + + GetResults(walletdb, results); + + BOOST_CHECK(pwalletMain->nOrderPosNext == 3); + BOOST_CHECK(2 == results.size()); + BOOST_CHECK(results[0].nTime == 1333333333); + BOOST_CHECK(results[0].strComment.empty()); + BOOST_CHECK(1 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[2].nTime == 1333333336); + BOOST_CHECK(results[2].strOtherAccount == "c"); + + + ae.nTime = 1333333330; + ae.strOtherAccount = "d"; + ae.nOrderPos = pwalletMain->IncOrderPosNext(); + walletdb.WriteAccountingEntry(ae); + + GetResults(walletdb, results); + + BOOST_CHECK(results.size() == 3); + BOOST_CHECK(pwalletMain->nOrderPosNext == 4); + BOOST_CHECK(results[0].nTime == 1333333333); + BOOST_CHECK(1 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[2].nTime == 1333333336); + BOOST_CHECK(results[3].nTime == 1333333330); + BOOST_CHECK(results[3].strComment.empty()); + + + wtx.mapValue["comment"] = "y"; + --wtx.nLockTime; // Just to change the hash :) + pwalletMain->AddToWallet(wtx); + vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]); + vpwtx[1]->nTimeReceived = (unsigned int)1333333336; + + wtx.mapValue["comment"] = "x"; + --wtx.nLockTime; // Just to change the hash :) + pwalletMain->AddToWallet(wtx); + vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]); + vpwtx[2]->nTimeReceived = (unsigned int)1333333329; + vpwtx[2]->nOrderPos = -1; + + GetResults(walletdb, results); + + BOOST_CHECK(results.size() == 3); + BOOST_CHECK(pwalletMain->nOrderPosNext == 6); + BOOST_CHECK(0 == vpwtx[2]->nOrderPos); + BOOST_CHECK(results[1].nTime == 1333333333); + BOOST_CHECK(2 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[3].nTime == 1333333336); + BOOST_CHECK(results[4].nTime == 1333333330); + BOOST_CHECK(results[4].strComment.empty()); + BOOST_CHECK(5 == vpwtx[1]->nOrderPos); + + + ae.nTime = 1333333334; + ae.strOtherAccount = "e"; + ae.nOrderPos = -1; + walletdb.WriteAccountingEntry(ae); + + GetResults(walletdb, results); + + BOOST_CHECK(results.size() == 4); + BOOST_CHECK(pwalletMain->nOrderPosNext == 7); + BOOST_CHECK(0 == vpwtx[2]->nOrderPos); + BOOST_CHECK(results[1].nTime == 1333333333); + BOOST_CHECK(2 == vpwtx[0]->nOrderPos); + BOOST_CHECK(results[3].nTime == 1333333336); + BOOST_CHECK(results[3].strComment.empty()); + BOOST_CHECK(results[4].nTime == 1333333330); + BOOST_CHECK(results[4].strComment.empty()); + BOOST_CHECK(results[5].nTime == 1333333334); + BOOST_CHECK(6 == vpwtx[1]->nOrderPos); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/allocator_tests.cpp b/src/test/allocator_tests.cpp new file mode 100644 index 0000000..d5cb8e8 --- /dev/null +++ b/src/test/allocator_tests.cpp @@ -0,0 +1,115 @@ +#include + +#include "init.h" +#include "main.h" +#include "util.h" + +BOOST_AUTO_TEST_SUITE(allocator_tests) + +// Dummy memory page locker for platform independent tests +static const void *last_lock_addr, *last_unlock_addr; +static size_t last_lock_len, last_unlock_len; +class TestLocker +{ +public: + bool Lock(const void *addr, size_t len) + { + last_lock_addr = addr; + last_lock_len = len; + return true; + } + bool Unlock(const void *addr, size_t len) + { + last_unlock_addr = addr; + last_unlock_len = len; + return true; + } +}; + +BOOST_AUTO_TEST_CASE(test_LockedPageManagerBase) +{ + const size_t test_page_size = 4096; + LockedPageManagerBase lpm(test_page_size); + size_t addr; + last_lock_addr = last_unlock_addr = 0; + last_lock_len = last_unlock_len = 0; + + /* Try large number of small objects */ + addr = 0; + for(int i=0; i<1000; ++i) + { + lpm.LockRange(reinterpret_cast(addr), 33); + addr += 33; + } + /* Try small number of page-sized objects, straddling two pages */ + addr = test_page_size*100 + 53; + for(int i=0; i<100; ++i) + { + lpm.LockRange(reinterpret_cast(addr), test_page_size); + addr += test_page_size; + } + /* Try small number of page-sized objects aligned to exactly one page */ + addr = test_page_size*300; + for(int i=0; i<100; ++i) + { + lpm.LockRange(reinterpret_cast(addr), test_page_size); + addr += test_page_size; + } + /* one very large object, straddling pages */ + lpm.LockRange(reinterpret_cast(test_page_size*600+1), test_page_size*500); + BOOST_CHECK(last_lock_addr == reinterpret_cast(test_page_size*(600+500))); + /* one very large object, page aligned */ + lpm.LockRange(reinterpret_cast(test_page_size*1200), test_page_size*500-1); + BOOST_CHECK(last_lock_addr == reinterpret_cast(test_page_size*(1200+500-1))); + + BOOST_CHECK(lpm.GetLockedPageCount() == ( + (1000*33+test_page_size-1)/test_page_size + // small objects + 101 + 100 + // page-sized objects + 501 + 500)); // large objects + BOOST_CHECK((last_lock_len & (test_page_size-1)) == 0); // always lock entire pages + BOOST_CHECK(last_unlock_len == 0); // nothing unlocked yet + + /* And unlock again */ + addr = 0; + for(int i=0; i<1000; ++i) + { + lpm.UnlockRange(reinterpret_cast(addr), 33); + addr += 33; + } + addr = test_page_size*100 + 53; + for(int i=0; i<100; ++i) + { + lpm.UnlockRange(reinterpret_cast(addr), test_page_size); + addr += test_page_size; + } + addr = test_page_size*300; + for(int i=0; i<100; ++i) + { + lpm.UnlockRange(reinterpret_cast(addr), test_page_size); + addr += test_page_size; + } + lpm.UnlockRange(reinterpret_cast(test_page_size*600+1), test_page_size*500); + lpm.UnlockRange(reinterpret_cast(test_page_size*1200), test_page_size*500-1); + + /* Check that everything is released */ + BOOST_CHECK(lpm.GetLockedPageCount() == 0); + + /* A few and unlocks of size zero (should have no effect) */ + addr = 0; + for(int i=0; i<1000; ++i) + { + lpm.LockRange(reinterpret_cast(addr), 0); + addr += 1; + } + BOOST_CHECK(lpm.GetLockedPageCount() == 0); + addr = 0; + for(int i=0; i<1000; ++i) + { + lpm.UnlockRange(reinterpret_cast(addr), 0); + addr += 1; + } + BOOST_CHECK(lpm.GetLockedPageCount() == 0); + BOOST_CHECK((last_unlock_len & (test_page_size-1)) == 0); // always unlock entire pages +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp new file mode 100644 index 0000000..fdf3285 --- /dev/null +++ b/src/test/base32_tests.cpp @@ -0,0 +1,20 @@ +#include + +#include "util.h" + +BOOST_AUTO_TEST_SUITE(base32_tests) + +BOOST_AUTO_TEST_CASE(base32_testvectors) +{ + static const std::string vstrIn[] = {"","f","fo","foo","foob","fooba","foobar"}; + static const std::string vstrOut[] = {"","my======","mzxq====","mzxw6===","mzxw6yq=","mzxw6ytb","mzxw6ytboi======"}; + for (unsigned int i=0; i +#include "json/json_spirit_reader_template.h" +#include "json/json_spirit_writer_template.h" +#include "json/json_spirit_utils.h" -#include "main.h" -#include "wallet.h" +#include "base58.h" #include "util.h" +using namespace json_spirit; +extern Array read_json(const std::string& filename); + BOOST_AUTO_TEST_SUITE(base58_tests) -// TODO: -// EncodeBase58Check -// DecodeBase58Check -// CBase58Data -// bool SetString(const char* psz) - // bool SetString(const std::string& str) - // std::string ToString() const - // int CompareTo(const CBase58Data& b58) const - // bool operator==(const CBase58Data& b58) const - // bool operator<=(const CBase58Data& b58) const - // bool operator>=(const CBase58Data& b58) const - // bool operator< (const CBase58Data& b58) const - // bool operator> (const CBase58Data& b58) const - -// CBitcoinAddress - // bool SetHash160(const uint160& hash160) - // bool SetPubKey(const std::vector& vchPubKey) - // bool IsValid() const - // CBitcoinAddress() - // CBitcoinAddress(uint160 hash160In) - // CBitcoinAddress(const std::vector& vchPubKey) - // CBitcoinAddress(const std::string& strAddress) - // CBitcoinAddress(const char* pszAddress) - // uint160 GetHash160() const - -#define U(x) (reinterpret_cast(x)) -static struct { - const unsigned char *data; - int size; -} vstrIn[] = { -{U(""), 0}, -{U("\x61"), 1}, -{U("\x62\x62\x62"), 3}, -{U("\x63\x63\x63"), 3}, -{U("\x73\x69\x6d\x70\x6c\x79\x20\x61\x20\x6c\x6f\x6e\x67\x20\x73\x74\x72\x69\x6e\x67"), 20}, -{U("\x00\xeb\x15\x23\x1d\xfc\xeb\x60\x92\x58\x86\xb6\x7d\x06\x52\x99\x92\x59\x15\xae\xb1\x72\xc0\x66\x47"), 25}, -{U("\x51\x6b\x6f\xcd\x0f"), 5}, -{U("\xbf\x4f\x89\x00\x1e\x67\x02\x74\xdd"), 9}, -{U("\x57\x2e\x47\x94"), 4}, -{U("\xec\xac\x89\xca\xd9\x39\x23\xc0\x23\x21"), 10}, -{U("\x10\xc8\x51\x1e"), 4}, -{U("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), 10}, +// Goal: test low-level base58 encoding functionality +BOOST_AUTO_TEST_CASE(base58_EncodeBase58) +{ + Array tests = read_json("base58_encode_decode.json"); + + BOOST_FOREACH(Value& tv, tests) + { + Array test = tv.get_array(); + std::string strTest = write_string(tv, false); + if (test.size() < 2) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::vector sourcedata = ParseHex(test[0].get_str()); + std::string base58string = test[1].get_str(); + BOOST_CHECK_MESSAGE( + EncodeBase58(&sourcedata[0], &sourcedata[sourcedata.size()]) == base58string, + strTest); + } +} + +// Goal: test low-level base58 decoding functionality +BOOST_AUTO_TEST_CASE(base58_DecodeBase58) +{ + Array tests = read_json("base58_encode_decode.json"); + std::vector result; + + BOOST_FOREACH(Value& tv, tests) + { + Array test = tv.get_array(); + std::string strTest = write_string(tv, false); + if (test.size() < 2) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::vector expected = ParseHex(test[0].get_str()); + std::string base58string = test[1].get_str(); + BOOST_CHECK_MESSAGE(DecodeBase58(base58string, result), strTest); + BOOST_CHECK_MESSAGE(result.size() == expected.size() && std::equal(result.begin(), result.end(), expected.begin()), strTest); + } + + BOOST_CHECK(!DecodeBase58("invalid", result)); +} + +// Visitor to check address type +class TestAddrTypeVisitor : public boost::static_visitor +{ +private: + std::string exp_addrType; +public: + TestAddrTypeVisitor(const std::string &exp_addrType) : exp_addrType(exp_addrType) { } + bool operator()(const CKeyID &id) const + { + return (exp_addrType == "pubkey"); + } + bool operator()(const CScriptID &id) const + { + return (exp_addrType == "script"); + } + bool operator()(const CNoDestination &no) const + { + return (exp_addrType == "none"); + } }; -const char *vstrOut[] = { -"", -"2g", -"a3gV", -"aPEr", -"2cFupjhnEsSn59qHXstmK2ffpLv2", -"1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L", -"ABnLTmg", -"3SEo3LWLoPntC", -"3EFU7m", -"EJDM8drfXA6uyA", -"Rt5zm", -"1111111111" +// Visitor to check address payload +class TestPayloadVisitor : public boost::static_visitor +{ +private: + std::vector exp_payload; +public: + TestPayloadVisitor(std::vector &exp_payload) : exp_payload(exp_payload) { } + bool operator()(const CKeyID &id) const + { + uint160 exp_key(exp_payload); + return exp_key == id; + } + bool operator()(const CScriptID &id) const + { + uint160 exp_key(exp_payload); + return exp_key == id; + } + bool operator()(const CNoDestination &no) const + { + return exp_payload.size() == 0; + } }; -BOOST_AUTO_TEST_CASE(base58_EncodeBase58) +// Goal: check that parsed keys match test payload +BOOST_AUTO_TEST_CASE(base58_keys_valid_parse) { - for (int i=0; i result; + CBitcoinSecret secret; + CBitcoinAddress addr; + // Save global state + bool fTestNet_stored = fTestNet; + + BOOST_FOREACH(Value& tv, tests) { - BOOST_CHECK_EQUAL(EncodeBase58(vstrIn[i].data, vstrIn[i].data + vstrIn[i].size), vstrOut[i]); + Array test = tv.get_array(); + std::string strTest = write_string(tv, false); + if (test.size() < 3) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::string exp_base58string = test[0].get_str(); + std::vector exp_payload = ParseHex(test[1].get_str()); + const Object &metadata = test[2].get_obj(); + bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); + bool isTestnet = find_value(metadata, "isTestnet").get_bool(); + fTestNet = isTestnet; // Override testnet flag + if(isPrivkey) + { + bool isCompressed = find_value(metadata, "isCompressed").get_bool(); + // Must be valid private key + // Note: CBitcoinSecret::SetString tests isValid, whereas CBitcoinAddress does not! + BOOST_CHECK_MESSAGE(secret.SetString(exp_base58string), "!SetString:"+ strTest); + BOOST_CHECK_MESSAGE(secret.IsValid(), "!IsValid:" + strTest); + bool fCompressedOut = false; + CSecret privkey = secret.GetSecret(fCompressedOut); + BOOST_CHECK_MESSAGE(fCompressedOut == isCompressed, "compressed mismatch:" + strTest); + BOOST_CHECK_MESSAGE(privkey.size() == exp_payload.size() && std::equal(privkey.begin(), privkey.end(), exp_payload.begin()), "key mismatch:" + strTest); + + // Private key must be invalid public key + addr.SetString(exp_base58string); + BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid privkey as pubkey:" + strTest); + } + else + { + std::string exp_addrType = find_value(metadata, "addrType").get_str(); // "script" or "pubkey" + // Must be valid public key + BOOST_CHECK_MESSAGE(addr.SetString(exp_base58string), "SetString:" + strTest); + BOOST_CHECK_MESSAGE(addr.IsValid(), "!IsValid:" + strTest); + BOOST_CHECK_MESSAGE(addr.IsScript() == (exp_addrType == "script"), "isScript mismatch" + strTest); + CTxDestination dest = addr.Get(); + BOOST_CHECK_MESSAGE(boost::apply_visitor(TestAddrTypeVisitor(exp_addrType), dest), "addrType mismatch" + strTest); + + // Public key must be invalid private key + secret.SetString(exp_base58string); + BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest); + } } + // Restore global state + fTestNet = fTestNet_stored; } -BOOST_AUTO_TEST_CASE(base58_DecodeBase58) +// Goal: check that generated keys match test vectors +BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) { + Array tests = read_json("base58_keys_valid.json"); std::vector result; - for (int i=0; i expected(vstrIn[i].data, vstrIn[i].data + vstrIn[i].size); - BOOST_CHECK(DecodeBase58(vstrOut[i], result)); - BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); + Array test = tv.get_array(); + std::string strTest = write_string(tv, false); + if (test.size() < 3) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::string exp_base58string = test[0].get_str(); + std::vector exp_payload = ParseHex(test[1].get_str()); + const Object &metadata = test[2].get_obj(); + bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); + bool isTestnet = find_value(metadata, "isTestnet").get_bool(); + fTestNet = isTestnet; // Override testnet flag + if(isPrivkey) + { + bool isCompressed = find_value(metadata, "isCompressed").get_bool(); + CBitcoinSecret secret; + secret.SetSecret(CSecret(exp_payload.begin(), exp_payload.end()), isCompressed); + BOOST_CHECK_MESSAGE(secret.ToString() == exp_base58string, "result mismatch: " + strTest); + } + else + { + std::string exp_addrType = find_value(metadata, "addrType").get_str(); + CTxDestination dest; + if(exp_addrType == "pubkey") + { + dest = CKeyID(uint160(exp_payload)); + } + else if(exp_addrType == "script") + { + dest = CScriptID(uint160(exp_payload)); + } + else if(exp_addrType == "none") + { + dest = CNoDestination(); + } + else + { + BOOST_ERROR("Bad addrtype: " << strTest); + continue; + } + CBitcoinAddress addrOut; + BOOST_CHECK_MESSAGE(boost::apply_visitor(CBitcoinAddressVisitor(&addrOut), dest), "encode dest: " + strTest); + BOOST_CHECK_MESSAGE(addrOut.ToString() == exp_base58string, "mismatch: " + strTest); + } + } + + // Visiting a CNoDestination must fail + CBitcoinAddress dummyAddr; + CTxDestination nodest = CNoDestination(); + BOOST_CHECK(!boost::apply_visitor(CBitcoinAddressVisitor(&dummyAddr), nodest)); + + // Restore global state + fTestNet = fTestNet_stored; +} + +// Goal: check that base58 parsing code is robust against a variety of corrupted data +BOOST_AUTO_TEST_CASE(base58_keys_invalid) +{ + Array tests = read_json("base58_keys_invalid.json"); // Negative testcases + std::vector result; + CBitcoinSecret secret; + CBitcoinAddress addr; + + BOOST_FOREACH(Value& tv, tests) + { + Array test = tv.get_array(); + std::string strTest = write_string(tv, false); + if (test.size() < 1) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::string exp_base58string = test[0].get_str(); + + // must be invalid as public and as private key + addr.SetString(exp_base58string); + BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid pubkey:" + strTest); + secret.SetString(exp_base58string); + BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid privkey:" + strTest); } - BOOST_CHECK(!DecodeBase58("invalid", result)); } + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index fff30ef..c5a053e 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -10,7 +10,7 @@ BOOST_AUTO_TEST_CASE(base64_testvectors) { static const std::string vstrIn[] = {"","f","fo","foo","foob","fooba","foobar"}; static const std::string vstrOut[] = {"","Zg==","Zm8=","Zm9v","Zm9vYg==","Zm9vYmE=","Zm9vYmFy"}; - for (int i=0; i +#include + +#include "bignum.h" +#include "util.h" + +BOOST_AUTO_TEST_SUITE(bignum_tests) + +// Unfortunately there's no standard way of preventing a function from being +// inlined, so we define a macro for it. +// +// You should use it like this: +// NOINLINE void function() {...} +#if defined(__GNUC__) +// This also works and will be defined for any compiler implementing GCC +// extensions, such as Clang and ICC. +#define NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) +#define NOINLINE __declspec(noinline) +#else +// We give out a warning because it impacts the correctness of one bignum test. +#warning You should define NOINLINE for your compiler. +#define NOINLINE +#endif + +// For the following test case, it is useful to use additional tools. +// +// The simplest one to use is the compiler flag -ftrapv, which detects integer +// overflows and similar errors. However, due to optimizations and compilers +// taking advantage of undefined behavior sometimes it may not actually detect +// anything. +// +// You can also use compiler-based stack protection to possibly detect possible +// stack buffer overruns. +// +// For more accurate diagnostics, you can use an undefined arithmetic operation +// detector such as the clang-based tool: +// +// "IOC: An Integer Overflow Checker for C/C++" +// +// Available at: http://embed.cs.utah.edu/ioc/ +// +// It might also be useful to use Google's AddressSanitizer to detect +// stack buffer overruns, which valgrind can't currently detect. + +// Let's force this code not to be inlined, in order to actually +// test a generic version of the function. This increases the chance +// that -ftrapv will detect overflows. +NOINLINE void mysetint64(CBigNum& num, int64 n) +{ + num.setint64(n); +} + +// For each number, we do 2 tests: one with inline code, then we reset the +// value to 0, then the second one with a non-inlined function. +BOOST_AUTO_TEST_CASE(bignum_setint64) +{ + int64 n; + + { + n = 0; + CBigNum num(n); + BOOST_CHECK(num.ToString() == "0"); + num.setulong(0); + BOOST_CHECK(num.ToString() == "0"); + mysetint64(num, n); + BOOST_CHECK(num.ToString() == "0"); + } + { + n = 1; + CBigNum num(n); + BOOST_CHECK(num.ToString() == "1"); + num.setulong(0); + BOOST_CHECK(num.ToString() == "0"); + mysetint64(num, n); + BOOST_CHECK(num.ToString() == "1"); + } + { + n = -1; + CBigNum num(n); + BOOST_CHECK(num.ToString() == "-1"); + num.setulong(0); + BOOST_CHECK(num.ToString() == "0"); + mysetint64(num, n); + BOOST_CHECK(num.ToString() == "-1"); + } + { + n = 5; + CBigNum num(n); + BOOST_CHECK(num.ToString() == "5"); + num.setulong(0); + BOOST_CHECK(num.ToString() == "0"); + mysetint64(num, n); + BOOST_CHECK(num.ToString() == "5"); + } + { + n = -5; + CBigNum num(n); + BOOST_CHECK(num.ToString() == "-5"); + num.setulong(0); + BOOST_CHECK(num.ToString() == "0"); + mysetint64(num, n); + BOOST_CHECK(num.ToString() == "-5"); + } + { + n = std::numeric_limits::min(); + CBigNum num(n); + BOOST_CHECK(num.ToString() == "-9223372036854775808"); + num.setulong(0); + BOOST_CHECK(num.ToString() == "0"); + mysetint64(num, n); + BOOST_CHECK(num.ToString() == "-9223372036854775808"); + } + { + n = std::numeric_limits::max(); + CBigNum num(n); + BOOST_CHECK(num.ToString() == "9223372036854775807"); + num.setulong(0); + BOOST_CHECK(num.ToString() == "0"); + mysetint64(num, n); + BOOST_CHECK(num.ToString() == "9223372036854775807"); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/data/base58_encode_decode.json b/src/test/data/base58_encode_decode.json new file mode 100644 index 0000000..9448f25 --- /dev/null +++ b/src/test/data/base58_encode_decode.json @@ -0,0 +1,14 @@ +[ +["", ""], +["61", "2g"], +["626262", "a3gV"], +["636363", "aPEr"], +["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"], +["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"], +["516b6fcd0f", "ABnLTmg"], +["bf4f89001e670274dd", "3SEo3LWLoPntC"], +["572e4794", "3EFU7m"], +["ecac89cad93923c02321", "EJDM8drfXA6uyA"], +["10c8511e", "Rt5zm"], +["00000000000000000000", "1111111111"] +] diff --git a/src/test/data/base58_keys_invalid.json b/src/test/data/base58_keys_invalid.json new file mode 100644 index 0000000..a088620 --- /dev/null +++ b/src/test/data/base58_keys_invalid.json @@ -0,0 +1,152 @@ +[ + [ + "" + ], + [ + "x" + ], + [ + "37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y" + ], + [ + "dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv" + ], + [ + "MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S" + ], + [ + "rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf" + ], + [ + "4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq" + ], + [ + "7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb" + ], + [ + "17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs" + ], + [ + "KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3" + ], + [ + "7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th" + ], + [ + "cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va" + ], + [ + "gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk" + ], + [ + "emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs" + ], + [ + "7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo" + ], + [ + "1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso" + ], + [ + "31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq" + ], + [ + "DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN" + ], + [ + "2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i" + ], + [ + "7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos" + ], + [ + "1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu" + ], + [ + "2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb" + ], + [ + "8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ" + ], + [ + "163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ" + ], + [ + "2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu" + ], + [ + "461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU" + ], + [ + "2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs" + ], + [ + "cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn" + ], + [ + "gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj" + ], + [ + "nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny" + ], + [ + "L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc" + ], + [ + "7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ" + ], + [ + "2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP" + ], + [ + "dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw" + ], + [ + "HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX" + ], + [ + "4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB" + ], + [ + "Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ" + ], + [ + "Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs" + ], + [ + "6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ" + ], + [ + "giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4" + ], + [ + "cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK" + ], + [ + "37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig" + ], + [ + "EsYbG4tWWWY45G31nox838qNdzksbPySWc" + ], + [ + "nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT" + ], + [ + "cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx" + ], + [ + "1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde" + ], + [ + "2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU" + ], + [ + "ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf" + ], + [ + "Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd" + ], + [ + "2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED" + ] +] diff --git a/src/test/data/base58_keys_valid.json b/src/test/data/base58_keys_valid.json new file mode 100644 index 0000000..e1e252e --- /dev/null +++ b/src/test/data/base58_keys_valid.json @@ -0,0 +1,452 @@ +[ + [ + "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", + "65a16059864a2fdbc7c99a4723a8395bc6f188eb", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", + "74f209f6ea907e2ea48f74fae05782ae8a665257", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", + "53c0307d6851aa0ce7825ba883c6bd9ad242b486", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", + "6349a418fc4578d10a372b54b45c280cc8c4382f", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr", + "eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD", + "55c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c4", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko", + "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH", + "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", + "6d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", + "fcc5460dd6e2487c7d75b1963625da0e8f4c5975", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ", + "f1d470f9b02370fdec2e6b708b08ac431bf7a5f7", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", + "c579342c2c4c9220205e2cdc285617040c924a0a", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc", + "a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi", + "7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj", + "d6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN", + "a81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d9", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", + "7987ccaa53d02c8873487ef919677cd3db7a6912", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", + "63bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk", + "ef66444b5b17f14e8fae6e7e19b045a78c54fd79", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "2NB72XtkjpnATMggui83aEtPawyyKvnbX2o", + "c3e55fceceaa4391ed2a9677f4a4d34eacd021a0", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9", + "e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT", + "8248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo", + "44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7", + "d1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c69", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", + "adc1cc2081a27206fae25792f28bbc55b831549d", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", + "188f91a931947eddd7432d6e614387e32b244709", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H", + "1694f5bc1a7295b600f40018a618a6ea48eeb498", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN", + "3b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR", + "091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8", + "ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq", + "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA", + "e7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", + "c4c1b72491ede1eedaca00618407ee0b772cad0d", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", + "f6fe69bcb548a829cce4c57bf6fff8af3a5981f9", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6", + "261f83568a098a8638844bd7aeca039d5f2352c0", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda", + "e930e1834a4d234702773951d627cce82fbb5d2e", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg", + "d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi", + "b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b3", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys", + "037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw", + "6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", + "5eadaf9bb7121f0f192561a5a62f5e5f54210292", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", + "3f210e7277c899c3a155cc1c90f4106cbddeec6e", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "myoqcgYiehufrsnnkqdqbp69dddVDMopJu", + "c8a3c2a09a298592c3e180f02487cd91ba3400b5", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C", + "99b31df7c9068d1481b596578ddbb4d3bd90baeb", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": true + } + ], + [ + "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4", + "c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2", + "07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": false + } + ], + [ + "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV", + "ea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801", + { + "isCompressed": false, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h", + "0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c", + { + "isCompressed": true, + "isPrivkey": true, + "isTestnet": true + } + ], + [ + "13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", + "1ed467017f043e91ed4c44b4e8dd674db211c4e6", + { + "addrType": "pubkey", + "isPrivkey": false, + "isTestnet": false + } + ], + [ + "3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", + "5ece0cadddc415b1980f001785947120acdb36fc", + { + "addrType": "script", + "isPrivkey": false, + "isTestnet": false + } + ] +] diff --git a/src/test/data/script_invalid.json b/src/test/data/script_invalid.json index c05d3ba..d413eae 100644 --- a/src/test/data/script_invalid.json +++ b/src/test/data/script_invalid.json @@ -3,6 +3,110 @@ ["", "NOP"], ["NOP", ""], ["NOP","NOP"], + +["0x4c01","0x01 NOP", "PUSHDATA1 with not enough bytes"], +["0x4d0200ff","0x01 NOP", "PUSHDATA2 with not enough bytes"], +["0x4e03000000ffff","0x01 NOP", "PUSHDATA4 with not enough bytes"], + +["1", "IF 0x50 ENDIF 1", "0x50 is reserved"], +["0x52", "0x5f ADD 0x60 EQUAL", "0x51 through 0x60 push 1 through 16 onto stack"], +["0","NOP"], +["1", "IF VER ELSE 1 ENDIF", "VER non-functional"], +["0", "IF VERIF ELSE 1 ENDIF", "VERIF illegal everywhere"], +["0", "IF VERNOTIF ELSE 1 ENDIF", "VERNOT illegal everywhere"], + +["1 IF", "1 ENDIF", "IF/ENDIF can't span scriptSig/scriptPubKey"], +["1 IF 0 ENDIF", "1 ENDIF"], +["1 ELSE 0 ENDIF", "1"], +["0 NOTIF", "123"], + +["0", "DUP IF ENDIF"], +["0", "IF 1 ENDIF"], +["0", "DUP IF ELSE ENDIF"], +["0", "IF 1 ELSE ENDIF"], +["0", "NOTIF ELSE 1 ENDIF"], + +["0 1", "IF IF 1 ELSE 0 ENDIF ENDIF"], +["0 0", "IF IF 1 ELSE 0 ENDIF ENDIF"], +["1 0", "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], +["0 1", "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], + +["0 0", "NOTIF IF 1 ELSE 0 ENDIF ENDIF"], +["0 1", "NOTIF IF 1 ELSE 0 ENDIF ENDIF"], +["1 1", "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], +["0 0", "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], + +["1", "RETURN"], +["1", "DUP IF RETURN ENDIF"], + +["0", "VERIFY 1"], +["1", "VERIFY"], +["1", "VERIFY 0"], + +["1 TOALTSTACK", "FROMALTSTACK 1", "alt stack not shared between sig/pubkey"], + +["IFDUP", "DEPTH 0 EQUAL"], +["DROP", "DEPTH 0 EQUAL"], +["DUP", "DEPTH 0 EQUAL"], +["1", "DUP 1 ADD 2 EQUALVERIFY 0 EQUAL"], +["NOP", "NIP"], +["NOP", "1 NIP"], +["NOP", "1 0 NIP"], +["NOP", "OVER 1"], +["1", "OVER"], +["0 1", "OVER DEPTH 3 EQUALVERIFY"], +["19 20 21", "PICK 19 EQUALVERIFY DEPTH 2 EQUAL"], +["NOP", "0 PICK"], +["1", "-1 PICK"], +["19 20 21", "0 PICK 20 EQUALVERIFY DEPTH 3 EQUAL"], +["19 20 21", "1 PICK 21 EQUALVERIFY DEPTH 3 EQUAL"], +["19 20 21", "2 PICK 22 EQUALVERIFY DEPTH 3 EQUAL"], +["NOP", "0 ROLL"], +["1", "-1 ROLL"], +["19 20 21", "0 ROLL 20 EQUALVERIFY DEPTH 2 EQUAL"], +["19 20 21", "1 ROLL 21 EQUALVERIFY DEPTH 2 EQUAL"], +["19 20 21", "2 ROLL 22 EQUALVERIFY DEPTH 2 EQUAL"], +["NOP", "ROT 1"], +["NOP", "1 ROT 1"], +["NOP", "1 2 ROT 1"], +["NOP", "0 1 2 ROT"], +["NOP", "SWAP 1"], +["1", "SWAP 1"], +["0 1", "SWAP 1 EQUALVERIFY"], +["NOP", "TUCK 1"], +["1", "TUCK 1"], +["1 0", "TUCK DEPTH 3 EQUALVERIFY SWAP 2DROP"], +["NOP", "2DUP 1"], +["1", "2DUP 1"], +["NOP", "3DUP 1"], +["1", "3DUP 1"], +["1 2", "3DUP 1"], +["NOP", "2OVER 1"], +["1", "2 3 2OVER 1"], +["NOP", "2SWAP 1"], +["1", "2 3 2SWAP 1"], + +["'a' 'b'", "CAT", "CAT disabled"], +["'a' 'b' 0", "IF CAT ELSE 1 ENDIF", "CAT disabled"], +["'abc' 1 1", "SUBSTR", "SUBSTR disabled"], +["'abc' 1 1 0", "IF SUBSTR ELSE 1 ENDIF", "SUBSTR disabled"], +["'abc' 2 0", "IF LEFT ELSE 1 ENDIF", "LEFT disabled"], +["'abc' 2 0", "IF RIGHT ELSE 1 ENDIF", "RIGHT disabled"], + +["NOP", "SIZE 1"], + +["'abc'", "IF INVERT ELSE 1 ENDIF", "INVERT disabled"], +["1 2 0 IF AND ELSE 1 ENDIF", "NOP", "AND disabled"], +["1 2 0 IF OR ELSE 1 ENDIF", "NOP", "OR disabled"], +["1 2 0 IF XOR ELSE 1 ENDIF", "NOP", "XOR disabled"], +["2 0 IF 2MUL ELSE 1 ENDIF", "NOP", "2MUL disabled"], +["2 0 IF 2DIV ELSE 1 ENDIF", "NOP", "2DIV disabled"], +["2 2 0 IF MUL ELSE 1 ENDIF", "NOP", "MUL disabled"], +["2 2 0 IF DIV ELSE 1 ENDIF", "NOP", "DIV disabled"], +["2 2 0 IF MOD ELSE 1 ENDIF", "NOP", "MOD disabled"], +["2 2 0 IF LSHIFT ELSE 1 ENDIF", "NOP", "LSHIFT disabled"], +["2 2 0 IF RSHIFT ELSE 1 ENDIF", "NOP", "RSHIFT disabled"], + ["0 1","EQUAL"], ["1 1 ADD", "0 EQUAL"], ["11 1 ADD 12 SUB", "11 EQUAL"], @@ -10,7 +114,7 @@ ["2147483648 0 ADD", "NOP", "arithmetic operands must be in range [-2^31...2^31] "], ["-2147483648 0 ADD", "NOP", "arithmetic operands must be in range [-2^31...2^31] "], ["2147483647 DUP ADD", "4294967294 NUMEQUAL", "NUMEQUAL must be in numeric range"], -["0xaabbccddeeff NOT", "0 EQUAL", "NOT is an arithmetic operand"], +["'abcdef' NOT", "0 EQUAL", "NOT is an arithmetic operand"], ["2 DUP MUL", "4 EQUAL", "disabled"], ["2 DUP DIV", "1 EQUAL", "disabled"], @@ -20,6 +124,122 @@ ["2 2 LSHIFT", "8 EQUAL", "disabled"], ["2 1 RSHIFT", "1 EQUAL", "disabled"], +["1","NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 2 EQUAL"], +["'NOP_1_to_10' NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_11' EQUAL"], + +["0x50","1", "opcode 0x50 is reserved"], +["1", "IF 0xba ELSE 1 ENDIF", "opcodes above NOP10 invalid"], +["1", "IF 0xc0 ELSE 1 ENDIF"], +["1", "IF 0xd1 ELSE 1 ENDIF"], +["1", "IF 0xee ELSE 1 ENDIF"], +["1", "IF 0xfd ELSE 1 ENDIF"], +["1", "IF 0xff ELSE 1 ENDIF"], + +["NOP", "RIPEMD160"], +["NOP", "SHA1"], +["NOP", "SHA256"], +["NOP", "HASH160"], +["NOP", "HASH256"], + +["NOP", +"'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", +">520 byte push"], +["0", +"IF 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' ENDIF 1", +">520 byte push in non-executed IF branch"], +["1", +"0x61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", +">201 opcodes executed. 0x61 is NOP"], +["0", +"IF 0x6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161 ENDIF 1", +">201 opcodes including non-executed IF branch. 0x61 is NOP"], +["1 2 3 4 5 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +"1 2 3 4 5 6 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +">1,000 stack size (0x6f is 3DUP)"], +["1 2 3 4 5 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +"1 TOALTSTACK 2 TOALTSTACK 3 4 5 6 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +">1,000 stack+altstack size"], +["NOP", +"0 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 2DUP 0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", +"10,001-byte scriptPubKey"], + +["NOP1","NOP10"], + +["1","VER", "OP_VER is reserved"], +["1","VERIF", "OP_VERIF is reserved"], +["1","VERNOTIF", "OP_VERNOTIF is reserved"], +["1","RESERVED1", "OP_RESERVED1 is reserved"], +["1","RESERVED2", "OP_RESERVED2 is reserved"], +["1","0xba", "0xba == OP_NOP10 + 1"], + +["2147483648", "1ADD 1", "We cannot do math on 5-byte integers"], +["-2147483648", "1ADD 1", "Because we use a sign bit, -2147483648 is also 5 bytes"], + +["1", "1 ENDIF", "ENDIF without IF"], +["1", "IF 1", "IF without ENDIF"], +["1 IF 1", "ENDIF", "IFs don't carry over"], + +["NOP", "IF 1 ENDIF", "The following tests check the if(stack.size() < N) tests in each opcode"], +["NOP", "NOTIF 1 ENDIF", "They are here to catch copy-and-paste errors"], +["NOP", "VERIFY 1", "Most of them are duplicated elsewhere,"], + +["NOP", "TOALTSTACK 1", "but, hey, more is always better, right?"], +["1", "FROMALTSTACK"], +["1", "2DROP 1"], +["1", "2DUP"], +["1 1", "3DUP"], +["1 1 1", "2OVER"], +["1 1 1 1 1", "2ROT"], +["1 1 1", "2SWAP"], +["NOP", "IFDUP 1"], +["NOP", "DROP 1"], +["NOP", "DUP 1"], +["1", "NIP"], +["1", "OVER"], +["1 1 1 3", "PICK"], +["0", "PICK 1"], +["1 1 1 3", "ROLL"], +["0", "ROLL 1"], +["1 1", "ROT"], +["1", "SWAP"], +["1", "TUCK"], + +["NOP", "SIZE 1"], + +["1", "EQUAL 1"], +["1", "EQUALVERIFY 1"], + +["NOP", "1ADD 1"], +["NOP", "1SUB 1"], +["NOP", "NEGATE 1"], +["NOP", "ABS 1"], +["NOP", "NOT 1"], +["NOP", "0NOTEQUAL 1"], + +["1", "ADD"], +["1", "SUB"], +["1", "BOOLAND"], +["1", "BOOLOR"], +["1", "NUMEQUAL"], +["1", "NUMEQUALVERIFY 1"], +["1", "NUMNOTEQUAL"], +["1", "LESSTHAN"], +["1", "GREATERTHAN"], +["1", "LESSTHANOREQUAL"], +["1", "GREATERTHANOREQUAL"], +["1", "MIN"], +["1", "MAX"], +["1 1", "WITHIN"], + +["NOP", "RIPEMD160 1"], +["NOP", "SHA1 1"], +["NOP", "SHA256 1"], +["NOP", "HASH160 1"], +["NOP", "HASH256 1"], + +["NOP 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "Tests for Script.IsPushOnly()"], +["NOP1 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL"], -["NOP1","NOP10"] +["0 0x01 0x50", "HASH160 0x14 0xece424a6bb6ddf4db592c0faed60685047a361b1 EQUAL", "OP_RESERVED in P2SH should fail"], +["0 0x01 VER", "HASH160 0x14 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", "OP_VER in P2SH should fail"] ] diff --git a/src/test/data/script_valid.json b/src/test/data/script_valid.json index e7ad6d9..c33a3c7 100644 --- a/src/test/data/script_valid.json +++ b/src/test/data/script_valid.json @@ -1,9 +1,101 @@ [ +["0x01 0x0b", "11 EQUAL", "push 1 byte"], +["0x02 0x417a", "'Az' EQUAL"], +["0x4b 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", "push 75 bytes"], + +["0x4c 0x01 0x07","7 EQUAL", "0x4c is OP_PUSHDATA1"], +["0x4d 0x0100 0x08","8 EQUAL", "0x4d is OP_PUSHDATA2"], +["0x4e 0x01000000 0x09","9 EQUAL", "0x4e is OP_PUSHDATA4"], + +["0x4c 0x00","0 EQUAL"], +["0x4d 0x0000","0 EQUAL"], +["0x4e 0x00000000","0 EQUAL"], +["0x4f 1000 ADD","999 EQUAL"], +["0", "IF 0x50 ENDIF 1", "0x50 is reserved (ok if not executed)"], +["0x51", "0x5f ADD 0x60 EQUAL", "0x51 through 0x60 push 1 through 16 onto stack"], +["1","NOP"], +["0", "IF VER ELSE 1 ENDIF", "VER non-functional (ok if not executed)"], +["0", "IF RESERVED1 RESERVED2 ELSE 1 ENDIF", "RESERVED ok in un-executed IF"], + +["1", "DUP IF ENDIF"], +["1", "IF 1 ENDIF"], +["1", "DUP IF ELSE ENDIF"], +["1", "IF 1 ELSE ENDIF"], +["0", "IF ELSE 1 ENDIF"], + +["1 1", "IF IF 1 ELSE 0 ENDIF ENDIF"], +["1 0", "IF IF 1 ELSE 0 ENDIF ENDIF"], +["1 1", "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], +["0 0", "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], + +["1 0", "NOTIF IF 1 ELSE 0 ENDIF ENDIF"], +["1 1", "NOTIF IF 1 ELSE 0 ENDIF ENDIF"], +["1 0", "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], +["0 1", "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF"], + +["1 1", "VERIFY"], + +["10 0 11 TOALTSTACK DROP FROMALTSTACK", "ADD 21 EQUAL"], +["'gavin_was_here' TOALTSTACK 11 FROMALTSTACK", "'gavin_was_here' EQUALVERIFY 11 EQUAL"], + +["0 IFDUP", "DEPTH 1 EQUALVERIFY 0 EQUAL"], +["1 IFDUP", "DEPTH 2 EQUALVERIFY 1 EQUALVERIFY 1 EQUAL"], +["0 DROP", "DEPTH 0 EQUAL"], +["0", "DUP 1 ADD 1 EQUALVERIFY 0 EQUAL"], +["0 1", "NIP"], +["1 0", "OVER DEPTH 3 EQUALVERIFY"], +["22 21 20", "0 PICK 20 EQUALVERIFY DEPTH 3 EQUAL"], +["22 21 20", "1 PICK 21 EQUALVERIFY DEPTH 3 EQUAL"], +["22 21 20", "2 PICK 22 EQUALVERIFY DEPTH 3 EQUAL"], +["22 21 20", "0 ROLL 20 EQUALVERIFY DEPTH 2 EQUAL"], +["22 21 20", "1 ROLL 21 EQUALVERIFY DEPTH 2 EQUAL"], +["22 21 20", "2 ROLL 22 EQUALVERIFY DEPTH 2 EQUAL"], +["22 21 20", "ROT 22 EQUAL"], +["22 21 20", "ROT DROP 20 EQUAL"], +["22 21 20", "ROT DROP DROP 21 EQUAL"], +["22 21 20", "ROT ROT 21 EQUAL"], +["22 21 20", "ROT ROT ROT 20 EQUAL"], +["25 24 23 22 21 20", "2ROT 24 EQUAL"], +["25 24 23 22 21 20", "2ROT DROP 25 EQUAL"], +["25 24 23 22 21 20", "2ROT 2DROP 20 EQUAL"], +["25 24 23 22 21 20", "2ROT 2DROP DROP 21 EQUAL"], +["25 24 23 22 21 20", "2ROT 2DROP 2DROP 22 EQUAL"], +["25 24 23 22 21 20", "2ROT 2DROP 2DROP DROP 23 EQUAL"], +["25 24 23 22 21 20", "2ROT 2ROT 22 EQUAL"], +["25 24 23 22 21 20", "2ROT 2ROT 2ROT 20 EQUAL"], +["1 0", "SWAP 1 EQUALVERIFY 0 EQUAL"], +["0 1", "TUCK DEPTH 3 EQUALVERIFY SWAP 2DROP"], +["13 14", "2DUP ROT EQUALVERIFY EQUAL"], +["-1 0 1 2", "3DUP DEPTH 7 EQUALVERIFY ADD ADD 3 EQUALVERIFY 2DROP 0 EQUALVERIFY"], +["1 2 3 5", "2OVER ADD ADD 8 EQUALVERIFY ADD ADD 6 EQUAL"], +["1 3 5 7", "2SWAP ADD 4 EQUALVERIFY ADD 12 EQUAL"], +["0", "SIZE 0 EQUAL"], +["1", "SIZE 1 EQUAL"], +["127", "SIZE 1 EQUAL"], +["128", "SIZE 2 EQUAL"], +["32767", "SIZE 2 EQUAL"], +["32768", "SIZE 3 EQUAL"], +["8388607", "SIZE 3 EQUAL"], +["8388608", "SIZE 4 EQUAL"], +["2147483647", "SIZE 4 EQUAL"], +["2147483648", "SIZE 5 EQUAL"], +["-1", "SIZE 1 EQUAL"], +["-127", "SIZE 1 EQUAL"], +["-128", "SIZE 2 EQUAL"], +["-32767", "SIZE 2 EQUAL"], +["-32768", "SIZE 3 EQUAL"], +["-8388607", "SIZE 3 EQUAL"], +["-8388608", "SIZE 4 EQUAL"], +["-2147483647", "SIZE 4 EQUAL"], +["-2147483648", "SIZE 5 EQUAL"], +["'abcdefghijklmnopqrstuvwxyz'", "SIZE 26 EQUAL"], + + ["2 -2 ADD", "0 EQUAL"], ["2147483647 -2147483647 ADD", "0 EQUAL"], ["-1 -1 ADD", "-2 EQUAL"], -["1","NOP"], ["0 0","EQUAL"], ["1 1 ADD", "2 EQUAL"], ["1 1ADD", "2 EQUAL"], @@ -73,6 +165,149 @@ ["2147483647 DUP ADD", "4294967294 EQUAL", ">32 bit EQUAL is valid"], ["2147483647 NEGATE DUP ADD", "-4294967294 EQUAL"], +["''", "RIPEMD160 0x14 0x9c1185a5c5e9fc54612808977ee8f548b2258d31 EQUAL"], +["'a'", "RIPEMD160 0x14 0x0bdc9d2d256b3ee9daae347be6f4dc835a467ffe EQUAL"], +["'abcdefghijklmnopqrstuvwxyz'", "RIPEMD160 0x14 0xf71c27109c692c1b56bbdceb5b9d2865b3708dbc EQUAL"], +["''", "SHA1 0x14 0xda39a3ee5e6b4b0d3255bfef95601890afd80709 EQUAL"], +["'a'", "SHA1 0x14 0x86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 EQUAL"], +["'abcdefghijklmnopqrstuvwxyz'", "SHA1 0x14 0x32d10c7b8cf96570ca04ce37f2a19d84240d3a89 EQUAL"], +["''", "SHA256 0x20 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EQUAL"], +["'a'", "SHA256 0x20 0xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb EQUAL"], +["'abcdefghijklmnopqrstuvwxyz'", "SHA256 0x20 0x71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 EQUAL"], +["''", "DUP HASH160 SWAP SHA256 RIPEMD160 EQUAL"], +["''", "DUP HASH256 SWAP SHA256 SHA256 EQUAL"], +["''", "NOP HASH160 0x14 0xb472a266d0bd89c13706a4132ccfb16f7c3b9fcb EQUAL"], +["'a'", "HASH160 NOP 0x14 0x994355199e516ff76c4fa4aab39337b9d84cf12b EQUAL"], +["'abcdefghijklmnopqrstuvwxyz'", "HASH160 0x4c 0x14 0xc286a1af0947f58d1ad787385b1c2c4a976f9e71 EQUAL"], +["''", "HASH256 0x20 0x5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456 EQUAL"], +["'a'", "HASH256 0x20 0xbf5d3affb73efd2ec6c36ad3112dd933efed63c4e1cbffcfa88e2759c144f2d8 EQUAL"], +["'abcdefghijklmnopqrstuvwxyz'", "HASH256 0x4c 0x20 0xca139bc10c2f660da42666f72e89a225936fc60f193c161124a672050c434671 EQUAL"], + + +["1","NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL"], +["'NOP_1_to_10' NOP1 NOP2 NOP3 NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_10' EQUAL"], + +["0", "IF 0xba ELSE 1 ENDIF", "opcodes above NOP10 invalid if executed"], +["0", "IF 0xc0 ELSE 1 ENDIF"], +["0", "IF 0xd1 ELSE 1 ENDIF"], +["0", "IF 0xee ELSE 1 ENDIF"], +["0", "IF 0xfd ELSE 1 ENDIF"], +["0", "IF 0xff ELSE 1 ENDIF"], + +["NOP", +"'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", +"520 byte push"], +["1", +"0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", +"201 opcodes executed. 0x61 is NOP"], +["1 2 3 4 5 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +"1 2 3 4 5 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +"1,000 stack size (0x6f is 3DUP)"], +["1 TOALTSTACK 2 TOALTSTACK 3 4 5 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +"1 2 3 4 5 6 7 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +"1,000 stack size (altstack cleared between scriptSig/scriptPubKey)"], +["'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", +"'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 2DUP 0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", +"Max-size (10,000-byte), max-push(520 bytes), max-opcodes(201), max stack size(1,000 items). 0x6f is 3DUP, 0x61 is NOP"], + +["NOP","1"], + +["1", "0x01 0x01 EQUAL", "The following is useful for checking implementations of BN_bn2mpi"], +["127", "0x01 0x7F EQUAL"], +["128", "0x02 0x8000 EQUAL", "Leave room for the sign bit"], +["32767", "0x02 0xFF7F EQUAL"], +["32768", "0x03 0x008000 EQUAL"], +["8388607", "0x03 0xFFFF7F EQUAL"], +["8388608", "0x04 0x00008000 EQUAL"], +["2147483647", "0x04 0xFFFFFF7F EQUAL"], +["2147483648", "0x05 0x0000008000 EQUAL"], +["-1", "0x01 0x81 EQUAL", "Numbers are little-endian with the MSB being a sign bit"], +["-127", "0x01 0xFF EQUAL"], +["-128", "0x02 0x8080 EQUAL"], +["-32767", "0x02 0xFFFF EQUAL"], +["-32768", "0x03 0x008080 EQUAL"], +["-8388607", "0x03 0xFFFFFF EQUAL"], +["-8388608", "0x04 0x00008080 EQUAL"], +["-2147483647", "0x04 0xFFFFFFFF EQUAL"], +["-2147483648", "0x05 0x0000008080 EQUAL"], + +["2147483647", "1ADD 2147483648 EQUAL", "We can do math on 4-byte integers, and compare 5-byte ones"], +["2147483647", "1ADD 1"], +["-2147483647", "1ADD 1"], + + +["NOP", "NOP 1", "The following tests check the if(stack.size() < N) tests in each opcode"], +["1", "IF 1 ENDIF", "They are here to catch copy-and-paste errors"], +["0", "NOTIF 1 ENDIF", "Most of them are duplicated elsewhere,"], +["1", "VERIFY 1", "but, hey, more is always better, right?"], + +["0", "TOALTSTACK 1"], +["1", "TOALTSTACK FROMALTSTACK"], +["0 0", "2DROP 1"], +["0 1", "2DUP"], +["0 0 1", "3DUP"], +["0 1 0 0", "2OVER"], +["0 1 0 0 0 0", "2ROT"], +["0 1 0 0", "2SWAP"], +["1", "IFDUP"], +["NOP", "DEPTH 1"], +["0", "DROP 1"], +["1", "DUP"], +["0 1", "NIP"], +["1 0", "OVER"], +["1 0 0 0 3", "PICK"], +["1 0", "PICK"], +["1 0 0 0 3", "ROLL"], +["1 0", "ROLL"], +["1 0 0", "ROT"], +["1 0", "SWAP"], +["0 1", "TUCK"], + +["1", "SIZE"], + +["0 0", "EQUAL"], +["0 0", "EQUALVERIFY 1"], + +["0", "1ADD"], +["2", "1SUB"], +["-1", "NEGATE"], +["-1", "ABS"], +["0", "NOT"], +["-1", "0NOTEQUAL"], + +["1 0", "ADD"], +["1 0", "SUB"], +["-1 -1", "BOOLAND"], +["-1 0", "BOOLOR"], +["0 0", "NUMEQUAL"], +["0 0", "NUMEQUALVERIFY 1"], +["-1 0", "NUMNOTEQUAL"], +["-1 0", "LESSTHAN"], +["1 0", "GREATERTHAN"], +["0 0", "LESSTHANOREQUAL"], +["0 0", "GREATERTHANOREQUAL"], +["-1 0", "MIN"], +["1 0", "MAX"], +["-1 -1 0", "WITHIN"], + +["0", "RIPEMD160"], +["0", "SHA1"], +["0", "SHA256"], +["0", "HASH160"], +["0", "HASH256"], +["NOP", "CODESEPARATOR 1"], + +["NOP", "NOP1 1"], +["NOP", "NOP2 1"], +["NOP", "NOP3 1"], +["NOP", "NOP4 1"], +["NOP", "NOP5 1"], +["NOP", "NOP6 1"], +["NOP", "NOP7 1"], +["NOP", "NOP8 1"], +["NOP", "NOP9 1"], +["NOP", "NOP10 1"], -["NOP","1"] +["0 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "Very basic P2SH"], +["0x4c 0 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL"] ] diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json new file mode 100644 index 0000000..f01ee06 --- /dev/null +++ b/src/test/data/tx_invalid.json @@ -0,0 +1,64 @@ +[ +["The following are deserialized transactions which are invalid."], +["They are in the form"], +["[[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], +["serializedTransaction, enforceP2SH]"], +["Objects that are only a single string (like this one) are ignored"], + +["0e1b5688cf179cd9f7cbda1fac0090f6e684bbf8cd946660120197c3f3681809 but with extra junk appended to the end of the scriptPubKey"], +[[["6ca7ec7b1847f6bdbd737176050e6a08d66ccd55bb94ad24f4018024107a5827", 0, "0x41 0x043b640e983c9690a14c039a2037ecc3467b27a0dcd58f19d76c7bc118d09fec45adc5370a1c5bf8067ca9f5557a4cf885fdb0fe0dcc9c3a7137226106fbc779a5 CHECKSIG VERIFY 1"]], +"010000000127587a10248001f424ad94bb55cd6cd6086a0e05767173bdbdf647187beca76c000000004948304502201b822ad10d6adc1a341ae8835be3f70a25201bbff31f59cbb9c5353a5f0eca18022100ea7b2f7074e9aa9cf70aa8d0ffee13e6b45dddabf1ab961bda378bcdb778fa4701ffffffff0100f2052a010000001976a914fc50c5907d86fed474ba5ce8b12a66e0a4c139d888ac00000000", true], + +["This is the nearly-standard transaction with CHECKSIGVERIFY 1 instead of CHECKSIG from tx_valid.json"], +["but with the signature duplicated in the scriptPubKey with a non-standard pushdata prefix"], +["See FindAndDelete, which will only remove if it uses the same pushdata prefix as is standard"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x4c 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true], + +["Same as above, but with the sig in the scriptSig also pushed with the same non-standard OP_PUSHDATA"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x4c 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006b4c473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true], + +["An invalid P2SH Transaction"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", true], + +["Tests for CTransaction::CheckTransaction()"], +["No inputs"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]], +"0100000000010000000000000000015100000000", true], + +["No outputs"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x05ab9e14d983742513f0f451e105ffb4198d1dd4 EQUAL"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022100f16703104aab4e4088317c862daec83440242411b039d14280e03dd33b487ab802201318a7be236672c5c56083eb7a5a195bc57a40af7923ff8545016cd3b571e2a601232103c40e5d339df3f30bf753e7e04450ae4ef76c9e45587d1d993bdc4cd06f0651c7acffffffff0000000000", true], + +["Negative output"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xae609aca8061d77c5e111f6bb62501a6bbe2bfdb EQUAL"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006d4830450220063222cbb128731fc09de0d7323746539166544d6c1df84d867ccea84bcc8903022100bf568e8552844de664cd41648a031554327aa8844af34b4f27397c65b92c04de0123210243ec37dee0e2e053a9c976f43147e79bc7d9dc606ea51010af1ac80db6b069e1acffffffff01ffffffffffffffff015100000000", true], + +["MAX_MONEY + 1 output"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x32afac281462b822adbec5094b8d4d337dd5bd6a EQUAL"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010140075af0750700015100000000", true], + +["MAX_MONEY output + 1 output"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xb558cbf4930954aa6a344363a15668d7477ae716 EQUAL"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510001000000000000015100000000", true], + +["Duplicate inputs"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x236d0639db62b0773fd8ac34dc85ae19e9aba80a EQUAL"]], +"01000000020001000000000000000000000000000000000000000000000000000000000000000000006c47304402204bb1197053d0d7799bf1b30cd503c44b58d6240cccbdc85b6fe76d087980208f02204beeed78200178ffc6c74237bb74b3f276bbb4098b5605d814304fe128bf1431012321039e8815e15952a7c3fada1905f8cf55419837133bd7756c0ef14fc8dfe50c0deaacffffffff0001000000000000000000000000000000000000000000000000000000000000000000006c47304402202306489afef52a6f62e90bf750bbcdf40c06f5c6b138286e6b6b86176bb9341802200dba98486ea68380f47ebb19a7df173b99e6bc9c681d6ccf3bde31465d1f16b3012321039e8815e15952a7c3fada1905f8cf55419837133bd7756c0ef14fc8dfe50c0deaacffffffff010000000000000000015100000000", true], + +["Coinbase of size 1"], +["Note the input is just required to make the tester happy"], +[[["0000000000000000000000000000000000000000000000000000000000000000", -1, "1"]], +"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0151ffffffff010000000000000000015100000000", true], + +["Coinbase of size 101"], +["Note the input is just required to make the tester happy"], +[[["0000000000000000000000000000000000000000000000000000000000000000", -1, "1"]], +"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff655151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000", true], + +["Null txin"], +[[["0000000000000000000000000000000000000000000000000000000000000000", -1, "HASH160 0x14 0x02dae7dbbda56097959cba59b1989dd3e47937bf EQUAL"]], +"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6e49304602210086f39e028e46dafa8e1e3be63906465f4cf038fbe5ed6403dc3e74ae876e6431022100c4625c675cfc5c7e3a0e0d7eaec92ac24da20c73a88eb40d09253e51ac6def5201232103a183ddc41e84753aca47723c965d1b5c8b0e2b537963518355e6dd6cf8415e50acffffffff010000000000000000015100000000", true] +] diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json new file mode 100644 index 0000000..5528ae7 --- /dev/null +++ b/src/test/data/tx_valid.json @@ -0,0 +1,71 @@ +[ +["The following are deserialized transactions which are valid."], +["They are in the form"], +["[[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], +["serializedTransaction, enforceP2SH]"], +["Objects that are only a single string (like this one) are ignored"], + +["The following is 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"], +["It is of particular interest because it contains an invalidly-encoded signature which OpenSSL accepts"], +["See http://r6.ca/blog/20111119T211504Z.html"], +["It is also the first OP_CHECKMULTISIG transaction in standard form"], +[[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], +"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", true], + +["The following is a tweaked form of 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"], +["It has an arbitrary extra byte stuffed into the signature at pos length - 2"], +[[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], +"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004A0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", true], + +["The following is c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73"], +["It is of interest because it contains a 0-sequence as well as a signature of SIGHASH type 0 (which is not a real type)"], +[[["406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602", 0, "DUP HASH160 0x14 0xdc44b1164188067c3a32d4780f5996fa14a4f2d9 EQUALVERIFY CHECKSIG"]], +"01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000", true], + +["A nearly-standard transaction with CHECKSIGVERIFY 1 instead of CHECKSIG"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true], + +["Same as above, but with the signature duplicated in the scriptPubKey with the proper pushdata prefix"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true], + +["The following is f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb"], +["It caught a bug in the workaround for 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63 in an overly simple implementation"], +[[["b464e85df2a238416f8bdae11d120add610380ea07f4ef19c5f9dfd472f96c3d", 0, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"], +["b7978cc96e59a8b13e0865d3f95657561a7f725be952438637475920bac9eb21", 1, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"]], +"01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000", true], + +["The following tests for the presence of a bug in the handling of SIGHASH_SINGLE"], +["It results in signing the constant 1, instead of something generated based on the transaction,"], +["when the input doing the signing has an index greater than the maximum output index"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0xe52b482f2faa8ecbf0db344f93c84ac908557f33 EQUALVERIFY CHECKSIG"], ["0000000000000000000000000000000000000000000000000000000000000200", 0, "1"]], +"01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000", true], + +["An invalid P2SH Transaction"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", false], + +["A valid P2SH Transaction using the standard transaction type put forth in BIP 16"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x8febbed40483661de6958d957412f82deed8e2f7 EQUAL"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000", true], + +["Tests for CTransaction::CheckTransaction()"], +["MAX_MONEY output"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x32afac281462b822adbec5094b8d4d337dd5bd6a EQUAL"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000", true], + +["MAX_MONEY output + 0 output"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xb558cbf4930954aa6a344363a15668d7477ae716 EQUAL"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000", true], + +["Coinbase of size 2"], +["Note the input is just required to make the tester happy"], +[[["0000000000000000000000000000000000000000000000000000000000000000", -1, "1"]], +"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000", true], + +["Coinbase of size 100"], +["Note the input is just required to make the tester happy"], +[[["0000000000000000000000000000000000000000000000000000000000000000", -1, "1"]], +"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000", true] +] diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index a6dab62..0a6df88 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -10,11 +10,18 @@ using namespace std; -static const string strSecret1 ("5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"); -static const string strSecret2 ("5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"); -static const string strSecret1C("Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"); -static const string strSecret2C("L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"); -static const string strAddress1("1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"); +static const string strSecret1 ("5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"); +static const string strSecret2 ("5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"); +static const string strSecret1C ("Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"); +static const string strSecret2C ("L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"); +static const CBitcoinAddress addr1 ("1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"); +static const CBitcoinAddress addr2 ("1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"); +static const CBitcoinAddress addr1C("1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"); +static const CBitcoinAddress addr2C("1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"); + + +static const string strAddressBad("1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"); + #ifdef KEY_TESTS_DUMPINFO void dumpKeyInfo(uint256 privkey) @@ -53,7 +60,7 @@ BOOST_AUTO_TEST_CASE(key_test1) BOOST_CHECK( bsecret2.SetString (strSecret2)); BOOST_CHECK( bsecret1C.SetString(strSecret1C)); BOOST_CHECK( bsecret2C.SetString(strSecret2C)); - BOOST_CHECK(!baddress1.SetString(strAddress1)); + BOOST_CHECK(!baddress1.SetString(strAddressBad)); bool fCompressed; CSecret secret1 = bsecret1.GetSecret (fCompressed); @@ -74,10 +81,10 @@ BOOST_AUTO_TEST_CASE(key_test1) key1C.SetSecret(secret1, true); key2C.SetSecret(secret2, true); - BOOST_CHECK(CBitcoinAddress(key1.GetPubKey ()).ToString() == "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"); - BOOST_CHECK(CBitcoinAddress(key2.GetPubKey ()).ToString() == "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"); - BOOST_CHECK(CBitcoinAddress(key1C.GetPubKey()).ToString() == "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"); - BOOST_CHECK(CBitcoinAddress(key2C.GetPubKey()).ToString() == "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"); + BOOST_CHECK(addr1.Get() == CTxDestination(key1.GetPubKey().GetID())); + BOOST_CHECK(addr2.Get() == CTxDestination(key2.GetPubKey().GetID())); + BOOST_CHECK(addr1C.Get() == CTxDestination(key1C.GetPubKey().GetID())); + BOOST_CHECK(addr2C.Get() == CTxDestination(key2C.GetPubKey().GetID())); for (int n=0; n<16; n++) { diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 5712b4a..4558a76 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -1,12 +1,203 @@ #include +#include "init.h" +#include "main.h" #include "uint256.h" #include "util.h" +#include "wallet.h" extern void SHA256Transform(void* pstate, void* pinput, const void* pinit); BOOST_AUTO_TEST_SUITE(miner_tests) +static +struct { + unsigned char extranonce; + unsigned int nonce; +} blockinfo[] = { + {4, 0xa4a3e223}, {2, 0x15c32f9e}, {1, 0x0375b547}, {1, 0x7004a8a5}, + {2, 0xce440296}, {2, 0x52cfe198}, {1, 0x77a72cd0}, {2, 0xbb5d6f84}, + {2, 0x83f30c2c}, {1, 0x48a73d5b}, {1, 0xef7dcd01}, {2, 0x6809c6c4}, + {2, 0x0883ab3c}, {1, 0x087bbbe2}, {2, 0x2104a814}, {2, 0xdffb6daa}, + {1, 0xee8a0a08}, {2, 0xba4237c1}, {1, 0xa70349dc}, {1, 0x344722bb}, + {3, 0xd6294733}, {2, 0xec9f5c94}, {2, 0xca2fbc28}, {1, 0x6ba4f406}, + {2, 0x015d4532}, {1, 0x6e119b7c}, {2, 0x43e8f314}, {2, 0x27962f38}, + {2, 0xb571b51b}, {2, 0xb36bee23}, {2, 0xd17924a8}, {2, 0x6bc212d9}, + {1, 0x630d4948}, {2, 0x9a4c4ebb}, {2, 0x554be537}, {1, 0xd63ddfc7}, + {2, 0xa10acc11}, {1, 0x759a8363}, {2, 0xfb73090d}, {1, 0xe82c6a34}, + {1, 0xe33e92d7}, {3, 0x658ef5cb}, {2, 0xba32ff22}, {5, 0x0227a10c}, + {1, 0xa9a70155}, {5, 0xd096d809}, {1, 0x37176174}, {1, 0x830b8d0f}, + {1, 0xc6e3910e}, {2, 0x823f3ca8}, {1, 0x99850849}, {1, 0x7521fb81}, + {1, 0xaacaabab}, {1, 0xd645a2eb}, {5, 0x7aea1781}, {5, 0x9d6e4b78}, + {1, 0x4ce90fd8}, {1, 0xabdc832d}, {6, 0x4a34f32a}, {2, 0xf2524c1c}, + {2, 0x1bbeb08a}, {1, 0xad47f480}, {1, 0x9f026aeb}, {1, 0x15a95049}, + {2, 0xd1cb95b2}, {2, 0xf84bbda5}, {1, 0x0fa62cd1}, {1, 0xe05f9169}, + {1, 0x78d194a9}, {5, 0x3e38147b}, {5, 0x737ba0d4}, {1, 0x63378e10}, + {1, 0x6d5f91cf}, {2, 0x88612eb8}, {2, 0xe9639484}, {1, 0xb7fabc9d}, + {2, 0x19b01592}, {1, 0x5a90dd31}, {2, 0x5bd7e028}, {2, 0x94d00323}, + {1, 0xa9b9c01a}, {1, 0x3a40de61}, {1, 0x56e7eec7}, {5, 0x859f7ef6}, + {1, 0xfd8e5630}, {1, 0x2b0c9f7f}, {1, 0xba700e26}, {1, 0x7170a408}, + {1, 0x70de86a8}, {1, 0x74d64cd5}, {1, 0x49e738a1}, {2, 0x6910b602}, + {0, 0x643c565f}, {1, 0x54264b3f}, {2, 0x97ea6396}, {2, 0x55174459}, + {2, 0x03e8779a}, {1, 0x98f34d8f}, {1, 0xc07b2b07}, {1, 0xdfe29668}, + {1, 0x3141c7c1}, {1, 0xb3b595f4}, {1, 0x735abf08}, {5, 0x623bfbce}, + {2, 0xd351e722}, {1, 0xf4ca48c9}, {1, 0x5b19c670}, {1, 0xa164bf0e}, + {2, 0xbbbeb305}, {2, 0xfe1c810a}, +}; + +// NOTE: These tests rely on CreateNewBlock doing its own self-validation! +BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) +{ + CReserveKey reservekey(pwalletMain); + CBlock *pblock; + CTransaction tx; + CScript script; + uint256 hash; + + // Simple block creation, nothing special yet: + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + + // We can't make transactions until we have inputs + // Therefore, load 100 blocks :) + std::vectortxFirst; + for (unsigned int i = 0; i < sizeof(blockinfo)/sizeof(*blockinfo); ++i) + { + pblock->nVersion = 1; + pblock->nTime = pindexBest->GetMedianTimePast()+1; + pblock->vtx[0].vin[0].scriptSig = CScript(); + pblock->vtx[0].vin[0].scriptSig.push_back(blockinfo[i].extranonce); + pblock->vtx[0].vin[0].scriptSig.push_back(pindexBest->nHeight); + pblock->vtx[0].vout[0].scriptPubKey = CScript(); + if (txFirst.size() < 2) + txFirst.push_back(new CTransaction(pblock->vtx[0])); + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + pblock->nNonce = blockinfo[i].nonce; + assert(ProcessBlock(NULL, pblock)); + pblock->hashPrevBlock = pblock->GetHash(); + } + delete pblock; + + // Just to make sure we can still make simple blocks + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + + // block sigops > limit: 1000 CHECKMULTISIG + 1 + tx.vin.resize(1); + // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG + tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vout.resize(1); + tx.vout[0].nValue = 5000000000LL; + for (unsigned int i = 0; i < 1001; ++i) + { + tx.vout[0].nValue -= 1000000; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // block size > limit + tx.vin[0].scriptSig = CScript(); + // 18 * (520char + DROP) + OP_1 = 9433 bytes + std::vector vchData(520); + for (unsigned int i = 0; i < 18; ++i) + tx.vin[0].scriptSig << vchData << OP_DROP; + tx.vin[0].scriptSig << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vout[0].nValue = 5000000000LL; + for (unsigned int i = 0; i < 128; ++i) + { + tx.vout[0].nValue -= 10000000; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // orphan in mempool + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // child with higher priority than parent + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vin[0].prevout.hash = txFirst[1]->GetHash(); + tx.vout[0].nValue = 4900000000LL; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + tx.vin.resize(2); + tx.vin[1].scriptSig = CScript() << OP_1; + tx.vin[1].prevout.hash = txFirst[0]->GetHash(); + tx.vin[1].prevout.n = 0; + tx.vout[0].nValue = 5900000000LL; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // coinbase in mempool + tx.vin.resize(1); + tx.vin[0].prevout.SetNull(); + tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; + tx.vout[0].nValue = 0; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // invalid (pre-p2sh) txn in mempool + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = 4900000000LL; + script = CScript() << OP_0; + tx.vout[0].scriptPubKey.SetDestination(script.GetID()); + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + tx.vin[0].scriptSig = CScript() << (std::vector)script; + tx.vout[0].nValue -= 1000000; + hash = tx.GetHash(); + mempool.addUnchecked(hash,tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // double spend txn pair in mempool + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = 4900000000LL; + tx.vout[0].scriptPubKey = CScript() << OP_1; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vout[0].scriptPubKey = CScript() << OP_2; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // subsidy changing + int nHeight = pindexBest->nHeight; + pindexBest->nHeight = 209999; + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + pindexBest->nHeight = 210000; + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + pindexBest->nHeight = nHeight; +} + BOOST_AUTO_TEST_CASE(sha256transform_equality) { unsigned int pSHA256InitState[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; diff --git a/src/test/mruset_tests.cpp b/src/test/mruset_tests.cpp index ca5a1f1..64a6678 100644 --- a/src/test/mruset_tests.cpp +++ b/src/test/mruset_tests.cpp @@ -71,7 +71,7 @@ int static permute(int n) return ret; } -// Test that an mruset acts like a moving window, if no duplcate elements are added +// Test that an mruset acts like a moving window, if no duplicate elements are added BOOST_AUTO_TEST_CASE(mruset_window) { mruset mru(MAX_SIZE); diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 8ae9290..6bc5e3b 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -175,12 +175,12 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) // CBasicKeyStore keystore, emptykeystore, partialkeystore; CKey key[3]; - CBitcoinAddress keyaddr[3]; + CTxDestination keyaddr[3]; for (int i = 0; i < 3; i++) { key[i].MakeNewKey(true); keystore.AddKey(key[i]); - keyaddr[i].SetPubKey(key[i].GetPubKey()); + keyaddr[i] = key[i].GetPubKey().GetID(); } partialkeystore.AddKey(key[0]); @@ -191,8 +191,8 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) s << key[0].GetPubKey() << OP_CHECKSIG; BOOST_CHECK(Solver(s, whichType, solutions)); BOOST_CHECK(solutions.size() == 1); - CBitcoinAddress addr; - BOOST_CHECK(ExtractAddress(s, addr)); + CTxDestination addr; + BOOST_CHECK(ExtractDestination(s, addr)); BOOST_CHECK(addr == keyaddr[0]); BOOST_CHECK(IsMine(keystore, s)); BOOST_CHECK(!IsMine(emptykeystore, s)); @@ -201,11 +201,11 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) vector solutions; txnouttype whichType; CScript s; - s << OP_DUP << OP_HASH160 << Hash160(key[0].GetPubKey()) << OP_EQUALVERIFY << OP_CHECKSIG; + s << OP_DUP << OP_HASH160 << key[0].GetPubKey().GetID() << OP_EQUALVERIFY << OP_CHECKSIG; BOOST_CHECK(Solver(s, whichType, solutions)); BOOST_CHECK(solutions.size() == 1); - CBitcoinAddress addr; - BOOST_CHECK(ExtractAddress(s, addr)); + CTxDestination addr; + BOOST_CHECK(ExtractDestination(s, addr)); BOOST_CHECK(addr == keyaddr[0]); BOOST_CHECK(IsMine(keystore, s)); BOOST_CHECK(!IsMine(emptykeystore, s)); @@ -217,8 +217,8 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) s << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; BOOST_CHECK(Solver(s, whichType, solutions)); BOOST_CHECK_EQUAL(solutions.size(), 4); - CBitcoinAddress addr; - BOOST_CHECK(!ExtractAddress(s, addr)); + CTxDestination addr; + BOOST_CHECK(!ExtractDestination(s, addr)); BOOST_CHECK(IsMine(keystore, s)); BOOST_CHECK(!IsMine(emptykeystore, s)); BOOST_CHECK(!IsMine(partialkeystore, s)); @@ -230,12 +230,12 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1) s << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG; BOOST_CHECK(Solver(s, whichType, solutions)); BOOST_CHECK_EQUAL(solutions.size(), 4); - vector addrs; + vector addrs; int nRequired; - BOOST_CHECK(ExtractAddresses(s, whichType, addrs, nRequired)); + BOOST_CHECK(ExtractDestinations(s, whichType, addrs, nRequired)); BOOST_CHECK(addrs[0] == keyaddr[0]); BOOST_CHECK(addrs[1] == keyaddr[1]); - BOOST_CHECK(nRequired = 1); + BOOST_CHECK(nRequired == 1); BOOST_CHECK(IsMine(keystore, s)); BOOST_CHECK(!IsMine(emptykeystore, s)); BOOST_CHECK(!IsMine(partialkeystore, s)); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp new file mode 100644 index 0000000..e5a7562 --- /dev/null +++ b/src/test/netbase_tests.cpp @@ -0,0 +1,102 @@ +#include + +#include +#include + +#include "netbase.h" + +using namespace std; + +BOOST_AUTO_TEST_SUITE(netbase_tests) + +BOOST_AUTO_TEST_CASE(netbase_networks) +{ + BOOST_CHECK(CNetAddr("127.0.0.1").GetNetwork() == NET_UNROUTABLE); + BOOST_CHECK(CNetAddr("::1").GetNetwork() == NET_UNROUTABLE); + BOOST_CHECK(CNetAddr("8.8.8.8").GetNetwork() == NET_IPV4); + BOOST_CHECK(CNetAddr("2001::8888").GetNetwork() == NET_IPV6); + BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_TOR); +} + +BOOST_AUTO_TEST_CASE(netbase_properties) +{ + BOOST_CHECK(CNetAddr("127.0.0.1").IsIPv4()); + BOOST_CHECK(CNetAddr("::FFFF:192.168.1.1").IsIPv4()); + BOOST_CHECK(CNetAddr("::1").IsIPv6()); + BOOST_CHECK(CNetAddr("10.0.0.1").IsRFC1918()); + BOOST_CHECK(CNetAddr("192.168.1.1").IsRFC1918()); + BOOST_CHECK(CNetAddr("172.31.255.255").IsRFC1918()); + BOOST_CHECK(CNetAddr("2001:0DB8::").IsRFC3849()); + BOOST_CHECK(CNetAddr("169.254.1.1").IsRFC3927()); + BOOST_CHECK(CNetAddr("2002::1").IsRFC3964()); + BOOST_CHECK(CNetAddr("FC00::").IsRFC4193()); + BOOST_CHECK(CNetAddr("2001::2").IsRFC4380()); + BOOST_CHECK(CNetAddr("2001:10::").IsRFC4843()); + BOOST_CHECK(CNetAddr("FE80::").IsRFC4862()); + BOOST_CHECK(CNetAddr("64:FF9B::").IsRFC6052()); + BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").IsTor()); + BOOST_CHECK(CNetAddr("127.0.0.1").IsLocal()); + BOOST_CHECK(CNetAddr("::1").IsLocal()); + BOOST_CHECK(CNetAddr("8.8.8.8").IsRoutable()); + BOOST_CHECK(CNetAddr("2001::1").IsRoutable()); + BOOST_CHECK(CNetAddr("127.0.0.1").IsValid()); +} + +bool static TestSplitHost(string test, string host, int port) +{ + string hostOut; + int portOut = -1; + SplitHostPort(test, portOut, hostOut); + return hostOut == host && port == portOut; +} + +BOOST_AUTO_TEST_CASE(netbase_splithost) +{ + BOOST_CHECK(TestSplitHost("www.bitcoin.org", "www.bitcoin.org", -1)); + BOOST_CHECK(TestSplitHost("[www.bitcoin.org]", "www.bitcoin.org", -1)); + BOOST_CHECK(TestSplitHost("www.bitcoin.org:80", "www.bitcoin.org", 80)); + BOOST_CHECK(TestSplitHost("[www.bitcoin.org]:80", "www.bitcoin.org", 80)); + BOOST_CHECK(TestSplitHost("127.0.0.1", "127.0.0.1", -1)); + BOOST_CHECK(TestSplitHost("127.0.0.1:8333", "127.0.0.1", 8333)); + BOOST_CHECK(TestSplitHost("[127.0.0.1]", "127.0.0.1", -1)); + BOOST_CHECK(TestSplitHost("[127.0.0.1]:8333", "127.0.0.1", 8333)); + BOOST_CHECK(TestSplitHost("::ffff:127.0.0.1", "::ffff:127.0.0.1", -1)); + BOOST_CHECK(TestSplitHost("[::ffff:127.0.0.1]:8333", "::ffff:127.0.0.1", 8333)); + BOOST_CHECK(TestSplitHost("[::]:8333", "::", 8333)); + BOOST_CHECK(TestSplitHost("::8333", "::8333", -1)); + BOOST_CHECK(TestSplitHost(":8333", "", 8333)); + BOOST_CHECK(TestSplitHost("[]:8333", "", 8333)); + BOOST_CHECK(TestSplitHost("", "", -1)); +} + +bool static TestParse(string src, string canon) +{ + CService addr; + if (!LookupNumeric(src.c_str(), addr, 65535)) + return canon == ""; + return canon == addr.ToString(); +} + +BOOST_AUTO_TEST_CASE(netbase_lookupnumeric) +{ + BOOST_CHECK(TestParse("127.0.0.1", "127.0.0.1:65535")); + BOOST_CHECK(TestParse("127.0.0.1:8333", "127.0.0.1:8333")); + BOOST_CHECK(TestParse("::ffff:127.0.0.1", "127.0.0.1:65535")); + BOOST_CHECK(TestParse("::", "[::]:65535")); + BOOST_CHECK(TestParse("[::]:8333", "[::]:8333")); + BOOST_CHECK(TestParse("[127.0.0.1]", "127.0.0.1:65535")); + BOOST_CHECK(TestParse(":::", "")); +} + +BOOST_AUTO_TEST_CASE(onioncat_test) +{ + // values from http://www.cypherpunk.at/onioncat/wiki/OnionCat + CNetAddr addr1("5wyqrzbvrdsumnok.onion"); + CNetAddr addr2("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca"); + BOOST_CHECK(addr1 == addr2); + BOOST_CHECK(addr1.IsTor()); + BOOST_CHECK(addr1.ToStringIP() == "5wyqrzbvrdsumnok.onion"); + BOOST_CHECK(addr1.IsRoutable()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 7a438e5..505ec8f 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -17,26 +17,19 @@ createArgs(int nRequired, const char* address1=NULL, const char* address2=NULL) result.push_back(nRequired); Array addresses; if (address1) addresses.push_back(address1); - if (address2) addresses.push_back(address1); + if (address2) addresses.push_back(address2); result.push_back(addresses); return result; } -// This can be removed this when addmultisigaddress is enabled on main net: -struct TestNetFixture -{ - TestNetFixture() { fTestNet = true; } - ~TestNetFixture() { fTestNet = false; } -}; - -BOOST_FIXTURE_TEST_CASE(rpc_addmultisig, TestNetFixture) +BOOST_AUTO_TEST_CASE(rpc_addmultisig) { rpcfn_type addmultisig = tableRPC["addmultisigaddress"]->actor; // old, 65-byte-long: - const char* address1Hex = "0434e3e09f49ea168c5bbf53f877ff4206923858aab7c7e1df25bc263978107c95e35065a27ef6f1b27222db0ec97e0e895eaca603d3ee0d4c060ce3d8a00286c8"; + const char address1Hex[] = "0434e3e09f49ea168c5bbf53f877ff4206923858aab7c7e1df25bc263978107c95e35065a27ef6f1b27222db0ec97e0e895eaca603d3ee0d4c060ce3d8a00286c8"; // new, compressed: - const char* address2Hex = "0388c2037017c62240b6b72ac1a2a5f94da790596ebd06177c8572752922165cb4"; + const char address2Hex[] = "0388c2037017c62240b6b72ac1a2a5f94da790596ebd06177c8572752922165cb4"; Value v; CBitcoinAddress address; @@ -62,7 +55,7 @@ BOOST_FIXTURE_TEST_CASE(rpc_addmultisig, TestNetFixture) string short1(address1Hex, address1Hex+sizeof(address1Hex)-2); // last byte missing BOOST_CHECK_THROW(addmultisig(createArgs(2, short1.c_str()), false), runtime_error); - string short2(address1Hex+2, address1Hex+sizeof(address1Hex)); // first byte missing + string short2(address1Hex+1, address1Hex+sizeof(address1Hex)); // first byte missing BOOST_CHECK_THROW(addmultisig(createArgs(2, short2.c_str()), false), runtime_error); } diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_P2SH_tests.cpp index aa72c00..eabfcd0 100644 --- a/src/test/script_P2SH_tests.cpp +++ b/src/test/script_P2SH_tests.cpp @@ -65,14 +65,14 @@ BOOST_AUTO_TEST_CASE(sign) // different keys, straight/P2SH, pubkey/pubkeyhash CScript standardScripts[4]; standardScripts[0] << key[0].GetPubKey() << OP_CHECKSIG; - standardScripts[1].SetBitcoinAddress(key[1].GetPubKey()); + standardScripts[1].SetDestination(key[1].GetPubKey().GetID()); standardScripts[2] << key[1].GetPubKey() << OP_CHECKSIG; - standardScripts[3].SetBitcoinAddress(key[2].GetPubKey()); + standardScripts[3].SetDestination(key[2].GetPubKey().GetID()); CScript evalScripts[4]; for (int i = 0; i < 4; i++) { keystore.AddCScript(standardScripts[i]); - evalScripts[i].SetPayToScriptHash(standardScripts[i]); + evalScripts[i].SetDestination(standardScripts[i].GetID()); } CTransaction txFrom; // Funding transaction: @@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE(norecurse) invalidAsScript << OP_INVALIDOPCODE << OP_INVALIDOPCODE; CScript p2sh; - p2sh.SetPayToScriptHash(invalidAsScript); + p2sh.SetDestination(invalidAsScript.GetID()); CScript scriptSig; scriptSig << Serialize(invalidAsScript); @@ -130,10 +130,10 @@ BOOST_AUTO_TEST_CASE(norecurse) // Should not verify, because it will try to execute OP_INVALIDOPCODE BOOST_CHECK(!Verify(scriptSig, p2sh, true)); - // Try to recurse, and verification should succeed because + // Try to recur, and verification should succeed because // the inner HASH160 <> EQUAL should only check the hash: CScript p2sh2; - p2sh2.SetPayToScriptHash(p2sh); + p2sh2.SetDestination(p2sh.GetID()); CScript scriptSig2; scriptSig2 << Serialize(invalidAsScript) << Serialize(p2sh); @@ -154,7 +154,7 @@ BOOST_AUTO_TEST_CASE(set) } CScript inner[4]; - inner[0].SetBitcoinAddress(key[0].GetPubKey()); + inner[0].SetDestination(key[0].GetPubKey().GetID()); inner[1].SetMultisig(2, std::vector(keys.begin(), keys.begin()+2)); inner[2].SetMultisig(1, std::vector(keys.begin(), keys.begin()+2)); inner[3].SetMultisig(2, std::vector(keys.begin(), keys.begin()+3)); @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(set) CScript outer[4]; for (int i = 0; i < 4; i++) { - outer[i].SetPayToScriptHash(inner[i]); + outer[i].SetDestination(inner[i].GetID()); keystore.AddCScript(inner[i]); } @@ -225,14 +225,14 @@ BOOST_AUTO_TEST_CASE(is) BOOST_AUTO_TEST_CASE(switchover) { - // Test switchover code + // Test switch over code CScript notValid; notValid << OP_11 << OP_12 << OP_EQUALVERIFY; CScript scriptSig; scriptSig << Serialize(notValid); CScript fund; - fund.SetPayToScriptHash(notValid); + fund.SetDestination(notValid.GetID()); // Validation should succeed under old rules (hash is correct): @@ -258,9 +258,9 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txFrom.vout.resize(6); // First three are standard: - CScript pay1; pay1.SetBitcoinAddress(key[0].GetPubKey()); + CScript pay1; pay1.SetDestination(key[0].GetPubKey().GetID()); keystore.AddCScript(pay1); - CScript payScriptHash1; payScriptHash1.SetPayToScriptHash(pay1); + CScript payScriptHash1; payScriptHash1.SetDestination(pay1.GetID()); CScript pay1of3; pay1of3.SetMultisig(1, keys); txFrom.vout[0].scriptPubKey = payScriptHash1; @@ -278,13 +278,13 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) for (int i = 0; i < 11; i++) oneOfEleven << key[0].GetPubKey(); oneOfEleven << OP_11 << OP_CHECKMULTISIG; - txFrom.vout[5].scriptPubKey.SetPayToScriptHash(oneOfEleven); + txFrom.vout[5].scriptPubKey.SetDestination(oneOfEleven.GetID()); mapInputs[txFrom.GetHash()] = make_pair(CTxIndex(), txFrom); CTransaction txTo; txTo.vout.resize(1); - txTo.vout[0].scriptPubKey.SetBitcoinAddress(key[1].GetPubKey()); + txTo.vout[0].scriptPubKey.SetDestination(key[1].GetPubKey().GetID()); txTo.vin.resize(3); txTo.vin[0].prevout.n = 0; @@ -311,7 +311,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) CTransaction txToNonStd; txToNonStd.vout.resize(1); - txToNonStd.vout[0].scriptPubKey.SetBitcoinAddress(key[1].GetPubKey()); + txToNonStd.vout[0].scriptPubKey.SetDestination(key[1].GetPubKey().GetID()); txToNonStd.vin.resize(2); txToNonStd.vin[0].prevout.n = 4; txToNonStd.vin[0].prevout.hash = txFrom.GetHash(); diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 745df4b..61d9a64 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -59,13 +59,15 @@ ParseScript(string s) } else if (starts_with(w, "0x") && IsHex(string(w.begin()+2, w.end()))) { - // Hex data: - result << ParseHex(string(w.begin()+2, w.end())); + // Raw hex data, inserted NOT pushed onto stack: + std::vector raw = ParseHex(string(w.begin()+2, w.end())); + result.insert(result.end(), raw.begin(), raw.end()); } - else if (s.size() >= 2 && starts_with(w, "'") && ends_with(w, "'")) + else if (w.size() >= 2 && starts_with(w, "'") && ends_with(w, "'")) { - // Single-quoted string, pushed as data: - std::vector value(s.begin()+1, s.end()-1); + // Single-quoted string, pushed as data. NOTE: this is poor-man's + // parsing, spaces/tabs/newlines in single-quoted strings won't work. + std::vector value(w.begin()+1, w.end()-1); result << value; } else if (mapOpNames.count(w)) @@ -100,7 +102,10 @@ read_json(const std::string& filename) Value v; if (!read_stream(ifs, v)) { - BOOST_ERROR("Cound not find/open " << filename); + if (ifs.fail()) + BOOST_ERROR("Cound not find/open " << filename); + else + BOOST_ERROR("JSON syntax error in " << filename); return Array(); } if (v.type() != array_type) @@ -323,5 +328,118 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, true, 0)); } +BOOST_AUTO_TEST_CASE(script_combineSigs) +{ + // Test the CombineSignatures function + CBasicKeyStore keystore; + vector keys; + for (int i = 0; i < 3; i++) + { + CKey key; + key.MakeNewKey(i%2 == 1); + keys.push_back(key); + keystore.AddKey(key); + } + + CTransaction txFrom; + txFrom.vout.resize(1); + txFrom.vout[0].scriptPubKey.SetDestination(keys[0].GetPubKey().GetID()); + CScript& scriptPubKey = txFrom.vout[0].scriptPubKey; + CTransaction txTo; + txTo.vin.resize(1); + txTo.vout.resize(1); + txTo.vin[0].prevout.n = 0; + txTo.vin[0].prevout.hash = txFrom.GetHash(); + CScript& scriptSig = txTo.vin[0].scriptSig; + txTo.vout[0].nValue = 1; + + CScript empty; + CScript combined = CombineSignatures(scriptPubKey, txTo, 0, empty, empty); + BOOST_CHECK(combined.empty()); + + // Single signature case: + SignSignature(keystore, txFrom, txTo, 0); // changes scriptSig + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig); + BOOST_CHECK(combined == scriptSig); + CScript scriptSigCopy = scriptSig; + // Signing again will give a different, valid signature: + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig); + BOOST_CHECK(combined == scriptSigCopy || combined == scriptSig); + + // P2SH, single-signature case: + CScript pkSingle; pkSingle << keys[0].GetPubKey() << OP_CHECKSIG; + keystore.AddCScript(pkSingle); + scriptPubKey.SetDestination(pkSingle.GetID()); + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig); + BOOST_CHECK(combined == scriptSig); + scriptSigCopy = scriptSig; + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig); + BOOST_CHECK(combined == scriptSigCopy || combined == scriptSig); + // dummy scriptSigCopy with placeholder, should always choose non-placeholder: + scriptSigCopy = CScript() << OP_0 << static_cast >(pkSingle); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSigCopy, scriptSig); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, scriptSigCopy); + BOOST_CHECK(combined == scriptSig); + + // Hardest case: Multisig 2-of-3 + scriptPubKey.SetMultisig(2, keys); + keystore.AddCScript(scriptPubKey); + SignSignature(keystore, txFrom, txTo, 0); + combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty); + BOOST_CHECK(combined == scriptSig); + combined = CombineSignatures(scriptPubKey, txTo, 0, empty, scriptSig); + BOOST_CHECK(combined == scriptSig); + + // A couple of partially-signed versions: + vector sig1; + uint256 hash1 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_ALL); + BOOST_CHECK(keys[0].Sign(hash1, sig1)); + sig1.push_back(SIGHASH_ALL); + vector sig2; + uint256 hash2 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_NONE); + BOOST_CHECK(keys[1].Sign(hash2, sig2)); + sig2.push_back(SIGHASH_NONE); + vector sig3; + uint256 hash3 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_SINGLE); + BOOST_CHECK(keys[2].Sign(hash3, sig3)); + sig3.push_back(SIGHASH_SINGLE); + + // Not fussy about order (or even existence) of placeholders or signatures: + CScript partial1a = CScript() << OP_0 << sig1 << OP_0; + CScript partial1b = CScript() << OP_0 << OP_0 << sig1; + CScript partial2a = CScript() << OP_0 << sig2; + CScript partial2b = CScript() << sig2 << OP_0; + CScript partial3a = CScript() << sig3; + CScript partial3b = CScript() << OP_0 << OP_0 << sig3; + CScript partial3c = CScript() << OP_0 << sig3 << OP_0; + CScript complete12 = CScript() << OP_0 << sig1 << sig2; + CScript complete13 = CScript() << OP_0 << sig1 << sig3; + CScript complete23 = CScript() << OP_0 << sig2 << sig3; + + combined = CombineSignatures(scriptPubKey, txTo, 0, partial1a, partial1b); + BOOST_CHECK(combined == partial1a); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial1a, partial2a); + BOOST_CHECK(combined == complete12); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial2a, partial1a); + BOOST_CHECK(combined == complete12); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial1b, partial2b); + BOOST_CHECK(combined == complete12); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial1b); + BOOST_CHECK(combined == complete13); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial2a, partial3a); + BOOST_CHECK(combined == complete23); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial2b); + BOOST_CHECK(combined == complete23); + combined = CombineSignatures(scriptPubKey, txTo, 0, partial3b, partial3a); + BOOST_CHECK(combined == partial3c); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index d301313..59673f9 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -32,7 +32,7 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount) BOOST_CHECK_EQUAL(s1.GetSigOpCount(false), 21); CScript p2sh; - p2sh.SetPayToScriptHash(s1); + p2sh.SetDestination(s1.GetID()); CScript scriptSig; scriptSig << OP_0 << Serialize(s1); BOOST_CHECK_EQUAL(p2sh.GetSigOpCount(scriptSig), 3); @@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount) BOOST_CHECK_EQUAL(s2.GetSigOpCount(true), 3); BOOST_CHECK_EQUAL(s2.GetSigOpCount(false), 20); - p2sh.SetPayToScriptHash(s2); + p2sh.SetDestination(s2.GetID()); BOOST_CHECK_EQUAL(p2sh.GetSigOpCount(true), 0); BOOST_CHECK_EQUAL(p2sh.GetSigOpCount(false), 0); CScript scriptSig2; diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index c7e4bdd..bcf0907 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -1,22 +1,32 @@ #define BOOST_TEST_MODULE Bitcoin Test Suite #include +#include "db.h" #include "main.h" #include "wallet.h" CWallet* pwalletMain; +CClientUIInterface uiInterface; extern bool fPrintToConsole; +extern void noui_connect(); + struct TestingSetup { TestingSetup() { - fPrintToConsole = true; // don't want to write to debug.log file - pwalletMain = new CWallet(); + fPrintToDebugger = true; // don't want to write to debug.log file + noui_connect(); + bitdb.MakeMock(); + LoadBlockIndex(true); + bool fFirstRun; + pwalletMain = new CWallet("wallet.dat"); + pwalletMain->LoadWallet(fFirstRun); RegisterWallet(pwalletMain); } ~TestingSetup() { delete pwalletMain; pwalletMain = NULL; + bitdb.Flush(true); } }; diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index b680ede..c230458 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -1,12 +1,156 @@ +#include +#include #include +#include "json/json_spirit_writer_template.h" #include "main.h" #include "wallet.h" using namespace std; +using namespace json_spirit; + +// In script_tests.cpp +extern Array read_json(const std::string& filename); +extern CScript ParseScript(string s); BOOST_AUTO_TEST_SUITE(transaction_tests) +BOOST_AUTO_TEST_CASE(tx_valid) +{ + // Read tests from test/data/tx_valid.json + // Format is an array of arrays + // Inner arrays are either [ "comment" ] + // or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH + // ... where all scripts are stringified scripts. + Array tests = read_json("tx_valid.json"); + + BOOST_FOREACH(Value& tv, tests) + { + Array test = tv.get_array(); + string strTest = write_string(tv, false); + if (test[0].type() == array_type) + { + if (test.size() != 3 || test[1].type() != str_type || test[2].type() != bool_type) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + + map mapprevOutScriptPubKeys; + Array inputs = test[0].get_array(); + bool fValid = true; + BOOST_FOREACH(Value& input, inputs) + { + if (input.type() != array_type) + { + fValid = false; + break; + } + Array vinput = input.get_array(); + if (vinput.size() != 3) + { + fValid = false; + break; + } + + mapprevOutScriptPubKeys[COutPoint(uint256(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str()); + } + if (!fValid) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + + string transaction = test[1].get_str(); + CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + stream >> tx; + + BOOST_CHECK_MESSAGE(tx.CheckTransaction(), strTest); + + for (unsigned int i = 0; i < tx.vin.size(); i++) + { + if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout)) + { + BOOST_ERROR("Bad test: " << strTest); + break; + } + + BOOST_CHECK_MESSAGE(VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], tx, i, test[2].get_bool(), 0), strTest); + } + } + } +} + +BOOST_AUTO_TEST_CASE(tx_invalid) +{ + // Read tests from test/data/tx_invalid.json + // Format is an array of arrays + // Inner arrays are either [ "comment" ] + // or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH + // ... where all scripts are stringified scripts. + Array tests = read_json("tx_invalid.json"); + + BOOST_FOREACH(Value& tv, tests) + { + Array test = tv.get_array(); + string strTest = write_string(tv, false); + if (test[0].type() == array_type) + { + if (test.size() != 3 || test[1].type() != str_type || test[2].type() != bool_type) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + + map mapprevOutScriptPubKeys; + Array inputs = test[0].get_array(); + bool fValid = true; + BOOST_FOREACH(Value& input, inputs) + { + if (input.type() != array_type) + { + fValid = false; + break; + } + Array vinput = input.get_array(); + if (vinput.size() != 3) + { + fValid = false; + break; + } + + mapprevOutScriptPubKeys[COutPoint(uint256(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str()); + } + if (!fValid) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + + string transaction = test[1].get_str(); + CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + stream >> tx; + + fValid = tx.CheckTransaction(); + + for (unsigned int i = 0; i < tx.vin.size() && fValid; i++) + { + if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout)) + { + BOOST_ERROR("Bad test: " << strTest); + break; + } + + fValid = VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], tx, i, test[2].get_bool(), 0); + } + + BOOST_CHECK_MESSAGE(!fValid, strTest); + } + } +} + BOOST_AUTO_TEST_CASE(basic_transaction_tests) { // Random real transaction (e2769b09e784f32f62ef849763d4f45b98e07ba658647343b915ff832b110436) @@ -52,9 +196,9 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, MapPrevTx& inputsRet) dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout[0].nValue = 21*CENT; - dummyTransactions[1].vout[0].scriptPubKey.SetBitcoinAddress(key[2].GetPubKey()); + dummyTransactions[1].vout[0].scriptPubKey.SetDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[1].nValue = 22*CENT; - dummyTransactions[1].vout[1].scriptPubKey.SetBitcoinAddress(key[3].GetPubKey()); + dummyTransactions[1].vout[1].scriptPubKey.SetDestination(key[3].GetPubKey().GetID()); inputsRet[dummyTransactions[1].GetHash()] = make_pair(CTxIndex(), dummyTransactions[1]); return dummyTransactions; diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 0393edb..3a4c035 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -103,11 +103,13 @@ BOOST_AUTO_TEST_CASE(util_HexStr) BOOST_AUTO_TEST_CASE(util_DateTimeStrFormat) { +/*These are platform-dependant and thus removed to avoid useless test failures BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M:%S", 0), "01/01/70 00:00:00"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M:%S", 0x7FFFFFFF), "01/19/38 03:14:07"); - // Formats used within bitcoin + // Formats used within Bitcoin BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M:%S", 1317425777), "09/30/11 23:36:17"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%x %H:%M", 1317425777), "09/30/11 23:36"); +*/ } BOOST_AUTO_TEST_CASE(util_ParseParameters) diff --git a/src/test/wallet_tests.cpp b/src/test/wallet_tests.cpp new file mode 100644 index 0000000..b0b3c47 --- /dev/null +++ b/src/test/wallet_tests.cpp @@ -0,0 +1,295 @@ +#include + +#include "main.h" +#include "wallet.h" + +// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles +#define RUN_TESTS 100 + +// some tests fail 1% of the time due to bad luck. +// we repeat those tests this many times and only complain if all iterations of the test fail +#define RANDOM_REPEATS 5 + +using namespace std; + +typedef set > CoinSet; + +BOOST_AUTO_TEST_SUITE(wallet_tests) + +static CWallet wallet; +static vector vCoins; + +static void add_coin(int64 nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) +{ + static int i; + CTransaction* tx = new CTransaction; + tx->nLockTime = i++; // so all transactions get different hashes + tx->vout.resize(nInput+1); + tx->vout[nInput].nValue = nValue; + CWalletTx* wtx = new CWalletTx(&wallet, *tx); + delete tx; + if (fIsFromMe) + { + // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(), + // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe() + wtx->vin.resize(1); + wtx->fDebitCached = true; + wtx->nDebitCached = 1; + } + COutput output(wtx, nInput, nAge); + vCoins.push_back(output); +} + +static void empty_wallet(void) +{ + BOOST_FOREACH(COutput output, vCoins) + delete output.tx; + vCoins.clear(); +} + +static bool equal_sets(CoinSet a, CoinSet b) +{ + pair ret = mismatch(a.begin(), a.end(), b.begin()); + return ret.first == a.end() && ret.second == b.end(); +} + +BOOST_AUTO_TEST_CASE(coin_selection_tests) +{ + static CoinSet setCoinsRet, setCoinsRet2; + static int64 nValueRet; + + // test multiple times to allow for differences in the shuffle order + for (int i = 0; i < RUN_TESTS; i++) + { + empty_wallet(); + + // with an empty wallet we can't even pay one cent + BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); + + add_coin(1*CENT, 4); // add a new 1 cent coin + + // with a new 1 cent coin, we still can't find a mature 1 cent + BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); + + // but we can find a new 1 cent + BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + + add_coin(2*CENT); // add a mature 2 cent coin + + // we can't make 3 cents of mature coins + BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); + + // we can make 3 cents of new coins + BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); + + add_coin(5*CENT); // add a mature 5 cent coin, + add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(20*CENT); // and a mature 20 cent coin + + // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 + + // we can't make 38 cents only if we disallow new coins: + BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); + // we can't even make 37 cents if we don't allow new coins even if they're from us + BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 6, 6, vCoins, setCoinsRet, nValueRet)); + // but we can make 37 cents if we accept new coins from ourself + BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); + // and we can make 38 cents if we accept all new coins + BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 38 * CENT); + + // try making 34 cents from 1,2,5,10,20 - we can't do it exactly + BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) + + // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5 + BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); + + // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough. + BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(nValueRet == 8 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); + + // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10) + BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); + + // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin + empty_wallet(); + + add_coin( 6*CENT); + add_coin( 7*CENT); + add_coin( 8*CENT); + add_coin(20*CENT); + add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total + + // check that we have 71 and not 72 + BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + + // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20 + BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); + + add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total + + // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20 + BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); + + add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30 + + // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18 + BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); // because in the event of a tie, the biggest coin wins + + // now try making 11 cents. we should get 5+6 + BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); + + // check that the smallest bigger coin is used + add_coin( 1*COIN); + add_coin( 2*COIN); + add_coin( 3*COIN); + add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents + BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); + + BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); + + // empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance + empty_wallet(); + add_coin(0.1*CENT); + add_coin(0.2*CENT); + add_coin(0.3*CENT); + add_coin(0.4*CENT); + add_coin(0.5*CENT); + + // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents + // we'll get sub-cent change whatever happens, so can expect 1.0 exactly + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + + // but if we add a bigger coin, making it possible to avoid sub-cent change, things change: + add_coin(1111*CENT); + + // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount + + // if we add more sub-cent coins: + add_coin(0.6*CENT); + add_coin(0.7*CENT); + + // and try again to make 1.0 cents, we can still make 1.0 cents + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount + + // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) + // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change + empty_wallet(); + for (int i = 0; i < 20; i++) + add_coin(50000 * COIN); + + BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount + BOOST_CHECK_EQUAL(setCoinsRet.size(), 10); // in ten coins + + // if there's not enough in the smaller coins to make at least 1 cent change (0.5+0.6+0.7 < 1.0+1.0), + // we need to try finding an exact subset anyway + + // sometimes it will fail, and so we use the next biggest coin: + empty_wallet(); + add_coin(0.5 * CENT); + add_coin(0.6 * CENT); + add_coin(0.7 * CENT); + add_coin(1111 * CENT); + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin + BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); + + // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) + empty_wallet(); + add_coin(0.4 * CENT); + add_coin(0.6 * CENT); + add_coin(0.8 * CENT); + add_coin(1111 * CENT); + BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); // in two coins 0.4+0.6 + + // test avoiding sub-cent change + empty_wallet(); + add_coin(0.0005 * COIN); + add_coin(0.01 * COIN); + add_coin(1 * COIN); + + // trying to make 1.0001 from these three coins + BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins + BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); + + // but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change + BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01 + BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); + + // test randomness + { + empty_wallet(); + for (int i2 = 0; i2 < 100; i2++) + add_coin(COIN); + + // picking 50 from 100 coins doesn't depend on the shuffle, + // but does depend on randomness in the stochastic approximation code + BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet2, nValueRet)); + BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2)); + + int fails = 0; + for (int i = 0; i < RANDOM_REPEATS; i++) + { + // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time + // run the test RANDOM_REPEATS times and only complain if all of them fail + BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet2, nValueRet)); + if (equal_sets(setCoinsRet, setCoinsRet2)) + fails++; + } + BOOST_CHECK_NE(fails, RANDOM_REPEATS); + + // add 75 cents in small change. not enough to make 90 cents, + // then try making 90 cents. there are multiple competing "smallest bigger" coins, + // one of which should be picked at random + add_coin( 5*CENT); add_coin(10*CENT); add_coin(15*CENT); add_coin(20*CENT); add_coin(25*CENT); + + fails = 0; + for (int i = 0; i < RANDOM_REPEATS; i++) + { + // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time + // run the test RANDOM_REPEATS times and only complain if all of them fail + BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet2, nValueRet)); + if (equal_sets(setCoinsRet, setCoinsRet2)) + fails++; + } + BOOST_CHECK_NE(fails, RANDOM_REPEATS); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/ui_interface.h b/src/ui_interface.h index 63d2e5c..0f7fdef 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -1,4 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2012 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_UI_INTERFACE_H @@ -6,45 +7,99 @@ #include #include "util.h" // for int64 +#include +#include -#define wxYES 0x00000002 -#define wxOK 0x00000004 -#define wxNO 0x00000008 -#define wxYES_NO (wxYES|wxNO) -#define wxCANCEL 0x00000010 -#define wxAPPLY 0x00000020 -#define wxCLOSE 0x00000040 -#define wxOK_DEFAULT 0x00000000 -#define wxYES_DEFAULT 0x00000000 -#define wxNO_DEFAULT 0x00000080 -#define wxCANCEL_DEFAULT 0x80000000 -#define wxICON_EXCLAMATION 0x00000100 -#define wxICON_HAND 0x00000200 -#define wxICON_WARNING wxICON_EXCLAMATION -#define wxICON_ERROR wxICON_HAND -#define wxICON_QUESTION 0x00000400 -#define wxICON_INFORMATION 0x00000800 -#define wxICON_STOP wxICON_HAND -#define wxICON_ASTERISK wxICON_INFORMATION -#define wxICON_MASK (0x00000100|0x00000200|0x00000400|0x00000800) -#define wxFORWARD 0x00001000 -#define wxBACKWARD 0x00002000 -#define wxRESET 0x00004000 -#define wxHELP 0x00008000 -#define wxMORE 0x00010000 -#define wxSETUP 0x00020000 -// Force blocking, modal message box dialog (not just notification) -#define wxMODAL 0x00040000 - -/* These UI communication functions are implemented in bitcoin.cpp (for ui) and noui.cpp (no ui) */ - -extern int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style=wxOK); -extern bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption); -extern void ThreadSafeHandleURI(const std::string& strURI); -extern void MainFrameRepaint(); -extern void AddressBookRepaint(); -extern void QueueShutdown(); -extern void InitMessage(const std::string &message); -extern std::string _(const char* psz); +class CBasicKeyStore; +class CWallet; +class uint256; + +/** General change type (added, updated, removed). */ +enum ChangeType +{ + CT_NEW, + CT_UPDATED, + CT_DELETED +}; + +/** Signals for UI communication. */ +class CClientUIInterface +{ +public: + /** Flags for CClientUIInterface::ThreadSafeMessageBox */ + enum MessageBoxFlags + { + YES = 0x00000002, + OK = 0x00000004, + NO = 0x00000008, + YES_NO = (YES|NO), + CANCEL = 0x00000010, + APPLY = 0x00000020, + CLOSE = 0x00000040, + OK_DEFAULT = 0x00000000, + YES_DEFAULT = 0x00000000, + NO_DEFAULT = 0x00000080, + CANCEL_DEFAULT = 0x80000000, + ICON_EXCLAMATION = 0x00000100, + ICON_HAND = 0x00000200, + ICON_WARNING = ICON_EXCLAMATION, + ICON_ERROR = ICON_HAND, + ICON_QUESTION = 0x00000400, + ICON_INFORMATION = 0x00000800, + ICON_STOP = ICON_HAND, + ICON_ASTERISK = ICON_INFORMATION, + ICON_MASK = (0x00000100|0x00000200|0x00000400|0x00000800), + FORWARD = 0x00001000, + BACKWARD = 0x00002000, + RESET = 0x00004000, + HELP = 0x00008000, + MORE = 0x00010000, + SETUP = 0x00020000, + // Force blocking, modal message box dialog (not just OS notification) + MODAL = 0x00040000 + }; + + /** Show message box. */ + boost::signals2::signal ThreadSafeMessageBox; + + /** Ask the user whether they want to pay a fee or not. */ + boost::signals2::signal > ThreadSafeAskFee; + + /** Handle a URL passed at the command line. */ + boost::signals2::signal ThreadSafeHandleURI; + + /** Progress message during initialization. */ + boost::signals2::signal InitMessage; + + /** Initiate client shutdown. */ + boost::signals2::signal QueueShutdown; + + /** Translate a message to the native language of the user. */ + boost::signals2::signal Translate; + + /** Block chain changed. */ + boost::signals2::signal NotifyBlocksChanged; + + /** Number of network connections changed. */ + boost::signals2::signal NotifyNumConnectionsChanged; + + /** + * New, updated or cancelled alert. + * @note called with lock cs_mapAlerts held. + */ + boost::signals2::signal NotifyAlertChanged; +}; + +extern CClientUIInterface uiInterface; + +/** + * Translation function: Call Translate signal on UI interface, which returns a boost::optional result. + * If no translation slot is registered, nothing is returned, and simply return the input. + */ +inline std::string _(const char* psz) +{ + boost::optional rv = uiInterface.Translate(psz); + return rv ? (*rv) : psz; +} #endif diff --git a/src/uint256.h b/src/uint256.h index fc5ed26..abd0b71 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -306,7 +306,7 @@ public: psz += 2; // hex string to uint - static unsigned char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 }; + static const unsigned char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 }; const char* pbegin = psz; while (phexdigit[(unsigned char)*psz] || *psz == '0') psz++; diff --git a/src/util.cpp b/src/util.cpp index 0c31324..95bc3a9 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,10 +1,10 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "util.h" +#include "sync.h" #include "strlcpy.h" #include "version.h" #include "ui_interface.h" @@ -24,12 +24,11 @@ namespace boost { #include #include #include -#include -#include #include #include #include #include +#include #ifdef WIN32 #ifdef _MSC_VER @@ -45,25 +44,28 @@ namespace boost { #ifdef _WIN32_IE #undef _WIN32_IE #endif -#define _WIN32_IE 0x0400 +#define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif +#include /* for _commit */ #include "shlobj.h" -#include "shlwapi.h" +#elif defined(__linux__) +# include #endif #ifndef WIN32 #include #endif + using namespace std; -using namespace boost; map mapArgs; map > mapMultiArgs; bool fDebug = false; +bool fDebugNet = false; bool fPrintToConsole = false; bool fPrintToDebugger = false; bool fRequestShutdown = false; @@ -76,27 +78,31 @@ bool fTestNet = false; bool fNoListen = false; bool fLogTimestamps = false; CMedianFilter vTimeOffsets(200,0); +bool fReopenDebugLog = false; -// Init openssl library multithreading support -static boost::interprocess::interprocess_mutex** ppmutexOpenSSL; +// Init OpenSSL library multithreading support +static CCriticalSection** ppmutexOpenSSL; void locking_callback(int mode, int i, const char* file, int line) { - if (mode & CRYPTO_LOCK) - ppmutexOpenSSL[i]->lock(); - else - ppmutexOpenSSL[i]->unlock(); + if (mode & CRYPTO_LOCK) { + ENTER_CRITICAL_SECTION(*ppmutexOpenSSL[i]); + } else { + LEAVE_CRITICAL_SECTION(*ppmutexOpenSSL[i]); + } } +LockedPageManager LockedPageManager::instance; + // Init class CInit { public: CInit() { - // Init openssl library multithreading support - ppmutexOpenSSL = (boost::interprocess::interprocess_mutex**)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(boost::interprocess::interprocess_mutex*)); + // Init OpenSSL library multithreading support + ppmutexOpenSSL = (CCriticalSection**)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(CCriticalSection*)); for (int i = 0; i < CRYPTO_num_locks(); i++) - ppmutexOpenSSL[i] = new boost::interprocess::interprocess_mutex(); + ppmutexOpenSSL[i] = new CCriticalSection(); CRYPTO_set_locking_callback(locking_callback); #ifdef WIN32 @@ -109,7 +115,7 @@ public: } ~CInit() { - // Shutdown openssl library multithreading support + // Shutdown OpenSSL library multithreading support CRYPTO_set_locking_callback(NULL); for (int i = 0; i < CRYPTO_num_locks(); i++) delete ppmutexOpenSSL[i]; @@ -155,7 +161,7 @@ void RandAddSeedPerfmon() { RAND_add(pdata, nSize, nSize/100.0); memset(pdata, 0, nSize); - printf("%s RandAddSeed() %d bytes\n", DateTimeStrFormat(GetTime()).c_str(), nSize); + printf("RandAddSeed() %lu bytes\n", nSize); } #endif } @@ -192,8 +198,6 @@ uint256 GetRandHash() - - static FILE* fileout = NULL; inline int OutputDebugStringF(const char* pszFormat, ...) @@ -207,9 +211,10 @@ inline int OutputDebugStringF(const char* pszFormat, ...) ret = vprintf(pszFormat, arg_ptr); va_end(arg_ptr); } - else + else if (!fPrintToDebugger) { // print to debug.log + if (!fileout) { boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; @@ -219,12 +224,26 @@ inline int OutputDebugStringF(const char* pszFormat, ...) if (fileout) { static bool fStartedNewLine = true; - static boost::mutex mutexDebugLog; - boost::mutex::scoped_lock scoped_lock(mutexDebugLog); + + // This routine may be called by global destructors during shutdown. + // Since the order of destruction of static/global objects is undefined, + // allocate mutexDebugLog on the heap the first time this routine + // is called to avoid crashes during shutdown. + static boost::mutex* mutexDebugLog = NULL; + if (mutexDebugLog == NULL) mutexDebugLog = new boost::mutex(); + boost::mutex::scoped_lock scoped_lock(*mutexDebugLog); + + // reopen the log file, if requested + if (fReopenDebugLog) { + fReopenDebugLog = false; + boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; + if (freopen(pathDebug.string().c_str(),"a",fileout) != NULL) + setbuf(fileout, NULL); // unbuffered + } // Debug print useful for profiling if (fLogTimestamps && fStartedNewLine) - fprintf(fileout, "%s ", DateTimeStrFormat(GetTime()).c_str()); + fprintf(fileout, "%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); if (pszFormat[strlen(pszFormat) - 1] == '\n') fStartedNewLine = true; else @@ -242,68 +261,30 @@ inline int OutputDebugStringF(const char* pszFormat, ...) { static CCriticalSection cs_OutputDebugStringF; - // accumulate a line at a time + // accumulate and output a line at a time { LOCK(cs_OutputDebugStringF); - static char pszBuffer[50000]; - static char* pend; - if (pend == NULL) - pend = pszBuffer; + static std::string buffer; + va_list arg_ptr; va_start(arg_ptr, pszFormat); - int limit = END(pszBuffer) - pend - 2; - int ret = _vsnprintf(pend, limit, pszFormat, arg_ptr); + buffer += vstrprintf(pszFormat, arg_ptr); va_end(arg_ptr); - if (ret < 0 || ret >= limit) - { - pend = END(pszBuffer) - 2; - *pend++ = '\n'; - } - else - pend += ret; - *pend = '\0'; - char* p1 = pszBuffer; - char* p2; - while ((p2 = strchr(p1, '\n'))) + + int line_start = 0, line_end; + while((line_end = buffer.find('\n', line_start)) != -1) { - p2++; - char c = *p2; - *p2 = '\0'; - OutputDebugStringA(p1); - *p2 = c; - p1 = p2; + OutputDebugStringA(buffer.substr(line_start, line_end - line_start).c_str()); + line_start = line_end + 1; } - if (p1 != pszBuffer) - memmove(pszBuffer, p1, pend - p1 + 1); - pend -= (p1 - pszBuffer); + buffer.erase(0, line_start); } } #endif return ret; } - -// Safer snprintf -// - prints up to limit-1 characters -// - output string is always null terminated even if limit reached -// - return value is the number of characters actually printed -int my_snprintf(char* buffer, size_t limit, const char* format, ...) -{ - if (limit == 0) - return 0; - va_list arg_ptr; - va_start(arg_ptr, format); - int ret = _vsnprintf(buffer, limit, format, arg_ptr); - va_end(arg_ptr); - if (ret < 0 || ret >= (int)limit) - { - ret = limit - 1; - buffer[limit-1] = 0; - } - return ret; -} - -string real_strprintf(const std::string &format, int dummy, ...) +string vstrprintf(const char *format, va_list ap) { char buffer[50000]; char* p = buffer; @@ -312,8 +293,12 @@ string real_strprintf(const std::string &format, int dummy, ...) loop { va_list arg_ptr; - va_start(arg_ptr, dummy); - ret = _vsnprintf(p, limit, format.c_str(), arg_ptr); + va_copy(arg_ptr, ap); +#ifdef WIN32 + ret = _vsnprintf(p, limit, format, arg_ptr); +#else + ret = vsnprintf(p, limit, format, arg_ptr); +#endif va_end(arg_ptr); if (ret >= 0 && ret < limit) break; @@ -330,19 +315,31 @@ string real_strprintf(const std::string &format, int dummy, ...) return str; } +string real_strprintf(const char *format, int dummy, ...) +{ + va_list arg_ptr; + va_start(arg_ptr, dummy); + string str = vstrprintf(format, arg_ptr); + va_end(arg_ptr); + return str; +} + +string real_strprintf(const std::string &format, int dummy, ...) +{ + va_list arg_ptr; + va_start(arg_ptr, dummy); + string str = vstrprintf(format.c_str(), arg_ptr); + va_end(arg_ptr); + return str; +} + bool error(const char *format, ...) { - char buffer[50000]; - int limit = sizeof(buffer); va_list arg_ptr; va_start(arg_ptr, format); - int ret = _vsnprintf(buffer, limit, format, arg_ptr); + std::string str = vstrprintf(format, arg_ptr); va_end(arg_ptr); - if (ret < 0 || ret >= limit) - { - buffer[limit-1] = 0; - } - printf("ERROR: %s\n", buffer); + printf("ERROR: %s\n", str.c_str()); return false; } @@ -374,9 +371,9 @@ string FormatMoney(int64 n, bool fPlus) int64 n_abs = (n > 0 ? n : -n); int64 quotient = n_abs/COIN; int64 remainder = n_abs%COIN; - string str = strprintf("%"PRI64d".%06"PRI64d, quotient, remainder); + string str = strprintf("%"PRI64d".%08"PRI64d, quotient, remainder); - // Right-trim excess 0's before the decimal point: + // Right-trim excess zeros before the decimal point: int nTrim = 0; for (int i = str.size()-1; (str[i] == '0' && isdigit(str[i-2])); --i) ++nTrim; @@ -437,7 +434,7 @@ bool ParseMoney(const char* pszIn, int64& nRet) } -static signed char phexdigit[256] = +static const signed char phexdigit[256] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, @@ -506,7 +503,7 @@ static void InterpretNegativeSetting(string name, map& mapSettin } } -void ParseParameters(int argc, const char*const argv[]) +void ParseParameters(int argc, const char* const argv[]) { mapArgs.clear(); mapMultiArgs.clear(); @@ -733,6 +730,193 @@ string DecodeBase64(const string& str) return string((const char*)&vchRet[0], vchRet.size()); } +string EncodeBase32(const unsigned char* pch, size_t len) +{ + static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; + + string strRet=""; + strRet.reserve((len+4)/5*8); + + int mode=0, left=0; + const unsigned char *pchEnd = pch+len; + + while (pch> 3]; + left = (enc & 7) << 2; + mode = 1; + break; + + case 1: // we have three bits + strRet += pbase32[left | (enc >> 6)]; + strRet += pbase32[(enc >> 1) & 31]; + left = (enc & 1) << 4; + mode = 2; + break; + + case 2: // we have one bit + strRet += pbase32[left | (enc >> 4)]; + left = (enc & 15) << 1; + mode = 3; + break; + + case 3: // we have four bits + strRet += pbase32[left | (enc >> 7)]; + strRet += pbase32[(enc >> 2) & 31]; + left = (enc & 3) << 3; + mode = 4; + break; + + case 4: // we have two bits + strRet += pbase32[left | (enc >> 5)]; + strRet += pbase32[enc & 31]; + mode = 0; + } + } + + static const int nPadding[5] = {0, 6, 4, 3, 1}; + if (mode) + { + strRet += pbase32[left]; + for (int n=0; n DecodeBase32(const char* p, bool* pfInvalid) +{ + static const int decode32_table[256] = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 0, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + + if (pfInvalid) + *pfInvalid = false; + + vector vchRet; + vchRet.reserve((strlen(p))*5/8); + + int mode = 0; + int left = 0; + + while (1) + { + int dec = decode32_table[(unsigned char)*p]; + if (dec == -1) break; + p++; + switch (mode) + { + case 0: // we have no bits and get 5 + left = dec; + mode = 1; + break; + + case 1: // we have 5 bits and keep 2 + vchRet.push_back((left<<3) | (dec>>2)); + left = dec & 3; + mode = 2; + break; + + case 2: // we have 2 bits and keep 7 + left = left << 5 | dec; + mode = 3; + break; + + case 3: // we have 7 bits and keep 4 + vchRet.push_back((left<<1) | (dec>>4)); + left = dec & 15; + mode = 4; + break; + + case 4: // we have 4 bits, and keep 1 + vchRet.push_back((left<<4) | (dec>>1)); + left = dec & 1; + mode = 5; + break; + + case 5: // we have 1 bit, and keep 6 + left = left << 5 | dec; + mode = 6; + break; + + case 6: // we have 6 bits, and keep 3 + vchRet.push_back((left<<2) | (dec>>3)); + left = dec & 7; + mode = 7; + break; + + case 7: // we have 3 bits, and keep 0 + vchRet.push_back((left<<5) | dec); + mode = 0; + break; + } + } + + if (pfInvalid) + switch (mode) + { + case 0: // 8n base32 characters processed: ok + break; + + case 1: // 8n+1 base32 characters processed: impossible + case 3: // +3 + case 6: // +6 + *pfInvalid = true; + break; + + case 2: // 8n+2 base32 characters processed: require '======' + if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || p[3] != '=' || p[4] != '=' || p[5] != '=' || decode32_table[(unsigned char)p[6]] != -1) + *pfInvalid = true; + break; + + case 4: // 8n+4 base32 characters processed: require '====' + if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || p[3] != '=' || decode32_table[(unsigned char)p[4]] != -1) + *pfInvalid = true; + break; + + case 5: // 8n+5 base32 characters processed: require '===' + if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || decode32_table[(unsigned char)p[3]] != -1) + *pfInvalid = true; + break; + + case 7: // 8n+7 base32 characters processed: require '=' + if (left || p[0] != '=' || decode32_table[(unsigned char)p[1]] != -1) + *pfInvalid = true; + break; + } + + return vchRet; +} + +string DecodeBase32(const string& str) +{ + vector vchRet = DecodeBase32(str.c_str()); + return string((const char*)&vchRet[0], vchRet.size()); +} + bool WildcardMatch(const char* psz, const char* mask) { @@ -770,37 +954,34 @@ bool WildcardMatch(const string& str, const string& mask) -void FormatException(char* pszMessage, std::exception* pex, const char* pszThread) +static std::string FormatException(std::exception* pex, const char* pszThread) { #ifdef WIN32 - char pszModule[MAX_PATH]; - pszModule[0] = '\0'; + char pszModule[MAX_PATH] = ""; GetModuleFileNameA(NULL, pszModule, sizeof(pszModule)); #else const char* pszModule = "novacoin"; #endif if (pex) - snprintf(pszMessage, 1000, + return strprintf( "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); else - snprintf(pszMessage, 1000, + return strprintf( "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); } void LogException(std::exception* pex, const char* pszThread) { - char pszMessage[10000]; - FormatException(pszMessage, pex, pszThread); - printf("\n%s", pszMessage); + std::string message = FormatException(pex, pszThread); + printf("\n%s", message.c_str()); } void PrintException(std::exception* pex, const char* pszThread) { - char pszMessage[10000]; - FormatException(pszMessage, pex, pszThread); - printf("\n\n************************\n%s\n", pszMessage); - fprintf(stderr, "\n\n************************\n%s\n", pszMessage); - strMiscWarning = pszMessage; + std::string message = FormatException(pex, pszThread); + printf("\n\n************************\n%s\n", message.c_str()); + fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); + strMiscWarning = message; throw; } @@ -819,45 +1000,22 @@ void LogStackTrace() { void PrintExceptionContinue(std::exception* pex, const char* pszThread) { - char pszMessage[10000]; - FormatException(pszMessage, pex, pszThread); - printf("\n\n************************\n%s\n", pszMessage); - fprintf(stderr, "\n\n************************\n%s\n", pszMessage); - strMiscWarning = pszMessage; -} - -#ifdef WIN32 -boost::filesystem::path MyGetSpecialFolderPath(int nFolder, bool fCreate) -{ - namespace fs = boost::filesystem; - - char pszPath[MAX_PATH] = ""; - if(SHGetSpecialFolderPathA(NULL, pszPath, nFolder, fCreate)) - { - return fs::path(pszPath); - } - else if (nFolder == CSIDL_STARTUP) - { - return fs::path(getenv("USERPROFILE")) / "Start Menu" / "Programs" / "Startup"; - } - else if (nFolder == CSIDL_APPDATA) - { - return fs::path(getenv("APPDATA")); - } - return fs::path(""); + std::string message = FormatException(pex, pszThread); + printf("\n\n************************\n%s\n", message.c_str()); + fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); + strMiscWarning = message; } -#endif boost::filesystem::path GetDefaultDataDir() { namespace fs = boost::filesystem; - - // Windows: C:\Documents and Settings\username\Application Data\NovaCoin + // Windows < Vista: C:\Documents and Settings\Username\Application Data\NovaCoin + // Windows >= Vista: C:\Users\Username\AppData\Roaming\NovaCoin // Mac: ~/Library/Application Support/NovaCoin - // Unix: ~/.ppcoin + // Unix: ~/.novacoin #ifdef WIN32 // Windows - return MyGetSpecialFolderPath(CSIDL_APPDATA, true) / "NovaCoin"; + return GetSpecialFolderPath(CSIDL_APPDATA) / "NovaCoin"; #else fs::path pathRet; char* pszHome = getenv("HOME"); @@ -914,9 +1072,7 @@ const boost::filesystem::path &GetDataDir(bool fNetSpecific) boost::filesystem::path GetConfigFile() { - namespace fs = boost::filesystem; - - fs::path pathConfigFile(GetArg("-conf", "novacoin.conf")); + boost::filesystem::path pathConfigFile(GetArg("-conf", "novacoin.conf")); if (!pathConfigFile.is_complete()) pathConfigFile = GetDataDir(false) / pathConfigFile; return pathConfigFile; } @@ -924,24 +1080,21 @@ boost::filesystem::path GetConfigFile() void ReadConfigFile(map& mapSettingsRet, map >& mapMultiSettingsRet) { - namespace fs = boost::filesystem; - namespace pod = boost::program_options::detail; - - fs::ifstream streamConfig(GetConfigFile()); + boost::filesystem::ifstream streamConfig(GetConfigFile()); if (!streamConfig.good()) return; // No bitcoin.conf file is OK set setOptions; setOptions.insert("*"); - for (pod::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) + for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) { // Don't overwrite existing settings so command line settings override bitcoin.conf string strKey = string("-") + it->string_key; if (mapSettingsRet.count(strKey) == 0) { mapSettingsRet[strKey] = it->value[0]; - // interpret nofoo=1 as foo=0 (and nofoo=0 as foo=1) as long as foo not set) + // interpret nofoo=1 as foo=0 (and nofoo=0 as foo=1) as long as foo not set) InterpretNegativeSetting(strKey, mapSettingsRet); } mapMultiSettingsRet[strKey].push_back(it->value[0]); @@ -950,9 +1103,7 @@ void ReadConfigFile(map& mapSettingsRet, boost::filesystem::path GetPidFile() { - namespace fs = boost::filesystem; - - fs::path pathPidFile(GetArg("-pid", "novacoind.pid")); + boost::filesystem::path pathPidFile(GetArg("-pid", "novacoind.pid")); if (!pathPidFile.is_complete()) pathPidFile = GetDataDir() / pathPidFile; return pathPidFile; } @@ -967,6 +1118,27 @@ void CreatePidFile(const boost::filesystem::path &path, pid_t pid) } } +bool RenameOver(boost::filesystem::path src, boost::filesystem::path dest) +{ +#ifdef WIN32 + return MoveFileExA(src.string().c_str(), dest.string().c_str(), + MOVEFILE_REPLACE_EXISTING); +#else + int rc = std::rename(src.string().c_str(), dest.string().c_str()); + return (rc == 0); +#endif /* WIN32 */ +} + +void FileCommit(FILE *fileout) +{ + fflush(fileout); // harmless if redundantly called +#ifdef WIN32 + _commit(_fileno(fileout)); +#else + fsync(fileno(fileout)); +#endif +} + int GetFilesize(FILE* file) { int nSavePos = ftell(file); @@ -1010,7 +1182,7 @@ void ShrinkDebugFile() // "Never go to sea with two chronometers; take one or three." // Our three time sources are: // - System clock -// - Median of other nodes's clocks +// - Median of other nodes clocks // - The user (asking the user to fix the system clock if the first two disagree) // static int64 nMockTime = 0; // For unit testing @@ -1071,10 +1243,10 @@ void AddTimeData(const CNetAddr& ip, int64 nTime) if (!fMatch) { fDone = true; - string strMessage = _("Warning: Please check that your computer's date and time are correct. If your clock is wrong NovaCoin will not work properly."); + string strMessage = _("Warning: Please check that your computer's date and time are correct! If your clock is wrong NovaCoin will not work properly."); strMiscWarning = strMessage; printf("*** %s\n", strMessage.c_str()); - ThreadSafeMessageBox(strMessage+" ", string("NovaCoin"), wxOK | wxICON_EXCLAMATION); + uiInterface.ThreadSafeMessageBox(strMessage+" ", string("NovaCoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION); } } } @@ -1120,258 +1292,58 @@ std::string FormatSubVersion(const std::string& name, int nClientVersion, const } #ifdef WIN32 -boost::filesystem::path static StartupShortcutPath() -{ - return MyGetSpecialFolderPath(CSIDL_STARTUP, true) / "NovaCoin.lnk"; -} - -bool GetStartOnSystemStartup() -{ - return filesystem::exists(StartupShortcutPath()); -} - -bool SetStartOnSystemStartup(bool fAutoStart) -{ - // If the shortcut exists already, remove it for updating - boost::filesystem::remove(StartupShortcutPath()); - - if (fAutoStart) - { - CoInitialize(NULL); - - // Get a pointer to the IShellLink interface. - IShellLink* psl = NULL; - HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, - CLSCTX_INPROC_SERVER, IID_IShellLink, - reinterpret_cast(&psl)); - - if (SUCCEEDED(hres)) - { - // Get the current executable path - TCHAR pszExePath[MAX_PATH]; - GetModuleFileName(NULL, pszExePath, sizeof(pszExePath)); - - TCHAR pszArgs[5] = TEXT("-min"); - - // Set the path to the shortcut target - psl->SetPath(pszExePath); - PathRemoveFileSpec(pszExePath); - psl->SetWorkingDirectory(pszExePath); - psl->SetShowCmd(SW_SHOWMINNOACTIVE); - psl->SetArguments(pszArgs); - - // Query IShellLink for the IPersistFile interface for - // saving the shortcut in persistent storage. - IPersistFile* ppf = NULL; - hres = psl->QueryInterface(IID_IPersistFile, - reinterpret_cast(&ppf)); - if (SUCCEEDED(hres)) - { - WCHAR pwsz[MAX_PATH]; - // Ensure that the string is ANSI. - MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); - // Save the link by calling IPersistFile::Save. - hres = ppf->Save(pwsz, TRUE); - ppf->Release(); - psl->Release(); - CoUninitialize(); - return true; - } - psl->Release(); - } - CoUninitialize(); - return false; - } - return true; -} - -#elif defined(LINUX) - -// Follow the Desktop Application Autostart Spec: -// http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html - -boost::filesystem::path static GetAutostartDir() +boost::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate) { namespace fs = boost::filesystem; - char* pszConfigHome = getenv("XDG_CONFIG_HOME"); - if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; - char* pszHome = getenv("HOME"); - if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; - return fs::path(); -} - -boost::filesystem::path static GetAutostartFilePath() -{ - return GetAutostartDir() / "novacoin.desktop"; -} + char pszPath[MAX_PATH] = ""; -bool GetStartOnSystemStartup() -{ - boost::filesystem::ifstream optionFile(GetAutostartFilePath()); - if (!optionFile.good()) - return false; - // Scan through file for "Hidden=true": - string line; - while (!optionFile.eof()) + if(SHGetSpecialFolderPathA(NULL, pszPath, nFolder, fCreate)) { - getline(optionFile, line); - if (line.find("Hidden") != string::npos && - line.find("true") != string::npos) - return false; + return fs::path(pszPath); } - optionFile.close(); - - return true; -} -bool SetStartOnSystemStartup(bool fAutoStart) -{ - if (!fAutoStart) - boost::filesystem::remove(GetAutostartFilePath()); - else - { - char pszExePath[MAX_PATH+1]; - memset(pszExePath, 0, sizeof(pszExePath)); - if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)-1) == -1) - return false; - - boost::filesystem::create_directories(GetAutostartDir()); - - boost::filesystem::ofstream optionFile(GetAutostartFilePath(), ios_base::out|ios_base::trunc); - if (!optionFile.good()) - return false; - // Write a bitcoin.desktop file to the autostart directory: - optionFile << "[Desktop Entry]\n"; - optionFile << "Type=Application\n"; - optionFile << "Name=NovaCoin\n"; - optionFile << "Exec=" << pszExePath << " -min\n"; - optionFile << "Terminal=false\n"; - optionFile << "Hidden=false\n"; - optionFile.close(); - } - return true; + printf("SHGetSpecialFolderPathA() failed, could not obtain requested path.\n"); + return fs::path(""); } -#else - -// TODO: OSX startup stuff; see: -// http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/CustomLogin.html - -bool GetStartOnSystemStartup() { return false; } -bool SetStartOnSystemStartup(bool fAutoStart) { return false; } - #endif - - -#ifdef DEBUG_LOCKORDER -// -// Early deadlock detection. -// Problem being solved: -// Thread 1 locks A, then B, then C -// Thread 2 locks D, then C, then A -// --> may result in deadlock between the two threads, depending on when they run. -// Solution implemented here: -// Keep track of pairs of locks: (A before B), (A before C), etc. -// Complain if any thread trys to lock in a different order. -// - -struct CLockLocation -{ - CLockLocation(const char* pszName, const char* pszFile, int nLine) - { - mutexName = pszName; - sourceFile = pszFile; - sourceLine = nLine; - } - - std::string ToString() const - { - return mutexName+" "+sourceFile+":"+itostr(sourceLine); - } - -private: - std::string mutexName; - std::string sourceFile; - int sourceLine; -}; - -typedef std::vector< std::pair > LockStack; - -static boost::interprocess::interprocess_mutex dd_mutex; -static std::map, LockStack> lockorders; -static boost::thread_specific_ptr lockstack; - - -static void potential_deadlock_detected(const std::pair& mismatch, const LockStack& s1, const LockStack& s2) +void runCommand(std::string strCommand) { - printf("POTENTIAL DEADLOCK DETECTED\n"); - printf("Previous lock order was:\n"); - BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, s2) - { - if (i.first == mismatch.first) printf(" (1)"); - if (i.first == mismatch.second) printf(" (2)"); - printf(" %s\n", i.second.ToString().c_str()); - } - printf("Current lock order is:\n"); - BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, s1) - { - if (i.first == mismatch.first) printf(" (1)"); - if (i.first == mismatch.second) printf(" (2)"); - printf(" %s\n", i.second.ToString().c_str()); - } + int nErr = ::system(strCommand.c_str()); + if (nErr) + printf("runCommand error: system(%s) returned %d\n", strCommand.c_str(), nErr); } -static void push_lock(void* c, const CLockLocation& locklocation, bool fTry) +void RenameThread(const char* name) { - bool fOrderOK = true; - if (lockstack.get() == NULL) - lockstack.reset(new LockStack); +#if defined(PR_SET_NAME) + // Only the first 15 characters are used (16 - NUL terminator) + ::prctl(PR_SET_NAME, name, 0, 0, 0); +#elif 0 && (defined(__FreeBSD__) || defined(__OpenBSD__)) + // TODO: This is currently disabled because it needs to be verified to work + // on FreeBSD or OpenBSD first. When verified the '0 &&' part can be + // removed. + pthread_set_name_np(pthread_self(), name); - if (fDebug) printf("Locking: %s\n", locklocation.ToString().c_str()); - dd_mutex.lock(); +// This is XCode 10.6-and-later; bring back if we drop 10.5 support: +// #elif defined(MAC_OSX) +// pthread_setname_np(name); - (*lockstack).push_back(std::make_pair(c, locklocation)); - - if (!fTry) BOOST_FOREACH(const PAIRTYPE(void*, CLockLocation)& i, (*lockstack)) - { - if (i.first == c) break; - - std::pair p1 = std::make_pair(i.first, c); - if (lockorders.count(p1)) - continue; - lockorders[p1] = (*lockstack); - - std::pair p2 = std::make_pair(c, i.first); - if (lockorders.count(p2)) - { - potential_deadlock_detected(p1, lockorders[p2], lockorders[p1]); - break; - } - } - dd_mutex.unlock(); +#else + // Prevent warnings for unused parameters... + (void)name; +#endif } -static void pop_lock() +bool NewThread(void(*pfn)(void*), void* parg) { - if (fDebug) + try { - const CLockLocation& locklocation = (*lockstack).rbegin()->second; - printf("Unlocked: %s\n", locklocation.ToString().c_str()); + boost::thread(pfn, parg); // thread detaches when out of scope + } catch(boost::thread_resource_error &e) { + printf("Error creating thread: %s\n", e.what()); + return false; } - dd_mutex.lock(); - (*lockstack).pop_back(); - dd_mutex.unlock(); -} - -void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry) -{ - push_lock(cs, CLockLocation(pszName, pszFile, nLine), fTry); -} - -void LeaveCritical() -{ - pop_lock(); + return true; } - -#endif /* DEBUG_LOCKORDER */ diff --git a/src/util.h b/src/util.h index 5f46849..672b23c 100644 --- a/src/util.h +++ b/src/util.h @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_UTIL_H @@ -13,7 +12,7 @@ #include #include #else -typedef int pid_t; /* define for windows compatiblity */ +typedef int pid_t; /* define for Windows compatibility */ #endif #include #include @@ -22,10 +21,6 @@ typedef int pid_t; /* define for windows compatiblity */ #include #include #include -#include -#include -#include -#include #include #include @@ -46,18 +41,12 @@ static const int64 CENT = 10000; #define UBEGIN(a) ((unsigned char*)&(a)) #define UEND(a) ((unsigned char*)&((&(a))[1])) #define ARRAYLEN(array) (sizeof(array)/sizeof((array)[0])) -#define printf OutputDebugStringF #define UVOIDBEGIN(a) ((void*)&(a)) #define CVOIDBEGIN(a) ((const void*)&(a)) #define UINTBEGIN(a) ((uint32_t*)&(a)) #define CUINTBEGIN(a) ((const uint32_t*)&(a)) -#ifdef snprintf -#undef snprintf -#endif -#define snprintf my_snprintf - #ifndef PRI64d #if defined(_MSC_VER) || defined(__MSVCRT__) #define PRI64d "I64d" @@ -70,6 +59,36 @@ static const int64 CENT = 10000; #endif #endif +#ifndef THROW_WITH_STACKTRACE +#define THROW_WITH_STACKTRACE(exception) \ +{ \ + LogStackTrace(); \ + throw (exception); \ +} +void LogStackTrace(); +#endif + + +/* Format characters for (s)size_t and ptrdiff_t */ +#if defined(_MSC_VER) || defined(__MSVCRT__) + /* (s)size_t and ptrdiff_t have the same size specifier in MSVC: + http://msdn.microsoft.com/en-us/library/tcxf1dw6%28v=vs.100%29.aspx + */ + #define PRIszx "Ix" + #define PRIszu "Iu" + #define PRIszd "Id" + #define PRIpdx "Ix" + #define PRIpdu "Iu" + #define PRIpdd "Id" +#else /* C99 standard */ + #define PRIszx "zx" + #define PRIszu "zu" + #define PRIszd "zd" + #define PRIpdx "tx" + #define PRIpdu "tu" + #define PRIpdd "td" +#endif + // This is needed because the foreach macro can't get over the comma in pair #define PAIRTYPE(t1, t2) std::pair @@ -95,11 +114,7 @@ T* alignup(T* p) #define S_IRUSR 0400 #define S_IWUSR 0200 #endif -#define unlink _unlink #else -#define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d) -#define strlwr(psz) to_lower(psz) -#define _strlwr(psz) to_lower(psz) #define MAX_PATH 1024 inline void Sleep(int64 n) { @@ -109,13 +124,14 @@ inline void Sleep(int64 n) } #endif -#ifndef THROW_WITH_STACKTRACE -#define THROW_WITH_STACKTRACE(exception) \ -{ \ - LogStackTrace(); \ - throw (exception); \ -} -void LogStackTrace(); +/* This GNU C extension enables the compiler to check the format string against the parameters provided. + * X is the number of the "format string" parameter, and Y is the number of the first variadic parameter. + * Parameters count from 1. + */ +#ifdef __GNUC__ +#define ATTR_WARN_PRINTF(X,Y) __attribute__((format(printf,X,Y))) +#else +#define ATTR_WARN_PRINTF(X,Y) #endif @@ -123,9 +139,12 @@ void LogStackTrace(); + + extern std::map mapArgs; extern std::map > mapMultiArgs; extern bool fDebug; +extern bool fDebugNet; extern bool fPrintToConsole; extern bool fPrintToDebugger; extern bool fRequestShutdown; @@ -137,20 +156,38 @@ extern std::string strMiscWarning; extern bool fTestNet; extern bool fNoListen; extern bool fLogTimestamps; +extern bool fReopenDebugLog; void RandAddSeed(); void RandAddSeedPerfmon(); -int OutputDebugStringF(const char* pszFormat, ...); -int my_snprintf(char* buffer, size_t limit, const char* format, ...); +int ATTR_WARN_PRINTF(1,2) OutputDebugStringF(const char* pszFormat, ...); -/* It is not allowed to use va_start with a pass-by-reference argument. - (C++ standard, 18.7, paragraph 3). Use a dummy argument to work around this, and use a - macro to keep similar semantics. +/* + Rationale for the real_strprintf / strprintf construction: + It is not allowed to use va_start with a pass-by-reference argument. + (C++ standard, 18.7, paragraph 3). Use a dummy argument to work around this, and use a + macro to keep similar semantics. */ + +/** Overload strprintf for char*, so that GCC format type warnings can be given */ +std::string ATTR_WARN_PRINTF(1,3) real_strprintf(const char *format, int dummy, ...); +/** Overload strprintf for std::string, to be able to use it with _ (translation). + * This will not support GCC format type warnings (-Wformat) so be careful. + */ std::string real_strprintf(const std::string &format, int dummy, ...); #define strprintf(format, ...) real_strprintf(format, 0, __VA_ARGS__) +std::string vstrprintf(const char *format, va_list ap); + +bool ATTR_WARN_PRINTF(1,2) error(const char *format, ...); + +/* Redefine printf so that it directs output to debug.log + * + * Do this *after* defining the other printf-like functions, because otherwise the + * __attribute__((format(printf,X,Y))) gets expanded to __attribute__((format(OutputDebugStringF,X,Y))) + * which confuses gcc. + */ +#define printf OutputDebugStringF -bool error(const char *format, ...); void LogException(std::exception* pex, const char* pszThread); void PrintException(std::exception* pex, const char* pszThread); void PrintExceptionContinue(std::exception* pex, const char* pszThread); @@ -165,18 +202,25 @@ std::vector DecodeBase64(const char* p, bool* pfInvalid = NULL); std::string DecodeBase64(const std::string& str); std::string EncodeBase64(const unsigned char* pch, size_t len); std::string EncodeBase64(const std::string& str); +std::vector DecodeBase32(const char* p, bool* pfInvalid = NULL); +std::string DecodeBase32(const std::string& str); +std::string EncodeBase32(const unsigned char* pch, size_t len); +std::string EncodeBase32(const std::string& str); void ParseParameters(int argc, const char*const argv[]); bool WildcardMatch(const char* psz, const char* mask); bool WildcardMatch(const std::string& str, const std::string& mask); +void FileCommit(FILE *fileout); int GetFilesize(FILE* file); +bool RenameOver(boost::filesystem::path src, boost::filesystem::path dest); boost::filesystem::path GetDefaultDataDir(); const boost::filesystem::path &GetDataDir(bool fNetSpecific = true); boost::filesystem::path GetConfigFile(); boost::filesystem::path GetPidFile(); void CreatePidFile(const boost::filesystem::path &path, pid_t pid); void ReadConfigFile(std::map& mapSettingsRet, std::map >& mapMultiSettingsRet); -bool GetStartOnSystemStartup(); -bool SetStartOnSystemStartup(bool fAutoStart); +#ifdef WIN32 +boost::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate = true); +#endif void ShrinkDebugFile(); int GetRandInt(int nMax); uint64 GetRand(uint64 nMax); @@ -187,6 +231,7 @@ int64 GetAdjustedTime(); std::string FormatFullVersion(); std::string FormatSubVersion(const std::string& name, int nClientVersion, const std::vector& comments); void AddTimeData(const CNetAddr& ip, int64 nTime); +void runCommand(std::string strCommand); @@ -196,154 +241,6 @@ void AddTimeData(const CNetAddr& ip, int64 nTime); - - -/** Wrapped boost mutex: supports recursive locking, but no waiting */ -typedef boost::interprocess::interprocess_recursive_mutex CCriticalSection; - -/** Wrapped boost mutex: supports waiting but not recursive locking */ -typedef boost::interprocess::interprocess_mutex CWaitableCriticalSection; - -#ifdef DEBUG_LOCKORDER -void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false); -void LeaveCritical(); -#else -void static inline EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false) {} -void static inline LeaveCritical() {} -#endif - -/** Wrapper around boost::interprocess::scoped_lock */ -template -class CMutexLock -{ -private: - boost::interprocess::scoped_lock lock; -public: - - void Enter(const char* pszName, const char* pszFile, int nLine) - { - if (!lock.owns()) - { - EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex())); -#ifdef DEBUG_LOCKCONTENTION - if (!lock.try_lock()) - { - printf("LOCKCONTENTION: %s\n", pszName); - printf("Locker: %s:%d\n", pszFile, nLine); -#endif - lock.lock(); -#ifdef DEBUG_LOCKCONTENTION - } -#endif - } - } - - void Leave() - { - if (lock.owns()) - { - lock.unlock(); - LeaveCritical(); - } - } - - bool TryEnter(const char* pszName, const char* pszFile, int nLine) - { - if (!lock.owns()) - { - EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true); - lock.try_lock(); - if (!lock.owns()) - LeaveCritical(); - } - return lock.owns(); - } - - CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) : lock(mutexIn, boost::interprocess::defer_lock) - { - if (fTry) - TryEnter(pszName, pszFile, nLine); - else - Enter(pszName, pszFile, nLine); - } - - ~CMutexLock() - { - if (lock.owns()) - LeaveCritical(); - } - - operator bool() - { - return lock.owns(); - } - - boost::interprocess::scoped_lock &GetLock() - { - return lock; - } -}; - -typedef CMutexLock CCriticalBlock; - -#define LOCK(cs) CCriticalBlock criticalblock(cs, #cs, __FILE__, __LINE__) -#define LOCK2(cs1,cs2) CCriticalBlock criticalblock1(cs1, #cs1, __FILE__, __LINE__),criticalblock2(cs2, #cs2, __FILE__, __LINE__) -#define TRY_LOCK(cs,name) CCriticalBlock name(cs, #cs, __FILE__, __LINE__, true) - -#define ENTER_CRITICAL_SECTION(cs) \ - { \ - EnterCritical(#cs, __FILE__, __LINE__, (void*)(&cs)); \ - (cs).lock(); \ - } - -#define LEAVE_CRITICAL_SECTION(cs) \ - { \ - (cs).unlock(); \ - LeaveCritical(); \ - } - -#ifdef MAC_OSX -// boost::interprocess::interprocess_semaphore seems to spinlock on OSX; prefer polling instead -class CSemaphore -{ -private: - CCriticalSection cs; - int val; - -public: - CSemaphore(int init) : val(init) {} - - void wait() { - do { - { - LOCK(cs); - if (val>0) { - val--; - return; - } - } - Sleep(100); - } while(1); - } - - bool try_wait() { - LOCK(cs); - if (val>0) { - val--; - return true; - } - return false; - } - - void post() { - LOCK(cs); - val++; - } -}; -#else -typedef boost::interprocess::interprocess_semaphore CSemaphore; -#endif - inline std::string i64tostr(int64 n) { return strprintf("%"PRI64d, n); @@ -395,9 +292,9 @@ inline int64 abs64(int64 n) template std::string HexStr(const T itbegin, const T itend, bool fSpaces=false) { - std::vector rv; - static char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + std::string rv; + static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; rv.reserve((itend-itbegin)*3); for(T it = itbegin; it < itend; ++it) { @@ -408,7 +305,7 @@ std::string HexStr(const T itbegin, const T itend, bool fSpaces=false) rv.push_back(hexmap[val&15]); } - return std::string(rv.begin(), rv.end()); + return rv; } inline std::string HexStr(const std::vector& vch, bool fSpaces=false) @@ -435,7 +332,7 @@ inline int64 GetPerformanceCounter() #else timeval t; gettimeofday(&t, NULL); - nCounter = t.tv_sec * 1000000 + t.tv_usec; + nCounter = (int64) t.tv_sec * 1000000 + t.tv_usec; #endif return nCounter; } @@ -461,6 +358,7 @@ inline std::string DateTimeStrFormat(int64 nTime) return DateTimeStrFormat(strTimestampFormat.c_str(), nTime); } + template void skipspaces(T& it) { @@ -530,20 +428,6 @@ bool SoftSetBoolArg(const std::string& strArg, bool fValue); -// Randomize the stack to help protect against buffer overrun exploits -#define IMPLEMENT_RANDOMIZE_STACK(ThreadFn) \ - { \ - static char nLoops; \ - if (nLoops <= 0) \ - nLoops = GetRand(20) + 1; \ - if (nLoops-- > 1) \ - { \ - ThreadFn; \ - return; \ - } \ - } - - template inline uint256 Hash(const T1 pbegin, const T1 pend) { @@ -555,6 +439,46 @@ inline uint256 Hash(const T1 pbegin, const T1 pend) return hash2; } +class CHashWriter +{ +private: + SHA256_CTX ctx; + +public: + int nType; + int nVersion; + + void Init() { + SHA256_Init(&ctx); + } + + CHashWriter(int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn) { + Init(); + } + + CHashWriter& write(const char *pch, size_t size) { + SHA256_Update(&ctx, pch, size); + return (*this); + } + + // invalidates the object + uint256 GetHash() { + uint256 hash1; + SHA256_Final((unsigned char*)&hash1, &ctx); + uint256 hash2; + SHA256((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2); + return hash2; + } + + template + CHashWriter& operator<<(const T& obj) { + // Serialize to this stream + ::Serialize(*this, obj, nType, nVersion); + return (*this); + } +}; + + template inline uint256 Hash(const T1 p1begin, const T1 p1end, const T2 p2begin, const T2 p2end) @@ -592,13 +516,9 @@ inline uint256 Hash(const T1 p1begin, const T1 p1end, template uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION) { - // Most of the time is spent allocating and deallocating CDataStream's - // buffer. If this ever needs to be optimized further, make a CStaticStream - // class with its buffer on the stack. - CDataStream ss(nType, nVersion); - ss.reserve(10000); + CHashWriter ss(nType, nVersion); ss << obj; - return Hash(ss.begin(), ss.end()); + return ss.GetHash(); } inline uint160 Hash160(const std::vector& vch) @@ -611,7 +531,7 @@ inline uint160 Hash160(const std::vector& vch) } -/** Median filter over a stream of values. +/** Median filter over a stream of values. * Returns the median of the last N numbers */ template class CMedianFilter @@ -628,7 +548,7 @@ public: vValues.push_back(initial_value); vSorted = vValues; } - + void input(T value) { if(vValues.size() == nSize) @@ -667,65 +587,14 @@ public: } }; +bool NewThread(void(*pfn)(void*), void* parg); - - - - - - - - -// Note: It turns out we might have been able to use boost::thread -// by using TerminateThread(boost::thread.native_handle(), 0); #ifdef WIN32 -typedef HANDLE bitcoin_pthread_t; - -inline bitcoin_pthread_t CreateThread(void(*pfn)(void*), void* parg, bool fWantHandle=false) -{ - DWORD nUnused = 0; - HANDLE hthread = - CreateThread( - NULL, // default security - 0, // inherit stack size from parent - (LPTHREAD_START_ROUTINE)pfn, // function pointer - parg, // argument - 0, // creation option, start immediately - &nUnused); // thread identifier - if (hthread == NULL) - { - printf("Error: CreateThread() returned %d\n", GetLastError()); - return (bitcoin_pthread_t)0; - } - if (!fWantHandle) - { - CloseHandle(hthread); - return (bitcoin_pthread_t)-1; - } - return hthread; -} - inline void SetThreadPriority(int nPriority) { SetThreadPriority(GetCurrentThread(), nPriority); } #else -inline pthread_t CreateThread(void(*pfn)(void*), void* parg, bool fWantHandle=false) -{ - pthread_t hthread = 0; - int ret = pthread_create(&hthread, NULL, (void*(*)(void*))pfn, parg); - if (ret != 0) - { - printf("Error: pthread_create() returned %d\n", ret); - return (pthread_t)0; - } - if (!fWantHandle) - { - pthread_detach(hthread); - return (pthread_t)-1; - } - return hthread; -} #define THREAD_PRIORITY_LOWEST PRIO_MAX #define THREAD_PRIORITY_BELOW_NORMAL 2 @@ -749,9 +618,7 @@ inline void ExitThread(size_t nExitCode) } #endif - - - +void RenameThread(const char* name); inline uint32_t ByteReverse(uint32_t value) { diff --git a/src/version.cpp b/src/version.cpp index 3ae1784..5e5a3bd 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -33,25 +33,24 @@ const std::string CLIENT_NAME("Satoshi"); # include "build.h" #endif -// git will put "#define GIT_ARCHIVE 1" on the next line inside archives. $Format:%n#define GIT_ARCHIVE 1$ +// git will put "#define GIT_ARCHIVE 1" on the next line inside archives. +#define GIT_ARCHIVE 1 #ifdef GIT_ARCHIVE -# define GIT_COMMIT_ID "$Format:%h$" +# define GIT_COMMIT_ID "32a928e" # define GIT_COMMIT_DATE "$Format:%cD" #endif -#define STRINGIFY(s) #s - #define BUILD_DESC_FROM_COMMIT(maj,min,rev,build,commit) \ - "v" STRINGIFY(maj) "." STRINGIFY(min) "." STRINGIFY(rev) "." STRINGIFY(build) "-g" commit + "v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "-g" commit #define BUILD_DESC_FROM_UNKNOWN(maj,min,rev,build) \ - "v" STRINGIFY(maj) "." STRINGIFY(min) "." STRINGIFY(rev) "." STRINGIFY(build) "-unk" + "v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "-unk" #ifndef BUILD_DESC # ifdef GIT_COMMIT_ID -# define BUILD_DESC BUILD_DESC_FROM_COMMIT(PPCOIN_VERSION_MAJOR, PPCOIN_VERSION_MINOR, PPCOIN_VERSION_REVISION, PPCOIN_VERSION_BUILD, GIT_COMMIT_ID) +# define BUILD_DESC BUILD_DESC_FROM_COMMIT(DISPLAY_VERSION_MAJOR, DISPLAY_VERSION_MINOR, DISPLAY_VERSION_REVISION, DISPLAY_VERSION_BUILD, GIT_COMMIT_ID) # else -# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(PPCOIN_VERSION_MAJOR, PPCOIN_VERSION_MINOR, PPCOIN_VERSION_REVISION, PPCOIN_VERSION_BUILD) +# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(DISPLAY_VERSION_MAJOR, DISPLAY_VERSION_MINOR, DISPLAY_VERSION_REVISION, DISPLAY_VERSION_BUILD) # endif #endif diff --git a/src/version.h b/src/version.h index 56297a3..0db6dc9 100644 --- a/src/version.h +++ b/src/version.h @@ -1,25 +1,19 @@ // Copyright (c) 2012 The Bitcoin developers -// Copyright (c) 2012-2013 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_VERSION_H #define BITCOIN_VERSION_H +#include "clientversion.h" #include // // client versioning // -// These need to be macro's, as version.cpp's voodoo requires it -#define CLIENT_VERSION_MAJOR 0 -#define CLIENT_VERSION_MINOR 6 -#define CLIENT_VERSION_REVISION 3 -#define CLIENT_VERSION_BUILD 0 - static const int CLIENT_VERSION = 1000000 * CLIENT_VERSION_MAJOR - + 10000 * CLIENT_VERSION_MINOR + + 10000 * CLIENT_VERSION_MINOR + 100 * CLIENT_VERSION_REVISION + 1 * CLIENT_VERSION_BUILD; @@ -27,12 +21,6 @@ extern const std::string CLIENT_NAME; extern const std::string CLIENT_BUILD; extern const std::string CLIENT_DATE; -// ppcoin version - intended for display purpose ONLY -#define PPCOIN_VERSION_MAJOR 0 -#define PPCOIN_VERSION_MINOR 3 -#define PPCOIN_VERSION_REVISION 5 -#define PPCOIN_VERSION_BUILD 0 - // // network protocol versioning // @@ -40,8 +28,6 @@ extern const std::string CLIENT_DATE; static const int PROTOCOL_VERSION = 60005; // earlier versions not supported as of Feb 2012, and are disconnected -// NOTE: as of bitcoin v0.6 message serialization (vSend, vRecv) still -// uses MIN_PROTO_VERSION(209), where message format uses PROTOCOL_VERSION static const int MIN_PROTO_VERSION = 209; // nTime field added to CAddress, starting with this version; @@ -50,9 +36,17 @@ static const int CADDR_TIME_VERSION = 31402; // only request blocks from nodes outside this range of versions static const int NOBLKS_VERSION_START = 60002; -static const int NOBLKS_VERSION_END = 60003; +static const int NOBLKS_VERSION_END = 60004; // BIP 0031, pong message, is enabled for all versions AFTER this one static const int BIP0031_VERSION = 60000; +// "mempool" command, enhanced "getdata" behavior starts with this version: +static const int MEMPOOL_GD_VERSION = 60002; + +#define DISPLAY_VERSION_MAJOR 0 +#define DISPLAY_VERSION_MINOR 4 +#define DISPLAY_VERSION_REVISION 0 +#define DISPLAY_VERSION_BUILD 0 + #endif diff --git a/src/wallet.cpp b/src/wallet.cpp index 2645f8c..20caf8a 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1,7 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2013 The PPCoin developers -// Copyright (c) 2013 NovaCoin Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -9,9 +7,11 @@ #include "walletdb.h" #include "crypter.h" #include "ui_interface.h" +#include "base58.h" #include "kernel.h" using namespace std; +extern int nStakeMaxAge; ////////////////////////////////////////////////////////////////////////////// @@ -19,9 +19,18 @@ using namespace std; // mapWallet // -std::vector CWallet::GenerateNewKey(bool bCompressed = true) +struct CompareValueOnly { - bool fCompressed = bCompressed ? CanSupportFeature(FEATURE_COMPRPUBKEY) : false; // default to compressed public keys if we want 0.6.0 wallets + bool operator()(const pair >& t1, + const pair >& t2) const + { + return t1.first < t2.first; + } +}; + +CPubKey CWallet::GenerateNewKey() +{ + bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets RandAddSeedPerfmon(); CKey key; @@ -47,7 +56,7 @@ bool CWallet::AddKey(const CKey& key) return true; } -bool CWallet::AddCryptedKey(const vector &vchPubKey, const vector &vchCryptedSecret) +bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector &vchCryptedSecret) { if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; @@ -281,11 +290,48 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // Need to completely rewrite the wallet file; if we don't, bdb might keep // bits of the unencrypted private key in slack space in the database file. CDB::Rewrite(strWalletFile); + } + NotifyStatusChanged(this); return true; } +int64 CWallet::IncOrderPosNext(CWalletDB *pwalletdb) +{ + int64 nRet = nOrderPosNext++; + if (pwalletdb) { + pwalletdb->WriteOrderPosNext(nOrderPosNext); + } else { + CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext); + } + return nRet; +} + +CWallet::TxItems CWallet::OrderedTxItems(std::list& acentries, std::string strAccount) +{ + CWalletDB walletdb(strWalletFile); + + // First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap. + TxItems txOrdered; + + // Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry + // would make this much faster for applications that do this a lot. + for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + CWalletTx* wtx = &((*it).second); + txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0))); + } + acentries.clear(); + walletdb.ListAccountCreditDebit(strAccount, acentries); + BOOST_FOREACH(CAccountingEntry& entry, acentries) + { + txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry))); + } + + return txOrdered; +} + void CWallet::WalletUpdateSpent(const CTransaction &tx) { // Anytime a signature is successfully verified, it's proof the outpoint is spent. @@ -299,12 +345,14 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) if (mi != mapWallet.end()) { CWalletTx& wtx = (*mi).second; - if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n])) + if (txin.prevout.n >= wtx.vout.size()) + printf("WalletUpdateSpent: bad wtx %s\n", wtx.GetHash().ToString().c_str()); + else if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n])) { - printf("WalletUpdateSpent found spent coin %sppc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + printf("WalletUpdateSpent found spent coin %snvc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); wtx.MarkSpent(txin.prevout.n); wtx.WriteToDisk(); - vWalletUpdated.push_back(txin.prevout.hash); + NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); } } } @@ -331,7 +379,56 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) wtx.BindWallet(this); bool fInsertedNew = ret.second; if (fInsertedNew) + { wtx.nTimeReceived = GetAdjustedTime(); + wtx.nOrderPos = IncOrderPosNext(); + + wtx.nTimeSmart = wtx.nTimeReceived; + if (wtxIn.hashBlock != 0) + { + if (mapBlockIndex.count(wtxIn.hashBlock)) + { + unsigned int latestNow = wtx.nTimeReceived; + unsigned int latestEntry = 0; + { + // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future + int64 latestTolerated = latestNow + 300; + std::list acentries; + TxItems txOrdered = OrderedTxItems(acentries); + for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx == &wtx) + continue; + CAccountingEntry *const pacentry = (*it).second.second; + int64 nSmartTime; + if (pwtx) + { + nSmartTime = pwtx->nTimeSmart; + if (!nSmartTime) + nSmartTime = pwtx->nTimeReceived; + } + else + nSmartTime = pacentry->nTime; + if (nSmartTime <= latestTolerated) + { + latestEntry = nSmartTime; + if (nSmartTime > latestNow) + latestNow = nSmartTime; + break; + } + } + } + + unsigned int& blocktime = mapBlockIndex[wtxIn.hashBlock]->nTime; + wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); + } + else + printf("AddToWallet() : found %s in block %s not in index\n", + wtxIn.GetHash().ToString().substr(0,10).c_str(), + wtxIn.hashBlock.ToString().c_str()); + } + } bool fUpdated = false; if (!fInsertedNew) @@ -366,29 +463,26 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) #ifndef QT_GUI // If default receiving address gets used, replace it with a new one CScript scriptDefaultKey; - scriptDefaultKey.SetBitcoinAddress(vchDefaultKey); + scriptDefaultKey.SetDestination(vchDefaultKey.GetID()); BOOST_FOREACH(const CTxOut& txout, wtx.vout) { if (txout.scriptPubKey == scriptDefaultKey) { - std::vector newDefaultKey; + CPubKey newDefaultKey; if (GetKeyFromPool(newDefaultKey, false)) { SetDefaultKey(newDefaultKey); - SetAddressBookName(CBitcoinAddress(vchDefaultKey), ""); + SetAddressBookName(vchDefaultKey.GetID(), ""); } } } #endif - // Notify UI - vWalletUpdated.push_back(hash); - // since AddToWallet is called directly for self-originating transactions, check for consumption of own coins WalletUpdateSpent(wtx); - } - // Refresh UI - MainFrameRepaint(); + // Notify UI of new or updated transaction + NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); + } return true; } @@ -463,7 +557,7 @@ int64 CWallet::GetDebit(const CTxIn &txin) const bool CWallet::IsChange(const CTxOut& txout) const { - CBitcoinAddress address; + CTxDestination address; // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a TX_PUBKEYHASH that is mine but isn't in the address book @@ -472,7 +566,7 @@ bool CWallet::IsChange(const CTxOut& txout) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - if (ExtractAddress(txout.scriptPubKey, address) && HaveKey(address)) + if (ExtractDestination(txout.scriptPubKey, address) && ::IsMine(*this, address)) { LOCK(cs_wallet); if (!mapAddressBook.count(address)) @@ -483,7 +577,8 @@ bool CWallet::IsChange(const CTxOut& txout) const int64 CWalletTx::GetTxTime() const { - return nTimeReceived; + int64 n = nTimeSmart; + return n ? n : nTimeReceived; } int CWalletTx::GetRequestCount() const @@ -525,8 +620,8 @@ int CWalletTx::GetRequestCount() const return nRequests; } -void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list >& listReceived, - list >& listSent, int64& nFee, string& strSentAccount) const +void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list >& listReceived, + list >& listSent, int64& nFee, string& strSentAccount) const { nGeneratedImmature = nGeneratedMature = nFee = 0; listReceived.clear(); @@ -553,13 +648,12 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l // Sent/received. BOOST_FOREACH(const CTxOut& txout, vout) { - CBitcoinAddress address; + CTxDestination address; vector vchPubKey; - if (!ExtractAddress(txout.scriptPubKey, address)) + if (!ExtractDestination(txout.scriptPubKey, address)) { printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", this->GetHash().ToString().c_str()); - address = " unknown "; } // Don't report 'change' txouts @@ -575,33 +669,32 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l } -void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived, +void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived, int64& nSent, int64& nFee) const { - nGenerated = nReceived = nSent = nFee = 0; + nReceived = nSent = nFee = 0; int64 allGeneratedImmature, allGeneratedMature, allFee; - allGeneratedImmature = allGeneratedMature = allFee = 0; string strSentAccount; - list > listReceived; - list > listSent; + list > listReceived; + list > listSent; GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount); if (strAccount == "") nGenerated = allGeneratedMature; if (strAccount == strSentAccount) { - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& s, listSent) + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& s, listSent) nSent += s.second; nFee = allFee; } { LOCK(pwallet->cs_wallet); - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listReceived) + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listReceived) { if (pwallet->mapAddressBook.count(r.first)) { - map::const_iterator mi = pwallet->mapAddressBook.find(r.first); + map::const_iterator mi = pwallet->mapAddressBook.find(r.first); if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount) nReceived += r.second; } @@ -734,7 +827,7 @@ void CWallet::ReacceptWalletTransactions() // Update fSpent if a tx got spent somewhere else by a copy of wallet.dat if (txindex.vSpent.size() != wtx.vout.size()) { - printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size()); + printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %"PRIszu" != wtx.vout.size() %"PRIszu"\n", txindex.vSpent.size(), wtx.vout.size()); continue; } for (unsigned int i = 0; i < txindex.vSpent.size(); i++) @@ -750,14 +843,14 @@ void CWallet::ReacceptWalletTransactions() } if (fUpdated) { - printf("ReacceptWalletTransactions found spent coin %sppc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + printf("ReacceptWalletTransactions found spent coin %snvc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); wtx.MarkDirty(); wtx.WriteToDisk(); } } else { - // Reaccept any txes of ours that aren't already in a block + // Re-accept any txes of ours that aren't already in a block if (!(wtx.IsCoinBase() || wtx.IsCoinStake())) wtx.AcceptWalletTransaction(txdb, false); } @@ -766,7 +859,7 @@ void CWallet::ReacceptWalletTransactions() { // TODO: optimize this to scan just part of the block chain? if (ScanForWalletTransactions(pindexGenesisBlock)) - fRepeat = true; // Found missing transactions: re-do Reaccept. + fRepeat = true; // Found missing transactions: re-do re-accept. } } } @@ -862,9 +955,8 @@ int64 CWallet::GetBalance() const for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) - continue; - nTotal += pcoin->GetAvailableCredit(); + if (pcoin->IsFinal() && pcoin->IsConfirmed()) + nTotal += pcoin->GetAvailableCredit(); } } @@ -879,14 +971,93 @@ int64 CWallet::GetUnconfirmedBalance() const for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (pcoin->IsFinal() && pcoin->IsConfirmed()) - continue; - nTotal += pcoin->GetAvailableCredit(); + if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + nTotal += pcoin->GetAvailableCredit(); + } + } + return nTotal; +} + +int64 CWallet::GetImmatureBalance() const +{ + int64 nTotal = 0; + { + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx& pcoin = (*it).second; + if (pcoin.IsCoinBase() && pcoin.GetBlocksToMaturity() > 0 && pcoin.IsInMainChain()) + nTotal += GetCredit(pcoin); } } return nTotal; } +// populate vCoins with vector of spendable COutputs +void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed) const +{ + vCoins.clear(); + + { + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + + if (!pcoin->IsFinal()) + continue; + + if (fOnlyConfirmed && !pcoin->IsConfirmed()) + continue; + + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + continue; + + for (unsigned int i = 0; i < pcoin->vout.size(); i++) + if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue > 0) + vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain())); + } + } +} + +static void ApproximateBestSubset(vector > >vValue, int64 nTotalLower, int64 nTargetValue, + vector& vfBest, int64& nBest, int iterations = 1000) +{ + vector vfIncluded; + + vfBest.assign(vValue.size(), true); + nBest = nTotalLower; + + for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) + { + vfIncluded.assign(vValue.size(), false); + int64 nTotal = 0; + bool fReachedTarget = false; + for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) + { + for (unsigned int i = 0; i < vValue.size(); i++) + { + if (nPass == 0 ? rand() % 2 : !vfIncluded[i]) + { + nTotal += vValue[i].first; + vfIncluded[i] = true; + if (nTotal >= nTargetValue) + { + fReachedTarget = true; + if (nTotal < nBest) + { + nBest = nTotal; + vfBest = vfIncluded; + } + nTotal -= vValue[i].first; + vfIncluded[i] = false; + } + } + } + } + } +} + // ppcoin: total coins staked (non-spendable until maturity) int64 CWallet::GetStake() const { @@ -914,8 +1085,7 @@ int64 CWallet::GetNewMint() const return nTotal; } - -bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, set >& setCoinsRet, int64& nValueRet) const +bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, vector vCoins, set >& setCoinsRet, int64& nValueRet) const { setCoinsRet.clear(); nValueRet = 0; @@ -927,61 +1097,42 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, in vector > > vValue; int64 nTotalLower = 0; - { - LOCK(cs_wallet); - vector vCoins; - vCoins.reserve(mapWallet.size()); - for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) - vCoins.push_back(&(*it).second); - random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); - - BOOST_FOREACH(const CWalletTx* pcoin, vCoins) - { - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) - continue; - - if ((pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetBlocksToMaturity() > 0) - continue; + random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); - int nDepth = pcoin->GetDepthInMainChain(); - if (nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs)) - continue; + BOOST_FOREACH(COutput output, vCoins) + { + const CWalletTx *pcoin = output.tx; - for (unsigned int i = 0; i < pcoin->vout.size(); i++) - { - if (pcoin->IsSpent(i) || !IsMine(pcoin->vout[i])) - continue; + if (output.nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs)) + continue; - if (pcoin->nTime > nSpendTime) - continue; // ppcoin: timestamp must not exceed spend time + int i = output.i; - int64 n = pcoin->vout[i].nValue; + if (pcoin->nTime > nSpendTime) + continue; // ppcoin: timestamp must not exceed spend time - if (n <= 0) - continue; + int64 n = pcoin->vout[i].nValue; - pair > coin = make_pair(n,make_pair(pcoin,i)); + pair > coin = make_pair(n,make_pair(pcoin, i)); - if (n == nTargetValue) - { - setCoinsRet.insert(coin.second); - nValueRet += coin.first; - return true; - } - else if (n < nTargetValue + CENT) - { - vValue.push_back(coin); - nTotalLower += n; - } - else if (n < coinLowestLarger.first) - { - coinLowestLarger = coin; - } - } + if (n == nTargetValue) + { + setCoinsRet.insert(coin.second); + nValueRet += coin.first; + return true; + } + else if (n < nTargetValue + CENT) + { + vValue.push_back(coin); + nTotalLower += n; + } + else if (n < coinLowestLarger.first) + { + coinLowestLarger = coin; } } - if (nTotalLower == nTargetValue || nTotalLower == nTargetValue + CENT) + if (nTotalLower == nTargetValue) { for (unsigned int i = 0; i < vValue.size(); ++i) { @@ -991,7 +1142,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, in return true; } - if (nTotalLower < nTargetValue + (coinLowestLarger.second.first ? CENT : 0)) + if (nTotalLower < nTargetValue) { if (coinLowestLarger.second.first == NULL) return false; @@ -1000,46 +1151,19 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, in return true; } - if (nTotalLower >= nTargetValue + CENT) - nTargetValue += CENT; - // Solve subset sum by stochastic approximation - sort(vValue.rbegin(), vValue.rend()); - vector vfIncluded; - vector vfBest(vValue.size(), true); - int64 nBest = nTotalLower; - - for (int nRep = 0; nRep < 1000 && nBest != nTargetValue; nRep++) - { - vfIncluded.assign(vValue.size(), false); - int64 nTotal = 0; - bool fReachedTarget = false; - for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) - { - for (unsigned int i = 0; i < vValue.size(); i++) - { - if (nPass == 0 ? rand() % 2 : !vfIncluded[i]) - { - nTotal += vValue[i].first; - vfIncluded[i] = true; - if (nTotal >= nTargetValue) - { - fReachedTarget = true; - if (nTotal < nBest) - { - nBest = nTotal; - vfBest = vfIncluded; - } - nTotal -= vValue[i].first; - vfIncluded[i] = false; - } - } - } - } - } - - // If the next larger is still closer, return it - if (coinLowestLarger.second.first && coinLowestLarger.first - nTargetValue <= nBest - nTargetValue) + sort(vValue.rbegin(), vValue.rend(), CompareValueOnly()); + vector vfBest; + int64 nBest; + + ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000); + if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT) + ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000); + + // If we have a bigger coin and (either the stochastic approximation didn't find a good solution, + // or the next bigger coin is closer), return the bigger coin + if (coinLowestLarger.second.first && + ((nBest != nTargetValue && nBest < nTargetValue + CENT) || coinLowestLarger.first <= nBest)) { setCoinsRet.insert(coinLowestLarger.second); nValueRet += coinLowestLarger.first; @@ -1053,14 +1177,11 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, in } //// debug print - if (fDebug && GetBoolArg("-printselectcoin")) - { - printf("SelectCoins() best subset: "); - for (unsigned int i = 0; i < vValue.size(); i++) - if (vfBest[i]) - printf("%s ", FormatMoney(vValue[i].first).c_str()); - printf("total %s\n", FormatMoney(nBest).c_str()); - } + printf("SelectCoins() best subset: "); + for (unsigned int i = 0; i < vValue.size(); i++) + if (vfBest[i]) + printf("%s ", FormatMoney(vValue[i].first).c_str()); + printf("total %s\n", FormatMoney(nBest).c_str()); } return true; @@ -1068,9 +1189,12 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, in bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set >& setCoinsRet, int64& nValueRet) const { - return (SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 6, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 1, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, nSpendTime, 0, 1, setCoinsRet, nValueRet)); + vector vCoins; + AvailableCoins(vCoins); + + return (SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 6, vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 1, vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue, nSpendTime, 0, 1, vCoins, setCoinsRet, nValueRet)); } @@ -1147,14 +1271,14 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // post-backup change. // Reserve a new key pair from key pool - vector vchPubKey = reservekey.GetReservedKey(); + CPubKey vchPubKey = reservekey.GetReservedKey(); // assert(mapKeys.count(vchPubKey)); // Fill a vout to ourself // TODO: pass in scriptChange instead of reservekey so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; - scriptChange.SetBitcoinAddress(vchPubKey); + scriptChange.SetDestination(vchPubKey.GetID()); // Insert change txn at random position: vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()); @@ -1182,6 +1306,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND); + if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); @@ -1274,7 +1399,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int if (!Solver(scriptPubKeyKernel, whichType, vSolutions)) { if (fDebug && GetBoolArg("-printcoinstake")) - printf("CreateCoinStake : failed to parse kernel\n", whichType); + printf("CreateCoinStake : failed to parse kernel\n"); break; } if (fDebug && GetBoolArg("-printcoinstake")) @@ -1338,7 +1463,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int if (pcoin.first->vout[pcoin.second].nValue > nCombineThreshold) continue; // Do not add input that is still too young - if (pcoin.first->nTime + STAKE_MAX_AGE > txNew.nTime) + if (pcoin.first->nTime + nStakeMaxAge > txNew.nTime) continue; txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); nCredit += pcoin.first->vout[pcoin.second].nValue; @@ -1397,6 +1522,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int return true; } + // Call after CreateTransaction unless you want to abort bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { @@ -1424,7 +1550,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) coin.BindWallet(this); coin.MarkSpent(txin.prevout.n); coin.WriteToDisk(); - vWalletUpdated.push_back(coin.GetHash()); + NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); } if (fFileBacked) @@ -1443,7 +1569,6 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) } wtxNew.RelayWalletTransaction(); } - MainFrameRepaint(); return true; } @@ -1478,19 +1603,18 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, return strError; } - if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."))) + if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending..."))) return "ABORTED"; if (!CommitTransaction(wtxNew, reservekey)) return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); - MainFrameRepaint(); return ""; } -string CWallet::SendMoneyToBitcoinAddress(const CBitcoinAddress& address, int64 nValue, CWalletTx& wtxNew, bool fAskFee) +string CWallet::SendMoneyToDestination(const CTxDestination& address, int64 nValue, CWalletTx& wtxNew, bool fAskFee) { // Check amount if (nValue <= 0) @@ -1498,9 +1622,9 @@ string CWallet::SendMoneyToBitcoinAddress(const CBitcoinAddress& address, int64 if (nValue + nTransactionFee > GetBalance()) return _("Insufficient funds"); - // Parse bitcoin address + // Parse Bitcoin address CScript scriptPubKey; - scriptPubKey.SetBitcoinAddress(address); + scriptPubKey.SetDestination(address); return SendMoney(scriptPubKey, nValue, wtxNew, fAskFee); } @@ -1508,12 +1632,12 @@ string CWallet::SendMoneyToBitcoinAddress(const CBitcoinAddress& address, int64 -int CWallet::LoadWallet(bool& fFirstRunRet) +DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { if (!fFileBacked) - return false; + return DB_LOAD_OK; fFirstRunRet = false; - int nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); + DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); if (nLoadWalletRet == DB_NEED_REWRITE) { if (CDB::Rewrite(strWalletFile, "\x04pool")) @@ -1523,34 +1647,34 @@ int CWallet::LoadWallet(bool& fFirstRunRet) // User will be prompted to unlock wallet the next operation // the requires a new key. } - nLoadWalletRet = DB_NEED_REWRITE; } if (nLoadWalletRet != DB_LOAD_OK) return nLoadWalletRet; - fFirstRunRet = vchDefaultKey.empty(); + fFirstRunRet = !vchDefaultKey.IsValid(); - CreateThread(ThreadFlushWalletDB, &strWalletFile); + NewThread(ThreadFlushWalletDB, &strWalletFile); return DB_LOAD_OK; } -bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& strName) +bool CWallet::SetAddressBookName(const CTxDestination& address, const string& strName) { + std::map::iterator mi = mapAddressBook.find(address); mapAddressBook[address] = strName; - AddressBookRepaint(); + NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address), (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); if (!fFileBacked) return false; - return CWalletDB(strWalletFile).WriteName(address.ToString(), strName); + return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName); } -bool CWallet::DelAddressBookName(const CBitcoinAddress& address) +bool CWallet::DelAddressBookName(const CTxDestination& address) { mapAddressBook.erase(address); - AddressBookRepaint(); + NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), CT_DELETED); if (!fFileBacked) return false; - return CWalletDB(strWalletFile).EraseName(address.ToString()); + return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); } @@ -1561,13 +1685,14 @@ void CWallet::PrintWallet(const CBlock& block) if (block.IsProofOfWork() && mapWallet.count(block.vtx[0].GetHash())) { CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()]; - printf(" mine: %d %d %s", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), FormatMoney(wtx.GetCredit()).c_str()); + printf(" mine: %d %d %"PRI64d"", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit()); } if (block.IsProofOfStake() && mapWallet.count(block.vtx[1].GetHash())) { CWalletTx& wtx = mapWallet[block.vtx[1].GetHash()]; - printf(" stake: %d %d %s", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), FormatMoney(wtx.GetCredit()).c_str()); - } + printf(" stake: %d %d %"PRI64d"", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit()); + } + } printf("\n"); } @@ -1586,7 +1711,7 @@ bool CWallet::GetTransaction(const uint256 &hashTx, CWalletTx& wtx) return false; } -bool CWallet::SetDefaultKey(const std::vector &vchPubKey) +bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) { if (fFileBacked) { @@ -1653,7 +1778,7 @@ bool CWallet::TopUpKeyPool() if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) throw runtime_error("TopUpKeyPool() : writing generated key failed"); setKeyPool.insert(nEnd); - printf("keypool added key %"PRI64d", size=%d\n", nEnd, setKeyPool.size()); + printf("keypool added key %"PRI64d", size=%"PRIszu"\n", nEnd, setKeyPool.size()); } } return true; @@ -1662,7 +1787,7 @@ bool CWallet::TopUpKeyPool() void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) { nIndex = -1; - keypool.vchPubKey.clear(); + keypool.vchPubKey = CPubKey(); { LOCK(cs_wallet); @@ -1679,9 +1804,9 @@ void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) setKeyPool.erase(setKeyPool.begin()); if (!walletdb.ReadPool(nIndex, keypool)) throw runtime_error("ReserveKeyFromKeyPool() : read failed"); - if (!HaveKey(Hash160(keypool.vchPubKey))) + if (!HaveKey(keypool.vchPubKey.GetID())) throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool"); - assert(!keypool.vchPubKey.empty()); + assert(keypool.vchPubKey.IsValid()); if (fDebug && GetBoolArg("-printkeypool")) printf("keypool reserve %"PRI64d"\n", nIndex); } @@ -1720,11 +1845,10 @@ void CWallet::ReturnKey(int64 nIndex) LOCK(cs_wallet); setKeyPool.insert(nIndex); } - if (fDebug && GetBoolArg("-printkeypool")) - printf("keypool return %"PRI64d"\n", nIndex); + printf("keypool return %"PRI64d"\n", nIndex); } -bool CWallet::GetKeyFromPool(vector& result, bool fAllowReuse) +bool CWallet::GetKeyFromPool(CPubKey& result, bool fAllowReuse) { int64 nIndex = 0; CKeyPool keypool; @@ -1733,7 +1857,7 @@ bool CWallet::GetKeyFromPool(vector& result, bool fAllowReuse) ReserveKeyFromKeyPool(nIndex, keypool); if (nIndex == -1) { - if (fAllowReuse && !vchDefaultKey.empty()) + if (fAllowReuse && vchDefaultKey.IsValid()) { result = vchDefaultKey; return true; @@ -1759,6 +1883,129 @@ int64 CWallet::GetOldestKeyPoolTime() return keypool.nTime; } +std::map CWallet::GetAddressBalances() +{ + map balances; + + { + LOCK(cs_wallet); + BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet) + { + CWalletTx *pcoin = &walletEntry.second; + + if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + continue; + + if ((pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetBlocksToMaturity() > 0) + continue; + + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < (pcoin->IsFromMe() ? 0 : 1)) + continue; + + for (unsigned int i = 0; i < pcoin->vout.size(); i++) + { + CTxDestination addr; + if (!IsMine(pcoin->vout[i])) + continue; + if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr)) + continue; + + int64 n = pcoin->IsSpent(i) ? 0 : pcoin->vout[i].nValue; + + if (!balances.count(addr)) + balances[addr] = 0; + balances[addr] += n; + } + } + } + + return balances; +} + +set< set > CWallet::GetAddressGroupings() +{ + set< set > groupings; + set grouping; + + BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet) + { + CWalletTx *pcoin = &walletEntry.second; + + if (pcoin->vin.size() > 0 && IsMine(pcoin->vin[0])) + { + // group all input addresses with each other + BOOST_FOREACH(CTxIn txin, pcoin->vin) + { + CTxDestination address; + if(!ExtractDestination(mapWallet[txin.prevout.hash].vout[txin.prevout.n].scriptPubKey, address)) + continue; + grouping.insert(address); + } + + // group change with input addresses + BOOST_FOREACH(CTxOut txout, pcoin->vout) + if (IsChange(txout)) + { + CWalletTx tx = mapWallet[pcoin->vin[0].prevout.hash]; + CTxDestination txoutAddr; + if(!ExtractDestination(txout.scriptPubKey, txoutAddr)) + continue; + grouping.insert(txoutAddr); + } + groupings.insert(grouping); + grouping.clear(); + } + + // group lone addrs by themselves + for (unsigned int i = 0; i < pcoin->vout.size(); i++) + if (IsMine(pcoin->vout[i])) + { + CTxDestination address; + if(!ExtractDestination(pcoin->vout[i].scriptPubKey, address)) + continue; + grouping.insert(address); + groupings.insert(grouping); + grouping.clear(); + } + } + + set< set* > uniqueGroupings; // a set of pointers to groups of addresses + map< CTxDestination, set* > setmap; // map addresses to the unique group containing it + BOOST_FOREACH(set grouping, groupings) + { + // make a set of all the groups hit by this new group + set< set* > hits; + map< CTxDestination, set* >::iterator it; + BOOST_FOREACH(CTxDestination address, grouping) + if ((it = setmap.find(address)) != setmap.end()) + hits.insert((*it).second); + + // merge all hit groups into a new single group and delete old groups + set* merged = new set(grouping); + BOOST_FOREACH(set* hit, hits) + { + merged->insert(hit->begin(), hit->end()); + uniqueGroupings.erase(hit); + delete hit; + } + uniqueGroupings.insert(merged); + + // update setmap + BOOST_FOREACH(CTxDestination element, *merged) + setmap[element] = merged; + } + + set< set > ret; + BOOST_FOREACH(set* uniqueGrouping, uniqueGroupings) + { + ret.insert(*uniqueGrouping); + delete uniqueGrouping; + } + + return ret; +} + // ppcoin: check 'spent' consistency between wallet and txindex // ppcoin: fix wallet spent state according to txindex void CWallet::FixSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion, bool fCheckOnly) @@ -1779,7 +2026,7 @@ void CWallet::FixSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion, bool CTxIndex txindex; if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex)) continue; - for (int n=0; n < pcoin->vout.size(); n++) + for (unsigned int n=0; n < pcoin->vout.size(); n++) { if (IsMine(pcoin->vout[n]) && pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull())) { @@ -1831,7 +2078,7 @@ void CWallet::DisableTransaction(const CTransaction &tx) } } -vector CReserveKey::GetReservedKey() +CPubKey CReserveKey::GetReservedKey() { if (nIndex == -1) { @@ -1841,11 +2088,11 @@ vector CReserveKey::GetReservedKey() vchPubKey = keypool.vchPubKey; else { - printf("CReserveKey::GetReservedKey(): Warning: using default key instead of a new key, top up your keypool."); + printf("CReserveKey::GetReservedKey(): Warning: Using default key instead of a new key, top up your keypool!"); vchPubKey = pwallet->vchDefaultKey; } } - assert(!vchPubKey.empty()); + assert(vchPubKey.IsValid()); return vchPubKey; } @@ -1854,7 +2101,7 @@ void CReserveKey::KeepKey() if (nIndex != -1) pwallet->KeepKey(nIndex); nIndex = -1; - vchPubKey.clear(); + vchPubKey = CPubKey(); } void CReserveKey::ReturnKey() @@ -1862,10 +2109,10 @@ void CReserveKey::ReturnKey() if (nIndex != -1) pwallet->ReturnKey(nIndex); nIndex = -1; - vchPubKey.clear(); + vchPubKey = CPubKey(); } -void CWallet::GetAllReserveAddresses(set& setAddress) +void CWallet::GetAllReserveKeys(set& setAddress) { setAddress.clear(); @@ -1877,10 +2124,21 @@ void CWallet::GetAllReserveAddresses(set& setAddress) CKeyPool keypool; if (!walletdb.ReadPool(id, keypool)) throw runtime_error("GetAllReserveKeyHashes() : read failed"); - CBitcoinAddress address(keypool.vchPubKey); - assert(!keypool.vchPubKey.empty()); - if (!HaveKey(address)) + assert(keypool.vchPubKey.IsValid()); + CKeyID keyID = keypool.vchPubKey.GetID(); + if (!HaveKey(keyID)) throw runtime_error("GetAllReserveKeyHashes() : unknown key in key pool"); - setAddress.insert(address); + setAddress.insert(keyID); + } +} + +void CWallet::UpdatedTransaction(const uint256 &hashTx) +{ + { + LOCK(cs_wallet); + // Only notify UI if this transaction is in this wallet + map::const_iterator mi = mapWallet.find(hashTx); + if (mi != mapWallet.end()) + NotifyTransactionChanged(this, hashTx, CT_UPDATED); } } diff --git a/src/wallet.h b/src/wallet.h index 056f553..7f848f6 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -1,21 +1,28 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_H #define BITCOIN_WALLET_H +#include +#include + +#include + #include "main.h" #include "key.h" #include "keystore.h" #include "script.h" +#include "ui_interface.h" +#include "util.h" +#include "walletdb.h" extern bool fWalletUnlockMintOnly; - +class CAccountingEntry; class CWalletTx; class CReserveKey; -class CWalletDB; +class COutput; /** (client) version numbers for particular wallet features */ enum WalletFeature @@ -34,14 +41,14 @@ class CKeyPool { public: int64 nTime; - std::vector vchPubKey; + CPubKey vchPubKey; CKeyPool() { nTime = GetTime(); } - CKeyPool(const std::vector& vchPubKeyIn) + CKeyPool(const CPubKey& vchPubKeyIn) { nTime = GetTime(); vchPubKey = vchPubKeyIn; @@ -62,7 +69,6 @@ public: class CWallet : public CCryptoKeyStore { private: - bool SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, std::set >& setCoinsRet, int64& nValueRet) const; bool SelectCoins(int64 nTargetValue, unsigned int nSpendTime, std::set >& setCoinsRet, int64& nValueRet) const; CWalletDB *pwalletdbEncryption; @@ -70,7 +76,7 @@ private: // the current wallet version: clients below this version are not able to load the wallet int nWalletVersion; - // the maxmimum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded + // the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded int nWalletMaxVersion; public: @@ -93,6 +99,7 @@ public: fFileBacked = false; nMasterKeyMaxID = 0; pwalletdbEncryption = NULL; + nOrderPosNext = 0; } CWallet(std::string strWalletFileIn) { @@ -102,23 +109,25 @@ public: fFileBacked = true; nMasterKeyMaxID = 0; pwalletdbEncryption = NULL; + nOrderPosNext = 0; } std::map mapWallet; - std::vector vWalletUpdated; - + int64 nOrderPosNext; std::map mapRequestCount; - std::map mapAddressBook; + std::map mapAddressBook; - std::vector vchDefaultKey; + CPubKey vchDefaultKey; // check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) { return nWalletMaxVersion >= wf; } + void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true) const; + bool SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, std::vector vCoins, std::set >& setCoinsRet, int64& nValueRet) const; // keystore implementation // Generate a new key - std::vector GenerateNewKey(bool bCompressed); + CPubKey GenerateNewKey(); // Adds a key to the store, and saves it to disk. bool AddKey(const CKey& key); // Adds a key to the store, without saving it to disk (used by LoadWallet) @@ -127,9 +136,9 @@ public: bool LoadMinVersion(int nVersion) { nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } // Adds an encrypted key to the store, and saves it to disk. - bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); + bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); // Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) - bool LoadCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret) { SetMinVersion(FEATURE_WALLETCRYPT); return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } + bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { SetMinVersion(FEATURE_WALLETCRYPT); return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } bool AddCScript(const CScript& redeemScript); bool LoadCScript(const CScript& redeemScript) { return CCryptoKeyStore::AddCScript(redeemScript); } @@ -137,6 +146,20 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); + /** Increment the next transaction order id + @return next transaction order id + */ + int64 IncOrderPosNext(CWalletDB *pwalletdb = NULL); + + typedef std::pair TxPair; + typedef std::multimap TxItems; + + /** Get the wallet's activity log + @return multimap of ordered transactions and accounting entries + @warning Returned pointers are *only* valid within the scope of passed acentries + */ + TxItems OrderedTxItems(std::list& acentries, std::string strAccount = ""); + void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn); bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate = false, bool fFindBlock = false); @@ -148,14 +171,15 @@ public: void ResendWalletTransactions(); int64 GetBalance() const; int64 GetUnconfirmedBalance() const; + int64 GetImmatureBalance() const; int64 GetStake() const; int64 GetNewMint() const; bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet); - bool CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64 nSearchInterval, CTransaction& txNew); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); + bool CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64 nSearchInterval, CTransaction& txNew); std::string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); - std::string SendMoneyToBitcoinAddress(const CBitcoinAddress& address, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); + std::string SendMoneyToDestination(const CTxDestination &address, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); bool NewKeyPool(); bool TopUpKeyPool(); @@ -163,9 +187,12 @@ public: void ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool); void KeepKey(int64 nIndex); void ReturnKey(int64 nIndex); - bool GetKeyFromPool(std::vector &key, bool fAllowReuse=true); + bool GetKeyFromPool(CPubKey &key, bool fAllowReuse=true); int64 GetOldestKeyPoolTime(); - void GetAllReserveAddresses(std::set& setAddress); + void GetAllReserveKeys(std::set& setAddress); + + std::set< std::set > GetAddressGroupings(); + std::map GetAddressBalances(); bool IsMine(const CTxIn& txin) const; int64 GetDebit(const CTxIn& txin) const; @@ -232,19 +259,13 @@ public: } void SetBestChain(const CBlockLocator& loc); - int LoadWallet(bool& fFirstRunRet); + DBErrors LoadWallet(bool& fFirstRunRet); - bool SetAddressBookName(const CBitcoinAddress& address, const std::string& strName); + bool SetAddressBookName(const CTxDestination& address, const std::string& strName); - bool DelAddressBookName(const CBitcoinAddress& address); + bool DelAddressBookName(const CTxDestination& address); - void UpdatedTransaction(const uint256 &hashTx) - { - { - LOCK(cs_wallet); - vWalletUpdated.push_back(hashTx); - } - } + void UpdatedTransaction(const uint256 &hashTx); void PrintWallet(const CBlock& block); @@ -265,7 +286,7 @@ public: bool GetTransaction(const uint256 &hashTx, CWalletTx& wtx); - bool SetDefaultKey(const std::vector &vchPubKey); + bool SetDefaultKey(const CPubKey &vchPubKey); // signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower bool SetMinVersion(enum WalletFeature, CWalletDB* pwalletdbIn = NULL, bool fExplicit = false); @@ -278,6 +299,16 @@ public: void FixSpentCoins(int& nMismatchSpent, int64& nBalanceInQuestion, bool fCheckOnly = false); void DisableTransaction(const CTransaction &tx); + + /** Address book entry changed. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal NotifyAddressBookChanged; + + /** Wallet transaction added, removed or updated. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal NotifyTransactionChanged; }; /** A key allocated from the key pool. */ @@ -286,7 +317,7 @@ class CReserveKey protected: CWallet* pwallet; int64 nIndex; - std::vector vchPubKey; + CPubKey vchPubKey; public: CReserveKey(CWallet* pwalletIn) { @@ -301,12 +332,34 @@ public: } void ReturnKey(); - std::vector GetReservedKey(); + CPubKey GetReservedKey(); void KeepKey(); }; -/** A transaction with a bunch of additional info that only the owner cares about. +typedef std::map mapValue_t; + + +static void ReadOrderPos(int64& nOrderPos, mapValue_t& mapValue) +{ + if (!mapValue.count("n")) + { + nOrderPos = -1; // TODO: calculate elsewhere + return; + } + nOrderPos = atoi64(mapValue["n"].c_str()); +} + + +static void WriteOrderPos(const int64& nOrderPos, mapValue_t& mapValue) +{ + if (nOrderPos == -1) + return; + mapValue["n"] = i64tostr(nOrderPos); +} + + +/** A transaction with a bunch of additional info that only the owner cares about. * It includes any unrecorded transactions needed to link it back to the block chain. */ class CWalletTx : public CMerkleTx @@ -316,13 +369,15 @@ private: public: std::vector vtxPrev; - std::map mapValue; + mapValue_t mapValue; std::vector > vOrderForm; unsigned int fTimeReceivedIsTxTime; unsigned int nTimeReceived; // time received by this node + unsigned int nTimeSmart; char fFromMe; std::string strFromAccount; std::vector vfSpent; // which outputs are already spent + int64 nOrderPos; // position in ordered transaction list // memory only mutable bool fDebitCached; @@ -362,6 +417,7 @@ public: vOrderForm.clear(); fTimeReceivedIsTxTime = false; nTimeReceived = 0; + nTimeSmart = 0; fFromMe = false; strFromAccount.clear(); vfSpent.clear(); @@ -373,6 +429,7 @@ public: nCreditCached = 0; nAvailableCreditCached = 0; nChangeCached = 0; + nOrderPos = -1; } IMPLEMENT_SERIALIZE @@ -394,6 +451,11 @@ public: fSpent = true; } pthis->mapValue["spent"] = str; + + WriteOrderPos(pthis->nOrderPos, pthis->mapValue); + + if (nTimeSmart) + pthis->mapValue["timesmart"] = strprintf("%u", nTimeSmart); } nSerSize += SerReadWrite(s, *(CMerkleTx*)this, nType, nVersion,ser_action); @@ -414,11 +476,17 @@ public: pthis->vfSpent.push_back(c != '0'); else pthis->vfSpent.assign(vout.size(), fSpent); + + ReadOrderPos(pthis->nOrderPos, pthis->mapValue); + + pthis->nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(pthis->mapValue["timesmart"]) : 0; } pthis->mapValue.erase("fromaccount"); pthis->mapValue.erase("version"); pthis->mapValue.erase("spent"); + pthis->mapValue.erase("n"); + pthis->mapValue.erase("timesmart"); ) // marks certain txout's as spent @@ -550,10 +618,10 @@ public: return nChangeCached; } - void GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, std::list >& listReceived, - std::list >& listSent, int64& nFee, std::string& strSentAccount) const; + void GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, std::list >& listReceived, + std::list >& listSent, int64& nFee, std::string& strSentAccount) const; - void GetAccountAmounts(const std::string& strAccount, int64& nGenerated, int64& nReceived, + void GetAccountAmounts(const std::string& strAccount, int64& nGenerated, int64& nReceived, int64& nSent, int64& nFee) const; bool IsFromMe() const @@ -619,6 +687,34 @@ public: }; + + +class COutput +{ +public: + const CWalletTx *tx; + int i; + int nDepth; + + COutput(const CWalletTx *txIn, int iIn, int nDepthIn) + { + tx = txIn; i = iIn; nDepth = nDepthIn; + } + + std::string ToString() const + { + return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString().substr(0,10).c_str(), i, nDepth, FormatMoney(tx->vout[i].nValue).c_str()); + } + + void print() const + { + printf("%s\n", ToString().c_str()); + } +}; + + + + /** Private key that includes an expiration date in case it never gets used. */ class CWalletKey { @@ -658,7 +754,7 @@ public: class CAccount { public: - std::vector vchPubKey; + CPubKey vchPubKey; CAccount() { @@ -667,7 +763,7 @@ public: void SetNull() { - vchPubKey.clear(); + vchPubKey = CPubKey(); } IMPLEMENT_SERIALIZE @@ -691,6 +787,9 @@ public: int64 nTime; std::string strOtherAccount; std::string strComment; + mapValue_t mapValue; + int64 nOrderPos; // position in ordered transaction list + uint64 nEntryNo; CAccountingEntry() { @@ -704,18 +803,55 @@ public: strAccount.clear(); strOtherAccount.clear(); strComment.clear(); + nOrderPos = -1; } IMPLEMENT_SERIALIZE ( + CAccountingEntry& me = *const_cast(this); if (!(nType & SER_GETHASH)) READWRITE(nVersion); // Note: strAccount is serialized as part of the key, not here. READWRITE(nCreditDebit); READWRITE(nTime); READWRITE(strOtherAccount); + + if (!fRead) + { + WriteOrderPos(nOrderPos, me.mapValue); + + if (!(mapValue.empty() && _ssExtra.empty())) + { + CDataStream ss(nType, nVersion); + ss.insert(ss.begin(), '\0'); + ss << mapValue; + ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end()); + me.strComment.append(ss.str()); + } + } + READWRITE(strComment); + + size_t nSepPos = strComment.find("\0", 0, 1); + if (fRead) + { + me.mapValue.clear(); + if (std::string::npos != nSepPos) + { + CDataStream ss(std::vector(strComment.begin() + nSepPos + 1, strComment.end()), nType, nVersion); + ss >> me.mapValue; + me._ssExtra = std::vector(ss.begin(), ss.end()); + } + ReadOrderPos(me.nOrderPos, me.mapValue); + } + if (std::string::npos != nSepPos) + me.strComment.erase(nSepPos); + + me.mapValue.erase("n"); ) + +private: + std::vector _ssExtra; }; bool GetWalletFile(CWallet* pwallet, std::string &strWalletFileOut); diff --git a/src/walletdb.cpp b/src/walletdb.cpp index ac355ab..ad24172 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -1,6 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers -// Copyright (c) 2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -13,10 +12,7 @@ using namespace boost; static uint64 nAccountingEntryNumber = 0; - -extern CCriticalSection cs_db; -extern map mapFileUseCount; -extern void CloseDb(const string& strFile); +extern bool fWalletUnlockMintOnly; // // CWalletDB @@ -47,9 +43,14 @@ bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account) return Write(make_pair(string("acc"), strAccount), account); } +bool CWalletDB::WriteAccountingEntry(const uint64 nAccEntryNum, const CAccountingEntry& acentry) +{ + return Write(boost::make_tuple(string("acentry"), acentry.strAccount, nAccEntryNum), acentry); +} + bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry) { - return Write(boost::make_tuple(string("acentry"), acentry.strAccount, ++nAccountingEntryNumber), acentry); + return WriteAccountingEntry(++nAccountingEntryNumber, acentry); } int64 CWalletDB::GetAccountCreditDebit(const string& strAccount) @@ -100,6 +101,7 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, list> acentry; + ssKey >> acentry.nEntryNo; entries.push_back(acentry); } @@ -107,214 +109,365 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, listvchDefaultKey.clear(); - int nFileVersion = 0; - vector vWalletUpgrade; - bool fIsEncrypted = false; + LOCK(pwallet->cs_wallet); + // Old wallets didn't have any defined order for transactions + // Probably a bad idea to change the output of this + + // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap. + typedef pair TxPair; + typedef multimap TxItems; + TxItems txByTime; - //// todo: shouldn't we catch exceptions and try to recover and continue? + for (map::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it) { - LOCK(pwallet->cs_wallet); - int nMinVersion = 0; - if (Read((string)"minversion", nMinVersion)) - { - if (nMinVersion > CLIENT_VERSION) - return DB_TOO_NEW; - pwallet->LoadMinVersion(nMinVersion); - } + CWalletTx* wtx = &((*it).second); + txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0))); + } + list acentries; + ListAccountCreditDebit("", acentries); + BOOST_FOREACH(CAccountingEntry& entry, acentries) + { + txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry))); + } - // Get cursor - Dbc* pcursor = GetCursor(); - if (!pcursor) + int64& nOrderPosNext = pwallet->nOrderPosNext; + nOrderPosNext = 0; + std::vector nOrderPosOffsets; + for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + CAccountingEntry *const pacentry = (*it).second.second; + int64& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos; + + if (nOrderPos == -1) { - printf("Error getting wallet database cursor\n"); - return DB_CORRUPT; - } + nOrderPos = nOrderPosNext++; + nOrderPosOffsets.push_back(nOrderPos); - loop + if (pacentry) + // Have to write accounting regardless, since we don't keep it in memory + if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) + return DB_LOAD_FAIL; + } + else { - // Read next record - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue); - if (ret == DB_NOTFOUND) - break; - else if (ret != 0) + int64 nOrderPosOff = 0; + BOOST_FOREACH(const int64& nOffsetStart, nOrderPosOffsets) { - printf("Error reading next record from wallet database\n"); - return DB_CORRUPT; + if (nOrderPos >= nOffsetStart) + ++nOrderPosOff; } + nOrderPos += nOrderPosOff; + nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1); + + if (!nOrderPosOff) + continue; - // Unserialize - // Taking advantage of the fact that pair serialization - // is just the two items serialized one after the other - string strType; - ssKey >> strType; - if (strType == "name") + // Since we're changing the order, write it back + if (pwtx) { - string strAddress; - ssKey >> strAddress; - ssValue >> pwallet->mapAddressBook[strAddress]; + if (!WriteTx(pwtx->GetHash(), *pwtx)) + return DB_LOAD_FAIL; } - else if (strType == "tx") - { - uint256 hash; - ssKey >> hash; - CWalletTx& wtx = pwallet->mapWallet[hash]; - ssValue >> wtx; - wtx.BindWallet(pwallet); + else + if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) + return DB_LOAD_FAIL; + } + } + + return DB_LOAD_OK; +} + - if (wtx.GetHash() != hash) - printf("Error in wallet.dat, hash mismatch\n"); +bool +ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, + int& nFileVersion, vector& vWalletUpgrade, + bool& fIsEncrypted, bool& fAnyUnordered, string& strType, string& strErr) +{ + try { + // Unserialize + // Taking advantage of the fact that pair serialization + // is just the two items serialized one after the other + ssKey >> strType; + if (strType == "name") + { + string strAddress; + ssKey >> strAddress; + ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()]; + } + else if (strType == "tx") + { + uint256 hash; + ssKey >> hash; + CWalletTx& wtx = pwallet->mapWallet[hash]; + ssValue >> wtx; + if (wtx.CheckTransaction() && (wtx.GetHash() == hash)) + wtx.BindWallet(pwallet); + else + { + pwallet->mapWallet.erase(hash); + return false; + } - // Undo serialize changes in 31600 - if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) + // Undo serialize changes in 31600 + if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) + { + if (!ssValue.empty()) { - if (!ssValue.empty()) - { - char fTmp; - char fUnused; - ssValue >> fTmp >> fUnused >> wtx.strFromAccount; - printf("LoadWallet() upgrading tx ver=%d %d '%s' %s\n", wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str()); - wtx.fTimeReceivedIsTxTime = fTmp; - } - else - { - printf("LoadWallet() repairing tx ver=%d %s\n", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str()); - wtx.fTimeReceivedIsTxTime = 0; - } - vWalletUpgrade.push_back(hash); + char fTmp; + char fUnused; + ssValue >> fTmp >> fUnused >> wtx.strFromAccount; + strErr = strprintf("LoadWallet() upgrading tx ver=%d %d '%s' %s", + wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str()); + wtx.fTimeReceivedIsTxTime = fTmp; } - - //// debug print - //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); - //printf(" %12"PRI64d" %s %s %s\n", - // wtx.vout[0].nValue, - // DateTimeStrFormat(wtx.GetBlockTime()).c_str(), - // wtx.hashBlock.ToString().substr(0,20).c_str(), - // wtx.mapValue["message"].c_str()); + else + { + strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str()); + wtx.fTimeReceivedIsTxTime = 0; + } + vWalletUpgrade.push_back(hash); } - else if (strType == "acentry") + + if (wtx.nOrderPos == -1) + fAnyUnordered = true; + + //// debug print + //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); + //printf(" %12"PRI64d" %s %s %s\n", + // wtx.vout[0].nValue, + // DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(), + // wtx.hashBlock.ToString().substr(0,20).c_str(), + // wtx.mapValue["message"].c_str()); + } + else if (strType == "acentry") + { + string strAccount; + ssKey >> strAccount; + uint64 nNumber; + ssKey >> nNumber; + if (nNumber > nAccountingEntryNumber) + nAccountingEntryNumber = nNumber; + + if (!fAnyUnordered) { - string strAccount; - ssKey >> strAccount; - uint64 nNumber; - ssKey >> nNumber; - if (nNumber > nAccountingEntryNumber) - nAccountingEntryNumber = nNumber; + CAccountingEntry acentry; + ssValue >> acentry; + if (acentry.nOrderPos == -1) + fAnyUnordered = true; } - else if (strType == "key" || strType == "wkey") + } + else if (strType == "key" || strType == "wkey") + { + vector vchPubKey; + ssKey >> vchPubKey; + CKey key; + if (strType == "key") { - vector vchPubKey; - ssKey >> vchPubKey; - CKey key; - if (strType == "key") + CPrivKey pkey; + ssValue >> pkey; + key.SetPubKey(vchPubKey); + if (!key.SetPrivKey(pkey)) { - CPrivKey pkey; - ssValue >> pkey; - key.SetPubKey(vchPubKey); - key.SetPrivKey(pkey); - if (key.GetPubKey() != vchPubKey) - { - printf("Error reading wallet database: CPrivKey pubkey inconsistency\n"); - return DB_CORRUPT; - } - if (!key.IsValid()) - { - printf("Error reading wallet database: invalid CPrivKey\n"); - return DB_CORRUPT; - } + strErr = "Error reading wallet database: CPrivKey corrupt"; + return false; } - else + if (key.GetPubKey() != vchPubKey) { - CWalletKey wkey; - ssValue >> wkey; - key.SetPubKey(vchPubKey); - key.SetPrivKey(wkey.vchPrivKey); - if (key.GetPubKey() != vchPubKey) - { - printf("Error reading wallet database: CWalletKey pubkey inconsistency\n"); - return DB_CORRUPT; - } - if (!key.IsValid()) - { - printf("Error reading wallet database: invalid CWalletKey\n"); - return DB_CORRUPT; - } + strErr = "Error reading wallet database: CPrivKey pubkey inconsistency"; + return false; } - if (!pwallet->LoadKey(key)) + if (!key.IsValid()) { - printf("Error reading wallet database: LoadKey failed\n"); - return DB_CORRUPT; + strErr = "Error reading wallet database: invalid CPrivKey"; + return false; } } - else if (strType == "mkey") + else { - unsigned int nID; - ssKey >> nID; - CMasterKey kMasterKey; - ssValue >> kMasterKey; - if(pwallet->mapMasterKeys.count(nID) != 0) + CWalletKey wkey; + ssValue >> wkey; + key.SetPubKey(vchPubKey); + if (!key.SetPrivKey(wkey.vchPrivKey)) + { + strErr = "Error reading wallet database: CPrivKey corrupt"; + return false; + } + if (key.GetPubKey() != vchPubKey) { - printf("Error reading wallet database: duplicate CMasterKey id %u\n", nID); - return DB_CORRUPT; + strErr = "Error reading wallet database: CWalletKey pubkey inconsistency"; + return false; } - pwallet->mapMasterKeys[nID] = kMasterKey; - if (pwallet->nMasterKeyMaxID < nID) - pwallet->nMasterKeyMaxID = nID; - } - else if (strType == "ckey") - { - vector vchPubKey; - ssKey >> vchPubKey; - vector vchPrivKey; - ssValue >> vchPrivKey; - if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) + if (!key.IsValid()) { - printf("Error reading wallet database: LoadCryptedKey failed\n"); - return DB_CORRUPT; + strErr = "Error reading wallet database: invalid CWalletKey"; + return false; } - fIsEncrypted = true; } - else if (strType == "defaultkey") + if (!pwallet->LoadKey(key)) { - ssValue >> pwallet->vchDefaultKey; + strErr = "Error reading wallet database: LoadKey failed"; + return false; } - else if (strType == "pool") + } + else if (strType == "mkey") + { + unsigned int nID; + ssKey >> nID; + CMasterKey kMasterKey; + ssValue >> kMasterKey; + if(pwallet->mapMasterKeys.count(nID) != 0) { - int64 nIndex; - ssKey >> nIndex; - pwallet->setKeyPool.insert(nIndex); + strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID); + return false; } - else if (strType == "version") + pwallet->mapMasterKeys[nID] = kMasterKey; + if (pwallet->nMasterKeyMaxID < nID) + pwallet->nMasterKeyMaxID = nID; + } + else if (strType == "ckey") + { + vector vchPubKey; + ssKey >> vchPubKey; + vector vchPrivKey; + ssValue >> vchPrivKey; + if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) { - ssValue >> nFileVersion; - if (nFileVersion == 10300) - nFileVersion = 300; + strErr = "Error reading wallet database: LoadCryptedKey failed"; + return false; } - else if (strType == "cscript") + fIsEncrypted = true; + } + else if (strType == "defaultkey") + { + ssValue >> pwallet->vchDefaultKey; + } + else if (strType == "pool") + { + int64 nIndex; + ssKey >> nIndex; + pwallet->setKeyPool.insert(nIndex); + } + else if (strType == "version") + { + ssValue >> nFileVersion; + if (nFileVersion == 10300) + nFileVersion = 300; + } + else if (strType == "cscript") + { + uint160 hash; + ssKey >> hash; + CScript script; + ssValue >> script; + if (!pwallet->LoadCScript(script)) { - uint160 hash; - ssKey >> hash; - CScript script; - ssValue >> script; - if (!pwallet->LoadCScript(script)) + strErr = "Error reading wallet database: LoadCScript failed"; + return false; + } + } + else if (strType == "orderposnext") + { + ssValue >> pwallet->nOrderPosNext; + } + } catch (...) + { + return false; + } + return true; +} + +static bool IsKeyType(string strType) +{ + return (strType== "key" || strType == "wkey" || + strType == "mkey" || strType == "ckey"); +} + +DBErrors CWalletDB::LoadWallet(CWallet* pwallet) +{ + pwallet->vchDefaultKey = CPubKey(); + int nFileVersion = 0; + vector vWalletUpgrade; + bool fIsEncrypted = false; + bool fAnyUnordered = false; + bool fNoncriticalErrors = false; + DBErrors result = DB_LOAD_OK; + + try { + LOCK(pwallet->cs_wallet); + int nMinVersion = 0; + if (Read((string)"minversion", nMinVersion)) + { + if (nMinVersion > CLIENT_VERSION) + return DB_TOO_NEW; + pwallet->LoadMinVersion(nMinVersion); + } + + // Get cursor + Dbc* pcursor = GetCursor(); + if (!pcursor) + { + printf("Error getting wallet database cursor\n"); + return DB_CORRUPT; + } + + loop + { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = ReadAtCursor(pcursor, ssKey, ssValue); + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) + { + printf("Error reading next record from wallet database\n"); + return DB_CORRUPT; + } + + // Try to be tolerant of single corrupt records: + string strType, strErr; + if (!ReadKeyValue(pwallet, ssKey, ssValue, nFileVersion, + vWalletUpgrade, fIsEncrypted, fAnyUnordered, strType, strErr)) + { + // losing keys is considered a catastrophic error, anything else + // we assume the user can live with: + if (IsKeyType(strType)) + result = DB_CORRUPT; + else { - printf("Error reading wallet database: LoadCScript failed\n"); - return DB_CORRUPT; + // Leave other errors alone, if we try to fix them we might make things worse. + fNoncriticalErrors = true; // ... but do warn the user there is something wrong. + if (strType == "tx") + // Rescan if there is a bad transaction record: + SoftSetBoolArg("-rescan", true); } } + if (!strErr.empty()) + printf("%s\n", strErr.c_str()); } pcursor->close(); } + catch (...) + { + result = DB_CORRUPT; + } - BOOST_FOREACH(uint256 hash, vWalletUpgrade) - WriteTx(hash, pwallet->mapWallet[hash]); + if (fNoncriticalErrors && result == DB_LOAD_OK) + result = DB_NONCRITICAL_ERROR; + + // Any wallet corruption at all: skip any rewriting or + // upgrading, we don't want to make it worse. + if (result != DB_LOAD_OK) + return result; printf("nFileVersion = %d\n", nFileVersion); + BOOST_FOREACH(uint256 hash, vWalletUpgrade) + WriteTx(hash, pwallet->mapWallet[hash]); // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000)) @@ -323,11 +476,17 @@ int CWalletDB::LoadWallet(CWallet* pwallet) if (nFileVersion < CLIENT_VERSION) // Update WriteVersion(CLIENT_VERSION); - return DB_LOAD_OK; + if (fAnyUnordered) + result = ReorderTransactions(pwallet); + + return result; } void ThreadFlushWalletDB(void* parg) { + // Make this thread recognisable as the wallet flushing thread + RenameThread("bitcoin-wallet"); + const string& strFile = ((const string*)parg)[0]; static bool fOneThread; if (fOneThread) @@ -351,13 +510,13 @@ void ThreadFlushWalletDB(void* parg) if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2) { - TRY_LOCK(cs_db,lockDb); + TRY_LOCK(bitdb.cs_db,lockDb); if (lockDb) { // Don't do this if any databases are in use int nRefCount = 0; - map::iterator mi = mapFileUseCount.begin(); - while (mi != mapFileUseCount.end()) + map::iterator mi = bitdb.mapFileUseCount.begin(); + while (mi != bitdb.mapFileUseCount.end()) { nRefCount += (*mi).second; mi++; @@ -365,20 +524,18 @@ void ThreadFlushWalletDB(void* parg) if (nRefCount == 0 && !fShutdown) { - map::iterator mi = mapFileUseCount.find(strFile); - if (mi != mapFileUseCount.end()) + map::iterator mi = bitdb.mapFileUseCount.find(strFile); + if (mi != bitdb.mapFileUseCount.end()) { - printf("%s ", DateTimeStrFormat(GetTime()).c_str()); printf("Flushing wallet.dat\n"); nLastFlushed = nWalletDBUpdated; int64 nStart = GetTimeMillis(); // Flush wallet.dat so it's self contained - CloseDb(strFile); - dbenv.txn_checkpoint(0, 0, 0); - dbenv.lsn_reset(strFile.c_str(), 0); + bitdb.CloseDb(strFile); + bitdb.CheckpointLSN(strFile); - mapFileUseCount.erase(mi++); + bitdb.mapFileUseCount.erase(mi++); printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart); } } @@ -394,14 +551,13 @@ bool BackupWallet(const CWallet& wallet, const string& strDest) while (!fShutdown) { { - LOCK(cs_db); - if (!mapFileUseCount.count(wallet.strWalletFile) || mapFileUseCount[wallet.strWalletFile] == 0) + LOCK(bitdb.cs_db); + if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0) { // Flush log data to the dat file - CloseDb(wallet.strWalletFile); - dbenv.txn_checkpoint(0, 0, 0); - dbenv.lsn_reset(wallet.strWalletFile.c_str(), 0); - mapFileUseCount.erase(wallet.strWalletFile); + bitdb.CloseDb(wallet.strWalletFile); + bitdb.CheckpointLSN(wallet.strWalletFile); + bitdb.mapFileUseCount.erase(wallet.strWalletFile); // Copy wallet.dat filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile; @@ -427,3 +583,94 @@ bool BackupWallet(const CWallet& wallet, const string& strDest) } return false; } + +// +// Try to (very carefully!) recover wallet.dat if there is a problem. +// +bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys) +{ + // Recovery procedure: + // move wallet.dat to wallet.timestamp.bak + // Call Salvage with fAggressive=true to + // get as much data as possible. + // Rewrite salvaged data to wallet.dat + // Set -rescan so any missing transactions will be + // found. + int64 now = GetTime(); + std::string newFilename = strprintf("wallet.%"PRI64d".bak", now); + + int result = dbenv.dbenv.dbrename(NULL, filename.c_str(), NULL, + newFilename.c_str(), DB_AUTO_COMMIT); + if (result == 0) + printf("Renamed %s to %s\n", filename.c_str(), newFilename.c_str()); + else + { + printf("Failed to rename %s to %s\n", filename.c_str(), newFilename.c_str()); + return false; + } + + std::vector salvagedData; + bool allOK = dbenv.Salvage(newFilename, true, salvagedData); + if (salvagedData.empty()) + { + printf("Salvage(aggressive) found no records in %s.\n", newFilename.c_str()); + return false; + } + printf("Salvage(aggressive) found %"PRIszu" records\n", salvagedData.size()); + + bool fSuccess = allOK; + Db* pdbCopy = new Db(&dbenv.dbenv, 0); + int ret = pdbCopy->open(NULL, // Txn pointer + filename.c_str(), // Filename + "main", // Logical db name + DB_BTREE, // Database type + DB_CREATE, // Flags + 0); + if (ret > 0) + { + printf("Cannot create database file %s\n", filename.c_str()); + return false; + } + CWallet dummyWallet; + int nFileVersion = 0; + vector vWalletUpgrade; + bool fIsEncrypted = false; + bool fAnyUnordered = false; + + DbTxn* ptxn = dbenv.TxnBegin(); + BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData) + { + if (fOnlyKeys) + { + CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); + CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); + string strType, strErr; + bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, + nFileVersion, vWalletUpgrade, + fIsEncrypted, fAnyUnordered, + strType, strErr); + if (!IsKeyType(strType)) + continue; + if (!fReadOK) + { + printf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType.c_str(), strErr.c_str()); + continue; + } + } + Dbt datKey(&row.first[0], row.first.size()); + Dbt datValue(&row.second[0], row.second.size()); + int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); + if (ret2 > 0) + fSuccess = false; + } + ptxn->commit(0); + pdbCopy->close(0); + delete pdbCopy; + + return fSuccess; +} + +bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename) +{ + return CWalletDB::Recover(dbenv, filename, false); +} diff --git a/src/walletdb.h b/src/walletdb.h index dee1750..a3e779a 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -6,6 +6,7 @@ #define BITCOIN_WALLETDB_H #include "db.h" +#include "base58.h" class CKeyPool; class CAccount; @@ -16,6 +17,7 @@ enum DBErrors { DB_LOAD_OK, DB_CORRUPT, + DB_NONCRITICAL_ERROR, DB_TOO_NEW, DB_LOAD_FAIL, DB_NEED_REWRITE @@ -32,21 +34,10 @@ private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); public: - bool ReadName(const std::string& strAddress, std::string& strName) - { - strName = ""; - return Read(std::make_pair(std::string("name"), strAddress), strName); - } - bool WriteName(const std::string& strAddress, const std::string& strName); bool EraseName(const std::string& strAddress); - bool ReadTx(uint256 hash, CWalletTx& wtx) - { - return Read(std::make_pair(std::string("tx"), hash), wtx); - } - bool WriteTx(uint256 hash, const CWalletTx& wtx) { nWalletDBUpdated++; @@ -59,27 +50,21 @@ public: return Erase(std::make_pair(std::string("tx"), hash)); } - bool ReadKey(const std::vector& vchPubKey, CPrivKey& vchPrivKey) - { - vchPrivKey.clear(); - return Read(std::make_pair(std::string("key"), vchPubKey), vchPrivKey); - } - - bool WriteKey(const std::vector& vchPubKey, const CPrivKey& vchPrivKey) + bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey) { nWalletDBUpdated++; - return Write(std::make_pair(std::string("key"), vchPubKey), vchPrivKey, false); + return Write(std::make_pair(std::string("key"), vchPubKey.Raw()), vchPrivKey, false); } - bool WriteCryptedKey(const std::vector& vchPubKey, const std::vector& vchCryptedSecret, bool fEraseUnencryptedKey = true) + bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector& vchCryptedSecret, bool fEraseUnencryptedKey = true) { nWalletDBUpdated++; - if (!Write(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) + if (!Write(std::make_pair(std::string("ckey"), vchPubKey.Raw()), vchCryptedSecret, false)) return false; if (fEraseUnencryptedKey) { - Erase(std::make_pair(std::string("key"), vchPubKey)); - Erase(std::make_pair(std::string("wkey"), vchPubKey)); + Erase(std::make_pair(std::string("key"), vchPubKey.Raw())); + Erase(std::make_pair(std::string("wkey"), vchPubKey.Raw())); } return true; } @@ -90,13 +75,6 @@ public: return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true); } - // Support for BIP 0013 : see https://en.bitcoin.it/wiki/BIP_0013 - bool ReadCScript(const uint160 &hash, CScript& redeemScript) - { - redeemScript.clear(); - return Read(std::make_pair(std::string("cscript"), hash), redeemScript); - } - bool WriteCScript(const uint160& hash, const CScript& redeemScript) { nWalletDBUpdated++; @@ -114,16 +92,16 @@ public: return Read(std::string("bestblock"), locator); } - bool ReadDefaultKey(std::vector& vchPubKey) + bool WriteOrderPosNext(int64 nOrderPosNext) { - vchPubKey.clear(); - return Read(std::string("defaultkey"), vchPubKey); + nWalletDBUpdated++; + return Write(std::string("orderposnext"), nOrderPosNext); } - bool WriteDefaultKey(const std::vector& vchPubKey) + bool WriteDefaultKey(const CPubKey& vchPubKey) { nWalletDBUpdated++; - return Write(std::string("defaultkey"), vchPubKey); + return Write(std::string("defaultkey"), vchPubKey.Raw()); } bool ReadPool(int64 nPool, CKeyPool& keypool) @@ -169,11 +147,17 @@ public: bool ReadAccount(const std::string& strAccount, CAccount& account); bool WriteAccount(const std::string& strAccount, const CAccount& account); +private: + bool WriteAccountingEntry(const uint64 nAccEntryNum, const CAccountingEntry& acentry); +public: bool WriteAccountingEntry(const CAccountingEntry& acentry); int64 GetAccountCreditDebit(const std::string& strAccount); void ListAccountCreditDebit(const std::string& strAccount, std::list& acentries); - int LoadWallet(CWallet* pwallet); + DBErrors ReorderTransactions(CWallet*); + DBErrors LoadWallet(CWallet* pwallet); + static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys); + static bool Recover(CDBEnv& dbenv, std::string filename); }; #endif // BITCOIN_WALLETDB_H -- 1.7.1