From e53ee0fe37a04a0181349af24a1ddd6c56bd72d3 Mon Sep 17 00:00:00 2001 From: timemarkovqtum Date: Mon, 1 Jul 2024 15:31:28 +0200 Subject: [PATCH] Port rpc wallet --- src/wallet/receive.cpp | 17 +- src/wallet/receive.h | 2 + src/wallet/rpc/backup.cpp | 83 +++++- src/wallet/rpc/coins.cpp | 24 +- src/wallet/rpc/encrypt.cpp | 29 ++- src/wallet/rpc/signmessage.cpp | 8 +- src/wallet/rpc/wallet.cpp | 458 ++++++++++++++++++++++++++++++++- src/wallet/spend.cpp | 35 ++- src/wallet/spend.h | 2 +- 9 files changed, 617 insertions(+), 41 deletions(-) diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index b9d8d9abc9..bc90230a5f 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -156,6 +156,17 @@ CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, c return 0; } +CAmount CachedTxGetStakeCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +{ + AssertLockHeld(wallet.cs_wallet); + + if (wallet.IsTxImmatureCoinStake(wtx) && wallet.IsTxInMainChain(wtx)) { + return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, filter); + } + + return 0; +} + CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { AssertLockHeld(wallet.cs_wallet); @@ -164,7 +175,7 @@ CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; // Must wait until coinbase is safely deep enough in the chain before valuing it - if (wallet.IsTxImmatureCoinBase(wtx)) + if (wallet.IsTxImmature(wtx)) return 0; if (allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) { @@ -314,6 +325,8 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) } ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE); ret.m_watchonly_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_WATCH_ONLY); + ret.m_mine_stake += CachedTxGetStakeCredit(wallet, wtx, ISMINE_SPENDABLE); + ret.m_watchonly_stake += CachedTxGetStakeCredit(wallet, wtx, ISMINE_WATCH_ONLY); } } return ret; @@ -333,7 +346,7 @@ std::map GetAddressBalances(const CWallet& wallet) if (!CachedTxIsTrusted(wallet, wtx, trusted_parents)) continue; - if (wallet.IsTxImmatureCoinBase(wtx)) + if (wallet.IsTxImmature(wtx)) continue; int nDepth = wallet.GetTxDepthInMainChain(wtx); diff --git a/src/wallet/receive.h b/src/wallet/receive.h index 255ecf00f0..db3a35fa96 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -31,6 +31,8 @@ CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const ismi CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx); CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount CachedTxGetStakeCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter = ISMINE_SPENDABLE) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); struct COutputEntry diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 6f70b09545..0fb36619a1 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -181,6 +181,8 @@ RPCHelpMan importprivkey() CKey key = DecodeSecret(strSecret); if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + if (pwallet->m_wallet_unlock_staking_only) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Wallet is unlocked for staking only."); CPubKey pubkey = key.GetPubKey(); CHECK_NONFATAL(key.VerifyPubKey(pubkey)); @@ -232,7 +234,7 @@ RPCHelpMan importaddress() "Note: Use \"getwalletinfo\" to query the scanning progress.\n" "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" for descriptor wallets.\n", { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Qtum address (or hex-encoded script)"}, {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"}, {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, {"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"}, @@ -305,7 +307,7 @@ RPCHelpMan importaddress() pwallet->ImportScriptPubKeys(strLabel, scripts, /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1); } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address or script"); } } if (fRescan) @@ -646,7 +648,7 @@ RPCHelpMan dumpprivkey() "Then the importprivkey can be used with this output\n" "Note: This command is only compatible with legacy wallets.\n", { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for the private key"}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address for the private key"}, }, RPCResult{ RPCResult::Type::STR, "key", "The private key" @@ -670,7 +672,9 @@ RPCHelpMan dumpprivkey() std::string strAddress = request.params[0].get_str(); CTxDestination dest = DecodeDestination(strAddress); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address"); + if (pwallet->m_wallet_unlock_staking_only) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Wallet is unlocked for staking only."); } auto keyid = GetKeyForDestination(spk_man, dest); if (keyid.IsNull()) { @@ -915,6 +919,10 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d case TxoutType::NONSTANDARD: case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_V1_TAPROOT: + case TxoutType::CREATE_SENDER: + case TxoutType::CALL_SENDER: + case TxoutType::CREATE: + case TxoutType::CALL: return "unrecognized script"; } // no default case, so the compiler can warn about missing cases NONFATAL_UNREACHABLE(); @@ -1431,7 +1439,7 @@ RPCHelpMan importmulti() "block from time %d, which is after or within %d seconds of key creation, and " "could contain transactions pertaining to the key. As a result, transactions " "and coins using this key may not appear in the wallet. This error could be " - "caused by pruning or data corruption (see bitcoind log for details) and could " + "caused by pruning or data corruption (see qtumd log for details) and could " "be dealt with by downloading and rescanning the relevant blocks (see -reindex " "option and rescanblockchain RPC).", GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); @@ -1461,6 +1469,14 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c const bool active = data.exists("active") ? data["active"].get_bool() : false; const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; const std::string label{LabelFromValue(data["label"])}; + const bool importForStaking = data.exists("importforstaking") ? data["importforstaking"].get_bool() : false; + + // Check import for staking param + bool isLegacy = (descriptor.rfind("pkh(", 0) == 0 || descriptor.rfind("pk(", 0) == 0); + if(importForStaking && !isLegacy) + { + throw JSONRPCError(RPC_INVALID_PARAMETER, "importforstaking can only be used for pkh or pk descriptors."); + } // Parse descriptor string FlatSigningProvider keys; @@ -1578,6 +1594,12 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } } + // Refresh address stake cache + if(isLegacy) + { + wallet.RefreshAddressStakeCache(); + } + result.pushKV("success", UniValue(true)); } catch (const UniValue& e) { result.pushKV("success", UniValue(false)); @@ -1587,6 +1609,49 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c return result; } +static UniValue ProcessDescriptorData(CWallet& wallet, UniValue data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +{ + // Insert descriptor + UniValue result = ProcessDescriptorImport(wallet, data, timestamp); + + // Insert pk or pkh descriptor if needed + const bool importForStaking = data.exists("importforstaking") ? data["importforstaking"].get_bool() : false; + if(result["success"].get_bool() && importForStaking) + { + // Check if is pk or pkh descriptor + std::string descriptor = data["desc"].get_str(); + std::string tmpDesc; + bool isPkh = false; + if(descriptor.rfind("pkh(", 0) == 0) + { + tmpDesc = "pk"; + isPkh = true; + } + else if(descriptor.rfind("pk(", 0) == 0) + { + tmpDesc = "pkh"; + isPkh = false; + } + else + { + return result; + } + + // Compute second descriptor + size_t checksize = GetDescriptorChecksum(descriptor).size() + 1; + size_t pos = isPkh ? 3 : 2; + size_t len = descriptor.size() - checksize - pos; + tmpDesc = tmpDesc + descriptor.substr(pos, len); + tmpDesc = tmpDesc + "#" + GetDescriptorChecksum(tmpDesc); + + // Insert second descriptor + data.pushKV("desc", tmpDesc); + result = ProcessDescriptorImport(wallet, data, timestamp); + } + + return result; +} + RPCHelpMan importdescriptors() { return RPCHelpMan{"importdescriptors", @@ -1612,6 +1677,7 @@ RPCHelpMan importdescriptors() }, {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"}, {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"}, + {"importforstaking", RPCArg::Type::BOOL, RPCArg::Default{false}, "Import corresponding pk or pkh descriptor as both are needed for staking, only apply for pk or pkh descriptors."}, }, }, }, @@ -1637,7 +1703,8 @@ RPCHelpMan importdescriptors() RPCExamples{ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"\", \"timestamp\":1455191478, \"internal\": true }, " "{ \"desc\": \"\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + - HelpExampleCli("importdescriptors", "'[{ \"desc\": \"\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"\" }]'") + HelpExampleCli("importdescriptors", "'[{ \"desc\": \"\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"\" }]'") + + HelpExampleCli("importdescriptors", "'[{ \"desc\": \"\", \"timestamp\":1455191478, \"importforstaking\":true}]'") }, [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue { @@ -1679,7 +1746,7 @@ RPCHelpMan importdescriptors() for (const UniValue& request : requests.getValues()) { // This throws an error if "timestamp" doesn't exist const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp); - const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp); + const UniValue result = ProcessDescriptorData(*pwallet, request, timestamp); response.push_back(result); if (lowest_timestamp > timestamp ) { @@ -1729,7 +1796,7 @@ RPCHelpMan importdescriptors() "block from time %d, which is after or within %d seconds of key creation, and " "could contain transactions pertaining to the desc. As a result, transactions " "and coins using this desc may not appear in the wallet. This error could be " - "caused by pruning or data corruption (see bitcoind log for details) and could " + "caused by pruning or data corruption (see qtumd log for details) and could " "be dealt with by downloading and rescanning the relevant blocks (see -reindex " "option and rescanblockchain RPC).", GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 0cb0891141..48a1f3988c 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -29,7 +29,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b // Get the address CTxDestination dest = DecodeDestination(params[0].get_str()); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address"); } addresses.emplace_back(dest); } @@ -61,8 +61,8 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b int depth{wallet.GetTxDepthInMainChain(wtx)}; if (depth < min_depth // Coinbase with less than 1 confirmation is no longer in the main chain - || (wtx.IsCoinBase() && (depth < 1)) - || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) + || ((wtx.IsCoinBase() || wtx.IsCoinStake()) && (depth < 1)) + || (wallet.IsTxImmature(wtx) && !include_immature_coinbase)) { continue; } @@ -83,7 +83,7 @@ RPCHelpMan getreceivedbyaddress() return RPCHelpMan{"getreceivedbyaddress", "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n", { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for transactions."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address for transactions."}, {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "Only include transactions confirmed at least this many times."}, {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."}, }, @@ -244,7 +244,7 @@ RPCHelpMan lockunspent() "\nUpdates list of temporarily unspendable outputs.\n" "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" "If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n" - "A locked transaction output will not be chosen by automatic coin selection, when spending bitcoins.\n" + "A locked transaction output will not be chosen by automatic coin selection, when spending qtums.\n" "Manually selected coins are automatically unlocked.\n" "Locks are stored in memory only, unless persistent=true, in which case they will be written to the\n" "wallet database and loaded on node start. Unwritten (persistent=false) locks are always cleared\n" @@ -441,6 +441,7 @@ RPCHelpMan getbalances() {RPCResult::Type::STR_AMOUNT, "trusted", "trusted balance (outputs created by the wallet or confirmed outputs)"}, {RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"}, {RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"}, + {RPCResult::Type::STR_AMOUNT, "stake", "balance from immature coinstake outputs"}, {RPCResult::Type::STR_AMOUNT, "used", /*optional=*/true, "(only present if avoid_reuse is set) balance from coins sent to addresses that were previously spent from (potentially privacy violating)"}, }}, {RPCResult::Type::OBJ, "watchonly", /*optional=*/true, "watchonly balances (not present if wallet does not watch anything)", @@ -448,6 +449,7 @@ RPCHelpMan getbalances() {RPCResult::Type::STR_AMOUNT, "trusted", "trusted balance (outputs created by the wallet or confirmed outputs)"}, {RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"}, {RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"}, + {RPCResult::Type::STR_AMOUNT, "stake", "balance from immature coinstake outputs"}, }}, RESULT_LAST_PROCESSED_BLOCK, } @@ -474,6 +476,7 @@ RPCHelpMan getbalances() balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted)); balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending)); balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature)); + balances_mine.pushKV("stake", ValueFromAmount(bal.m_mine_stake)); if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { // If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get // the total balance, and then subtract bal to get the reused address balance. @@ -488,6 +491,7 @@ RPCHelpMan getbalances() balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted)); balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending)); balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature)); + balances_watchonly.pushKV("stake", ValueFromAmount(bal.m_watchonly_stake)); balances.pushKV("watchonly", balances_watchonly); } @@ -507,9 +511,9 @@ RPCHelpMan listunspent() { {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum confirmations to filter"}, {"maxconf", RPCArg::Type::NUM, RPCArg::Default{9999999}, "The maximum confirmations to filter"}, - {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The bitcoin addresses to filter", + {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The qtum addresses to filter", { - {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address"}, + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "qtum address"}, }, }, {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include outputs that are not safe to spend\n" @@ -531,7 +535,7 @@ RPCHelpMan listunspent() { {RPCResult::Type::STR_HEX, "txid", "the transaction id"}, {RPCResult::Type::NUM, "vout", "the vout value"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "the bitcoin address"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "the qtum address"}, {RPCResult::Type::STR, "label", /*optional=*/true, "The associated label, or \"\" for the default label"}, {RPCResult::Type::STR, "scriptPubKey", "the script key"}, {RPCResult::Type::STR_AMOUNT, "amount", "the transaction output amount in " + CURRENCY_UNIT}, @@ -583,7 +587,7 @@ RPCHelpMan listunspent() const UniValue& input = inputs[idx]; CTxDestination dest = DecodeDestination(input.get_str()); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + input.get_str()); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Qtum address: ") + input.get_str()); } if (!destinations.insert(dest).second) { throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str()); @@ -652,7 +656,7 @@ RPCHelpMan listunspent() for (const COutput& out : vecOutputs) { CTxDestination address; const CScript& scriptPubKey = out.txout.scriptPubKey; - bool fValidAddress = ExtractDestination(scriptPubKey, address); + bool fValidAddress = ExtractDestination(scriptPubKey, address, nullptr, true); bool reused = avoid_reuse && pwallet->IsSpentKey(scriptPubKey); if (destinations.size() && (!fValidAddress || !destinations.count(address))) diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index e237286603..34fdb50176 100644 --- a/src/wallet/rpc/encrypt.cpp +++ b/src/wallet/rpc/encrypt.cpp @@ -12,13 +12,14 @@ RPCHelpMan walletpassphrase() { return RPCHelpMan{"walletpassphrase", "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" - "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" + "This is needed prior to performing transactions related to private keys such as sending qtums\n" "\nNote:\n" "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" "time that overrides the old one.\n", { {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"}, {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."}, + {"stakingonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "Unlock wallet for staking only"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ @@ -26,6 +27,8 @@ RPCHelpMan walletpassphrase() + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + "\nLock the wallet again (before 60 seconds)\n" + HelpExampleCli("walletlock", "") + + "\nUnlock the wallet for staking only, for a long time\n" + + HelpExampleCli("walletpassphrase","\"my pass phrase\" 99999999 true") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") }, @@ -42,6 +45,9 @@ RPCHelpMan walletpassphrase() { LOCK(pwallet->cs_wallet); + if (request.mode != JSONRPCRequest::EXECUTE) + return true; + if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); } @@ -67,7 +73,17 @@ RPCHelpMan walletpassphrase() throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); } + // Used to restore m_wallet_unlock_staking_only value in case of unlock failure + bool tmpStakingOnly = pwallet->m_wallet_unlock_staking_only; + + // ppcoin: if user OS account compromised prevent trivial sendmoney commands + if (!request.params[2].isNull()) + pwallet->m_wallet_unlock_staking_only = request.params[2].get_bool(); + else + pwallet->m_wallet_unlock_staking_only = false; + if (!pwallet->Unlock(strWalletPass)) { + pwallet->m_wallet_unlock_staking_only = tmpStakingOnly; // Check if the passphrase has a null character (see #27067 for details) if (strWalletPass.find('\0') == std::string::npos) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); @@ -130,6 +146,9 @@ RPCHelpMan walletpassphrasechange() std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; + if (request.mode != JSONRPCRequest::EXECUTE) + return true; + if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); } @@ -194,6 +213,9 @@ RPCHelpMan walletlock() std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; + if (request.mode != JSONRPCRequest::EXECUTE) + return true; + if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); } @@ -232,7 +254,7 @@ RPCHelpMan encryptwallet() RPCExamples{ "\nEncrypt your wallet\n" + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + - "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" + "\nNow set the passphrase to use the wallet, such as for signing or sending qtum\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + "\nNow we can do something like sign\n" + HelpExampleCli("signmessage", "\"address\" \"test message\"") + @@ -246,6 +268,9 @@ RPCHelpMan encryptwallet() std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; + if (request.mode != JSONRPCRequest::EXECUTE) + return true; + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt."); } diff --git a/src/wallet/rpc/signmessage.cpp b/src/wallet/rpc/signmessage.cpp index c9fb693482..5fe4ceecb9 100644 --- a/src/wallet/rpc/signmessage.cpp +++ b/src/wallet/rpc/signmessage.cpp @@ -17,7 +17,7 @@ RPCHelpMan signmessage() "\nSign a message with the private key of an address" + HELP_REQUIRING_PASSPHRASE, { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the private key."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to use for the private key."}, {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, }, RPCResult{ @@ -27,11 +27,11 @@ RPCHelpMan signmessage() "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" - + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + + + HelpExampleCli("signmessage", "\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" - + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + + + HelpExampleCli("verifymessage", "\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + "\nAs a JSON-RPC call\n" - + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") + + HelpExampleRpc("signmessage", "\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 6a8ce954fb..b2d57ae895 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include @@ -41,6 +44,432 @@ bool HaveKey(const SigningProvider& wallet, const CKey& key) return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); } +UniValue GetJsonSuperStakerConfig(const CSuperStakerInfo& superStaker) +{ + // Fill the json object with information + UniValue result(UniValue::VOBJ); + result.pushKV("address", EncodeDestination(PKHash(superStaker.stakerAddress))); + result.pushKV("customconfig", superStaker.fCustomConfig); + if(superStaker.fCustomConfig) + { + result.pushKV("stakingminfee", (int64_t)superStaker.nMinFee); + result.pushKV("stakingminutxovalue", FormatMoney(superStaker.nMinDelegateUtxo)); + UniValue addressList(UniValue::VARR); + for(uint160 address : superStaker.delegateAddressList) + { + addressList.push_back(EncodeDestination(PKHash(address))); + } + if(interfaces::AllowList == superStaker.nDelegateAddressType) + { + result.pushKV("allow", addressList); + } + if(interfaces::ExcludeList == superStaker.nDelegateAddressType) + { + result.pushKV("exclude", addressList); + } + } + + return result; +} + +static RPCHelpMan setsuperstakervaluesforaddress() +{ + return RPCHelpMan{"setsuperstakervaluesforaddress", + "\nList super staker configuration values for address." + + HELP_REQUIRING_PASSPHRASE, + { + {"params", RPCArg::Type::OBJ, RPCArg::Optional::NO, "", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address of the staker"}, + {"stakingminutxovalue", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The output number"}, + {"stakingminfee", RPCArg::Type::NUM, RPCArg::Optional::NO, "depends on the value of the 'replaceable' and 'locktime' arguments"}, + {"allow", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "A json array with allow delegate addresses.", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The delegate address"}, + }, + }, + {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "A json array with exclude delegate addresses.", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The delegate address"}, + }, + }, + }, + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "Address of the staker."}, + {RPCResult::Type::BOOL, "customconfig", "Custom configuration exist."}, + {RPCResult::Type::NUM, "stakingminfee", true, "Minimum fee for delegate."}, + {RPCResult::Type::STR, "stakingminutxovalue", true, "Minimum UTXO value for delegate."}, + {RPCResult::Type::ARR, "allow", true, "List of allowed delegate addresses.", + { + {RPCResult::Type::STR, "address", "The delegate address"}, + }, + }, + {RPCResult::Type::ARR, "exclude", true, "List of excluded delegate addresses.", + { + {RPCResult::Type::STR, "address", "The delegate address"}, + }, + } + } + }, + RPCExamples{ + HelpExampleCli("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10}\"") + + HelpExampleCli("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"allow\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") + + HelpExampleCli("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"exclude\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") + + HelpExampleRpc("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10}\"") + + HelpExampleRpc("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"allow\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") + + HelpExampleRpc("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"exclude\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + LOCK(pwallet->cs_wallet); + + // Get params for the super staker + UniValue params(UniValue::VOBJ); + params = request.params[0].get_obj(); + + // Parse the super staker address + if(!params.exists("address")) + throw JSONRPCError(RPC_TYPE_ERROR, "The super staker address doesn't exist"); + CTxDestination destStaker = DecodeDestination(params["address"].get_str()); + if (!std::holds_alternative(destStaker)) { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for staker. Only P2PK and P2PKH allowed"); + } + PKHash pkhStaker = std::get(destStaker); + + // Parse the staking min utxo value + if(!params.exists("stakingminutxovalue")) + throw JSONRPCError(RPC_TYPE_ERROR, "The staking min utxo value doesn't exist"); + CAmount nMinUtxoValue = AmountFromValue(params["stakingminutxovalue"]); + if (nMinUtxoValue < 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for staking min utxo value"); + + // Parse the staking min fee + if(!params.exists("stakingminfee")) + throw JSONRPCError(RPC_TYPE_ERROR, "The staking min fee doesn't exist"); + CAmount nMinFee = params["stakingminfee"].getInt(); + if (nMinFee < 0 || nMinFee > 100) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for staking min fee"); + + // Parse the delegation address lists + if(params.exists("allow") && params.exists("exclude")) + throw JSONRPCError(RPC_TYPE_ERROR, "The delegation address lists can be empty, or have either allow list or exclude list"); + + // Parse the delegation address lists + int nDelegateAddressType = interfaces::AcceptAll; + std::vector addressList; + if(params.exists("allow")) + { + addressList = params["allow"].get_array().getValues(); + nDelegateAddressType = interfaces::AllowList; + } + else if(params.exists("exclude")) + { + addressList = params["exclude"].get_array().getValues(); + nDelegateAddressType = interfaces::ExcludeList; + } + std::vector delegateAddressList; + for(UniValue address : addressList) + { + CTxDestination destAddress = DecodeDestination(address.get_str()); + if (!std::holds_alternative(destAddress)) { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for delegate in allow list or exclude list. Only P2PK and P2PKH allowed"); + } + PKHash pkhAddress = std::get(destAddress); + delegateAddressList.push_back(uint160(pkhAddress)); + } + + // Search for super staker + CSuperStakerInfo superStaker; + bool found = false; + for(auto item : pwallet->mapSuperStaker) + { + if(PKHash(item.second.stakerAddress) == pkhStaker) + { + superStaker = item.second; + found = true; + break; + } + } + + if(found) + { + // Set custom configuration + superStaker.fCustomConfig = true; + superStaker.nMinFee = nMinFee; + superStaker.nMinDelegateUtxo = nMinUtxoValue; + superStaker.nDelegateAddressType = nDelegateAddressType; + superStaker.delegateAddressList = delegateAddressList; + + // Update super staker data + if(!pwallet->AddSuperStakerEntry(superStaker)) + throw JSONRPCError(RPC_TYPE_ERROR, "Failed to update the super staker"); + } + else + { + throw JSONRPCError(RPC_TYPE_ERROR, "Failed to find the super staker"); + } + + return GetJsonSuperStakerConfig(superStaker); +}, + }; +} + +static RPCHelpMan listsuperstakercustomvalues() +{ + return RPCHelpMan{"listsuperstakercustomvalues", + "\nList custom super staker configurations values." + + HELP_REQUIRING_PASSPHRASE, + {}, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "Address of the staker."}, + {RPCResult::Type::BOOL, "customconfig", "Custom configuration exist."}, + {RPCResult::Type::NUM, "stakingminfee", true, "Minimum fee for delegate."}, + {RPCResult::Type::STR, "stakingminutxovalue", true, "Minimum UTXO value for delegate."}, + {RPCResult::Type::ARR, "allow", true, "List of allowed delegate addresses.", + { + {RPCResult::Type::STR, "address", "The delegate address"}, + }, + }, + {RPCResult::Type::ARR, "exclude", true, "List of excluded delegate addresses.", + { + {RPCResult::Type::STR, "address", "The delegate address"}, + }, + } + }} + }, + }, + RPCExamples{ + HelpExampleCli("listsuperstakercustomvalues", "") + + HelpExampleRpc("listsuperstakercustomvalues", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + LOCK(pwallet->cs_wallet); + + // Search for super stakers + UniValue result(UniValue::VARR); + for(auto item : pwallet->mapSuperStaker) + { + CSuperStakerInfo superStaker = item.second; + if(superStaker.fCustomConfig) + { + result.push_back(GetJsonSuperStakerConfig(superStaker)); + } + } + + return result; +}, + }; +} + +static RPCHelpMan listsuperstakervaluesforaddress() +{ + return RPCHelpMan{"listsuperstakervaluesforaddress", + "\nList super staker configuration values for address." + + HELP_REQUIRING_PASSPHRASE, + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The super staker Qtum address."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "Address of the staker."}, + {RPCResult::Type::BOOL, "customconfig", "Custom configuration exist."}, + {RPCResult::Type::NUM, "stakingminfee", true, "Minimum fee for delegate."}, + {RPCResult::Type::STR, "stakingminutxovalue", true, "Minimum UTXO value for delegate."}, + {RPCResult::Type::ARR, "allow", true, "List of allowed delegate addresses.", + { + {RPCResult::Type::STR, "address", "The delegate address"}, + }, + }, + {RPCResult::Type::ARR, "exclude", true, "List of excluded delegate addresses.", + { + {RPCResult::Type::STR, "address", "The delegate address"}, + }, + } + } + }, + RPCExamples{ + HelpExampleCli("listsuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") + + HelpExampleRpc("listsuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + LOCK(pwallet->cs_wallet); + + // Parse the super staker address + CTxDestination destStaker = DecodeDestination(request.params[0].get_str()); + if (!std::holds_alternative(destStaker)) { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for staker. Only P2PK and P2PKH allowed"); + } + PKHash pkhStaker = std::get(destStaker); + + // Search for super staker + CSuperStakerInfo superStaker; + bool found = false; + for(auto item : pwallet->mapSuperStaker) + { + if(PKHash(item.second.stakerAddress) == pkhStaker) + { + superStaker = item.second; + found = true; + break; + } + } + + if(!found) + { + throw JSONRPCError(RPC_TYPE_ERROR, "Failed to find the super staker"); + } + + return GetJsonSuperStakerConfig(superStaker); +}, + }; +} + +static RPCHelpMan removesuperstakervaluesforaddress() +{ + return RPCHelpMan{"removesuperstakervaluesforaddress", + "\nRemove super staker configuration values for address." + + HELP_REQUIRING_PASSPHRASE, + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The super staker Qtum address."}, + }, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{ + HelpExampleCli("removesuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") + + HelpExampleRpc("removesuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + LOCK(pwallet->cs_wallet); + + // Parse the super staker address + CTxDestination destStaker = DecodeDestination(request.params[0].get_str()); + if (!std::holds_alternative(destStaker)) { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for staker. Only P2PK and P2PKH allowed"); + } + PKHash pkhStaker = std::get(destStaker); + + // Search for super staker + CSuperStakerInfo superStaker; + bool found = false; + for(auto item : pwallet->mapSuperStaker) + { + if(PKHash(item.second.stakerAddress) == pkhStaker && + item.second.fCustomConfig) + { + superStaker = item.second; + found = true; + break; + } + } + + if(found) + { + // Remove custom configuration + superStaker.fCustomConfig = false; + superStaker.nMinFee = 0; + superStaker.nMinDelegateUtxo = 0; + superStaker.nDelegateAddressType = 0; + superStaker.delegateAddressList.clear(); + + // Update super staker data + if(!pwallet->AddSuperStakerEntry(superStaker)) + throw JSONRPCError(RPC_TYPE_ERROR, "Failed to update the super staker"); + } + else + { + throw JSONRPCError(RPC_TYPE_ERROR, "Failed to find the super staker"); + } + + return UniValue::VNULL; +}, + }; +} + +static RPCHelpMan reservebalance() +{ + return RPCHelpMan{"reservebalance", + "\nSet reserve amount not participating in network protection." + "\nIf no parameters provided current setting is printed.\n", + { + {"reserve", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED,"is true or false to turn balance reserve on or off."}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "is a real and rounded to cent."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "reserve", "Balance reserve on or off"}, + {RPCResult::Type::STR_AMOUNT, "amount", "Amount reserve rounded to cent"} + } + }, + RPCExamples{ + "\nSet reserve balance to 100\n" + + HelpExampleCli("reservebalance", "true 100") + + "\nSet reserve balance to 0\n" + + HelpExampleCli("reservebalance", "false") + + "\nGet reserve balance\n" + + HelpExampleCli("reservebalance", "") }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + if (request.params.size() > 0) + { + bool fReserve = request.params[0].get_bool(); + if (fReserve) + { + if (request.params.size() == 1) + throw std::runtime_error("must provide amount to reserve balance.\n"); + int64_t nAmount = AmountFromValue(request.params[1]); + nAmount = (nAmount / CENT) * CENT; // round to cent + if (nAmount < 0) + throw std::runtime_error("amount cannot be negative.\n"); + pwallet->m_reserve_balance = nAmount; + } + else + { + if (request.params.size() > 1) + throw std::runtime_error("cannot specify amount to turn off reserve.\n"); + pwallet->m_reserve_balance = 0; + } + } + + UniValue result(UniValue::VOBJ); + result.pushKV("reserve", (pwallet->m_reserve_balance > 0)); + result.pushKV("amount", ValueFromAmount(pwallet->m_reserve_balance)); + return result; +}, + }; +} + static RPCHelpMan getwalletinfo() { return RPCHelpMan{"getwalletinfo", @@ -54,6 +483,7 @@ static RPCHelpMan getwalletinfo() {RPCResult::Type::NUM, "walletversion", "the wallet version"}, {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"}, {RPCResult::Type::STR_AMOUNT, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"}, + {RPCResult::Type::STR_AMOUNT, "stake", "DEPRECATED. Identical to getbalances().mine.stake"}, {RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"}, {RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"}, {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"}, @@ -96,12 +526,25 @@ static RPCHelpMan getwalletinfo() size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); const auto bal = GetBalance(*pwallet); + CAmount balance = bal.m_mine_trusted; + CAmount stake = bal.m_mine_stake; + CAmount unconfirmedBalance = bal.m_mine_untrusted_pending; + CAmount immatureBalance = bal.m_mine_immature; + bool privateKeysEnabled = !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + if(!privateKeysEnabled) + { + balance += bal.m_watchonly_trusted; + stake += bal.m_watchonly_stake; + unconfirmedBalance += bal.m_watchonly_untrusted_pending; + immatureBalance += bal.m_watchonly_immature; + } obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); obj.pushKV("format", pwallet->GetDatabase().Format()); - obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted)); - obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending)); - obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature)); + obj.pushKV("balance", ValueFromAmount(balance)); + obj.pushKV("stake", ValueFromAmount(stake)); + obj.pushKV("unconfirmed_balance", ValueFromAmount(unconfirmedBalance)); + obj.pushKV("immature_balance", ValueFromAmount(immatureBalance)); obj.pushKV("txcount", (int)pwallet->mapWallet.size()); const auto kp_oldest = pwallet->GetOldestKeyPoolTime(); if (kp_oldest.has_value()) { @@ -124,7 +567,7 @@ static RPCHelpMan getwalletinfo() obj.pushKV("unlocked_until", pwallet->nRelockTime); } obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); - obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + obj.pushKV("private_keys_enabled", privateKeysEnabled); obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)); if (pwallet->IsScanning()) { UniValue scanning(UniValue::VOBJ); @@ -219,7 +662,7 @@ static RPCHelpMan loadwallet() { return RPCHelpMan{"loadwallet", "\nLoads a wallet from a wallet file or directory." - "\nNote that all wallet command-line options used when starting bitcoind will be" + "\nNote that all wallet command-line options used when starting qtumd will be" "\napplied to the new wallet.\n", { {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."}, @@ -961,6 +1404,11 @@ Span GetWalletRPCCommands() {"wallet", &walletpassphrase}, {"wallet", &walletpassphrasechange}, {"wallet", &walletprocesspsbt}, + {"wallet", &reservebalance, }, + {"wallet", &setsuperstakervaluesforaddress}, + {"wallet", &listsuperstakercustomvalues}, + {"wallet", &listsuperstakervaluesforaddress}, + {"wallet", &removesuperstakervaluesforaddress}, }; return commands; } diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 5d23ebd35a..20e3c13228 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -325,7 +325,7 @@ CoinsResult AvailableCoins(const CWallet& wallet, const uint256& txid = entry.first; const CWalletTx& wtx = entry.second; - if (wallet.IsTxImmatureCoinBase(wtx) && !params.include_immature_coinbase) + if (wallet.IsTxImmature(wtx) && !params.include_immature_coinbase) continue; int nDepth = wallet.GetTxDepthInMainChain(wtx); @@ -979,7 +979,10 @@ static util::Result CreateTransactionInternal( const std::vector& vecSend, std::optional change_pos, const CCoinControl& coin_control, - bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) + bool sign, + CAmount nGasFee = 0, + bool hasSender = false, + const CTxDestination& signSenderAddress = CNoDestination()) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { AssertLockHeld(wallet.cs_wallet); @@ -1001,6 +1004,11 @@ static util::Result CreateTransactionInternal( const OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend); ReserveDestination reservedest(&wallet, change_type); unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from + COutPoint senderInput; + if(hasSender && coin_control.HasSelected()){ + std::vector vSenderInputs = coin_control.ListSelected(); + senderInput=vSenderInputs[0]; + } for (const auto& recipient : vecSend) { recipients_sum += recipient.nAmount; @@ -1104,7 +1112,7 @@ static util::Result CreateTransactionInternal( } // Include the fees for things that aren't inputs, excluding the change output - const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.m_subtract_fee_outputs ? 0 : coin_selection_params.tx_noinputs_size); + const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.m_subtract_fee_outputs ? 0 : coin_selection_params.tx_noinputs_size)+nGasFee; CAmount selection_target = recipients_sum + not_input_fees; // This can only happen if feerate is 0, and requested destinations are value of 0 (e.g. OP_RETURN) @@ -1220,7 +1228,7 @@ static util::Result CreateTransactionInternal( if (nBytes == -1) { return util::Error{_("Missing solving data for estimating transaction size")}; } - CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes) + result.GetTotalBumpFees(); + CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes) + result.GetTotalBumpFees() + nGasFee; const CAmount output_value = CalculateOutputValue(txNew); Assume(recipients_sum + change_amount == output_value); CAmount current_fee = result.GetSelectedValue() - output_value; @@ -1290,6 +1298,11 @@ static util::Result CreateTransactionInternal( return util::Error{error}; } + // Signing transaction outputs + if(sign && txNew.HasOpSender() && !wallet.SignTransactionOutput(txNew)) { + return util::Error{_("Signing transaction output failed")}; + } + if (sign && !wallet.SignTransaction(txNew)) { return util::Error{_("Signing transaction failed")}; } @@ -1304,7 +1317,7 @@ static util::Result CreateTransactionInternal( return util::Error{_("Transaction too large")}; } - if (current_fee > wallet.m_default_max_tx_fee) { + if (!tx->HasCreateOrCall() && current_fee > wallet.m_default_max_tx_fee) { return util::Error{TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)}; } @@ -1337,7 +1350,10 @@ util::Result CreateTransaction( const std::vector& vecSend, std::optional change_pos, const CCoinControl& coin_control, - bool sign) + bool sign, + CAmount nGasFee, + bool hasSender, + const CTxDestination& signSenderAddress) { if (vecSend.empty()) { return util::Error{_("Transaction must have at least one recipient")}; @@ -1349,7 +1365,7 @@ util::Result CreateTransaction( LOCK(wallet.cs_wallet); - auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign); + auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign, nGasFee, hasSender, signSenderAddress); TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), bool(res), @@ -1368,7 +1384,7 @@ util::Result CreateTransaction( ExtractDestination(txr_ungrouped.tx->vout[*txr_ungrouped.change_pos].scriptPubKey, tmp_cc.destChange); } - auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign); + auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign, nGasFee, hasSender, signSenderAddress); // if fee of this alternative one is within the range of the max fee, we use this one const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false}; TRACE5(coin_selection, aps_create_tx_internal, @@ -1426,7 +1442,8 @@ util::Result FundTransaction(CWallet& wallet, const CM preset_txin.SetScriptWitness(txin.scriptWitness); } - auto res = CreateTransaction(wallet, vecSend, change_pos, coinControl, false); + CAmount nGasFee = wallet.GetTxGasFee(tx); + auto res = CreateTransaction(wallet, vecSend, change_pos, coinControl, false, nGasFee); if (!res) { return res; } diff --git a/src/wallet/spend.h b/src/wallet/spend.h index 62a7b4e4c8..413a461177 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -218,7 +218,7 @@ struct CreatedTransactionResult * selected by SelectCoins(); Also create the change output, when needed * @note passing change_pos as std::nullopt will result in setting a random position */ -util::Result CreateTransaction(CWallet& wallet, const std::vector& vecSend, std::optional change_pos, const CCoinControl& coin_control, bool sign = true); +util::Result CreateTransaction(CWallet& wallet, const std::vector& vecSend, std::optional change_pos, const CCoinControl& coin_control, bool sign = true, CAmount nGasFee=0, bool hasSender=false, const CTxDestination& signSenderAddress = CNoDestination()); /** * Insert additional inputs into the transaction by