Non-test code from: https://github.com/zcash/zcash/pull/3619
This commit is contained in:
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user