diff options
-rw-r--r-- | src/Makefile.test.include | 7 | ||||
-rw-r--r-- | src/bitcoin-cli.cpp | 16 | ||||
-rw-r--r-- | src/bitcoind.cpp | 7 | ||||
-rw-r--r-- | src/httprpc.cpp | 5 | ||||
-rw-r--r-- | src/httpserver.cpp | 11 | ||||
-rw-r--r-- | src/httpserver.h | 2 | ||||
-rw-r--r-- | src/init.cpp | 12 | ||||
-rw-r--r-- | src/init.h | 16 | ||||
-rw-r--r-- | src/merkleblock.cpp | 3 | ||||
-rw-r--r-- | src/merkleblock.h | 2 | ||||
-rw-r--r-- | src/policy/fees.cpp | 19 | ||||
-rw-r--r-- | src/qt/bitcoin.cpp | 62 | ||||
-rw-r--r-- | src/qt/clientmodel.cpp | 1 | ||||
-rw-r--r-- | src/random.cpp | 5 | ||||
-rw-r--r-- | src/rpc/client.cpp | 1 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 75 | ||||
-rw-r--r-- | src/rpc/misc.cpp | 1 | ||||
-rw-r--r-- | src/rpc/net.cpp | 2 | ||||
-rw-r--r-- | src/validation.h | 8 | ||||
-rw-r--r-- | src/validationinterface.h | 1 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 25 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 77 | ||||
-rw-r--r-- | src/wallet/wallet.h | 14 | ||||
-rw-r--r-- | src/warnings.h | 7 | ||||
-rwxr-xr-x | test/functional/fundrawtransaction.py | 17 | ||||
-rwxr-xr-x | test/functional/multiwallet.py | 47 | ||||
-rw-r--r-- | test/functional/test_framework/authproxy.py | 3 | ||||
-rw-r--r-- | test/functional/test_framework/coverage.py | 2 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 |
29 files changed, 306 insertions, 143 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 90504ad52d..6415b3d2e3 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -96,12 +96,13 @@ endif test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS) -test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ - $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) -test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_test_bitcoin_LDADD = if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) endif +test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ + $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) +test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_test_bitcoin_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 8b48c7f8e4..c2bc15584f 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -46,6 +46,7 @@ std::string HelpMessageCli() strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases)")); + strUsage += HelpMessageOpt("-usewallet=<walletname>", _("Send RPC for non-default wallet on RPC server (argument is wallet filename in bitcoind directory, required if bitcoind/-Qt runs with multiple wallets)")); return strUsage; } @@ -241,7 +242,20 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params) assert(output_buffer); evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); - int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); + // check if we should use a special wallet endpoint + std::string endpoint = "/"; + std::string walletName = GetArg("-usewallet", ""); + if (!walletName.empty()) { + char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false); + if (encodedURI) { + endpoint = "/wallet/"+ std::string(encodedURI); + free(encodedURI); + } + else { + throw CConnectionFailed("uri-encode failed"); + } + } + int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str()); req.release(); // ownership moved to evcon in above call if (r != 0) { throw CConnectionFailed("send http request failed"); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 374678310c..f3844e9d47 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -159,7 +159,12 @@ bool AppInit(int argc, char* argv[]) return false; #endif // HAVE_DECL_DAEMON } - + // Lock data directory after daemonization + if (!AppInitLockDataDirectory()) + { + // If locking the data directory failed, exit immediately + exit(EXIT_FAILURE); + } fRet = AppInitMain(threadGroup, scheduler); } catch (const std::exception& e) { diff --git a/src/httprpc.cpp b/src/httprpc.cpp index a207d5ece4..69c3e3f49f 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -233,7 +233,10 @@ bool StartHTTPRPC() return false; RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); - +#ifdef ENABLE_WALLET + // ifdef can be removed once we switch to better endpoint support and API versioning + RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC); +#endif assert(EventBase()); httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); RPCSetTimerInterface(httpRPCTimerInterface); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 290a2efca2..ba01255400 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -666,3 +666,14 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) } } +std::string urlDecode(const std::string &urlEncoded) { + std::string res; + if (!urlEncoded.empty()) { + char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, NULL); + if (decoded) { + res = std::string(decoded); + free(decoded); + } + } + return res; +} diff --git a/src/httpserver.h b/src/httpserver.h index 9df56e5fc5..3e434bf0a0 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -148,4 +148,6 @@ private: struct event* ev; }; +std::string urlDecode(const std::string &urlEncoded); + #endif // BITCOIN_HTTPSERVER_H diff --git a/src/init.cpp b/src/init.cpp index bda0e905b3..f0dbdb47c6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1170,13 +1170,13 @@ bool AppInitSanityChecks() return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME))); // Probe the data directory lock to give an early error message, if possible + // We cannot hold the data directory lock here, as the forking for daemon() hasn't yet happened, + // and a fork will cause weird behavior to it. return LockDataDirectory(true); } -bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) +bool AppInitLockDataDirectory() { - const CChainParams& chainparams = Params(); - // ********************************************************* Step 4a: application initialization // After daemonization get the data directory lock again and hold on to it until exit // This creates a slight window for a race condition to happen, however this condition is harmless: it // will at most make us exit without printing a message to console. @@ -1184,7 +1184,13 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) // Detailed error printed inside LockDataDirectory return false; } + return true; +} +bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) +{ + const CChainParams& chainparams = Params(); + // ********************************************************* Step 4a: application initialization #ifndef WIN32 CreatePidFile(GetPidFile(), getpid()); #endif diff --git a/src/init.h b/src/init.h index 8222794374..a0a824738c 100644 --- a/src/init.h +++ b/src/init.h @@ -27,27 +27,33 @@ void InitLogging(); void InitParameterInteraction(); /** Initialize bitcoin core: Basic context setup. - * @note This can be done before daemonization. + * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read. */ bool AppInitBasicSetup(); /** * Initialization: parameter interaction. - * @note This can be done before daemonization. + * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitBasicSetup should have been called. */ bool AppInitParameterInteraction(); /** * Initialization sanity checks: ecc init, sanity checks, dir lock. - * @note This can be done before daemonization. + * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitParameterInteraction should have been called. */ bool AppInitSanityChecks(); /** - * Bitcoin core main initialization. - * @note This should only be done after daemonization. + * Lock bitcoin core data directory. + * @note This should only be done after daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitSanityChecks should have been called. */ +bool AppInitLockDataDirectory(); +/** + * Bitcoin core main initialization. + * @note This should only be done after daemonization. Call Shutdown() if this function fails. + * @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called. + */ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler); /** The help message mode determines what help message to show */ diff --git a/src/merkleblock.cpp b/src/merkleblock.cpp index ba5f7b400c..f0abea0611 100644 --- a/src/merkleblock.cpp +++ b/src/merkleblock.cpp @@ -59,6 +59,9 @@ CMerkleBlock::CMerkleBlock(const CBlock& block, const std::set<uint256>& txids) } uint256 CPartialMerkleTree::CalcHash(int height, unsigned int pos, const std::vector<uint256> &vTxid) { + //we can never have zero txs in a merkle block, we always need the coinbase tx + //if we do not have this assert, we can hit a memory access violation when indexing into vTxid + assert(vTxid.size() != 0); if (height == 0) { // hash at height 0 is the txids themself return vTxid[pos]; diff --git a/src/merkleblock.h b/src/merkleblock.h index de4c5c8d29..f590c487de 100644 --- a/src/merkleblock.h +++ b/src/merkleblock.h @@ -121,6 +121,8 @@ public: /** * Used to relay blocks as header + vector<merkle branch> * to filtered nodes. + * + * NOTE: The class assumes that the given CBlock has *at least* 1 transaction. If the CBlock has 0 txs, it will hit an assertion. */ class CMerkleBlock { diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 0f186fa845..73cc0b4a5e 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -839,20 +839,20 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation EstimationResult tempResult; // Return failure if trying to analyze a target we're not tracking - if (confTarget <= 0 || (unsigned int)confTarget > longStats->GetMaxConfirms()) - return CFeeRate(0); + if (confTarget <= 0 || (unsigned int)confTarget > longStats->GetMaxConfirms()) { + return CFeeRate(0); // error conditon + } // It's not possible to get reasonable estimates for confTarget of 1 - if (confTarget == 1) - confTarget = 2; + if (confTarget == 1) confTarget = 2; unsigned int maxUsableEstimate = MaxUsableEstimate(); - if (maxUsableEstimate <= 1) - return CFeeRate(0); - if ((unsigned int)confTarget > maxUsableEstimate) { confTarget = maxUsableEstimate; } + if (feeCalc) feeCalc->returnedTarget = confTarget; + + if (confTarget <= 1) return CFeeRate(0); // error conditon assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints /** true is passed to estimateCombined fee for target/2 and target so @@ -899,10 +899,7 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation } } - if (feeCalc) feeCalc->returnedTarget = confTarget; - - if (median < 0) - return CFeeRate(0); + if (median < 0) return CFeeRate(0); // error conditon return CFeeRate(median); } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 8a745cadce..4a4116c670 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -178,6 +178,10 @@ class BitcoinCore: public QObject Q_OBJECT public: explicit BitcoinCore(); + /** Basic initialization, before starting initialization/shutdown thread. + * Return true on success. + */ + static bool baseInitialize(); public Q_SLOTS: void initialize(); @@ -270,26 +274,32 @@ void BitcoinCore::handleRunawayException(const std::exception *e) Q_EMIT runawayException(QString::fromStdString(GetWarnings("gui"))); } +bool BitcoinCore::baseInitialize() +{ + if (!AppInitBasicSetup()) + { + return false; + } + if (!AppInitParameterInteraction()) + { + return false; + } + if (!AppInitSanityChecks()) + { + return false; + } + if (!AppInitLockDataDirectory()) + { + return false; + } + return true; +} + void BitcoinCore::initialize() { try { qDebug() << __func__ << ": Running initialization in thread"; - if (!AppInitBasicSetup()) - { - Q_EMIT initializeResult(false); - return; - } - if (!AppInitParameterInteraction()) - { - Q_EMIT initializeResult(false); - return; - } - if (!AppInitSanityChecks()) - { - Q_EMIT initializeResult(false); - return; - } bool rv = AppInitMain(threadGroup, scheduler); Q_EMIT initializeResult(rv); } catch (const std::exception& e) { @@ -689,16 +699,26 @@ int main(int argc, char *argv[]) if (GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); + int rv = EXIT_SUCCESS; try { app.createWindow(networkStyle.data()); - app.requestInitialize(); + // Perform base initialization before spinning up initialization/shutdown thread + // This is acceptable because this function only contains steps that are quick to execute, + // so the GUI thread won't be held up. + if (BitcoinCore::baseInitialize()) { + app.requestInitialize(); #if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 - WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); + WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); #endif - app.exec(); - app.requestShutdown(); - app.exec(); + app.exec(); + app.requestShutdown(); + app.exec(); + rv = app.getReturnValue(); + } else { + // A dialog with detailed error will have been shown by InitError() + rv = EXIT_FAILURE; + } } catch (const std::exception& e) { PrintExceptionContinue(&e, "Runaway exception"); app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); @@ -706,6 +726,6 @@ int main(int argc, char *argv[]) PrintExceptionContinue(NULL, "Runaway exception"); app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); } - return app.getReturnValue(); + return rv; } #endif // BITCOIN_QT_TEST diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 33f4535ee2..52ce11cefd 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -18,6 +18,7 @@ #include "txmempool.h" #include "ui_interface.h" #include "util.h" +#include "warnings.h" #include <stdint.h> diff --git a/src/random.cpp b/src/random.cpp index 1ee6158e4b..b308e8f4a1 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -186,6 +186,7 @@ void GetDevURandom(unsigned char *ent32) do { ssize_t n = read(f, ent32 + have, NUM_OS_RANDOM_BYTES - have); if (n <= 0 || n + have > NUM_OS_RANDOM_BYTES) { + close(f); RandFailure(); } have += n; @@ -226,10 +227,12 @@ void GetOSRand(unsigned char *ent32) RandFailure(); } } -#elif defined(HAVE_GETENTROPY) +#elif defined(HAVE_GETENTROPY) && defined(__OpenBSD__) /* On OpenBSD this can return up to 256 bytes of entropy, will return an * error if more are requested. * The call cannot return less than the requested number of bytes. + getentropy is explicitly limited to openbsd here, as a similar (but not + the same) function may exist on other platforms via glibc. */ if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) { RandFailure(); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 775ad4b6c9..1ab1956124 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -114,7 +114,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getrawmempool", 0, "verbose" }, { "estimatefee", 0, "nblocks" }, { "estimatesmartfee", 0, "nblocks" }, - { "estimatesmartfee", 1, "conservative" }, { "estimaterawfee", 0, "nblocks" }, { "estimaterawfee", 1, "threshold" }, { "prioritisetransaction", 1, "dummy" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index b8c94d32ec..daca32d251 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -24,6 +24,7 @@ #include "util.h" #include "utilstrencodings.h" #include "validationinterface.h" +#include "warnings.h" #include <memory> #include <stdint.h> @@ -806,42 +807,59 @@ UniValue estimatesmartfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "estimatesmartfee nblocks (conservative)\n" + "estimatesmartfee conf_target (\"estimate_mode\")\n" "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within nblocks blocks if possible and return the number of blocks\n" + "confirmation within conf_target blocks if possible and return the number of blocks\n" "for which the estimate is valid. Uses virtual transaction size as defined\n" "in BIP 141 (witness data is discounted).\n" "\nArguments:\n" - "1. nblocks (numeric)\n" - "2. conservative (bool, optional, default=true) Whether to return a more conservative estimate which\n" - " also satisfies a longer history. A conservative estimate potentially returns a higher\n" - " feerate and is more likely to be sufficient for the desired target, but is not as\n" - " responsive to short term drops in the prevailing fee market\n" + "1. conf_target (numeric) Confirmation target in blocks (1 - 1008)\n" + "2. \"estimate_mode\" (string, optional, default=CONSERVATIVE) The fee estimate mode.\n" + " Whether to return a more conservative estimate which also satisfies\n" + " a longer history. A conservative estimate potentially returns a\n" + " higher feerate and is more likely to be sufficient for the desired\n" + " target, but is not as responsive to short term drops in the\n" + " prevailing fee market. Must be one of:\n" + " \"UNSET\" (defaults to CONSERVATIVE)\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" "\nResult:\n" "{\n" - " \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in BTC)\n" + " \"feerate\" : x.x, (numeric, optional) estimate fee-per-kilobyte (in BTC)\n" + " \"errors\": [ str... ] (json array of strings, optional) Errors encountered during processing\n" " \"blocks\" : n (numeric) block number where estimate was found\n" "}\n" "\n" - "A negative value is returned if not enough transactions and blocks\n" + "The request target will be clamped between 2 and the highest target\n" + "fee estimation is able to return based on how long it has been running.\n" + "An error is returned if not enough transactions and blocks\n" "have been observed to make an estimate for any number of blocks.\n" "\nExample:\n" + HelpExampleCli("estimatesmartfee", "6") ); - RPCTypeCheck(request.params, {UniValue::VNUM}); - - int nBlocks = request.params[0].get_int(); + RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); + RPCTypeCheckArgument(request.params[0], UniValue::VNUM); + unsigned int conf_target = ParseConfirmTarget(request.params[0]); bool conservative = true; if (request.params.size() > 1 && !request.params[1].isNull()) { - RPCTypeCheckArgument(request.params[1], UniValue::VBOOL); - conservative = request.params[1].get_bool(); + FeeEstimateMode fee_mode; + if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false; } UniValue result(UniValue::VOBJ); + UniValue errors(UniValue::VARR); FeeCalculation feeCalc; - CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &feeCalc, conservative); - result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); + CFeeRate feeRate = ::feeEstimator.estimateSmartFee(conf_target, &feeCalc, conservative); + if (feeRate != CFeeRate(0)) { + result.push_back(Pair("feerate", ValueFromAmount(feeRate.GetFeePerK()))); + } else { + errors.push_back("Insufficient data or no feerate found"); + result.push_back(Pair("errors", errors)); + } result.push_back(Pair("blocks", feeCalc.returnedTarget)); return result; } @@ -850,18 +868,18 @@ UniValue estimaterawfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "estimaterawfee nblocks (threshold)\n" + "estimaterawfee conf_target (threshold)\n" "\nWARNING: This interface is unstable and may disappear or change!\n" "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" " implementation of fee estimation. The parameters it can be called with\n" " and the results it returns will change if the internal implementation changes.\n" "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within nblocks blocks if possible. Uses virtual transaction size as defined\n" - "in BIP 141 (witness data is discounted).\n" + "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" + "defined in BIP 141 (witness data is discounted).\n" "\nArguments:\n" - "1. nblocks (numeric) Confirmation target in blocks (1 - 1008)\n" + "1. conf_target (numeric) Confirmation target in blocks (1 - 1008)\n" "2. threshold (numeric, optional) The proportion of transactions in a given feerate range that must have been\n" - " confirmed within nblocks in order to consider those feerates as high enough and proceed to check\n" + " confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" " lower buckets. Default: 0.95\n" "\nResult:\n" "{\n" @@ -889,12 +907,9 @@ UniValue estimaterawfee(const JSONRPCRequest& request) + HelpExampleCli("estimaterawfee", "6 0.9") ); - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM, UniValue::VNUM}, true); + RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); - int nBlocks = request.params[0].get_int(); - if (nBlocks < 1 || (unsigned int)nBlocks > ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid nblocks"); - } + unsigned int conf_target = ParseConfirmTarget(request.params[0]); double threshold = 0.95; if (!request.params[1].isNull()) { threshold = request.params[1].get_real(); @@ -910,9 +925,9 @@ UniValue estimaterawfee(const JSONRPCRequest& request) EstimationResult buckets; // Only output results for horizons which track the target - if ((unsigned int)nBlocks > ::feeEstimator.HighestTargetTracked(horizon)) continue; + if (conf_target > ::feeEstimator.HighestTargetTracked(horizon)) continue; - feeRate = ::feeEstimator.estimateRawFee(nBlocks, threshold, horizon, &buckets); + feeRate = ::feeEstimator.estimateRawFee(conf_target, threshold, horizon, &buckets); UniValue horizon_result(UniValue::VOBJ); UniValue errors(UniValue::VARR); UniValue passbucket(UniValue::VOBJ); @@ -963,9 +978,9 @@ static const CRPCCommand commands[] = { "generating", "generatetoaddress", &generatetoaddress, true, {"nblocks","address","maxtries"} }, { "util", "estimatefee", &estimatefee, true, {"nblocks"} }, - { "util", "estimatesmartfee", &estimatesmartfee, true, {"nblocks", "conservative"} }, + { "util", "estimatesmartfee", &estimatesmartfee, true, {"conf_target", "estimate_mode"} }, - { "hidden", "estimaterawfee", &estimaterawfee, true, {"nblocks", "threshold"} }, + { "hidden", "estimaterawfee", &estimaterawfee, true, {"conf_target", "threshold"} }, }; void RegisterMiningRPCCommands(CRPCTable &t) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index fcbbe1ceed..3c4f071fcf 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -21,6 +21,7 @@ #include "wallet/wallet.h" #include "wallet/walletdb.h" #endif +#include "warnings.h" #include <stdint.h> #ifdef HAVE_MALLOC_INFO diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index ed452fcb02..090d9e448e 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -18,7 +18,7 @@ #include "util.h" #include "utilstrencodings.h" #include "version.h" - +#include "warnings.h" #include <univalue.h> diff --git a/src/validation.h b/src/validation.h index a9f995abb8..95c8e5b93e 100644 --- a/src/validation.h +++ b/src/validation.h @@ -268,14 +268,6 @@ void UnloadBlockIndex(); void ThreadScriptCheck(); /** Check whether we are doing an initial block download (synchronizing from disk or network) */ bool IsInitialBlockDownload(); -/** Format a string that describes several potential problems detected by the core. - * strFor can have three values: - * - "rpc": get critical warnings, which should put the client in safe mode if non-empty - * - "statusbar": get all warnings - * - "gui": get all warnings, translated (where possible) for GUI - * This function only returns the highest priority warning of the set selected by strFor. - */ -std::string GetWarnings(const std::string& strFor); /** Retrieve a transaction (from memory pool, or from disk, if possible) */ bool GetTransaction(const uint256 &hash, CTransactionRef &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false); /** Find the best known block, and make it the tip of the block chain */ diff --git a/src/validationinterface.h b/src/validationinterface.h index 568da66df2..d6da2bc1fd 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -86,7 +86,6 @@ public: void TransactionAddedToMempool(const CTransactionRef &); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef> &); void BlockDisconnected(const std::shared_ptr<const CBlock> &); - void UpdatedTransaction(const uint256 &); void SetBestChain(const CBlockLocator &); void Inventory(const uint256 &); void Broadcast(int64_t nBestBlockTime, CConnman* connman); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ee8c7548fc..f63ce1bb48 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -9,6 +9,7 @@ #include "consensus/validation.h" #include "core_io.h" #include "init.h" +#include "httpserver.h" #include "validation.h" #include "net.h" #include "policy/feerate.h" @@ -30,10 +31,21 @@ #include <univalue.h> +static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; + CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) { - // TODO: Some way to access secondary wallets - return vpwallets.empty() ? nullptr : vpwallets[0]; + if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { + // wallet endpoint was used + std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); + for (CWalletRef pwallet : ::vpwallets) { + if (pwallet->GetName() == requestedWallet) { + return pwallet; + } + } + throw JSONRPCError(RPC_INVALID_PARAMETER, "Requested wallet does not exist or is not loaded"); + } + return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[0] : nullptr; } std::string HelpRequiringPassphrase(CWallet * const pwallet) @@ -2693,7 +2705,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " \"changePosition\" (numeric, optional, default random) The index of the change output\n" " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" - " \"reserveChangeKey\" (boolean, optional, default true) Reserves the change output key from the keypool\n" " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n" " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" " The fee will be equally deducted from the amount of each specified output.\n" @@ -2732,7 +2743,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) CCoinControl coinControl; int changePosition = -1; bool lockUnspents = false; - bool reserveChangeKey = true; UniValue subtractFeeFromOutputs; std::set<int> setSubtractFeeFromOutputs; @@ -2752,7 +2762,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) {"changePosition", UniValueType(UniValue::VNUM)}, {"includeWatching", UniValueType(UniValue::VBOOL)}, {"lockUnspents", UniValueType(UniValue::VBOOL)}, - {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, + {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so. {"feeRate", UniValueType()}, // will be checked below {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, {"replaceable", UniValueType(UniValue::VBOOL)}, @@ -2779,9 +2789,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) if (options.exists("lockUnspents")) lockUnspents = options["lockUnspents"].get_bool(); - if (options.exists("reserveChangeKey")) - reserveChangeKey = options["reserveChangeKey"].get_bool(); - if (options.exists("feeRate")) { coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"])); @@ -2830,7 +2837,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) CAmount nFeeOut; std::string strFailReason; - if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl, reserveChangeKey)) { + if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e230ee0eb0..7a0aac5f53 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -89,7 +89,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } -CPubKey CWallet::GenerateNewKey(bool internal) +CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal) { AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets @@ -102,14 +102,15 @@ CPubKey CWallet::GenerateNewKey(bool internal) // use HD key derivation if HD was enabled during wallet creation if (IsHDEnabled()) { - DeriveNewChildKey(metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); + DeriveNewChildKey(walletdb, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); } else { secret.MakeNewKey(fCompressed); } // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) + if (fCompressed) { SetMinVersion(FEATURE_COMPRPUBKEY); + } CPubKey pubkey = secret.GetPubKey(); assert(secret.VerifyPubKey(pubkey)); @@ -117,12 +118,13 @@ CPubKey CWallet::GenerateNewKey(bool internal) mapKeyMetadata[pubkey.GetID()] = metadata; UpdateTimeFirstKey(nCreationTime); - if (!AddKeyPubKey(secret, pubkey)) + if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) { throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } return pubkey; } -void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal) +void CWallet::DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata& metadata, CKey& secret, bool internal) { // for now we use a fixed keypath scheme of m/0'/0'/k CKey key; //master key seed (256bit) @@ -164,33 +166,52 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool inter secret = childKey.key; metadata.hdMasterKeyID = hdChain.masterKeyID; // update the chain model in the database - if (!CWalletDB(*dbw).WriteHDChain(hdChain)) + if (!walletdb.WriteHDChain(hdChain)) throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); } -bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +bool CWallet::AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey& secret, const CPubKey &pubkey) { AssertLockHeld(cs_wallet); // mapKeyMetadata - if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) + + // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey + // which is overridden below. To avoid flushes, the database handle is + // tunneled through to it. + bool needsDB = !pwalletdbEncryption; + if (needsDB) { + pwalletdbEncryption = &walletdb; + } + if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) { + if (needsDB) pwalletdbEncryption = NULL; return false; + } + if (needsDB) pwalletdbEncryption = NULL; // check if we need to remove from watch-only CScript script; script = GetScriptForDestination(pubkey.GetID()); - if (HaveWatchOnly(script)) + if (HaveWatchOnly(script)) { RemoveWatchOnly(script); + } script = GetScriptForRawPubKey(pubkey); - if (HaveWatchOnly(script)) + if (HaveWatchOnly(script)) { RemoveWatchOnly(script); + } if (!IsCrypted()) { - return CWalletDB(*dbw).WriteKey(pubkey, + return walletdb.WriteKey(pubkey, secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); } return true; } +bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + CWalletDB walletdb(*dbw); + return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey); +} + bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { @@ -2452,7 +2473,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx) return true; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl, bool keepReserveKey) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) { std::vector<CRecipient> vecSend; @@ -2474,8 +2495,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } - if (nChangePosInOut != -1) + + if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); + // we dont have the normal Create/Commit cycle, and dont want to risk reusing change, + // so just remove the key from the keypool here. + reservekey.KeepKey(); + } // Copy output sizes from new transaction; they may have had the fee subtracted from them for (unsigned int idx = 0; idx < tx.vout.size(); idx++) @@ -2496,9 +2522,6 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC } } - // optionally keep the change output key - if (keepReserveKey) - reservekey.KeepKey(); return true; } @@ -3204,22 +3227,21 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) internal = true; } - if (!setInternalKeyPool.empty()) { - nEnd = *(setInternalKeyPool.rbegin()) + 1; - } - if (!setExternalKeyPool.empty()) { - nEnd = std::max(nEnd, *(setExternalKeyPool.rbegin()) + 1); - } + assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? + int64_t index = ++m_max_keypool_index; - if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(internal), internal))) + if (!walletdb.WritePool(index, CKeyPool(GenerateNewKey(walletdb, internal), internal))) { throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); + } if (internal) { - setInternalKeyPool.insert(nEnd); + setInternalKeyPool.insert(index); } else { - setExternalKeyPool.insert(nEnd); + setExternalKeyPool.insert(index); } - LogPrintf("keypool added key %d, size=%u (%u internal), new key is %s\n", nEnd, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size(), internal ? "internal" : "external"); + } + if (missingInternal + missingExternal > 0) { + LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size()); } } return true; @@ -3294,7 +3316,8 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) if (nIndex == -1) { if (IsLocked()) return false; - result = GenerateNewKey(internal); + CWalletDB walletdb(*dbw); + result = GenerateNewKey(walletdb, internal); return true; } KeepKey(nIndex); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a7fef8a802..bcd7e4b4ee 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -40,7 +40,7 @@ extern unsigned int nTxConfirmTarget; extern bool bSpendZeroConfChange; extern bool fWalletRbf; -static const unsigned int DEFAULT_KEYPOOL_SIZE = 100; +static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default static const CAmount DEFAULT_TRANSACTION_FEE = 0; //! -fallbackfee default @@ -699,10 +699,11 @@ private: CHDChain hdChain; /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal = false); + void DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata& metadata, CKey& secret, bool internal = false); std::set<int64_t> setInternalKeyPool; std::set<int64_t> setExternalKeyPool; + int64_t m_max_keypool_index; int64_t nTimeFirstKey; @@ -745,13 +746,14 @@ public: } } - void LoadKeyPool(int nIndex, const CKeyPool &keypool) + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) { if (keypool.fInternal) { setInternalKeyPool.insert(nIndex); } else { setExternalKeyPool.insert(nIndex); } + m_max_keypool_index = std::max(m_max_keypool_index, nIndex); // If no metadata exists yet, create a default with the pool key's // creation time. Note that this may be overwritten by actually @@ -797,6 +799,7 @@ public: nAccountingEntryNumber = 0; nNextResend = 0; nLastResend = 0; + m_max_keypool_index = 0; nTimeFirstKey = 0; fBroadcastTransactions = false; nRelockTime = 0; @@ -868,9 +871,10 @@ public: * keystore implementation * Generate a new key */ - CPubKey GenerateNewKey(bool internal = false); + CPubKey GenerateNewKey(CWalletDB& walletdb, bool internal = false); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; + bool AddKeyPubKeyWithDB(CWalletDB &walletdb,const CKey& key, const CPubKey &pubkey); //! Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } //! Load metadata (used by LoadWallet) @@ -947,7 +951,7 @@ public: * Insert additional inputs into the transaction by * calling CreateTransaction(); */ - bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl, bool keepReserveKey = true); + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); bool SignTransaction(CMutableTransaction& tx); /** diff --git a/src/warnings.h b/src/warnings.h index a7aa657426..fd0ca53942 100644 --- a/src/warnings.h +++ b/src/warnings.h @@ -14,6 +14,13 @@ void SetfLargeWorkForkFound(bool flag); bool GetfLargeWorkForkFound(); void SetfLargeWorkInvalidChainFound(bool flag); bool GetfLargeWorkInvalidChainFound(); +/** Format a string that describes several potential problems detected by the core. + * strFor can have three values: + * - "rpc": get critical warnings, which should put the client in safe mode if non-empty + * - "statusbar": get all warnings + * - "gui": get all warnings, translated (where possible) for GUI + * This function only returns the highest priority warning of the set selected by strFor. + */ std::string GetWarnings(const std::string& strFor); static const bool DEFAULT_TESTSAFEMODE = false; diff --git a/test/functional/fundrawtransaction.py b/test/functional/fundrawtransaction.py index 0baab6d01c..e52e773918 100755 --- a/test/functional/fundrawtransaction.py +++ b/test/functional/fundrawtransaction.py @@ -636,20 +636,9 @@ class RawTransactionsTest(BitcoinTestFramework): assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate) assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate) - ############################# - # Test address reuse option # - ############################# - - result3 = self.nodes[3].fundrawtransaction(rawtx, {"reserveChangeKey": False}) - res_dec = self.nodes[0].decoderawtransaction(result3["hex"]) - changeaddress = "" - for out in res_dec['vout']: - if out['value'] > 1.0: - changeaddress += out['scriptPubKey']['addresses'][0] - assert(changeaddress != "") - nextaddr = self.nodes[3].getrawchangeaddress() - # frt should not have removed the key from the keypool - assert(changeaddress == nextaddr) + ################################ + # Test no address reuse occurs # + ################################ result3 = self.nodes[3].fundrawtransaction(rawtx) res_dec = self.nodes[0].decoderawtransaction(result3["hex"]) diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py new file mode 100755 index 0000000000..2b4dd2d3e7 --- /dev/null +++ b/test/functional/multiwallet.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test multiwallet.""" +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +class MultiWalletTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']] + + def run_test(self): + w1 = self.nodes[0] / "wallet/w1" + w1.generate(1) + + #accessing wallet RPC without using wallet endpoint fails + assert_raises_jsonrpc(-32601, "Method not found", self.nodes[0].getwalletinfo) + + #check w1 wallet balance + walletinfo = w1.getwalletinfo() + assert_equal(walletinfo['immature_balance'], 50) + + #check w1 wallet balance + w2 = self.nodes[0] / "wallet/w2" + walletinfo = w2.getwalletinfo() + assert_equal(walletinfo['immature_balance'], 0) + + w3 = self.nodes[0] / "wallet/w3" + + w1.generate(101) + assert_equal(w1.getbalance(), 100) + assert_equal(w2.getbalance(), 0) + assert_equal(w3.getbalance(), 0) + + w1.sendtoaddress(w2.getnewaddress(), 1) + w1.sendtoaddress(w3.getnewaddress(), 2) + w1.generate(1) + assert_equal(w2.getbalance(), 1) + assert_equal(w3.getbalance(), 2) + +if __name__ == '__main__': + MultiWalletTest().main() diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py index dfcc524313..b3671cbdc5 100644 --- a/test/functional/test_framework/authproxy.py +++ b/test/functional/test_framework/authproxy.py @@ -191,3 +191,6 @@ class AuthServiceProxy(object): else: log.debug("<-- [%.6f] %s"%(elapsed,responsedata)) return response + + def __truediv__(self, relative_uri): + return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn) diff --git a/test/functional/test_framework/coverage.py b/test/functional/test_framework/coverage.py index 3f87ef91f6..227b1a17af 100644 --- a/test/functional/test_framework/coverage.py +++ b/test/functional/test_framework/coverage.py @@ -56,6 +56,8 @@ class AuthServiceProxyWrapper(object): def url(self): return self.auth_service_proxy_instance.url + def __truediv__(self, relative_uri): + return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri) def get_filename(dirname, n_node): """ diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index b7bc6e841b..51577589fe 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -63,6 +63,7 @@ BASE_SCRIPTS= [ 'segwit.py', # vv Tests less than 2m vv 'wallet.py', + 'multiwallet.py', 'wallet-accounts.py', 'p2p-segwit.py', 'wallet-dump.py', |