From 46fe14779f394532a9ca57b583d2768b719ea1bd Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 10 Dec 2020 01:49:14 +0100 Subject: [PATCH 1/3] [RPC] Redirect sendtoaddress to shieldedsendmany when shielded recipient --- src/wallet/rpcwallet.cpp | 58 +++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index dc515daafd58..27edaa2fb5a2 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -933,6 +933,8 @@ void SendMoney(const CTxDestination& address, CAmount nValue, CWalletTx& wtxNew) throw JSONRPCError(RPC_WALLET_ERROR, res.ToString()); } +static SaplingOperation CreateShieldedTransaction(const JSONRPCRequest& request); + UniValue sendtoaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) @@ -958,22 +960,60 @@ UniValue sendtoaddress(const JSONRPCRequest& request) HelpExampleCli("sendtoaddress", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\" 0.1 \"donation\" \"seans outpost\"") + HelpExampleRpc("sendtoaddress", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\", 0.1, \"donation\", \"seans outpost\"")); - LOCK2(cs_main, pwalletMain->cs_wallet); - - bool isStaking = false; - CTxDestination address = DecodeDestination(request.params[0].get_str(), isStaking); - if (!IsValidDestination(address) || isStaking) + bool isStaking = false, isShielded = false; + const std::string addrStr = request.params[0].get_str(); + const CWDestination& destination = Standard::DecodeDestination(addrStr, isStaking, isShielded); + if (!Standard::IsValidDestination(destination) || isStaking) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address"); + const std::string commentStr = (request.params.size() > 2 && !request.params[2].isNull()) ? + request.params[2].get_str() : ""; + const std::string toStr = (request.params.size() > 3 && !request.params[3].isNull()) ? + request.params[3].get_str() : ""; + + if (isShielded) { + // convert params to 'shieldedsendmany' format + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + req.params.push_back(UniValue("from_transparent")); + UniValue recipients(UniValue::VARR); + UniValue recipient(UniValue::VOBJ); + recipient.pushKV("address", addrStr); + recipient.pushKV("amount", request.params[1]); + recipients.push_back(recipient); + req.params.push_back(recipients); + + // send + SaplingOperation operation = CreateShieldedTransaction(req); + std::string txid; + auto res = operation.send(txid); + if (!res) + throw JSONRPCError(RPC_WALLET_ERROR, res.getError()); + + // add comment + const uint256 txHash(txid); + assert(pwalletMain->mapWallet.count(txHash)); + if (!commentStr.empty()) { + pwalletMain->mapWallet[txHash].mapValue["comment"] = commentStr; + } + if (!toStr.empty()) { + pwalletMain->mapWallet[txHash].mapValue["to"] = toStr; + } + + return txid; + } + + const CTxDestination& address = *Standard::GetTransparentDestination(destination); + LOCK2(cs_main, pwalletMain->cs_wallet); // Amount CAmount nAmount = AmountFromValue(request.params[1]); // Wallet comments CWalletTx wtx; - if (request.params.size() > 2 && !request.params[2].isNull() && !request.params[2].get_str().empty()) - wtx.mapValue["comment"] = request.params[2].get_str(); - if (request.params.size() > 3 && !request.params[3].isNull() && !request.params[3].get_str().empty()) - wtx.mapValue["to"] = request.params[3].get_str(); + if (!commentStr.empty()) + wtx.mapValue["comment"] = commentStr; + if (!toStr.empty()) + wtx.mapValue["to"] = toStr; EnsureWalletIsUnlocked(); From 774c5441b39b523fcd7bce309060b7c1e0671688 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 10 Dec 2020 02:11:52 +0100 Subject: [PATCH 2/3] [Test] Add case for (shielded) sendtoaddress --- test/functional/sapling_wallet.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/functional/sapling_wallet.py b/test/functional/sapling_wallet.py index 3d683011e97a..85322edd47a6 100755 --- a/test/functional/sapling_wallet.py +++ b/test/functional/sapling_wallet.py @@ -324,7 +324,31 @@ def run_test(self): # Balance of node 0 is: prev_balance - 1 PIV (+fee) sent externally + 250 PIV matured coinbase assert_equal(self.nodes[0].getbalance(), satoshi_round(prev_balance + Decimal('249') - Decimal(fee))) + # Now shield some funds using sendtoaddress + self.log.info("TX12: Shielding coins with sendtoaddress RPC...") + prev_balance = self.nodes[0].getbalance() + mytxid12 = self.nodes[0].sendtoaddress(saplingAddr0, Decimal('10')) + self.check_tx_priority([mytxid12]) + self.log.info("Done. Checking details and balances...") + + # Decrypted transaction details should be correct + pt = self.nodes[0].viewshieldedtransaction(mytxid12) + fee = pt["fee"] + assert_equal(pt['txid'], mytxid12) + assert_equal(len(pt['spends']), 0) + assert_equal(len(pt['outputs']), 1) + out = pt['outputs'][0] + assert_equal(out['address'], saplingAddr0) + assert_equal(out['outgoing'], False) + assert_equal(out['value'], Decimal('10')) + + # Verify balance + self.nodes[2].generate(1) + self.sync_all() + assert_equal(self.nodes[0].getshieldedbalance(saplingAddr0), Decimal('29')) # 19 prev balance + 10 received + self.log.info("All good.") + if __name__ == '__main__': WalletSaplingTest().main() From 7dbd3bff6a459d388cbff552a5ca11a24b541ac1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 11 Dec 2020 15:20:11 +0100 Subject: [PATCH 3/3] Refactor: Decouple ShieldedSendManyTo from sendtoaddress/sendmany --- src/wallet/rpcwallet.cpp | 113 ++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 62 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 27edaa2fb5a2..c668a2e56290 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -935,6 +935,53 @@ void SendMoney(const CTxDestination& address, CAmount nValue, CWalletTx& wtxNew) static SaplingOperation CreateShieldedTransaction(const JSONRPCRequest& request); +/* + * redirect sendtoaddress/sendmany inputs to shieldedsendmany implementation (CreateShieldedTransaction) + */ +static UniValue ShieldedSendManyTo(const UniValue& sendTo, + const std::string& commentStr, + const std::string& toStr, + int nMinDepth, + bool fIncludeDelegated) +{ + // convert params to 'shieldedsendmany' format + JSONRPCRequest req; + req.params = UniValue(UniValue::VARR); + if (!fIncludeDelegated) { + req.params.push_back(UniValue("from_transparent")); + } else { + req.params.push_back(UniValue("from_trans_cold")); + } + UniValue recipients(UniValue::VARR); + for (const std::string& key : sendTo.getKeys()) { + UniValue recipient(UniValue::VOBJ); + recipient.pushKV("address", key); + recipient.pushKV("amount", sendTo[key]); + recipients.push_back(recipient); + } + req.params.push_back(recipients); + req.params.push_back(nMinDepth); + + // send + SaplingOperation operation = CreateShieldedTransaction(req); + std::string txid; + auto res = operation.send(txid); + if (!res) + throw JSONRPCError(RPC_WALLET_ERROR, res.getError()); + + // add comments + const uint256 txHash(txid); + assert(pwalletMain->mapWallet.count(txHash)); + if (!commentStr.empty()) { + pwalletMain->mapWallet[txHash].mapValue["comment"] = commentStr; + } + if (!toStr.empty()) { + pwalletMain->mapWallet[txHash].mapValue["to"] = toStr; + } + + return txid; +} + UniValue sendtoaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) @@ -971,35 +1018,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request) request.params[3].get_str() : ""; if (isShielded) { - // convert params to 'shieldedsendmany' format - JSONRPCRequest req; - req.params = UniValue(UniValue::VARR); - req.params.push_back(UniValue("from_transparent")); - UniValue recipients(UniValue::VARR); - UniValue recipient(UniValue::VOBJ); - recipient.pushKV("address", addrStr); - recipient.pushKV("amount", request.params[1]); - recipients.push_back(recipient); - req.params.push_back(recipients); - - // send - SaplingOperation operation = CreateShieldedTransaction(req); - std::string txid; - auto res = operation.send(txid); - if (!res) - throw JSONRPCError(RPC_WALLET_ERROR, res.getError()); - - // add comment - const uint256 txHash(txid); - assert(pwalletMain->mapWallet.count(txHash)); - if (!commentStr.empty()) { - pwalletMain->mapWallet[txHash].mapValue["comment"] = commentStr; - } - if (!toStr.empty()) { - pwalletMain->mapWallet[txHash].mapValue["to"] = toStr; - } - - return txid; + UniValue sendTo(UniValue::VOBJ); + sendTo.pushKV(addrStr, request.params[1]); + return ShieldedSendManyTo(sendTo, commentStr, toStr, 1, false); } const CTxDestination& address = *Standard::GetTransparentDestination(destination); @@ -2168,39 +2189,7 @@ UniValue sendmany(const JSONRPCRequest& request) } if (fShieldSend) { - // convert params to 'shieldedsendmany' format - JSONRPCRequest req; - req.params = UniValue(UniValue::VARR); - if (!fIncludeDelegated) { - req.params.push_back(UniValue("from_transparent")); - } else { - req.params.push_back(UniValue("from_trans_cold")); - } - UniValue recipients(UniValue::VARR); - for (const std::string& key : sendTo.getKeys()) { - UniValue recipient(UniValue::VOBJ); - recipient.pushKV("address", key); - recipient.pushKV("amount", sendTo[key]); - recipients.push_back(recipient); - } - req.params.push_back(recipients); - req.params.push_back(nMinDepth); - - // send - SaplingOperation operation = CreateShieldedTransaction(req); - std::string txid; - auto res = operation.send(txid); - if (!res) - throw JSONRPCError(RPC_WALLET_ERROR, res.getError()); - - // add comment - if (!comment.empty()) { - const uint256 txHash(txid); - assert(pwalletMain->mapWallet.count(txHash)); - pwalletMain->mapWallet[txHash].mapValue["comment"] = comment; - } - - return txid; + return ShieldedSendManyTo(sendTo, comment, "", nMinDepth, fIncludeDelegated); } // All recipients are transparent: use Legacy sendmany t->t