diff options
Diffstat (limited to 'src')
93 files changed, 2599 insertions, 1515 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index cc8dded413..390e9f1436 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -98,6 +98,8 @@ BITCOIN_CORE_H = \ eccryptoverify.h \ ecwrapper.h \ hash.h \ + httprpc.h \ + httpserver.h \ init.h \ key.h \ keystore.h \ @@ -119,6 +121,7 @@ BITCOIN_CORE_H = \ protocol.h \ pubkey.h \ random.h \ + reverselock.h \ rpcclient.h \ rpcprotocol.h \ rpcserver.h \ @@ -163,13 +166,15 @@ obj/build.h: FORCE libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h # server: shared between bitcoind and bitcoin-qt -libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) +libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_server_a_SOURCES = \ addrman.cpp \ alert.cpp \ bloom.cpp \ chain.cpp \ checkpoints.cpp \ + httprpc.cpp \ + httpserver.cpp \ init.cpp \ leveldbwrapper.cpp \ main.cpp \ @@ -319,12 +324,12 @@ if ENABLE_WALLET bitcoind_LDADD += libbitcoin_wallet.a endif -bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) +bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) # # bitcoin-cli binary # bitcoin_cli_SOURCES = bitcoin-cli.cpp -bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES) +bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) bitcoin_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS @@ -337,7 +342,7 @@ bitcoin_cli_LDADD = \ $(LIBBITCOIN_UTIL) \ $(LIBSECP256K1) -bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) +bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS) # # bitcoin-tx binary # diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 748f2b14d5..8d60aca25c 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -362,7 +362,8 @@ if ENABLE_WALLET qt_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ - $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) + $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_bitcoin_qt_LIBTOOLFLAGS = --tag CXX diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index c5392cf307..4250bb8f3b 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -32,7 +32,8 @@ qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) \ $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ - $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) + $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) CLEAN_BITCOIN_QT_TEST = $(TEST_QT_MOC_CPP) qt/test/*.gcda qt/test/*.gcno diff --git a/src/Makefile.test.include b/src/Makefile.test.include index f9384a09a4..cc60cd92bb 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -15,6 +15,8 @@ EXTRA_DIST += \ test/data/tx394b54bb.hex \ test/data/txcreate1.hex \ test/data/txcreate2.hex \ + test/data/txcreatedata1.hex \ + test/data/txcreatedata2.hex \ test/data/txcreatesign.hex JSON_TEST_FILES = \ @@ -50,6 +52,7 @@ BITCOIN_TESTS =\ test/getarg_tests.cpp \ test/hash_tests.cpp \ test/key_tests.cpp \ + test/limitedmap_tests.cpp \ test/main_tests.cpp \ test/mempool_tests.cpp \ test/miner_tests.cpp \ @@ -59,6 +62,7 @@ BITCOIN_TESTS =\ test/pmt_tests.cpp \ test/policyestimator_tests.cpp \ test/pow_tests.cpp \ + test/reverselock_tests.cpp \ test/rpc_tests.cpp \ test/sanity_tests.cpp \ test/scheduler_tests.cpp \ diff --git a/src/addrman.cpp b/src/addrman.cpp index b605f4351d..ff1f7e9187 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -341,8 +341,10 @@ CAddrInfo CAddrMan::Select_() while (1) { int nKBucket = GetRandInt(ADDRMAN_TRIED_BUCKET_COUNT); int nKBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE); - if (vvTried[nKBucket][nKBucketPos] == -1) - continue; + while (vvTried[nKBucket][nKBucketPos] == -1) { + nKBucket = (nKBucket + insecure_rand()) % ADDRMAN_TRIED_BUCKET_COUNT; + nKBucketPos = (nKBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE; + } int nId = vvTried[nKBucket][nKBucketPos]; assert(mapInfo.count(nId) == 1); CAddrInfo& info = mapInfo[nId]; @@ -356,8 +358,10 @@ CAddrInfo CAddrMan::Select_() while (1) { int nUBucket = GetRandInt(ADDRMAN_NEW_BUCKET_COUNT); int nUBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE); - if (vvNew[nUBucket][nUBucketPos] == -1) - continue; + while (vvNew[nUBucket][nUBucketPos] == -1) { + nUBucket = (nUBucket + insecure_rand()) % ADDRMAN_NEW_BUCKET_COUNT; + nUBucketPos = (nUBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE; + } int nId = vvNew[nUBucket][nUBucketPos]; assert(mapInfo.count(nId) == 1); CAddrInfo& info = mapInfo[nId]; diff --git a/src/addrman.h b/src/addrman.h index 2623d89809..384b6cfdb9 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -265,7 +265,7 @@ public: * Notice that vvTried, mapAddr and vVector are never encoded explicitly; * they are instead reconstructed from the other information. * - * vvNew is serialized, but only used if ADDRMAN_UNKOWN_BUCKET_COUNT didn't change, + * vvNew is serialized, but only used if ADDRMAN_UNKNOWN_BUCKET_COUNT didn't change, * otherwise it is reconstructed as well. * * This format is more complex, but significantly smaller (at most 1.5 MiB), and supports diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 1c5a312874..866c6f2d44 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -11,6 +11,12 @@ #include "utilstrencodings.h" #include <boost/filesystem/operations.hpp> +#include <stdio.h> + +#include <event2/event.h> +#include <event2/http.h> +#include <event2/buffer.h> +#include <event2/keyvalq_struct.h> #include "univalue/univalue.h" @@ -32,9 +38,6 @@ std::string HelpMessageCli() strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); - strUsage += HelpMessageGroup(_("SSL options: (see the Bitcoin Wiki for SSL setup instructions)")); - strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections")); - return strUsage; } @@ -92,32 +95,75 @@ static bool AppInitRPC(int argc, char* argv[]) fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); return false; } + if (GetBoolArg("-rpcssl", false)) + { + fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n"); + return false; + } return true; } -UniValue CallRPC(const string& strMethod, const UniValue& params) + +/** Reply structure for request_done to fill in */ +struct HTTPReply { - // Connect to localhost - bool fUseSSL = GetBoolArg("-rpcssl", false); - boost::asio::io_service io_service; - boost::asio::ssl::context context(io_service, boost::asio::ssl::context::sslv23); - context.set_options(boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3); - boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslStream(io_service, context); - SSLIOStreamDevice<boost::asio::ip::tcp> d(sslStream, fUseSSL); - boost::iostreams::stream< SSLIOStreamDevice<boost::asio::ip::tcp> > stream(d); - - const bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort()))); - if (!fConnected) - throw CConnectionFailed("couldn't connect to server"); + int status; + std::string body; +}; + +static void http_request_done(struct evhttp_request *req, void *ctx) +{ + HTTPReply *reply = static_cast<HTTPReply*>(ctx); + + if (req == NULL) { + /* If req is NULL, it means an error occurred while connecting, but + * I'm not sure how to find out which one. We also don't really care. + */ + reply->status = 0; + return; + } - // Find credentials to use + reply->status = evhttp_request_get_response_code(req); + + struct evbuffer *buf = evhttp_request_get_input_buffer(req); + if (buf) + { + size_t size = evbuffer_get_length(buf); + const char *data = (const char*)evbuffer_pullup(buf, size); + if (data) + reply->body = std::string(data, size); + evbuffer_drain(buf, size); + } +} + +UniValue CallRPC(const string& strMethod, const UniValue& params) +{ + std::string host = GetArg("-rpcconnect", "127.0.0.1"); + int port = GetArg("-rpcport", BaseParams().RPCPort()); + + // Create event base + struct event_base *base = event_base_new(); // TODO RAII + if (!base) + throw runtime_error("cannot create event_base"); + + // Synchronously look up hostname + struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, host.c_str(), port); // TODO RAII + if (evcon == NULL) + throw runtime_error("create connection failed"); + evhttp_connection_set_timeout(evcon, GetArg("-rpctimeout", 30)); + + HTTPReply response; + struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII + if (req == NULL) + throw runtime_error("create http request failed"); + + // Get credentials std::string strRPCUserColonPass; if (mapArgs["-rpcpassword"] == "") { // Try fall back to cookie-based authentication if no password is provided if (!GetAuthCookie(&strRPCUserColonPass)) { throw runtime_error(strprintf( - _("You must set rpcpassword=<password> in the configuration file:\n%s\n" - "If the file does not exist, create it with owner-readable-only file permissions."), + _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"), GetConfigFile().string().c_str())); } @@ -125,34 +171,41 @@ UniValue CallRPC(const string& strMethod, const UniValue& params) strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; } - // HTTP basic authentication - map<string, string> mapRequestHeaders; - mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass); - - // Send request - string strRequest = JSONRPCRequest(strMethod, params, 1); - string strPost = HTTPPost(strRequest, mapRequestHeaders); - stream << strPost << std::flush; - - // Receive HTTP reply status - int nProto = 0; - int nStatus = ReadHTTPStatus(stream, nProto); + struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req); + assert(output_headers); + evhttp_add_header(output_headers, "Host", host.c_str()); + evhttp_add_header(output_headers, "Connection", "close"); + evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str()); + + // Attach request data + std::string strRequest = JSONRPCRequest(strMethod, params, 1); + struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req); + assert(output_buffer); + evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); + + int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/"); + if (r != 0) { + evhttp_connection_free(evcon); + event_base_free(base); + throw CConnectionFailed("send http request failed"); + } - // Receive HTTP reply message headers and body - map<string, string> mapHeaders; - string strReply; - ReadHTTPMessage(stream, mapHeaders, strReply, nProto, std::numeric_limits<size_t>::max()); + event_base_dispatch(base); + evhttp_connection_free(evcon); + event_base_free(base); - if (nStatus == HTTP_UNAUTHORIZED) + if (response.status == 0) + throw CConnectionFailed("couldn't connect to server"); + else if (response.status == HTTP_UNAUTHORIZED) throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); - else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR) - throw runtime_error(strprintf("server returned HTTP error %d", nStatus)); - else if (strReply.empty()) + else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) + throw runtime_error(strprintf("server returned HTTP error %d", response.status)); + else if (response.body.empty()) throw runtime_error("no response from server"); // Parse reply UniValue valReply(UniValue::VSTR); - if (!valReply.read(strReply)) + if (!valReply.read(response.body)) throw runtime_error("couldn't parse reply from server"); const UniValue& reply = valReply.get_obj(); if (reply.empty()) @@ -248,6 +301,10 @@ int CommandLineRPC(int argc, char *argv[]) int main(int argc, char* argv[]) { SetupEnvironment(); + if (!SetupNetworking()) { + fprintf(stderr, "Error: Initializing networking failed\n"); + exit(1); + } try { if(!AppInitRPC(argc, argv)) diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index e389d51a73..97a073174d 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -70,6 +70,7 @@ static bool AppInitRawTx(int argc, char* argv[]) strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N")); strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N")); strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX")); + strUsage += HelpMessageOpt("outdata=[VALUE:]DATA", _("Add data-based output to TX")); strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT", _("Add raw script output to TX")); strUsage += HelpMessageOpt("sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " + _("This command requires JSON registers:") + @@ -231,6 +232,35 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) tx.vout.push_back(txout); } +static void MutateTxAddOutData(CMutableTransaction& tx, const string& strInput) +{ + CAmount value = 0; + + // separate [VALUE:]DATA in string + size_t pos = strInput.find(':'); + + if (pos==0) + throw runtime_error("TX output value not specified"); + + if (pos != string::npos) { + // extract and validate VALUE + string strValue = strInput.substr(0, pos); + if (!ParseMoney(strValue, value)) + throw runtime_error("invalid TX output value"); + } + + // extract and validate DATA + string strData = strInput.substr(pos + 1, string::npos); + + if (!IsHex(strData)) + throw runtime_error("invalid TX output data"); + + std::vector<unsigned char> data = ParseHex(strData); + + CTxOut txout(value, CScript() << OP_RETURN << data); + tx.vout.push_back(txout); +} + static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) { // separate VALUE:SCRIPT in string @@ -470,6 +500,8 @@ static void MutateTx(CMutableTransaction& tx, const string& command, MutateTxDelOutput(tx, commandVal); else if (command == "outaddr") MutateTxAddOutAddr(tx, commandVal); + else if (command == "outdata") + MutateTxAddOutData(tx, commandVal); else if (command == "outscript") MutateTxAddOutScript(tx, commandVal); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 39bb301f44..b512f74c22 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -10,11 +10,16 @@ #include "noui.h" #include "scheduler.h" #include "util.h" +#include "httpserver.h" +#include "httprpc.h" +#include "rpcserver.h" #include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem.hpp> #include <boost/thread.hpp> +#include <stdio.h> + /* Introduction text for doxygen: */ /*! \mainpage Developer documentation @@ -44,7 +49,7 @@ void WaitForShutdown(boost::thread_group* threadGroup) } if (threadGroup) { - threadGroup->interrupt_all(); + Interrupt(*threadGroup); threadGroup->join_all(); } } @@ -154,7 +159,7 @@ bool AppInit(int argc, char* argv[]) if (!fRet) { - threadGroup.interrupt_all(); + Interrupt(threadGroup); // threadGroup.join_all(); was left out intentionally here, because we didn't re-test all of // the startup-failure cases to make sure they don't result in a hang due to some // thread-blocking-waiting-for-another-thread-during-startup case diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 623104690a..9c843f6e7e 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -12,8 +12,6 @@ #include <boost/assign/list_of.hpp> -using namespace std; - #include "chainparamsseeds.h" static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) @@ -22,7 +20,7 @@ static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesi txNew.nVersion = 1; txNew.vin.resize(1); txNew.vout.resize(1); - txNew.vin[0].scriptSig = CScript() << 486604799 << CScriptNum(4) << vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); + txNew.vin[0].scriptSig = CScript() << 486604799 << CScriptNum(4) << std::vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); txNew.vout[0].nValue = genesisReward; txNew.vout[0].scriptPubKey = genesisOutputScript; @@ -117,7 +115,7 @@ public: fMineBlocksOnDemand = false; fTestnetToBeDeprecatedFieldRPC = false; - checkpointData = (Checkpoints::CCheckpointData) { + checkpointData = (CCheckpointData) { boost::assign::map_list_of ( 11111, uint256S("0x0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")) ( 33333, uint256S("0x000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")) @@ -190,7 +188,7 @@ public: fMineBlocksOnDemand = false; fTestnetToBeDeprecatedFieldRPC = true; - checkpointData = (Checkpoints::CCheckpointData) { + checkpointData = (CCheckpointData) { boost::assign::map_list_of ( 546, uint256S("000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70")), 1337966069, @@ -239,7 +237,7 @@ public: fMineBlocksOnDemand = true; fTestnetToBeDeprecatedFieldRPC = false; - checkpointData = (Checkpoints::CCheckpointData){ + checkpointData = (CCheckpointData){ boost::assign::map_list_of ( 0, uint256S("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")), 0, diff --git a/src/chainparams.h b/src/chainparams.h index 66d865b620..5db39aa09c 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -7,7 +7,6 @@ #define BITCOIN_CHAINPARAMS_H #include "chainparamsbase.h" -#include "checkpoints.h" #include "consensus/params.h" #include "primitives/block.h" #include "protocol.h" @@ -24,6 +23,14 @@ struct SeedSpec6 { uint16_t port; }; +typedef std::map<int, uint256> MapCheckpoints; + +struct CCheckpointData { + MapCheckpoints mapCheckpoints; + int64_t nTimeLastCheckpoint; + int64_t nTransactionsLastCheckpoint; + double fTransactionsPerDay; +}; /** * CChainParams defines various tweakable parameters of a given instance of the @@ -67,7 +74,7 @@ public: const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; } const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; } - const Checkpoints::CCheckpointData& Checkpoints() const { return checkpointData; } + const CCheckpointData& Checkpoints() const { return checkpointData; } protected: CChainParams() {} @@ -87,7 +94,7 @@ protected: bool fRequireStandard; bool fMineBlocksOnDemand; bool fTestnetToBeDeprecatedFieldRPC; - Checkpoints::CCheckpointData checkpointData; + CCheckpointData checkpointData; }; /** diff --git a/src/checkpoints.h b/src/checkpoints.h index 001e3cc801..5fce6fa81e 100644 --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -10,6 +10,7 @@ #include <map> class CBlockIndex; +struct CCheckpointData; /** * Block-chain checkpoints are compiled-in sanity checks. @@ -17,14 +18,6 @@ class CBlockIndex; */ namespace Checkpoints { -typedef std::map<int, uint256> MapCheckpoints; - -struct CCheckpointData { - MapCheckpoints mapCheckpoints; - int64_t nTimeLastCheckpoint; - int64_t nTransactionsLastCheckpoint; - double fTransactionsPerDay; -}; //! Return conservative estimate of total number of blocks, 0 if unknown int GetTotalBlocksEstimate(const CCheckpointData& data); diff --git a/src/coincontrol.h b/src/coincontrol.h index 3e8de83c39..bc965f9e19 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -14,6 +14,8 @@ public: CTxDestination destChange; //! If false, allows unselected inputs, but requires all selected inputs be used bool fAllowOtherInputs; + //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria + bool fAllowWatchOnly; CCoinControl() { @@ -24,6 +26,7 @@ public: { destChange = CNoDestination(); fAllowOtherInputs = false; + fAllowWatchOnly = false; setSelected.clear(); } diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 719e090a42..d6051edc38 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -30,14 +30,17 @@ private: std::string strRejectReason; unsigned int chRejectCode; bool corruptionPossible; + std::string strDebugMessage; public: CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {} bool DoS(int level, bool ret = false, - unsigned int chRejectCodeIn=0, std::string strRejectReasonIn="", - bool corruptionIn=false) { + unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="", + bool corruptionIn=false, + const std::string &strDebugMessageIn="") { chRejectCode = chRejectCodeIn; strRejectReason = strRejectReasonIn; corruptionPossible = corruptionIn; + strDebugMessage = strDebugMessageIn; if (mode == MODE_ERROR) return ret; nDoS += level; @@ -45,8 +48,9 @@ public: return ret; } bool Invalid(bool ret = false, - unsigned int _chRejectCode=0, std::string _strRejectReason="") { - return DoS(0, ret, _chRejectCode, _strRejectReason); + unsigned int _chRejectCode=0, const std::string &_strRejectReason="", + const std::string &_strDebugMessage="") { + return DoS(0, ret, _chRejectCode, _strRejectReason, false, _strDebugMessage); } bool Error(const std::string& strRejectReasonIn) { if (mode == MODE_VALID) @@ -75,6 +79,7 @@ public: } unsigned int GetRejectCode() const { return chRejectCode; } std::string GetRejectReason() const { return strRejectReason; } + std::string GetDebugMessage() const { return strDebugMessage; } }; #endif // BITCOIN_CONSENSUS_VALIDATION_H diff --git a/src/ecwrapper.cpp b/src/ecwrapper.cpp index 5e3aec25ba..f94bc954fd 100644 --- a/src/ecwrapper.cpp +++ b/src/ecwrapper.cpp @@ -13,6 +13,29 @@ namespace { +class ecgroup_order +{ +public: + static const EC_GROUP* get() + { + static const ecgroup_order wrapper; + return wrapper.pgroup; + } + +private: + ecgroup_order() + : pgroup(EC_GROUP_new_by_curve_name(NID_secp256k1)) + { + } + + ~ecgroup_order() + { + EC_GROUP_free(pgroup); + } + + EC_GROUP* pgroup; +}; + /** * Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields * recid selects which key is recovered @@ -92,8 +115,10 @@ err: } // anon namespace CECKey::CECKey() { - pkey = EC_KEY_new_by_curve_name(NID_secp256k1); + pkey = EC_KEY_new(); assert(pkey != NULL); + int result = EC_KEY_set_group(pkey, ecgroup_order::get()); + assert(result); } CECKey::~CECKey() { @@ -185,11 +210,9 @@ bool CECKey::TweakPublic(const unsigned char vchTweak[32]) { bool CECKey::SanityCheck() { - EC_KEY *pkey = EC_KEY_new_by_curve_name(NID_secp256k1); - if(pkey == NULL) + const EC_GROUP *pgroup = ecgroup_order::get(); + if(pgroup == NULL) return false; - EC_KEY_free(pkey); - // TODO Is there more EC functionality that could be missing? return true; } diff --git a/src/httprpc.cpp b/src/httprpc.cpp new file mode 100644 index 0000000000..98ac750bb1 --- /dev/null +++ b/src/httprpc.cpp @@ -0,0 +1,193 @@ +#include "httprpc.h" + +#include "base58.h" +#include "chainparams.h" +#include "httpserver.h" +#include "rpcprotocol.h" +#include "rpcserver.h" +#include "random.h" +#include "sync.h" +#include "util.h" +#include "utilstrencodings.h" +#include "ui_interface.h" + +#include <boost/algorithm/string.hpp> // boost::trim + +/** Simple one-shot callback timer to be used by the RPC mechanism to e.g. + * re-lock the wellet. + */ +class HTTPRPCTimer : public RPCTimerBase +{ +public: + HTTPRPCTimer(struct event_base* eventBase, boost::function<void(void)>& func, int64_t millis) : + ev(eventBase, false, func) + { + struct timeval tv; + tv.tv_sec = millis/1000; + tv.tv_usec = (millis%1000)*1000; + ev.trigger(&tv); + } +private: + HTTPEvent ev; +}; + +class HTTPRPCTimerInterface : public RPCTimerInterface +{ +public: + HTTPRPCTimerInterface(struct event_base* base) : base(base) + { + } + const char* Name() + { + return "HTTP"; + } + RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) + { + return new HTTPRPCTimer(base, func, millis); + } +private: + struct event_base* base; +}; + + +/* Pre-base64-encoded authentication token */ +static std::string strRPCUserColonPass; +/* Stored RPC timer interface (for unregistration) */ +static HTTPRPCTimerInterface* httpRPCTimerInterface = 0; + +static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) +{ + // Send error reply from json-rpc error object + int nStatus = HTTP_INTERNAL_SERVER_ERROR; + int code = find_value(objError, "code").get_int(); + + if (code == RPC_INVALID_REQUEST) + nStatus = HTTP_BAD_REQUEST; + else if (code == RPC_METHOD_NOT_FOUND) + nStatus = HTTP_NOT_FOUND; + + std::string strReply = JSONRPCReply(NullUniValue, objError, id); + + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(nStatus, strReply); +} + +static bool RPCAuthorized(const std::string& strAuth) +{ + if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called + return false; + if (strAuth.substr(0, 6) != "Basic ") + return false; + std::string strUserPass64 = strAuth.substr(6); + boost::trim(strUserPass64); + std::string strUserPass = DecodeBase64(strUserPass64); + return TimingResistantEqual(strUserPass, strRPCUserColonPass); +} + +static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &) +{ + // JSONRPC handles only POST + if (req->GetRequestMethod() != HTTPRequest::POST) { + req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests"); + return false; + } + // Check authorization + std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); + if (!authHeader.first) { + req->WriteReply(HTTP_UNAUTHORIZED); + return false; + } + + if (!RPCAuthorized(authHeader.second)) { + LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString()); + + /* Deter brute-forcing + If this results in a DoS the user really + shouldn't have their RPC port exposed. */ + MilliSleep(250); + + req->WriteReply(HTTP_UNAUTHORIZED); + return false; + } + + JSONRequest jreq; + try { + // Parse request + UniValue valRequest; + if (!valRequest.read(req->ReadBody())) + throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); + + std::string strReply; + // singleton request + if (valRequest.isObject()) { + jreq.parse(valRequest); + + UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); + + // Send reply + strReply = JSONRPCReply(result, NullUniValue, jreq.id); + + // array of requests + } else if (valRequest.isArray()) + strReply = JSONRPCExecBatch(valRequest.get_array()); + else + throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); + + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strReply); + } catch (const UniValue& objError) { + JSONErrorReply(req, objError, jreq.id); + return false; + } catch (const std::exception& e) { + JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + return false; + } + return true; +} + +static bool InitRPCAuthentication() +{ + if (mapArgs["-rpcpassword"] == "") + { + LogPrintf("No rpcpassword set - using random cookie authentication\n"); + if (!GenerateAuthCookie(&strRPCUserColonPass)) { + uiInterface.ThreadSafeMessageBox( + _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode + "", CClientUIInterface::MSG_ERROR); + return false; + } + } else { + strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; + } + return true; +} + +bool StartHTTPRPC() +{ + LogPrint("rpc", "Starting HTTP RPC server\n"); + if (!InitRPCAuthentication()) + return false; + + RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); + + assert(EventBase()); + httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); + RPCRegisterTimerInterface(httpRPCTimerInterface); + return true; +} + +void InterruptHTTPRPC() +{ + LogPrint("rpc", "Interrupting HTTP RPC server\n"); +} + +void StopHTTPRPC() +{ + LogPrint("rpc", "Stopping HTTP RPC server\n"); + UnregisterHTTPHandler("/", true); + if (httpRPCTimerInterface) { + RPCUnregisterTimerInterface(httpRPCTimerInterface); + delete httpRPCTimerInterface; + httpRPCTimerInterface = 0; + } +} diff --git a/src/httprpc.h b/src/httprpc.h new file mode 100644 index 0000000000..d354457188 --- /dev/null +++ b/src/httprpc.h @@ -0,0 +1,37 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_HTTPRPC_H +#define BITCOIN_HTTPRPC_H + +#include <string> +#include <map> + +class HTTPRequest; + +/** Start HTTP RPC subsystem. + * Precondition; HTTP and RPC has been started. + */ +bool StartHTTPRPC(); +/** Interrupt HTTP RPC subsystem. + */ +void InterruptHTTPRPC(); +/** Stop HTTP RPC subsystem. + * Precondition; HTTP and RPC has been stopped. + */ +void StopHTTPRPC(); + +/** Start HTTP REST subsystem. + * Precondition; HTTP and RPC has been started. + */ +bool StartREST(); +/** Interrupt RPC REST subsystem. + */ +void InterruptREST(); +/** Stop HTTP REST subsystem. + * Precondition; HTTP and RPC has been stopped. + */ +void StopREST(); + +#endif diff --git a/src/httpserver.cpp b/src/httpserver.cpp new file mode 100644 index 0000000000..baca007571 --- /dev/null +++ b/src/httpserver.cpp @@ -0,0 +1,575 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "httpserver.h" + +#include "chainparamsbase.h" +#include "compat.h" +#include "util.h" +#include "netbase.h" +#include "rpcprotocol.h" // For HTTP status codes +#include "sync.h" +#include "ui_interface.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> + +#include <event2/event.h> +#include <event2/http.h> +#include <event2/thread.h> +#include <event2/buffer.h> +#include <event2/util.h> +#include <event2/keyvalq_struct.h> + +#ifdef EVENT__HAVE_NETINET_IN_H +#include <netinet/in.h> +#ifdef _XOPEN_SOURCE_EXTENDED +#include <arpa/inet.h> +#endif +#endif + +#include <boost/algorithm/string/case_conv.hpp> // for to_lower() +#include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> + +/** HTTP request work item */ +class HTTPWorkItem : public HTTPClosure +{ +public: + HTTPWorkItem(HTTPRequest* req, const std::string &path, const HTTPRequestHandler& func): + req(req), path(path), func(func) + { + } + void operator()() + { + func(req.get(), path); + } + + boost::scoped_ptr<HTTPRequest> req; + +private: + std::string path; + HTTPRequestHandler func; +}; + +/** Simple work queue for distributing work over multiple threads. + * Work items are simply callable objects. + */ +template <typename WorkItem> +class WorkQueue +{ +private: + /** Mutex protects entire object */ + CWaitableCriticalSection cs; + CConditionVariable cond; + /* XXX in C++11 we can use std::unique_ptr here and avoid manual cleanup */ + std::deque<WorkItem*> queue; + bool running; + size_t maxDepth; + +public: + WorkQueue(size_t maxDepth) : running(true), + maxDepth(maxDepth) + { + } + /* Precondition: worker threads have all stopped */ + ~WorkQueue() + { + while (!queue.empty()) { + delete queue.front(); + queue.pop_front(); + } + } + /** Enqueue a work item */ + bool Enqueue(WorkItem* item) + { + boost::unique_lock<boost::mutex> lock(cs); + if (queue.size() >= maxDepth) { + return false; + } + queue.push_back(item); + cond.notify_one(); + return true; + } + /** Thread function */ + void Run() + { + while (running) { + WorkItem* i = 0; + { + boost::unique_lock<boost::mutex> lock(cs); + while (running && queue.empty()) + cond.wait(lock); + if (!running) + break; + i = queue.front(); + queue.pop_front(); + } + (*i)(); + delete i; + } + } + /** Interrupt and exit loops */ + void Interrupt() + { + boost::unique_lock<boost::mutex> lock(cs); + running = false; + cond.notify_all(); + } + + /** Return current depth of queue */ + size_t Depth() + { + boost::unique_lock<boost::mutex> lock(cs); + return queue.size(); + } +}; + +struct HTTPPathHandler +{ + HTTPPathHandler() {} + HTTPPathHandler(std::string prefix, bool exactMatch, HTTPRequestHandler handler): + prefix(prefix), exactMatch(exactMatch), handler(handler) + { + } + std::string prefix; + bool exactMatch; + HTTPRequestHandler handler; +}; + +/** HTTP module state */ + +//! libevent event loop +static struct event_base* eventBase = 0; +//! HTTP server +struct evhttp* eventHTTP = 0; +//! List of subnets to allow RPC connections from +static std::vector<CSubNet> rpc_allow_subnets; +//! Work queue for handling longer requests off the event loop thread +static WorkQueue<HTTPClosure>* workQueue = 0; +//! Handlers for (sub)paths +std::vector<HTTPPathHandler> pathHandlers; + +/** Check if a network address is allowed to access the HTTP server */ +static bool ClientAllowed(const CNetAddr& netaddr) +{ + if (!netaddr.IsValid()) + return false; + BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets) + if (subnet.Match(netaddr)) + return true; + return false; +} + +/** Initialize ACL list for HTTP server */ +static bool InitHTTPAllowList() +{ + rpc_allow_subnets.clear(); + rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet + rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost + if (mapMultiArgs.count("-rpcallowip")) { + const std::vector<std::string>& vAllow = mapMultiArgs["-rpcallowip"]; + BOOST_FOREACH (std::string strAllow, vAllow) { + CSubNet subnet(strAllow); + if (!subnet.IsValid()) { + uiInterface.ThreadSafeMessageBox( + strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow), + "", CClientUIInterface::MSG_ERROR); + return false; + } + rpc_allow_subnets.push_back(subnet); + } + } + std::string strAllowed; + BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets) + strAllowed += subnet.ToString() + " "; + LogPrint("http", "Allowing HTTP connections from: %s\n", strAllowed); + return true; +} + +/** HTTP request method as string - use for logging only */ +static std::string RequestMethodString(HTTPRequest::RequestMethod m) +{ + switch (m) { + case HTTPRequest::GET: + return "GET"; + break; + case HTTPRequest::POST: + return "POST"; + break; + case HTTPRequest::HEAD: + return "HEAD"; + break; + case HTTPRequest::PUT: + return "PUT"; + break; + default: + return "unknown"; + } +} + +/** HTTP request callback */ +static void http_request_cb(struct evhttp_request* req, void* arg) +{ + std::auto_ptr<HTTPRequest> hreq(new HTTPRequest(req)); + + LogPrint("http", "Received a %s request for %s from %s\n", + RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString()); + + // Early address-based allow check + if (!ClientAllowed(hreq->GetPeer())) { + hreq->WriteReply(HTTP_FORBIDDEN); + return; + } + + // Early reject unknown HTTP methods + if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) { + hreq->WriteReply(HTTP_BADMETHOD); + return; + } + + // Find registered handler for prefix + std::string strURI = hreq->GetURI(); + std::string path; + std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin(); + std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end(); + for (; i != iend; ++i) { + bool match = false; + if (i->exactMatch) + match = (strURI == i->prefix); + else + match = (strURI.substr(0, i->prefix.size()) == i->prefix); + if (match) { + path = strURI.substr(i->prefix.size()); + break; + } + } + + // Dispatch to worker thread + if (i != iend) { + std::auto_ptr<HTTPWorkItem> item(new HTTPWorkItem(hreq.release(), path, i->handler)); + assert(workQueue); + if (workQueue->Enqueue(item.get())) + item.release(); /* if true, queue took ownership */ + else + item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded"); + } else { + hreq->WriteReply(HTTP_NOTFOUND); + } +} + +/** Event dispatcher thread */ +static void ThreadHTTP(struct event_base* base, struct evhttp* http) +{ + RenameThread("bitcoin-http"); + LogPrint("http", "Entering http event loop\n"); + event_base_dispatch(base); + // Event loop will be interrupted by InterruptHTTPServer() + LogPrint("http", "Exited http event loop\n"); +} + +/** Bind HTTP server to specified addresses */ +static bool HTTPBindAddresses(struct evhttp* http) +{ + int defaultPort = GetArg("-rpcport", BaseParams().RPCPort()); + int nBound = 0; + std::vector<std::pair<std::string, uint16_t> > endpoints; + + // Determine what addresses to bind to + if (!mapArgs.count("-rpcallowip")) { // Default to loopback if not allowing external IPs + endpoints.push_back(std::make_pair("::1", defaultPort)); + endpoints.push_back(std::make_pair("127.0.0.1", defaultPort)); + if (mapArgs.count("-rpcbind")) { + LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n"); + } + } else if (mapArgs.count("-rpcbind")) { // Specific bind address + const std::vector<std::string>& vbind = mapMultiArgs["-rpcbind"]; + for (std::vector<std::string>::const_iterator i = vbind.begin(); i != vbind.end(); ++i) { + int port = defaultPort; + std::string host; + SplitHostPort(*i, port, host); + endpoints.push_back(std::make_pair(host, port)); + } + } else { // No specific bind address specified, bind to any + endpoints.push_back(std::make_pair("::", defaultPort)); + endpoints.push_back(std::make_pair("0.0.0.0", defaultPort)); + } + + // Bind addresses + for (std::vector<std::pair<std::string, uint16_t> >::iterator i = endpoints.begin(); i != endpoints.end(); ++i) { + LogPrint("http", "Binding RPC on address %s port %i\n", i->first, i->second); + if (evhttp_bind_socket(http, i->first.empty() ? NULL : i->first.c_str(), i->second) == 0) { + nBound += 1; + } else { + LogPrintf("Binding RPC on address %s port %i failed.\n", i->first, i->second); + } + } + return nBound > 0; +} + +/** Simple wrapper to set thread name and run work queue */ +static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue) +{ + RenameThread("bitcoin-httpworker"); + queue->Run(); +} + +bool InitHTTPServer() +{ + struct evhttp* http = 0; + struct event_base* base = 0; + + if (!InitHTTPAllowList()) + return false; + + if (GetBoolArg("-rpcssl", false)) { + uiInterface.ThreadSafeMessageBox( + "SSL mode for RPC (-rpcssl) is no longer supported.", + "", CClientUIInterface::MSG_ERROR); + return false; + } + +#ifdef WIN32 + evthread_use_windows_threads(); +#else + evthread_use_pthreads(); +#endif + + base = event_base_new(); // XXX RAII + if (!base) { + LogPrintf("Couldn't create an event_base: exiting\n"); + return false; + } + + /* Create a new evhttp object to handle requests. */ + http = evhttp_new(base); // XXX RAII + if (!http) { + LogPrintf("couldn't create evhttp. Exiting.\n"); + event_base_free(base); + return false; + } + + evhttp_set_timeout(http, GetArg("-rpctimeout", DEFAULT_HTTP_TIMEOUT)); + evhttp_set_max_body_size(http, MAX_SIZE); + evhttp_set_gencb(http, http_request_cb, NULL); + + if (!HTTPBindAddresses(http)) { + LogPrintf("Unable to bind any endpoint for RPC server\n"); + evhttp_free(http); + event_base_free(base); + return false; + } + + LogPrint("http", "Initialized HTTP server\n"); + int workQueueDepth = std::max((long)GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); + LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth); + + workQueue = new WorkQueue<HTTPClosure>(workQueueDepth); + eventBase = base; + eventHTTP = http; + return true; +} + +bool StartHTTPServer(boost::thread_group& threadGroup) +{ + LogPrint("http", "Starting HTTP server\n"); + int rpcThreads = std::max((long)GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); + LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); + threadGroup.create_thread(boost::bind(&ThreadHTTP, eventBase, eventHTTP)); + + for (int i = 0; i < rpcThreads; i++) + threadGroup.create_thread(boost::bind(&HTTPWorkQueueRun, workQueue)); + return true; +} + +void InterruptHTTPServer() +{ + LogPrint("http", "Interrupting HTTP server\n"); + if (eventBase) + event_base_loopbreak(eventBase); + if (workQueue) + workQueue->Interrupt(); +} + +void StopHTTPServer() +{ + LogPrint("http", "Stopping HTTP server\n"); + delete workQueue; + if (eventHTTP) { + evhttp_free(eventHTTP); + eventHTTP = 0; + } + if (eventBase) { + event_base_free(eventBase); + eventBase = 0; + } +} + +struct event_base* EventBase() +{ + return eventBase; +} + +static void httpevent_callback_fn(evutil_socket_t, short, void* data) +{ + // Static handler: simply call inner handler + HTTPEvent *self = ((HTTPEvent*)data); + self->handler(); + if (self->deleteWhenTriggered) + delete self; +} + +HTTPEvent::HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const boost::function<void(void)>& handler): + deleteWhenTriggered(deleteWhenTriggered), handler(handler) +{ + ev = event_new(base, -1, 0, httpevent_callback_fn, this); + assert(ev); +} +HTTPEvent::~HTTPEvent() +{ + event_free(ev); +} +void HTTPEvent::trigger(struct timeval* tv) +{ + if (tv == NULL) + event_active(ev, 0, 0); // immediately trigger event in main thread + else + evtimer_add(ev, tv); // trigger after timeval passed +} +HTTPRequest::HTTPRequest(struct evhttp_request* req) : req(req), + replySent(false) +{ +} +HTTPRequest::~HTTPRequest() +{ + if (!replySent) { + // Keep track of whether reply was sent to avoid request leaks + LogPrintf("%s: Unhandled request\n", __func__); + WriteReply(HTTP_INTERNAL, "Unhandled request"); + } + // evhttpd cleans up the request, as long as a reply was sent. +} + +std::pair<bool, std::string> HTTPRequest::GetHeader(const std::string& hdr) +{ + const struct evkeyvalq* headers = evhttp_request_get_input_headers(req); + assert(headers); + const char* val = evhttp_find_header(headers, hdr.c_str()); + if (val) + return std::make_pair(true, val); + else + return std::make_pair(false, ""); +} + +std::string HTTPRequest::ReadBody() +{ + struct evbuffer* buf = evhttp_request_get_input_buffer(req); + if (!buf) + return ""; + size_t size = evbuffer_get_length(buf); + /** Trivial implementation: if this is ever a performance bottleneck, + * internal copying can be avoided in multi-segment buffers by using + * evbuffer_peek and an awkward loop. Though in that case, it'd be even + * better to not copy into an intermediate string but use a stream + * abstraction to consume the evbuffer on the fly in the parsing algorithm. + */ + const char* data = (const char*)evbuffer_pullup(buf, size); + if (!data) // returns NULL in case of empty buffer + return ""; + std::string rv(data, size); + evbuffer_drain(buf, size); + return rv; +} + +void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value) +{ + struct evkeyvalq* headers = evhttp_request_get_output_headers(req); + assert(headers); + evhttp_add_header(headers, hdr.c_str(), value.c_str()); +} + +/** Closure sent to main thread to request a reply to be sent to + * a HTTP request. + * Replies must be sent in the main loop in the main http thread, + * this cannot be done from worker threads. + */ +void HTTPRequest::WriteReply(int nStatus, const std::string& strReply) +{ + assert(!replySent && req); + // Send event to main http thread to send reply message + struct evbuffer* evb = evhttp_request_get_output_buffer(req); + assert(evb); + evbuffer_add(evb, strReply.data(), strReply.size()); + HTTPEvent* ev = new HTTPEvent(eventBase, true, + boost::bind(evhttp_send_reply, req, nStatus, (const char*)NULL, (struct evbuffer *)NULL)); + ev->trigger(0); + replySent = true; + req = 0; // transferred back to main thread +} + +CService HTTPRequest::GetPeer() +{ + evhttp_connection* con = evhttp_request_get_connection(req); + CService peer; + if (con) { + // evhttp retains ownership over returned address string + const char* address = ""; + uint16_t port = 0; + evhttp_connection_get_peer(con, (char**)&address, &port); + peer = CService(address, port); + } + return peer; +} + +std::string HTTPRequest::GetURI() +{ + return evhttp_request_get_uri(req); +} + +HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() +{ + switch (evhttp_request_get_command(req)) { + case EVHTTP_REQ_GET: + return GET; + break; + case EVHTTP_REQ_POST: + return POST; + break; + case EVHTTP_REQ_HEAD: + return HEAD; + break; + case EVHTTP_REQ_PUT: + return PUT; + break; + default: + return UNKNOWN; + break; + } +} + +void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler) +{ + LogPrint("http", "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch); + pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler)); +} + +void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) +{ + std::vector<HTTPPathHandler>::iterator i = pathHandlers.begin(); + std::vector<HTTPPathHandler>::iterator iend = pathHandlers.end(); + for (; i != iend; ++i) + if (i->prefix == prefix && i->exactMatch == exactMatch) + break; + if (i != iend) + { + LogPrint("http", "Unregistering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch); + pathHandlers.erase(i); + } +} + diff --git a/src/httpserver.h b/src/httpserver.h new file mode 100644 index 0000000000..459c60c047 --- /dev/null +++ b/src/httpserver.h @@ -0,0 +1,149 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_HTTPSERVER_H +#define BITCOIN_HTTPSERVER_H + +#include <string> +#include <stdint.h> +#include <boost/thread.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/function.hpp> + +static const int DEFAULT_HTTP_THREADS=4; +static const int DEFAULT_HTTP_WORKQUEUE=16; +static const int DEFAULT_HTTP_TIMEOUT=30; + +struct evhttp_request; +struct event_base; +class CService; +class HTTPRequest; + +/** Initialize HTTP server. + * Call this before RegisterHTTPHandler or EventBase(). + */ +bool InitHTTPServer(); +/** Start HTTP server. + * This is separate from InitHTTPServer to give users race-condition-free time + * to register their handlers between InitHTTPServer and StartHTTPServer. + */ +bool StartHTTPServer(boost::thread_group& threadGroup); +/** Interrupt HTTP server threads */ +void InterruptHTTPServer(); +/** Stop HTTP server */ +void StopHTTPServer(); + +/** Handler for requests to a certain HTTP path */ +typedef boost::function<void(HTTPRequest* req, const std::string &)> HTTPRequestHandler; +/** Register handler for prefix. + * If multiple handlers match a prefix, the first-registered one will + * be invoked. + */ +void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler); +/** Unregister handler for prefix */ +void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch); + +/** Return evhttp event base. This can be used by submodules to + * queue timers or custom events. + */ +struct event_base* EventBase(); + +/** In-flight HTTP request. + * Thin C++ wrapper around evhttp_request. + */ +class HTTPRequest +{ +private: + struct evhttp_request* req; + bool replySent; + +public: + HTTPRequest(struct evhttp_request* req); + ~HTTPRequest(); + + enum RequestMethod { + UNKNOWN, + GET, + POST, + HEAD, + PUT + }; + + /** Get requested URI. + */ + std::string GetURI(); + + /** Get CService (address:ip) for the origin of the http request. + */ + CService GetPeer(); + + /** Get request method. + */ + RequestMethod GetRequestMethod(); + + /** + * Get the request header specified by hdr, or an empty string. + * Return an pair (isPresent,string). + */ + std::pair<bool, std::string> GetHeader(const std::string& hdr); + + /** + * Read request body. + * + * @note As this consumes the underlying buffer, call this only once. + * Repeated calls will return an empty string. + */ + std::string ReadBody(); + + /** + * Write output header. + * + * @note call this before calling WriteErrorReply or Reply. + */ + void WriteHeader(const std::string& hdr, const std::string& value); + + /** + * Write HTTP reply. + * nStatus is the HTTP status code to send. + * strReply is the body of the reply. Keep it empty to send a standard message. + * + * @note Can be called only once. As this will give the request back to the + * main thread, do not call any other HTTPRequest methods after calling this. + */ + void WriteReply(int nStatus, const std::string& strReply = ""); +}; + +/** Event handler closure. + */ +class HTTPClosure +{ +public: + virtual void operator()() = 0; + virtual ~HTTPClosure() {} +}; + +/** Event class. This can be used either as an cross-thread trigger or as a timer. + */ +class HTTPEvent +{ +public: + /** Create a new event. + * deleteWhenTriggered deletes this event object after the event is triggered (and the handler called) + * handler is the handler to call when the event is triggered. + */ + HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const boost::function<void(void)>& handler); + ~HTTPEvent(); + + /** Trigger the event. If tv is 0, trigger it immediately. Otherwise trigger it after + * the given time has elapsed. + */ + void trigger(struct timeval* tv); + + bool deleteWhenTriggered; + boost::function<void(void)> handler; +private: + struct event* ev; +}; + +#endif // BITCOIN_HTTPSERVER_H diff --git a/src/init.cpp b/src/init.cpp index ef1c39db25..a12e38ff53 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -16,6 +16,8 @@ #include "checkpoints.h" #include "compat/sanity.h" #include "consensus/validation.h" +#include "httpserver.h" +#include "httprpc.h" #include "key.h" #include "main.h" #include "miner.h" @@ -144,6 +146,15 @@ public: static CCoinsViewDB *pcoinsdbview = NULL; static CCoinsViewErrorCatcher *pcoinscatcher = NULL; +void Interrupt(boost::thread_group& threadGroup) +{ + InterruptHTTPServer(); + InterruptHTTPRPC(); + InterruptRPC(); + InterruptREST(); + threadGroup.interrupt_all(); +} + void Shutdown() { LogPrintf("%s: In progress...\n", __func__); @@ -158,7 +169,11 @@ void Shutdown() /// module was initialized. RenameThread("bitcoin-shutoff"); mempool.AddTransactionsUpdated(1); - StopRPCThreads(); + + StopHTTPRPC(); + StopREST(); + StopRPC(); + StopHTTPServer(); #ifdef ENABLE_WALLET if (pwalletMain) pwalletMain->Flush(false); @@ -293,7 +308,7 @@ std::string HelpMessage(HelpMessageMode mode) #ifndef WIN32 strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "bitcoind.pid")); #endif - strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by pruning (deleting) old blocks. This mode disables wallet support and is incompatible with -txindex. " + strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by pruning (deleting) old blocks. This mode is incompatible with -txindex and -rescan. " "Warning: Reverting this setting requires re-downloading the entire blockchain. " "(default: 0 = disable pruning blocks, >%u = target size in MiB to use for block files)"), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); strUsage += HelpMessageOpt("-reindex", _("Rebuild block chain index from current blk000??.dat files on startup")); @@ -335,7 +350,6 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6")); strUsage += HelpMessageOpt("-whitelist=<netmask>", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") + " " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway")); - strUsage += HelpMessageOpt("-whiteconnections=<n>", strprintf(_("Reserve this many inbound connections for whitelisted peers (default: %d)"), 0)); #ifdef ENABLE_WALLET strUsage += HelpMessageGroup(_("Wallet options:")); @@ -373,7 +387,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", 1)); strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", 0)); } - string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, net, proxy, prune"; // Don't translate these and qt below + string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, mempoolrej, net, proxy, prune, http"; // Don't translate these and qt below if (mode == HMM_BITCOIN_QT) debugCategories += ", qt"; strUsage += HelpMessageOpt("-debug=<category>", strprintf(_("Output debugging information (default: %u, supplying <category> is optional)"), 0) + ". " + @@ -423,14 +437,11 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)"), 8332, 18332)); strUsage += HelpMessageOpt("-rpcallowip=<ip>", _("Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times")); - strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), 4)); - strUsage += HelpMessageOpt("-rpckeepalive", strprintf(_("RPC support for HTTP persistent connections (default: %d)"), 1)); - - strUsage += HelpMessageGroup(_("RPC SSL options: (see the Bitcoin Wiki for SSL setup instructions)")); - strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections")); - strUsage += HelpMessageOpt("-rpcsslcertificatechainfile=<file.cert>", strprintf(_("Server certificate file (default: %s)"), "server.cert")); - strUsage += HelpMessageOpt("-rpcsslprivatekeyfile=<file.pem>", strprintf(_("Server private key (default: %s)"), "server.pem")); - strUsage += HelpMessageOpt("-rpcsslciphers=<ciphers>", strprintf(_("Acceptable ciphers (default: %s)"), "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH")); + strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), DEFAULT_HTTP_THREADS)); + if (showDebug) { + strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE)); + strUsage += HelpMessageOpt("-rpctimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_TIMEOUT)); + } if (mode == HMM_BITCOIN_QT) { @@ -602,6 +613,23 @@ bool InitSanityCheck(void) return true; } +bool AppInitServers(boost::thread_group& threadGroup) +{ + RPCServer::OnStopped(&OnRPCStopped); + RPCServer::OnPreCommand(&OnRPCPreCommand); + if (!InitHTTPServer()) + return false; + if (!StartRPC()) + return false; + if (!StartHTTPRPC()) + return false; + if (GetBoolArg("-rest", false) && !StartREST()) + return false; + if (!StartHTTPServer(threadGroup)) + return false; + return true; +} + /** Initialize bitcoin. * @pre Parameters should be parsed and config file should be read. */ @@ -629,17 +657,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD); PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy"); if (setProcDEPPol != NULL) setProcDEPPol(PROCESS_DEP_ENABLE); - - // Initialize Windows Sockets - WSADATA wsadata; - int ret = WSAStartup(MAKEWORD(2,2), &wsadata); - if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2) - { - return InitError(strprintf("Error: Winsock library failed to start (WSAStartup returned error %d)", ret)); - } #endif -#ifndef WIN32 + if (!SetupNetworking()) + return InitError("Error: Initializing networking failed"); + +#ifndef WIN32 if (GetBoolArg("-sysperms", false)) { #ifdef ENABLE_WALLET if (!GetBoolArg("-disablewallet", false)) @@ -754,25 +777,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1); int nUserMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); nMaxConnections = std::max(nUserMaxConnections, 0); - int nUserWhiteConnections = GetArg("-whiteconnections", 0); - nWhiteConnections = std::max(nUserWhiteConnections, 0); - - if ((mapArgs.count("-whitelist")) || (mapArgs.count("-whitebind"))) { - if (!(mapArgs.count("-maxconnections"))) { - // User is using whitelist feature, - // but did not specify -maxconnections parameter. - // Silently increase the default to compensate, - // so that the whitelist connection reservation feature - // does not inadvertently reduce the default - // inbound connection capacity of the network. - nMaxConnections += nWhiteConnections; - } - } else { - // User not using whitelist feature. - // Silently disable connection reservation, - // for the same reason as above. - nWhiteConnections = 0; - } // Trim requested connection counts, to fit into system limitations nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); @@ -784,13 +788,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (nMaxConnections < nUserMaxConnections) InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections)); - // Connection capacity is prioritized in this order: - // outbound connections (hardcoded to 8), - // then whitelisted connections, - // then non-whitelisted connections get whatever's left (if any). - if ((nWhiteConnections > 0) && (nWhiteConnections >= (nMaxConnections - 8))) - InitWarning(strprintf(_("All non-whitelisted incoming connections will be dropped, because -whiteconnections is %d and -maxconnections is only %d."), nWhiteConnections, nMaxConnections)); - // ********************************************************* Step 3: parameter-to-internal-flags fDebug = !mapMultiArgs["-debug"].empty(); @@ -921,6 +918,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Option to startup with mocktime set (used for regression testing): SetMockTime(GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op + if (GetBoolArg("-peerbloomfilters", true)) + nLocalServices |= NODE_BLOOM; + // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log // Initialize elliptic curve code @@ -968,8 +968,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("Using data directory %s\n", strDataDir); LogPrintf("Using config file %s\n", GetConfigFile().string()); LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD); - if (nWhiteConnections > 0) - LogPrintf("Reserving %i of these connections for whitelisted inbound peers\n", nWhiteConnections); std::ostringstream strErrors; LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads); @@ -990,9 +988,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (fServer) { uiInterface.InitMessage.connect(SetRPCWarmupStatus); - RPCServer::OnStopped(&OnRPCStopped); - RPCServer::OnPreCommand(&OnRPCPreCommand); - StartRPCThreads(); + if (!AppInitServers(threadGroup)) + return InitError(_("Unable to start HTTP server. See debug log for details.")); } int64_t nStart; diff --git a/src/init.h b/src/init.h index dcb2b29360..8cd51b0286 100644 --- a/src/init.h +++ b/src/init.h @@ -20,6 +20,8 @@ extern CWallet* pwalletMain; void StartShutdown(); bool ShutdownRequested(); +/** Interrupt threads */ +void Interrupt(boost::thread_group& threadGroup); void Shutdown(); bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler); diff --git a/src/keystore.cpp b/src/keystore.cpp index 3bae24b7b9..cf49ba83ad 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -6,23 +6,30 @@ #include "keystore.h" #include "key.h" +#include "pubkey.h" #include "util.h" #include <boost/foreach.hpp> -bool CKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const +bool CKeyStore::AddKey(const CKey &key) { + return AddKeyPubKey(key, key.GetPubKey()); +} + +bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { CKey key; - if (!GetKey(address, key)) + if (!GetKey(address, key)) { + WatchKeyMap::const_iterator it = mapWatchKeys.find(address); + if (it != mapWatchKeys.end()) { + vchPubKeyOut = it->second; + return true; + } return false; + } vchPubKeyOut = key.GetPubKey(); return true; } -bool CKeyStore::AddKey(const CKey &key) { - return AddKeyPubKey(key, key.GetPubKey()); -} - bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) { LOCK(cs_KeyStore); @@ -58,10 +65,29 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) return false; } +static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) +{ + //TODO: Use Solver to extract this? + CScript::const_iterator pc = dest.begin(); + opcodetype opcode; + std::vector<unsigned char> vch; + if (!dest.GetOp(pc, opcode, vch) || vch.size() < 33 || vch.size() > 65) + return false; + pubKeyOut = CPubKey(vch); + if (!pubKeyOut.IsFullyValid()) + return false; + if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch)) + return false; + return true; +} + bool CBasicKeyStore::AddWatchOnly(const CScript &dest) { LOCK(cs_KeyStore); setWatchOnly.insert(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) + mapWatchKeys[pubKey.GetID()] = pubKey; return true; } @@ -69,6 +95,9 @@ bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest) { LOCK(cs_KeyStore); setWatchOnly.erase(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) + mapWatchKeys.erase(pubKey.GetID()); return true; } diff --git a/src/keystore.h b/src/keystore.h index 4a4b6d20af..b917bf20b4 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -32,7 +32,7 @@ public: virtual bool HaveKey(const CKeyID &address) const =0; virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0; virtual void GetKeys(std::set<CKeyID> &setAddress) const =0; - virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; + virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const =0; //! Support for BIP 0013 : see https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki virtual bool AddCScript(const CScript& redeemScript) =0; @@ -47,6 +47,7 @@ public: }; typedef std::map<CKeyID, CKey> KeyMap; +typedef std::map<CKeyID, CPubKey> WatchKeyMap; typedef std::map<CScriptID, CScript > ScriptMap; typedef std::set<CScript> WatchOnlySet; @@ -55,11 +56,13 @@ class CBasicKeyStore : public CKeyStore { protected: KeyMap mapKeys; + WatchKeyMap mapWatchKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; public: bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; bool HaveKey(const CKeyID &address) const { bool result; diff --git a/src/leveldbwrapper.cpp b/src/leveldbwrapper.cpp index c353dfa6d9..26cacf95ae 100644 --- a/src/leveldbwrapper.cpp +++ b/src/leveldbwrapper.cpp @@ -58,7 +58,8 @@ CLevelDBWrapper::CLevelDBWrapper(const boost::filesystem::path& path, size_t nCa } else { if (fWipe) { LogPrintf("Wiping LevelDB in %s\n", path.string()); - leveldb::DestroyDB(path.string(), options); + leveldb::Status result = leveldb::DestroyDB(path.string(), options); + HandleError(result); } TryCreateDirectory(path); LogPrintf("Opening LevelDB in %s\n", path.string()); diff --git a/src/limitedmap.h b/src/limitedmap.h index e8ea549653..5456dfc7c4 100644 --- a/src/limitedmap.h +++ b/src/limitedmap.h @@ -27,7 +27,11 @@ protected: size_type nMaxSize; public: - limitedmap(size_type nMaxSizeIn = 0) { nMaxSize = nMaxSizeIn; } + limitedmap(size_type nMaxSizeIn) + { + assert(nMaxSizeIn > 0); + nMaxSize = nMaxSizeIn; + } const_iterator begin() const { return map.begin(); } const_iterator end() const { return map.end(); } size_type size() const { return map.size(); } @@ -38,13 +42,12 @@ public: { std::pair<iterator, bool> ret = map.insert(x); if (ret.second) { - if (nMaxSize && map.size() == nMaxSize) { + if (map.size() > nMaxSize) { map.erase(rmap.begin()->second); rmap.erase(rmap.begin()); } rmap.insert(make_pair(x.second, ret.first)); } - return; } void erase(const key_type& k) { @@ -81,11 +84,11 @@ public: size_type max_size() const { return nMaxSize; } size_type max_size(size_type s) { - if (s) - while (map.size() > s) { - map.erase(rmap.begin()->second); - rmap.erase(rmap.begin()); - } + assert(s > 0); + while (map.size() > s) { + map.erase(rmap.begin()->second); + rmap.erase(rmap.begin()); + } nMaxSize = s; return nMaxSize; } diff --git a/src/main.cpp b/src/main.cpp index 755bec47b8..a880533e65 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -696,30 +696,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) { // Basic checks that don't depend on any context if (tx.vin.empty()) - return state.DoS(10, error("CheckTransaction(): vin empty"), - REJECT_INVALID, "bad-txns-vin-empty"); + return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty"); if (tx.vout.empty()) - return state.DoS(10, error("CheckTransaction(): vout empty"), - REJECT_INVALID, "bad-txns-vout-empty"); + return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty"); // Size limits if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) - return state.DoS(100, error("CheckTransaction(): size limits failed"), - REJECT_INVALID, "bad-txns-oversize"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize"); // Check for negative or overflow output values CAmount nValueOut = 0; BOOST_FOREACH(const CTxOut& txout, tx.vout) { if (txout.nValue < 0) - return state.DoS(100, error("CheckTransaction(): txout.nValue negative"), - REJECT_INVALID, "bad-txns-vout-negative"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative"); if (txout.nValue > MAX_MONEY) - return state.DoS(100, error("CheckTransaction(): txout.nValue too high"), - REJECT_INVALID, "bad-txns-vout-toolarge"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge"); nValueOut += txout.nValue; if (!MoneyRange(nValueOut)) - return state.DoS(100, error("CheckTransaction(): txout total out of range"), - REJECT_INVALID, "bad-txns-txouttotal-toolarge"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); } // Check for duplicate inputs @@ -727,23 +721,20 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) BOOST_FOREACH(const CTxIn& txin, tx.vin) { if (vInOutPoints.count(txin.prevout)) - return state.DoS(100, error("CheckTransaction(): duplicate inputs"), - REJECT_INVALID, "bad-txns-inputs-duplicate"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate"); vInOutPoints.insert(txin.prevout); } if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100) - return state.DoS(100, error("CheckTransaction(): coinbase script size"), - REJECT_INVALID, "bad-cb-length"); + return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); } else { BOOST_FOREACH(const CTxIn& txin, tx.vin) if (txin.prevout.IsNull()) - return state.DoS(10, error("CheckTransaction(): prevout is null"), - REJECT_INVALID, "bad-txns-prevout-null"); + return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null"); } return true; @@ -778,6 +769,14 @@ CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowF return nMinFee; } +/** Convert CValidationState to a human-readable message for logging */ +static std::string FormatStateMessage(const CValidationState &state) +{ + return strprintf("%s%s (code %i)", + state.GetRejectReason(), + state.GetDebugMessage().empty() ? "" : ", "+state.GetDebugMessage(), + state.GetRejectCode()); +} bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, bool* pfMissingInputs, bool fRejectAbsurdFee) @@ -787,31 +786,27 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa *pfMissingInputs = false; if (!CheckTransaction(tx, state)) - return error("AcceptToMemoryPool: CheckTransaction failed"); + return false; // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) - return state.DoS(100, error("AcceptToMemoryPool: coinbase as individual tx"), - REJECT_INVALID, "coinbase"); + return state.DoS(100, false, REJECT_INVALID, "coinbase"); // Rather not work on nonstandard transactions (unless -testnet/-regtest) string reason; if (fRequireStandard && !IsStandardTx(tx, reason)) - return state.DoS(0, - error("AcceptToMemoryPool: nonstandard transaction: %s", reason), - REJECT_NONSTANDARD, reason); + return state.DoS(0, false, REJECT_NONSTANDARD, reason); // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. if (!CheckFinalTx(tx)) - return state.DoS(0, error("AcceptToMemoryPool: non-final"), - REJECT_NONSTANDARD, "non-final"); + return state.DoS(0, false, REJECT_NONSTANDARD, "non-final"); // is it already in the memory pool? uint256 hash = tx.GetHash(); if (pool.exists(hash)) - return false; + return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool"); // Check for conflicts with in-memory transactions { @@ -822,7 +817,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa if (pool.mapNextTx.count(outpoint)) { // Disable replacement feature for now - return false; + return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); } } } @@ -839,7 +834,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // do we already have it? if (view.HaveCoins(hash)) - return false; + return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known"); // do all inputs exist? // Note that this does not check for the presence of actual outputs (see the next check for that), @@ -848,14 +843,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa if (!view.HaveCoins(txin.prevout.hash)) { if (pfMissingInputs) *pfMissingInputs = true; - return false; + return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid() } } // are the actual inputs available? if (!view.HaveInputs(tx)) - return state.Invalid(error("AcceptToMemoryPool: inputs already spent"), - REJECT_DUPLICATE, "bad-txns-inputs-spent"); + return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent"); // Bring the best block into scope view.GetBestBlock(); @@ -868,7 +862,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // Check for non-standard pay-to-script-hash in inputs if (fRequireStandard && !AreInputsStandard(tx, view)) - return error("AcceptToMemoryPool: nonstandard transaction input"); + return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); // Check that the transaction doesn't have an excessive number of // sigops, making it impossible to mine. Since the coinbase transaction @@ -878,10 +872,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa unsigned int nSigOps = GetLegacySigOpCount(tx); nSigOps += GetP2SHSigOpCount(tx, view); if (nSigOps > MAX_STANDARD_TX_SIGOPS) - return state.DoS(0, - error("AcceptToMemoryPool: too many sigops %s, %d > %d", - hash.ToString(), nSigOps, MAX_STANDARD_TX_SIGOPS), - REJECT_NONSTANDARD, "bad-txns-too-many-sigops"); + return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false, + strprintf("%d > %d", nSigOps, MAX_STANDARD_TX_SIGOPS)); CAmount nValueOut = tx.GetValueOut(); CAmount nFees = nValueIn-nValueOut; @@ -893,9 +885,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // Don't accept it if it can't get into a block CAmount txMinFee = GetMinRelayFee(tx, nSize, true); if (fLimitFree && nFees < txMinFee) - return state.DoS(0, error("AcceptToMemoryPool: not enough fees %s, %d < %d", - hash.ToString(), nFees, txMinFee), - REJECT_INSUFFICIENTFEE, "insufficient fee"); + return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient fee", false, + strprintf("%d < %d", nFees, txMinFee)); // Require that free transactions have sufficient priority to be mined in the next block. if (GetBoolArg("-relaypriority", true) && nFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(view.GetPriority(tx, chainActive.Height() + 1))) { @@ -920,24 +911,20 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // -limitfreerelay unit is thousand-bytes-per-minute // At default rate it would take over a month to fill 1GB if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000) - return state.DoS(0, error("AcceptToMemoryPool: free transaction rejected by rate limiter"), - REJECT_INSUFFICIENTFEE, "rate limited free transaction"); + return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "rate limited free transaction"); LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); dFreeCount += nSize; } if (fRejectAbsurdFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000) - return state.Invalid(error("AcceptToMemoryPool: absurdly high fees %s, %d > %d", - hash.ToString(), - nFees, ::minRelayTxFee.GetFee(nSize) * 10000), - REJECT_HIGHFEE, "absurdly-high-fee"); + return state.Invalid(false, + REJECT_HIGHFEE, "absurdly-high-fee", + strprintf("%d > %d", nFees, ::minRelayTxFee.GetFee(nSize) * 10000)); // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true)) - { - return error("AcceptToMemoryPool: ConnectInputs failed %s", hash.ToString()); - } + return false; // Check again against just the consensus-critical mandatory script // verification flags, in case of bugs in the standard flags that cause @@ -950,7 +937,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // can be exploited as a DoS attack. if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true)) { - return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString()); + return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s, %s", + __func__, hash.ToString(), FormatStateMessage(state)); } // Store transaction in memory @@ -1035,7 +1023,7 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock // CBlock and CBlockIndex // -bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart) +bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart) { // Open history file to append CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION); @@ -1241,7 +1229,7 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state if (state.IsInvalid(nDoS)) { std::map<uint256, NodeId>::iterator it = mapBlockSource.find(pindex->GetBlockHash()); if (it != mapBlockSource.end() && State(it->second)) { - assert(state.GetRejectCode() < 0x100); + assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), pindex->GetBlockHash()}; State(it->second)->rejects.push_back(reject); if (nDoS > 0) @@ -1292,7 +1280,7 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach bool CScriptCheck::operator()() { const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, cacheStore), &error)) { - return ::error("CScriptCheck(): %s:%d VerifySignature failed: %s", ptxTo->GetHash().ToString(), nIn, ScriptErrorString(error)); + return false; } return true; } @@ -1310,7 +1298,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins // This doesn't trigger the DoS code on purpose; if it did, it would make it easier // for an attacker to attempt to split the network. if (!inputs.HaveInputs(tx)) - return state.Invalid(error("CheckInputs(): %s inputs unavailable", tx.GetHash().ToString())); + return state.Invalid(false, 0, "", "Inputs unavailable"); CAmount nValueIn = 0; CAmount nFees = 0; @@ -1323,33 +1311,29 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins // If prev is coinbase, check that it's matured if (coins->IsCoinBase()) { if (nSpendHeight - coins->nHeight < COINBASE_MATURITY) - return state.Invalid( - error("CheckInputs(): tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight), - REJECT_INVALID, "bad-txns-premature-spend-of-coinbase"); + return state.Invalid(false, + REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", + strprintf("tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight)); } // Check for negative or overflow input values nValueIn += coins->vout[prevout.n].nValue; if (!MoneyRange(coins->vout[prevout.n].nValue) || !MoneyRange(nValueIn)) - return state.DoS(100, error("CheckInputs(): txin values out of range"), - REJECT_INVALID, "bad-txns-inputvalues-outofrange"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); } if (nValueIn < tx.GetValueOut()) - return state.DoS(100, error("CheckInputs(): %s value in (%s) < value out (%s)", - tx.GetHash().ToString(), FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())), - REJECT_INVALID, "bad-txns-in-belowout"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, + strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(tx.GetValueOut()))); // Tally transaction fees CAmount nTxFee = nValueIn - tx.GetValueOut(); if (nTxFee < 0) - return state.DoS(100, error("CheckInputs(): %s nTxFee < 0", tx.GetHash().ToString()), - REJECT_INVALID, "bad-txns-fee-negative"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-negative"); nFees += nTxFee; if (!MoneyRange(nFees)) - return state.DoS(100, error("CheckInputs(): nFees out of range"), - REJECT_INVALID, "bad-txns-fee-outofrange"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); return true; } }// namespace Consensus @@ -1473,7 +1457,7 @@ bool AbortNode(const std::string& strMessage, const std::string& userMessage="") strMiscWarning = strMessage; LogPrintf("*** %s\n", strMessage); uiInterface.ThreadSafeMessageBox( - userMessage.empty() ? _("Error: A fatal internal error occured, see debug.log for details") : userMessage, + userMessage.empty() ? _("Error: A fatal internal error occurred, see debug.log for details") : userMessage, "", CClientUIInterface::MSG_ERROR); StartShutdown(); return false; @@ -1520,7 +1504,7 @@ static bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const CO return fClean; } -bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean) +bool DisconnectBlock(const CBlock& block, CValidationState& state, const CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean) { assert(pindex->GetBlockHash() == view.GetBestBlock()); @@ -1795,7 +1779,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin std::vector<CScriptCheck> vChecks; if (!CheckInputs(tx, state, view, fScriptChecks, flags, false, nScriptCheckThreads ? &vChecks : NULL)) - return false; + return error("ConnectBlock(): CheckInputs on %s failed with %s", + tx.GetHash().ToString(), FormatStateMessage(state)); control.Add(vChecks); } @@ -2077,7 +2062,7 @@ static int64_t nTimePostConnect = 0; * Connect a new block to chainActive. pblock is either NULL or a pointer to a CBlock * corresponding to pindexNew, to bypass loading it again from disk. */ -bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *pblock) { +bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, const CBlock *pblock) { assert(pindexNew->pprev == chainActive.Tip()); mempool.check(pcoinsTip); // Read block from disk. @@ -2209,7 +2194,7 @@ static void PruneBlockIndexCandidates() { * Try to make some progress towards making pindexMostWork the active block. * pblock is either NULL or a pointer to a CBlock corresponding to pindexMostWork. */ -static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMostWork, CBlock *pblock) { +static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMostWork, const CBlock *pblock) { AssertLockHeld(cs_main); bool fInvalidFound = false; const CBlockIndex *pindexOldTip = chainActive.Tip(); @@ -2278,7 +2263,7 @@ static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMo * or an activated best chain. pblock is either NULL or a pointer to a block * that is already loaded (to avoid loading it again from disk). */ -bool ActivateBestChain(CValidationState &state, CBlock *pblock) { +bool ActivateBestChain(CValidationState &state, const CBlock *pblock) { CBlockIndex *pindexNewTip = NULL; CBlockIndex *pindexMostWork = NULL; const CChainParams& chainParams = Params(); @@ -2622,7 +2607,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo // Check transactions BOOST_FOREACH(const CTransaction& tx, block.vtx) if (!CheckTransaction(tx, state)) - return error("CheckBlock(): CheckTransaction failed"); + return error("CheckBlock(): CheckTransaction of %s failed with %s", + tx.GetHash().ToString(), + FormatStateMessage(state)); unsigned int nSigOps = 0; BOOST_FOREACH(const CTransaction& tx, block.vtx) @@ -2749,7 +2736,7 @@ bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBloc return true; } -bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, bool fRequested, CDiskBlockPos* dbp) +bool AcceptBlock(const CBlock& block, CValidationState& state, CBlockIndex** ppindex, bool fRequested, CDiskBlockPos* dbp) { const CChainParams& chainparams = Params(); AssertLockHeld(cs_main); @@ -2826,7 +2813,7 @@ static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned } -bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp) +bool ProcessNewBlock(CValidationState &state, const CNode* pfrom, const CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp) { // Preliminary checks bool checked = CheckBlock(*pblock, state); @@ -4357,11 +4344,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, int nDoS = 0; if (state.IsInvalid(nDoS)) { - LogPrint("mempool", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(), + LogPrint("mempoolrej", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(), pfrom->id, pfrom->cleanSubVer, - state.GetRejectReason()); - pfrom->PushMessage("reject", strCommand, state.GetRejectCode(), - state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash); + FormatStateMessage(state)); + if (state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P + pfrom->PushMessage("reject", strCommand, state.GetRejectCode(), + state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash); if (nDoS > 0) Misbehaving(pfrom->GetId(), nDoS); } @@ -4441,6 +4429,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, ProcessNewBlock(state, pfrom, &block, forceProcessing, NULL); int nDoS; if (state.IsInvalid(nDoS)) { + assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes pfrom->PushMessage("reject", strCommand, state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash); if (nDoS > 0) { @@ -4533,6 +4522,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, if (pingUsecTime > 0) { // Successful ping time measurement, replace previous pfrom->nPingUsecTime = pingUsecTime; + pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime, pingUsecTime); } else { // This should never happen sProblem = "Timing mishap"; @@ -4601,6 +4591,21 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } + else if (!(nLocalServices & NODE_BLOOM) && + (strCommand == "filterload" || + strCommand == "filteradd" || + strCommand == "filterclear") && + //TODO: Remove this line after reasonable network upgrade + pfrom->nVersion >= NO_BLOOM_VERSION) + { + if (pfrom->nVersion >= NO_BLOOM_VERSION) + Misbehaving(pfrom->GetId(), 100); + //TODO: Enable this after reasonable network upgrade + //else + // pfrom->fDisconnect = true; + } + + else if (strCommand == "filterload") { CBloomFilter filter; diff --git a/src/main.h b/src/main.h index eec7e6fa59..e3479b4b3b 100644 --- a/src/main.h +++ b/src/main.h @@ -142,7 +142,7 @@ void UnregisterNodeSignals(CNodeSignals& nodeSignals); * @param[out] dbp If pblock is stored to disk (or already there), this will be set to its location. * @return True if state.IsValid() */ -bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp); +bool ProcessNewBlock(CValidationState &state, const CNode* pfrom, const CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp); /** Check whether enough disk space is available for an incoming block */ bool CheckDiskSpace(uint64_t nAdditionalBytes = 0); /** Open a block file (blk?????.dat) */ @@ -179,7 +179,7 @@ std::string GetWarnings(const std::string& strFor); /** Retrieve a transaction (from memory pool, or from disk, if possible) */ bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock, bool fAllowSlow = false); /** Find the best known block, and make it the tip of the block chain */ -bool ActivateBestChain(CValidationState &state, CBlock *pblock = NULL); +bool ActivateBestChain(CValidationState &state, const CBlock *pblock = NULL); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams); /** @@ -335,7 +335,7 @@ public: /** Functions for disk access for blocks */ -bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart); +bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& messageStart); bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos); bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex); @@ -346,7 +346,7 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex); * In case pfClean is provided, operation will try to be tolerant about errors, and *pfClean * will be true if no problems were found. Otherwise, the return value will be false in case * of problems. Note that in any case, coins may be modified. */ -bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool* pfClean = NULL); +bool DisconnectBlock(const CBlock& block, CValidationState& state, const CBlockIndex* pindex, CCoinsViewCache& coins, bool* pfClean = NULL); /** Apply the effects of this block (with given index) on the UTXO set represented by coins */ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool fJustCheck = false); @@ -363,7 +363,7 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn bool TestBlockValidity(CValidationState &state, const CBlock& block, CBlockIndex *pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true); /** Store block on disk. If dbp is non-NULL, the file is known to already reside on disk */ -bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex **pindex, bool fRequested, CDiskBlockPos* dbp); +bool AcceptBlock(const CBlock& block, CValidationState& state, CBlockIndex **pindex, bool fRequested, CDiskBlockPos* dbp); bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBlockIndex **ppindex= NULL); @@ -455,7 +455,16 @@ extern CBlockTreeDB *pblocktree; */ int GetSpendHeight(const CCoinsViewCache& inputs); -/** local "reject" message codes for RPC which can not be triggered by p2p trasactions */ +/** Reject codes greater or equal to this can be returned by AcceptToMemPool + * for transactions, to signal internal conditions. They cannot and should not + * be sent over the P2P network. + */ +static const unsigned int REJECT_INTERNAL = 0x100; +/** Too high fee. Can not be triggered by P2P transactions */ static const unsigned int REJECT_HIGHFEE = 0x100; +/** Transaction is already known (either in mempool or blockchain) */ +static const unsigned int REJECT_ALREADY_KNOWN = 0x101; +/** Transaction conflicts with a transaction already known */ +static const unsigned int REJECT_CONFLICT = 0x102; #endif // BITCOIN_MAIN_H diff --git a/src/merkleblock.cpp b/src/merkleblock.cpp index 4d90fd8cd7..f8e877df25 100644 --- a/src/merkleblock.cpp +++ b/src/merkleblock.cpp @@ -168,7 +168,7 @@ uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch) { // traverse the partial tree unsigned int nBitsUsed = 0, nHashUsed = 0; uint256 hashMerkleRoot = TraverseAndExtract(nHeight, 0, nBitsUsed, nHashUsed, vMatch); - // verify that no problems occured during the tree traversal + // verify that no problems occurred during the tree traversal if (fBad) return uint256(); // verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence) diff --git a/src/miner.cpp b/src/miner.cpp index 864e4bf31f..9dd1d459b5 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -352,7 +352,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) return pblocktemplate.release(); } -void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce) +void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce) { // Update nExtraNonce static uint256 hashPrevBlock; @@ -409,7 +409,7 @@ bool static ScanHash(const CBlockHeader *pblock, uint32_t& nNonce, uint256 *phas } } -static bool ProcessBlockFound(CBlock* pblock, const CChainParams& chainparams) +static bool ProcessBlockFound(const CBlock* pblock, const CChainParams& chainparams) { LogPrintf("%s\n", pblock->ToString()); LogPrintf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue)); @@ -444,8 +444,10 @@ void static BitcoinMiner(const CChainParams& chainparams) GetMainSignals().ScriptForMining(coinbaseScript); try { - //throw an error if no script was provided - if (!coinbaseScript->reserveScript.size()) + // Throw an error if no script was provided. This can happen + // due to some internal error but also if the keypool is empty. + // In the latter case, already the pointer is NULL. + if (!coinbaseScript || coinbaseScript->reserveScript.empty()) throw std::runtime_error("No coinbase script available (mining requires a wallet)"); while (true) { diff --git a/src/miner.h b/src/miner.h index d690e9447d..7e0e58d540 100644 --- a/src/miner.h +++ b/src/miner.h @@ -29,7 +29,7 @@ void GenerateBitcoins(bool fGenerate, int nThreads, const CChainParams& chainpar /** Generate a new block, without valid proof-of-work */ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn); /** Modify the extranonce in a block */ -void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce); +void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce); int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); #endif // BITCOIN_MINER_H diff --git a/src/net.cpp b/src/net.cpp index e4ead3c92e..87c4f0af0a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -81,7 +81,6 @@ uint64_t nLocalHostNonce = 0; static std::vector<ListenSocket> vhListenSocket; CAddrMan addrman; int nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; -int nWhiteConnections = 0; bool fAddressesInitialized = false; std::string strSubVersion; @@ -629,6 +628,7 @@ void CNode::copyStats(CNodeStats &stats) // Raw ping time is in microseconds, but show it to user as whole seconds (Bitcoin users should be well used to small numbers with many decimal places by now :) stats.dPingTime = (((double)nPingUsecTime) / 1e6); + stats.dPingMin = (((double)nMinPingUsecTime) / 1e6); stats.dPingWait = (((double)nPingUsecWait) / 1e6); // Leave string empty if addrLocal invalid (not filled in yet) @@ -659,7 +659,7 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes) return false; if (msg.in_data && msg.hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) { - LogPrint("net", "Oversized message from peer=%i, disconnecting", GetId()); + LogPrint("net", "Oversized message from peer=%i, disconnecting\n", GetId()); return false; } @@ -776,6 +776,222 @@ void SocketSendData(CNode *pnode) static list<CNode*> vNodesDisconnected; +class CNodeRef { +public: + CNodeRef(CNode *pnode) : _pnode(pnode) { + LOCK(cs_vNodes); + _pnode->AddRef(); + } + + ~CNodeRef() { + LOCK(cs_vNodes); + _pnode->Release(); + } + + CNode& operator *() const {return *_pnode;}; + CNode* operator ->() const {return _pnode;}; + + CNodeRef& operator =(const CNodeRef& other) + { + if (this != &other) { + LOCK(cs_vNodes); + + _pnode->Release(); + _pnode = other._pnode; + _pnode->AddRef(); + } + return *this; + } + + CNodeRef(const CNodeRef& other): + _pnode(other._pnode) + { + LOCK(cs_vNodes); + _pnode->AddRef(); + } +private: + CNode *_pnode; +}; + +static bool ReverseCompareNodeMinPingTime(const CNodeRef &a, const CNodeRef &b) +{ + return a->nMinPingUsecTime > b->nMinPingUsecTime; +} + +static bool ReverseCompareNodeTimeConnected(const CNodeRef &a, const CNodeRef &b) +{ + return a->nTimeConnected > b->nTimeConnected; +} + +class CompareNetGroupKeyed +{ + std::vector<unsigned char> vchSecretKey; +public: + CompareNetGroupKeyed() + { + vchSecretKey.resize(32, 0); + GetRandBytes(vchSecretKey.data(), vchSecretKey.size()); + } + + bool operator()(const CNodeRef &a, const CNodeRef &b) + { + std::vector<unsigned char> vchGroupA, vchGroupB; + CSHA256 hashA, hashB; + std::vector<unsigned char> vchA(32), vchB(32); + + vchGroupA = a->addr.GetGroup(); + vchGroupB = b->addr.GetGroup(); + + hashA.Write(begin_ptr(vchGroupA), vchGroupA.size()); + hashB.Write(begin_ptr(vchGroupB), vchGroupB.size()); + + hashA.Write(begin_ptr(vchSecretKey), vchSecretKey.size()); + hashB.Write(begin_ptr(vchSecretKey), vchSecretKey.size()); + + hashA.Finalize(begin_ptr(vchA)); + hashB.Finalize(begin_ptr(vchB)); + + return vchA < vchB; + } +}; + +static bool AttemptToEvictConnection(bool fPreferNewConnection) { + std::vector<CNodeRef> vEvictionCandidates; + { + LOCK(cs_vNodes); + + BOOST_FOREACH(CNode *node, vNodes) { + if (node->fWhitelisted) + continue; + if (!node->fInbound) + continue; + if (node->fDisconnect) + continue; + if (node->addr.IsLocal()) + continue; + vEvictionCandidates.push_back(CNodeRef(node)); + } + } + + if (vEvictionCandidates.empty()) return false; + + // Protect connections with certain characteristics + + // Deterministically select 4 peers to protect by netgroup. + // An attacker cannot predict which netgroups will be protected. + static CompareNetGroupKeyed comparerNetGroupKeyed; + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), comparerNetGroupKeyed); + vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end()); + + if (vEvictionCandidates.empty()) return false; + + // Protect the 8 nodes with the best ping times. + // An attacker cannot manipulate this metric without physically moving nodes closer to the target. + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeMinPingTime); + vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(8, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end()); + + if (vEvictionCandidates.empty()) return false; + + // Protect the half of the remaining nodes which have been connected the longest. + // This replicates the existing implicit behavior. + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected); + vEvictionCandidates.erase(vEvictionCandidates.end() - static_cast<int>(vEvictionCandidates.size() / 2), vEvictionCandidates.end()); + + if (vEvictionCandidates.empty()) return false; + + // Identify the network group with the most connections + std::vector<unsigned char> naMostConnections; + unsigned int nMostConnections = 0; + std::map<std::vector<unsigned char>, std::vector<CNodeRef> > mapAddrCounts; + BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) { + mapAddrCounts[node->addr.GetGroup()].push_back(node); + + if (mapAddrCounts[node->addr.GetGroup()].size() > nMostConnections) { + nMostConnections = mapAddrCounts[node->addr.GetGroup()].size(); + naMostConnections = node->addr.GetGroup(); + } + } + + // Reduce to the network group with the most connections + vEvictionCandidates = mapAddrCounts[naMostConnections]; + + // Do not disconnect peers if there is only 1 connection from their network group + if (vEvictionCandidates.size() <= 1) + // unless we prefer the new connection (for whitelisted peers) + if (!fPreferNewConnection) + return false; + + // Disconnect the most recent connection from the network group with the most connections + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected); + vEvictionCandidates[0]->fDisconnect = true; + + return true; +} + +static void AcceptConnection(const ListenSocket& hListenSocket) { + struct sockaddr_storage sockaddr; + socklen_t len = sizeof(sockaddr); + SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); + CAddress addr; + int nInbound = 0; + int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS; + + if (hSocket != INVALID_SOCKET) + if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) + LogPrintf("Warning: Unknown socket family\n"); + + bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr); + { + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if (pnode->fInbound) + nInbound++; + } + + if (hSocket == INVALID_SOCKET) + { + int nErr = WSAGetLastError(); + if (nErr != WSAEWOULDBLOCK) + LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); + return; + } + + if (!IsSelectableSocket(hSocket)) + { + LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); + CloseSocket(hSocket); + return; + } + + if (CNode::IsBanned(addr) && !whitelisted) + { + LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); + CloseSocket(hSocket); + return; + } + + if (nInbound >= nMaxInbound) + { + if (!AttemptToEvictConnection(whitelisted)) { + // No connection to evict, disconnect the new connection + LogPrint("net", "failed to find an eviction candidate - connection dropped (full)\n"); + CloseSocket(hSocket); + return; + } + } + + CNode* pnode = new CNode(hSocket, addr, "", true); + pnode->AddRef(); + pnode->fWhitelisted = whitelisted; + + LogPrint("net", "connection from %s accepted\n", addr.ToString()); + + { + LOCK(cs_vNodes); + vNodes.push_back(pnode); + } +} + void ThreadSocketHandler() { unsigned int nPrevNodeCount = 0; @@ -933,64 +1149,7 @@ void ThreadSocketHandler() { if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv)) { - struct sockaddr_storage sockaddr; - socklen_t len = sizeof(sockaddr); - SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); - CAddress addr; - int nInbound = 0; - int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS; - - if (hSocket != INVALID_SOCKET) - if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) - LogPrintf("Warning: Unknown socket family\n"); - - bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr); - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if (pnode->fInbound) - nInbound++; - } - - if (hSocket == INVALID_SOCKET) - { - int nErr = WSAGetLastError(); - if (nErr != WSAEWOULDBLOCK) - LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); - } - else if (!IsSelectableSocket(hSocket)) - { - LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); - CloseSocket(hSocket); - } - else if (nInbound >= nMaxInbound) - { - LogPrint("net", "connection from %s dropped (full)\n", addr.ToString()); - CloseSocket(hSocket); - } - else if (!whitelisted && (nInbound >= (nMaxInbound - nWhiteConnections))) - { - LogPrint("net", "connection from %s dropped (non-whitelisted)\n", addr.ToString()); - CloseSocket(hSocket); - } - else if (CNode::IsBanned(addr) && !whitelisted) - { - LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); - CloseSocket(hSocket); - } - else - { - CNode* pnode = new CNode(hSocket, addr, "", true); - pnode->AddRef(); - pnode->fWhitelisted = whitelisted; - - LogPrint("net", "connection from %s accepted\n", addr.ToString()); - - { - LOCK(cs_vNodes); - vNodes.push_back(pnode); - } - } + AcceptConnection(hListenSocket); } } @@ -1120,10 +1279,14 @@ void ThreadMapPort() #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0); -#else +#elif MINIUPNPC_API_VERSION < 14 /* miniupnpc 1.6 */ int error = 0; devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); +#else + /* miniupnpc 1.9.20150730 */ + int error = 0; + devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); #endif struct UPNPUrls urls; @@ -1625,8 +1788,10 @@ bool BindListenPort(const CService &addrBind, string& strError, bool fWhiteliste setsockopt(hListenSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&nOne, sizeof(int)); #endif // Allow binding if the port is still in TIME_WAIT state after - // the program was closed and restarted. Not an issue on windows! + // the program was closed and restarted. setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (void*)&nOne, sizeof(int)); +#else + setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&nOne, sizeof(int)); #endif // Set to non-blocking, incoming connections will also inherit this @@ -2096,6 +2261,7 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa nPingUsecStart = 0; nPingUsecTime = 0; fPingQueued = false; + nMinPingUsecTime = std::numeric_limits<int64_t>::max(); { LOCK(cs_nLastNodeId); @@ -2319,7 +2485,7 @@ void DumpBanlist() { int64_t nStart = GetTimeMillis(); - CNode::SweepBanned(); //clean unused entires (if bantime has expired) + CNode::SweepBanned(); //clean unused entries (if bantime has expired) CBanDB bandb; banmap_t banmap; @@ -143,19 +143,8 @@ extern uint64_t nLocalServices; extern uint64_t nLocalHostNonce; extern CAddrMan addrman; -// The allocation of connections against the maximum allowed (nMaxConnections) -// is prioritized as follows: -// 1st: Outbound connections (MAX_OUTBOUND_CONNECTIONS) -// 2nd: Inbound connections from whitelisted peers (nWhiteConnections) -// 3rd: Inbound connections from non-whitelisted peers -// Thus, the number of connection slots for the general public to use is: -// nMaxConnections - (MAX_OUTBOUND_CONNECTIONS + nWhiteConnections) -// Any additional inbound connections beyond limits will be immediately closed - /** Maximum number of connections to simultaneously allow (aka connection slots) */ extern int nMaxConnections; -/** Number of connection slots to reserve for inbound from whitelisted peers */ -extern int nWhiteConnections; extern std::vector<CNode*> vNodes; extern CCriticalSection cs_vNodes; @@ -200,6 +189,7 @@ public: bool fWhitelisted; double dPingTime; double dPingWait; + double dPingMin; std::string addrLocal; }; @@ -395,6 +385,8 @@ public: int64_t nPingUsecStart; // Last measured round-trip time. int64_t nPingUsecTime; + // Best measured round-trip time. + int64_t nMinPingUsecTime; // Whether a ping is requested. bool fPingQueued; @@ -695,7 +687,7 @@ public: static bool BannedSetIsDirty(); //!set the "dirty" flag for the banlist static void SetBannedSetDirty(bool dirty=true); - //!clean unused entires (if bantime has expired) + //!clean unused entries (if bantime has expired) static void SweepBanned(); void copyStats(CNodeStats &stats); diff --git a/src/netbase.cpp b/src/netbase.cpp index b7e2e57917..7a87d125c2 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -349,7 +349,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials } if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { CloseSocket(hSocket); - return error("Proxy authentication unsuccesful"); + return error("Proxy authentication unsuccessful"); } } else if (pchRet1[1] == 0x00) { // Perform no authentication @@ -983,7 +983,7 @@ std::vector<unsigned char> CNetAddr::GetGroup() const nBits -= 8; } if (nBits > 0) - vchRet.push_back(GetByte(15 - nStartByte) | ((1 << nBits) - 1)); + vchRet.push_back(GetByte(15 - nStartByte) | ((1 << (8 - nBits)) - 1)); return vchRet; } diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index cdee541d2f..ffe31d1942 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -261,7 +261,7 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe blocksAgo = 0; if (blocksAgo < 0) { LogPrint("estimatefee", "Blockpolicy error, blocks ago is negative for mempool tx\n"); - return; //This can't happen becasue we call this with our best seen height, no entries can have higher + return; //This can't happen because we call this with our best seen height, no entries can have higher } if (blocksAgo >= (int)unconfTxs.size()) { diff --git a/src/policy/fees.h b/src/policy/fees.h index ce4d782566..15577d128a 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -118,7 +118,7 @@ public: /** * Initialize the data structures. This is called by BlockPolicyEstimator's * constructor with default values. - * @param defaultBuckets contains the upper limits for the bucket boundries + * @param defaultBuckets contains the upper limits for the bucket boundaries * @param maxConfirms max number of confirms to track * @param decay how much to decay the historical moving average per block * @param dataTypeString for logging purposes diff --git a/src/protocol.h b/src/protocol.h index b5e65032a2..50aeaf44ba 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -75,6 +75,10 @@ enum { // Bitcoin Core does not support this but a patch set called Bitcoin XT does. // See BIP 64 for details on how this is implemented. NODE_GETUTXO = (1 << 1), + // NODE_BLOOM means the node is capable and willing to handle bloom-filtered connections. + // Bitcoin Core nodes used to support this by default, without advertising this bit, + // but no longer do as of protocol version 70011 (= NO_BLOOM_VERSION) + NODE_BLOOM = (1 << 2), // Bits 24-31 are reserved for temporary experiments. Just pick a bit that // isn't getting used, or one not being used much, and notify the diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 1da2d3e344..ea7f86d18e 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -266,13 +266,6 @@ void BitcoinCore::initialize() { qDebug() << __func__ << ": Running AppInit2 in thread"; int rv = AppInit2(threadGroup, scheduler); - if(rv) - { - /* Start a dummy RPC thread if no RPC thread is active yet - * to handle timeouts. - */ - StartDummyRPCThread(); - } Q_EMIT initializeResult(rv); } catch (const std::exception& e) { handleRunawayException(&e); @@ -286,7 +279,7 @@ void BitcoinCore::shutdown() try { qDebug() << __func__ << ": Running Shutdown in thread"; - threadGroup.interrupt_all(); + Interrupt(threadGroup); threadGroup.join_all(); Shutdown(); qDebug() << __func__ << ": Shutdown finished"; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 1d3f7762ab..db9e558764 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -97,6 +97,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n trayIconMenu(0), notificator(0), rpcConsole(0), + helpMessageDialog(0), prevBlocks(0), spinnerFrame(0), platformStyle(platformStyle) @@ -132,6 +133,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n #endif rpcConsole = new RPCConsole(platformStyle, 0); + helpMessageDialog = new HelpMessageDialog(this, false); #ifdef ENABLE_WALLET if(enableWallet) { @@ -590,9 +592,7 @@ void BitcoinGUI::aboutClicked() void BitcoinGUI::showHelpMessageClicked() { - HelpMessageDialog *help = new HelpMessageDialog(this, false); - help->setAttribute(Qt::WA_DeleteOnClose); - help->show(); + helpMessageDialog->show(); } #ifdef ENABLE_WALLET diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index dd0d4bb0e2..f1b7a502ba 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -28,6 +28,7 @@ class SendCoinsRecipient; class UnitDisplayStatusBarControl; class WalletFrame; class WalletModel; +class HelpMessageDialog; class CWallet; @@ -113,6 +114,7 @@ private: QMenu *trayIconMenu; Notificator *notificator; RPCConsole *rpcConsole; + HelpMessageDialog *helpMessageDialog; /** Keep track of previous number of blocks, to detect progress */ int prevBlocks; diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index d966036019..31a6d65a8d 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -756,7 +756,7 @@ void PaymentServer::setOptionsModel(OptionsModel *optionsModel) void PaymentServer::handlePaymentACK(const QString& paymentACKMsg) { - // currently we don't futher process or store the paymentACK message + // currently we don't further process or store the paymentACK message Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL); } diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 35729bbb8b..b742a47c9b 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -28,6 +28,7 @@ #include <QScrollBar> #include <QThread> #include <QTime> +#include <QTimer> #if QT_VERSION < 0x050000 #include <QUrl> @@ -66,6 +67,40 @@ Q_SIGNALS: void reply(int category, const QString &command); }; +/** Class for handling RPC timers + * (used for e.g. re-locking the wallet after a timeout) + */ +class QtRPCTimerBase: public QObject, public RPCTimerBase +{ + Q_OBJECT +public: + QtRPCTimerBase(boost::function<void(void)>& func, int64_t millis): + func(func) + { + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), this, SLOT(timeout())); + timer.start(millis); + } + ~QtRPCTimerBase() {} +private Q_SLOTS: + void timeout() { func(); } +private: + QTimer timer; + boost::function<void(void)> func; +}; + +class QtRPCTimerInterface: public RPCTimerInterface +{ +public: + ~QtRPCTimerInterface() {} + const char *Name() { return "Qt"; } + RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) + { + return new QtRPCTimerBase(func, millis); + } +}; + + #include "rpcconsole.moc" /** @@ -232,6 +267,9 @@ RPCConsole::RPCConsole(const PlatformStyle *platformStyle, QWidget *parent) : ui->label_berkeleyDBVersion->hide(); ui->berkeleyDBVersion->hide(); #endif + // Register RPC timer interface + rpcTimerInterface = new QtRPCTimerInterface(); + RPCRegisterTimerInterface(rpcTimerInterface); startExecutor(); setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); @@ -246,6 +284,8 @@ RPCConsole::~RPCConsole() { GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this); Q_EMIT stopExecutor(); + RPCUnregisterTimerInterface(rpcTimerInterface); + delete rpcTimerInterface; delete ui; } @@ -570,7 +610,7 @@ void RPCConsole::peerLayoutChanged() if (detailNodeRow < 0) { - // detail node dissapeared from table (node disconnected) + // detail node disappeared from table (node disconnected) fUnselect = true; } else diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index b94efee84a..1409fca525 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -14,6 +14,7 @@ class ClientModel; class PlatformStyle; +class RPCTimerInterface; namespace Ui { class RPCConsole; @@ -108,6 +109,7 @@ private: NodeId cachedNodeid; QMenu *contextMenu; const PlatformStyle *platformStyle; + RPCTimerInterface *rpcTimerInterface; }; #endif // BITCOIN_QT_RPCCONSOLE_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 449a7bc5e8..60a3fc128e 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -754,10 +754,9 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text) } else // Valid address { - CPubKey pubkey; CKeyID keyid; addr.GetKeyID(keyid); - if (!model->getPubKey(keyid, pubkey)) // Unknown change address + if (!model->havePrivKey(keyid)) // Unknown change address { ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address")); } diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 8430e017c1..c15b64c327 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -57,7 +57,7 @@ SplashScreen::SplashScreen(Qt::WindowFlags f, const NetworkStyle *networkStyle) QPainter pixPaint(&pixmap); pixPaint.setPen(QColor(100,100,100)); - // draw a slighly radial gradient + // draw a slightly radial gradient QRadialGradient gradient(QPoint(0,0), splashSize.width()/devicePixelRatio); gradient.setColorAt(0, Qt::white); gradient.setColorAt(1, QColor(247,247,247)); diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index bb768f1325..f91de2008c 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -17,6 +17,8 @@ #include <QObject> #include <QTest> +#include <openssl/ssl.h> + #if defined(QT_STATICPLUGIN) && QT_VERSION < 0x050000 #include <QtPlugin> Q_IMPORT_PLUGIN(qcncodecs) @@ -36,6 +38,8 @@ int main(int argc, char *argv[]) QCoreApplication app(argc, argv); app.setApplicationName("Bitcoin-Qt-test"); + SSL_library_init(); + URITests test1; if (QTest::qExec(&test1) != 0) fInvalid = true; diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index af78a51d0f..801c6c62d2 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -165,7 +165,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco if (fAllFromMe) { - if(fAllFromMe == ISMINE_WATCH_ONLY) + if(fAllFromMe & ISMINE_WATCH_ONLY) strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>"; // @@ -190,7 +190,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); if(toSelf == ISMINE_SPENDABLE) strHTML += " (own address)"; - else if(toSelf == ISMINE_WATCH_ONLY) + else if(toSelf & ISMINE_WATCH_ONLY) strHTML += " (watch-only)"; strHTML += "<br>"; } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 15d13e9fc9..d8623daf5d 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -56,7 +56,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * CTxDestination address; sub.idx = parts.size(); // sequence number sub.credit = txout.nValue; - sub.involvesWatchAddress = mine == ISMINE_WATCH_ONLY; + sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) { // Received by Bitcoin Address @@ -86,7 +86,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * BOOST_FOREACH(const CTxIn& txin, wtx.vin) { isminetype mine = wallet->IsMine(txin); - if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true; + if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; if(fAllFromMe > mine) fAllFromMe = mine; } @@ -94,7 +94,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * BOOST_FOREACH(const CTxOut& txout, wtx.vout) { isminetype mine = wallet->IsMine(txout); - if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true; + if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; if(fAllToMe > mine) fAllToMe = mine; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f580853732..5c21db8bdf 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -556,6 +556,11 @@ bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const return wallet->GetPubKey(address, vchPubKeyOut); } +bool WalletModel::havePrivKey(const CKeyID &address) const +{ + return wallet->HaveKey(address); +} + // returns a list of COutputs from COutPoints void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index c29682e4f6..a5e877d81f 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -187,6 +187,7 @@ public: UnlockContext requestUnlock(); bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; + bool havePrivKey(const CKeyID &address) const; void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs); bool isSpent(const COutPoint& outpoint) const; void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index fa96f62e03..77efdb5cdd 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -56,6 +56,9 @@ WalletView::WalletView(const PlatformStyle *platformStyle, QWidget *parent): receiveCoinsPage = new ReceiveCoinsDialog(platformStyle); sendCoinsPage = new SendCoinsDialog(platformStyle); + usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this); + usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this); + addWidget(overviewPage); addWidget(transactionsPage); addWidget(receiveCoinsPage); @@ -115,6 +118,8 @@ void WalletView::setWalletModel(WalletModel *walletModel) overviewPage->setWalletModel(walletModel); receiveCoinsPage->setModel(walletModel); sendCoinsPage->setModel(walletModel); + usedReceivingAddressesPage->setModel(walletModel->getAddressTableModel()); + usedSendingAddressesPage->setModel(walletModel->getAddressTableModel()); if (walletModel) { @@ -273,20 +278,20 @@ void WalletView::usedSendingAddresses() { if(!walletModel) return; - AddressBookPage *dlg = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this); - dlg->setAttribute(Qt::WA_DeleteOnClose); - dlg->setModel(walletModel->getAddressTableModel()); - dlg->show(); + + usedSendingAddressesPage->show(); + usedSendingAddressesPage->raise(); + usedSendingAddressesPage->activateWindow(); } void WalletView::usedReceivingAddresses() { if(!walletModel) return; - AddressBookPage *dlg = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this); - dlg->setAttribute(Qt::WA_DeleteOnClose); - dlg->setModel(walletModel->getAddressTableModel()); - dlg->show(); + + usedReceivingAddressesPage->show(); + usedReceivingAddressesPage->raise(); + usedReceivingAddressesPage->activateWindow(); } void WalletView::showProgress(const QString &title, int nProgress) diff --git a/src/qt/walletview.h b/src/qt/walletview.h index f97cf1ee80..2a6a6a2df2 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -18,6 +18,7 @@ class SendCoinsDialog; class SendCoinsRecipient; class TransactionView; class WalletModel; +class AddressBookPage; QT_BEGIN_NAMESPACE class QModelIndex; @@ -61,6 +62,8 @@ private: QWidget *transactionsPage; ReceiveCoinsDialog *receiveCoinsPage; SendCoinsDialog *sendCoinsPage; + AddressBookPage *usedSendingAddressesPage; + AddressBookPage *usedReceivingAddressesPage; TransactionView *transactionView; diff --git a/src/rest.cpp b/src/rest.cpp index 0dd238b683..226e237fc6 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -7,6 +7,7 @@ #include "primitives/block.h" #include "primitives/transaction.h" #include "main.h" +#include "httpserver.h" #include "rpcserver.h" #include "streams.h" #include "sync.h" @@ -56,35 +57,38 @@ struct CCoin { } }; -class RestErr -{ -public: - enum HTTPStatusCode status; - string message; -}; - extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); extern UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false); +extern UniValue mempoolInfoToJSON(); +extern UniValue mempoolToJSON(bool fVerbose = false); extern void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); extern UniValue blockheaderToJSON(const CBlockIndex* blockindex); -static RestErr RESTERR(enum HTTPStatusCode status, string message) +static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, string message) { - RestErr re; - re.status = status; - re.message = message; - return re; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(status, message + "\r\n"); + return false; } -static enum RetFormat ParseDataFormat(vector<string>& params, const string strReq) +static enum RetFormat ParseDataFormat(std::string& param, const std::string& strReq) { - boost::split(params, strReq, boost::is_any_of(".")); - if (params.size() > 1) { - for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++) - if (params[1] == rf_names[i].name) - return rf_names[i].rf; + const std::string::size_type pos = strReq.rfind('.'); + if (pos == std::string::npos) + { + param = strReq; + return rf_names[0].rf; } + param = strReq.substr(0, pos); + const std::string suff(strReq, pos + 1); + + for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++) + if (suff == rf_names[i].name) + return rf_names[i].rf; + + /* If no suffix is found, return original string. */ + param = strReq; return rf_names[0].rf; } @@ -113,28 +117,35 @@ static bool ParseHashStr(const string& strReq, uint256& v) return true; } -static bool rest_headers(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool CheckWarmup(HTTPRequest* req) { - vector<string> params; - const RetFormat rf = ParseDataFormat(params, strURIPart); + std::string statusmessage; + if (RPCIsInWarmup(&statusmessage)) + return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage); + return true; +} + +static bool rest_headers(HTTPRequest* req, + const std::string& strURIPart) +{ + if (!CheckWarmup(req)) + return false; + std::string param; + const RetFormat rf = ParseDataFormat(param, strURIPart); vector<string> path; - boost::split(path, params[0], boost::is_any_of("/")); + boost::split(path, param, boost::is_any_of("/")); if (path.size() != 2) - throw RESTERR(HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>."); + return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>."); long count = strtol(path[0].c_str(), NULL, 10); if (count < 1 || count > 2000) - throw RESTERR(HTTP_BAD_REQUEST, "Header count out of range: " + path[0]); + return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]); string hashStr = path[1]; uint256 hash; if (!ParseHashStr(hashStr, hash)) - throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); std::vector<const CBlockIndex *> headers; headers.reserve(count); @@ -158,28 +169,29 @@ static bool rest_headers(AcceptedConnection* conn, switch (rf) { case RF_BINARY: { string binaryHeader = ssHeader.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryHeader.size(), "application/octet-stream") << binaryHeader << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, binaryHeader); return true; } case RF_HEX: { string strHex = HexStr(ssHeader.begin(), ssHeader.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } - case RF_JSON: { UniValue jsonHeaders(UniValue::VARR); BOOST_FOREACH(const CBlockIndex *pindex, headers) { jsonHeaders.push_back(blockheaderToJSON(pindex)); } string strJSON = jsonHeaders.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } - default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)"); } } @@ -187,34 +199,32 @@ static bool rest_headers(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_block(AcceptedConnection* conn, +static bool rest_block(HTTPRequest* req, const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun, bool showTxDetails) { - vector<string> params; - const RetFormat rf = ParseDataFormat(params, strURIPart); + if (!CheckWarmup(req)) + return false; + std::string hashStr; + const RetFormat rf = ParseDataFormat(hashStr, strURIPart); - string hashStr = params[0]; uint256 hash; if (!ParseHashStr(hashStr, hash)) - throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); CBlock block; CBlockIndex* pblockindex = NULL; { LOCK(cs_main); if (mapBlockIndex.count(hash) == 0) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); pblockindex = mapBlockIndex[hash]; if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); if (!ReadBlockFromDisk(block, pblockindex)) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); } CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); @@ -223,25 +233,28 @@ static bool rest_block(AcceptedConnection* conn, switch (rf) { case RF_BINARY: { string binaryBlock = ssBlock.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryBlock.size(), "application/octet-stream") << binaryBlock << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, binaryBlock); return true; } case RF_HEX: { string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } case RF_JSON: { UniValue objBlock = blockToJSON(block, pblockindex, showTxDetails); string strJSON = objBlock.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } @@ -249,43 +262,34 @@ static bool rest_block(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_block_extended(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_block_extended(HTTPRequest* req, const std::string& strURIPart) { - return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, true); + return rest_block(req, strURIPart, true); } -static bool rest_block_notxdetails(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_block_notxdetails(HTTPRequest* req, const std::string& strURIPart) { - return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, false); + return rest_block(req, strURIPart, false); } -static bool rest_chaininfo(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_chaininfo(HTTPRequest* req, const std::string& strURIPart) { - vector<string> params; - const RetFormat rf = ParseDataFormat(params, strURIPart); + if (!CheckWarmup(req)) + return false; + std::string param; + const RetFormat rf = ParseDataFormat(param, strURIPart); switch (rf) { case RF_JSON: { UniValue rpcParams(UniValue::VARR); UniValue chainInfoObject = getblockchaininfo(rpcParams, false); string strJSON = chainInfoObject.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); } } @@ -293,24 +297,71 @@ static bool rest_chaininfo(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_tx(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_mempool_info(HTTPRequest* req, const std::string& strURIPart) { - vector<string> params; - const RetFormat rf = ParseDataFormat(params, strURIPart); + if (!CheckWarmup(req)) + return false; + std::string param; + const RetFormat rf = ParseDataFormat(param, strURIPart); + + switch (rf) { + case RF_JSON: { + UniValue mempoolInfoObject = mempoolInfoToJSON(); + + string strJSON = mempoolInfoObject.write() + "\n"; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); + return true; + } + default: { + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); + } + } + + // not reached + return true; // continue to process further HTTP reqs on this cxn +} + +static bool rest_mempool_contents(HTTPRequest* req, const std::string& strURIPart) +{ + if (!CheckWarmup(req)) + return false; + std::string param; + const RetFormat rf = ParseDataFormat(param, strURIPart); + + switch (rf) { + case RF_JSON: { + UniValue mempoolObject = mempoolToJSON(true); + + string strJSON = mempoolObject.write() + "\n"; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); + return true; + } + default: { + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); + } + } + + // not reached + return true; // continue to process further HTTP reqs on this cxn +} + +static bool rest_tx(HTTPRequest* req, const std::string& strURIPart) +{ + if (!CheckWarmup(req)) + return false; + std::string hashStr; + const RetFormat rf = ParseDataFormat(hashStr, strURIPart); - string hashStr = params[0]; uint256 hash; if (!ParseHashStr(hashStr, hash)) - throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); CTransaction tx; uint256 hashBlock = uint256(); if (!GetTransaction(hash, tx, hashBlock, true)) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << tx; @@ -318,13 +369,15 @@ static bool rest_tx(AcceptedConnection* conn, switch (rf) { case RF_BINARY: { string binaryTx = ssTx.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryTx.size(), "application/octet-stream") << binaryTx << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, binaryTx); return true; } case RF_HEX: { string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } @@ -332,12 +385,13 @@ static bool rest_tx(AcceptedConnection* conn, UniValue objTx(UniValue::VOBJ); TxToJSON(tx, hashBlock, objTx); string strJSON = objTx.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } @@ -345,25 +399,24 @@ static bool rest_tx(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_getutxos(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart) { - vector<string> params; - enum RetFormat rf = ParseDataFormat(params, strURIPart); + if (!CheckWarmup(req)) + return false; + std::string param; + const RetFormat rf = ParseDataFormat(param, strURIPart); vector<string> uriParts; - if (params.size() > 0 && params[0].length() > 1) + if (param.length() > 1) { - std::string strUriParams = params[0].substr(1); + std::string strUriParams = param.substr(1); boost::split(uriParts, strUriParams, boost::is_any_of("/")); } // throw exception in case of a empty request - if (strRequest.length() == 0 && uriParts.size() == 0) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); + std::string strRequestMutable = req->ReadBody(); + if (strRequestMutable.length() == 0 && uriParts.size() == 0) + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); bool fInputParsed = false; bool fCheckMemPool = false; @@ -387,7 +440,7 @@ static bool rest_getutxos(AcceptedConnection* conn, std::string strOutput = uriParts[i].substr(uriParts[i].find("-")+1); if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid)) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error"); txid.SetHex(strTxid); vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput)); @@ -396,15 +449,13 @@ static bool rest_getutxos(AcceptedConnection* conn, if (vOutPoints.size() > 0) fInputParsed = true; else - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); } - string strRequestMutable = strRequest; //convert const string to string for allowing hex to bin converting - switch (rf) { case RF_HEX: { // convert hex to bin, continue then with bin part - std::vector<unsigned char> strRequestV = ParseHex(strRequest); + std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable); strRequestMutable.assign(strRequestV.begin(), strRequestV.end()); } @@ -414,7 +465,7 @@ static bool rest_getutxos(AcceptedConnection* conn, if (strRequestMutable.size() > 0) { if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed"); CDataStream oss(SER_NETWORK, PROTOCOL_VERSION); oss << strRequestMutable; @@ -423,24 +474,24 @@ static bool rest_getutxos(AcceptedConnection* conn, } } catch (const std::ios_base::failure& e) { // abort in case of unreadable binary data - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error"); } break; } case RF_JSON: { if (!fInputParsed) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); break; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } // limit max outpoints if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size())); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size())); // check spentness and form a bitmap (as well as a JSON capable human-readble string representation) vector<unsigned char> bitmap; @@ -490,7 +541,8 @@ static bool rest_getutxos(AcceptedConnection* conn, ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs; string ssGetUTXOResponseString = ssGetUTXOResponse.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, ssGetUTXOResponseString.size(), "application/octet-stream") << ssGetUTXOResponseString << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, ssGetUTXOResponseString); return true; } @@ -499,7 +551,8 @@ static bool rest_getutxos(AcceptedConnection* conn, ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs; string strHex = HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } @@ -529,11 +582,12 @@ static bool rest_getutxos(AcceptedConnection* conn, // return json string string strJSON = objGetUTXOResponse.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } @@ -543,43 +597,31 @@ static bool rest_getutxos(AcceptedConnection* conn, static const struct { const char* prefix; - bool (*handler)(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun); + bool (*handler)(HTTPRequest* req, const std::string& strReq); } uri_prefixes[] = { {"/rest/tx/", rest_tx}, {"/rest/block/notxdetails/", rest_block_notxdetails}, {"/rest/block/", rest_block_extended}, {"/rest/chaininfo", rest_chaininfo}, + {"/rest/mempool/info", rest_mempool_info}, + {"/rest/mempool/contents", rest_mempool_contents}, {"/rest/headers/", rest_headers}, {"/rest/getutxos", rest_getutxos}, }; -bool HTTPReq_REST(AcceptedConnection* conn, - const std::string& strURI, - const string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +bool StartREST() { - try { - std::string statusmessage; - if (RPCIsInWarmup(&statusmessage)) - throw RESTERR(HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage); - - for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) { - unsigned int plen = strlen(uri_prefixes[i].prefix); - if (strURI.substr(0, plen) == uri_prefixes[i].prefix) { - string strURIPart = strURI.substr(plen); - return uri_prefixes[i].handler(conn, strURIPart, strRequest, mapHeaders, fRun); - } - } - } catch (const RestErr& re) { - conn->stream() << HTTPReply(re.status, re.message + "\r\n", false, false, "text/plain") << std::flush; - return false; - } + for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) + RegisterHTTPHandler(uri_prefixes[i].prefix, false, uri_prefixes[i].handler); + return true; +} - conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush; - return false; +void InterruptREST() +{ +} + +void StopREST() +{ + for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) + UnregisterHTTPHandler(uri_prefixes[i].prefix, false); } diff --git a/src/reverselock.h b/src/reverselock.h new file mode 100644 index 0000000000..567636e16a --- /dev/null +++ b/src/reverselock.h @@ -0,0 +1,31 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_REVERSELOCK_H +#define BITCOIN_REVERSELOCK_H + +/** + * An RAII-style reverse lock. Unlocks on construction and locks on destruction. + */ +template<typename Lock> +class reverse_lock +{ +public: + + explicit reverse_lock(Lock& lock) : lock(lock) { + lock.unlock(); + } + + ~reverse_lock() { + lock.lock(); + } + +private: + reverse_lock(reverse_lock const&); + reverse_lock& operator=(reverse_lock const&); + + Lock& lock; +}; + +#endif // BITCOIN_REVERSELOCK_H diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 80d49490d2..e6751de96b 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -175,45 +175,8 @@ UniValue getdifficulty(const UniValue& params, bool fHelp) return GetDifficulty(); } - -UniValue getrawmempool(const UniValue& params, bool fHelp) +UniValue mempoolToJSON(bool fVerbose = false) { - if (fHelp || params.size() > 1) - throw runtime_error( - "getrawmempool ( verbose )\n" - "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" - "\nArguments:\n" - "1. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n" - "\nResult: (for verbose = false):\n" - "[ (json array of string)\n" - " \"transactionid\" (string) The transaction id\n" - " ,...\n" - "]\n" - "\nResult: (for verbose = true):\n" - "{ (json object)\n" - " \"transactionid\" : { (json object)\n" - " \"size\" : n, (numeric) transaction size in bytes\n" - " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" - " \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n" - " \"height\" : n, (numeric) block height when transaction entered pool\n" - " \"startingpriority\" : n, (numeric) priority when transaction entered pool\n" - " \"currentpriority\" : n, (numeric) transaction priority now\n" - " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n" - " \"transactionid\", (string) parent transaction id\n" - " ... ]\n" - " }, ...\n" - "}\n" - "\nExamples\n" - + HelpExampleCli("getrawmempool", "true") - + HelpExampleRpc("getrawmempool", "true") - ); - - LOCK(cs_main); - - bool fVerbose = false; - if (params.size() > 0) - fVerbose = params[0].get_bool(); - if (fVerbose) { LOCK(mempool.cs); @@ -261,6 +224,47 @@ UniValue getrawmempool(const UniValue& params, bool fHelp) } } +UniValue getrawmempool(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getrawmempool ( verbose )\n" + "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" + "\nArguments:\n" + "1. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n" + "\nResult: (for verbose = false):\n" + "[ (json array of string)\n" + " \"transactionid\" (string) The transaction id\n" + " ,...\n" + "]\n" + "\nResult: (for verbose = true):\n" + "{ (json object)\n" + " \"transactionid\" : { (json object)\n" + " \"size\" : n, (numeric) transaction size in bytes\n" + " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" + " \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n" + " \"height\" : n, (numeric) block height when transaction entered pool\n" + " \"startingpriority\" : n, (numeric) priority when transaction entered pool\n" + " \"currentpriority\" : n, (numeric) transaction priority now\n" + " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n" + " \"transactionid\", (string) parent transaction id\n" + " ... ]\n" + " }, ...\n" + "}\n" + "\nExamples\n" + + HelpExampleCli("getrawmempool", "true") + + HelpExampleRpc("getrawmempool", "true") + ); + + LOCK(cs_main); + + bool fVerbose = false; + if (params.size() > 0) + fVerbose = params[0].get_bool(); + + return mempoolToJSON(fVerbose); +} + UniValue getblockhash(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 1) @@ -757,6 +761,16 @@ UniValue getchaintips(const UniValue& params, bool fHelp) return res; } +UniValue mempoolInfoToJSON() +{ + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("size", (int64_t) mempool.size())); + ret.push_back(Pair("bytes", (int64_t) mempool.GetTotalTxSize())); + ret.push_back(Pair("usage", (int64_t) mempool.DynamicMemoryUsage())); + + return ret; +} + UniValue getmempoolinfo(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 0) @@ -774,12 +788,7 @@ UniValue getmempoolinfo(const UniValue& params, bool fHelp) + HelpExampleRpc("getmempoolinfo", "") ); - UniValue ret(UniValue::VOBJ); - ret.push_back(Pair("size", (int64_t) mempool.size())); - ret.push_back(Pair("bytes", (int64_t) mempool.GetTotalTxSize())); - ret.push_back(Pair("usage", (int64_t) mempool.DynamicMemoryUsage())); - - return ret; + return mempoolInfoToJSON(); } UniValue invalidateblock(const UniValue& params, bool fHelp) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index b41e960e8a..0c8e6d6d66 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -87,6 +87,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "lockunspent", 1 }, { "importprivkey", 2 }, { "importaddress", 2 }, + { "importaddress", 3 }, + { "importpubkey", 2 }, { "verifychain", 0 }, { "verifychain", 1 }, { "keypoolrefill", 0 }, diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index b7d4ff58fc..91de37fdcc 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -119,7 +119,7 @@ UniValue generate(const UniValue& params, bool fHelp) "generate numblocks\n" "\nMine blocks immediately (before the RPC call returns)\n" "\nNote: this function can only be used on the regtest network\n" - "1. numblocks (numeric) How many blocks are generated immediately.\n" + "1. numblocks (numeric, required) How many blocks are generated immediately.\n" "\nResult\n" "[ blockhashes ] (array) hashes of blocks generated\n" "\nExamples:\n" @@ -138,8 +138,12 @@ UniValue generate(const UniValue& params, bool fHelp) boost::shared_ptr<CReserveScript> coinbaseScript; GetMainSignals().ScriptForMining(coinbaseScript); + // If the keypool is exhausted, no script is returned at all. Catch this. + if (!coinbaseScript) + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + //throw an error if no script was provided - if (!coinbaseScript->reserveScript.size()) + if (coinbaseScript->reserveScript.empty()) throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet)"); { // Don't keep cs_main locked diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 6d10d1ce15..e2b6d5826c 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -157,13 +157,14 @@ UniValue validateaddress(const UniValue& params, bool fHelp) "1. \"bitcoinaddress\" (string, required) The bitcoin address to validate\n" "\nResult:\n" "{\n" - " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n" + " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n" " \"address\" : \"bitcoinaddress\", (string) The bitcoin address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" - " \"ismine\" : true|false, (boolean) If the address is yours or not\n" - " \"isscript\" : true|false, (boolean) If the key is a script\n" + " \"ismine\" : true|false, (boolean) If the address is yours or not\n" + " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" + " \"isscript\" : true|false, (boolean) If the key is a script\n" " \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n" - " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" + " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" " \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n" "}\n" "\nExamples:\n" diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 56ec59171d..30d0ed6270 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -96,6 +96,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp) " \"conntime\": ttt, (numeric) The connection time in seconds since epoch (Jan 1 1970 GMT)\n" " \"timeoffset\": ttt, (numeric) The time offset in seconds\n" " \"pingtime\": n, (numeric) ping time\n" + " \"minping\": n, (numeric) minimum observed ping time\n" " \"pingwait\": n, (numeric) ping wait\n" " \"version\": v, (numeric) The peer version, such as 7001\n" " \"subver\": \"/Satoshi:0.8.5/\", (string) The string version\n" @@ -139,6 +140,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("conntime", stats.nTimeConnected)); obj.push_back(Pair("timeoffset", stats.nTimeOffset)); obj.push_back(Pair("pingtime", stats.dPingTime)); + obj.push_back(Pair("minping", stats.dPingMin)); if (stats.dPingWait > 0.0) obj.push_back(Pair("pingwait", stats.dPingWait)); obj.push_back(Pair("version", stats.nVersion)); diff --git a/src/rpcprotocol.cpp b/src/rpcprotocol.cpp index 2e5c913734..d83cd87f94 100644 --- a/src/rpcprotocol.cpp +++ b/src/rpcprotocol.cpp @@ -5,7 +5,6 @@ #include "rpcprotocol.h" -#include "clientversion.h" #include "random.h" #include "tinyformat.h" #include "util.h" @@ -16,236 +15,8 @@ #include <stdint.h> #include <fstream> -#include <boost/algorithm/string.hpp> -#include <boost/asio.hpp> -#include <boost/asio/ssl.hpp> -#include <boost/bind.hpp> -#include <boost/filesystem.hpp> -#include <boost/foreach.hpp> -#include <boost/iostreams/concepts.hpp> -#include <boost/iostreams/stream.hpp> -#include <boost/shared_ptr.hpp> - -#include "univalue/univalue.h" - using namespace std; -//! Number of bytes to allocate and read at most at once in post data -const size_t POST_READ_SIZE = 256 * 1024; - -/** - * HTTP protocol - * - * This ain't Apache. We're just using HTTP header for the length field - * and to be compatible with other JSON-RPC implementations. - */ - -string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders) -{ - ostringstream s; - s << "POST / HTTP/1.1\r\n" - << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n" - << "Host: 127.0.0.1\r\n" - << "Content-Type: application/json\r\n" - << "Content-Length: " << strMsg.size() << "\r\n" - << "Connection: close\r\n" - << "Accept: application/json\r\n"; - BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders) - s << item.first << ": " << item.second << "\r\n"; - s << "\r\n" << strMsg; - - return s.str(); -} - -static string rfc1123Time() -{ - return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime()); -} - -static const char *httpStatusDescription(int nStatus) -{ - switch (nStatus) { - case HTTP_OK: return "OK"; - case HTTP_BAD_REQUEST: return "Bad Request"; - case HTTP_FORBIDDEN: return "Forbidden"; - case HTTP_NOT_FOUND: return "Not Found"; - case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error"; - default: return ""; - } -} - -string HTTPError(int nStatus, bool keepalive, bool headersOnly) -{ - if (nStatus == HTTP_UNAUTHORIZED) - return strprintf("HTTP/1.0 401 Authorization Required\r\n" - "Date: %s\r\n" - "Server: bitcoin-json-rpc/%s\r\n" - "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" - "Content-Type: text/html\r\n" - "Content-Length: 296\r\n" - "\r\n" - "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" - "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" - "<HTML>\r\n" - "<HEAD>\r\n" - "<TITLE>Error</TITLE>\r\n" - "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" - "</HEAD>\r\n" - "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n" - "</HTML>\r\n", rfc1123Time(), FormatFullVersion()); - - return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive, - headersOnly, "text/plain"); -} - -string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType) -{ - return strprintf( - "HTTP/1.1 %d %s\r\n" - "Date: %s\r\n" - "Connection: %s\r\n" - "Content-Length: %u\r\n" - "Content-Type: %s\r\n" - "Server: bitcoin-json-rpc/%s\r\n" - "\r\n", - nStatus, - httpStatusDescription(nStatus), - rfc1123Time(), - keepalive ? "keep-alive" : "close", - contentLength, - contentType, - FormatFullVersion()); -} - -string HTTPReply(int nStatus, const string& strMsg, bool keepalive, - bool headersOnly, const char *contentType) -{ - if (headersOnly) - { - return HTTPReplyHeader(nStatus, keepalive, 0, contentType); - } else { - return HTTPReplyHeader(nStatus, keepalive, strMsg.size(), contentType) + strMsg; - } -} - -bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, - string& http_method, string& http_uri) -{ - string str; - getline(stream, str); - - // HTTP request line is space-delimited - vector<string> vWords; - boost::split(vWords, str, boost::is_any_of(" ")); - if (vWords.size() < 2) - return false; - - // HTTP methods permitted: GET, POST - http_method = vWords[0]; - if (http_method != "GET" && http_method != "POST") - return false; - - // HTTP URI must be an absolute path, relative to current host - http_uri = vWords[1]; - if (http_uri.size() == 0 || http_uri[0] != '/') - return false; - - // parse proto, if present - string strProto = ""; - if (vWords.size() > 2) - strProto = vWords[2]; - - proto = 0; - const char *ver = strstr(strProto.c_str(), "HTTP/1."); - if (ver != NULL) - proto = atoi(ver+7); - - return true; -} - -int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) -{ - string str; - getline(stream, str); - vector<string> vWords; - boost::split(vWords, str, boost::is_any_of(" ")); - if (vWords.size() < 2) - return HTTP_INTERNAL_SERVER_ERROR; - proto = 0; - const char *ver = strstr(str.c_str(), "HTTP/1."); - if (ver != NULL) - proto = atoi(ver+7); - return atoi(vWords[1].c_str()); -} - -int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet) -{ - int nLen = 0; - while (true) - { - string str; - std::getline(stream, str); - if (str.empty() || str == "\r") - break; - string::size_type nColon = str.find(":"); - if (nColon != string::npos) - { - string strHeader = str.substr(0, nColon); - boost::trim(strHeader); - boost::to_lower(strHeader); - string strValue = str.substr(nColon+1); - boost::trim(strValue); - mapHeadersRet[strHeader] = strValue; - if (strHeader == "content-length") - nLen = atoi(strValue.c_str()); - } - } - return nLen; -} - - -int ReadHTTPMessage(std::basic_istream<char>& stream, map<string, - string>& mapHeadersRet, string& strMessageRet, - int nProto, size_t max_size) -{ - mapHeadersRet.clear(); - strMessageRet = ""; - - // Read header - int nLen = ReadHTTPHeaders(stream, mapHeadersRet); - if (nLen < 0 || (size_t)nLen > max_size) - return HTTP_INTERNAL_SERVER_ERROR; - - // Read message - if (nLen > 0) - { - vector<char> vch; - size_t ptr = 0; - while (ptr < (size_t)nLen) - { - size_t bytes_to_read = std::min((size_t)nLen - ptr, POST_READ_SIZE); - vch.resize(ptr + bytes_to_read); - stream.read(&vch[ptr], bytes_to_read); - if (!stream) // Connection lost while reading - return HTTP_INTERNAL_SERVER_ERROR; - ptr += bytes_to_read; - } - strMessageRet = string(vch.begin(), vch.end()); - } - - string sConHdr = mapHeadersRet["connection"]; - - if ((sConHdr != "close") && (sConHdr != "keep-alive")) - { - if (nProto >= 1) - mapHeadersRet["connection"] = "keep-alive"; - else - mapHeadersRet["connection"] = "close"; - } - - return HTTP_OK; -} - /** * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, * but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index 2360ec2c60..5381e4bcfd 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -10,10 +10,6 @@ #include <map> #include <stdint.h> #include <string> -#include <boost/iostreams/concepts.hpp> -#include <boost/iostreams/stream.hpp> -#include <boost/asio.hpp> -#include <boost/asio/ssl.hpp> #include <boost/filesystem.hpp> #include "univalue/univalue.h" @@ -26,6 +22,7 @@ enum HTTPStatusCode HTTP_UNAUTHORIZED = 401, HTTP_FORBIDDEN = 403, HTTP_NOT_FOUND = 404, + HTTP_BAD_METHOD = 405, HTTP_INTERNAL_SERVER_ERROR = 500, HTTP_SERVICE_UNAVAILABLE = 503, }; @@ -79,88 +76,6 @@ enum RPCErrorCode RPC_WALLET_ALREADY_UNLOCKED = -17, //! Wallet is already unlocked }; -/** - * IOStream device that speaks SSL but can also speak non-SSL - */ -template <typename Protocol> -class SSLIOStreamDevice : public boost::iostreams::device<boost::iostreams::bidirectional> { -public: - SSLIOStreamDevice(boost::asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn) - { - fUseSSL = fUseSSLIn; - fNeedHandshake = fUseSSLIn; - } - - void handshake(boost::asio::ssl::stream_base::handshake_type role) - { - if (!fNeedHandshake) return; - fNeedHandshake = false; - stream.handshake(role); - } - std::streamsize read(char* s, std::streamsize n) - { - handshake(boost::asio::ssl::stream_base::server); // HTTPS servers read first - if (fUseSSL) return stream.read_some(boost::asio::buffer(s, n)); - return stream.next_layer().read_some(boost::asio::buffer(s, n)); - } - std::streamsize write(const char* s, std::streamsize n) - { - handshake(boost::asio::ssl::stream_base::client); // HTTPS clients write first - if (fUseSSL) return boost::asio::write(stream, boost::asio::buffer(s, n)); - return boost::asio::write(stream.next_layer(), boost::asio::buffer(s, n)); - } - bool connect(const std::string& server, const std::string& port) - { - using namespace boost::asio::ip; - tcp::resolver resolver(stream.get_io_service()); - tcp::resolver::iterator endpoint_iterator; -#if BOOST_VERSION >= 104300 - try { -#endif - // The default query (flags address_configured) tries IPv6 if - // non-localhost IPv6 configured, and IPv4 if non-localhost IPv4 - // configured. - tcp::resolver::query query(server.c_str(), port.c_str()); - endpoint_iterator = resolver.resolve(query); -#if BOOST_VERSION >= 104300 - } catch (const boost::system::system_error&) { - // If we at first don't succeed, try blanket lookup (IPv4+IPv6 independent of configured interfaces) - tcp::resolver::query query(server.c_str(), port.c_str(), resolver_query_base::flags()); - endpoint_iterator = resolver.resolve(query); - } -#endif - boost::system::error_code error = boost::asio::error::host_not_found; - tcp::resolver::iterator end; - while (error && endpoint_iterator != end) - { - stream.lowest_layer().close(); - stream.lowest_layer().connect(*endpoint_iterator++, error); - } - if (error) - return false; - return true; - } - -private: - bool fNeedHandshake; - bool fUseSSL; - boost::asio::ssl::stream<typename Protocol::socket>& stream; -}; - -std::string HTTPPost(const std::string& strMsg, const std::map<std::string,std::string>& mapRequestHeaders); -std::string HTTPError(int nStatus, bool keepalive, - bool headerOnly = false); -std::string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, - const char *contentType = "application/json"); -std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive, - bool headerOnly = false, - const char *contentType = "application/json"); -bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, - std::string& http_method, std::string& http_uri); -int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto); -int ReadHTTPHeaders(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet); -int ReadHTTPMessage(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet, - std::string& strMessageRet, int nProto, size_t max_size); std::string JSONRPCRequest(const std::string& strMethod, const UniValue& params, const UniValue& id); UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index a3246b41fb..9eeca5b7d9 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -318,8 +318,9 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 2) throw runtime_error( - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,...}\n" - "\nCreate a transaction spending the given inputs and sending to the given addresses.\n" + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...}\n" + "\nCreate a transaction spending the given inputs and creating new outputs.\n" + "Outputs can be addresses or data.\n" "Returns hex-encoded raw transaction.\n" "Note that the transaction's inputs are not signed, and\n" "it is not stored in the wallet or transmitted to the network.\n" @@ -328,23 +329,25 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) "1. \"transactions\" (string, required) A json array of json objects\n" " [\n" " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" + " \"txid\":\"id\", (string, required) The transaction id\n" " \"vout\":n (numeric, required) The output number\n" " }\n" " ,...\n" " ]\n" - "2. \"addresses\" (string, required) a json object with addresses as keys and amounts as values\n" + "2. \"outputs\" (string, required) a json object with outputs\n" " {\n" " \"address\": x.xxx (numeric, required) The key is the bitcoin address, the value is the " + CURRENCY_UNIT + " amount\n" - " ,...\n" + " \"data\": \"hex\", (string, required) The key is \"data\", the value is hex encoded data\n" + " ...\n" " }\n" - "\nResult:\n" "\"transaction\" (string) hex string of the transaction\n" "\nExamples\n" + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"data\\\":\\\"00010203\\\"}\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"address\\\":0.01}\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"data\\\":\\\"00010203\\\"}\"") ); LOCK(cs_main); @@ -375,19 +378,27 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) set<CBitcoinAddress> setAddress; vector<string> addrList = sendTo.getKeys(); BOOST_FOREACH(const string& name_, addrList) { - CBitcoinAddress address(name_); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_); - if (setAddress.count(address)) - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_); - setAddress.insert(address); + if (name_ == "data") { + std::vector<unsigned char> data = ParseHexV(sendTo[name_].getValStr(),"Data"); + + CTxOut out(0, CScript() << OP_RETURN << data); + rawTx.vout.push_back(out); + } else { + CBitcoinAddress address(name_); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_); - CScript scriptPubKey = GetScriptForDestination(address.Get()); - CAmount nAmount = AmountFromValue(sendTo[name_]); + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_); + setAddress.insert(address); - CTxOut out(nAmount, scriptPubKey); - rawTx.vout.push_back(out); + CScript scriptPubKey = GetScriptForDestination(address.Get()); + CAmount nAmount = AmountFromValue(sendTo[name_]); + + CTxOut out(nAmount, scriptPubKey); + rawTx.vout.push_back(out); + } } return EncodeHexTx(rawTx); diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 68091010f7..b831d3d3b2 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -12,13 +12,9 @@ #include "ui_interface.h" #include "util.h" #include "utilstrencodings.h" -#ifdef ENABLE_WALLET -#include "wallet/wallet.h" -#endif -#include <boost/algorithm/string.hpp> -#include <boost/asio.hpp> -#include <boost/asio/ssl.hpp> +#include "univalue/univalue.h" + #include <boost/bind.hpp> #include <boost/filesystem.hpp> #include <boost/foreach.hpp> @@ -27,28 +23,20 @@ #include <boost/shared_ptr.hpp> #include <boost/signals2/signal.hpp> #include <boost/thread.hpp> +#include <boost/algorithm/string/case_conv.hpp> // for to_upper() -#include "univalue/univalue.h" - -using namespace boost::asio; using namespace RPCServer; using namespace std; -static std::string strRPCUserColonPass; - static bool fRPCRunning = false; static bool fRPCInWarmup = true; static std::string rpcWarmupStatus("RPC server started"); static CCriticalSection cs_rpcWarmup; - -//! These are created by StartRPCThreads, destroyed in StopRPCThreads -static boost::asio::io_service* rpc_io_service = NULL; -static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers; -static ssl::context* rpc_ssl_context = NULL; -static boost::thread_group* rpc_worker_group = NULL; -static boost::asio::io_service::work *rpc_dummy_work = NULL; -static std::vector<CSubNet> rpc_allow_subnets; //!< List of subnets to allow RPC connections from -static std::vector< boost::shared_ptr<ip::tcp::acceptor> > rpc_acceptors; +/* Timer-creating functions */ +static std::vector<RPCTimerInterface*> timerInterfaces; +/* Map of name to timer. + * @note Can be changed to std::unique_ptr when C++11 */ +static std::map<std::string, boost::shared_ptr<RPCTimerBase> > deadlineTimers; static struct CRPCSignals { @@ -169,7 +157,6 @@ vector<unsigned char> ParseHexO(const UniValue& o, string strKey) return ParseHexV(find_value(o, strKey), strKey); } - /** * Note: This interface may still be subject to change. */ @@ -261,8 +248,6 @@ UniValue stop(const UniValue& params, bool fHelp) return "Bitcoin server stopping"; } - - /** * Call Table */ @@ -363,6 +348,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "importprivkey", &importprivkey, true }, { "wallet", "importwallet", &importwallet, true }, { "wallet", "importaddress", &importaddress, true }, + { "wallet", "importpubkey", &importpubkey, true }, { "wallet", "keypoolrefill", &keypoolrefill, true }, { "wallet", "listaccounts", &listaccounts, false }, { "wallet", "listaddressgroupings", &listaddressgroupings, false }, @@ -398,7 +384,7 @@ CRPCTable::CRPCTable() } } -const CRPCCommand *CRPCTable::operator[](const std::string& name) const +const CRPCCommand *CRPCTable::operator[](const std::string &name) const { map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name); if (it == mapCommands.end()) @@ -406,373 +392,26 @@ const CRPCCommand *CRPCTable::operator[](const std::string& name) const return (*it).second; } - -bool HTTPAuthorized(map<string, string>& mapHeaders) -{ - string strAuth = mapHeaders["authorization"]; - if (strAuth.substr(0,6) != "Basic ") - return false; - string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); - string strUserPass = DecodeBase64(strUserPass64); - return TimingResistantEqual(strUserPass, strRPCUserColonPass); -} - -void ErrorReply(std::ostream& stream, const UniValue& objError, const UniValue& id) -{ - // Send error reply from json-rpc error object - int nStatus = HTTP_INTERNAL_SERVER_ERROR; - int code = find_value(objError, "code").get_int(); - if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; - else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; - string strReply = JSONRPCReply(NullUniValue, objError, id); - stream << HTTPReply(nStatus, strReply, false) << std::flush; -} - -CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address) -{ - CNetAddr netaddr; - // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses - if (address.is_v6() - && (address.to_v6().is_v4_compatible() - || address.to_v6().is_v4_mapped())) - address = address.to_v6().to_v4(); - - if(address.is_v4()) - { - boost::asio::ip::address_v4::bytes_type bytes = address.to_v4().to_bytes(); - netaddr.SetRaw(NET_IPV4, &bytes[0]); - } - else - { - boost::asio::ip::address_v6::bytes_type bytes = address.to_v6().to_bytes(); - netaddr.SetRaw(NET_IPV6, &bytes[0]); - } - return netaddr; -} - -bool ClientAllowed(const boost::asio::ip::address& address) -{ - CNetAddr netaddr = BoostAsioToCNetAddr(address); - BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) - if (subnet.Match(netaddr)) - return true; - return false; -} - -template <typename Protocol> -class AcceptedConnectionImpl : public AcceptedConnection -{ -public: - AcceptedConnectionImpl( - boost::asio::io_service& io_service, - ssl::context &context, - bool fUseSSL) : - sslStream(io_service, context), - _d(sslStream, fUseSSL), - _stream(_d) - { - } - - virtual std::iostream& stream() - { - return _stream; - } - - virtual std::string peer_address_to_string() const - { - return peer.address().to_string(); - } - - virtual void close() - { - _stream.close(); - } - - typename Protocol::endpoint peer; - boost::asio::ssl::stream<typename Protocol::socket> sslStream; - -private: - SSLIOStreamDevice<Protocol> _d; - boost::iostreams::stream< SSLIOStreamDevice<Protocol> > _stream; -}; - -void ServiceConnection(AcceptedConnection *conn); - -//! Forward declaration required for RPCListen -template <typename Protocol, typename SocketAcceptorService> -static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, - ssl::context& context, - bool fUseSSL, - boost::shared_ptr< AcceptedConnection > conn, - const boost::system::error_code& error); - -/** - * Sets up I/O resources to accept and handle a new connection. - */ -template <typename Protocol, typename SocketAcceptorService> -static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, - ssl::context& context, - const bool fUseSSL) -{ - // Accept connection - boost::shared_ptr< AcceptedConnectionImpl<Protocol> > conn(new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL)); - - acceptor->async_accept( - conn->sslStream.lowest_layer(), - conn->peer, - boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>, - acceptor, - boost::ref(context), - fUseSSL, - conn, - _1)); -} - - -/** - * Accept and handle incoming connection. - */ -template <typename Protocol, typename SocketAcceptorService> -static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, - ssl::context& context, - const bool fUseSSL, - boost::shared_ptr< AcceptedConnection > conn, - const boost::system::error_code& error) +bool StartRPC() { - // Immediately start accepting new connections, except when we're cancelled or our socket is closed. - if (error != boost::asio::error::operation_aborted && acceptor->is_open()) - RPCListen(acceptor, context, fUseSSL); - - AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn.get()); - - if (error) - { - // TODO: Actually handle errors - LogPrintf("%s: Error: %s\n", __func__, error.message()); - } - // Restrict callers by IP. It is important to - // do this before starting client thread, to filter out - // certain DoS and misbehaving clients. - else if (tcp_conn && !ClientAllowed(tcp_conn->peer.address())) - { - // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. - if (!fUseSSL) - conn->stream() << HTTPError(HTTP_FORBIDDEN, false) << std::flush; - conn->close(); - } - else { - ServiceConnection(conn.get()); - conn->close(); - } -} - -static ip::tcp::endpoint ParseEndpoint(const std::string &strEndpoint, int defaultPort) -{ - std::string addr; - int port = defaultPort; - SplitHostPort(strEndpoint, port, addr); - return ip::tcp::endpoint(boost::asio::ip::address::from_string(addr), port); -} - -void StartRPCThreads() -{ - rpc_allow_subnets.clear(); - rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet - rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost - if (mapMultiArgs.count("-rpcallowip")) - { - const vector<string>& vAllow = mapMultiArgs["-rpcallowip"]; - BOOST_FOREACH(string strAllow, vAllow) - { - CSubNet subnet(strAllow); - if(!subnet.IsValid()) - { - uiInterface.ThreadSafeMessageBox( - strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow), - "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - rpc_allow_subnets.push_back(subnet); - } - } - std::string strAllowed; - BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) - strAllowed += subnet.ToString() + " "; - LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed); - - if (mapArgs["-rpcpassword"] == "") - { - LogPrintf("No rpcpassword set - using random cookie authentication\n"); - if (!GenerateAuthCookie(&strRPCUserColonPass)) { - uiInterface.ThreadSafeMessageBox( - _("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode - "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - } else { - strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; - } - - assert(rpc_io_service == NULL); - rpc_io_service = new boost::asio::io_service(); - rpc_ssl_context = new ssl::context(*rpc_io_service, ssl::context::sslv23); - - const bool fUseSSL = GetBoolArg("-rpcssl", false); - - if (fUseSSL) - { - rpc_ssl_context->set_options(ssl::context::no_sslv2 | ssl::context::no_sslv3); - - boost::filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert")); - if (!pathCertFile.is_complete()) pathCertFile = boost::filesystem::path(GetDataDir()) / pathCertFile; - if (boost::filesystem::exists(pathCertFile)) rpc_ssl_context->use_certificate_chain_file(pathCertFile.string()); - else LogPrintf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string()); - - boost::filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem")); - if (!pathPKFile.is_complete()) pathPKFile = boost::filesystem::path(GetDataDir()) / pathPKFile; - if (boost::filesystem::exists(pathPKFile)) rpc_ssl_context->use_private_key_file(pathPKFile.string(), ssl::context::pem); - else LogPrintf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string()); - - string strCiphers = GetArg("-rpcsslciphers", "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH"); - SSL_CTX_set_cipher_list(rpc_ssl_context->impl(), strCiphers.c_str()); - } - - std::vector<ip::tcp::endpoint> vEndpoints; - bool bBindAny = false; - int defaultPort = GetArg("-rpcport", BaseParams().RPCPort()); - if (!mapArgs.count("-rpcallowip")) // Default to loopback if not allowing external IPs - { - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::loopback(), defaultPort)); - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::loopback(), defaultPort)); - if (mapArgs.count("-rpcbind")) - { - LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n"); - } - } else if (mapArgs.count("-rpcbind")) // Specific bind address - { - BOOST_FOREACH(const std::string &addr, mapMultiArgs["-rpcbind"]) - { - try { - vEndpoints.push_back(ParseEndpoint(addr, defaultPort)); - } - catch (const boost::system::system_error&) - { - uiInterface.ThreadSafeMessageBox( - strprintf(_("Could not parse -rpcbind value %s as network address"), addr), - "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - } - } else { // No specific bind address specified, bind to any - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::any(), defaultPort)); - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::any(), defaultPort)); - // Prefer making the socket dual IPv6/IPv4 instead of binding - // to both addresses seperately. - bBindAny = true; - } - - bool fListening = false; - std::string strerr; - std::string straddress; - BOOST_FOREACH(const ip::tcp::endpoint &endpoint, vEndpoints) - { - try { - boost::asio::ip::address bindAddress = endpoint.address(); - straddress = bindAddress.to_string(); - LogPrintf("Binding RPC on address %s port %i (IPv4+IPv6 bind any: %i)\n", straddress, endpoint.port(), bBindAny); - boost::system::error_code v6_only_error; - boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(*rpc_io_service)); - - acceptor->open(endpoint.protocol()); - acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - - // Try making the socket dual IPv6/IPv4 when listening on the IPv6 "any" address - acceptor->set_option(boost::asio::ip::v6_only( - !bBindAny || bindAddress != boost::asio::ip::address_v6::any()), v6_only_error); - - acceptor->bind(endpoint); - acceptor->listen(socket_base::max_connections); - - RPCListen(acceptor, *rpc_ssl_context, fUseSSL); - - fListening = true; - rpc_acceptors.push_back(acceptor); - // If dual IPv6/IPv4 bind successful, skip binding to IPv4 separately - if(bBindAny && bindAddress == boost::asio::ip::address_v6::any() && !v6_only_error) - break; - } - catch (const boost::system::system_error& e) - { - LogPrintf("ERROR: Binding RPC on address %s port %i failed: %s\n", straddress, endpoint.port(), e.what()); - strerr = strprintf(_("An error occurred while setting up the RPC address %s port %u for listening: %s"), straddress, endpoint.port(), e.what()); - } - } - - if (!fListening) { - uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - - rpc_worker_group = new boost::thread_group(); - for (int i = 0; i < GetArg("-rpcthreads", 4); i++) - rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service)); + LogPrint("rpc", "Starting RPC\n"); fRPCRunning = true; g_rpcSignals.Started(); + return true; } -void StartDummyRPCThread() +void InterruptRPC() { - if(rpc_io_service == NULL) - { - rpc_io_service = new boost::asio::io_service(); - /* Create dummy "work" to keep the thread from exiting when no timeouts active, - * see http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.stopping_the_io_service_from_running_out_of_work */ - rpc_dummy_work = new boost::asio::io_service::work(*rpc_io_service); - rpc_worker_group = new boost::thread_group(); - rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service)); - fRPCRunning = true; - } + LogPrint("rpc", "Interrupting RPC\n"); + // Interrupt e.g. running longpolls + fRPCRunning = false; } -void StopRPCThreads() +void StopRPC() { - if (rpc_io_service == NULL) return; - // Set this to false first, so that longpolling loops will exit when woken up - fRPCRunning = false; - - // First, cancel all timers and acceptors - // This is not done automatically by ->stop(), and in some cases the destructor of - // boost::asio::io_service can hang if this is skipped. - boost::system::error_code ec; - BOOST_FOREACH(const boost::shared_ptr<ip::tcp::acceptor> &acceptor, rpc_acceptors) - { - acceptor->cancel(ec); - if (ec) - LogPrintf("%s: Warning: %s when cancelling acceptor\n", __func__, ec.message()); - } - rpc_acceptors.clear(); - BOOST_FOREACH(const PAIRTYPE(std::string, boost::shared_ptr<deadline_timer>) &timer, deadlineTimers) - { - timer.second->cancel(ec); - if (ec) - LogPrintf("%s: Warning: %s when cancelling timer\n", __func__, ec.message()); - } + LogPrint("rpc", "Stopping RPC\n"); deadlineTimers.clear(); - - DeleteAuthCookie(); - - rpc_io_service->stop(); g_rpcSignals.Stopped(); - if (rpc_worker_group != NULL) - rpc_worker_group->join_all(); - delete rpc_dummy_work; rpc_dummy_work = NULL; - delete rpc_worker_group; rpc_worker_group = NULL; - delete rpc_ssl_context; rpc_ssl_context = NULL; - delete rpc_io_service; rpc_io_service = NULL; } bool IsRPCRunning() @@ -801,36 +440,6 @@ bool RPCIsInWarmup(std::string *outStatus) return fRPCInWarmup; } -void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func) -{ - if (!err) - func(); -} - -void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds) -{ - assert(rpc_io_service != NULL); - - if (deadlineTimers.count(name) == 0) - { - deadlineTimers.insert(make_pair(name, - boost::shared_ptr<deadline_timer>(new deadline_timer(*rpc_io_service)))); - } - deadlineTimers[name]->expires_from_now(boost::posix_time::seconds(nSeconds)); - deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func)); -} - -class JSONRequest -{ -public: - UniValue id; - string strMethod; - UniValue params; - - JSONRequest() { id = NullUniValue; } - void parse(const UniValue& valRequest); -}; - void JSONRequest::parse(const UniValue& valRequest) { // Parse request @@ -861,7 +470,6 @@ void JSONRequest::parse(const UniValue& valRequest) throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); } - static UniValue JSONRPCExecOne(const UniValue& req) { UniValue rpc_result(UniValue::VOBJ); @@ -886,7 +494,7 @@ static UniValue JSONRPCExecOne(const UniValue& req) return rpc_result; } -static string JSONRPCExecBatch(const UniValue& vReq) +std::string JSONRPCExecBatch(const UniValue& vReq) { UniValue ret(UniValue::VARR); for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) @@ -895,107 +503,6 @@ static string JSONRPCExecBatch(const UniValue& vReq) return ret.write() + "\n"; } -static bool HTTPReq_JSONRPC(AcceptedConnection *conn, - string& strRequest, - map<string, string>& mapHeaders, - bool fRun) -{ - // Check authorization - if (mapHeaders.count("authorization") == 0) - { - conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush; - return false; - } - - if (!HTTPAuthorized(mapHeaders)) - { - LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string()); - /* Deter brute-forcing - We don't support exposing the RPC port, so this shouldn't result - in a DoS. */ - MilliSleep(250); - - conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush; - return false; - } - - JSONRequest jreq; - try - { - // Parse request - UniValue valRequest; - if (!valRequest.read(strRequest)) - throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); - - string strReply; - - // singleton request - if (valRequest.isObject()) { - jreq.parse(valRequest); - - UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); - - // Send reply - strReply = JSONRPCReply(result, NullUniValue, jreq.id); - - // array of requests - } else if (valRequest.isArray()) - strReply = JSONRPCExecBatch(valRequest.get_array()); - else - throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); - - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, strReply.size()) << strReply << std::flush; - } - catch (const UniValue& objError) - { - ErrorReply(conn->stream(), objError, jreq.id); - return false; - } - catch (const std::exception& e) - { - ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); - return false; - } - return true; -} - -void ServiceConnection(AcceptedConnection *conn) -{ - bool fRun = true; - while (fRun && !ShutdownRequested()) - { - int nProto = 0; - map<string, string> mapHeaders; - string strRequest, strMethod, strURI; - - // Read HTTP request line - if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI)) - break; - - // Read HTTP message headers and body - ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto, MAX_SIZE); - - // HTTP Keep-Alive is false; close connection immediately - if ((mapHeaders["connection"] == "close") || (!GetBoolArg("-rpckeepalive", true))) - fRun = false; - - // Process via JSON-RPC API - if (strURI == "/") { - if (!HTTPReq_JSONRPC(conn, strRequest, mapHeaders, fRun)) - break; - - // Process via HTTP REST API - } else if (strURI.substr(0, 6) == "/rest/" && GetBoolArg("-rest", false)) { - if (!HTTPReq_REST(conn, strURI, strRequest, mapHeaders, fRun)) - break; - - } else { - conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush; - break; - } - } -} - UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms) const { // Return immediately if in warmup @@ -1036,4 +543,26 @@ std::string HelpExampleRpc(const std::string& methodname, const std::string& arg "\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; } +void RPCRegisterTimerInterface(RPCTimerInterface *iface) +{ + timerInterfaces.push_back(iface); +} + +void RPCUnregisterTimerInterface(RPCTimerInterface *iface) +{ + std::vector<RPCTimerInterface*>::iterator i = std::find(timerInterfaces.begin(), timerInterfaces.end(), iface); + assert(i != timerInterfaces.end()); + timerInterfaces.erase(i); +} + +void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds) +{ + if (timerInterfaces.empty()) + throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC"); + deadlineTimers.erase(name); + RPCTimerInterface* timerInterface = timerInterfaces[0]; + LogPrint("rpc", "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); + deadlineTimers.insert(std::make_pair(name, timerInterface->NewTimer(func, nSeconds*1000))); +} + const CRPCTable tableRPC; diff --git a/src/rpcserver.h b/src/rpcserver.h index 89d3980223..83cc37918b 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -32,26 +32,17 @@ namespace RPCServer class CBlockIndex; class CNetAddr; -class AcceptedConnection +class JSONRequest { public: - virtual ~AcceptedConnection() {} + UniValue id; + std::string strMethod; + UniValue params; - virtual std::iostream& stream() = 0; - virtual std::string peer_address_to_string() const = 0; - virtual void close() = 0; + JSONRequest() { id = NullUniValue; } + void parse(const UniValue& valRequest); }; -/** Start RPC threads */ -void StartRPCThreads(); -/** - * Alternative to StartRPCThreads for the GUI, when no server is - * used. The RPC thread in this case is only used to handle timeouts. - * If real RPC threads have already been started this is a no-op. - */ -void StartDummyRPCThread(); -/** Stop RPC threads */ -void StopRPCThreads(); /** Query whether RPC is running */ bool IsRPCRunning(); @@ -81,15 +72,45 @@ void RPCTypeCheck(const UniValue& params, void RPCTypeCheckObj(const UniValue& o, const std::map<std::string, UniValue::VType>& typesExpected, bool fAllowNull=false); +/** Opaque base class for timers returned by NewTimerFunc. + * This provides no methods at the moment, but makes sure that delete + * cleans up the whole state. + */ +class RPCTimerBase +{ +public: + virtual ~RPCTimerBase() {} +}; + /** - * Run func nSeconds from now. Uses boost deadline timers. + * RPC timer "driver". + */ +class RPCTimerInterface +{ +public: + virtual ~RPCTimerInterface() {} + /** Implementation name */ + virtual const char *Name() = 0; + /** Factory function for timers. + * RPC will call the function to create a timer that will call func in *millis* milliseconds. + * @note As the RPC mechanism is backend-neutral, it can use different implementations of timers. + * This is needed to cope with the case in which there is no HTTP server, but + * only GUI RPC console, and to break the dependency of pcserver on httprpc. + */ + virtual RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) = 0; +}; + +/** Register factory function for timers */ +void RPCRegisterTimerInterface(RPCTimerInterface *iface); +/** Unregister factory function for timers */ +void RPCUnregisterTimerInterface(RPCTimerInterface *iface); + +/** + * Run func nSeconds from now. * Overrides previous timer <name> (if any). */ void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds); -//! Convert boost::asio address to CNetAddr -extern CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address); - typedef UniValue(*rpcfn_type)(const UniValue& params, bool fHelp); class CRPCCommand @@ -134,9 +155,6 @@ extern uint256 ParseHashO(const UniValue& o, std::string strKey); extern std::vector<unsigned char> ParseHexV(const UniValue& v, std::string strName); extern std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey); -extern void InitRPCMining(); -extern void ShutdownRPCMining(); - extern int64_t nWalletUnlockTime; extern CAmount AmountFromValue(const UniValue& value); extern UniValue ValueFromAmount(const CAmount& amount); @@ -161,6 +179,7 @@ extern UniValue clearbanned(const UniValue& params, bool fHelp); extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue importprivkey(const UniValue& params, bool fHelp); extern UniValue importaddress(const UniValue& params, bool fHelp); +extern UniValue importpubkey(const UniValue& params, bool fHelp); extern UniValue dumpwallet(const UniValue& params, bool fHelp); extern UniValue importwallet(const UniValue& params, bool fHelp); @@ -243,11 +262,9 @@ extern UniValue getchaintips(const UniValue& params, bool fHelp); extern UniValue invalidateblock(const UniValue& params, bool fHelp); extern UniValue reconsiderblock(const UniValue& params, bool fHelp); -// in rest.cpp -extern bool HTTPReq_REST(AcceptedConnection *conn, - const std::string& strURI, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun); +bool StartRPC(); +void InterruptRPC(); +void StopRPC(); +std::string JSONRPCExecBatch(const UniValue& vReq); #endif // BITCOIN_RPCSERVER_H diff --git a/src/scheduler.cpp b/src/scheduler.cpp index d5bb588b71..184ddc28ab 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -4,6 +4,8 @@ #include "scheduler.h" +#include "reverselock.h" + #include <assert.h> #include <boost/bind.hpp> #include <utility> @@ -65,11 +67,12 @@ void CScheduler::serviceQueue() Function f = taskQueue.begin()->second; taskQueue.erase(taskQueue.begin()); - // Unlock before calling f, so it can reschedule itself or another task - // without deadlocking: - lock.unlock(); - f(); - lock.lock(); + { + // Unlock before calling f, so it can reschedule itself or another task + // without deadlocking: + reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock); + f(); + } } catch (...) { --nThreadsServicingQueue; throw; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 0b78fdf5a8..bd5e54b33f 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1128,7 +1128,7 @@ bool TransactionSignatureChecker::CheckSig(const vector<unsigned char>& vchSigIn bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const { - // There are two times of nLockTime: lock-by-blockheight + // There are two kinds of nLockTime: lock-by-blockheight // and lock-by-blocktime, distinguished by whether // nLockTime < LOCKTIME_THRESHOLD. // diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 66657127ab..1d5aac7b34 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -286,6 +286,11 @@ CScript GetScriptForDestination(const CTxDestination& dest) return script; } +CScript GetScriptForRawPubKey(const CPubKey& pubKey) +{ + return CScript() << std::vector<unsigned char>(pubKey.begin(), pubKey.end()) << OP_CHECKSIG; +} + CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys) { CScript script; diff --git a/src/script/standard.h b/src/script/standard.h index 46ae5f9f10..9e17dac700 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -73,6 +73,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet); CScript GetScriptForDestination(const CTxDestination& dest); +CScript GetScriptForRawPubKey(const CPubKey& pubkey); CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/sync.cpp b/src/sync.cpp index a422939964..1837e8d53d 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -33,20 +33,22 @@ void PrintLockContention(const char* pszName, const char* pszFile, int nLine) // struct CLockLocation { - CLockLocation(const char* pszName, const char* pszFile, int nLine) + CLockLocation(const char* pszName, const char* pszFile, int nLine, bool fTryIn) { mutexName = pszName; sourceFile = pszFile; sourceLine = nLine; + fTry = fTryIn; } std::string ToString() const { - return mutexName + " " + sourceFile + ":" + itostr(sourceLine); + return mutexName + " " + sourceFile + ":" + itostr(sourceLine) + (fTry ? " (TRY)" : ""); } std::string MutexName() const { return mutexName; } + bool fTry; private: std::string mutexName; std::string sourceFile; @@ -62,23 +64,52 @@ static boost::thread_specific_ptr<LockStack> lockstack; static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, const LockStack& s1, const LockStack& s2) { + // We attempt to not assert on probably-not deadlocks by assuming that + // a try lock will immediately have otherwise bailed if it had + // failed to get the lock + // We do this by, for the locks which triggered the potential deadlock, + // in either lockorder, checking that the second of the two which is locked + // is only a TRY_LOCK, ignoring locks if they are reentrant. + bool firstLocked = false; + bool secondLocked = false; + bool onlyMaybeDeadlock = false; + LogPrintf("POTENTIAL DEADLOCK DETECTED\n"); LogPrintf("Previous lock order was:\n"); BOOST_FOREACH (const PAIRTYPE(void*, CLockLocation) & i, s2) { - if (i.first == mismatch.first) + if (i.first == mismatch.first) { LogPrintf(" (1)"); - if (i.first == mismatch.second) + if (!firstLocked && secondLocked && i.second.fTry) + onlyMaybeDeadlock = true; + firstLocked = true; + } + if (i.first == mismatch.second) { LogPrintf(" (2)"); + if (!secondLocked && firstLocked && i.second.fTry) + onlyMaybeDeadlock = true; + secondLocked = true; + } LogPrintf(" %s\n", i.second.ToString()); } + firstLocked = false; + secondLocked = false; LogPrintf("Current lock order is:\n"); BOOST_FOREACH (const PAIRTYPE(void*, CLockLocation) & i, s1) { - if (i.first == mismatch.first) + if (i.first == mismatch.first) { LogPrintf(" (1)"); - if (i.first == mismatch.second) + if (!firstLocked && secondLocked && i.second.fTry) + onlyMaybeDeadlock = true; + firstLocked = true; + } + if (i.first == mismatch.second) { LogPrintf(" (2)"); + if (!secondLocked && firstLocked && i.second.fTry) + onlyMaybeDeadlock = true; + secondLocked = true; + } LogPrintf(" %s\n", i.second.ToString()); } + assert(onlyMaybeDeadlock); } static void push_lock(void* c, const CLockLocation& locklocation, bool fTry) @@ -101,10 +132,8 @@ static void push_lock(void* c, const CLockLocation& locklocation, bool fTry) lockorders[p1] = (*lockstack); std::pair<void*, void*> p2 = std::make_pair(c, i.first); - if (lockorders.count(p2)) { + if (lockorders.count(p2)) potential_deadlock_detected(p1, lockorders[p2], lockorders[p1]); - break; - } } } dd_mutex.unlock(); @@ -119,7 +148,7 @@ static void pop_lock() void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry) { - push_lock(cs, CLockLocation(pszName, pszFile, nLine), fTry); + push_lock(cs, CLockLocation(pszName, pszFile, nLine, fTry), fTry); } void LeaveCritical() diff --git a/src/sync.h b/src/sync.h index 705647e4a5..68a9443084 100644 --- a/src/sync.h +++ b/src/sync.h @@ -16,7 +16,7 @@ //////////////////////////////////////////////// // // -// THE SIMPLE DEFINITON, EXCLUDING DEBUG CODE // +// THE SIMPLE DEFINITION, EXCLUDING DEBUG CODE // // // //////////////////////////////////////////////// diff --git a/src/test/Checkpoints_tests.cpp b/src/test/Checkpoints_tests.cpp index 703cf307d1..0a23c430ed 100644 --- a/src/test/Checkpoints_tests.cpp +++ b/src/test/Checkpoints_tests.cpp @@ -20,7 +20,7 @@ BOOST_FIXTURE_TEST_SUITE(Checkpoints_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sanity) { - const Checkpoints::CCheckpointData& checkpoints = Params(CBaseChainParams::MAIN).Checkpoints(); + const CCheckpointData& checkpoints = Params(CBaseChainParams::MAIN).Checkpoints(); BOOST_CHECK(Checkpoints::GetTotalBlocksEstimate(checkpoints) >= 134444); } diff --git a/src/test/data/bitcoin-util-test.json b/src/test/data/bitcoin-util-test.json index afd35af503..3bf80ca434 100644 --- a/src/test/data/bitcoin-util-test.json +++ b/src/test/data/bitcoin-util-test.json @@ -56,5 +56,35 @@ "sign=ALL", "outaddr=0.001:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7"], "output_cmp": "txcreatesign.hex" + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", + "outdata=4:badhexdata"], + "return_code": 1 + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", + "outdata=badhexdata"], + "return_code": 1 + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", + "outaddr=0.18:13tuJJDR2RgArmgfv6JScSdreahzgc4T6o", + "outdata=4:54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e"], + "output_cmp": "txcreatedata1.hex" + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", + "outaddr=0.18:13tuJJDR2RgArmgfv6JScSdreahzgc4T6o", + "outdata=54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e"], + "output_cmp": "txcreatedata2.hex" } ] diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index 20bdbd08a5..5cad5af7c3 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -128,7 +128,7 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP2 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000fe64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], -["By-time locks, with argument just beyond tx nLockTime (but within numerical boundries)"], +["By-time locks, with argument just beyond tx nLockTime (but within numerical boundaries)"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000001 NOP2 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 NOP2 1"]], @@ -181,7 +181,7 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 NOP2 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffff7f", "P2SH,CHECKLOCKTIMEVERIFY"], -["6 byte non-minimally-encoded arguments are invalid even in their contents are valid"], +["6 byte non-minimally-encoded arguments are invalid even if their contents are valid"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x06 0x000000000000 NOP2 1"]], "010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 24fff575c1..9744a3c848 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -197,7 +197,7 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP2 1"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], -["By-time locks, with argument just beyond tx nLockTime (but within numerical boundries)"], +["By-time locks, with argument just beyond tx nLockTime (but within numerical boundaries)"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP2 1"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 NOP2 1"]], diff --git a/src/test/data/txcreatedata1.hex b/src/test/data/txcreatedata1.hex new file mode 100644 index 0000000000..eccc7604e6 --- /dev/null +++ b/src/test/data/txcreatedata1.hex @@ -0,0 +1 @@ +01000000011f5c38dfcf6f1a5f5a87c416076d392c87e6d41970d5ad5e477a02d66bde97580000000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d71700000000526a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e00000000 diff --git a/src/test/data/txcreatedata2.hex b/src/test/data/txcreatedata2.hex new file mode 100644 index 0000000000..3c7644c297 --- /dev/null +++ b/src/test/data/txcreatedata2.hex @@ -0,0 +1 @@ +01000000011f5c38dfcf6f1a5f5a87c416076d392c87e6d41970d5ad5e477a02d66bde97580000000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0000000000000000526a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e00000000 diff --git a/src/test/limitedmap_tests.cpp b/src/test/limitedmap_tests.cpp new file mode 100644 index 0000000000..faaddffad8 --- /dev/null +++ b/src/test/limitedmap_tests.cpp @@ -0,0 +1,101 @@ +// Copyright (c) 2012-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "limitedmap.h" + +#include "test/test_bitcoin.h" + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(limitedmap_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(limitedmap_test) +{ + // create a limitedmap capped at 10 items + limitedmap<int, int> map(10); + + // check that the max size is 10 + BOOST_CHECK(map.max_size() == 10); + + // check that it's empty + BOOST_CHECK(map.size() == 0); + + // insert (-1, -1) + map.insert(std::pair<int, int>(-1, -1)); + + // make sure that the size is updated + BOOST_CHECK(map.size() == 1); + + // make sure that the new items is in the map + BOOST_CHECK(map.count(-1) == 1); + + // insert 10 new items + for (int i = 0; i < 10; i++) { + map.insert(std::pair<int, int>(i, i + 1)); + } + + // make sure that the map now contains 10 items... + BOOST_CHECK(map.size() == 10); + + // ...and that the first item has been discarded + BOOST_CHECK(map.count(-1) == 0); + + // iterate over the map, both with an index and an iterator + limitedmap<int, int>::const_iterator it = map.begin(); + for (int i = 0; i < 10; i++) { + // make sure the item is present + BOOST_CHECK(map.count(i) == 1); + + // use the iterator to check for the expected key adn value + BOOST_CHECK(it->first == i); + BOOST_CHECK(it->second == i + 1); + + // use find to check for the value + BOOST_CHECK(map.find(i)->second == i + 1); + + // update and recheck + map.update(it, i + 2); + BOOST_CHECK(map.find(i)->second == i + 2); + + it++; + } + + // check that we've exhausted the iterator + BOOST_CHECK(it == map.end()); + + // resize the map to 5 items + map.max_size(5); + + // check that the max size and size are now 5 + BOOST_CHECK(map.max_size() == 5); + BOOST_CHECK(map.size() == 5); + + // check that items less than 5 have been discarded + // and items greater than 5 are retained + for (int i = 0; i < 10; i++) { + if (i < 5) { + BOOST_CHECK(map.count(i) == 0); + } else { + BOOST_CHECK(map.count(i) == 1); + } + } + + // erase some items not in the map + for (int i = 100; i < 1000; i += 100) { + map.erase(i); + } + + // check that the size is unaffected + BOOST_CHECK(map.size() == 5); + + // erase the remaining elements + for (int i = 5; i < 10; i++) { + map.erase(i); + } + + // check that the map is now empty + BOOST_CHECK(map.empty()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 7154476c7c..3f99dc98d9 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -7,6 +7,7 @@ #include <string> +#include <boost/assign/list_of.hpp> #include <boost/test/unit_test.hpp> using namespace std; @@ -156,4 +157,20 @@ BOOST_AUTO_TEST_CASE(subnet_test) BOOST_CHECK(CSubNet(CNetAddr("1:2:3:4:5:6:7:8")).ToString() == "1:2:3:4:5:6:7:8/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); } +BOOST_AUTO_TEST_CASE(netbase_getgroup) +{ + BOOST_CHECK(CNetAddr("127.0.0.1").GetGroup() == boost::assign::list_of(0)); // Local -> !Routable() + BOOST_CHECK(CNetAddr("257.0.0.1").GetGroup() == boost::assign::list_of(0)); // !Valid -> !Routable() + BOOST_CHECK(CNetAddr("10.0.0.1").GetGroup() == boost::assign::list_of(0)); // RFC1918 -> !Routable() + BOOST_CHECK(CNetAddr("169.254.1.1").GetGroup() == boost::assign::list_of(0)); // RFC3927 -> !Routable() + BOOST_CHECK(CNetAddr("1.2.3.4").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // IPv4 + BOOST_CHECK(CNetAddr("::FFFF:0:102:304").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC6145 + BOOST_CHECK(CNetAddr("64:FF9B::102:304").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC6052 + BOOST_CHECK(CNetAddr("2002:102:304:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC3964 + BOOST_CHECK(CNetAddr("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC4380 + BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == boost::assign::list_of((unsigned char)NET_TOR)(239)); // Tor + BOOST_CHECK(CNetAddr("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(4)(112)(175)); //he.net + BOOST_CHECK(CNetAddr("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(32)(1)); //IPv6 +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/reverselock_tests.cpp b/src/test/reverselock_tests.cpp new file mode 100644 index 0000000000..e7e627ae0f --- /dev/null +++ b/src/test/reverselock_tests.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "reverselock.h" +#include "test/test_bitcoin.h" + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(reverselock_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(reverselock_basics) +{ + boost::mutex mutex; + boost::unique_lock<boost::mutex> lock(mutex); + + BOOST_CHECK(lock.owns_lock()); + { + reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock); + BOOST_CHECK(!lock.owns_lock()); + } + BOOST_CHECK(lock.owns_lock()); +} + +BOOST_AUTO_TEST_CASE(reverselock_errors) +{ + boost::mutex mutex; + boost::unique_lock<boost::mutex> lock(mutex); + + // Make sure trying to reverse lock an unlocked lock fails + lock.unlock(); + + BOOST_CHECK(!lock.owns_lock()); + + bool failed = false; + try { + reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock); + } catch(...) { + failed = true; + } + + BOOST_CHECK(failed); + BOOST_CHECK(!lock.owns_lock()); + + // Make sure trying to lock a lock after it has been reverse locked fails + failed = false; + bool locked = false; + + lock.lock(); + BOOST_CHECK(lock.owns_lock()); + + try { + reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock); + lock.lock(); + locked = true; + } catch(...) { + failed = true; + } + + BOOST_CHECK(locked && failed); + BOOST_CHECK(lock.owns_lock()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 7946b02855..c0476db99b 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -110,6 +110,24 @@ BOOST_AUTO_TEST_CASE(rpc_rawsign) BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true); } +BOOST_AUTO_TEST_CASE(rpc_createraw_op_return) +{ + BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\"}")); + + // Allow more than one data transaction output + BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\",\"data\":\"68656c6c6f776f726c64\"}")); + + // Key not "data" (bad address) + BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"somedata\":\"68656c6c6f776f726c64\"}"), runtime_error); + + // Bad hex encoding of data output + BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"12345\"}"), runtime_error); + BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"12345g\"}"), runtime_error); + + // Data 81 bytes long + BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081\"}")); +} + BOOST_AUTO_TEST_CASE(rpc_format_monetary_values) { BOOST_CHECK(ValueFromAmount(0LL).write() == "0.00000000"); @@ -206,21 +224,6 @@ BOOST_AUTO_TEST_CASE(json_parse_errors) BOOST_CHECK_THROW(ParseNonRFCJSONValue("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNL"), std::runtime_error); } -BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr) -{ - // Check IPv4 addresses - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4"); - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1"); - // Check IPv6 addresses - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1"); - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(), - "123:4567:89ab:cdef:123:4567:89ab:cdef"); - // v4 compatible must be interpreted as IPv4 - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1"); - // v4 mapped must be interpreted as IPv4 - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1"); -} - BOOST_AUTO_TEST_CASE(rpc_ban) { BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index 9368963ff2..52f41be8ae 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -27,8 +27,6 @@ BOOST_FIXTURE_TEST_SUITE(rpc_wallet_tests, TestingSetup) BOOST_AUTO_TEST_CASE(rpc_addmultisig) { - LOCK(pwalletMain->cs_wallet); - rpcfn_type addmultisig = tableRPC["addmultisigaddress"]->actor; // old, 65-byte-long: @@ -68,25 +66,28 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) { // Test RPC calls for various wallet statistics UniValue r; - - LOCK2(cs_main, pwalletMain->cs_wallet); - - CPubKey demoPubkey = pwalletMain->GenerateNewKey(); - CBitcoinAddress demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID())); + CPubKey demoPubkey; + CBitcoinAddress demoAddress; UniValue retValue; string strAccount = "walletDemoAccount"; - string strPurpose = "receive"; - BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */ - CWalletDB walletdb(pwalletMain->strWalletFile); - CAccount account; - account.vchPubKey = demoPubkey; - pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, strPurpose); - walletdb.WriteAccount(strAccount, account); - }); - - CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(); - CBitcoinAddress setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID())); - + CBitcoinAddress setaccountDemoAddress; + { + LOCK(pwalletMain->cs_wallet); + + demoPubkey = pwalletMain->GenerateNewKey(); + demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID())); + string strPurpose = "receive"; + BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */ + CWalletDB walletdb(pwalletMain->strWalletFile); + CAccount account; + account.vchPubKey = demoPubkey; + pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, strPurpose); + walletdb.WriteAccount(strAccount, account); + }); + + CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(); + setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID())); + } /********************************* * setaccount *********************************/ diff --git a/src/test/univalue_tests.cpp b/src/test/univalue_tests.cpp index 67cb9b9623..ee31c0955b 100644 --- a/src/test/univalue_tests.cpp +++ b/src/test/univalue_tests.cpp @@ -314,6 +314,21 @@ BOOST_AUTO_TEST_CASE(univalue_readwrite) BOOST_CHECK(obj["key3"].isObject()); BOOST_CHECK_EQUAL(strJson1, v.write()); + + /* Check for (correctly reporting) a parsing error if the initial + JSON construct is followed by more stuff. Note that whitespace + is, of course, exempt. */ + + BOOST_CHECK(v.read(" {}\n ")); + BOOST_CHECK(v.isObject()); + BOOST_CHECK(v.read(" []\n ")); + BOOST_CHECK(v.isArray()); + + BOOST_CHECK(!v.read("@{}")); + BOOST_CHECK(!v.read("{} garbage")); + BOOST_CHECK(!v.read("[]{}")); + BOOST_CHECK(!v.read("{}[]")); + BOOST_CHECK(!v.read("{} 42")); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/timedata.cpp b/src/timedata.cpp index c3e9c75f6e..a14d69c116 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -40,16 +40,20 @@ static int64_t abs64(int64_t n) return (n >= 0 ? n : -n); } +#define BITCOIN_TIMEDATA_MAX_SAMPLES 200 + void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) { LOCK(cs_nTimeOffset); // Ignore duplicates static set<CNetAddr> setKnown; + if (setKnown.size() == BITCOIN_TIMEDATA_MAX_SAMPLES) + return; if (!setKnown.insert(ip).second) return; // Add data - static CMedianFilter<int64_t> vTimeOffsets(200,0); + static CMedianFilter<int64_t> vTimeOffsets(BITCOIN_TIMEDATA_MAX_SAMPLES, 0); vTimeOffsets.input(nOffsetSample); LogPrintf("Added time data, samples %d, offset %+d (%+d minutes)\n", vTimeOffsets.size(), nOffsetSample, nOffsetSample/60); diff --git a/src/univalue/univalue_read.cpp b/src/univalue/univalue_read.cpp index 261771811d..64591234cb 100644 --- a/src/univalue/univalue_read.cpp +++ b/src/univalue/univalue_read.cpp @@ -244,16 +244,16 @@ bool UniValue::read(const char *raw) bool expectColon = false; vector<UniValue*> stack; + string tokenVal; + unsigned int consumed; enum jtokentype tok = JTOK_NONE; enum jtokentype last_tok = JTOK_NONE; - while (1) { + do { last_tok = tok; - string tokenVal; - unsigned int consumed; tok = getJsonToken(tokenVal, consumed, raw); if (tok == JTOK_NONE || tok == JTOK_ERR) - break; + return false; raw += consumed; switch (tok) { @@ -377,9 +377,11 @@ bool UniValue::read(const char *raw) default: return false; } - } + } while (!stack.empty ()); - if (stack.size() != 0) + /* Check that nothing follows the initial construct (parsed above). */ + tok = getJsonToken(tokenVal, consumed, raw); + if (tok != JTOK_NONE) return false; return true; diff --git a/src/util.cpp b/src/util.cpp index a7ec740de8..f50d25e17a 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -794,6 +794,18 @@ void SetupEnvironment() boost::filesystem::path::imbue(loc); } +bool SetupNetworking() +{ +#ifdef WIN32 + // Initialize Windows Sockets + WSADATA wsadata; + int ret = WSAStartup(MAKEWORD(2,2), &wsadata); + if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2) + return false; +#endif + return true; +} + void SetThreadPriority(int nPriority) { #ifdef WIN32 diff --git a/src/util.h b/src/util.h index afc9a378bb..0b2dc01ac6 100644 --- a/src/util.h +++ b/src/util.h @@ -59,6 +59,7 @@ inline std::string _(const char* psz) } void SetupEnvironment(); +bool SetupNetworking(); /** Return true if log accepts specified category */ bool LogAcceptCategory(const char* category); diff --git a/src/version.h b/src/version.h index 38b3d2e734..6cdddf9255 100644 --- a/src/version.h +++ b/src/version.h @@ -9,7 +9,7 @@ * network protocol versioning */ -static const int PROTOCOL_VERSION = 70002; +static const int PROTOCOL_VERSION = 70011; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; @@ -34,4 +34,7 @@ static const int BIP0031_VERSION = 60000; //! "mempool" command, enhanced "getdata" behavior starts with this version static const int MEMPOOL_GD_VERSION = 60002; +//! "filter*" commands are disabled without NODE_BLOOM after and including this version +static const int NO_BLOOM_VERSION = 70011; + #endif // BITCOIN_VERSION_H diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 0b0fb562e0..c86ad9758e 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -255,7 +255,7 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co { LOCK(cs_KeyStore); if (!IsCrypted()) - return CKeyStore::GetPubKey(address, vchPubKeyOut); + return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); if (mi != mapCryptedKeys.end()) @@ -263,6 +263,8 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co vchPubKeyOut = (*mi).second.first; return true; } + // Check for watch-only pubkeys + return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); } return false; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index dbe36a2be1..8d557979c0 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -149,46 +149,124 @@ UniValue importprivkey(const UniValue& params, bool fHelp) return NullUniValue; } +void ImportAddress(const CBitcoinAddress& address, const string& strLabel); +void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript) +{ + if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + + pwalletMain->MarkDirty(); + + if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script)) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + + if (isRedeemScript) { + if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script)) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); + ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel); + } +} + +void ImportAddress(const CBitcoinAddress& address, const string& strLabel) +{ + CScript script = GetScriptForDestination(address.Get()); + ImportScript(script, strLabel, false); + // add to address book or update label + if (address.IsValid()) + pwalletMain->SetAddressBook(address.Get(), strLabel, "receive"); +} + UniValue importaddress(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() < 1 || params.size() > 3) + if (fHelp || params.size() < 1 || params.size() > 4) throw runtime_error( - "importaddress \"address\" ( \"label\" rescan )\n" - "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n" + "importaddress \"address\" ( \"label\" rescan p2sh )\n" + "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n" "\nArguments:\n" - "1. \"address\" (string, required) The address\n" + "1. \"script\" (string, required) The hex-encoded script (or address)\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" + "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n" "\nNote: This call can take minutes to complete if rescan is true.\n" + "If you have the full public key, you should call importpublickey instead of this.\n" "\nExamples:\n" - "\nImport an address with rescan\n" - + HelpExampleCli("importaddress", "\"myaddress\"") + + "\nImport a script with rescan\n" + + HelpExampleCli("importaddress", "\"myscript\"") + "\nImport using a label without rescan\n" - + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + + + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") + "\nAs a JSON-RPC call\n" - + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") + + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false") ); if (fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Importing addresses is disabled in pruned mode"); - LOCK2(cs_main, pwalletMain->cs_wallet); + string strLabel = ""; + if (params.size() > 1) + strLabel = params[1].get_str(); + + // Whether to perform rescan after import + bool fRescan = true; + if (params.size() > 2) + fRescan = params[2].get_bool(); - CScript script; + // Whether to import a p2sh version, too + bool fP2SH = false; + if (params.size() > 3) + fP2SH = params[3].get_bool(); + + LOCK2(cs_main, pwalletMain->cs_wallet); CBitcoinAddress address(params[0].get_str()); if (address.IsValid()) { - script = GetScriptForDestination(address.Get()); + if (fP2SH) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); + ImportAddress(address, strLabel); } else if (IsHex(params[0].get_str())) { std::vector<unsigned char> data(ParseHex(params[0].get_str())); - script = CScript(data.begin(), data.end()); + ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } + if (fRescan) + { + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); + pwalletMain->ReacceptWalletTransactions(); + } + + return NullUniValue; +} + +UniValue importpubkey(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() < 1 || params.size() > 4) + throw runtime_error( + "importpubkey \"pubkey\" ( \"label\" rescan )\n" + "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n" + "\nArguments:\n" + "1. \"pubkey\" (string, required) The hex-encoded public key\n" + "2. \"label\" (string, optional, default=\"\") An optional label\n" + "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" + "\nNote: This call can take minutes to complete if rescan is true.\n" + "\nExamples:\n" + "\nImport a public key with rescan\n" + + HelpExampleCli("importpubkey", "\"mypubkey\"") + + "\nImport using a label without rescan\n" + + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") + ); + + if (fPruneMode) + throw JSONRPCError(RPC_WALLET_ERROR, "Importing public keys is disabled in pruned mode"); + string strLabel = ""; if (params.size() > 1) strLabel = params[1].get_str(); @@ -198,33 +276,28 @@ UniValue importaddress(const UniValue& params, bool fHelp) if (params.size() > 2) fRescan = params[2].get_bool(); - { - if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); - - // add to address book or update label - if (address.IsValid()) - pwalletMain->SetAddressBook(address.Get(), strLabel, "receive"); - - // Don't throw error in case an address is already there - if (pwalletMain->HaveWatchOnly(script)) - return NullUniValue; + if (!IsHex(params[0].get_str())) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); + std::vector<unsigned char> data(ParseHex(params[0].get_str())); + CPubKey pubKey(data.begin(), data.end()); + if (!pubKey.IsFullyValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); - pwalletMain->MarkDirty(); + LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->AddWatchOnly(script)) - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel); + ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false); - if (fRescan) - { - pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); - pwalletMain->ReacceptWalletTransactions(); - } + if (fRescan) + { + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); + pwalletMain->ReacceptWalletTransactions(); } return NullUniValue; } + UniValue importwallet(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 198b5baf60..5d182f3d42 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -797,7 +797,7 @@ UniValue movecmd(const UniValue& params, bool fHelp) "4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n" "5. \"comment\" (string, optional) An optional comment, stored in the wallet only.\n" "\nResult:\n" - "true|false (boolean) true if successfull.\n" + "true|false (boolean) true if successful.\n" "\nExamples:\n" "\nMove 0.01 " + CURRENCY_UNIT + " from the default account to the account named tabby\n" + HelpExampleCli("move", "\"\" \"tabby\" 0.01") + @@ -2368,15 +2368,20 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp) if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() != 1) + if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "fundrawtransaction \"hexstring\"\n" + "fundrawtransaction \"hexstring\" includeWatching\n" "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" "This will not modify existing inputs, and will add one change output to the outputs.\n" "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" "The inputs added will not be signed, use signrawtransaction for that.\n" + "Note that all existing inputs must have their previous output transaction be in the wallet.\n" + "Note that all inputs selected must be of standard form and P2SH scripts must be" + "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" + "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" "\nArguments:\n" - "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "2. includeWatching (boolean, optional, default false) Also select inputs which are watch only\n" "\nResult:\n" "{\n" " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" @@ -2395,18 +2400,22 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp) + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") ); - RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)); // parse hex string from parameter CTransaction origTx; if (!DecodeHexTx(origTx, params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + bool includeWatching = false; + if (params.size() > 1) + includeWatching = true; + CMutableTransaction tx(origTx); CAmount nFee; string strFailReason; int nChangePos = -1; - if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason)) + if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason, includeWatching)) throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); UniValue result(UniValue::VOBJ); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index dcc2983139..c3b1172201 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -114,6 +114,9 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) script = GetScriptForDestination(pubkey.GetID()); if (HaveWatchOnly(script)) RemoveWatchOnly(script); + script = GetScriptForRawPubKey(pubkey); + if (HaveWatchOnly(script)) + RemoveWatchOnly(script); if (!fFileBacked) return true; @@ -1527,7 +1530,9 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) && (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i))) - vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO)); + vCoins.push_back(COutput(pcoin, i, nDepth, + ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || + (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO))); } } } @@ -1743,7 +1748,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx* return res; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching) { vector<CRecipient> vecSend; @@ -1756,6 +1761,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC CCoinControl coinControl; coinControl.fAllowOtherInputs = true; + coinControl.fAllowWatchOnly = includeWatching; BOOST_FOREACH(const CTxIn& txin, tx.vin) coinControl.Select(txin.prevout); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index ae007e4673..bd30b67b09 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -627,7 +627,7 @@ public: CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; - bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason); + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching); bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp index 5482348e35..d27b1531e3 100644 --- a/src/wallet/wallet_ismine.cpp +++ b/src/wallet/wallet_ismine.cpp @@ -9,6 +9,7 @@ #include "keystore.h" #include "script/script.h" #include "script/standard.h" +#include "script/sign.h" #include <boost/foreach.hpp> @@ -40,7 +41,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) txnouttype whichType; if (!Solver(scriptPubKey, whichType, vSolutions)) { if (keystore.HaveWatchOnly(scriptPubKey)) - return ISMINE_WATCH_ONLY; + return ISMINE_WATCH_UNSOLVABLE; return ISMINE_NO; } @@ -85,7 +86,10 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) } } - if (keystore.HaveWatchOnly(scriptPubKey)) - return ISMINE_WATCH_ONLY; + if (keystore.HaveWatchOnly(scriptPubKey)) { + // TODO: This could be optimized some by doing some work after the above solver + CScript scriptSig; + return ProduceSignature(DummySignatureCreator(&keystore), scriptPubKey, scriptSig) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE; + } return ISMINE_NO; } diff --git a/src/wallet/wallet_ismine.h b/src/wallet/wallet_ismine.h index 7846565f8d..9f45f76c6b 100644 --- a/src/wallet/wallet_ismine.h +++ b/src/wallet/wallet_ismine.h @@ -17,8 +17,12 @@ class CScript; enum isminetype { ISMINE_NO = 0, - ISMINE_WATCH_ONLY = 1, - ISMINE_SPENDABLE = 2, + //! Indicates that we dont know how to create a scriptSig that would solve this if we were given the appropriate private keys + ISMINE_WATCH_UNSOLVABLE = 1, + //! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys + ISMINE_WATCH_SOLVABLE = 2, + ISMINE_WATCH_ONLY = ISMINE_WATCH_SOLVABLE | ISMINE_WATCH_UNSOLVABLE, + ISMINE_SPENDABLE = 4, ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE }; /** used for bitflags of isminetype */ |