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(
CMutableTransaction contextualTx, boost::optional<TransactionBuilder> builder,
std::vector<MergeToAddressInputUTXO> utxoInputs, CMutableTransaction contextualTx,
std::vector<MergeToAddressInputNote> noteInputs, std::vector<MergeToAddressInputUTXO> utxoInputs,
MergeToAddressRecipient recipient, std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
CAmount fee, std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
UniValue contextInfo) : MergeToAddressRecipient recipient,
tx_(contextualTx), utxoInputs_(utxoInputs), noteInputs_(noteInputs), CAmount fee,
recipient_(recipient), fee_(fee), contextinfo_(contextInfo) UniValue contextInfo) :
tx_(contextualTx), utxoInputs_(utxoInputs), sproutNoteInputs_(sproutNoteInputs),
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,17 +261,19 @@ 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)
CMutableTransaction rawTx(tx_); if (!isUsingBuilder_) {
for (MergeToAddressInputUTXO& t : utxoInputs_) { CMutableTransaction rawTx(tx_);
CTxIn in(std::get<0>(t)); for (const MergeToAddressInputUTXO& t : utxoInputs_) {
rawTx.vin.push_back(in); CTxIn in(std::get<0>(t));
rawTx.vin.push_back(in);
}
if (isToTaddr_) {
CScript scriptPubKey = GetScriptForDestination(toTaddr_);
CTxOut out(sendAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
tx_ = CTransaction(rawTx);
} }
if (isToTaddr_) {
CScript scriptPubKey = GetScriptForDestination(toTaddr_);
CTxOut out(sendAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
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;
@@ -463,11 +600,11 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey); auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
try { try {
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt( SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
decryptor, decryptor,
prevJoinSplit.ciphertexts[changeOutputIndex], prevJoinSplit.ciphertexts[changeOutputIndex],
prevJoinSplit.ephemeralKey, prevJoinSplit.ephemeralKey,
hSig, hSig,
(unsigned char)changeOutputIndex); (unsigned char)changeOutputIndex);
SproutNote note = plaintext.note(changeAddress); SproutNote note = plaintext.note(changeAddress);
info.notes.push_back(note); info.notes.push_back(note);
@@ -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.
@@ -721,9 +857,9 @@ UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInf
} }
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit( UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(
MergeToAddressJSInfo& info, MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses, std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor) uint256 anchor)
{ {
if (anchor.IsNull()) { if (anchor.IsNull()) {
throw std::runtime_error("anchor is null"); throw std::runtime_error("anchor is null");
@@ -775,18 +911,18 @@ UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(
uint256 esk; // payment disclosure - secret uint256 esk; // payment disclosure - secret
JSDescription jsdesc = JSDescription::Randomized( JSDescription jsdesc = JSDescription::Randomized(
mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION), mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION),
*pzcashParams, *pzcashParams,
joinSplitPubKey_, joinSplitPubKey_,
anchor, anchor,
inputs, inputs,
outputs, outputs,
inputMap, inputMap,
outputMap, outputMap,
info.vpub_old, info.vpub_old,
info.vpub_new, info.vpub_new,
!this->testmode, !this->testmode,
&esk); // parameter expects pointer to esk, so pass in address &esk); // parameter expects pointer to esk, so pass in address
{ {
auto verifier = libzcash::ProofVerifier::Strict(); auto verifier = libzcash::ProofVerifier::Strict();
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) { if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
@@ -924,7 +1060,7 @@ UniValue AsyncRPCOperation_mergetoaddress::getStatus() const
/** /**
* Lock input utxos * Lock input utxos
*/ */
void AsyncRPCOperation_mergetoaddress::lock_utxos() { void AsyncRPCOperation_mergetoaddress::lock_utxos() {
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto utxo : utxoInputs_) { for (auto utxo : utxoInputs_) {
pwalletMain->LockCoin(std::get<0>(utxo)); pwalletMain->LockCoin(std::get<0>(utxo));
@@ -945,9 +1081,12 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
/** /**
* Lock input notes * Lock input notes
*/ */
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,12 +56,14 @@ class AsyncRPCOperation_mergetoaddress : public AsyncRPCOperation
{ {
public: public:
AsyncRPCOperation_mergetoaddress( AsyncRPCOperation_mergetoaddress(
CMutableTransaction contextualTx, boost::optional<TransactionBuilder> builder,
std::vector<MergeToAddressInputUTXO> utxoInputs, CMutableTransaction contextualTx,
std::vector<MergeToAddressInputNote> noteInputs, std::vector<MergeToAddressInputUTXO> utxoInputs,
MergeToAddressRecipient recipient, std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE, std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
UniValue contextInfo = NullUniValue); MergeToAddressRecipient recipient,
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE,
UniValue contextInfo = NullUniValue);
virtual ~AsyncRPCOperation_mergetoaddress(); virtual ~AsyncRPCOperation_mergetoaddress();
// We don't want to be copied or moved around // We don't want to be copied or moved around
@@ -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);
@@ -111,9 +119,9 @@ private:
// JoinSplit where you have the witnesses and anchor // JoinSplit where you have the witnesses and anchor
UniValue perform_joinsplit( UniValue perform_joinsplit(
MergeToAddressJSInfo& info, MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses, std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor); uint256 anchor);
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
@@ -171,9 +179,9 @@ public:
} }
UniValue perform_joinsplit( UniValue perform_joinsplit(
MergeToAddressJSInfo& info, MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses, std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor) uint256 anchor)
{ {
return delegate->perform_joinsplit(info, witnesses, anchor); return delegate->perform_joinsplit(info, witnesses, anchor);
} }

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)
{ {
@@ -4539,52 +4536,52 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
if (fHelp || params.size() < 2 || params.size() > 6) if (fHelp || params.size() < 2 || params.size() > 6)
throw runtime_error( throw runtime_error(
"z_mergetoaddress [\"fromaddress\", ... ] \"toaddress\" ( fee ) ( transparent_limit ) ( shielded_limit ) ( memo )\n" "z_mergetoaddress [\"fromaddress\", ... ] \"toaddress\" ( fee ) ( transparent_limit ) ( shielded_limit ) ( memo )\n"
+ strDisabledMsg + + strDisabledMsg +
"\nMerge multiple UTXOs and notes into a single UTXO or note. Coinbase UTXOs are ignored; use `z_shieldcoinbase`" "\nMerge multiple UTXOs and notes into a single UTXO or note. Coinbase UTXOs are ignored; use `z_shieldcoinbase`"
"\nto combine those into a single note." "\nto combine those into a single note."
"\n\nThis is an asynchronous operation, and UTXOs selected for merging will be locked. If there is an error, they" "\n\nThis is an asynchronous operation, and UTXOs selected for merging will be locked. If there is an error, they"
"\nare unlocked. The RPC call `listlockunspent` can be used to return a list of locked UTXOs." "\nare unlocked. The RPC call `listlockunspent` can be used to return a list of locked UTXOs."
"\n\nThe number of UTXOs and notes selected for merging can be limited by the caller. If the transparent limit" "\n\nThe number of UTXOs and notes selected for merging can be limited by the caller. If the transparent limit"
"\nparameter is set to zero, and Overwinter is not yet active, the -mempooltxinputlimit option will determine the" "\nparameter is set to zero, and Overwinter is not yet active, the -mempooltxinputlimit option will determine the"
"\nnumber of UTXOs. Any limit is constrained by the consensus rule defining a maximum transaction size of" "\nnumber of UTXOs. Any limit is constrained by the consensus rule defining a maximum transaction size of"
+ strprintf("\n%d bytes before Sapling, and %d bytes once Sapling activates.", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING) + strprintf("\n%d bytes before Sapling, and %d bytes once Sapling activates.", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING)
+ HelpRequiringPassphrase() + "\n" + HelpRequiringPassphrase() + "\n"
"\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"
" ,...\n" " ,...\n"
" ]\n" " ]\n"
"2. \"toaddress\" (string, required) The t-addr or z-addr to send the funds to.\n" "2. \"toaddress\" (string, required) The t-addr or z-addr to send the funds to.\n"
"3. fee (numeric, optional, default=" "3. fee (numeric, optional, default="
+ strprintf("%s", FormatMoney(MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" + strprintf("%s", FormatMoney(MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
"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"
" \"remainingUTXOs\": xxx (numeric) Number of UTXOs still available for merging.\n" " \"remainingUTXOs\": xxx (numeric) Number of UTXOs still available for merging.\n"
" \"remainingTransparentValue\": xxx (numeric) Value of UTXOs still available for merging.\n" " \"remainingTransparentValue\": xxx (numeric) Value of UTXOs still available for merging.\n"
" \"remainingNotes\": xxx (numeric) Number of notes still available for merging.\n" " \"remainingNotes\": xxx (numeric) Number of notes still available for merging.\n"
" \"remainingShieldedValue\": xxx (numeric) Value of notes still available for merging.\n" " \"remainingShieldedValue\": xxx (numeric) Value of notes still available for merging.\n"
" \"mergingUTXOs\": xxx (numeric) Number of UTXOs being merged.\n" " \"mergingUTXOs\": xxx (numeric) Number of UTXOs being merged.\n"
" \"mergingTransparentValue\": xxx (numeric) Value of UTXOs being merged.\n" " \"mergingTransparentValue\": xxx (numeric) Value of UTXOs being merged.\n"
" \"mergingNotes\": xxx (numeric) Number of notes being merged.\n" " \"mergingNotes\": xxx (numeric) Number of notes being merged.\n"
" \"mergingShieldedValue\": xxx (numeric) Value of notes being merged.\n" " \"mergingShieldedValue\": xxx (numeric) Value of notes being merged.\n"
" \"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_mergetoaddress", "'[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"]' ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf") + HelpExampleCli("z_mergetoaddress", "'[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"]' ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf")
+ HelpExampleRpc("z_mergetoaddress", "[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"], \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") + HelpExampleRpc("z_mergetoaddress", "[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"], \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
); );
if (!fEnableMergeToAddress) { if (!fEnableMergeToAddress) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: z_mergetoaddress is disabled."); throw JSONRPCError(RPC_WALLET_ERROR, "Error: z_mergetoaddress is disabled.");
@@ -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 taddrs.insert(taddr);
if (!(useAny || useAnyUTXO)) {
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 zaddrs.insert(zaddr);
if (!(useAny || useAnyNote)) {
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,32 @@ 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;
}
}
if (maxedOutNotesFlag) {
remainingNoteValue += nValue;
}
}
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; mergedNoteValue += nValue;
} }
} }
@@ -4833,17 +4853,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
remainingNoteValue += nValue; remainingNoteValue += nValue;
} }
} }
// TODO: Add Sapling support
} }
#ifdef __LP64__
uint64_t numUtxos = utxoInputs.size(); //ca333
uint64_t numNotes = noteInputs.size();
#else
size_t numUtxos = utxoInputs.size(); size_t numUtxos = utxoInputs.size();
size_t numNotes = noteInputs.size(); size_t numNotes = sproutNoteInputs.size() + saplingNoteInputs.size();
#endif
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.");
@@ -4860,8 +4873,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
CAmount mergedValue = mergedUTXOValue + mergedNoteValue; CAmount mergedValue = mergedUTXOValue + mergedNoteValue;
if (mergedValue < nFee) { if (mergedValue < nFee) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
strprintf("Insufficient funds, have %s, which is less than miners fee %s", strprintf("Insufficient funds, have %s, which is less than miners fee %s",
FormatMoney(mergedValue), FormatMoney(nFee))); FormatMoney(mergedValue), FormatMoney(nFee)));
} }
// Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee) // Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee)
@@ -4878,17 +4891,22 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
// Contextual transaction we will build on // Contextual transaction we will build on
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 {