diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.test.include | 7 | ||||
-rw-r--r-- | src/bitcoin-cli.cpp | 16 | ||||
-rw-r--r-- | src/httprpc.cpp | 5 | ||||
-rw-r--r-- | src/httpserver.cpp | 11 | ||||
-rw-r--r-- | src/httpserver.h | 2 | ||||
-rw-r--r-- | src/init.cpp | 2 | ||||
-rw-r--r-- | src/policy/policy.cpp | 10 | ||||
-rw-r--r-- | src/policy/policy.h | 4 | ||||
-rw-r--r-- | src/qt/clientmodel.cpp | 1 | ||||
-rw-r--r-- | src/rpc/client.cpp | 1 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 1 | ||||
-rw-r--r-- | src/rpc/misc.cpp | 8 | ||||
-rw-r--r-- | src/rpc/net.cpp | 2 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 135 | ||||
-rw-r--r-- | src/test/transaction_tests.cpp | 10 | ||||
-rw-r--r-- | src/validation.h | 8 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 25 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 43 | ||||
-rw-r--r-- | src/wallet/wallet.h | 5 | ||||
-rw-r--r-- | src/warnings.h | 7 |
20 files changed, 223 insertions, 80 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 90504ad52d..6415b3d2e3 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -96,12 +96,13 @@ endif test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS) -test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ - $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) -test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_test_bitcoin_LDADD = if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) endif +test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ + $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) +test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_test_bitcoin_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 8b48c7f8e4..445b9d8e89 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -46,6 +46,7 @@ std::string HelpMessageCli() strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases)")); + strUsage += HelpMessageOpt("-rpcwallet=<walletname>", _("Send RPC for non-default wallet on RPC server (argument is wallet filename in bitcoind directory, required if bitcoind/-Qt runs with multiple wallets)")); return strUsage; } @@ -241,7 +242,20 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params) assert(output_buffer); evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); - int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); + // check if we should use a special wallet endpoint + std::string endpoint = "/"; + std::string walletName = GetArg("-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.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str()); req.release(); // ownership moved to evcon in above call if (r != 0) { throw CConnectionFailed("send http request failed"); diff --git a/src/httprpc.cpp b/src/httprpc.cpp index a207d5ece4..69c3e3f49f 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -233,7 +233,10 @@ bool StartHTTPRPC() return false; RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); - +#ifdef ENABLE_WALLET + // ifdef can be removed once we switch to better endpoint support and API versioning + RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC); +#endif assert(EventBase()); httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); RPCSetTimerInterface(httpRPCTimerInterface); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 290a2efca2..ba01255400 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -666,3 +666,14 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) } } +std::string urlDecode(const std::string &urlEncoded) { + std::string res; + if (!urlEncoded.empty()) { + char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, NULL); + if (decoded) { + res = std::string(decoded); + free(decoded); + } + } + return res; +} diff --git a/src/httpserver.h b/src/httpserver.h index 9df56e5fc5..3e434bf0a0 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -148,4 +148,6 @@ private: struct event* ev; }; +std::string urlDecode(const std::string &urlEncoded); + #endif // BITCOIN_HTTPSERVER_H diff --git a/src/init.cpp b/src/init.cpp index d9b98be739..f0dbdb47c6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -479,7 +479,7 @@ std::string HelpMessage(HelpMessageMode mode) if (showDebug) { strUsage += HelpMessageOpt("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", defaultChainParams->RequireStandard())); strUsage += HelpMessageOpt("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE))); - strUsage += HelpMessageOpt("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to defined dust, the value of an output such that it will cost about 1/3 of its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE))); + strUsage += HelpMessageOpt("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to defined 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("-bytespersigop", strprintf(_("Equivalent bytes per sigop in transactions for relay and mining (default: %u)"), DEFAULT_BYTES_PER_SIGOP)); strUsage += HelpMessageOpt("-datacarrier", strprintf(_("Relay and mine data carrier transactions (default: %u)"), DEFAULT_ACCEPT_DATACARRIER)); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 9f2d623e76..605e3e0696 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -19,16 +19,18 @@ 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 non-segwit txout is 34 bytes big, and will // need a CTxIn of at least 148 bytes to spend: // so dust is a spendable txout less than - // 546*dustRelayFee/1000 (in satoshis). + // 182*dustRelayFee/1000 (in satoshis). + // 546 satoshis at the default rate of 3000 sat/kB. // A typical spendable segwit txout is 31 bytes big, and will // need a CTxIn of at least 67 bytes to spend: // so dust is a spendable txout less than - // 294*dustRelayFee/1000 (in satoshis). + // 98*dustRelayFee/1000 (in satoshis). + // 294 satoshis at the default rate of 3000 sat/kB. if (txout.scriptPubKey.IsUnspendable()) return 0; @@ -44,7 +46,7 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) nSize += (32 + 4 + 1 + 107 + 4); // the 148 mentioned above } - return 3 * dustRelayFeeIn.GetFee(nSize); + return dustRelayFeeIn.GetFee(nSize); } bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) diff --git a/src/policy/policy.h b/src/policy/policy.h index 2c2ea9d5b8..c06820f84e 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -40,12 +40,12 @@ static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100; static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80; /** The maximum size of a standard witnessScript */ static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600; -/** Min feerate for defining dust. Historically this has been the same as the +/** 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 = 1000; +static const unsigned int DUST_RELAY_TX_FEE = 3000; /** * Standard script verification flags that standard transactions will comply * with. However scripts violating these flags may still be present in valid diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 33f4535ee2..52ce11cefd 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -18,6 +18,7 @@ #include "txmempool.h" #include "ui_interface.h" #include "util.h" +#include "warnings.h" #include <stdint.h> diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 1ab1956124..7c75586d03 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -95,6 +95,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "signrawtransaction", 1, "prevtxs" }, { "signrawtransaction", 2, "privkeys" }, { "sendrawtransaction", 1, "allowhighfees" }, + { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "gettxout", 1, "n" }, { "gettxout", 2, "include_mempool" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 4fd632dfc5..daca32d251 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -24,6 +24,7 @@ #include "util.h" #include "utilstrencodings.h" #include "validationinterface.h" +#include "warnings.h" #include <memory> #include <stdint.h> diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index fcbbe1ceed..f3c86038a3 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -21,6 +21,7 @@ #include "wallet/wallet.h" #include "wallet/walletdb.h" #endif +#include "warnings.h" #include <stdint.h> #ifdef HAVE_MALLOC_INFO @@ -50,6 +51,7 @@ UniValue getinfo(const JSONRPCRequest& request) "\nDEPRECATED. Returns an object containing various state info.\n" "\nResult:\n" "{\n" + " \"deprecation-warning\": \"...\" (string) warning that the getinfo command is deprecated and will be removed in 0.16\n" " \"version\": xxxxx, (numeric) the server version\n" " \"protocolversion\": xxxxx, (numeric) the protocol version\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" @@ -57,7 +59,7 @@ UniValue getinfo(const JSONRPCRequest& request) " \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n" " \"timeoffset\": xxxxx, (numeric) the time offset\n" " \"connections\": xxxxx, (numeric) the number of connections\n" - " \"proxy\": \"host:port\", (string, optional) the proxy used by the server\n" + " \"proxy\": \"host:port\", (string, optional) the proxy used by the server\n" " \"difficulty\": xxxxxx, (numeric) the current difficulty\n" " \"testnet\": true|false, (boolean) if the server is using testnet or not\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" @@ -65,7 +67,7 @@ UniValue getinfo(const JSONRPCRequest& request) " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee set in " + CURRENCY_UNIT + "/kB\n" " \"relayfee\": x.xxxx, (numeric) minimum relay fee for transactions in " + CURRENCY_UNIT + "/kB\n" - " \"errors\": \"...\" (string) any error messages\n" + " \"errors\": \"...\" (string) any error messages\n" "}\n" "\nExamples:\n" + HelpExampleCli("getinfo", "") @@ -84,6 +86,8 @@ UniValue getinfo(const JSONRPCRequest& request) GetProxy(NET_IPV4, proxy); UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("deprecation-warning", "WARNING: getinfo is deprecated and will be fully removed in 0.16." + " Projects should transition to using getblockchaininfo, getnetworkinfo, and getwalletinfo before upgrading to 0.16")); obj.push_back(Pair("version", CLIENT_VERSION)); obj.push_back(Pair("protocolversion", PROTOCOL_VERSION)); #ifdef ENABLE_WALLET diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index ed452fcb02..090d9e448e 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -18,7 +18,7 @@ #include "util.h" #include "utilstrencodings.h" #include "version.h" - +#include "warnings.h" #include <univalue.h> diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index b878624df8..10886fea9f 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -554,6 +554,93 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: vErrorsRet.push_back(entry); } +UniValue combinerawtransaction(const JSONRPCRequest& request) +{ + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "combinerawtransaction [\"hexstring\",...]\n" + "\nCombine multiple partially signed transactions into one transaction.\n" + "The combined transaction may be another partially signed transaction or a \n" + "fully signed transaction." + + "\nArguments:\n" + "1. \"txs\" (string) A json array of hex strings of partially signed transactions\n" + " [\n" + " \"hexstring\" (string) A transaction hash\n" + " ,...\n" + " ]\n" + + "\nResult:\n" + "\"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" + + "\nExamples:\n" + + HelpExampleCli("combinerawtransaction", "[\"myhex1\", \"myhex2\", \"myhex3\"]") + ); + + + UniValue txs = request.params[0].get_array(); + std::vector<CMutableTransaction> txVariants(txs.size()); + + for (unsigned int idx = 0; idx < txs.size(); idx++) { + if (!DecodeHexTx(txVariants[idx], txs[idx].get_str(), true)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed for tx %d", idx)); + } + } + + if (txVariants.empty()) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions"); + } + + // mergedTx will end up with all the signatures; it + // starts as a clone of the rawtx: + CMutableTransaction mergedTx(txVariants[0]); + + // Fetch previous transactions (inputs): + CCoinsView viewDummy; + CCoinsViewCache view(&viewDummy); + { + LOCK(cs_main); + LOCK(mempool.cs); + CCoinsViewCache &viewChain = *pcoinsTip; + CCoinsViewMemPool viewMempool(&viewChain, mempool); + view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view + + for (const CTxIn& txin : mergedTx.vin) { + view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail. + } + + view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long + } + + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(mergedTx); + // Sign what we can: + for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { + CTxIn& txin = mergedTx.vin[i]; + const Coin& coin = view.AccessCoin(txin.prevout); + if (coin.IsSpent()) { + throw JSONRPCError(RPC_VERIFY_ERROR, "Input not found or already spent"); + } + const CScript& prevPubKey = coin.out.scriptPubKey; + const CAmount& amount = coin.out.nValue; + + SignatureData sigdata; + + // ... and merge in other signatures: + for (const CMutableTransaction& txv : txVariants) { + if (txv.vin.size() > i) { + sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i)); + } + } + + UpdateTransaction(mergedTx, i, sigdata); + } + + return EncodeHexTx(mergedTx); +} + UniValue signrawtransaction(const JSONRPCRequest& request) { #ifdef ENABLE_WALLET @@ -626,26 +713,9 @@ UniValue signrawtransaction(const JSONRPCRequest& request) #endif RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); - std::vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1")); - CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); - std::vector<CMutableTransaction> txVariants; - while (!ssData.empty()) { - try { - CMutableTransaction tx; - ssData >> tx; - txVariants.push_back(tx); - } - catch (const std::exception&) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } - } - - if (txVariants.empty()) - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transaction"); - - // mergedTx will end up with all the signatures; it - // starts as a clone of the rawtx: - CMutableTransaction mergedTx(txVariants[0]); + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str(), true)) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); // Fetch previous transactions (inputs): CCoinsView viewDummy; @@ -656,7 +726,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request) CCoinsViewMemPool viewMempool(&viewChain, mempool); view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view - for (const CTxIn& txin : mergedTx.vin) { + for (const CTxIn& txin : mtx.vin) { view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail. } @@ -781,10 +851,10 @@ UniValue signrawtransaction(const JSONRPCRequest& request) // Use CTransaction for the constant parts of the // transaction to avoid rehashing. - const CTransaction txConst(mergedTx); + const CTransaction txConst(mtx); // Sign what we can: - for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { - CTxIn& txin = mergedTx.vin[i]; + for (unsigned int i = 0; i < mtx.vin.size(); i++) { + CTxIn& txin = mtx.vin[i]; const Coin& coin = view.AccessCoin(txin.prevout); if (coin.IsSpent()) { TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); @@ -795,17 +865,11 @@ UniValue signrawtransaction(const JSONRPCRequest& request) SignatureData sigdata; // Only sign SIGHASH_SINGLE if there's a corresponding output: - if (!fHashSingle || (i < mergedTx.vout.size())) - ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mergedTx, i, amount, nHashType), prevPubKey, sigdata); + if (!fHashSingle || (i < mtx.vout.size())) + ProduceSignature(MutableTransactionSignatureCreator(&keystore, &mtx, i, amount, nHashType), prevPubKey, sigdata); + sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i)); - // ... and merge in other signatures: - for (const CMutableTransaction& txv : txVariants) { - if (txv.vin.size() > i) { - sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i)); - } - } - - UpdateTransaction(mergedTx, i, sigdata); + UpdateTransaction(mtx, i, sigdata); ScriptError serror = SCRIPT_ERR_OK; if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { @@ -815,7 +879,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request) bool fComplete = vErrors.empty(); UniValue result(UniValue::VOBJ); - result.push_back(Pair("hex", EncodeHexTx(mergedTx))); + result.push_back(Pair("hex", EncodeHexTx(mtx))); result.push_back(Pair("complete", fComplete)); if (!vErrors.empty()) { result.push_back(Pair("errors", vErrors)); @@ -905,6 +969,7 @@ static const CRPCCommand commands[] = { "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} }, { "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} }, + { "rawtransactions", "combinerawtransaction", &combinerawtransaction, true, {"txs"} }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ { "blockchain", "gettxoutproof", &gettxoutproof, true, {"txids", "blockhash"} }, diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 39f9f58604..6654634bf1 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -692,7 +692,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) BOOST_CHECK(IsStandardTx(t, reason)); // Check dust with default relay fee: - CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK()/1000 * 3; + CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK()/1000; BOOST_CHECK_EQUAL(nDustThreshold, 546); // dust: t.vout[0].nValue = nDustThreshold - 1; @@ -702,13 +702,13 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) BOOST_CHECK(IsStandardTx(t, reason)); // Check dust with odd relay fee to verify rounding: - // nDustThreshold = 182 * 1234 / 1000 * 3 - dustRelayFee = CFeeRate(1234); + // nDustThreshold = 182 * 3702 / 1000 + dustRelayFee = CFeeRate(3702); // dust: - t.vout[0].nValue = 672 - 1; + t.vout[0].nValue = 673 - 1; BOOST_CHECK(!IsStandardTx(t, reason)); // not dust: - t.vout[0].nValue = 672; + t.vout[0].nValue = 673; BOOST_CHECK(IsStandardTx(t, reason)); dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); diff --git a/src/validation.h b/src/validation.h index a9f995abb8..95c8e5b93e 100644 --- a/src/validation.h +++ b/src/validation.h @@ -268,14 +268,6 @@ void UnloadBlockIndex(); void ThreadScriptCheck(); /** Check whether we are doing an initial block download (synchronizing from disk or network) */ bool IsInitialBlockDownload(); -/** Format a string that describes several potential problems detected by the core. - * strFor can have three values: - * - "rpc": get critical warnings, which should put the client in safe mode if non-empty - * - "statusbar": get all warnings - * - "gui": get all warnings, translated (where possible) for GUI - * This function only returns the highest priority warning of the set selected by strFor. - */ -std::string GetWarnings(const std::string& strFor); /** Retrieve a transaction (from memory pool, or from disk, if possible) */ bool GetTransaction(const uint256 &hash, CTransactionRef &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false); /** Find the best known block, and make it the tip of the block chain */ diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ee8c7548fc..f63ce1bb48 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -9,6 +9,7 @@ #include "consensus/validation.h" #include "core_io.h" #include "init.h" +#include "httpserver.h" #include "validation.h" #include "net.h" #include "policy/feerate.h" @@ -30,10 +31,21 @@ #include <univalue.h> +static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; + CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) { - // TODO: Some way to access secondary wallets - return vpwallets.empty() ? nullptr : vpwallets[0]; + if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { + // wallet endpoint was used + std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); + for (CWalletRef pwallet : ::vpwallets) { + if (pwallet->GetName() == requestedWallet) { + return pwallet; + } + } + throw JSONRPCError(RPC_INVALID_PARAMETER, "Requested wallet does not exist or is not loaded"); + } + return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[0] : nullptr; } std::string HelpRequiringPassphrase(CWallet * const pwallet) @@ -2693,7 +2705,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " \"changePosition\" (numeric, optional, default random) The index of the change output\n" " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" - " \"reserveChangeKey\" (boolean, optional, default true) Reserves the change output key from the keypool\n" " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n" " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" " The fee will be equally deducted from the amount of each specified output.\n" @@ -2732,7 +2743,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) CCoinControl coinControl; int changePosition = -1; bool lockUnspents = false; - bool reserveChangeKey = true; UniValue subtractFeeFromOutputs; std::set<int> setSubtractFeeFromOutputs; @@ -2752,7 +2762,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) {"changePosition", UniValueType(UniValue::VNUM)}, {"includeWatching", UniValueType(UniValue::VBOOL)}, {"lockUnspents", UniValueType(UniValue::VBOOL)}, - {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, + {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so. {"feeRate", UniValueType()}, // will be checked below {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, {"replaceable", UniValueType(UniValue::VBOOL)}, @@ -2779,9 +2789,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) if (options.exists("lockUnspents")) lockUnspents = options["lockUnspents"].get_bool(); - if (options.exists("reserveChangeKey")) - reserveChangeKey = options["reserveChangeKey"].get_bool(); - if (options.exists("feeRate")) { coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"])); @@ -2830,7 +2837,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) CAmount nFeeOut; std::string strFailReason; - if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl, reserveChangeKey)) { + if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 458c54774f..223790aa40 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -57,6 +57,8 @@ CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE); */ CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE); +CFeeRate CWallet::m_discard_rate = CFeeRate(DEFAULT_DISCARD_FEE); + const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet @@ -2471,7 +2473,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx) return true; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl, bool keepReserveKey) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) { std::vector<CRecipient> vecSend; @@ -2493,8 +2495,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } - if (nChangePosInOut != -1) + + if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); + // we dont have the normal Create/Commit cycle, and dont want to risk reusing change, + // so just remove the key from the keypool here. + reservekey.KeepKey(); + } // Copy output sizes from new transaction; they may have had the fee subtracted from them for (unsigned int idx = 0; idx < tx.vout.size(); idx++) @@ -2515,13 +2522,21 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC } } - // optionally keep the change output key - if (keepReserveKey) - reservekey.KeepKey(); return true; } +static CFeeRate GetDiscardRate(const CBlockPolicyEstimator& estimator) +{ + unsigned int highest_target = estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + CFeeRate discard_rate = estimator.estimateSmartFee(highest_target, nullptr /* FeeCalculation */, false /* conservative */); + // Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate + discard_rate = (discard_rate == CFeeRate(0)) ? CWallet::m_discard_rate : std::min(discard_rate, CWallet::m_discard_rate); + // Discard rate must be at least dustRelayFee + discard_rate = std::max(discard_rate, ::dustRelayFee); + return discard_rate; +} + bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { @@ -2621,6 +2636,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT CTxOut change_prototype_txout(0, scriptChange); size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0); + CFeeRate discard_rate = GetDiscardRate(::feeEstimator); nFeeRet = 0; bool pick_new_inputs = true; CAmount nValueIn = 0; @@ -2688,7 +2704,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT // Never create dust outputs; if we would, just // add the dust to the fee. - if (IsDust(newTxOut, ::dustRelayFee)) + if (IsDust(newTxOut, discard_rate)) { nChangePosInOut = -1; nFeeRet += nChange; @@ -2768,7 +2784,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT // (because of reduced tx size) and so we should add a // change output. Only try this once. CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, coin_control, ::mempool, ::feeEstimator, nullptr); - CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, ::dustRelayFee); + CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate); CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change; if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { pick_new_inputs = false; @@ -3808,6 +3824,9 @@ std::string CWallet::GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE)); strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE))); + strUsage += HelpMessageOpt("-discardfee=<amt>", strprintf(_("The fee rate (in %s/kB) used to discard change (to fee) if it would be dust at this fee rate (default: %s) " + "Note: We will always discard up to the dust relay fee and a discard fee above that is limited by the longest target fee estimate"), + CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE))); strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE))); strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), @@ -4133,6 +4152,16 @@ bool CWallet::ParameterInteraction() _("This is the transaction fee you may pay when fee estimates are not available.")); CWallet::fallbackFee = CFeeRate(nFeePerK); } + if (IsArgSet("-discardfee")) + { + CAmount nFeePerK = 0; + if (!ParseMoney(GetArg("-discardfee", ""), nFeePerK)) + return InitError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), GetArg("-discardfee", ""))); + if (nFeePerK > HIGH_TX_FEE_PER_KB) + InitWarning(AmountHighWarn("-discardfee") + " " + + _("This is the transaction fee you may discard if change is smaller than dust at this level")); + CWallet::m_discard_rate = CFeeRate(nFeePerK); + } if (IsArgSet("-paytxfee")) { CAmount nFeePerK = 0; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 574fd8710d..bcd7e4b4ee 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -45,6 +45,8 @@ static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; static const CAmount DEFAULT_TRANSACTION_FEE = 0; //! -fallbackfee default static const CAmount DEFAULT_FALLBACK_FEE = 20000; +//! -m_discard_rate default +static const CAmount DEFAULT_DISCARD_FEE = 10000; //! -mintxfee default static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000; //! minimum recommended increment for BIP 125 replacement txs @@ -949,7 +951,7 @@ public: * Insert additional inputs into the transaction by * calling CreateTransaction(); */ - bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl, bool keepReserveKey = true); + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); bool SignTransaction(CMutableTransaction& tx); /** @@ -969,6 +971,7 @@ public: static CFeeRate minTxFee; static CFeeRate fallbackFee; + static CFeeRate m_discard_rate; /** * Estimate the minimum fee considering user set parameters * and the required fee diff --git a/src/warnings.h b/src/warnings.h index a7aa657426..fd0ca53942 100644 --- a/src/warnings.h +++ b/src/warnings.h @@ -14,6 +14,13 @@ void SetfLargeWorkForkFound(bool flag); bool GetfLargeWorkForkFound(); void SetfLargeWorkInvalidChainFound(bool flag); bool GetfLargeWorkInvalidChainFound(); +/** Format a string that describes several potential problems detected by the core. + * strFor can have three values: + * - "rpc": get critical warnings, which should put the client in safe mode if non-empty + * - "statusbar": get all warnings + * - "gui": get all warnings, translated (where possible) for GUI + * This function only returns the highest priority warning of the set selected by strFor. + */ std::string GetWarnings(const std::string& strFor); static const bool DEFAULT_TESTSAFEMODE = false; |