Files
hush3/src/wallet/rpcwallet_zops.cpp
dan_s a918136a7c Split chainparams.cpp and rpcwallet.cpp into smaller files
chainparams.cpp: 5420 -> 593 lines
  - Extract HUSH3 checkpoint data to chainparams_checkpoints_hush3.h
  - Extract DRAGONX checkpoint data to chainparams_checkpoints_dragonx.h

rpcwallet.cpp: 6392 -> 4099 lines
  - Extract z-index query RPCs to rpcwallet_zindex.cpp
  - Extract shielded async operation RPCs to rpcwallet_zops.cpp
  - Create rpcwallet_internal.h for shared declarations
2026-02-27 01:51:24 -06:00

1195 lines
53 KiB
C++

// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
//
// Shielded async operation RPC methods — extracted from rpcwallet.cpp
// Functions: z_getoperationresult, z_getoperationstatus,
// z_getoperationstatus_IMPL, z_sendmany, z_shieldcoinbase,
// z_mergetoaddress, z_listoperationids
#include "amount.h"
#include "consensus/upgrades.h"
#include "core_io.h"
#include "init.h"
#include "key_io.h"
#include "main.h"
#include "net.h"
#include "netbase.h"
#include "rpc/server.h"
#include "timedata.h"
#include "transaction_builder.h"
#include "util.h"
#include "utilmoneystr.h"
#include "wallet.h"
#include "walletdb.h"
#include "primitives/transaction.h"
#include "zcash/zip32.h"
#include "zcash/Note.hpp"
#include "utiltime.h"
#include "asyncrpcoperation.h"
#include "asyncrpcqueue.h"
#include "wallet/asyncrpcoperation_mergetoaddress.h"
#include "wallet/asyncrpcoperation_sendmany.h"
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
#include "consensus/upgrades.h"
#include "sodium.h"
#include "hush_defs.h"
#include <utf8.h>
#include "rpcwallet_internal.h"
#include <stdint.h>
#include <univalue.h>
#include <numeric>
using namespace std;
using namespace libzcash;
extern CAmount fConsolidationTxFee;
extern int32_t HUSH_INSYNC;
extern int32_t HUSH_TESTNODE;
// Forward declarations of sietch functions (defined in sietch.h, already linked via hush_impl.o)
std::string newSietchZaddr();
SendManyRecipient newSietchRecipient(std::string zaddr);
std::string randomSietchZaddr();
UniValue z_getoperationresult(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
throw runtime_error(
"z_getoperationresult ([\"operationid\", ... ]) \n"
"\nRetrieve the result and status of an operation which has finished, and then remove the operation from memory."
+ HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"operationid\" (array, optional) A list of operation ids we are interested in. If not provided, examine all operations known to the node.\n"
"\nResult:\n"
"\" [object, ...]\" (array) A list of JSON objects\n"
"\nExamples:\n"
+ HelpExampleCli("z_getoperationresult", "'[\"operationid\", ... ]'")
+ HelpExampleRpc("z_getoperationresult", "'[\"operationid\", ... ]'")
);
// This call will remove finished operations
return z_getoperationstatus_IMPL(params, true);
}
UniValue z_getoperationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
throw runtime_error(
"z_getoperationstatus ([\"operationid\", ... ]) \n"
"\nGet operation status and any associated result or error data. The operation will remain in memory."
+ HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"operationid\" (array, optional) A list of operation ids we are interested in. If not provided, examine all operations known to the node.\n"
"\nResult:\n"
"\" [object, ...]\" (array) A list of JSON objects\n"
"\nExamples:\n"
+ HelpExampleCli("z_getoperationstatus", "'[\"operationid\", ... ]'")
+ HelpExampleRpc("z_getoperationstatus", "'[\"operationid\", ... ]'")
);
// This call is idempotent so we don't want to remove finished operations
return z_getoperationstatus_IMPL(params, false);
}
UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedOperations=false)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
std::set<AsyncRPCOperationId> filter;
if (params.size()==1) {
UniValue ids = params[0].get_array();
for (const UniValue & v : ids.getValues()) {
filter.insert(v.get_str());
}
}
bool useFilter = (filter.size()>0);
UniValue ret(UniValue::VARR);
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::vector<AsyncRPCOperationId> ids = q->getAllOperationIds();
for (auto id : ids) {
if (useFilter && !filter.count(id))
continue;
std::shared_ptr<AsyncRPCOperation> operation = q->getOperationForId(id);
if (!operation) {
continue;
// It's possible that the operation was removed from the internal queue and map during this loop
// throw JSONRPCError(RPC_INVALID_PARAMETER, "No operation exists for that id.");
}
UniValue obj = operation->getStatus();
std::string s = obj["status"].get_str();
if (fRemoveFinishedOperations) {
// Caller is only interested in retrieving finished results
if ("success"==s || "failed"==s || "cancelled"==s) {
ret.push_back(obj);
q->popOperationForId(id);
}
} else {
ret.push_back(obj);
}
}
std::vector<UniValue> arrTmp = ret.getValues();
// sort results chronologically by creation_time
std::sort(arrTmp.begin(), arrTmp.end(), [](UniValue a, UniValue b) -> bool {
const int64_t t1 = find_value(a.get_obj(), "creation_time").get_int64();
const int64_t t2 = find_value(b.get_obj(), "creation_time").get_int64();
return t1 < t2;
});
ret.clear();
ret.setArray();
ret.push_backV(arrTmp);
return ret;
}
// transaction.h comment: spending taddr output requires CTxIn >= 148 bytes and typical taddr txout is 34 bytes
#define CTXIN_SPEND_DUST_SIZE 148
#define CTXOUT_REGULAR_SIZE 34
UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 2 || params.size() > 5)
throw runtime_error(
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) (opreturn)\n"
"\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision."
"\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself."
"\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed."
+ HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from. Use 'z' to spend from any zaddr.\n"
"2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n"
" [{\n"
" \"address\":address (string, required) The address is a taddr or zaddr\n"
" \"amount\":amount (numeric, required) The amount to send this address\n"
" \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n"
" }, ... ]\n"
"3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n"
"4. fee (numeric, optional, default="
+ strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
"5. opreturn (string, optional) Hex encoded data for OP_RETURN. Or a utf8 string prefixed with 'utf8:' which will be automatically converted to hex\n"
"\nResult:\n"
"\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
"\nExamples:\n"
+ HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'")
+ HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]")
+ HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]'")
+ HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]")
+ HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex")
+ HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
// Hilarious that Komodo commented this out, opening themselves up to metadata attackz, lulz
THROW_IF_SYNCING(HUSH_INSYNC);
// Check that the from address is valid.
auto fromaddress = params[0].get_str();
bool fromTaddr = false;
bool fromSapling = false;
uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus());
UniValue outputs = params[1].get_array();
if (outputs.size()==0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty.");
// TODO: implement special symbolic fromaddrs
// TODO: list of (zaddr,amount)
// "z" => spend from any zaddr
// "t" => spend from any taddr
// "*" => spend from any addr, zaddrs first
if(fromaddress == "z") {
// TODO: refactor this and z_getbalances to use common code
std::set<libzcash::PaymentAddress> zaddrs = {};
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {};
pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs);
zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end());
int nMinDepth = 1;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth);
std::map<std::string, CAmount> mapBalances;
for (auto & entry : saplingEntries) {
auto zaddr = EncodePaymentAddress(entry.address);
CAmount nBalance = CAmount(entry.note.value());
if(mapBalances.count(zaddr)) {
mapBalances[zaddr] += nBalance;
} else {
mapBalances[zaddr] = nBalance;
}
}
std::vector<std::pair<std::string,CAmount>> vec;
std::copy(mapBalances.begin(), mapBalances.end(), std::back_inserter<std::vector<std::pair<std::string,CAmount>>>(vec));
std::sort(vec.begin(), vec.end(), [](const std::pair<std::string, CAmount> &l, const std::pair<std::string,CAmount> &r)
{
if (l.second != r.second) {
return l.second > r.second;
}
return l.first > r.first;
});
//TODO: avoid calculating nTotalOut twice
CAmount nTotalOut = 0;
for (const UniValue& o : outputs.getValues()) {
if (!o.isObject())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object");
UniValue av = find_value(o, "amount");
CAmount nAmount = AmountFromValue( av );
if (nAmount < 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");
nTotalOut += nAmount;
}
//GOAL: choose one random zaddress with enough funds
CAmount nFee;
if (params.size() > 3) {
if (params[3].get_real() == 0.0) {
nFee = 0;
} else {
nFee = AmountFromValue( params[3] );
}
}
// the total amount needed in a single zaddr to use as fromaddress
CAmount nMinBal = nTotalOut + nFee;
std::vector<std::string> vPotentialAddresses;
for (auto & entry : vec) {
if(entry.second >= nMinBal) {
vPotentialAddresses.push_back(entry.first);
}
}
// select a random address with enough confirmed balance
auto nPotentials = vPotentialAddresses.size();
if (nPotentials > 0) {
fprintf(stderr,"%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials);
fromaddress = vPotentialAddresses[ GetRandInt(nPotentials) ];
} else {
// Automagic zaddr source election failed, exit honorably
throw JSONRPCError(RPC_INVALID_PARAMETER, "No single zaddr currently has enough funds to make that transaction, you may need to wait for confirmations.");
}
} else {
CTxDestination taddr = DecodeDestination(fromaddress);
fromTaddr = IsValidDestination(taddr);
if (!fromTaddr) {
auto res = DecodePaymentAddress(fromaddress);
if (!IsValidPaymentAddress(res, branchId)) {
// invalid
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr.");
}
// Check that we have the spending key
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found.");
}
// Remember whether this is a Sapling address
fromSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
}
}
// Recipients
std::vector<SendManyRecipient> taddrRecipients;
std::vector<SendManyRecipient> zaddrRecipients;
CAmount nTotalOut = 0;
// Optional OP_RETURN data
CScript opret;
UniValue opretValue;
if(params.size() == 5) {
opretValue = params[4].get_str();
// Support a prefix "utf8:" which allows giving utf8 text instead of hex
if(opretValue.get_str().substr(0,5) == "utf8:") {
auto str = opretValue.get_str().substr(5);
if (utf8::is_valid(str)) {
opretValue = HexStr(str);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in opreturn");
}
}
}
bool containsSaplingOutput = false;
// Create the CScript representation of the OP_RETURN
if (!opretValue.isNull()) {
opret << OP_RETURN << ParseHex(opretValue.get_str().c_str());
}
for (const UniValue& o : outputs.getValues()) {
if (!o.isObject())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object");
// sanity check, report error if unknown key-value pairs
for (const string& name_ : o.getKeys()) {
std::string s = name_;
if (s != "address" && s != "amount" && s!="memo") {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s);
}
}
string address = find_value(o, "address").get_str();
bool isZaddr = false;
CTxDestination taddr = DecodeDestination(address);
if (!IsValidDestination(taddr)) {
auto res = DecodePaymentAddress(address);
if (IsValidPaymentAddress(res, branchId)) {
isZaddr = true;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address );
}
}// else if ( ASSETCHAINS_PRIVATE != 0 && hush_isnotaryvout((char *)address.c_str()) == 0 ) {
// throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extreme Privacy! You must send to a zaddr");
//}
UniValue memoValue = find_value(o, "memo");
string memo;
if (!memoValue.isNull()) {
memo = memoValue.get_str();
if (!isZaddr) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr.");
} else if(memo.substr(0,5) == "utf8:") {
// Support a prefix "utf8:" which allows giving utf8 text instead of hex
auto str = memo.substr(5);
if (utf8::is_valid(str)) {
memo = HexStr(str);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in memo");
}
} else if (!IsHex(memo)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format or to use 'utf8:' prefix.");
}
if (memo.length() > HUSH_MEMO_SIZE*2) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", HUSH_MEMO_SIZE ));
}
}
UniValue av = find_value(o, "amount");
CAmount nAmount = AmountFromValue( av );
if (nAmount < 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");
if (isZaddr) {
zaddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) );
} else {
taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) );
}
nTotalOut += nAmount;
}
// SIETCH: Sprinkle our cave with some magic privacy zdust
// End goal is to have this be as large as possible without slowing xtns down too much
// A value of 7 will provide much stronger linkability privacy versus pre-Sietch operations
unsigned int DEFAULT_MIN_ZOUTS=7;
unsigned int MAX_ZOUTS=50;
unsigned int MIN_ZOUTS=GetArg("--sietch-min-zouts", DEFAULT_MIN_ZOUTS);
if((MIN_ZOUTS<3) || (MIN_ZOUTS>MAX_ZOUTS)) {
fprintf(stderr,"%s: Sietch min zouts must be >= 3 and <= %d, setting to default value of %d\n", __FUNCTION__, MAX_ZOUTS, DEFAULT_MIN_ZOUTS );
MIN_ZOUTS=DEFAULT_MIN_ZOUTS;
}
int nAmount = 0;
while (zaddrRecipients.size() < MIN_ZOUTS) {
// OK, we identify this xtn as needing privacy zdust, we must decide how much, non-deterministically
int decider = 1 + GetRandInt(100); // random int between 1 and 100
string zdust1, zdust2;
// Which zaddr we send to is randomly generated
zdust1 = randomSietchZaddr();
// And their ordering when given to internals is also non-deterministic, which
// helps breaks assumptions blockchain analysts may use from z_sendmany internals
if (decider % 2) {
zaddrRecipients.insert(std::begin(zaddrRecipients), newSietchRecipient(zdust1) );
} else {
zaddrRecipients.push_back( newSietchRecipient(zdust1) );
}
if(fZdebug)
fprintf(stderr,"%s: adding %s as zdust receiver\n", __FUNCTION__, zdust1.c_str());
//50% chance of adding another zout
if (decider % 2) {
zdust2 = randomSietchZaddr();
// 50% chance of adding it to the front or back since all odd numbers are 1 or 3 mod 4
if(decider % 4 == 3) {
zaddrRecipients.push_back( newSietchRecipient(zdust2) );
} else {
zaddrRecipients.insert(std::begin(zaddrRecipients), newSietchRecipient(zdust2) );
}
if(fZdebug)
fprintf(stderr,"%s: adding %s as zdust receiver\n", __FUNCTION__, zdust2.c_str());
}
}
int nextBlockHeight = chainActive.Height() + 1;
CMutableTransaction mtx;
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
/*
const bool sapling = true; //NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
if (!sapling) {
if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nVersion = OVERWINTER_TX_VERSION;
} else {
mtx.fOverwintered = false;
mtx.nVersion = 2;
}
}
*/
// As a sanity check, estimate and verify that the size of the transaction will be valid.
// Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid.
if(fZdebug)
LogPrintf("%s: Verifying xtn size is valid\n", __FUNCTION__);
size_t txsize = 0;
for (int i = 0; i < zaddrRecipients.size(); i++) {
auto address = std::get<0>(zaddrRecipients[i]);
auto res = DecodePaymentAddress(address);
bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
if (toSapling) {
mtx.vShieldedOutput.push_back(OutputDescription());
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, not a Sapling zaddr!");
}
}
CTransaction tx(mtx);
txsize += GetSerializeSize(tx, SER_NETWORK, tx.nVersion);
if (fromTaddr) {
txsize += CTXIN_SPEND_DUST_SIZE;
//TODO: On HUSH since block 340k there can no longer be taddr change,
// (except for notary addresses)
// so we can likely make a better estimation of max txsize
txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change
}
txsize += CTXOUT_REGULAR_SIZE * taddrRecipients.size();
if (txsize > max_tx_size) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Size of raw transaction %d would be larger than limit of %d bytes", txsize, max_tx_size ));
}
// Minimum confirmations
int nMinDepth = 1;
if (params.size() > 2) {
nMinDepth = params[2].get_int();
}
if (nMinDepth < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
}
// Fee in Puposhis, not currency format
CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE;
CAmount nDefaultFee = nFee;
if (params.size() > 3) {
if (params[3].get_real() == 0.0) {
nFee = 0;
} else {
nFee = AmountFromValue( params[3] );
}
// Check that the user specified fee is not absurd.
// This allows amount=0 (and all amount < nDefaultFee) transactions to use the default network fee
// or anything less than nDefaultFee instead of being forced to use a custom fee and leak metadata
if (nTotalOut < nDefaultFee) {
if (nFee > nDefaultFee) {
// Allow large fees if OP_RETURN is being used
if( opretValue.isNull() ) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Small transaction amount %s has fee %s that is greater than the default fee %s", FormatMoney(nTotalOut), FormatMoney(nFee), FormatMoney(nDefaultFee)));
}
}
} else {
// Check that the user specified fee is not absurd.
if (nFee > nTotalOut) {
// Allow large fees if OP_RETURN is being used
if( opretValue.isNull() ) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the sum of outputs %s and also greater than the default fee", FormatMoney(nFee), FormatMoney(nTotalOut)));
}
}
}
}
// Use input parameters as the optional context info to be returned by z_getoperationstatus and z_getoperationresult.
UniValue o(UniValue::VOBJ);
o.push_back(Pair("fromaddress", fromaddress));
o.push_back(Pair("amounts", params[1]));
o.push_back(Pair("minconf", nMinDepth));
o.push_back(Pair("fee", std::stod(FormatMoney(nFee))));
UniValue contextInfo = o;
if(fZdebug)
LogPrintf("%s: Building the raw ztransaction\n", __FUNCTION__);
// Sapling Tx Builder
boost::optional<TransactionBuilder> builder;
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
// Contextual transaction
CMutableTransaction contextualTx; // = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
contextualTx.nVersion = 2;
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo, opret) );
q->addOperation(operation);
if(fZdebug)
LogPrintf("%s: Submitted to async queue\n", __FUNCTION__);
AsyncRPCOperationId operationId = operation->getId();
return operationId;
}
/**
When estimating the number of coinbase utxos we can shield in a single transaction:
1. Joinsplit description is 1802 bytes.
2. Transaction overhead ~ 100 bytes
3. Spending a typical P2PKH is >=148 bytes, as defined in CTXIN_SPEND_DUST_SIZE.
4. Spending a multi-sig P2SH address can vary greatly:
https://github.com/bitcoin/bitcoin/blob/c3ad56f4e0b587d8d763af03d743fdfc2d180c9b/src/main.cpp#L517
In real-world coinbase utxos, we consider a 3-of-3 multisig, where the size is roughly:
(3*(33+1))+3 = 105 byte redeem script
105 + 1 + 3*(73+1) = 328 bytes of scriptSig, rounded up to 400 based on testnet experiments.
*/
#define CTXIN_SPEND_P2SH_SIZE 400
#define SHIELD_COINBASE_DEFAULT_LIMIT 50
UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 2 || params.size() > 4)
throw runtime_error(
"z_shieldcoinbase \"fromaddress\" \"tozaddress\" ( fee ) ( limit )\n"
"\nShield transparent coinbase funds by sending to a shielded zaddr. This is an asynchronous operation and utxos"
"\nselected for shielding will be locked. If there is an error, they are unlocked. The RPC call `listlockunspent`"
"\ncan be used to return a list of locked utxos. The number of coinbase utxos selected for shielding can be limited"
"\nby the caller. If the limit parameter is set to zero, as many as will fit will be used."
"\nAny limit is constrained by the consensus rule defining a maximum"
"\ntransaction size of " + strprintf("%d bytes.", MAX_TX_SIZE_AFTER_SAPLING)
+ HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"fromaddress\" (string, required) The address is a taddr or \"*\" for all taddrs belonging to the wallet.\n"
"2. \"toaddress\" (string, required) The address is a zaddr.\n"
"3. fee (numeric, optional, default="
+ strprintf("%s", FormatMoney(SHIELD_COINBASE_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
"4. limit (numeric, optional, default="
+ strprintf("%d", SHIELD_COINBASE_DEFAULT_LIMIT) + ") Limit on the maximum number of utxos to shield. Set to 0 to use as many as will fit in the transaction.\n"
"\nResult:\n"
"{\n"
" \"remainingUTXOs\": xxx (numeric) Number of coinbase utxos still available for shielding.\n"
" \"remainingValue\": xxx (numeric) Value of coinbase utxos still available for shielding.\n"
" \"shieldingUTXOs\": xxx (numeric) Number of coinbase utxos being shielded.\n"
" \"shieldingValue\": xxx (numeric) Value of coinbase utxos being shielded.\n"
" \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
+ HelpExampleRpc("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
// Hilarious that Komodo commented this out, opening themselves up to metadata attackz, lulz
THROW_IF_SYNCING(HUSH_INSYNC);
// Validate the from address
auto fromaddress = params[0].get_str();
bool isFromWildcard = fromaddress == "*";
CTxDestination taddr;
if (!isFromWildcard) {
taddr = DecodeDestination(fromaddress);
if (!IsValidDestination(taddr)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or \"*\".");
}
}
// Validate the destination address
auto destaddress = params[1].get_str();
if (!IsValidPaymentAddressString(destaddress, CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()))) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress );
}
// Convert fee from currency format to zatoshis
CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE;
if (params.size() > 2) {
if (params[2].get_real() == 0.0) {
nFee = 0;
} else {
nFee = AmountFromValue( params[2] );
}
}
int nLimit = SHIELD_COINBASE_DEFAULT_LIMIT;
if (params.size() > 3) {
nLimit = params[3].get_int();
if (nLimit < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of utxos cannot be negative");
}
}
int nextBlockHeight = chainActive.Height() + 1;
bool overwinterActive = nextBlockHeight>=1 ? true : false; // NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
// Prepare to get coinbase utxos
std::vector<ShieldCoinbaseUTXO> inputs;
CAmount shieldedValue = 0;
CAmount remainingValue = 0;
//TODO: update these estimates for Sapling SpendDescriptions
size_t estimatedTxSize = 2000; // 1802 joinsplit description + tx overhead + wiggle room
#ifdef __LP64__
uint64_t utxoCounter = 0;
#else
size_t utxoCounter = 0;
#endif
bool maxedOutFlag = false;
size_t mempoolLimit = (nLimit != 0) ? nLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0));
// Set of addresses to filter utxos by
std::set<CTxDestination> destinations = {};
if (!isFromWildcard) {
destinations.insert(taddr);
}
// Get available utxos
vector<COutput> vecOutputs;
pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, true);
// Find unspent coinbase utxos and update estimated size
BOOST_FOREACH(const COutput& out, vecOutputs) {
if (!out.fSpendable) {
continue;
}
CTxDestination address;
if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) {
continue;
}
// If taddr is not wildcard "*", filter utxos
if (destinations.size() > 0 && !destinations.count(address)) {
continue;
}
if (!out.tx->IsCoinBase()) {
continue;
}
utxoCounter++;
auto scriptPubKey = out.tx->vout[out.i].scriptPubKey;
CAmount nValue = out.tx->vout[out.i].nValue;
if (!maxedOutFlag) {
size_t increase = (boost::get<CScriptID>(&address) != nullptr) ? CTXIN_SPEND_P2SH_SIZE : CTXIN_SPEND_DUST_SIZE;
if (estimatedTxSize + increase >= max_tx_size ||
(mempoolLimit > 0 && utxoCounter > mempoolLimit))
{
maxedOutFlag = true;
} else {
estimatedTxSize += increase;
ShieldCoinbaseUTXO utxo = {out.tx->GetHash(), out.i, scriptPubKey, nValue};
inputs.push_back(utxo);
shieldedValue += nValue;
}
}
if (maxedOutFlag) {
remainingValue += nValue;
}
}
#ifdef __LP64__
uint64_t numUtxos = inputs.size();
#else
size_t numUtxos = inputs.size();
#endif
if (numUtxos == 0) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any coinbase funds to shield.");
}
if (shieldedValue < nFee) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
strprintf("Insufficient coinbase funds, have %s, which is less than miners fee %s",
FormatMoney(shieldedValue), FormatMoney(nFee)));
}
// Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee)
CAmount netAmount = shieldedValue - nFee;
if (nFee > netAmount) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the net amount to be shielded %s", FormatMoney(nFee), FormatMoney(netAmount)));
}
// Keep record of parameters in context object
UniValue contextInfo(UniValue::VOBJ);
contextInfo.push_back(Pair("fromaddress", params[0]));
contextInfo.push_back(Pair("toaddress", params[1]));
contextInfo.push_back(Pair("fee", ValueFromAmount(nFee)));
// Builder (used if Sapling addresses are involved)
TransactionBuilder builder = TransactionBuilder(
Params().GetConsensus(), nextBlockHeight, pwalletMain);
// Contextual transaction we will build on
int blockHeight = chainActive.LastTip()->GetHeight();
nextBlockHeight = blockHeight + 1;
// (used if no Sapling addresses are involved)
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
Params().GetConsensus(), nextBlockHeight);
contextualTx.nLockTime = chainActive.LastTip()->GetHeight();
if (contextualTx.nVersion == 1) {
contextualTx.nVersion = 2; // Tx format should support ztx's
}
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(builder, contextualTx, inputs, destaddress, nFee, contextInfo) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();
// Return continuation information
UniValue o(UniValue::VOBJ);
o.push_back(Pair("remainingUTXOs", static_cast<uint64_t>(utxoCounter - numUtxos)));
o.push_back(Pair("remainingValue", ValueFromAmount(remainingValue)));
o.push_back(Pair("shieldingUTXOs", static_cast<uint64_t>(numUtxos)));
o.push_back(Pair("shieldingValue", ValueFromAmount(shieldedValue)));
o.push_back(Pair("opid", operationId));
return o;
}
#define MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT 50
#define MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT 90
#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, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
string enableArg = "zmergetoaddress";
if (fHelp || params.size() < 2 || params.size() > 7)
throw runtime_error(
"z_mergetoaddress [\"fromaddress\", ... ] \"toaddress\" ( fee ) ( transparent_limit ) ( shielded_limit ) ( memo )\n"
"\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."
"\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."
"\n\nThe number of UTXOs and notes selected for merging can be limited by the caller."
"\nArguments:\n"
"1. fromaddresses (string, required) A JSON array with addresses.\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 all t-addrs belonging to the wallet.\n"
" - \"ANY_ZADDR\": Merge notes from all z-addrs belonging to the wallet.\n"
" If a special string is given, any given addresses of that type will be ignored.\n"
" [\n"
" \"address\" (string) Can be a t-addr or a z-addr\n"
" ,...\n"
" ]\n"
"2. \"toaddress\" (string, required) The t-addr or z-addr to send the funds to.\n"
"3. fee (numeric, optional, default="
+ 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="
+ strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT) + ") Limit on the maximum number of UTXOs to merge. Set to 0 to use as many as will fit in the transaction.\n"
"4. shielded_limit (numeric, optional, default="
+ strprintf("%d Sapling Notes", 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. maximum_utxo_size (numeric, optional) eg, 0.0001 anything under 10000 satoshies will be merged, ignores 10,000 sat p2pk utxo that dragon uses, and merges coinbase utxo.\n"
"6. \"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"
"{\n"
" \"remainingUTXOs\": xxx (numeric) Number 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"
" \"remainingShieldedValue\": xxx (numeric) Value of notes still available for merging.\n"
" \"mergingUTXOs\": xxx (numeric) Number of UTXOs being merged.\n"
" \"mergingTransparentValue\": xxx (numeric) Value of UTXOs being merged.\n"
" \"mergingNotes\": xxx (numeric) Number 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"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("z_mergetoaddress", "'[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"]' zs1aW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf")
+ HelpExampleRpc("z_mergetoaddress", "[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"], \"zs1aW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
// Hilarious that Komodo commented this out, opening themselves up to metadata attackz, lulz
THROW_IF_SYNCING(HUSH_INSYNC);
bool useAnyUTXO = false;
bool useAnySapling = false;
std::set<CTxDestination> taddrs = {};
std::set<libzcash::PaymentAddress> zaddrs = {};
UniValue addresses = params[0].get_array();
if (addresses.size()==0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, fromaddresses array is empty.");
// Keep track of addresses
std::set<std::string> setAddress;
// Sources
for (const UniValue& o : addresses.getValues()) {
if (!o.isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected string");
std::string address = o.get_str();
if (address == "ANY_TADDR") {
useAnyUTXO = true;
} else if (address == "ANY_ZADDR" || address == "ANY_SAPLING") {
useAnySapling = true;
} else if (address == "*") {
useAnyUTXO = useAnySapling = true;
} else {
CTxDestination taddr = DecodeDestination(address);
if (IsValidDestination(taddr)) {
taddrs.insert(taddr);
} else {
auto zaddr = DecodePaymentAddress(address);
if (IsValidPaymentAddress(zaddr)) {
zaddrs.insert(zaddr);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unknown address format: ") + address);
}
}
}
if (setAddress.count(address))
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ") + address);
setAddress.insert(address);
}
if (useAnyUTXO && taddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific t-addrs when using \"ANY_TADDR\"");
}
if ((useAnySapling) && zaddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific z-addrs when using \"ANY_SAPLING\" or \"ANY_ZADDR\"");
}
const int nextBlockHeight = chainActive.Height() + 1;
const bool overwinterActive = nextBlockHeight >=1 ? true : false; // NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
// Validate the destination address
auto destaddress = params[1].get_str();
bool isToSaplingZaddr = false;
CTxDestination taddr = DecodeDestination(destaddress);
if (!IsValidDestination(taddr)) {
auto decodeAddr = DecodePaymentAddress(destaddress);
if (IsValidPaymentAddress(decodeAddr)) {
if (boost::get<libzcash::SaplingPaymentAddress>(&decodeAddr) != nullptr) {
isToSaplingZaddr = true;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only Sapling zaddrs allowed!");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress );
}
}
// Convert fee from currency format to puposhis
CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE;
if (params.size() > 2) {
if (params[2].get_real() == 0.0) {
nFee = 0;
} else {
nFee = AmountFromValue( params[2] );
}
}
int nUTXOLimit = MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT;
if (params.size() > 3) {
nUTXOLimit = params[3].get_int();
if (nUTXOLimit < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of UTXOs cannot be negative");
}
}
int saplingNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT;
if (params.size() > 4) {
int nNoteLimit = params[4].get_int();
if (nNoteLimit < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative");
}
saplingNoteLimit = nNoteLimit;
}
CAmount maximum_utxo_size;
if (params.size() > 5) {
maximum_utxo_size = AmountFromValue( params[5] );
if (maximum_utxo_size < 10) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Maximum size must be bigger than 0.00000010.");
}
} else {
maximum_utxo_size = 0;
}
std::string memo;
if (params.size() > 6) {
memo = params[6].get_str();
if (!isToSaplingZaddr) {
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)) {
//TODO: this sux, would be nice to accept in utf8
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format.");
}
if (memo.length() > HUSH_MEMO_SIZE*2) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", HUSH_MEMO_SIZE ));
}
}
MergeToAddressRecipient recipient(destaddress, memo);
// Prepare to get UTXOs and notes
std::vector<MergeToAddressInputUTXO> utxoInputs;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs;
CAmount mergedUTXOValue = 0;
CAmount mergedNoteValue = 0;
CAmount remainingUTXOValue = 0;
CAmount remainingNoteValue = 0;
size_t utxoCounter = 0;
size_t noteCounter = 0;
bool maxedOutUTXOsFlag = false;
bool maxedOutNotesFlag = false;
size_t mempoolLimit = (nUTXOLimit != 0) ? nUTXOLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0));
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
size_t estimatedTxSize = 200; // tx overhead + wiggle room
if (isToSaplingZaddr) {
estimatedTxSize += OUTPUTDESCRIPTION_SIZE;
}
if (useAnyUTXO || taddrs.size() > 0) {
// Get available utxos
vector<COutput> vecOutputs;
pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, maximum_utxo_size != 0 ? true : false);
// Find unspent utxos and update estimated size
for (const COutput& out : vecOutputs) {
if (!out.fSpendable) {
continue;
}
CScript scriptPubKey = out.tx->vout[out.i].scriptPubKey;
CTxDestination address;
if (!ExtractDestination(scriptPubKey, address)) {
continue;
}
// If taddr is not wildcard "*", filter utxos
if (taddrs.size() > 0 && !taddrs.count(address)) {
continue;
}
CAmount nValue = out.tx->vout[out.i].nValue;
if (maximum_utxo_size != 0) {
//fprintf(stderr, "utxo txid.%s vout.%i nValue.%li scriptpubkeylength.%i\n",out.tx->GetHash().ToString().c_str(),out.i,nValue,out.tx->vout[out.i].scriptPubKey.size());
if (nValue > maximum_utxo_size)
continue;
if (nValue == 10000 && out.tx->vout[out.i].scriptPubKey.size() == 35)
continue;
}
utxoCounter++;
if (!maxedOutUTXOsFlag) {
size_t increase = (boost::get<CScriptID>(&address) != nullptr) ? CTXIN_SPEND_P2SH_SIZE : CTXIN_SPEND_DUST_SIZE;
if (estimatedTxSize + increase >= max_tx_size ||
(mempoolLimit > 0 && utxoCounter > mempoolLimit))
{
maxedOutUTXOsFlag = true;
} else {
estimatedTxSize += increase;
COutPoint utxo(out.tx->GetHash(), out.i);
utxoInputs.emplace_back(utxo, nValue, scriptPubKey);
mergedUTXOValue += nValue;
}
}
if (maxedOutUTXOsFlag) {
remainingUTXOValue += nValue;
}
}
}
if (useAnySapling || zaddrs.size() > 0) {
// Get available notes
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs);
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;
}
}
if (maxedOutNotesFlag) {
remainingNoteValue += nValue;
}
}
}
size_t numUtxos = utxoInputs.size();
size_t numNotes = saplingNoteInputs.size();
//fprintf(stderr, "num utxos.%li\n", numUtxos);
if (numUtxos < 2 && numNotes == 0) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any funds to merge.");
}
// Sanity check: Don't do anything if:
// - We only have one from address
// - It's equal to toaddress
// - The address only contains a single UTXO or note
if (setAddress.size() == 1 && setAddress.count(destaddress) && (numUtxos + numNotes) == 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Destination address is also the only source address, and all its funds are already merged.");
}
CAmount mergedValue = mergedUTXOValue + mergedNoteValue;
if (mergedValue < nFee) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
strprintf("Insufficient funds, have %s, which is less than miners fee %s",
FormatMoney(mergedValue), FormatMoney(nFee)));
}
// Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee)
CAmount netAmount = mergedValue - nFee;
if (nFee > netAmount) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the net amount to be shielded %s", FormatMoney(nFee), FormatMoney(netAmount)));
}
// Keep record of parameters in context object
UniValue contextInfo(UniValue::VOBJ);
contextInfo.push_back(Pair("fromaddresses", params[0]));
contextInfo.push_back(Pair("toaddress", params[1]));
contextInfo.push_back(Pair("fee", ValueFromAmount(nFee)));
// Contextual transaction we will build on
CMutableTransaction contextualTx; //= CreateNewContextualCMutableTransaction( Params().GetConsensus(), nextBlockHeight);
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
} else {
contextualTx.nExpiryHeight = 0; // set non z-tx to have no expiry height.
}
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();
// Return continuation information
UniValue o(UniValue::VOBJ);
o.push_back(Pair("remainingUTXOs", static_cast<uint64_t>(utxoCounter - numUtxos)));
o.push_back(Pair("remainingTransparentValue", ValueFromAmount(remainingUTXOValue)));
o.push_back(Pair("remainingNotes", static_cast<uint64_t>(noteCounter - numNotes)));
o.push_back(Pair("remainingShieldedValue", ValueFromAmount(remainingNoteValue)));
o.push_back(Pair("mergingUTXOs", static_cast<uint64_t>(numUtxos)));
o.push_back(Pair("mergingTransparentValue", ValueFromAmount(mergedUTXOValue)));
o.push_back(Pair("mergingNotes", static_cast<uint64_t>(numNotes)));
o.push_back(Pair("mergingShieldedValue", ValueFromAmount(mergedNoteValue)));
o.push_back(Pair("opid", operationId));
return o;
}
UniValue z_listoperationids(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
throw runtime_error(
"z_listoperationids\n"
"\nReturns the list of operation ids currently known to the wallet.\n"
"\nArguments:\n"
"1. \"status\" (string, optional) Filter result by the operation's state e.g. \"success\".\n"
"\nResult:\n"
"[ (json array of string)\n"
" \"operationid\" (string) an operation id belonging to the wallet\n"
" ,...\n"
"]\n"
"\nExamples:\n"
+ HelpExampleCli("z_listoperationids", "")
+ HelpExampleRpc("z_listoperationids", "")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
std::string filter;
bool useFilter = false;
if (params.size()==1) {
filter = params[0].get_str();
useFilter = true;
}
UniValue ret(UniValue::VARR);
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::vector<AsyncRPCOperationId> ids = q->getAllOperationIds();
for (auto id : ids) {
std::shared_ptr<AsyncRPCOperation> operation = q->getOperationForId(id);
if (!operation) {
continue;
}
std::string state = operation->getStateAsString();
if (useFilter && filter.compare(state)!=0)
continue;
ret.push_back(id);
}
return ret;
}
#include "script/sign.h"
int32_t decode_hex(uint8_t *bytes,int32_t n,char *hex);