This commit is contained in:
jl777
2018-12-05 01:14:05 -11:00
parent 8c939c1af5
commit e34bb5163a
4 changed files with 589 additions and 421 deletions

View File

@@ -34,6 +34,8 @@
using namespace libzcash; using namespace libzcash;
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
int mta_find_output(UniValue obj, int n) int mta_find_output(UniValue obj, int n)
{ {
UniValue outputMapValue = find_value(obj, "outputmap"); UniValue outputMapValue = find_value(obj, "outputmap");
@@ -53,20 +55,22 @@ int mta_find_output(UniValue obj, int n)
} }
AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress( AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx, CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs, std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputNote> noteInputs, std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient, MergeToAddressRecipient recipient,
CAmount fee, CAmount fee,
UniValue contextInfo) : UniValue contextInfo) :
tx_(contextualTx), utxoInputs_(utxoInputs), noteInputs_(noteInputs), tx_(contextualTx), utxoInputs_(utxoInputs), sproutNoteInputs_(sproutNoteInputs),
recipient_(recipient), fee_(fee), contextinfo_(contextInfo) saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
{ {
if (fee < 0 || fee > MAX_MONEY) { if (fee < 0 || fee > MAX_MONEY) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range");
} }
if (utxoInputs.empty() && noteInputs.empty()) { if (utxoInputs.empty() && sproutNoteInputs.empty() && saplingNoteInputs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs"); throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs");
} }
@@ -74,6 +78,20 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing");
} }
if (sproutNoteInputs.size() > 0 && saplingNoteInputs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
if (sproutNoteInputs.size() > 0 && builder) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Sprout notes are not supported by the TransactionBuilder");
}
isUsingBuilder_ = false;
if (builder) {
isUsingBuilder_ = true;
builder_ = builder.get();
}
toTaddr_ = DecodeDestination(std::get<0>(recipient)); toTaddr_ = DecodeDestination(std::get<0>(recipient));
isToTaddr_ = IsValidDestination(toTaddr_); isToTaddr_ = IsValidDestination(toTaddr_);
isToZaddr_ = false; isToZaddr_ = false;
@@ -82,10 +100,6 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
auto address = DecodePaymentAddress(std::get<0>(recipient)); auto address = DecodePaymentAddress(std::get<0>(recipient));
if (IsValidPaymentAddress(address)) { if (IsValidPaymentAddress(address)) {
isToZaddr_ = true; isToZaddr_ = true;
// TODO: Add Sapling support. For now, return an error to the user.
if (boost::get<libzcash::SproutPaymentAddress>(&address) == nullptr) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout zaddrs are supported");
}
toPaymentAddress_ = address; toPaymentAddress_ = address;
} else { } else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid recipient address"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid recipient address");
@@ -155,9 +169,9 @@ void AsyncRPCOperation_mergetoaddress::main()
#ifdef ENABLE_MINING #ifdef ENABLE_MINING
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
GenerateBitcoins(GetBoolArg("-gen", false), pwalletMain, GetArg("-genproclimit", 0)); GenerateBitcoins(GetBoolArg("-gen", false), pwalletMain, GetArg("-genproclimit", 1));
#else #else
GenerateBitcoins(GetBoolArg("-gen", false), GetArg("-genproclimit", 0)); GenerateBitcoins(GetBoolArg("-gen", false), GetArg("-genproclimit", 1));
#endif #endif
#endif #endif
@@ -203,7 +217,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
{ {
assert(isToTaddr_ != isToZaddr_); assert(isToTaddr_ != isToZaddr_);
bool isPureTaddrOnlyTx = (noteInputs_.empty() && isToTaddr_); bool isPureTaddrOnlyTx = (sproutNoteInputs_.empty() && saplingNoteInputs_.empty() && isToTaddr_);
CAmount minersFee = fee_; CAmount minersFee = fee_;
size_t numInputs = utxoInputs_.size(); size_t numInputs = utxoInputs_.size();
@@ -228,7 +242,11 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
} }
CAmount z_inputs_total = 0; CAmount z_inputs_total = 0;
for (MergeToAddressInputNote& t : noteInputs_) { for (const MergeToAddressInputSproutNote& t : sproutNoteInputs_) {
z_inputs_total += std::get<2>(t);
}
for (const MergeToAddressInputSaplingNote& t : saplingNoteInputs_) {
z_inputs_total += std::get<2>(t); z_inputs_total += std::get<2>(t);
} }
@@ -243,8 +261,9 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
CAmount sendAmount = targetAmount - minersFee; CAmount sendAmount = targetAmount - minersFee;
// update the transaction with the UTXO inputs and output (if any) // update the transaction with the UTXO inputs and output (if any)
if (!isUsingBuilder_) {
CMutableTransaction rawTx(tx_); CMutableTransaction rawTx(tx_);
for (MergeToAddressInputUTXO& t : utxoInputs_) { for (const MergeToAddressInputUTXO& t : utxoInputs_) {
CTxIn in(std::get<0>(t)); CTxIn in(std::get<0>(t));
rawTx.vin.push_back(in); rawTx.vin.push_back(in);
} }
@@ -254,6 +273,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
rawTx.vout.push_back(out); rawTx.vout.push_back(out);
} }
tx_ = CTransaction(rawTx); tx_ = CTransaction(rawTx);
}
LogPrint(isPureTaddrOnlyTx ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n", LogPrint(isPureTaddrOnlyTx ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee)); getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee));
@@ -272,6 +292,127 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
} }
/**
* SCENARIO #0
*
* Sprout not involved, so we just use the TransactionBuilder and we're done.
*
* This is based on code from AsyncRPCOperation_sendmany::main_impl() and should be refactored.
*/
if (isUsingBuilder_) {
builder_.SetFee(minersFee);
for (const MergeToAddressInputUTXO& t : utxoInputs_) {
COutPoint outPoint = std::get<0>(t);
CAmount amount = std::get<1>(t);
CScript scriptPubKey = std::get<2>(t);
builder_.AddTransparentInput(outPoint, scriptPubKey, amount);
}
boost::optional<uint256> ovk;
// Select Sapling notes
std::vector<SaplingOutPoint> saplingOPs;
std::vector<SaplingNote> saplingNotes;
std::vector<SaplingExpandedSpendingKey> expsks;
for (const MergeToAddressInputSaplingNote& saplingNoteInput: saplingNoteInputs_) {
saplingOPs.push_back(std::get<0>(saplingNoteInput));
saplingNotes.push_back(std::get<1>(saplingNoteInput));
auto expsk = std::get<3>(saplingNoteInput);
expsks.push_back(expsk);
if (!ovk) {
ovk = expsk.full_viewing_key().ovk;
}
}
// Fetch Sapling anchor and witnesses
uint256 anchor;
std::vector<boost::optional<SaplingWitness>> witnesses;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetSaplingNoteWitnesses(saplingOPs, witnesses, anchor);
}
// Add Sapling spends
for (size_t i = 0; i < saplingNotes.size(); i++) {
if (!witnesses[i]) {
throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note");
}
assert(builder_.AddSaplingSpend(expsks[i], saplingNotes[i], anchor, witnesses[i].get()));
}
if (isToTaddr_) {
if (!builder_.AddTransparentOutput(toTaddr_, sendAmount)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr.");
}
} else {
std::string zaddr = std::get<0>(recipient_);
std::string memo = std::get<1>(recipient_);
std::array<unsigned char, ZC_MEMO_SIZE> hexMemo = get_memo_from_hex_string(memo);
auto saplingPaymentAddress = boost::get<libzcash::SaplingPaymentAddress>(&toPaymentAddress_);
if (saplingPaymentAddress == nullptr) {
// This should never happen as we have already determined that the payment is to sapling
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not get Sapling payment address.");
}
if (saplingNoteInputs_.size() == 0 && utxoInputs_.size() > 0) {
// Sending from t-addresses, which we don't have ovks for. Instead,
// generate a common one from the HD seed. This ensures the data is
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"AsyncRPCOperation_sendmany: HD seed not found");
}
ovk = ovkForShieldingFromTaddr(seed);
}
if (!ovk) {
throw JSONRPCError(RPC_WALLET_ERROR, "Sending to a Sapling address requires an ovk.");
}
builder_.AddSaplingOutput(ovk.get(), *saplingPaymentAddress, sendAmount, hexMemo);
}
// Build the transaction
auto maybe_tx = builder_.Build();
if (!maybe_tx) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction.");
}
tx_ = maybe_tx.get();
// Send the transaction
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction
auto signedtxn = EncodeHexTx(tx_);
if (!testmode) {
UniValue params = UniValue(UniValue::VARR);
params.push_back(signedtxn);
UniValue sendResultValue = sendrawtransaction(params, false);
if (sendResultValue.isNull()) {
throw JSONRPCError(RPC_WALLET_ERROR, "sendrawtransaction did not return an error or a txid.");
}
auto txid = sendResultValue.get_str();
UniValue o(UniValue::VOBJ);
o.push_back(Pair("txid", txid));
set_result(o);
} else {
// Test mode does not send the transaction to the network.
UniValue o(UniValue::VOBJ);
o.push_back(Pair("test", 1));
o.push_back(Pair("txid", tx_.GetHash().ToString()));
o.push_back(Pair("hex", signedtxn));
set_result(o);
}
return true;
}
/**
* END SCENARIO #0
*/
/** /**
* SCENARIO #1 * SCENARIO #1
* *
@@ -305,7 +446,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
* *
* We only need a single JoinSplit. * We only need a single JoinSplit.
*/ */
if (noteInputs_.empty() && isToZaddr_) { if (sproutNoteInputs_.empty() && isToZaddr_) {
// Create JoinSplit to target z-addr. // Create JoinSplit to target z-addr.
MergeToAddressJSInfo info; MergeToAddressJSInfo info;
info.vpub_old = sendAmount; info.vpub_old = sendAmount;
@@ -328,12 +469,8 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// Copy zinputs to more flexible containers // Copy zinputs to more flexible containers
std::deque<MergeToAddressInputNote> zInputsDeque; std::deque<MergeToAddressInputSproutNote> zInputsDeque;
for (auto o : noteInputs_) { for (const auto& o : sproutNoteInputs_) {
// TODO: Add Sapling support. For now, return an error to the user.
if (boost::get<libzcash::SproutSpendingKey>(&std::get<3>(o)) == nullptr) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout zaddrs are supported");
}
zInputsDeque.push_back(o); zInputsDeque.push_back(o);
} }
@@ -342,7 +479,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// to happen as creating a chained joinsplit transaction can take longer than the block interval. // to happen as creating a chained joinsplit transaction can take longer than the block interval.
{ {
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto t : noteInputs_) { for (auto t : sproutNoteInputs_) {
JSOutPoint jso = std::get<0>(t); JSOutPoint jso = std::get<0>(t);
std::vector<JSOutPoint> vOutPoints = {jso}; std::vector<JSOutPoint> vOutPoints = {jso};
uint256 inputAnchor; uint256 inputAnchor;
@@ -373,7 +510,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// At this point, we are guaranteed to have at least one input note. // At this point, we are guaranteed to have at least one input note.
// Use address of first input note as the temporary change address. // Use address of first input note as the temporary change address.
SproutSpendingKey changeKey = boost::get<libzcash::SproutSpendingKey>(std::get<3>(zInputsDeque.front())); SproutSpendingKey changeKey = std::get<3>(zInputsDeque.front());
SproutPaymentAddress changeAddress = changeKey.address(); SproutPaymentAddress changeAddress = changeKey.address();
CAmount vpubOldTarget = 0; CAmount vpubOldTarget = 0;
@@ -495,11 +632,11 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
uint256 inputAnchor; uint256 inputAnchor;
int numInputsNeeded = (jsChange > 0) ? 1 : 0; int numInputsNeeded = (jsChange > 0) ? 1 : 0;
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) { while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
MergeToAddressInputNote t = zInputsDeque.front(); MergeToAddressInputSproutNote t = zInputsDeque.front();
JSOutPoint jso = std::get<0>(t); JSOutPoint jso = std::get<0>(t);
SproutNote note = std::get<1>(t); SproutNote note = std::get<1>(t);
CAmount noteFunds = std::get<2>(t); CAmount noteFunds = std::get<2>(t);
SproutSpendingKey zkey = boost::get<libzcash::SproutSpendingKey>(std::get<3>(t)); SproutSpendingKey zkey = std::get<3>(t);
zInputsDeque.pop_front(); zInputsDeque.pop_front();
MergeToAddressWitnessAnchorData wad = jsopWitnessAnchorMap[jso.ToString()]; MergeToAddressWitnessAnchorData wad = jsopWitnessAnchorMap[jso.ToString()];
@@ -525,7 +662,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) { if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString())); throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
} }
wtxHeight = mapBlockIndex[wtx.hashBlock]->GetHeight(); wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight;
wtxDepth = wtx.GetDepthInMainChain(); wtxDepth = wtx.GetDepthInMainChain();
} }
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n", LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
@@ -628,7 +765,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
extern UniValue signrawtransaction(const UniValue& params, bool fHelp); extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
/** /**
* Sign and send a raw transaction. * Sign and send a raw transaction.
@@ -947,7 +1083,10 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
*/ */
void AsyncRPCOperation_mergetoaddress::lock_notes() { void AsyncRPCOperation_mergetoaddress::lock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : noteInputs_) { for (auto note : sproutNoteInputs_) {
pwalletMain->LockNote(std::get<0>(note));
}
for (auto note : saplingNoteInputs_) {
pwalletMain->LockNote(std::get<0>(note)); pwalletMain->LockNote(std::get<0>(note));
} }
} }
@@ -957,7 +1096,10 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
*/ */
void AsyncRPCOperation_mergetoaddress::unlock_notes() { void AsyncRPCOperation_mergetoaddress::unlock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : noteInputs_) { for (auto note : sproutNoteInputs_) {
pwalletMain->UnlockNote(std::get<0>(note));
}
for (auto note : saplingNoteInputs_) {
pwalletMain->UnlockNote(std::get<0>(note)); pwalletMain->UnlockNote(std::get<0>(note));
} }
} }

View File

@@ -9,6 +9,7 @@
#include "asyncrpcoperation.h" #include "asyncrpcoperation.h"
#include "paymentdisclosure.h" #include "paymentdisclosure.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "transaction_builder.h"
#include "wallet.h" #include "wallet.h"
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "zcash/JoinSplit.hpp" #include "zcash/JoinSplit.hpp"
@@ -24,11 +25,13 @@
using namespace libzcash; using namespace libzcash;
// Input UTXO is a tuple of txid, vout, amount // Input UTXO is a tuple of txid, vout, amount, script
typedef std::tuple<COutPoint, CAmount> MergeToAddressInputUTXO; typedef std::tuple<COutPoint, CAmount, CScript> MergeToAddressInputUTXO;
// Input JSOP is a tuple of JSOutpoint, note, amount, spending key // Input JSOP is a tuple of JSOutpoint, note, amount, spending key
typedef std::tuple<JSOutPoint, SproutNote, CAmount, SpendingKey> MergeToAddressInputNote; typedef std::tuple<JSOutPoint, SproutNote, CAmount, SproutSpendingKey> MergeToAddressInputSproutNote;
typedef std::tuple<SaplingOutPoint, SaplingNote, CAmount, SaplingExpandedSpendingKey> MergeToAddressInputSaplingNote;
// A recipient is a tuple of address, memo (optional if zaddr) // A recipient is a tuple of address, memo (optional if zaddr)
typedef std::tuple<std::string, std::string> MergeToAddressRecipient; typedef std::tuple<std::string, std::string> MergeToAddressRecipient;
@@ -53,9 +56,11 @@ class AsyncRPCOperation_mergetoaddress : public AsyncRPCOperation
{ {
public: public:
AsyncRPCOperation_mergetoaddress( AsyncRPCOperation_mergetoaddress(
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx, CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs, std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputNote> noteInputs, std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient, MergeToAddressRecipient recipient,
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE, CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE,
UniValue contextInfo = NullUniValue); UniValue contextInfo = NullUniValue);
@@ -80,6 +85,7 @@ private:
UniValue contextinfo_; // optional data to include in return value from getStatus() UniValue contextinfo_; // optional data to include in return value from getStatus()
bool isUsingBuilder_; // Indicates that no Sprout addresses are involved
uint32_t consensusBranchId_; uint32_t consensusBranchId_;
CAmount fee_; CAmount fee_;
int mindepth_; int mindepth_;
@@ -96,8 +102,10 @@ private:
std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap; std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap;
std::vector<MergeToAddressInputUTXO> utxoInputs_; std::vector<MergeToAddressInputUTXO> utxoInputs_;
std::vector<MergeToAddressInputNote> noteInputs_; std::vector<MergeToAddressInputSproutNote> sproutNoteInputs_;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs_;
TransactionBuilder builder_;
CTransaction tx_; CTransaction tx_;
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s); std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);

View File

@@ -2849,8 +2849,8 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
"\nExamples\n" "\nExamples\n"
+ HelpExampleCli("z_listunspent", "") + HelpExampleCli("z_listunspent", "")
+ HelpExampleCli("z_listunspent", "6 9999999 false \"[\\\"ztbx5DLDxa5ZLFTchHhoPNkKs57QzSyib6UqXpEdy76T1aUdFxJt1w9318Z8DJ73XzbnWHKEZP9Yjg712N5kMmP4QzS9iC9\\\",\\\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\\\"]\"") + HelpExampleCli("z_listunspent", "6 9999999 false \"[\\\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\\\",\\\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\\\"]\"")
+ HelpExampleRpc("z_listunspent", "6 9999999 false \"[\\\"ztbx5DLDxa5ZLFTchHhoPNkKs57QzSyib6UqXpEdy76T1aUdFxJt1w9318Z8DJ73XzbnWHKEZP9Yjg712N5kMmP4QzS9iC9\\\",\\\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\\\"]\"") + HelpExampleRpc("z_listunspent", "6 9999999 false \"[\\\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\\\",\\\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\\\"]\"")
); );
RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VBOOL)(UniValue::VARR)); RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VBOOL)(UniValue::VARR));
@@ -3686,8 +3686,8 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
" \"change\": true|false, (boolean) true if the address that received the note is also one of the sending addresses\n" " \"change\": true|false, (boolean) true if the address that received the note is also one of the sending addresses\n"
"}\n" "}\n"
"\nExamples:\n" "\nExamples:\n"
+ HelpExampleCli("z_listreceivedbyaddress", "\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") + HelpExampleCli("z_listreceivedbyaddress", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
+ HelpExampleRpc("z_listreceivedbyaddress", "\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") + HelpExampleRpc("z_listreceivedbyaddress", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
); );
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
@@ -4019,8 +4019,8 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
"\nResult:\n" "\nResult:\n"
"\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" "\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
"\nExamples:\n" "\nExamples:\n"
+ HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\" ,\"amount\": 5.0}]'") + HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'")
+ HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\" ,\"amount\": 5.0}]") + HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]")
); );
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
@@ -4110,15 +4110,10 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
} }
// If we are sending from a shielded address, all recipient // If we are sending from a shielded address, all recipient
// shielded addresses must be of the same type. // shielded addresses must be of the same type.
if (fromSprout && toSapling) { if ((fromSprout && toSapling) || (fromSapling && toSprout)) {
throw JSONRPCError( throw JSONRPCError(
RPC_INVALID_PARAMETER, RPC_INVALID_PARAMETER,
"Cannot send from a Sprout address to a Sapling address using z_sendmany"); "Cannot send between Sprout and Sapling addresses using z_sendmany");
}
if (fromSapling && toSprout) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send from a Sapling address to a Sprout address using z_sendmany");
} }
} else { } else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address );
@@ -4330,8 +4325,8 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
" \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" " \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
"}\n" "}\n"
"\nExamples:\n" "\nExamples:\n"
+ HelpExampleCli("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") + HelpExampleCli("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
+ HelpExampleRpc("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") + HelpExampleRpc("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
); );
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
@@ -4519,11 +4514,13 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
return o; return o;
} }
#define MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT 50 #define MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT 50
#define MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT 10 #define MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT 10
#define MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT 90
#define JOINSPLIT_SIZE GetSerializeSize(JSDescription(), SER_NETWORK, PROTOCOL_VERSION) #define JOINSPLIT_SIZE GetSerializeSize(JSDescription(), SER_NETWORK, PROTOCOL_VERSION)
#define OUTPUTDESCRIPTION_SIZE GetSerializeSize(OutputDescription(), SER_NETWORK, PROTOCOL_VERSION)
#define SPENDDESCRIPTION_SIZE GetSerializeSize(SpendDescription(), SER_NETWORK, PROTOCOL_VERSION)
UniValue z_mergetoaddress(const UniValue& params, bool fHelp) UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
{ {
@@ -4553,9 +4550,9 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
"\nArguments:\n" "\nArguments:\n"
"1. fromaddresses (string, required) A JSON array with addresses.\n" "1. fromaddresses (string, required) A JSON array with addresses.\n"
" The following special strings are accepted inside the array:\n" " The following special strings are accepted inside the array:\n"
" - \"*\": Merge both UTXOs and notes from all addresses belonging to the wallet.\n" " - \"ANY_TADDR\": Merge UTXOs from any t-addrs belonging to the wallet.\n"
" - \"ANY_TADDR\": Merge UTXOs from all t-addrs belonging to the wallet.\n" " - \"ANY_SPROUT\": Merge notes from any Sprout z-addrs belonging to the wallet.\n"
" - \"ANY_ZADDR\": Merge notes from all z-addrs belonging to the wallet.\n" " - \"ANY_SAPLING\": Merge notes from any Sapling z-addrs belonging to the wallet.\n"
" If a special string is given, any given addresses of that type will be ignored.\n" " If a special string is given, any given addresses of that type will be ignored.\n"
" [\n" " [\n"
" \"address\" (string) Can be a t-addr or a z-addr\n" " \"address\" (string) Can be a t-addr or a z-addr\n"
@@ -4567,7 +4564,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
"4. transparent_limit (numeric, optional, default=" "4. transparent_limit (numeric, optional, default="
+ strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT) + ") Limit on the maximum number of UTXOs to merge. Set to 0 to use node option -mempooltxinputlimit (before Overwinter), or as many as will fit in the transaction (after Overwinter).\n" + strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT) + ") Limit on the maximum number of UTXOs to merge. Set to 0 to use node option -mempooltxinputlimit (before Overwinter), or as many as will fit in the transaction (after Overwinter).\n"
"4. shielded_limit (numeric, optional, default=" "4. shielded_limit (numeric, optional, default="
+ strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT) + ") Limit on the maximum number of notes to merge. Set to 0 to merge as many as will fit in the transaction.\n" + strprintf("%d Sprout or %d Sapling Notes", MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT, MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT) + ") Limit on the maximum number of notes to merge. Set to 0 to merge as many as will fit in the transaction.\n"
"5. \"memo\" (string, optional) Encoded as hex. When toaddress is a z-addr, this will be stored in the memo field of the new note.\n" "5. \"memo\" (string, optional) Encoded as hex. When toaddress is a z-addr, this will be stored in the memo field of the new note.\n"
"\nResult:\n" "\nResult:\n"
"{\n" "{\n"
@@ -4583,7 +4580,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
"}\n" "}\n"
"\nExamples:\n" "\nExamples:\n"
+ HelpExampleCli("z_mergetoaddress", "'[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"]' ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf") + HelpExampleCli("z_mergetoaddress", "'[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"]' ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf")
+ HelpExampleRpc("z_mergetoaddress", "[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"], \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") + HelpExampleRpc("z_mergetoaddress", "[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"], \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
); );
if (!fEnableMergeToAddress) { if (!fEnableMergeToAddress) {
@@ -4592,14 +4589,12 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
bool useAny = false;
bool useAnyUTXO = false; bool useAnyUTXO = false;
bool useAnyNote = false; bool useAnySprout = false;
bool useAnySapling = false;
std::set<CTxDestination> taddrs = {}; std::set<CTxDestination> taddrs = {};
std::set<libzcash::PaymentAddress> zaddrs = {}; std::set<libzcash::PaymentAddress> zaddrs = {};
uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus());
UniValue addresses = params[0].get_array(); UniValue addresses = params[0].get_array();
if (addresses.size()==0) if (addresses.size()==0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, fromaddresses array is empty."); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, fromaddresses array is empty.");
@@ -4608,39 +4603,28 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
std::set<std::string> setAddress; std::set<std::string> setAddress;
// Sources // Sources
bool containsSaplingZaddrSource = false;
for (const UniValue& o : addresses.getValues()) { for (const UniValue& o : addresses.getValues()) {
if (!o.isStr()) if (!o.isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected string"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected string");
std::string address = o.get_str(); std::string address = o.get_str();
if (address == "*") {
useAny = true; if (address == "ANY_TADDR") {
} else if (address == "ANY_TADDR") {
useAnyUTXO = true; useAnyUTXO = true;
} else if (address == "ANY_ZADDR") { } else if (address == "ANY_SPROUT") {
useAnyNote = true; useAnySprout = true;
} else if (address == "ANY_SAPLING") {
useAnySapling = true;
} else { } else {
CTxDestination taddr = DecodeDestination(address); CTxDestination taddr = DecodeDestination(address);
if (IsValidDestination(taddr)) { if (IsValidDestination(taddr)) {
// Ignore any listed t-addrs if we are using all of them
if (!(useAny || useAnyUTXO)) {
taddrs.insert(taddr); taddrs.insert(taddr);
}
} else { } else {
auto zaddr = DecodePaymentAddress(address); auto zaddr = DecodePaymentAddress(address);
if (IsValidPaymentAddress(zaddr, branchId)) { if (IsValidPaymentAddress(zaddr)) {
// Ignore listed z-addrs if we are using all of them
if (!(useAny || useAnyNote)) {
zaddrs.insert(zaddr); zaddrs.insert(zaddr);
}
// Check if z-addr is Sapling
bool isSapling = boost::get<libzcash::SaplingPaymentAddress>(&zaddr) != nullptr;
containsSaplingZaddrSource |= isSapling;
} else { } else {
throw JSONRPCError( throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unknown address format: ") + address);
RPC_INVALID_PARAMETER,
string("Invalid parameter, unknown address format: ") + address);
} }
} }
} }
@@ -4650,28 +4634,38 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
setAddress.insert(address); setAddress.insert(address);
} }
if (useAnyUTXO && taddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific t-addrs when using \"ANY_TADDR\"");
}
if ((useAnySprout || useAnySapling) && zaddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific z-addrs when using \"ANY_SPROUT\" or \"ANY_SAPLING\"");
}
const int nextBlockHeight = chainActive.Height() + 1;
const bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
const bool saplingActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING);
// Validate the destination address // Validate the destination address
auto destaddress = params[1].get_str(); auto destaddress = params[1].get_str();
bool isToZaddr = false; bool isToSproutZaddr = false;
bool isToSaplingZaddr = false; bool isToSaplingZaddr = false;
CTxDestination taddr = DecodeDestination(destaddress); CTxDestination taddr = DecodeDestination(destaddress);
if (!IsValidDestination(taddr)) { if (!IsValidDestination(taddr)) {
if (IsValidPaymentAddressString(destaddress, branchId)) { auto decodeAddr = DecodePaymentAddress(destaddress);
isToZaddr = true; if (IsValidPaymentAddress(decodeAddr)) {
if (boost::get<libzcash::SaplingPaymentAddress>(&decodeAddr) != nullptr) {
// Is this a Sapling address? isToSaplingZaddr = true;
auto res = DecodePaymentAddress(destaddress); // If Sapling is not active, do not allow sending to a sapling addresses.
if (IsValidPaymentAddress(res)) { if (!saplingActive) {
isToSaplingZaddr = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr; throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
} else { } else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); isToSproutZaddr = true;
} }
} else { } else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress );
} }
} }
else if ( ASSETCHAINS_PRIVATE != 0 )
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cant use transparent addresses in private chain");
// Convert fee from currency format to zatoshis // Convert fee from currency format to zatoshis
CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE; CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE;
@@ -4691,18 +4685,21 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
} }
} }
int nNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT; int sproutNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT;
int saplingNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT;
if (params.size() > 4) { if (params.size() > 4) {
nNoteLimit = params[4].get_int(); int nNoteLimit = params[4].get_int();
if (nNoteLimit < 0) { if (nNoteLimit < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative");
} }
sproutNoteLimit = nNoteLimit;
saplingNoteLimit = nNoteLimit;
} }
std::string memo; std::string memo;
if (params.size() > 5) { if (params.size() > 5) {
memo = params[5].get_str(); memo = params[5].get_str();
if (!isToZaddr) { if (!(isToSproutZaddr || isToSaplingZaddr)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo can not be used with a taddr. It can only be used with a zaddr."); throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo can not be used with a taddr. It can only be used with a zaddr.");
} else if (!IsHex(memo)) { } else if (!IsHex(memo)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format."); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format.");
@@ -4714,50 +4711,29 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
MergeToAddressRecipient recipient(destaddress, memo); MergeToAddressRecipient recipient(destaddress, memo);
int nextBlockHeight = chainActive.Height() + 1;
bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING;
}
// This RPC does not support Sapling yet.
if (isToSaplingZaddr || containsSaplingZaddrSource) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling is not supported yet by z_mergetoadress");
}
// If this RPC does support Sapling...
// If Sapling is not active, do not allow sending from or sending to Sapling addresses.
if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
if (isToSaplingZaddr || containsSaplingZaddrSource) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
}
// Prepare to get UTXOs and notes // Prepare to get UTXOs and notes
std::vector<MergeToAddressInputUTXO> utxoInputs; std::vector<MergeToAddressInputUTXO> utxoInputs;
std::vector<MergeToAddressInputNote> noteInputs; std::vector<MergeToAddressInputSproutNote> sproutNoteInputs;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs;
CAmount mergedUTXOValue = 0; CAmount mergedUTXOValue = 0;
CAmount mergedNoteValue = 0; CAmount mergedNoteValue = 0;
CAmount remainingUTXOValue = 0; CAmount remainingUTXOValue = 0;
CAmount remainingNoteValue = 0; CAmount remainingNoteValue = 0;
#ifdef __LP64__
uint64_t utxoCounter = 0;
uint64_t noteCounter = 0;
#else
size_t utxoCounter = 0; size_t utxoCounter = 0;
size_t noteCounter = 0; size_t noteCounter = 0;
#endif
bool maxedOutUTXOsFlag = false; bool maxedOutUTXOsFlag = false;
bool maxedOutNotesFlag = false; bool maxedOutNotesFlag = false;
size_t mempoolLimit = (nUTXOLimit != 0) ? nUTXOLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0)); size_t mempoolLimit = (nUTXOLimit != 0) ? nUTXOLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0));
unsigned int max_tx_size = saplingActive ? MAX_TX_SIZE_AFTER_SAPLING : MAX_TX_SIZE_BEFORE_SAPLING;
size_t estimatedTxSize = 200; // tx overhead + wiggle room size_t estimatedTxSize = 200; // tx overhead + wiggle room
if (isToZaddr) { if (isToSproutZaddr) {
estimatedTxSize += JOINSPLIT_SIZE; estimatedTxSize += JOINSPLIT_SIZE;
} else if (isToSaplingZaddr) {
estimatedTxSize += OUTPUTDESCRIPTION_SIZE;
} }
if (useAny || useAnyUTXO || taddrs.size() > 0) { if (useAnyUTXO || taddrs.size() > 0) {
// Get available utxos // Get available utxos
vector<COutput> vecOutputs; vector<COutput> vecOutputs;
pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, false); pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, false);
@@ -4768,8 +4744,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
continue; continue;
} }
CScript scriptPubKey = out.tx->vout[out.i].scriptPubKey;
CTxDestination address; CTxDestination address;
if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { if (!ExtractDestination(scriptPubKey, address)) {
continue; continue;
} }
// If taddr is not wildcard "*", filter utxos // If taddr is not wildcard "*", filter utxos
@@ -4789,7 +4767,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
} else { } else {
estimatedTxSize += increase; estimatedTxSize += increase;
COutPoint utxo(out.tx->GetHash(), out.i); COutPoint utxo(out.tx->GetHash(), out.i);
utxoInputs.emplace_back(utxo, nValue); utxoInputs.emplace_back(utxo, nValue, scriptPubKey);
mergedUTXOValue += nValue; mergedUTXOValue += nValue;
} }
} }
@@ -4800,23 +4778,40 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
} }
} }
if (useAny || useAnyNote || zaddrs.size() > 0) { if (useAnySprout || useAnySapling || zaddrs.size() > 0) {
// Get available notes // Get available notes
std::vector<CSproutNotePlaintextEntry> sproutEntries; std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries; std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs); pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs);
// If Sapling is not active, do not allow sending from a sapling addresses.
if (!saplingActive && saplingEntries.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
// Sending from both Sprout and Sapling is currently unsupported using z_mergetoaddress
if (sproutEntries.size() > 0 && saplingEntries.size() > 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
// If sending between shielded addresses, they must be the same type
if ((saplingEntries.size() > 0 && isToSproutZaddr) || (sproutEntries.size() > 0 && isToSaplingZaddr)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send between Sprout and Sapling addresses using z_mergetoaddress");
}
// Find unspent notes and update estimated size // Find unspent notes and update estimated size
for (CSproutNotePlaintextEntry& entry : sproutEntries) { for (const CSproutNotePlaintextEntry& entry : sproutEntries) {
noteCounter++; noteCounter++;
CAmount nValue = entry.plaintext.value(); CAmount nValue = entry.plaintext.value();
if (!maxedOutNotesFlag) { if (!maxedOutNotesFlag) {
// If we haven't added any notes yet and the merge is to a // If we haven't added any notes yet and the merge is to a
// z-address, we have already accounted for the first JoinSplit. // z-address, we have already accounted for the first JoinSplit.
size_t increase = (noteInputs.empty() && !isToZaddr) || (noteInputs.size() % 2 == 0) ? JOINSPLIT_SIZE : 0; size_t increase = (sproutNoteInputs.empty() && !isToSproutZaddr) || (sproutNoteInputs.size() % 2 == 0) ? JOINSPLIT_SIZE : 0;
if (estimatedTxSize + increase >= max_tx_size || if (estimatedTxSize + increase >= max_tx_size ||
(nNoteLimit > 0 && noteCounter > nNoteLimit)) (sproutNoteLimit > 0 && noteCounter > sproutNoteLimit))
{ {
maxedOutNotesFlag = true; maxedOutNotesFlag = true;
} else { } else {
@@ -4824,7 +4819,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
auto zaddr = entry.address; auto zaddr = entry.address;
SproutSpendingKey zkey; SproutSpendingKey zkey;
pwalletMain->GetSproutSpendingKey(zaddr, zkey); pwalletMain->GetSproutSpendingKey(zaddr, zkey);
noteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey); sproutNoteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey);
mergedNoteValue += nValue; mergedNoteValue += nValue;
} }
} }
@@ -4833,17 +4828,35 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
remainingNoteValue += nValue; remainingNoteValue += nValue;
} }
} }
// TODO: Add Sapling support
for (const SaplingNoteEntry& entry : saplingEntries) {
noteCounter++;
CAmount nValue = entry.note.value();
if (!maxedOutNotesFlag) {
size_t increase = SPENDDESCRIPTION_SIZE;
if (estimatedTxSize + increase >= max_tx_size ||
(saplingNoteLimit > 0 && noteCounter > saplingNoteLimit))
{
maxedOutNotesFlag = true;
} else {
estimatedTxSize += increase;
libzcash::SaplingExtendedSpendingKey extsk;
if (!pwalletMain->GetSaplingExtendedSpendingKey(entry.address, extsk)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find spending key for payment address.");
}
saplingNoteInputs.emplace_back(entry.op, entry.note, nValue, extsk.expsk);
mergedNoteValue += nValue;
}
} }
#ifdef __LP64__ if (maxedOutNotesFlag) {
uint64_t numUtxos = utxoInputs.size(); //ca333 remainingNoteValue += nValue;
uint64_t numNotes = noteInputs.size(); }
#else }
size_t numUtxos = utxoInputs.size(); }
size_t numNotes = noteInputs.size();
#endif
size_t numUtxos = utxoInputs.size();
size_t numNotes = sproutNoteInputs.size() + saplingNoteInputs.size();
if (numUtxos == 0 && numNotes == 0) { if (numUtxos == 0 && numNotes == 0) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any funds to merge."); throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any funds to merge.");
@@ -4880,15 +4893,20 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction( CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
Params().GetConsensus(), Params().GetConsensus(),
nextBlockHeight); nextBlockHeight);
bool isShielded = numNotes > 0 || isToZaddr; bool isSproutShielded = sproutNoteInputs.size() > 0 || isToSproutZaddr;
if (contextualTx.nVersion == 1 && isShielded) { if (contextualTx.nVersion == 1 && isSproutShielded) {
contextualTx.nVersion = 2; // Tx format should support vjoinsplit contextualTx.nVersion = 2; // Tx format should support vjoinsplit
} }
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
}
// Create operation and add to global queue // Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue(); std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation( std::shared_ptr<AsyncRPCOperation> operation(
new AsyncRPCOperation_mergetoaddress(contextualTx, utxoInputs, noteInputs, recipient, nFee, contextInfo) ); new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
q->addOperation(operation); q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId(); AsyncRPCOperationId operationId = operation->getId();

View File

@@ -4960,10 +4960,10 @@ void CWallet::GetFilteredNotes(
} }
// skip locked notes // skip locked notes
// TODO: Add locking for Sapling notes // TODO: Add locking for Sapling notes -> done
// if (ignoreLocked && IsLockedNote(op)) { if (ignoreLocked && IsLockedNote(op)) {
// continue; continue;
// } }
auto note = notePt.note(nd.ivk).get(); auto note = notePt.note(nd.ivk).get();
saplingEntries.push_back(SaplingNoteEntry { saplingEntries.push_back(SaplingNoteEntry {