diff --git a/CMakeLists.txt b/CMakeLists.txt index daa629cf75826..0f661c7d34771 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,28 +251,16 @@ if(ZMQ_FOUND) endif() set(WALLET_SOURCES - ./src/activemasternode.cpp ./src/bip38.cpp ./src/wallet/db.cpp ./src/addressbook.cpp ./src/crypter.cpp - ./src/budget/budgetdb.cpp - ./src/budget/budgetmanager.cpp - ./src/budget/budgetproposal.cpp - ./src/budget/budgetvote.cpp - ./src/budget/finalizedbudget.cpp - ./src/budget/finalizedbudgetvote.cpp - ./src/masternode.cpp - ./src/masternode-payments.cpp - ./src/masternode-sync.cpp - ./src/tiertwo_networksync.cpp - ./src/masternodeconfig.cpp - ./src/masternodeman.cpp - ./src/messagesigner.cpp ./src/zpiv/mintpool.cpp ./src/wallet/hdchain.cpp ./src/wallet/rpcdump.cpp ./src/zpiv/zerocoin.cpp + ./src/wallet/fees.cpp + ./src/wallet/init.cpp ./src/wallet/scriptpubkeyman.cpp ./src/wallet/rpcwallet.cpp ./src/kernel.cpp @@ -353,8 +341,15 @@ add_library(ZEROCOIN_A STATIC ${ZEROCOIN_SOURCES}) target_include_directories(ZEROCOIN_A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) set(COMMON_SOURCES + ./src/activemasternode.cpp ./src/base58.cpp ./src/bip38.cpp + ./src/budget/budgetdb.cpp + ./src/budget/budgetmanager.cpp + ./src/budget/budgetproposal.cpp + ./src/budget/budgetvote.cpp + ./src/budget/finalizedbudget.cpp + ./src/budget/finalizedbudgetvote.cpp ./src/consensus/params.cpp ./src/consensus/upgrades.cpp ./src/chainparams.cpp @@ -377,6 +372,12 @@ set(COMMON_SOURCES ./src/invalid.cpp ./src/key.cpp ./src/keystore.cpp + ./src/masternode.cpp + ./src/masternode-payments.cpp + ./src/masternode-sync.cpp + ./src/masternodeconfig.cpp + ./src/masternodeman.cpp + ./src/messagesigner.cpp ./src/netaddress.cpp ./src/netbase.cpp ./src/policy/feerate.cpp @@ -391,6 +392,7 @@ set(COMMON_SOURCES ./src/script/script_error.cpp ./src/spork.cpp ./src/sporkdb.cpp + ./src/tiertwo_networksync.cpp ./src/warnings.cpp ) add_library(COMMON_A STATIC ${BitcoinHeaders} ${COMMON_SOURCES}) diff --git a/doc/release-notes.md b/doc/release-notes.md index 1ccd2a81131c9..fa996c5f363a0 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -44,6 +44,7 @@ Notable Changes (Developers: add your notes here as part of your pull requests whenever possible) + Cold-Staking Re-Activation -------------------------- PIVX Core v6.0.0 includes a fix for the vulnerability identified within the cold-staking protocol (see PR [#2258](https://github.com/PIVX-Project/PIVX/pull/2258)). @@ -60,13 +61,24 @@ Scripts with the old opcode are still accepted on the network (the restriction o Multi-wallet support -------------------- -PIVX Core now supports loading multiple, separate wallets (See [PR 2337](https://github.com/PIVX-Project/PIVX/pull/2337)). The wallets are completely separated, with individual balances, keys and received transactions. +PIVX Core now supports loading multiple, separate wallets (See [PR #2337](https://github.com/PIVX-Project/PIVX/pull/2337)). The wallets are completely separated, with individual balances, keys and received transactions. Multi-wallet is enabled by using more than one `-wallet` argument when starting PIVX client, either on the command line or in the pivx.conf config file. **In pivx-qt, only the first wallet will be displayed and accessible for creating and signing transactions.** GUI selectable multiple wallets will be supported in a future version. However, even in 6.0 other loaded wallets will remain synchronized to the node's current tip in the background. -!TODO: update after endpoint support and multi-wallet RPC support +PIVX Core 6.0.0 contains the following changes to the RPC interface and pivx-cli for multi-wallet: + +* When running PIVX Core with a single wallet, there are **no** changes to the RPC interface or `pivx-cli`. All RPC calls and `pivx-cli` commands continue to work as before. +* When running PIVX Core with multi-wallet, all *node-level* RPC methods continue to work as before. HTTP RPC requests should be send to the normal `:` endpoint, and `pivx-cli` commands should be run as before. A *node-level* RPC method is any method which does not require access to the wallet. +* When running PIVX Core with multi-wallet, *wallet-level* RPC methods must specify the wallet for which they're intended in every request. HTTP RPC requests should be send to the `:/wallet/` endpoint, for example `127.0.0.1:8332/wallet/wallet1.dat`. `pivx-cli` commands should be run with a `-rpcwallet` option, for example `pivx-cli -rpcwallet=wallet1.dat getbalance`. + +* A new *node-level* `listwallets` RPC method is added to display which wallets are currently loaded. The names returned by this method are the same as those used in the HTTP endpoint and for the `rpcwallet` argument. + +The `getwalletinfo` RPC method returns the name of the wallet used for the query. + +Note that while multi-wallet is now fully supported, the RPC multi-wallet interface should be considered unstable for version 6.0.0, and there may backwards-incompatible changes in future versions. + GUI changes @@ -125,13 +137,18 @@ A new init option flag '-blocksdir' will allow one to keep the blockfiles extern Low-level RPC changes --------------------- -- The `listunspent` RPC now takes a `query_options` argument (see [PR 2317](https://github.com/PIVX-Project/PIVX/pull/2317)), which is a JSON object +- The `listunspent` RPC now takes a `query_options` argument (see [PR #2317](https://github.com/PIVX-Project/PIVX/pull/2317)), which is a JSON object containing one or more of the following members: - `minimumAmount` - a number specifying the minimum value of each UTXO - `maximumAmount` - a number specifying the maximum value of each UTXO - `maximumCount` - a number specifying the minimum number of UTXOs - `minimumSumAmount` - a number specifying the minimum sum value of all UTXOs +- The `listunspent` RPC also takes an additional boolean argument `include_unsafe` (true by default) to optionally exclude "unsafe" utxos. + An unconfirmed output from outside keys is considered unsafe (see [PR #2351](https://github.com/PIVX-Project/PIVX/pull/2351)). + +- The `listunspent` output also shows whether the utxo is considered safe to spend or not. + - the `stop` RPC no longer accepts the (already deprecated, ignored, and undocumented) optional boolean argument `detach`. diff --git a/src/Makefile.am b/src/Makefile.am index 7f2d2b1a2ab0c..87564a4d964a7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -296,6 +296,8 @@ BITCOIN_CORE_H = \ wallet/rpcwallet.h \ wallet/scriptpubkeyman.h \ destination_io.h \ + wallet/fees.h \ + wallet/init.h \ wallet/wallet.h \ wallet/walletdb.h \ warnings.h \ @@ -386,28 +388,16 @@ endif libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ - activemasternode.cpp \ bip38.cpp \ interfaces/wallet.cpp \ addressbook.cpp \ crypter.cpp \ key_io.cpp \ - budget/budgetdb.cpp \ - budget/budgetmanager.cpp \ - budget/budgetproposal.cpp \ - budget/budgetvote.cpp \ - budget/finalizedbudget.cpp \ - budget/finalizedbudgetvote.cpp \ - masternode.cpp \ - masternode-payments.cpp \ - tiertwo_networksync.cpp \ - masternode-sync.cpp \ - masternodeconfig.cpp \ - masternodeman.cpp \ - messagesigner.cpp \ legacy/stakemodifier.cpp \ kernel.cpp \ wallet/db.cpp \ + wallet/fees.cpp \ + wallet/init.cpp \ wallet/rpcdump.cpp \ wallet/rpcwallet.cpp \ wallet/hdchain.cpp \ @@ -490,8 +480,15 @@ libzerocoin_libbitcoin_zerocoin_a_SOURCES = \ libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_common_a_SOURCES = \ + activemasternode.cpp \ base58.cpp \ bip38.cpp \ + budget/budgetdb.cpp \ + budget/budgetmanager.cpp \ + budget/budgetproposal.cpp \ + budget/budgetvote.cpp \ + budget/finalizedbudget.cpp \ + budget/finalizedbudgetvote.cpp \ chainparams.cpp \ consensus/upgrades.cpp \ coins.cpp \ @@ -506,6 +503,12 @@ libbitcoin_common_a_SOURCES = \ invalid.cpp \ key.cpp \ keystore.cpp \ + masternode.cpp \ + masternode-payments.cpp \ + masternode-sync.cpp \ + masternodeconfig.cpp \ + masternodeman.cpp \ + messagesigner.cpp \ netaddress.cpp \ netbase.cpp \ policy/feerate.cpp \ @@ -517,6 +520,7 @@ libbitcoin_common_a_SOURCES = \ script/script.cpp \ script/sign.cpp \ script/standard.cpp \ + tiertwo_networksync.cpp \ warnings.cpp \ script/script_error.cpp \ spork.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 58fc2b8b1b2d1..6123d0918fd64 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -176,13 +176,13 @@ test_test_pivx_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(SAPLING_TESTS) test_test_pivx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_FLAGS) test_test_pivx_LDADD = -test_test_pivx_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBBITCOIN_ZEROCOIN) \ - $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(LIBSAPLING) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) - if ENABLE_WALLET test_test_pivx_LDADD += $(LIBBITCOIN_WALLET) endif +test_test_pivx_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBBITCOIN_ZEROCOIN) \ + $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(LIBSAPLING) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) + test_test_pivx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_test_pivx_LDADD += $(LIBRUSTZCASH) $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(LIBZCASH_LIBS) diff --git a/src/guiinterfaceutil.h b/src/guiinterfaceutil.h index 9e8c779269f84..2cd03176edc17 100644 --- a/src/guiinterfaceutil.h +++ b/src/guiinterfaceutil.h @@ -6,6 +6,8 @@ #define GUIINTERFACEUTIL_H #include "guiinterface.h" +#include "tinyformat.h" +#include "util/system.h" inline static bool UIError(const std::string &str) { diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 24db0d4554140..6c9d6f25ec43b 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -228,7 +228,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 ed88665cfe93f..2dbf0b1ce5511 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -1,5 +1,5 @@ -// Copyright (c) 2015 The Bitcoin Core developers -// Copyright (c) 2018-2020 The PIVX developers +// Copyright (c) 2015-2021 The Bitcoin Core developers +// Copyright (c) 2018-2021 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -679,3 +679,15 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) pathHandlers.erase(i); } } + +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 ddbe04c142260..6fb3590348ffe 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -1,9 +1,10 @@ -// Copyright (c) 2015 The Bitcoin Core developers +// Copyright (c) 2015-2021 The Bitcoin Core developers +// Copyright (c) 2021 The PIVX 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 +#ifndef PIVX_HTTPSERVER_H +#define PIVX_HTTPSERVER_H #include #include @@ -148,4 +149,6 @@ class HTTPEvent struct event* ev; }; -#endif // BITCOIN_HTTPSERVER_H +std::string urlDecode(const std::string &urlEncoded); + +#endif // PIVX_HTTPSERVER_H diff --git a/src/init.cpp b/src/init.cpp index 91322bfe4b00c..47dd50e99bf26 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -57,13 +57,13 @@ #include "validation.h" #include "validationinterface.h" #include "zpivchain.h" +#include "warnings.h" #ifdef ENABLE_WALLET +#include "wallet/init.h" #include "wallet/wallet.h" #include "wallet/rpcwallet.h" - #endif -#include "warnings.h" #include #include @@ -507,7 +507,7 @@ std::string HelpMessage(HelpMessageMode mode) " " + _("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")); #if ENABLE_WALLET - strUsage += CWallet::GetWalletHelpString(showDebug); + strUsage += GetWalletHelpString(showDebug); #endif if (mode == HMM_BITCOIN_QT) { @@ -561,6 +561,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-minrelaytxfee=", strprintf(_("Fees (in %s/Kb) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)"), CURRENCY_UNIT, FormatMoney(::minRelayTxFee.GetFeePerK()))); strUsage += HelpMessageOpt("-printtoconsole", strprintf(_("Send trace/debug info to console instead of debug.log file (default: %u)"), 0)); if (showDebug) { + strUsage += HelpMessageOpt("-dustrelayfee=", strprintf("Fee rate (in %s/kB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE))); strUsage += HelpMessageOpt("-printpriority", strprintf(_("Log transaction priority and fee per kB when mining blocks (default: %u)"), DEFAULT_PRINTPRIORITY)); } strUsage += HelpMessageOpt("-shrinkdebugfile", _("Shrink debug.log file on client startup (default: 1 when no -debug)")); @@ -1146,9 +1147,18 @@ bool AppInitParameterInteraction() if (!chainparams.IsTestChain() && !fRequireStandard) return UIError(strprintf("%s is not currently supported for %s chain", "-acceptnonstdtxn", chainparams.NetworkIDString())); + // Feerate used to define dust. Shouldn't be changed lightly as old + // implementations may inadvertently create non-standard transactions + if (gArgs.IsArgSet("-dustrelayfee")) { + CAmount n = 0; + if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n) || 0 == n) + return UIError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", ""))); + dustRelayFee = CFeeRate(n); + } + #ifdef ENABLE_WALLET strWalletFile = gArgs.GetArg("-wallet", DEFAULT_WALLET_DAT); - if (!CWallet::ParameterInteraction()) + if (!WalletParameterInteraction()) return false; #endif // ENABLE_WALLET @@ -1322,7 +1332,7 @@ bool AppInitMain() // ********************************************************* Step 5: Verify wallet database integrity #ifdef ENABLE_WALLET - if (!CWallet::Verify()) { + if (!WalletVerify()) { return false; } #endif @@ -1711,7 +1721,7 @@ bool AppInitMain() // ********************************************************* Step 8: Backup and Load wallet #ifdef ENABLE_WALLET - if (!CWallet::InitLoadWallet()) + if (!InitLoadWallet()) return false; #else LogPrintf("No wallet compiled in!\n"); diff --git a/src/miner.cpp b/src/miner.cpp index 6695eef856d45..3f782bea7eff6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -39,7 +39,7 @@ double dHashesPerSec = 0.0; int64_t nHPSTimerStart = 0; -std::unique_ptr CreateNewBlockWithKey(CReserveKey* reservekey, CWallet* pwallet) +std::unique_ptr CreateNewBlockWithKey(std::unique_ptr& reservekey, CWallet* pwallet) { CPubKey pubkey; if (!reservekey->GetReservedKey(pubkey)) return nullptr; @@ -62,7 +62,7 @@ std::unique_ptr CreateNewBlockWithScript(const CScript& coinbase return BlockAssembler(Params(), DEFAULT_PRINTPRIORITY).CreateNewBlock(coinbaseScript, pwallet, false); } -bool ProcessBlockFound(const std::shared_ptr& pblock, CWallet& wallet, Optional& reservekey) +bool ProcessBlockFound(const std::shared_ptr& pblock, CWallet& wallet, std::unique_ptr& reservekey) { LogPrintf("%s\n", pblock->ToString()); LogPrintf("generated %s\n", FormatMoney(pblock->vtx[0]->vout[0].nValue)); @@ -118,10 +118,7 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) const int64_t nSpacingMillis = consensus.nTargetSpacing * 1000; // Each thread has its own key and counter - Optional opReservekey{nullopt}; - if (!fProofOfStake) { - opReservekey = CReserveKey(pwallet); - } + std::unique_ptr pReservekey = fProofOfStake ? nullptr : std::make_unique(pwallet); // Available UTXO set std::vector availableCoins; @@ -171,7 +168,7 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) std::unique_ptr pblocktemplate((fProofOfStake ? BlockAssembler(Params(), DEFAULT_PRINTPRIORITY).CreateNewBlock(CScript(), pwallet, true, &availableCoins) : - CreateNewBlockWithKey(opReservekey.get_ptr(), pwallet))); + CreateNewBlockWithKey(pReservekey, pwallet))); if (!pblocktemplate) continue; std::shared_ptr pblock = std::make_shared(pblocktemplate->block); @@ -179,7 +176,7 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) if (fProofOfStake) { LogPrintf("%s : proof-of-stake block was signed %s \n", __func__, pblock->GetHash().ToString().c_str()); SetThreadPriority(THREAD_PRIORITY_NORMAL); - if (!ProcessBlockFound(pblock, *pwallet, opReservekey)) { + if (!ProcessBlockFound(pblock, *pwallet, pReservekey)) { LogPrintf("%s: New block orphaned\n", __func__); continue; } @@ -209,7 +206,7 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) SetThreadPriority(THREAD_PRIORITY_NORMAL); LogPrintf("%s:\n", __func__); LogPrintf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex(), hashTarget.GetHex()); - ProcessBlockFound(pblock, *pwallet, opReservekey); + ProcessBlockFound(pblock, *pwallet, pReservekey); SetThreadPriority(THREAD_PRIORITY_LOWEST); // In regression test mode, stop mining after a block is found. This diff --git a/src/miner.h b/src/miner.h index 6a141e9920678..22bd7f9fe1dd7 100644 --- a/src/miner.h +++ b/src/miner.h @@ -27,7 +27,7 @@ static const bool DEFAULT_PRINTPRIORITY = false; /** Run the miner threads */ void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads); /** Generate a new PoW block, without valid proof-of-work */ - std::unique_ptr CreateNewBlockWithKey(CReserveKey* reservekey, CWallet* pwallet); + std::unique_ptr CreateNewBlockWithKey(std::unique_ptr& reservekey, CWallet* pwallet); std::unique_ptr CreateNewBlockWithScript(const CScript& coinbaseScript, CWallet* pwallet); void BitcoinMiner(CWallet* pwallet, bool fProofOfStake); diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 87ce9e53a0118..7c2cb6862da34 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -180,12 +180,6 @@ bool CNetAddr::IsLocal() const return false; } -bool CNetAddr::IsMulticast() const -{ - return (IsIPv4() && (GetByte(3) & 0xF0) == 0xE0) - || (GetByte(15) == 0xFF); -} - bool CNetAddr::IsValid() const { // Cleanup 3-byte shifted addresses caused by garbage in size field diff --git a/src/netaddress.h b/src/netaddress.h index 7894e2aff5c21..18a3a826bfc55 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -65,7 +65,6 @@ class CNetAddr bool IsLocal() const; bool IsRoutable() const; bool IsValid() const; - bool IsMulticast() const; enum Network GetNetwork() const; std::string ToString() const; std::string ToStringIP() const; diff --git a/src/pivx-cli.cpp b/src/pivx-cli.cpp index 3da747b0de4ed..ce15f3a8629db 100644 --- a/src/pivx-cli.cpp +++ b/src/pivx-cli.cpp @@ -1,7 +1,7 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2015 The Bitcoin developers +// Copyright (c) 2009-2021 The Bitcoin developers // Copyright (c) 2009-2015 The Dash developers -// Copyright (c) 2015-2019 The PIVX developers +// Copyright (c) 2015-2021 The PIVX developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -42,7 +42,8 @@ std::string HelpMessageCli() strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start")); strUsage += HelpMessageOpt("-rpcuser=", _("Username for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcpassword=", _("Password for JSON-RPC connections")); - strUsage += HelpMessageOpt("-rpcclienttimeout=", strprintf(_("Timeout during HTTP requests (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); + strUsage += HelpMessageOpt("-rpcclienttimeout=", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); + strUsage += HelpMessageOpt("-rpcwallet=", _("Send RPC for non-default wallet on RPC server (argument is wallet filename in pivxd directory, required if pivxd/-Qt runs with multiple wallets)")); return strUsage; } @@ -190,7 +191,19 @@ 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, req, EVHTTP_REQ_POST, "/"); + // check if we should use a special wallet endpoint + std::string endpoint = "/"; + std::string walletName = gArgs.GetArg("-rpcwallet", ""); + 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, req, EVHTTP_REQ_POST, endpoint.c_str()); if (r != 0) { evhttp_connection_free(evcon); event_base_free(base); @@ -263,6 +276,18 @@ int CommandLineRPC(int argc, char* argv[]) throw CConnectionFailed("server in warmup"); strPrint = "error: " + error.write(); nRet = abs(code); + if (error.isObject()) { + UniValue errCode = find_value(error, "code"); + UniValue errMsg = find_value(error, "message"); + strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n"; + + if (errMsg.isStr()) + strPrint += "error message:\n"+errMsg.get_str(); + + if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) { + strPrint += "\nTry adding \"-rpcwallet=\" option to pivx-cli command line."; + } + } } else { // Result if (result.isNull()) diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index deae2082bead1..c2dc818175eef 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -16,11 +16,13 @@ bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG; -CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee) +CFeeRate dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); + +CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) { // "Dust" is defined in terms of dustRelayFee, // which has units satoshis-per-kilobyte. - // If you'd pay more than 1/3 in fees + // If you'd pay more in fees than the value of the output // to spend something, then we consider it dust. // A typical spendable txout is 34 bytes big, and will // need a CTxIn of at least 148 bytes to spend: @@ -31,26 +33,26 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee) size_t nSize = GetSerializeSize(txout, SER_DISK, 0); nSize += (32 + 4 + 1 + 107 + 4); // the 148 mentioned above - return 3 * dustRelayFee.GetFee(nSize); + return dustRelayFeeIn.GetFee(nSize); } -CAmount GetDustThreshold(const CFeeRate& dustRelayFee) +CAmount GetDustThreshold(const CFeeRate& dustRelayFeeIn) { // return the dust threshold for a typical 34 bytes output - return 3 * dustRelayFee.GetFee(182); + return dustRelayFeeIn.GetFee(182); } -bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee) +bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) { - return (txout.nValue < GetDustThreshold(txout, dustRelayFee)); + return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn)); } -CAmount GetShieldedDustThreshold(const CFeeRate& dustRelayFee) +CAmount GetShieldedDustThreshold(const CFeeRate& dustRelayFeeIn) { unsigned int K = DEFAULT_SHIELDEDTXFEE_K; // Fixed (100) for now - return 3 * K * dustRelayFee.GetFee(SPENDDESCRIPTION_SIZE + - CTXOUT_REGULAR_SIZE + - BINDINGSIG_SIZE); + return K * dustRelayFeeIn.GetFee(SPENDDESCRIPTION_SIZE + + CTXOUT_REGULAR_SIZE + + BINDINGSIG_SIZE); } /** @@ -174,7 +176,7 @@ bool IsStandardTx(const CTransactionRef& tx, int nBlockHeight, std::string& reas else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { reason = "bare-multisig"; return false; - } else if (IsDust(txout, ::minRelayTxFee)) { + } else if (IsDust(txout, dustRelayFee)) { reason = "dust"; return false; } diff --git a/src/policy/policy.h b/src/policy/policy.h index 58ec491d5e65d..46f25b2227bb5 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -29,6 +29,12 @@ static const unsigned int DEFAULT_MAX_MEMPOOL_SIZE = 300; /** Default for -permitbaremultisig */ static const bool DEFAULT_PERMIT_BAREMULTISIG = true; extern bool fIsBareMultisigStd; +/** Min feerate for defining dust. Historically this has been based on the + * minRelayTxFee, however changing the dust limit changes which transactions are + * standard and should be done with care and ideally rarely. It makes sense to + * only increase the dust limit after prior releases were already not creating + * outputs below the new threshold */ +static const unsigned int DUST_RELAY_TX_FEE = 30000; /** * Standard script verification flags that standard transactions will comply @@ -50,12 +56,12 @@ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCR BOOST_STATIC_ASSERT(DEFAULT_BLOCK_MAX_SIZE <= MAX_BLOCK_SIZE_CURRENT); BOOST_STATIC_ASSERT(DEFAULT_BLOCK_PRIORITY_SIZE <= DEFAULT_BLOCK_MAX_SIZE); -CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); +CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn); -bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee); +bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn); -CAmount GetDustThreshold(const CFeeRate& dustRelayFee); -CAmount GetShieldedDustThreshold(const CFeeRate& dustRelayFee); +CAmount GetDustThreshold(const CFeeRate& dustRelayFeeIn); +CAmount GetShieldedDustThreshold(const CFeeRate& dustRelayFeeIn); bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType); @@ -71,4 +77,6 @@ bool IsStandardTx(const CTransactionRef& tx, int nBlockHeight, std::string& reas */ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); +extern CFeeRate dustRelayFee; + #endif // BITCOIN_POLICY_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index a36c811b86b66..a362df2b387a9 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -15,6 +15,7 @@ #include "optionsmodel.h" #include "policy/policy.h" #include "txmempool.h" +#include "wallet/fees.h" #include "wallet/wallet.h" #include "walletmodel.h" @@ -535,7 +536,7 @@ TotalAmounts CoinControlDialog::getTotals() const if (shieldedOut) nShieldOuts++; else nTransOuts++; if (a.first > 0 && !t.fDust) { - if (a.first < (shieldedOut ? GetShieldedDustThreshold(minRelayTxFee) : GetDustThreshold(minRelayTxFee))) + if (a.first < (shieldedOut ? GetShieldedDustThreshold(dustRelayFee) : GetDustThreshold(dustRelayFee))) t.fDust = true; } t.nBytes += (shieldedOut ? OUTPUTDESCRIPTION_SIZE @@ -566,8 +567,8 @@ TotalAmounts CoinControlDialog::getTotals() const t.nChange = t.nAmount - t.nPayFee - t.nPayAmount; // Never create dust outputs; if we would, just add the dust to the fee. - CAmount dustThreshold = fSelectTransparent ? GetDustThreshold(minRelayTxFee) : - GetShieldedDustThreshold(minRelayTxFee); + CAmount dustThreshold = fSelectTransparent ? GetDustThreshold(dustRelayFee) + : GetShieldedDustThreshold(dustRelayFee); if (t.nChange > 0 && t.nChange < dustThreshold) { t.nPayFee += t.nChange; t.nChange = 0; @@ -632,18 +633,18 @@ void CoinControlDialog::updateLabels() // tool tips QString toolTip1 = tr("This label turns red, if the transaction size is greater than 1000 bytes.") + "

"; - toolTip1 += tr("This means a fee of at least %1 per kB is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CWallet::GetRequiredFee(1000))) + "

"; + toolTip1 += tr("This means a fee of at least %1 per kB is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, GetRequiredFee(1000))) + "

"; toolTip1 += tr("Can vary +/- 1 byte per input."); QString toolTip3 = tr("This label turns red, if recipient receives an amount smaller than %1 (transparent) / %2 (shield)." - ).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, GetDustThreshold(minRelayTxFee))).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, GetShieldedDustThreshold(minRelayTxFee))); + ).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, GetDustThreshold(dustRelayFee))).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, GetShieldedDustThreshold(dustRelayFee))); // how many satoshis the estimated fee can vary per byte we guess wrong double dFeeVary; if (payTxFee.GetFeePerK() > 0) - dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000; + dFeeVary = (double)std::max(GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000; else - dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), mempool.estimateSmartFee(nTxConfirmTarget).GetFeePerK()) / 1000; + dFeeVary = (double)std::max(GetRequiredFee(1000), mempool.estimateSmartFee(nTxConfirmTarget).GetFeePerK()) / 1000; QString toolTip4 = tr("Can vary +/- %1 u%2 per input.").arg(dFeeVary).arg(CURRENCY_UNIT.c_str()); ui->labelCoinControlFee->setToolTip(toolTip4); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index ba4a73b4336ad..0401c77bfdf5c 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -250,7 +250,7 @@ bool isDust(const QString& address, const CAmount& amount) CTxDestination dest = DecodeDestination(address.toStdString()); CScript script = GetScriptForDestination(dest); CTxOut txOut(amount, script); - return IsDust(txOut, ::minRelayTxFee); + return IsDust(txOut, dustRelayFee); } QString HtmlEscape(const QString& str, bool fMultiLine) diff --git a/src/qt/pivx/sendcustomfeedialog.cpp b/src/qt/pivx/sendcustomfeedialog.cpp index 9712b8d224dfa..f7a139333c5e6 100644 --- a/src/qt/pivx/sendcustomfeedialog.cpp +++ b/src/qt/pivx/sendcustomfeedialog.cpp @@ -5,9 +5,10 @@ #include "qt/pivx/sendcustomfeedialog.h" #include "qt/pivx/forms/ui_sendcustomfeedialog.h" #include "qt/pivx/qtutils.h" -#include "walletmodel.h" +#include "qt/walletmodel.h" #include "optionsmodel.h" #include "guiutil.h" +#include "wallet/fees.h" #include #include @@ -123,19 +124,19 @@ void SendCustomFeeDialog::accept() // Check insane fee const CAmount insaneFee = ::minRelayTxFee.GetFeePerK() * 10000; if (customFee >= insaneFee) { - ui->lineEditCustomFee->setText(BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), insaneFee - CWallet::GetRequiredFee(1000))); + ui->lineEditCustomFee->setText(BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), insaneFee - GetRequiredFee(1000))); inform(tr("Fee too high. Must be below: %1").arg( BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), insaneFee))); - } else if (customFee < CWallet::GetRequiredFee(1000)) { + } else if (customFee < GetRequiredFee(1000)) { CAmount nFee = 0; if (walletModel->hasWalletCustomFee()) { walletModel->getWalletCustomFee(nFee); } else { - nFee = CWallet::GetRequiredFee(1000); + nFee = GetRequiredFee(1000); } ui->lineEditCustomFee->setText(BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), nFee)); inform(tr("Fee too low. Must be at least: %1").arg( - BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000)))); + BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), GetRequiredFee(1000)))); } else { walletModel->setWalletCustomFee(fUseCustomFee, customFee); QDialog::accept(); diff --git a/src/qt/rpcexecutor.cpp b/src/qt/rpcexecutor.cpp index 305e9bd16ce12..70409b2652b34 100644 --- a/src/qt/rpcexecutor.cpp +++ b/src/qt/rpcexecutor.cpp @@ -7,6 +7,12 @@ #include "rpc/client.h" +#ifdef ENABLE_WALLET +#include "wallet/wallet.h" +#endif + +#include + #include QString RPCExecutor::categoryClass(int category) @@ -218,6 +224,14 @@ bool RPCExecutor::ExecuteCommandLine(std::string& strResult, const std::string& JSONRPCRequest req; req.params = RPCConvertValues(stack.back()[0], std::vector(stack.back().begin() + 1, stack.back().end())); req.strMethod = stack.back()[0]; +#ifdef ENABLE_WALLET + // TODO: Move this logic to WalletModel + if (!vpwallets.empty()) { + // in Qt, use always the wallet with index 0 when running with multiple wallets + QByteArray encodedName = QUrl::toPercentEncoding(QString::fromStdString(vpwallets[0]->GetName())); + req.URI = "/wallet/"+std::string(encodedName.constData(), encodedName.length()); + } +#endif lastResult = tableRPC.execute(req); state = STATE_COMMAND_EXECUTED; curarg.clear(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 1575a002d2a50..ce681222beaef 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1044,63 +1044,41 @@ void WalletModel::listCoins(std::map>& void WalletModel::listAvailableNotes(std::map>& mapCoins) const { - std::vector notes; - Optional dummy = nullopt; - wallet->GetSaplingScriptPubKeyMan()->GetFilteredNotes(notes, dummy); - for (const auto& note : notes) { - ListCoinsKey key{QString::fromStdString(KeyIO::EncodePaymentAddress(note.address)), false, nullopt}; - ListCoinsValue value{ - note.op.hash, - (int)note.op.n, - (CAmount)note.note.value(), - 0, - note.confirmations - }; - mapCoins[key].emplace_back(value); + for (const auto& it: wallet->ListNotes()) { + const ListCoinsKey key{QString::fromStdString(KeyIO::EncodePaymentAddress(it.first)), false, nullopt}; + + for (const SaplingNoteEntry& note : it.second) { + mapCoins[key].emplace_back(note.op.hash, + (int)note.op.n, + (CAmount)note.note.value(), + 0, + note.confirmations); + } } } // AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) void WalletModel::listCoins(std::map>& mapCoins) const { - CWallet::AvailableCoinsFilter filter; - filter.fIncludeLocked = true; - std::vector vCoins; - wallet->AvailableCoins(&vCoins, nullptr, filter); + for (const auto& it: wallet->ListCoins()) { + const std::pair>& addresses = it.first; + const std::vector& coins = it.second; - for (const COutput& out : vCoins) { - if (!out.fSpendable) continue; - - const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; - const bool isP2CS = scriptPubKey.IsPayToColdStaking(); - - CTxDestination outputAddress; - CTxDestination outputAddressStaker; - if (isP2CS) { - txnouttype type; std::vector addresses; int nRequired; - if(!ExtractDestinations(scriptPubKey, type, addresses, nRequired) - || addresses.size() != 2) throw std::runtime_error("Cannot extract P2CS addresses from a stored transaction"); - outputAddressStaker = addresses[0]; - outputAddress = addresses[1]; - } else { - if (!ExtractDestination(scriptPubKey, outputAddress)) - continue; - } + const QString& address = QString::fromStdString(EncodeDestination(addresses.first)); + const Optional& stakerAddr = addresses.second == nullopt ? nullopt : Optional( + QString::fromStdString(EncodeDestination(*addresses.second, CChainParams::STAKING_ADDRESS))); + // P2CS cannot be "change" + const bool isChange = stakerAddr == nullopt ? wallet->IsChange(addresses.first) : false; - QString address = QString::fromStdString(EncodeDestination(outputAddress)); - Optional stakerAddr = IsValidDestination(outputAddressStaker) ? - Optional(QString::fromStdString(EncodeDestination(outputAddressStaker, CChainParams::STAKING_ADDRESS))) : - nullopt; - - ListCoinsKey key{address, wallet->IsChange(outputAddress), stakerAddr}; - ListCoinsValue value{ - out.tx->GetHash(), - out.i, - out.tx->tx->vout[out.i].nValue, - out.tx->GetTxTime(), - out.nDepth - }; - mapCoins[key].emplace_back(value); + const ListCoinsKey key{address, isChange, stakerAddr}; + + for (const COutput& out: coins) { + mapCoins[key].emplace_back(out.tx->GetHash(), + out.i, + out.tx->tx->vout[out.i].nValue, + out.tx->GetTxTime(), + out.nDepth); + } } } @@ -1130,12 +1108,7 @@ std::set WalletModel::listLockedCoins() void WalletModel::loadReceiveRequests(std::vector& vReceiveRequests) { - LOCK(wallet->cs_wallet); - for (auto it = wallet->NewAddressBookIterator(); it.IsValid(); it.Next()) { - for (const std::pair &item2 : it.GetValue().destdata) - if (item2.first.size() > 2 && item2.first.substr(0, 2) == "rr") // receive request - vReceiveRequests.push_back(item2.second); - } + vReceiveRequests = wallet->GetDestValues("rr"); // receive request } bool WalletModel::saveReceiveRequest(const std::string& sAddress, const int64_t nId, const std::string& sRequest) diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index b5977b7ae2c09..2cdcec7575d20 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -323,6 +323,11 @@ class WalletModel : public QObject class ListCoinsValue { public: + ListCoinsValue() = delete; + ListCoinsValue(const uint256& _txhash, int _outIndex, CAmount _nValue, int64_t _nTime, int _nDepth) : + txhash(_txhash), outIndex(_outIndex), nValue(_nValue), nTime(_nTime), nDepth(_nDepth) + {} + uint256 txhash; int outIndex; CAmount nValue; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index cc3d93f59ab45..d1d1ef12eaaa4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -24,6 +24,7 @@ #include "hash.h" #include "validationinterface.h" #include "wallet/wallet.h" +#include "warnings.h" #include #include @@ -1010,6 +1011,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) " \"info\": \"xxxx\", (string) additional information about upgrade\n" " }, ...\n" "}\n" + " \"warnings\" : \"...\", (string) any network and blockchain warnings.\n" "\nExamples:\n" + HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "")); @@ -1039,7 +1041,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) NetworkUpgradeDescPushBack(upgrades, consensusParams, Consensus::UpgradeIndex(i), nTipHeight); } obj.pushKV("upgrades", upgrades); - + obj.pushKV("warnings", GetWarnings("statusbar")); return obj; } diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 1a0dd69efb810..002d4b91e6a0f 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -109,6 +109,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { { "listunspent", 2, "addresses" }, { "listunspent", 3, "watchonly_config" }, { "listunspent", 4, "query_options" }, + { "listunspent", 5, "include_unsafe" }, { "lockunspent", 0, "unlock" }, { "lockunspent", 1, "transactions" }, { "logging", 0, "include" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 575c2e5bfe15c..24d3611bbe749 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -366,13 +366,14 @@ UniValue getmininginfo(const JSONRPCRequest& request) " \"currentblocksize\": nnn, (numeric) The last block size\n" " \"currentblocktx\": nnn, (numeric) The last block transaction\n" " \"difficulty\": xxx.xxxxx (numeric) The current difficulty\n" - " \"errors\": \"...\" (string) Current errors\n" " \"generate\": true|false (boolean) If the generation is on or off (see getgenerate or setgenerate calls)\n" " \"genproclimit\": n (numeric) The processor limit for generation. -1 if no generation. (see getgenerate or setgenerate calls)\n" " \"hashespersec\": n (numeric) The hashes per second of the generation, or 0 if no generation.\n" " \"pooledtx\": n (numeric) The size of the mem pool\n" " \"testnet\": true|false (boolean) If using testnet or not\n" " \"chain\": \"xxxx\", (string) current network name (main, test, regtest)\n" + " \"warnings\": \"...\" (string) any network and blockchain warnings\n" + " \"errors\": \"...\" (string) DEPRECATED. Same as warnings. Only shown when bitcoind is started with -deprecatedrpc=getmininginfo\n" "}\n" "\nExamples:\n" + @@ -385,12 +386,17 @@ UniValue getmininginfo(const JSONRPCRequest& request) obj.pushKV("currentblocksize", (uint64_t)nLastBlockSize); obj.pushKV("currentblocktx", (uint64_t)nLastBlockTx); obj.pushKV("difficulty", (double)GetDifficulty()); - obj.pushKV("errors", GetWarnings("statusbar")); obj.pushKV("genproclimit", (int)gArgs.GetArg("-genproclimit", -1)); obj.pushKV("networkhashps", getnetworkhashps(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); obj.pushKV("testnet", Params().IsTestnet()); obj.pushKV("chain", Params().NetworkIDString()); + obj.pushKV("errors", GetWarnings("statusbar")); + if (IsDeprecatedRPCEnabled("getmininginfo")) { + obj.pushKV("errors", GetWarnings("statusbar")); + } else { + obj.pushKV("warnings", GetWarnings("statusbar")); + } #ifdef ENABLE_WALLET obj.pushKV("generate", getgenerate(request)); obj.pushKV("hashespersec", gethashespersec(request)); diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index e7e99732bb600..2dd5bfd965ae0 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -17,6 +17,7 @@ #include "util/system.h" #include "version.h" #include "validation.h" +#include "warnings.h" #include @@ -391,6 +392,7 @@ UniValue getnetworkinfo(const JSONRPCRequest& request) " }\n" " ,...\n" " ]\n" + " \"warnings\": \"...\" (string) any network and blockchain warnings\n" "}\n" "\nExamples:\n" + @@ -420,6 +422,7 @@ UniValue getnetworkinfo(const JSONRPCRequest& request) } } obj.pushKV("localaddresses", localAddresses); + obj.pushKV("warnings", GetWarnings("statusbar")); return obj; } diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 84cedade73471..11c6c411aae45 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -76,6 +76,8 @@ enum RPCErrorCode { RPC_WALLET_WRONG_ENC_STATE = -15, //! Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) RPC_WALLET_ENCRYPTION_FAILED = -16, //! Failed to encrypt the wallet RPC_WALLET_ALREADY_UNLOCKED = -17, //! Wallet is already unlocked + RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified + RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded) }; UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); diff --git a/src/sapling/sapling_operation.cpp b/src/sapling/sapling_operation.cpp index b513e33d16f31..9d2a43cbcdfb0 100644 --- a/src/sapling/sapling_operation.cpp +++ b/src/sapling/sapling_operation.cpp @@ -287,7 +287,7 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues) const auto* tx = wallet->GetWalletTx(outpoint.outPoint.hash); if (!tx) continue; nSelectedValue += tx->tx->vout[outpoint.outPoint.n].nValue; - selectedUTXOInputs.emplace_back(tx, outpoint.outPoint.n, 0, true, true); + selectedUTXOInputs.emplace_back(tx, outpoint.outPoint.n, 0, true, true, true); } return loadUtxos(txValues, selectedUTXOInputs, nSelectedValue); } @@ -313,7 +313,7 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues) // Final step, append utxo to the transaction // Get dust threshold - CAmount dustThreshold = GetDustThreshold(minRelayTxFee); + CAmount dustThreshold = GetDustThreshold(dustRelayFee); CAmount dustChange = -1; CAmount selectedUTXOAmount = 0; @@ -445,7 +445,7 @@ OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, uint256& std::vector notes; std::vector spendingKeys; txValues.shieldedInTotal = 0; - CAmount dustThreshold = GetShieldedDustThreshold(minRelayTxFee); + CAmount dustThreshold = GetShieldedDustThreshold(dustRelayFee); CAmount dustChange = -1; for (const auto& t : shieldedInputs) { // Get the spending key for the address. diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index 769cbd5f8bf63..0965c701de87f 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -511,6 +511,20 @@ void SaplingScriptPubKeyMan::GetFilteredNotes( } } +/* Return list of available notes grouped by sapling address. */ +std::map> SaplingScriptPubKeyMan::ListNotes() const +{ + std::vector notes; + Optional dummy = nullopt; + GetFilteredNotes(notes, dummy); + + std::map> result; + for (const auto& note : notes) { + result[note.address].emplace_back(std::move(note)); + } + return result; +} + Optional SaplingScriptPubKeyMan::GetAddressFromInputIfPossible(const uint256& txHash, int index) const { diff --git a/src/sapling/saplingscriptpubkeyman.h b/src/sapling/saplingscriptpubkeyman.h index 33d06f78ed2fc..600c8652a6ce6 100644 --- a/src/sapling/saplingscriptpubkeyman.h +++ b/src/sapling/saplingscriptpubkeyman.h @@ -297,6 +297,8 @@ class SaplingScriptPubKeyMan { bool requireSpendingKey=true, bool ignoreLocked=true) const; + /* Return list of available notes grouped by sapling address. */ + std::map> ListNotes() const; //! Return the address from where the shielded spend is taking the funds from (if possible) Optional GetAddressFromInputIfPossible(const CWalletTx* wtx, int index) const; diff --git a/src/sapling/transaction_builder.cpp b/src/sapling/transaction_builder.cpp index 80acb95475534..c2a5d0930d0c9 100644 --- a/src/sapling/transaction_builder.cpp +++ b/src/sapling/transaction_builder.cpp @@ -421,8 +421,8 @@ TransactionBuilderResult TransactionBuilder::Build(bool fDummySig) if (change > 0) { // If we get here and the change is dust, add it to the fee - CAmount dustThreshold = (spends.empty() && outputs.empty()) ? GetDustThreshold(minRelayTxFee) : - GetShieldedDustThreshold(minRelayTxFee); + CAmount dustThreshold = (spends.empty() && outputs.empty()) ? GetDustThreshold(dustRelayFee) + : GetShieldedDustThreshold(dustRelayFee); if (change > dustThreshold) { // Send change to the specified change address. If no change address // was set, send change to the first Sapling address given as input diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 837bb84cdd9ac..b036da1091ea7 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -240,10 +240,7 @@ bool EvalScript(std::vector >& stack, const CScript& { static const CScriptNum bnZero(0); static const CScriptNum bnOne(1); - static const CScriptNum bnFalse(0); - static const CScriptNum bnTrue(1); static const valtype vchFalse(0); - static const valtype vchZero(0); static const valtype vchTrue(1, 1); CScript::const_iterator pc = script.begin(); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index f38373e92eee8..6838c7ab63b31 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -425,11 +425,26 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) std::string reason; BOOST_CHECK(IsStandardTx(t, 0, reason)); - t.vout[0].nValue = 5011; // dust + // Check dust with default relay fee: + CAmount nDustThreshold = GetDustThreshold(dustRelayFee); + BOOST_CHECK_EQUAL(nDustThreshold, 5460); + // dust: + t.vout[0].nValue = nDustThreshold - 1; BOOST_CHECK(!IsStandardTx(t, 0, reason)); + // not dust: + t.vout[0].nValue = nDustThreshold; + BOOST_CHECK(IsStandardTx(t, 0, reason)); - t.vout[0].nValue = 6011; // not dust + // Check dust with odd relay fee to verify rounding: + // nDustThreshold = 182 * 3702 / 1000 + dustRelayFee = CFeeRate(3702); + // dust: + t.vout[0].nValue = 673 - 1; + BOOST_CHECK(!IsStandardTx(t, 0, reason)); + // not dust: + t.vout[0].nValue = 673; BOOST_CHECK(IsStandardTx(t, 0, reason)); + dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); t.vout[0].scriptPubKey = CScript() << OP_1; BOOST_CHECK(!IsStandardTx(t, 0, reason)); diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 6e36edf870097..9f1d0c095ead0 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -22,6 +22,39 @@ #include +namespace { +//! Make sure database has a unique fileid within the environment. If it +//! doesn't, throw an error. BDB caches do not work properly when more than one +//! open database has the same fileid (values written to one database may show +//! up in reads to other databases). +//! +//! BerkeleyDB generates unique fileids by default +//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html), +//! so bitcoin should never create different databases with the same fileid, but +//! this error can be triggered if users manually copy database files. +void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db) +{ + if (env.IsMock()) return; + + u_int8_t fileid[DB_FILE_ID_LEN]; + int ret = db.get_mpf()->get_fileid(fileid); + if (ret != 0) { + throw std::runtime_error(strprintf("CDB: Can't open database %s (get_fileid failed with %d)", filename, ret)); + } + + for (const auto& item : env.mapDb) { + u_int8_t item_fileid[DB_FILE_ID_LEN]; + if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 && + memcmp(fileid, item_fileid, sizeof(fileid)) == 0) { + const char* item_filename = nullptr; + item.second->get_dbname(&item_filename, nullptr); + throw std::runtime_error(strprintf("CDB: Can't open database %s (duplicates fileid %s from %s)", filename, + HexStr(std::begin(item_fileid), std::end(item_fileid)), + item_filename ? item_filename : "(unknown database)")); + } + } +} +} // namespace // // CDB @@ -210,7 +243,6 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco if (recoverKVcallback) { CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); - std::string strType, strErr; if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) continue; } @@ -353,9 +385,8 @@ void CDBEnv::CheckpointLSN(const std::string& strFile) } -CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb(NULL), activeTxn(NULL) +CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr) { - int ret; fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w')); fFlushOnClose = fFlushOnCloseIn; env = dbw.env; @@ -364,7 +395,7 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb } const std::string &strFilename = dbw.strFile; - bool fCreate = strchr(pszMode, 'c') != NULL; + bool fCreate = strchr(pszMode, 'c') != nullptr; unsigned int nFlags = DB_THREAD; if (fCreate) nFlags |= DB_CREATE; @@ -374,35 +405,34 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb if (!env->Open(GetDataDir())) throw std::runtime_error("CDB: Failed to open database environment."); - strFile = strFilename; - ++env->mapFileUseCount[strFile]; - pdb = env->mapDb[strFile]; - if (pdb == NULL) { - pdb = new Db(env->dbenv, 0); + pdb = env->mapDb[strFilename]; + if (pdb == nullptr) { + int ret; + std::unique_ptr pdb_temp(new Db(env->dbenv, 0)); bool fMockDb = env->IsMock(); if (fMockDb) { - DbMpoolFile* mpf = pdb->get_mpf(); + DbMpoolFile* mpf = pdb_temp->get_mpf(); ret = mpf->set_flags(DB_MPOOL_NOFILE, 1); - if (ret != 0) - throw std::runtime_error(strprintf("CDB : Failed to configure for no temp file backing for database %s", strFile)); + if (ret != 0) { + throw std::runtime_error(strprintf("CDB: Failed to configure for no temp file backing for database %s", strFilename)); + } } - ret = pdb->open(NULL, // Txn pointer - fMockDb ? NULL : strFile.c_str(), // Filename - fMockDb ? strFile.c_str() : "main", // Logical db name - DB_BTREE, // Database type - nFlags, // Flags - 0); + ret = pdb_temp->open(nullptr, // Txn pointer + fMockDb ? nullptr : strFilename.c_str(), // Filename + fMockDb ? strFilename.c_str() : "main", // Logical db name + DB_BTREE, // Database type + nFlags, // Flags + 0); if (ret != 0) { - delete pdb; - pdb = NULL; - --env->mapFileUseCount[strFile]; - std::string tempCopy(strFile); - strFile = ""; - throw std::runtime_error(strprintf("CDB : Error %d, can't open database %s", ret, tempCopy)); + throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename)); } + CheckUniqueFileid(*env, strFilename, *pdb_temp); + + pdb = pdb_temp.release(); + env->mapDb[strFilename] = pdb; if (fCreate && !Exists(std::string("version"))) { bool fTmp = fReadOnly; @@ -410,9 +440,9 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb WriteVersion(CLIENT_VERSION); fReadOnly = fTmp; } - - env->mapDb[strFile] = pdb; } + ++env->mapFileUseCount[strFilename]; + strFile = strFilename; } } @@ -466,15 +496,6 @@ void CDBEnv::CloseDb(const std::string& strFile) } } -bool CDBEnv::RemoveDb(const std::string& strFile) -{ - this->CloseDb(strFile); - - LOCK(cs_db); - int rc = dbenv->dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT); - return (rc == 0); -} - bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip) { if (dbw.IsDummy()) { diff --git a/src/wallet/db.h b/src/wallet/db.h index 1c76d58c91e85..413e98dd7b66b 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -46,7 +46,7 @@ class CDBEnv void Reset(); void MakeMock(); - bool IsMock() { return fMockDb; } + bool IsMock() const { return fMockDb; } /** * Verify that database file strFile is OK. If it is not, @@ -76,7 +76,6 @@ class CDBEnv void CheckpointLSN(const std::string& strFile); void CloseDb(const std::string& strFile); - bool RemoveDb(const std::string& strFile); DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC) { diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp new file mode 100644 index 0000000000000..fb83aa7025835 --- /dev/null +++ b/src/wallet/fees.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "wallet/fees.h" + +#include "policy/policy.h" +#include "txmempool.h" +#include "util/system.h" +#include "validation.h" +#include "wallet/wallet.h" + +CAmount GetRequiredFee(unsigned int nTxBytes) +{ + return std::max(CWallet::minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); +} + +CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool) +{ + // payTxFee is user-set "I want to pay this much" + CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes); + // User didn't set: use -txconfirmtarget to estimate... + if (nFeeNeeded == 0) { + int estimateFoundTarget = (int) nConfirmTarget; + nFeeNeeded = pool.estimateSmartFee((int) nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes); + // ... unless we don't have enough mempool data for our desired target + // so we make sure we're paying at least minTxFee + if (nFeeNeeded == 0 || (unsigned int) estimateFoundTarget > nConfirmTarget) + nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes)); + } + // prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee + if (nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes)) + nFeeNeeded = ::minRelayTxFee.GetFee(nTxBytes); + // But always obey the maximum + if (nFeeNeeded > maxTxFee) + nFeeNeeded = maxTxFee; + return nFeeNeeded; +} diff --git a/src/wallet/fees.h b/src/wallet/fees.h new file mode 100644 index 0000000000000..144a15b3f0704 --- /dev/null +++ b/src/wallet/fees.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_WALLET_FEES_H +#define PIVX_WALLET_FEES_H + +#include "amount.h" + +class CTxMemPool; + +/** + * Return the minimum required fee taking into account the + * floating relay fee and user set minimum transaction fee + */ +CAmount GetRequiredFee(unsigned int nTxBytes); + +/** + * Estimate the minimum fee considering user set parameters + * and the required fee + */ +CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool); + + +#endif // PIVX_WALLET_FEES_H diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp new file mode 100644 index 0000000000000..98dc0417094b4 --- /dev/null +++ b/src/wallet/init.cpp @@ -0,0 +1,232 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "wallet/init.h" + +#include "guiinterfaceutil.h" +#include "net.h" +#include "util/system.h" +#include "utilmoneystr.h" +#include "validation.h" +#include "wallet/wallet.h" + +std::string GetWalletHelpString(bool showDebug) +{ + std::string strUsage = HelpMessageGroup(_("Wallet options:")); + strUsage += HelpMessageOpt("-backuppath=", _("Specify custom backup path to add a copy of any wallet backup. If set as dir, every backup generates a timestamped file. If set as file, will rewrite to that file every backup.")); + strUsage += HelpMessageOpt("-createwalletbackups=", strprintf(_("Number of automatic wallet backups (default: %d)"), DEFAULT_CREATEWALLETBACKUPS)); + strUsage += HelpMessageOpt("-custombackupthreshold=", strprintf(_("Number of custom location backups to retain (default: %d)"), DEFAULT_CUSTOMBACKUPTHRESHOLD)); + strUsage += HelpMessageOpt("-disablewallet", strprintf(_("Do not load the wallet and disable wallet RPC calls (default: %u)"), DEFAULT_DISABLE_WALLET)); + strUsage += HelpMessageOpt("-keypool=", strprintf(_("Set key pool size to (default: %u)"), DEFAULT_KEYPOOL_SIZE)); + strUsage += HelpMessageOpt("-legacywallet", _("On first run, create a legacy wallet instead of a HD wallet")); + strUsage += HelpMessageOpt("-maxtxfee=", strprintf(_("Maximum total fees to use in a single wallet transaction, setting too low may abort large transactions (default: %s)"), FormatMoney(maxTxFee))); + strUsage += HelpMessageOpt("-mintxfee=", strprintf(_("Fees (in %s/Kb) smaller than this are considered zero fee for transaction creation (default: %s)"), CURRENCY_UNIT, FormatMoney(CWallet::minTxFee.GetFeePerK()))); + strUsage += HelpMessageOpt("-paytxfee=", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); + strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions") + " " + _("on startup")); + strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet file") + " " + _("on startup")); + strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE)); + strUsage += HelpMessageOpt("-txconfirmtarget=", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), 1)); + strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format") + " " + _("on startup")); + strUsage += HelpMessageOpt("-wallet=", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT)); + strUsage += HelpMessageOpt("-walletnotify=", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); + strUsage += HelpMessageOpt("-zapwallettxes=", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + + " " + _("(1 = keep tx meta data e.g. payment request information, 2 = drop tx meta data)")); + strUsage += HelpMessageGroup(_("Mining/Staking options:")); + strUsage += HelpMessageOpt("-coldstaking=", strprintf(_("Enable cold staking functionality (0-1, default: %u). Disabled if staking=0"), DEFAULT_COLDSTAKING)); + strUsage += HelpMessageOpt("-gen", strprintf(_("Generate coins (default: %u)"), DEFAULT_GENERATE)); + strUsage += HelpMessageOpt("-genproclimit=", strprintf(_("Set the number of threads for coin generation if enabled (-1 = all cores, default: %d)"), DEFAULT_GENERATE_PROCLIMIT)); + strUsage += HelpMessageOpt("-minstakesplit=", strprintf(_("Minimum positive amount (in PIV) allowed by GUI and RPC for the stake split threshold (default: %s)"), FormatMoney(DEFAULT_MIN_STAKE_SPLIT_THRESHOLD))); + strUsage += HelpMessageOpt("-staking=", strprintf(_("Enable staking functionality (0-1, default: %u)"), DEFAULT_STAKING)); + if (showDebug) { + strUsage += HelpMessageGroup(_("Wallet debugging/testing options:")); + strUsage += HelpMessageOpt("-dblogsize=", strprintf(_("Flush database activity from memory pool to disk log every megabytes (default: %u)"), DEFAULT_WALLET_DBLOGSIZE)); + strUsage += HelpMessageOpt("-flushwallet", strprintf(_("Run a thread to flush wallet periodically (default: %u)"), DEFAULT_FLUSHWALLET)); + strUsage += HelpMessageOpt("-printcoinstake", _("Display verbose coin stake messages in the debug.log file.")); + strUsage += HelpMessageOpt("-privdb", strprintf(_("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)"), DEFAULT_WALLET_PRIVDB)); + } + + return strUsage; +} + +bool WalletParameterInteraction() +{ + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { + return true; + } + + if (gArgs.GetBoolArg("-sysperms", false)) { + return UIError(strprintf(_("%s is not allowed in combination with enabled wallet functionality"), "-sysperms")); + } + + gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT); + const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; + + if (gArgs.GetBoolArg("-salvagewallet", false)) { + if (is_multiwallet) { + return UIError(strprintf(_("%s is only allowed with a single wallet file"), "-salvagewallet")); + } + // Rewrite just private keys: rescan to find transactions + if (gArgs.SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); + } + } + + bool zapwallettxes = gArgs.GetBoolArg("-zapwallettxes", false); + // -zapwallettxes implies dropping the mempool on startup + if (zapwallettxes && gArgs.SoftSetBoolArg("-persistmempool", false)) { + LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -persistmempool=0\n", __func__, zapwallettxes); + } + + // -zapwallettxes implies a rescan + if (zapwallettxes) { + if (is_multiwallet) { + return UIError(strprintf(_("%s is only allowed with a single wallet file"), "-zapwallettxes")); + } + if (gArgs.SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -zapwallettxes= -> setting -rescan=1\n", __func__); + } + } + + if (is_multiwallet) { + if (gArgs.GetBoolArg("-upgradewallet", false)) { + return UIError(strprintf(_("%s is only allowed with a single wallet file"), "-upgradewallet")); + } + } + + if (gArgs.IsArgSet("-mintxfee")) { + CAmount n = 0; + if (ParseMoney(gArgs.GetArg("-mintxfee", ""), n) && n > 0) + CWallet::minTxFee = CFeeRate(n); + else + return UIError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); + } + if (gArgs.IsArgSet("-paytxfee")) { + CAmount nFeePerK = 0; + if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) + return UIError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); + if (nFeePerK > nHighTransactionFeeWarning) + UIWarning(strprintf(_("Warning: %s is set very high! This is the transaction fee you will pay if you send a transaction."), "-paytxfee")); + payTxFee = CFeeRate(nFeePerK, 1000); + if (payTxFee < ::minRelayTxFee) { + return UIError(strprintf(_("Invalid amount for %s: '%s' (must be at least %s)"), "-paytxfee", + gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString())); + } + } + if (gArgs.IsArgSet("-maxtxfee")) { + CAmount nMaxFee = 0; + if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) + return UIError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); + if (nMaxFee > nHighTransactionMaxFeeWarning) + UIWarning(strprintf(_("Warning: %s is set very high! Fees this large could be paid on a single transaction."), "-maxtxfee")); + maxTxFee = nMaxFee; + if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee) { + return UIError(strprintf(_("Invalid amount for %s: '%s' (must be at least the minimum relay fee of %s to prevent stuck transactions)"), + "-maxtxfee", gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString())); + } + } + if (gArgs.IsArgSet("-minstakesplit")) { + CAmount n = 0; + if (ParseMoney(gArgs.GetArg("-minstakesplit", ""), n) && n > 0) + CWallet::minStakeSplitThreshold = n; + else + return UIError(AmountErrMsg("minstakesplit", gArgs.GetArg("-minstakesplit", ""))); + } + nTxConfirmTarget = gArgs.GetArg("-txconfirmtarget", 1); + bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); + bdisableSystemnotifications = gArgs.GetBoolArg("-disablesystemnotifications", false); + + return true; +} + +bool WalletVerify() +{ + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { + return true; + } + + uiInterface.InitMessage(_("Verifying wallet(s)...")); + + // Keep track of each wallet absolute path to detect duplicates. + std::set wallet_paths; + + for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { + if (fs::path(walletFile).filename() != walletFile) { + return UIError(strprintf(_("Error loading wallet %s. %s parameter must only specify a filename (not a path)."), walletFile, "-wallet")); + } + if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { + return UIError(strprintf(_("Error loading wallet %s. Invalid characters in %s filename."), walletFile, "-wallet")); + } + + fs::path wallet_path = fs::absolute(walletFile, GetDataDir()); + + if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) { + return UIError(strprintf(_("Error loading wallet %s. %s filename must be a regular file."), walletFile, "-wallet")); + } + + if (!wallet_paths.insert(wallet_path).second) { + return UIError(strprintf(_("Error loading wallet %s. Duplicate %s filename specified."), walletFile, "-wallet")); + } + + std::string strError; + if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) { + return UIError(strError); + } + + if (gArgs.GetBoolArg("-salvagewallet", false)) { + // Recover readable keypairs: + CWallet dummyWallet; + std::string backup_filename; + // Even if we don't use this lock in this function, we want to preserve + // lock order in LoadToWallet if query of chain state is needed to know + // tx status. If lock can't be taken, tx confirmation status may be not + // reliable. + LOCK(cs_main); + if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) { + return false; + } + } + + std::string strWarning; + bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError); + if (!strWarning.empty()) { + UIWarning(strWarning); + } + if (!dbV) { + return UIError(strError); + } + } + + return true; +} + +bool InitLoadWallet() +{ + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { + LogPrintf("Wallet disabled!\n"); + return true; + } + + for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { + // automatic backups + std::string strWarning, strError; + if(!AutoBackupWallet(walletFile, strWarning, strError)) { + if (!strWarning.empty()) { + UIWarning(strprintf("%s: %s", walletFile, strWarning)); + } + if (!strError.empty()) { + return UIError(strprintf("%s: %s", walletFile, strError)); + } + } + + CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile); + if (!pwallet) { + return false; + } + vpwallets.emplace_back(pwallet); + } + + return true; +} diff --git a/src/wallet/init.h b/src/wallet/init.h new file mode 100644 index 0000000000000..f63844b99da26 --- /dev/null +++ b/src/wallet/init.h @@ -0,0 +1,26 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_WALLET_INIT_H +#define PIVX_WALLET_INIT_H + +#include + +//! Return the wallets help message. +std::string GetWalletHelpString(bool showDebug); + +//! Wallets parameter interaction +bool WalletParameterInteraction(); + +//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. +// This function will perform salvage on the wallet if requested, as long as only one wallet is +// being loaded (CWallet::ParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +bool WalletVerify(); + +//! Load wallet databases. +bool InitLoadWallet(); + +#endif // PIVX_WALLET_INIT_H diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 6265a8f41586e..e8f7de0b9e712 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -86,9 +86,8 @@ UniValue importprivkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( "importprivkey \"privkey\" ( \"label\" rescan is_staking_address )\n" - "\nAdds a private key (as returned by dumpprivkey) to your wallet.\n" + + "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" @@ -215,8 +214,7 @@ UniValue importaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( "importaddress \"script\" ( \"label\" rescan )\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" - + "\nAdds a script (in hex), or address, that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "\nArguments:\n" "1. \"script\" (string, required) hex-encoded script (or address)\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" @@ -282,7 +280,7 @@ UniValue importpubkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::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" + "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "\nArguments:\n" "1. \"pubkey\" (string, required) The hex-encoded public key\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" @@ -339,9 +337,8 @@ UniValue importwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "importwallet \"filename\"\n" - "\nImports keys from a wallet dump file (see dumpwallet).\n" + + "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup.\n" + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" "1. \"filename\" (string, required) The wallet file\n" @@ -506,9 +503,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "dumpwallet \"filename\"\n" - "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n" + + "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n" + "Imported scripts are not currently included in wallet dumps, these must be backed up separately.\n" + "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n" + "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n" + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" "1. \"filename\" (string, required) The filename\n" @@ -967,7 +966,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw std::runtime_error( "importmulti \"requests\" ( \"options\" )\n" - "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options).\n" + + "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n" + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e0a879746589d..f9eeeb32496de 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -13,7 +13,7 @@ #include "coincontrol.h" #include "core_io.h" #include "destination_io.h" -#include "init.h" +#include "httpserver.h" #include "key_io.h" #include "masternode-sync.h" #include "net.h" @@ -29,15 +29,27 @@ #include "wallet/walletdb.h" #include "zpivchain.h" +#include // for StartShutdown + #include #include +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_WALLET_NOT_FOUND, "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) @@ -47,13 +59,14 @@ std::string HelpRequiringPassphrase(CWallet* const pwallet) bool EnsureWalletIsAvailable(CWallet* const pwallet, bool avoidException) { - if (!pwallet) { - if (!avoidException) - throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (wallet disabled)"); - else - return false; + if (pwallet) return true; + if (avoidException) return false; + if (::vpwallets.empty()) { + // Wallet RPC methods are disabled if no wallets are loaded. + throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (disabled)"); } - return true; + throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED, + "Wallet file not specified (must request wallet RPC through /wallet/ uri-path)."); } void EnsureWalletIsUnlocked(CWallet* const pwallet, bool fAllowAnonOnly) @@ -2395,7 +2408,7 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( "addmultisigaddress nrequired [\"key\",...] ( \"label\" )\n" - "\nAdd a nrequired-to-sign multisignature address to the wallet.\n" + "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" "Each key is a PIVX address or hex-encoded public key.\n" "If 'label' is specified, assign address to that label.\n" @@ -3362,7 +3375,7 @@ UniValue walletpassphrase(const JSONRPCRequest& request) if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - if (pwallet->IsCrypted() && (request.fHelp || request.params.size() < 2 || request.params.size() > 3)) + if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { throw std::runtime_error( "walletpassphrase \"passphrase\" timeout ( staking_only )\n" "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" @@ -3386,6 +3399,7 @@ UniValue walletpassphrase(const JSONRPCRequest& request) HelpExampleCli("walletlock", "") + "\nAs json rpc call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")); + } LOCK2(cs_main, pwallet->cs_wallet); @@ -3441,7 +3455,7 @@ UniValue walletpassphrasechange(const JSONRPCRequest& request) if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) + if (request.fHelp || request.params.size() != 2) { throw std::runtime_error( "walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n" "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n" @@ -3452,6 +3466,7 @@ UniValue walletpassphrasechange(const JSONRPCRequest& request) "\nExamples:\n" + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")); + } LOCK2(cs_main, pwallet->cs_wallet); @@ -3489,7 +3504,7 @@ UniValue walletlock(const JSONRPCRequest& request) if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 0)) + if (request.fHelp || !request.params.empty()) { throw std::runtime_error( "walletlock\n" "\nRemoves the wallet encryption key from memory, locking the wallet.\n" @@ -3505,6 +3520,7 @@ UniValue walletlock(const JSONRPCRequest& request) HelpExampleCli("walletlock", "") + "\nAs json rpc call\n" + HelpExampleRpc("walletlock", "")); + } // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -3531,7 +3547,7 @@ UniValue encryptwallet(const JSONRPCRequest& request) if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - if (!pwallet->IsCrypted() && (request.fHelp || request.params.size() != 1)) + if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "encryptwallet \"passphrase\"\n" "\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n" @@ -3555,6 +3571,7 @@ UniValue encryptwallet(const JSONRPCRequest& request) HelpExampleCli("walletlock", "") + "\nAs a json rpc call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")); + } LOCK2(cs_main, pwallet->cs_wallet); @@ -3591,9 +3608,9 @@ UniValue listunspent(const JSONRPCRequest& request) if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - if (request.fHelp || request.params.size() > 5) + if (request.fHelp || request.params.size() > 6) throw std::runtime_error( - "listunspent ( minconf maxconf [\"address\",...] watchonly_config [query_options])\n" + "listunspent ( minconf maxconf [\"address\",...] watchonly_config [query_options] include_unsafe)\n" "\nReturns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" "Optionally filter to only include txouts paid to specified addresses.\n" @@ -3616,6 +3633,9 @@ UniValue listunspent(const JSONRPCRequest& request) " \"maximumCount\" (numeric or string, default=unlimited) Maximum number of UTXOs\n" " \"minimumSumAmount\" (numeric or string, default=unlimited) Minimum sum value of all UTXOs in " + CURRENCY_UNIT + "\n" " }\n" + "6. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n" + " See description of \"safe\" attribute below.\n" + "\nResult\n" "[ (array of json object)\n" " {\n" @@ -3629,7 +3649,10 @@ UniValue listunspent(const JSONRPCRequest& request) " \"amount\" : x.xxx, (numeric) the transaction amount in PIV\n" " \"confirmations\" : n, (numeric) The number of confirmations\n" " \"spendable\" : true|false (boolean) Whether we have the private keys to spend this output\n" - " \"solvable\" : xxx (bool) Whether we know how to spend this output, ignoring the lack of keys\n" + " \"solvable\" : xxx (boolean) Whether we know how to spend this output, ignoring the lack of keys\n" + " \"safe\" : xxx (boolean) Whether this output is considered safe to spend. Unconfirmed transactions\n" + " from outside keys and unconfirmed replacement transactions are considered unsafe\n" + " and are not eligible for spending by fundrawtransaction and sendtoaddress.\n" " }\n" " ,...\n" "]\n" @@ -3638,6 +3661,7 @@ UniValue listunspent(const JSONRPCRequest& request) HelpExampleCli("listunspent", "") + HelpExampleCli("listunspent", "6 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleCli("listunspent", "6 9999999 '[]' 1 '{ \"minimumAmount\": 0.005 }'") + + HelpExampleCli("listunspent", "6 9999999 '[]' 1 '{ \"minimumAmount\": 0.005 }' false") + HelpExampleRpc("listunspent", "6, 9999999, [] , 1, { \"minimumAmount\": 0.005 } ") ); @@ -3713,11 +3737,13 @@ UniValue listunspent(const JSONRPCRequest& request) CCoinControl coinControl; coinControl.fAllowWatchOnly = nWatchonlyConfig == 2; + bool include_unsafe = request.params.size() < 6 || request.params[5].get_bool(); + coinFilter.fOnlySafe = !include_unsafe; + UniValue results(UniValue::VARR); std::vector vecOutputs; LOCK2(cs_main, pwallet->cs_wallet); - coinFilter.fOnlyConfirmed = false; pwallet->AvailableCoins(&vecOutputs, &coinControl, coinFilter); for (const COutput& out : vecOutputs) { if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) @@ -3759,6 +3785,7 @@ UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("confirmations", out.nDepth); entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); + entry.pushKV("safe", out.fSafe); results.push_back(entry); } @@ -3989,6 +4016,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request) "\nResult:\n" "{\n" + " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" " \"balance\": xxxxxxx, (numeric) the total PIV balance of the wallet (cold balance excluded)\n" " \"delegated_balance\": xxxxx, (numeric) the PIV balance held in P2CS (cold staking) contracts\n" @@ -4020,6 +4048,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); + obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); obj.pushKV("balance", ValueFromAmount(pwallet->GetAvailableBalance())); obj.pushKV("delegated_balance", ValueFromAmount(pwallet->GetDelegatedBalance())); @@ -4058,6 +4087,37 @@ UniValue getwalletinfo(const JSONRPCRequest& request) return obj; } +UniValue listwallets(const JSONRPCRequest& request) +{ + if (request.fHelp || !request.params.empty()) + throw std::runtime_error( + "listwallets\n" + "Returns a list of currently loaded wallets.\n" + "For full information on the wallet, use \"getwalletinfo\"\n" + "\nResult:\n" + "[ (json array of strings)\n" + " \"walletname\" (string) the wallet name\n" + " ...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("listwallets", "") + + HelpExampleRpc("listwallets", "") + ); + + UniValue obj(UniValue::VARR); + + for (CWalletRef pwallet : vpwallets) { + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + LOCK(pwallet->cs_wallet); + obj.push_back(pwallet->GetName()); + } + + return obj; +} + UniValue getstakingstatus(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -4491,7 +4551,8 @@ static const CRPCCommand commands[] = { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly","filter"} }, { "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} }, { "wallet", "listtransactions", &listtransactions, false, {"dummy","count","from","include_watchonly","include_delegated","include_cold"} }, - { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","watchonly_config" } }, + { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","watchonly_config","query_options","include_unsafe" } }, + { "wallet", "listwallets", &listwallets, true, {} }, { "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} }, { "wallet", "rawdelegatestake", &rawdelegatestake, false, {"staking_addr","amount","owner_addr","ext_owner","include_delegated","from_shield","force"} }, { "wallet", "sendmany", &sendmany, false, {"dummy","amounts","minconf","comment","include_delegated"} }, diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 483073906c1fa..09213a4b9fe8f 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -53,7 +53,7 @@ static void add_coin(std::unique_ptr& pwallet, const CAmount& nValue, i if (fIsFromMe) { wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); } - COutput output(wtx.get(), nInput, nAge, true, true); + COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); wtxn.emplace_back(std::move(wtx)); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d7b86d9bee64a..3e084d7eda065 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -10,7 +10,6 @@ #include "budget/budgetmanager.h" #include "coincontrol.h" #include "evo/deterministicmns.h" -#include "init.h" #include "guiinterfaceutil.h" #include "masternode.h" #include "masternode-payments.h" @@ -21,8 +20,11 @@ #include "spork.h" #include "util/system.h" #include "utilmoneystr.h" +#include "wallet/fees.h" #include "zpivchain.h" +#include // for StartShutdown/ShutdownRequested + #include #include @@ -72,6 +74,53 @@ std::string COutput::ToString() const return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } +class CAffectedKeysVisitor : public boost::static_visitor +{ +private: + const CKeyStore& keystore; + std::vector& vKeys; + +public: + CAffectedKeysVisitor(const CKeyStore& keystoreIn, std::vector& vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} + + void Process(const CScript& script) + { + txnouttype type; + std::vector vDest; + int nRequired; + if (ExtractDestinations(script, type, vDest, nRequired)) { + for (const CTxDestination& dest : vDest) + boost::apply_visitor(*this, dest); + } + } + + void operator()(const CKeyID& keyId) + { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CScriptID& scriptId) + { + CScript script; + if (keystore.GetCScript(scriptId, script)) + Process(script); + } + + void operator()(const CNoDestination& none) {} +}; + +std::vector CWallet::GetAffectedKeys(const CScript& spk) +{ + std::vector ret; + std::vector vAffected; + CAffectedKeysVisitor(*this, vAffected).Process(spk); + for (const CKeyID& keyid : vAffected) { + ret.emplace_back(keyid); + } + return ret; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -643,96 +692,6 @@ void CWallet::SyncMetaData(std::pair::iterator, typename } } -///////// Init //////////////// - -bool CWallet::ParameterInteraction() -{ - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return true; - } - - if (gArgs.GetBoolArg("-sysperms", false)) { - return UIError(strprintf(_("%s is not allowed in combination with enabled wallet functionality"), "-sysperms")); - } - - gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT); - const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; - - if (gArgs.GetBoolArg("-salvagewallet", false) && gArgs.SoftSetBoolArg("-rescan", true)) { - if (is_multiwallet) { - return UIError(strprintf(_("%s is only allowed with a single wallet file"), "-salvagewallet")); - } - // Rewrite just private keys: rescan to find transactions - LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); - } - - int zapwallettxes = gArgs.GetArg("-zapwallettxes", 0); - // -zapwallettxes implies dropping the mempool on startup - if (zapwallettxes != 0 && gArgs.SoftSetBoolArg("-persistmempool", false)) { - LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -persistmempool=0\n", __func__, zapwallettxes); - } - - // -zapwallettxes implies a rescan - if (zapwallettxes != 0 && gArgs.SoftSetBoolArg("-rescan", true)) { - if (is_multiwallet) { - return UIError(strprintf(_("%s is only allowed with a single wallet file"), "-zapwallettxes")); - } - LogPrintf("%s: parameter interaction: -zapwallettxes= -> setting -rescan=1\n", __func__); - } - - if (is_multiwallet) { - if (gArgs.GetBoolArg("-upgradewallet", false)) { - return UIError(strprintf(_("%s is only allowed with a single wallet file"), "-upgradewallet")); - } - } - - if (gArgs.IsArgSet("-mintxfee")) { - CAmount n = 0; - if (ParseMoney(gArgs.GetArg("-mintxfee", ""), n) && n > 0) - CWallet::minTxFee = CFeeRate(n); - else - return UIError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); - } - if (gArgs.IsArgSet("-paytxfee")) { - CAmount nFeePerK = 0; - if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) - return UIError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); - if (nFeePerK > nHighTransactionFeeWarning) - UIWarning(strprintf(_("Warning: %s is set very high! This is the transaction fee you will pay if you send a transaction."), "-paytxfee")); - payTxFee = CFeeRate(nFeePerK, 1000); - if (payTxFee < ::minRelayTxFee) { - return UIError(strprintf(_("Invalid amount for %s: '%s' (must be at least %s)"), "-paytxfee", - gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString())); - } - } - if (gArgs.IsArgSet("-maxtxfee")) { - CAmount nMaxFee = 0; - if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) - return UIError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); - if (nMaxFee > nHighTransactionMaxFeeWarning) - UIWarning(strprintf(_("Warning: %s is set very high! Fees this large could be paid on a single transaction."), "-maxtxfee")); - maxTxFee = nMaxFee; - if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee) { - return UIError(strprintf(_("Invalid amount for %s: '%s' (must be at least the minimum relay fee of %s to prevent stuck transactions)"), - "-maxtxfee", gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString())); - } - } - if (gArgs.IsArgSet("-minstakesplit")) { - CAmount n = 0; - if (ParseMoney(gArgs.GetArg("-minstakesplit", ""), n) && n > 0) - CWallet::minStakeSplitThreshold = n; - else - return UIError(AmountErrMsg("minstakesplit", gArgs.GetArg("-minstakesplit", ""))); - } - nTxConfirmTarget = gArgs.GetArg("-txconfirmtarget", 1); - bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); - bdisableSystemnotifications = gArgs.GetBoolArg("-disablesystemnotifications", false); - - return true; -} - -//////// End Init //////////// - const CKeyingMaterial& CWallet::GetEncryptionKey() const { return vMasterKey; @@ -2109,53 +2068,6 @@ void CWallet::Flush(bool shutdown) bitdb.Flush(shutdown); } -bool CWallet::Verify() -{ - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return true; - } - - uiInterface.InitMessage(_("Verifying wallet(s)...")); - - for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - if (fs::path(walletFile).filename() != walletFile) { - return UIError(strprintf(_("%s parameter must only specify a filename (not a path)"), "-wallet")); - } else if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { - return UIError(strprintf(_("Invalid characters in %s filename"), "-wallet")); - } - - std::string strError; - if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) { - return UIError(strError); - } - - if (gArgs.GetBoolArg("-salvagewallet", false)) { - // Recover readable keypairs: - CWallet dummyWallet; - std::string backup_filename; - // Even if we don't use this lock in this function, we want to preserve - // lock order in LoadToWallet if query of chain state is needed to know - // tx status. If lock can't be taken, tx confirmation status may be not - // reliable. - LOCK(cs_main); - if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) { - return false; - } - } - - std::string strWarning; - bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError); - if (!strWarning.empty()) { - UIWarning(strWarning); - } - if (!dbV) { - return UIError(strError); - } - } - - return true; -} - void CWallet::ResendWalletTransactions(CConnman* connman) { // Do this infrequently and randomly to avoid giving away @@ -2439,6 +2351,8 @@ void CWallet::GetAvailableP2CSCoins(std::vector& vCoins) const { if (fConflicted || nDepth < 0) continue; + bool fSafe = pcoin->IsTrusted(); + if (pcoin->tx->HasP2CSOutputs()) { for (int i = 0; i < (int) pcoin->tx->vout.size(); i++) { const auto &utxo = pcoin->tx->vout[i]; @@ -2451,7 +2365,7 @@ void CWallet::GetAvailableP2CSCoins(std::vector& vCoins) const { bool isMineSpendable = mine & ISMINE_SPENDABLE_DELEGATED; if (mine & ISMINE_COLD || isMineSpendable) // Depth and solvability members are not used, no need waste resources and set them for now. - vCoins.emplace_back(pcoin, i, 0, isMineSpendable, true); + vCoins.emplace_back(pcoin, i, 0, isMineSpendable, true, fSafe); } } } @@ -2463,9 +2377,10 @@ void CWallet::GetAvailableP2CSCoins(std::vector& vCoins) const { /** * Test if the transaction is spendable. */ -static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlyConfirmed, int& nDepth) +static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlySafe, int& nDepth, bool& safeTx) { - if (fOnlyConfirmed && !pcoin->IsTrusted()) return false; + safeTx = pcoin->IsTrusted(); + if (fOnlySafe && !safeTx) return false; if (pcoin->GetBlocksToMaturity() > 0) return false; nDepth = pcoin->GetDepthInMainChain(); @@ -2478,22 +2393,23 @@ static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlyConfir } // cs_main lock required -static bool CheckTXAvailability(const CWalletTx* pcoin, bool fOnlyConfirmed, int& nDepth) +static bool CheckTXAvailability(const CWalletTx* pcoin, bool fOnlySafe, int& nDepth, bool& safeTx) { AssertLockHeld(cs_main); if (!CheckFinalTx(pcoin->tx)) return false; - return CheckTXAvailabilityInternal(pcoin, fOnlyConfirmed, nDepth); + return CheckTXAvailabilityInternal(pcoin, fOnlySafe, nDepth, safeTx); } // cs_main lock NOT required static bool CheckTXAvailability(const CWalletTx* pcoin, - bool fOnlyConfirmed, + bool fOnlySafe, int& nDepth, + bool& safeTx, int nBlockHeight) { // Mimic CheckFinalTx without cs_main lock if (!IsFinalTx(pcoin->tx, nBlockHeight + 1, GetAdjustedTime())) return false; - return CheckTXAvailabilityInternal(pcoin, fOnlyConfirmed, nDepth); + return CheckTXAvailabilityInternal(pcoin, fOnlySafe, nDepth, safeTx); } bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, std::string strTxHash, std::string strOutputIndex, std::string& strError) @@ -2537,10 +2453,11 @@ bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& } int nDepth = 0; + bool safeTx = false; { LOCK(cs_wallet); // Check availability - if (!CheckTXAvailability(wtx, true, nDepth, m_last_block_processed_height)) { + if (!CheckTXAvailability(wtx, true, nDepth, safeTx, m_last_block_processed_height)) { strError = "Not available collateral transaction"; return error("%s: tx %s not available", __func__, strTxHash); } @@ -2566,7 +2483,7 @@ bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& } return GetVinAndKeysFromOutput( - COutput(wtx, nOutputIndex, nDepth, true, true), + COutput(wtx, nOutputIndex, nDepth, true, true, true), txinRet, pubKeyRet, keyRet); @@ -2638,8 +2555,9 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates const CWalletTx* pcoin = &(*it).second; // Check if the tx is selectable - int nDepth; - if (!CheckTXAvailability(pcoin, coinsFilter.fOnlyConfirmed, nDepth, m_last_block_processed_height)) + int nDepth = 0; + bool safeTx = false; + if (!CheckTXAvailability(pcoin, coinsFilter.fOnlySafe, nDepth, safeTx, m_last_block_processed_height)) continue; // Check min depth filtering requirements @@ -2680,7 +2598,7 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates // found valid coin if (!pCoins) return true; - pCoins->emplace_back(pcoin, (int) i, nDepth, res.spendable, res.solvable); + pCoins->emplace_back(pcoin, (int) i, nDepth, res.spendable, res.solvable, safeTx); // Checks the sum amount of all UTXO's. if (coinsFilter.nMinimumSumAmount != 0) { @@ -2701,11 +2619,11 @@ bool CWallet::AvailableCoins(std::vector* pCoins, // --> populates } } -std::map > CWallet::AvailableCoinsByAddress(bool fConfirmed, CAmount maxCoinValue, bool fIncludeColdStaking) +std::map > CWallet::AvailableCoinsByAddress(bool fOnlySafe, CAmount maxCoinValue, bool fIncludeColdStaking) { CWallet::AvailableCoinsFilter coinFilter; coinFilter.fIncludeColdStaking = true; - coinFilter.fOnlyConfirmed = fConfirmed; + coinFilter.fOnlySafe = fOnlySafe; coinFilter.fIncludeColdStaking = fIncludeColdStaking; coinFilter.nMaxOutValue = maxCoinValue; std::vector vCoins; @@ -2733,7 +2651,8 @@ std::map > CWallet::AvailableCoinsByAddres return mapCoins; } -static void ApproximateBestSubset(std::vector > > vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, std::vector& vfBest, CAmount& nBest, int iterations = 1000) +static void ApproximateBestSubset(const std::vector > >& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, + std::vector& vfBest, CAmount& nBest, int iterations = 1000) { std::vector vfIncluded; @@ -2785,8 +2704,9 @@ bool CWallet::StakeableCoins(std::vector* pCoins) const CWalletTx* pcoin = &(it).second; // Check if the tx is selectable - int nDepth; - if (!CheckTXAvailability(pcoin, true, nDepth)) + int nDepth = 0; + bool safeTx = false; + if (!CheckTXAvailability(pcoin, true, nDepth, safeTx)) continue; // Check min depth requirement for stake inputs @@ -2810,7 +2730,7 @@ bool CWallet::StakeableCoins(std::vector* pCoins) // found valid coin if (!pCoins) return true; if (!pindex) pindex = mapBlockIndex.at(pcoin->m_confirm.hashBlock); - pCoins->emplace_back(CStakeableOutput(pcoin, (int) index, nDepth, res.spendable, res.solvable, pindex)); + pCoins->emplace_back(pcoin, (int) index, nDepth, pindex); } } return (pCoins && !pCoins->empty()); @@ -2961,6 +2881,41 @@ bool CWallet::SelectCoinsToSpend(const std::vector& vAvailableCoins, co return res; } +std::map>, std::vector> CWallet::ListCoins() const +{ + std::map>, std::vector> result; + + CWallet::AvailableCoinsFilter filter; + filter.fIncludeLocked = true; + filter.fOnlySpendable = true; + std::vector availableCoins; + AvailableCoins(&availableCoins, nullptr, filter); + + for (const COutput& coin : availableCoins) { + const CScript& scriptPubKey = coin.tx->tx->vout[coin.i].scriptPubKey; + txnouttype type; std::vector addresses; int nRequired; + if (ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { + if (addresses.size() == 1) { + // P2PK, P2PKH scripts + const auto& addrpair = std::make_pair(addresses[0], nullopt); + result[addrpair].emplace_back(std::move(coin)); + } else if (type == TX_COLDSTAKE) { + // P2CS scripts + assert(addresses.size() == 2); + const auto& addrpair = std::make_pair(addresses[1], Optional(addresses[0])); + result[addrpair].emplace_back(std::move(coin)); + } + } + } + + return result; +} + +std::map> CWallet::ListNotes() const +{ + return m_sspk_man->ListNotes(); +} + bool CWallet::CreateBudgetFeeTX(CTransactionRef& tx, const uint256& hash, CReserveKey& keyChange, bool fFinalization) { CScript scriptChange; @@ -3086,7 +3041,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, // Fill outputs for (const CRecipient& rec : vecSend) { CTxOut txout(rec.nAmount, rec.scriptPubKey); - if (IsDust(txout, ::minRelayTxFee)) { + if (IsDust(txout, dustRelayFee)) { strFailReason = _("Transaction amount too small"); return false; } @@ -3151,7 +3106,7 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, // Never create dust outputs; if we would, just // add the dust to the fee. - if (IsDust(newTxOut, ::minRelayTxFee)) { + if (IsDust(newTxOut, dustRelayFee)) { nFeeRet += nChange; nChange = 0; reservekey.ReturnKey(); @@ -3168,8 +3123,10 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, txNew.vout.insert(position, newTxOut); } } - } else + } else { reservekey.ReturnKey(); + nChangePosInOut = -1; + } // Fill vin for (const std::pair& coin : setCoins) { @@ -3481,33 +3438,6 @@ CWallet::CommitResult CWallet::CommitTransaction(CTransactionRef tx, CReserveKey return res; } -CAmount CWallet::GetRequiredFee(unsigned int nTxBytes) -{ - return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); -} - -CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool) -{ - // payTxFee is user-set "I want to pay this much" - CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes); - // User didn't set: use -txconfirmtarget to estimate... - if (nFeeNeeded == 0) { - int estimateFoundTarget = (int) nConfirmTarget; - nFeeNeeded = pool.estimateSmartFee((int) nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes); - // ... unless we don't have enough mempool data for our desired target - // so we make sure we're paying at least minTxFee - if (nFeeNeeded == 0 || (unsigned int) estimateFoundTarget > nConfirmTarget) - nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes)); - } - // prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee - if (nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes)) - nFeeNeeded = ::minRelayTxFee.GetFee(nTxBytes); - // But always obey the maximum - if (nFeeNeeded > maxTxFee) - nFeeNeeded = maxTxFee; - return nFeeNeeded; -} - DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK2(cs_main, cs_wallet); @@ -3687,8 +3617,8 @@ std::map CWallet::GetAddressBalances() { LOCK(cs_wallet); - for (std::pair walletEntry : mapWallet) { - CWalletTx* pcoin = &walletEntry.second; + for (const auto& walletEntry : mapWallet) { + const CWalletTx* pcoin = &walletEntry.second; if (!IsFinalTx(pcoin->tx, m_last_block_processed_height) || !pcoin->IsTrusted()) continue; @@ -3729,8 +3659,8 @@ std::set > CWallet::GetAddressGroupings() std::set > groupings; std::set grouping; - for (std::pair walletEntry : mapWallet) { - CWalletTx* pcoin = &walletEntry.second; + for (const auto& walletEntry : mapWallet) { + const CWalletTx* pcoin = &walletEntry.second; if (pcoin->tx->vin.size() > 0) { bool any_mine = false; @@ -3914,53 +3844,6 @@ bool CWallet::SetStakeSplitThreshold(const CAmount sst) /** @} */ // end of Actions -class CAffectedKeysVisitor : public boost::static_visitor -{ -private: - const CKeyStore& keystore; - std::vector& vKeys; - -public: - CAffectedKeysVisitor(const CKeyStore& keystoreIn, std::vector& vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} - - void Process(const CScript& script) - { - txnouttype type; - std::vector vDest; - int nRequired; - if (ExtractDestinations(script, type, vDest, nRequired)) { - for (const CTxDestination& dest : vDest) - boost::apply_visitor(*this, dest); - } - } - - void operator()(const CKeyID& keyId) - { - if (keystore.HaveKey(keyId)) - vKeys.push_back(keyId); - } - - void operator()(const CScriptID& scriptId) - { - CScript script; - if (keystore.GetCScript(scriptId, script)) - Process(script); - } - - void operator()(const CNoDestination& none) {} -}; - -std::vector CWallet::GetAffectedKeys(const CScript& spk) -{ - std::vector ret; - std::vector vAffected; - CAffectedKeysVisitor(*this, vAffected).Process(spk); - for (const CKeyID& keyid : vAffected) { - ret.emplace_back(keyid); - } - return ret; -} - void CWallet::GetKeyBirthTimes(std::map& mapKeyBirth) const { @@ -4037,6 +3920,20 @@ bool CWallet::LoadDestData(const CTxDestination& dest, const std::string& key, c return true; } +std::vector CWallet::GetDestValues(const std::string& prefix) const +{ + LOCK(cs_wallet); + std::vector values; + for (const auto& address : mapAddressBook) { + for (const auto& data : address.second.destdata) { + if (!data.first.compare(0, prefix.size(), prefix)) { + values.emplace_back(data.second); + } + } + } + return values; +} + void CWallet::AutoCombineDust(CConnman* connman) { { @@ -4182,44 +4079,6 @@ void CWallet::LockIfMyCollateral(const CTransactionRef& ptx) } } -std::string CWallet::GetWalletHelpString(bool showDebug) -{ - std::string strUsage = HelpMessageGroup(_("Wallet options:")); - strUsage += HelpMessageOpt("-backuppath=", _("Specify custom backup path to add a copy of any wallet backup. If set as dir, every backup generates a timestamped file. If set as file, will rewrite to that file every backup.")); - strUsage += HelpMessageOpt("-createwalletbackups=", strprintf(_("Number of automatic wallet backups (default: %d)"), DEFAULT_CREATEWALLETBACKUPS)); - strUsage += HelpMessageOpt("-custombackupthreshold=", strprintf(_("Number of custom location backups to retain (default: %d)"), DEFAULT_CUSTOMBACKUPTHRESHOLD)); - strUsage += HelpMessageOpt("-disablewallet", strprintf(_("Do not load the wallet and disable wallet RPC calls (default: %u)"), DEFAULT_DISABLE_WALLET)); - strUsage += HelpMessageOpt("-keypool=", strprintf(_("Set key pool size to (default: %u)"), DEFAULT_KEYPOOL_SIZE)); - strUsage += HelpMessageOpt("-legacywallet", _("On first run, create a legacy wallet instead of a HD wallet")); - strUsage += HelpMessageOpt("-maxtxfee=", strprintf(_("Maximum total fees to use in a single wallet transaction, setting too low may abort large transactions (default: %s)"), FormatMoney(maxTxFee))); - strUsage += HelpMessageOpt("-mintxfee=", strprintf(_("Fees (in %s/Kb) smaller than this are considered zero fee for transaction creation (default: %s)"), CURRENCY_UNIT, FormatMoney(CWallet::minTxFee.GetFeePerK()))); - strUsage += HelpMessageOpt("-paytxfee=", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); - strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions") + " " + _("on startup")); - strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet file") + " " + _("on startup")); - strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE)); - strUsage += HelpMessageOpt("-txconfirmtarget=", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), 1)); - strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format") + " " + _("on startup")); - strUsage += HelpMessageOpt("-wallet=", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT)); - strUsage += HelpMessageOpt("-walletnotify=", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); - strUsage += HelpMessageOpt("-zapwallettxes=", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + - " " + _("(1 = keep tx meta data e.g. payment request information, 2 = drop tx meta data)")); - strUsage += HelpMessageGroup(_("Mining/Staking options:")); - strUsage += HelpMessageOpt("-coldstaking=", strprintf(_("Enable cold staking functionality (0-1, default: %u). Disabled if staking=0"), DEFAULT_COLDSTAKING)); - strUsage += HelpMessageOpt("-gen", strprintf(_("Generate coins (default: %u)"), DEFAULT_GENERATE)); - strUsage += HelpMessageOpt("-genproclimit=", strprintf(_("Set the number of threads for coin generation if enabled (-1 = all cores, default: %d)"), DEFAULT_GENERATE_PROCLIMIT)); - strUsage += HelpMessageOpt("-minstakesplit=", strprintf(_("Minimum positive amount (in PIV) allowed by GUI and RPC for the stake split threshold (default: %s)"), FormatMoney(DEFAULT_MIN_STAKE_SPLIT_THRESHOLD))); - strUsage += HelpMessageOpt("-staking=", strprintf(_("Enable staking functionality (0-1, default: %u)"), DEFAULT_STAKING)); - if (showDebug) { - strUsage += HelpMessageGroup(_("Wallet debugging/testing options:")); - strUsage += HelpMessageOpt("-dblogsize=", strprintf(_("Flush database activity from memory pool to disk log every megabytes (default: %u)"), DEFAULT_WALLET_DBLOGSIZE)); - strUsage += HelpMessageOpt("-flushwallet", strprintf(_("Run a thread to flush wallet periodically (default: %u)"), DEFAULT_FLUSHWALLET)); - strUsage += HelpMessageOpt("-printcoinstake", _("Display verbose coin stake messages in the debug.log file.")); - strUsage += HelpMessageOpt("-privdb", strprintf(_("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)"), DEFAULT_WALLET_PRIVDB)); - } - - return strUsage; -} - CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) { // needed to restore wallet transaction meta data after -zapwallettxes @@ -4413,34 +4272,6 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) return walletInstance; } -bool CWallet::InitLoadWallet() -{ - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - LogPrintf("Wallet disabled!\n"); - return true; - } - - for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - // automatic backups - std::string strWarning, strError; - if(!AutoBackupWallet(walletFile, strWarning, strError)) { - if (!strWarning.empty()) { - UIWarning(strprintf("%s: %s", walletFile, strWarning)); - } - if (!strError.empty()) { - return UIError(strprintf("%s: %s", walletFile, strError)); - } - } - - CWallet * const pwallet = CreateWalletFromFile(walletFile); - if (!pwallet) { - return false; - } - vpwallets.emplace_back(pwallet); - } - - return true; -} std::atomic CWallet::fFlushScheduled(false); @@ -4545,6 +4376,7 @@ void CWallet::SetNull() nNextResend = 0; nLastResend = 0; nTimeFirstKey = 0; + nRelockTime = 0; fAbortRescan = false; fScanningWallet = false; fWalletUnlockStaking = false; @@ -4959,7 +4791,10 @@ const CWDestination* CAddressBookIterator::GetDestKey() return &it->first; } -CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, - const CBlockIndex*& _pindex) : COutput(txIn, iIn, nDepthIn, fSpendableIn, fSolvableIn), - pindex(_pindex) {} - +CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, + int iIn, + int nDepthIn, + const CBlockIndex*& _pindex) : + COutput(txIn, iIn, nDepthIn, true /*fSpendable*/, true/*fSolvable*/, true/*fSafe*/), + pindex(_pindex) +{} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d9d8113e21939..3b354245dfd8d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -98,6 +98,7 @@ class CScheduler; class ScriptPubKeyMan; class SaplingScriptPubKeyMan; class SaplingNoteData; +struct SaplingNoteEntry; class CDeterministicMNList; /** (client) version numbers for particular wallet features */ @@ -766,7 +767,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface AvailableCoinsFilter() {} AvailableCoinsFilter(bool _fIncludeDelegated, bool _fIncludeColdStaking, - bool _fOnlyConfirmed, + bool _fOnlySafe, bool _fOnlySpendable, std::set* _onlyFilteredDest, int _minDepth, @@ -774,7 +775,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount _nMaxOutValue = 0) : fIncludeDelegated(_fIncludeDelegated), fIncludeColdStaking(_fIncludeColdStaking), - fOnlyConfirmed(_fOnlyConfirmed), + fOnlySafe(_fOnlySafe), fOnlySpendable(_fOnlySpendable), onlyFilteredDest(_onlyFilteredDest), minDepth(_minDepth), @@ -783,7 +784,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool fIncludeDelegated{true}; bool fIncludeColdStaking{false}; - bool fOnlyConfirmed{true}; + bool fOnlySafe{true}; bool fOnlySpendable{false}; std::set* onlyFilteredDest{nullptr}; int minDepth{0}; @@ -810,6 +811,17 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::map > AvailableCoinsByAddress(bool fConfirmed, CAmount maxCoinValue, bool fIncludeColdStaking); + /** + * Return list of available coins and locked coins grouped by non-change output address. + * PIVX: group coins by pair >. The optional destination + * is reserved for the staker address in case of P2CS. + */ + std::map>, std::vector> ListCoins() const; + /** + * Return list of available shield notes grouped by sapling address. + */ + std::map> ListNotes() const; + /// Get 10000 PIV output and keys which can be used for the Masternode bool GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, std::string strTxHash, std::string strOutputIndex, std::string& strError); @@ -940,6 +952,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool EraseDestData(const CTxDestination& dest, const std::string& key); //! Adds a destination data tuple to the store, without saving it to disk bool LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value); + //! Get all destination values matching a prefix. + std::vector GetDestValues(const std::string& prefix) const; //! Adds a watch-only address to the store, and saves it to disk. bool AddWatchOnly(const CScript& dest) override; @@ -1064,16 +1078,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetUnconfirmedShieldedBalance() const; static CFeeRate minTxFee; - /** - * Estimate the minimum fee considering user set parameters - * and the required fee - */ - static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool); - /** - * Return the minimum required fee taking into account the - * floating relay fee and user set minimum transaction fee - */ - static CAmount GetRequiredFee(unsigned int nTxBytes); size_t KeypoolCountExternalKeys(); bool TopUpKeyPool(unsigned int kpSize = 0); @@ -1147,21 +1151,11 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! Flush wallet (bitdb flush) void Flush(bool shutdown=false); - //! Verify the wallet database and perform salvage if required - static bool Verify(); - /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ bool AbandonTransaction(const uint256& hashTx); - /* Returns the wallets help message */ - static std::string GetWalletHelpString(bool showDebug); - /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ static CWallet* CreateWalletFromFile(const std::string walletFile); - static bool InitLoadWallet(); - - /* Wallets parameter interaction */ - static bool ParameterInteraction(); /** * Wallet post-init setup @@ -1218,6 +1212,10 @@ class CReserveKey pwallet = pwalletIn; } + CReserveKey() = default; + CReserveKey(const CReserveKey&) = delete; + CReserveKey& operator=(const CReserveKey&) = delete; + ~CReserveKey() { ReturnKey(); @@ -1234,11 +1232,23 @@ class COutput const CWalletTx* tx; int i; int nDepth; + + /** Whether we have the private keys to spend this output */ bool fSpendable; + + /** Whether we know how to spend this output, ignoring the lack of keys */ bool fSolvable; - COutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn) : - tx(txIn), i(iIn), nDepth(nDepthIn), fSpendable(fSpendableIn), fSolvable(fSolvableIn) {} + /** + * Whether this output is considered safe to spend. Unconfirmed transactions + * from outside keys and unconfirmed replacement transactions are considered + * unsafe and will not be used to fund new spending transactions. + */ + bool fSafe; + + COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn) : + tx(txIn), i(iIn), nDepth(nDepthIn), fSpendable(fSpendableIn), fSolvable(fSolvableIn), fSafe(fSafeIn) + {} CAmount Value() const { return tx->tx->vout[i].nValue; } std::string ToString() const; @@ -1249,7 +1259,7 @@ class CStakeableOutput : public COutput public: const CBlockIndex* pindex{nullptr}; - CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, + CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, const CBlockIndex*& pindex); }; diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index be9a0717f329b..f3fef36d1cd25 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -38,7 +38,7 @@ def set_test_params(self): self.num_nodes = 1 def run_test(self): - #self._test_getblockchaininfo() + self._test_getblockchaininfo() self._test_gettxoutsetinfo() self._test_getblockheader() #self._test_getdifficulty() @@ -54,6 +54,10 @@ def _test_getblockchaininfo(self): 'chainwork', 'difficulty', 'headers', + 'initial_block_downloading', + 'shield_pool_value', + 'softforks', + 'upgrades', 'verificationprogress', 'warnings', ] diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 396008674f95c..d00dcfd880d3b 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -76,6 +76,12 @@ def run_test(self): self.nodes[0].generate(121) self.sync_all() + # ensure that setting changePosition in fundraw with an exact match is handled properly + out_no_change = 250.0 - 0.0000374 # one coinbase input minus min fee + rawmatch = self.nodes[0].createrawtransaction([], {self.nodes[2].getnewaddress(): DecimalAmt(out_no_change)}) + rawmatch = self.nodes[0].fundrawtransaction(rawmatch, {"changePosition": 1}) + assert_equal(rawmatch["changepos"], -1) + watchonly_address = self.nodes[0].getnewaddress() watchonly_pubkey = self.nodes[0].validateaddress(watchonly_address)["pubkey"] self.watchonly_amount = DecimalAmt(200.0) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 712cb83b5ce1f..b90a8e4826087 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -70,6 +70,7 @@ 'wallet_import_rescan.py', # ~ 204 sec 'p2p_invalid_block.py', # ~ 213 sec 'feature_logging.py', # ~ 195 sec + 'wallet_multiwallet.py', # ~ 190 sec 'wallet_abandonconflict.py', # ~ 188 sec 'feature_blockindexstats.py', # ~ 167 sec 'wallet_importmulti.py', # ~ 157 sec @@ -223,6 +224,7 @@ 'sapling_mempool.py', 'wallet_importmulti.py', 'wallet_import_rescan.py', + 'wallet_multiwallet.py', ] # Place the lists with the longest tests (on average) first diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index b78231f713673..3d5b04d62730e 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -76,6 +76,9 @@ def run_test(self): # Exercise locking of unspent outputs unspent_0 = self.nodes[1].listunspent()[0] + assert unspent_0["solvable"] + assert unspent_0["spendable"] + assert unspent_0["safe"] unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]} self.nodes[1].lockunspent(False, [unspent_0]) assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[1].sendtoaddress, self.nodes[1].getnewaddress(), 20) @@ -84,16 +87,32 @@ def run_test(self): assert_equal(len(self.nodes[1].listlockunspent()), 0) # Send 21 PIV from 1 to 0 using sendtoaddress call. + # Locked memory should use at least 32 bytes to sign the transaction + self.log.info("test getmemoryinfo") + memory_before = self.nodes[0].getmemoryinfo() self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 21) + memory_after = self.nodes[0].getmemoryinfo() + assert(memory_before['locked']['used'] + 32 <= memory_after['locked']['used']) + self.sync_mempools(self.nodes[0:3]) + + # Node0 should have two unspent outputs. + # One safe, the other one not yet + node0utxos = self.nodes[0].listunspent(0) + assert_equal(len(node0utxos), 2) + newutxos = [x for x in node0utxos if x["txid"] != utxos[0]["txid"]] + assert_equal(len(newutxos), 1) + assert not newutxos[0]["safe"] + + # Mine the other tx self.nodes[1].generate(1) self.sync_all(self.nodes[0:3]) + node0utxos = self.nodes[0].listunspent() + assert_equal(len(node0utxos), 2) + for u in node0utxos: + assert u["safe"] - # Node0 should have two unspent outputs. # Create a couple of transactions to send them to node2, submit them through # node1, and make sure both node0 and node2 pick them up properly: - node0utxos = self.nodes[0].listunspent(1) - assert_equal(len(node0utxos), 2) - # create both transactions fee_per_kbyte = Decimal('0.001') txns_to_send = [] diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py new file mode 100755 index 0000000000000..703dbd3a62370 --- /dev/null +++ b/test/functional/wallet_multiwallet.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Copyright (c) 2021 The PIVX developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test multiwallet. + +Verify that a pivxd node can load multiple wallet files +""" +import os +import shutil + +from test_framework.test_framework import PivxTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error + +class MultiWalletTest(PivxTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3', '-wallet=w'], []] + self.supports_cli = True + + def run_test(self): + node = self.nodes[0] + + data_dir = lambda *p: os.path.join(node.datadir, 'regtest', *p) + wallet_dir = lambda *p: data_dir('wallets', *p) + wallet = lambda name: node.get_wallet_rpc(name) + + assert_equal(set(node.listwallets()), {"w1", "w2", "w3", "w"}) + + self.stop_nodes() + + # !TODO: backport bitcoin#12220 + #self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist') + #self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir()) + #self.assert_start_raises_init_error(0, ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir()) + + # should not initialize if there are duplicate wallets + self.assert_start_raises_init_error(0, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.') + + # should not initialize if wallet file is a directory + os.mkdir(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w11')) + #os.mkdir(wallet_dir('w11')) + self.assert_start_raises_init_error(0, ['-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.') + + #should not initialize if one wallet is a copy of another + #shutil.copyfile(wallet_dir('w2'), wallet_dir('w22')) # !TODO: backport bitcoin#11970 + shutil.copyfile(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w2'), + os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w22')) + self.assert_start_raises_init_error(0, ['-wallet=w2', '-wallet=w22'], 'duplicates fileid') + + # should not initialize if wallet file is a symlink + os.symlink(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w1'), + os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w12')) + # os.symlink(wallet_dir('w1'), wallet_dir('w12')) # !TODO: backport bitcoin#11970 + self.assert_start_raises_init_error(0, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.') + + self.log.info("Do not allow -zapwallettxes with multiwallet") + self.assert_start_raises_init_error(0, ['-zapwallettxes', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") + self.assert_start_raises_init_error(0, ['-zapwallettxes=1', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") + self.assert_start_raises_init_error(0, ['-zapwallettxes=2', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") + + self.log.info("Do not allow -salvagewallet with multiwallet") + self.assert_start_raises_init_error(0, ['-salvagewallet', '-wallet=w1', '-wallet=w2'], "Error: -salvagewallet is only allowed with a single wallet file") + self.assert_start_raises_init_error(0, ['-salvagewallet=1', '-wallet=w1', '-wallet=w2'], "Error: -salvagewallet is only allowed with a single wallet file") + + self.log.info("Do not allow -upgradewallet with multiwallet") + self.assert_start_raises_init_error(0, ['-upgradewallet', '-wallet=w1', '-wallet=w2'], "Error: -upgradewallet is only allowed with a single wallet file") + self.assert_start_raises_init_error(0, ['-upgradewallet=1', '-wallet=w1', '-wallet=w2'], "Error: -upgradewallet is only allowed with a single wallet file") + + # !TODO: backport bitcoin#12220 + ''' + # if wallets/ doesn't exist, datadir should be the default wallet dir + wallet_dir2 = data_dir('walletdir') + os.rename(wallet_dir(), wallet_dir2) + self.start_node(0, ['-wallet=w4', '-wallet=w5']) + assert_equal(set(node.listwallets()), {"w4", "w5"}) + w5 = wallet("w5") + w5.generate(1) + + # now if wallets/ exists again, but the rootdir is specified as the walletdir, w4 and w5 should still be loaded + os.rename(wallet_dir2, wallet_dir()) + self.restart_node(0, ['-wallet=w4', '-wallet=w5', '-walletdir=' + data_dir()]) + assert_equal(set(node.listwallets()), {"w4", "w5"}) + w5 = wallet("w5") + w5_info = w5.getwalletinfo() + assert_equal(w5_info['immature_balance'], 50) + + competing_wallet_dir = os.path.join(self.options.tmpdir, 'competing_walletdir') + os.mkdir(competing_wallet_dir) + self.restart_node(0, ['-walletdir='+competing_wallet_dir]) + self.assert_start_raises_init_error(1, ['-walletdir='+competing_wallet_dir], 'Error initializing wallet database environment') + ''' + self.restart_node(0, self.extra_args[0]) + + w1 = wallet("w1") + w2 = wallet("w2") + w3 = wallet("w3") + w4 = wallet("w") + wallet_bad = wallet("bad") + + w1.generate(1) + + # accessing invalid wallet fails + assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", wallet_bad.getwalletinfo) + + # accessing wallet RPC without using wallet endpoint fails + assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo) + + # check w1 wallet balance + w1_info = w1.getwalletinfo() + assert_equal(w1_info['immature_balance'], 250) + w1_name = w1_info['walletname'] + assert_equal(w1_name, "w1") + + # check w2 wallet balance + w2_info = w2.getwalletinfo() + assert_equal(w2_info['immature_balance'], 0) + w2_name = w2_info['walletname'] + assert_equal(w2_name, "w2") + + w3_name = w3.getwalletinfo()['walletname'] + assert_equal(w3_name, "w3") + + w4_name = w4.getwalletinfo()['walletname'] + assert_equal(w4_name, "w") + + w1.generate(101) + assert_equal(w1.getbalance(), 500) + assert_equal(w2.getbalance(), 0) + assert_equal(w3.getbalance(), 0) + assert_equal(w4.getbalance(), 0) + + w1.sendtoaddress(w2.getnewaddress(), 1) + w1.sendtoaddress(w3.getnewaddress(), 2) + w1.sendtoaddress(w4.getnewaddress(), 3) + w1.generate(1) + assert_equal(w2.getbalance(), 1) + assert_equal(w3.getbalance(), 2) + assert_equal(w4.getbalance(), 3) + + batch = w1.batch([w1.getblockchaininfo.get_request(), w1.getwalletinfo.get_request()]) + assert_equal(batch[0]["result"]["chain"], "regtest") + #assert_equal(batch[1]["result"]["walletname"], "w1") + +if __name__ == '__main__': + MultiWalletTest().main() \ No newline at end of file