Many z_mergetoaddress updates
- add ZIP 317 support - address review comments - restructure `AsyncRPCOperation_mergetoaddress` (removing the just-added `prepare`)
This commit is contained in:
parent
94f5bfc595
commit
d0522df5c0
|
@ -21,24 +21,20 @@ RPC Changes
|
||||||
also available for testnet and regtest nodes, in which case it does not
|
also available for testnet and regtest nodes, in which case it does not
|
||||||
return end-of-service halt information (as testnet and regtest nodes do not
|
return end-of-service halt information (as testnet and regtest nodes do not
|
||||||
have an end-of-service halt feature.)
|
have an end-of-service halt feature.)
|
||||||
- Several `z_sendmany` failures have moved from synchronous to asynchronous, so
|
- Several `z_sendmany`, `z_shieldcoinbase` and `z_mergetoaddress` failures have
|
||||||
while you should already be checking the async operation status, there are now
|
moved from synchronous to asynchronous, so while you should already be
|
||||||
more cases that may trigger failure at that stage.
|
checking the async operation status, there are now more cases that may trigger
|
||||||
|
failure at that stage.
|
||||||
- The `AllowRevealedRecipients` privacy policy is now required in order to choose a
|
- The `AllowRevealedRecipients` privacy policy is now required in order to choose a
|
||||||
transparent change address for a transaction. This will only occur when the wallet
|
transparent change address for a transaction. This will only occur when the wallet
|
||||||
is unable to construct the transaction without selecting funds from the transparent
|
is unable to construct the transaction without selecting funds from the transparent
|
||||||
pool, so the impact of this change is that for such transactions, the user must specify
|
pool, so the impact of this change is that for such transactions, the user must specify
|
||||||
`AllowFullyTransparent`.
|
`AllowFullyTransparent`.
|
||||||
|
- The `z_shieldcoinbase` and `z_mergetoaddress` RPC methods now support an
|
||||||
|
optional privacy policy.
|
||||||
- The `estimatepriority` RPC call has been removed.
|
- The `estimatepriority` RPC call has been removed.
|
||||||
- The `priority_delta` argument to the `prioritisetransaction` RPC call now has
|
- The `priority_delta` argument to the `prioritisetransaction` RPC call now has
|
||||||
no effect and must be set to a dummy value (0 or null).
|
no effect and must be set to a dummy value (0 or null).
|
||||||
- The `z_shieldcoinbase` and `z_mergetoaddress` RPC methods no longer support
|
|
||||||
transfers of funds to Sprout recipients. While Sprout change may still be
|
|
||||||
produced in the process of spending Sprout funds, it is no longer possible
|
|
||||||
to transfer funds between Sprout addresses. Also, some failures have moved
|
|
||||||
from synchronous to asynchronous, so while you should already be checking
|
|
||||||
the async operation status, there are now more cases that may trigger
|
|
||||||
failure at that stage.
|
|
||||||
|
|
||||||
Changes to Transaction Fee Selection
|
Changes to Transaction Fee Selection
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
|
@ -24,7 +24,7 @@ def assert_mergetoaddress_exception(expected_error_msg, merge_to_address_lambda)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
fail("Expected JSONRPCException. Found %s" % repr(e))
|
fail("Expected JSONRPCException. Found %s" % repr(e))
|
||||||
else:
|
else:
|
||||||
fail("Expected exception: %s" % expected_error_msg)
|
fail("Expected exception: “%s”, but didn’t fail" % expected_error_msg)
|
||||||
|
|
||||||
|
|
||||||
class MergeToAddressHelper:
|
class MergeToAddressHelper:
|
||||||
|
@ -129,9 +129,9 @@ class MergeToAddressHelper:
|
||||||
"Amount out of range",
|
"Amount out of range",
|
||||||
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, Decimal('21000000.00000001')))
|
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, Decimal('21000000.00000001')))
|
||||||
|
|
||||||
# Merging will fail because fee is larger than sum of UTXOs
|
# Merging will fail because fee is larger than `-maxtxfee`
|
||||||
assert_mergetoaddress_exception(
|
assert_mergetoaddress_exception(
|
||||||
"Insufficient funds, have 50.00, which is less than miners fee 999.00",
|
"Fee (999.00 ZEC) is greater than the maximum fee allowed by this instance (0.10 ZEC). Run zcashd with `-maxtxfee` to adjust this limit.",
|
||||||
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, 999))
|
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, 999))
|
||||||
|
|
||||||
# Merging will fail because transparent limit parameter must be at least 0
|
# Merging will fail because transparent limit parameter must be at least 0
|
||||||
|
@ -174,7 +174,7 @@ class MergeToAddressHelper:
|
||||||
# Confirm balances and that do_not_shield_taddr containing funds of 10 was left alone
|
# Confirm balances and that do_not_shield_taddr containing funds of 10 was left alone
|
||||||
assert_equal(test.nodes[0].getbalance(), Decimal('10'))
|
assert_equal(test.nodes[0].getbalance(), Decimal('10'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('10'))
|
assert_equal(test.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('10'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('40') - DEFAULT_FEE)
|
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('40') - conventional_fee(4))
|
||||||
assert_equal(test.nodes[1].getbalance(), Decimal('40'))
|
assert_equal(test.nodes[1].getbalance(), Decimal('40'))
|
||||||
assert_equal(test.nodes[2].getbalance(), Decimal('30'))
|
assert_equal(test.nodes[2].getbalance(), Decimal('30'))
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ class MergeToAddressHelper:
|
||||||
test.sync_all()
|
test.sync_all()
|
||||||
|
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('0'))
|
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('0'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr2), Decimal('40') - DEFAULT_FEE)
|
assert_equal(test.nodes[0].z_getbalance(myzaddr2), Decimal('40') - conventional_fee(4))
|
||||||
|
|
||||||
# Shield coinbase UTXOs from any node 2 taddr, and set fee to 0
|
# Shield coinbase UTXOs from any node 2 taddr, and set fee to 0
|
||||||
result = test.nodes[2].z_shieldcoinbase("*", myzaddr, 0)
|
result = test.nodes[2].z_shieldcoinbase("*", myzaddr, 0)
|
||||||
|
@ -202,7 +202,7 @@ class MergeToAddressHelper:
|
||||||
|
|
||||||
assert_equal(test.nodes[0].getbalance(), Decimal('10'))
|
assert_equal(test.nodes[0].getbalance(), Decimal('10'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('30'))
|
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('30'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr2), Decimal('40') - DEFAULT_FEE)
|
assert_equal(test.nodes[0].z_getbalance(myzaddr2), Decimal('40') - conventional_fee(4))
|
||||||
assert_equal(test.nodes[1].getbalance(), Decimal('60'))
|
assert_equal(test.nodes[1].getbalance(), Decimal('60'))
|
||||||
assert_equal(test.nodes[2].getbalance(), Decimal('0'))
|
assert_equal(test.nodes[2].getbalance(), Decimal('0'))
|
||||||
|
|
||||||
|
@ -213,9 +213,9 @@ class MergeToAddressHelper:
|
||||||
generate_and_check(test.nodes[1], 2)
|
generate_and_check(test.nodes[1], 2)
|
||||||
test.sync_all()
|
test.sync_all()
|
||||||
|
|
||||||
assert_equal(test.nodes[0].getbalance(), Decimal('80') - DEFAULT_FEE)
|
assert_equal(test.nodes[0].getbalance(), Decimal('80') - conventional_fee(4))
|
||||||
assert_equal(test.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('10'))
|
assert_equal(test.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('10'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(mytaddr), Decimal('70') - DEFAULT_FEE)
|
assert_equal(test.nodes[0].z_getbalance(mytaddr), Decimal('70') - conventional_fee(4))
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('0'))
|
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('0'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr2), Decimal('0'))
|
assert_equal(test.nodes[0].z_getbalance(myzaddr2), Decimal('0'))
|
||||||
assert_equal(test.nodes[1].getbalance(), Decimal('70'))
|
assert_equal(test.nodes[1].getbalance(), Decimal('70'))
|
||||||
|
@ -235,8 +235,8 @@ class MergeToAddressHelper:
|
||||||
assert_equal(test.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('0'))
|
assert_equal(test.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('0'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(mytaddr), Decimal('0'))
|
assert_equal(test.nodes[0].z_getbalance(mytaddr), Decimal('0'))
|
||||||
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('0'))
|
assert_equal(test.nodes[0].z_getbalance(myzaddr), Decimal('0'))
|
||||||
assert_equal(test.nodes[1].getbalance(), Decimal('160') - DEFAULT_FEE)
|
assert_equal(test.nodes[1].getbalance(), Decimal('160') - conventional_fee(4))
|
||||||
assert_equal(test.nodes[1].z_getbalance(n1taddr), Decimal('80') - DEFAULT_FEE)
|
assert_equal(test.nodes[1].z_getbalance(n1taddr), Decimal('80') - conventional_fee(4))
|
||||||
assert_equal(test.nodes[2].getbalance(), Decimal('0'))
|
assert_equal(test.nodes[2].getbalance(), Decimal('0'))
|
||||||
|
|
||||||
# Generate 5 regular UTXOs on node 0, and 20 regular UTXOs on node 2.
|
# Generate 5 regular UTXOs on node 0, and 20 regular UTXOs on node 2.
|
||||||
|
|
|
@ -317,18 +317,18 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
|
||||||
// 0.00005 z-ZEC out, default fee
|
// 0.00005 z-ZEC out, default fee
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingOutput(fvk.ovk, pa, 5000, {});
|
builder.AddSaplingOutput(fvk.ovk, pa, 5000, {});
|
||||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
EXPECT_EQ("Change cannot be negative: -0.00006 ZEC", builder.Build().GetError());
|
||||||
|
|
||||||
// Fail if there is only a transparent output
|
// Fail if there is only a transparent output
|
||||||
// 0.00005 t-ZEC out, default fee
|
// 0.00005 t-ZEC out, default fee
|
||||||
builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentOutput(taddr, 5000);
|
builder.AddTransparentOutput(taddr, 5000);
|
||||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
EXPECT_EQ("Change cannot be negative: -0.00006 ZEC", builder.Build().GetError());
|
||||||
|
|
||||||
// Fails if there is insufficient input
|
// Fails if there is insufficient input
|
||||||
// 0.00005 t-ZEC out, default fee, 0.00005999 z-ZEC in
|
// 0.00005 t-ZEC out, default fee, 0.00005999 z-ZEC in
|
||||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
EXPECT_EQ("Change cannot be negative: -0.00000001 ZEC", builder.Build().GetError());
|
||||||
|
|
||||||
// Succeeds if there is sufficient input
|
// Succeeds if there is sufficient input
|
||||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 1);
|
builder.AddTransparentInput(COutPoint(), scriptPubKey, 1);
|
||||||
|
|
|
@ -501,7 +501,8 @@ TransactionBuilderResult TransactionBuilder::Build()
|
||||||
change -= tOut.nValue;
|
change -= tOut.nValue;
|
||||||
}
|
}
|
||||||
if (change < 0) {
|
if (change < 0) {
|
||||||
return TransactionBuilderResult("Change cannot be negative");
|
return TransactionBuilderResult(
|
||||||
|
strprintf("Change cannot be negative: %s", DisplayMoney(change)));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
#include "walletdb.h"
|
#include "walletdb.h"
|
||||||
#include "wallet/paymentdisclosuredb.h"
|
|
||||||
#include "zcash/IncrementalMerkleTree.hpp"
|
#include "zcash/IncrementalMerkleTree.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -40,53 +39,16 @@
|
||||||
|
|
||||||
using namespace libzcash;
|
using namespace libzcash;
|
||||||
|
|
||||||
int mta_find_output(UniValue obj, int n)
|
|
||||||
{
|
|
||||||
UniValue outputMapValue = find_value(obj, "outputmap");
|
|
||||||
if (!outputMapValue.isArray()) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Missing outputmap for JoinSplit operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
UniValue outputMap = outputMapValue.get_array();
|
|
||||||
assert(outputMap.size() == ZC_NUM_JS_OUTPUTS);
|
|
||||||
for (size_t i = 0; i < outputMap.size(); i++) {
|
|
||||||
if (outputMap[i].get_int() == n) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw std::logic_error("n is not present in outputmap");
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
|
AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
|
||||||
WalletTxBuilder builder,
|
CWallet& wallet,
|
||||||
ZTXOSelector ztxoSelector,
|
TransactionStrategy strategy,
|
||||||
SpendableInputs allInputs,
|
TransactionEffects effects,
|
||||||
MergeToAddressRecipient recipient,
|
UniValue contextInfo)
|
||||||
TransactionStrategy strategy,
|
: strategy_(strategy), effects_(effects), contextinfo_(contextInfo)
|
||||||
CAmount fee,
|
|
||||||
UniValue contextInfo) :
|
|
||||||
builder_(builder), ztxoSelector_(ztxoSelector), allInputs_(allInputs), toPaymentAddress_(recipient.first), memo_(recipient.second), fee_(fee), strategy_(strategy), contextinfo_(contextInfo)
|
|
||||||
{
|
{
|
||||||
if (!MoneyRange(fee)) {
|
effects.LockSpendable(*pwalletMain);
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyIO keyIO(Params());
|
KeyIO keyIO(Params());
|
||||||
isToTaddr_ = false;
|
|
||||||
|
|
||||||
examine(toPaymentAddress_, match {
|
|
||||||
[&](const CKeyID&) { isToTaddr_ = true; },
|
|
||||||
[&](const CScriptID&) { isToTaddr_ = true; },
|
|
||||||
[](const SproutPaymentAddress&) { },
|
|
||||||
[](const libzcash::SaplingPaymentAddress&) { },
|
|
||||||
[](const libzcash::UnifiedAddress&) {
|
|
||||||
// TODO: This should be removable with WalletTxBuilder, but need to test it.
|
|
||||||
throw JSONRPCError(
|
|
||||||
RPC_INVALID_ADDRESS_OR_KEY,
|
|
||||||
"z_mergetoaddress does not yet support sending to unified addresses");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Log the context info i.e. the call parameters to z_mergetoaddress
|
// Log the context info i.e. the call parameters to z_mergetoaddress
|
||||||
if (LogAcceptCategory("zrpcunsafe")) {
|
if (LogAcceptCategory("zrpcunsafe")) {
|
||||||
|
@ -94,15 +56,21 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
|
||||||
} else {
|
} else {
|
||||||
LogPrint("zrpc", "%s: z_mergetoaddress initialized\n", getId());
|
LogPrint("zrpc", "%s: z_mergetoaddress initialized\n", getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable payment disclosure if requested
|
|
||||||
paymentDisclosureMode = fExperimentalPaymentDisclosure;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress()
|
AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<uint256, UniValue>
|
||||||
|
main_impl(
|
||||||
|
const CChainParams& chainparams,
|
||||||
|
CWallet& wallet,
|
||||||
|
const TransactionStrategy& strategy,
|
||||||
|
const TransactionEffects& effects,
|
||||||
|
const std::string& id,
|
||||||
|
bool testmode);
|
||||||
|
|
||||||
void AsyncRPCOperation_mergetoaddress::main()
|
void AsyncRPCOperation_mergetoaddress::main()
|
||||||
{
|
{
|
||||||
set_state(OperationStatus::EXECUTING);
|
set_state(OperationStatus::EXECUTING);
|
||||||
|
@ -116,7 +84,10 @@ void AsyncRPCOperation_mergetoaddress::main()
|
||||||
|
|
||||||
std::optional<uint256> txid;
|
std::optional<uint256> txid;
|
||||||
try {
|
try {
|
||||||
txid = main_impl(*pwalletMain, Params());
|
UniValue sendResult;
|
||||||
|
std::tie(txid, sendResult) =
|
||||||
|
main_impl(Params(), *pwalletMain, strategy_, effects_, getId(), testmode);
|
||||||
|
set_result(sendResult);
|
||||||
} catch (const UniValue& objError) {
|
} catch (const UniValue& objError) {
|
||||||
int code = find_value(objError, "code").get_int();
|
int code = find_value(objError, "code").get_int();
|
||||||
std::string message = find_value(objError, "message").get_str();
|
std::string message = find_value(objError, "message").get_str();
|
||||||
|
@ -157,111 +128,68 @@ void AsyncRPCOperation_mergetoaddress::main()
|
||||||
LogPrintf("%s", s);
|
LogPrintf("%s", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
tl::expected<void, InputSelectionError> AsyncRPCOperation_mergetoaddress::prepare(const CChainParams& chainparams, CWallet& wallet) {
|
std::pair<uint256, UniValue>
|
||||||
CAmount minersFee = fee_;
|
main_impl(
|
||||||
|
const CChainParams& chainparams,
|
||||||
size_t numInputs = allInputs_.utxos.size();
|
CWallet& wallet,
|
||||||
|
const TransactionStrategy& strategy,
|
||||||
CAmount t_inputs_total = 0;
|
const TransactionEffects& effects,
|
||||||
for (COutput& t : allInputs_.utxos) {
|
const std::string& id,
|
||||||
t_inputs_total += t.Value();
|
bool testmode)
|
||||||
}
|
|
||||||
|
|
||||||
CAmount z_inputs_total = 0;
|
|
||||||
for (const SproutNoteEntry& t : allInputs_.sproutNoteEntries) {
|
|
||||||
z_inputs_total += t.note.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const SaplingNoteEntry& t : allInputs_.saplingNoteEntries) {
|
|
||||||
z_inputs_total += t.note.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const OrchardNoteMetadata& t : allInputs_.orchardNoteMetadata) {
|
|
||||||
z_inputs_total += t.GetNoteValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
CAmount targetAmount = z_inputs_total + t_inputs_total;
|
|
||||||
|
|
||||||
CAmount sendAmount = targetAmount - minersFee;
|
|
||||||
if (sendAmount <= 0) {
|
|
||||||
return tl::make_unexpected(InvalidFundsError(sendAmount, InsufficientFundsError(minersFee)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab the current consensus branch ID
|
|
||||||
{
|
|
||||||
LOCK(cs_main);
|
|
||||||
consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, chainparams.GetConsensus());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto payment = Payment(toPaymentAddress_, sendAmount, memo_);
|
|
||||||
|
|
||||||
return builder_.PrepareTransaction(
|
|
||||||
wallet,
|
|
||||||
ztxoSelector_,
|
|
||||||
allInputs_,
|
|
||||||
{payment},
|
|
||||||
chainActive,
|
|
||||||
strategy_,
|
|
||||||
minersFee,
|
|
||||||
nAnchorConfirmations)
|
|
||||||
.map([&](const TransactionEffects& effects) -> void {
|
|
||||||
effects.LockSpendable(wallet);
|
|
||||||
effects_ = effects;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notes:
|
|
||||||
// 1. #1359 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
|
|
||||||
// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them.
|
|
||||||
uint256 AsyncRPCOperation_mergetoaddress::main_impl(CWallet& wallet, const CChainParams& chainparams)
|
|
||||||
{
|
{
|
||||||
// The caller must call `prepare` before `main_impl` is invoked.
|
|
||||||
const auto effects = effects_.value();
|
|
||||||
try {
|
try {
|
||||||
const auto& spendable = effects.GetSpendable();
|
const auto& spendable = effects.GetSpendable();
|
||||||
const auto& payments = effects.GetPayments();
|
const auto& payments = effects.GetPayments();
|
||||||
spendable.LogInputs(getId());
|
spendable.LogInputs(id);
|
||||||
|
|
||||||
if (allInputs_.sproutNoteEntries.empty()
|
bool sendsToShielded = false;
|
||||||
&& allInputs_.saplingNoteEntries.empty()
|
for (const auto& resolvedPayment : payments.GetResolvedPayments()) {
|
||||||
&& allInputs_.orchardNoteMetadata.empty()
|
sendsToShielded = sendsToShielded || examine(resolvedPayment.address, match {
|
||||||
&& isToTaddr_) {
|
[](const CKeyID&) { return true; },
|
||||||
|
[](const CScriptID&) { return true; },
|
||||||
|
[](const libzcash::SaplingPaymentAddress&) { return false; },
|
||||||
|
[](const libzcash::OrchardRawAddress&) { return false; },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (spendable.sproutNoteEntries.empty()
|
||||||
|
&& spendable.saplingNoteEntries.empty()
|
||||||
|
&& spendable.orchardNoteMetadata.empty()
|
||||||
|
&& !sendsToShielded) {
|
||||||
LogPrint("zrpc", "%s: spending %s to send %s with fee %s\n",
|
LogPrint("zrpc", "%s: spending %s to send %s with fee %s\n",
|
||||||
getId(),
|
id,
|
||||||
FormatMoney(payments.Total()),
|
FormatMoney(payments.Total()),
|
||||||
FormatMoney(spendable.Total()),
|
FormatMoney(spendable.Total()),
|
||||||
FormatMoney(effects.GetFee()));
|
FormatMoney(effects.GetFee()));
|
||||||
} else {
|
} else {
|
||||||
LogPrint("zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
|
LogPrint("zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
|
||||||
getId(),
|
id,
|
||||||
FormatMoney(payments.Total()),
|
FormatMoney(payments.Total()),
|
||||||
FormatMoney(spendable.Total()),
|
FormatMoney(spendable.Total()),
|
||||||
FormatMoney(effects.GetFee()));
|
FormatMoney(effects.GetFee()));
|
||||||
}
|
}
|
||||||
LogPrint("zrpc", "%s: total transparent input: %s\n", getId(),
|
LogPrint("zrpc", "%s: total transparent input: %s\n", id,
|
||||||
FormatMoney(spendable.GetTransparentTotal()));
|
FormatMoney(spendable.GetTransparentTotal()));
|
||||||
LogPrint("zrpcunsafe", "%s: total shielded input: %s\n", getId(),
|
LogPrint("zrpcunsafe", "%s: total shielded input: %s\n", id,
|
||||||
FormatMoney(spendable.GetSaplingTotal() + spendable.GetOrchardTotal()));
|
FormatMoney(spendable.GetSaplingTotal() + spendable.GetOrchardTotal()));
|
||||||
LogPrint("zrpc", "%s: total transparent output: %s\n", getId(),
|
LogPrint("zrpc", "%s: total transparent output: %s\n", id,
|
||||||
FormatMoney(payments.GetTransparentTotal()));
|
FormatMoney(payments.GetTransparentTotal()));
|
||||||
LogPrint("zrpcunsafe", "%s: total shielded Sapling output: %s\n", getId(),
|
LogPrint("zrpcunsafe", "%s: total shielded Sapling output: %s\n", id,
|
||||||
FormatMoney(payments.GetSaplingTotal()));
|
FormatMoney(payments.GetSaplingTotal()));
|
||||||
LogPrint("zrpcunsafe", "%s: total shielded Orchard output: %s\n", getId(),
|
LogPrint("zrpcunsafe", "%s: total shielded Orchard output: %s\n", id,
|
||||||
FormatMoney(payments.GetOrchardTotal()));
|
FormatMoney(payments.GetOrchardTotal()));
|
||||||
LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(effects.GetFee()));
|
LogPrint("zrpc", "%s: fee: %s\n", id, FormatMoney(effects.GetFee()));
|
||||||
|
|
||||||
auto buildResult = effects.ApproveAndBuild(
|
auto buildResult = effects.ApproveAndBuild(
|
||||||
chainparams.GetConsensus(),
|
chainparams.GetConsensus(),
|
||||||
wallet,
|
wallet,
|
||||||
chainActive,
|
chainActive,
|
||||||
strategy_);
|
strategy);
|
||||||
auto tx = buildResult.GetTxOrThrow();
|
auto tx = buildResult.GetTxOrThrow();
|
||||||
|
|
||||||
UniValue sendResult = SendTransaction(tx, payments.GetResolvedPayments(), std::nullopt, testmode);
|
UniValue sendResult = SendTransaction(tx, payments.GetResolvedPayments(), std::nullopt, testmode);
|
||||||
set_result(sendResult);
|
|
||||||
|
|
||||||
effects.UnlockSpendable(wallet);
|
effects.UnlockSpendable(wallet);
|
||||||
return tx.GetHash();
|
return {tx.GetHash(), sendResult};
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
effects.UnlockSpendable(wallet);
|
effects.UnlockSpendable(wallet);
|
||||||
throw;
|
throw;
|
||||||
|
|
|
@ -27,9 +27,6 @@
|
||||||
|
|
||||||
using namespace libzcash;
|
using namespace libzcash;
|
||||||
|
|
||||||
// A recipient is a tuple of address, memo (optional if zaddr)
|
|
||||||
typedef std::pair<libzcash::PaymentAddress, std::optional<Memo>> MergeToAddressRecipient;
|
|
||||||
|
|
||||||
// Package of info which is passed to perform_joinsplit methods.
|
// Package of info which is passed to perform_joinsplit methods.
|
||||||
struct MergeToAddressJSInfo {
|
struct MergeToAddressJSInfo {
|
||||||
std::vector<JSInput> vjsin;
|
std::vector<JSInput> vjsin;
|
||||||
|
@ -50,13 +47,10 @@ class AsyncRPCOperation_mergetoaddress : public AsyncRPCOperation
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AsyncRPCOperation_mergetoaddress(
|
AsyncRPCOperation_mergetoaddress(
|
||||||
WalletTxBuilder builder,
|
CWallet& wallet,
|
||||||
ZTXOSelector ztxoSelector,
|
TransactionStrategy strategy,
|
||||||
SpendableInputs allInputs,
|
TransactionEffects effects,
|
||||||
MergeToAddressRecipient recipient,
|
UniValue contextInfo = NullUniValue);
|
||||||
TransactionStrategy strategy,
|
|
||||||
CAmount fee = DEFAULT_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
|
||||||
|
@ -65,69 +59,20 @@ public:
|
||||||
AsyncRPCOperation_mergetoaddress& operator=(AsyncRPCOperation_mergetoaddress const&) = delete; // Copy assign
|
AsyncRPCOperation_mergetoaddress& operator=(AsyncRPCOperation_mergetoaddress const&) = delete; // Copy assign
|
||||||
AsyncRPCOperation_mergetoaddress& operator=(AsyncRPCOperation_mergetoaddress&&) = delete; // Move assign
|
AsyncRPCOperation_mergetoaddress& operator=(AsyncRPCOperation_mergetoaddress&&) = delete; // Move assign
|
||||||
|
|
||||||
tl::expected<void, InputSelectionError> prepare(const CChainParams& chainparams, CWallet& wallet);
|
|
||||||
|
|
||||||
virtual void main();
|
virtual void main();
|
||||||
|
|
||||||
virtual UniValue getStatus() const;
|
virtual UniValue getStatus() const;
|
||||||
|
|
||||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
/// Set to true to disable sending txs and generating proofs
|
||||||
|
bool testmode = false;
|
||||||
bool paymentDisclosureMode = false; // Set to true to save esk for encrypted notes in payment disclosure database.
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class TEST_FRIEND_AsyncRPCOperation_mergetoaddress; // class for unit testing
|
const TransactionStrategy strategy_;
|
||||||
|
|
||||||
UniValue contextinfo_; // optional data to include in return value from getStatus()
|
const TransactionEffects effects_;
|
||||||
|
|
||||||
uint32_t consensusBranchId_;
|
/// optional data to include in return value from getStatus()
|
||||||
CAmount fee_;
|
UniValue contextinfo_;
|
||||||
int mindepth_;
|
|
||||||
bool isToTaddr_;
|
|
||||||
bool isToZaddr_;
|
|
||||||
PaymentAddress toPaymentAddress_;
|
|
||||||
std::optional<Memo> memo_;
|
|
||||||
|
|
||||||
ed25519::VerificationKey joinSplitPubKey_;
|
|
||||||
ed25519::SigningKey joinSplitPrivKey_;
|
|
||||||
|
|
||||||
// The key is the result string from calling JSOutPoint::ToString()
|
|
||||||
std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap;
|
|
||||||
|
|
||||||
WalletTxBuilder builder_;
|
|
||||||
ZTXOSelector ztxoSelector_;
|
|
||||||
SpendableInputs allInputs_;
|
|
||||||
TransactionStrategy strategy_;
|
|
||||||
|
|
||||||
std::optional<TransactionEffects> effects_;
|
|
||||||
|
|
||||||
uint256 main_impl(CWallet& wallet, const CChainParams& chainparams);
|
|
||||||
|
|
||||||
// payment disclosure!
|
|
||||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// To test private methods, a friend class can act as a proxy
|
|
||||||
class TEST_FRIEND_AsyncRPCOperation_mergetoaddress
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::shared_ptr<AsyncRPCOperation_mergetoaddress> delegate;
|
|
||||||
|
|
||||||
TEST_FRIEND_AsyncRPCOperation_mergetoaddress(std::shared_ptr<AsyncRPCOperation_mergetoaddress> ptr) : delegate(ptr) {}
|
|
||||||
|
|
||||||
// Delegated methods
|
|
||||||
|
|
||||||
uint256 main_impl(CWallet& wallet, const CChainParams& chainparams)
|
|
||||||
{
|
|
||||||
return delegate->main_impl(wallet, chainparams);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_state(OperationStatus state)
|
|
||||||
{
|
|
||||||
delegate->state_.store(state);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ZCASH_WALLET_ASYNCRPCOPERATION_MERGETOADDRESS_H
|
#endif // ZCASH_WALLET_ASYNCRPCOPERATION_MERGETOADDRESS_H
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
#include "util/test.h"
|
#include "util/test.h"
|
||||||
#include "gtest/utils.h"
|
#include "gtest/utils.h"
|
||||||
#include "wallet/asyncrpcoperation_mergetoaddress.h"
|
|
||||||
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
||||||
#include "wallet/asyncrpcoperation_sendmany.h"
|
#include "wallet/asyncrpcoperation_sendmany.h"
|
||||||
#include "wallet/memo.h"
|
#include "wallet/memo.h"
|
||||||
|
@ -57,9 +56,9 @@ TEST(WalletRPCTests, RPCZMergeToAddressInternals)
|
||||||
auto taddr = pwalletMain->GenerateNewKey(true).GetID();
|
auto taddr = pwalletMain->GenerateNewKey(true).GetID();
|
||||||
std::string taddr_string = keyIO.EncodeDestination(taddr);
|
std::string taddr_string = keyIO.EncodeDestination(taddr);
|
||||||
|
|
||||||
MergeToAddressRecipient taddr1(keyIO.DecodePaymentAddress(taddr_string).value(), Memo());
|
NetAmountRecipient taddr1(keyIO.DecodePaymentAddress(taddr_string).value(), Memo());
|
||||||
auto pa = pwalletMain->GenerateNewSproutZKey();
|
auto pa = pwalletMain->GenerateNewSproutZKey();
|
||||||
MergeToAddressRecipient zaddr1(pa, Memo());
|
NetAmountRecipient zaddr1(pa, Memo());
|
||||||
|
|
||||||
WalletTxBuilder builder(Params(), minRelayTxFee);
|
WalletTxBuilder builder(Params(), minRelayTxFee);
|
||||||
auto selector = CWallet::LegacyTransparentZTXOSelector(
|
auto selector = CWallet::LegacyTransparentZTXOSelector(
|
||||||
|
@ -72,11 +71,24 @@ TEST(WalletRPCTests, RPCZMergeToAddressInternals)
|
||||||
SpendableInputs inputs;
|
SpendableInputs inputs;
|
||||||
auto wtx = FakeWalletTx();
|
auto wtx = FakeWalletTx();
|
||||||
inputs.utxos.emplace_back(COutput(&wtx, 0, 100, true));
|
inputs.utxos.emplace_back(COutput(&wtx, 0, 100, true));
|
||||||
auto operation = AsyncRPCOperation_mergetoaddress(std::move(builder), selector, inputs, zaddr1, strategy, 0);
|
builder.PrepareTransaction(
|
||||||
operation.main();
|
*pwalletMain,
|
||||||
EXPECT_TRUE(operation.isFailed());
|
selector,
|
||||||
std::string msg = operation.getErrorMessage();
|
inputs,
|
||||||
EXPECT_EQ(msg, "Sending funds into the Sprout pool is no longer supported.");
|
zaddr1,
|
||||||
|
chainActive,
|
||||||
|
strategy,
|
||||||
|
0,
|
||||||
|
1)
|
||||||
|
.map_error([](const auto& err) {
|
||||||
|
EXPECT_TRUE(examine(err, match {
|
||||||
|
[](const AddressResolutionError& are) {
|
||||||
|
return are == AddressResolutionError::SproutRecipientsNotSupported;
|
||||||
|
},
|
||||||
|
[](const auto&) { return false; },
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.map([](const auto&) { EXPECT_TRUE(false); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnloadGlobalWallet();
|
UnloadGlobalWallet();
|
||||||
|
|
|
@ -5370,8 +5370,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
||||||
" ,...\n"
|
" ,...\n"
|
||||||
" ]\n"
|
" ]\n"
|
||||||
"2. \"toaddress\" (string, required) The taddr or zaddr to send the funds to.\n"
|
"2. \"toaddress\" (string, required) The taddr or zaddr to send the funds to.\n"
|
||||||
"3. fee (numeric, optional, default="
|
"3. fee (numeric, optional, default=null) The fee amount in " + CURRENCY_UNIT + " to attach to this transaction. The default behavior\n"
|
||||||
+ strprintf("%s", FormatMoney(DEFAULT_FEE)) + ") The fee amount to attach to this transaction.\n"
|
" is to use a fee calculated according to ZIP 317.\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 as many as will fit in the transaction.\n"
|
+ 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"
|
||||||
"5. shielded_limit (numeric, optional, default="
|
"5. shielded_limit (numeric, optional, default="
|
||||||
|
@ -5529,14 +5529,9 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
||||||
string("Invalid parameter, unknown address format: ") + destStr);
|
string("Invalid parameter, unknown address format: ") + destStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert fee from currency format to zatoshis
|
std::optional<CAmount> nFee;
|
||||||
CAmount nFee = DEFAULT_FEE;
|
if (params.size() > 2 && !params[2].isNull()) {
|
||||||
if (params.size() > 2) {
|
nFee = AmountFromValue( params[2] );
|
||||||
if (params[2].get_real() == 0.0) {
|
|
||||||
nFee = 0;
|
|
||||||
} else {
|
|
||||||
nFee = AmountFromValue( params[2] );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int nUTXOLimit = MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT;
|
int nUTXOLimit = MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT;
|
||||||
|
@ -5563,7 +5558,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
||||||
memo = Memo::FromHexOrThrow(params[5].get_str());
|
memo = Memo::FromHexOrThrow(params[5].get_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
MergeToAddressRecipient recipient(destaddress.value(), memo);
|
NetAmountRecipient recipient(destaddress.value(), memo);
|
||||||
|
|
||||||
// Prepare to get UTXOs and notes
|
// Prepare to get UTXOs and notes
|
||||||
SpendableInputs allInputs;
|
SpendableInputs allInputs;
|
||||||
|
@ -5737,28 +5732,18 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
||||||
// - We only have one from address
|
// - We only have one from address
|
||||||
// - It's equal to toaddress
|
// - It's equal to toaddress
|
||||||
// - The address only contains a single UTXO or note
|
// - The address only contains a single UTXO or note
|
||||||
|
// TODO: Move this to WalletTxBuilder
|
||||||
if (setAddress.size() == 1 && setAddress.count(destStr) && (numUtxos + numNotes) == 1) {
|
if (setAddress.size() == 1 && setAddress.count(destStr) && (numUtxos + numNotes) == 1) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Destination address is also the only source address, and all its funds are already merged.");
|
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
|
// Keep record of parameters in context object
|
||||||
UniValue contextInfo(UniValue::VOBJ);
|
UniValue contextInfo(UniValue::VOBJ);
|
||||||
contextInfo.pushKV("fromaddresses", params[0]);
|
contextInfo.pushKV("fromaddresses", params[0]);
|
||||||
contextInfo.pushKV("toaddress", params[1]);
|
contextInfo.pushKV("toaddress", params[1]);
|
||||||
contextInfo.pushKV("fee", ValueFromAmount(nFee));
|
if (nFee.has_value()) {
|
||||||
|
contextInfo.pushKV("fee", ValueFromAmount(nFee.value()));
|
||||||
|
}
|
||||||
|
|
||||||
// The privacy policy is determined early so as to be able to use it
|
// The privacy policy is determined early so as to be able to use it
|
||||||
// for selector construction.
|
// for selector construction.
|
||||||
|
@ -5769,37 +5754,14 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
||||||
params.size() > 6 ? std::optional(params[6].get_str()) : std::nullopt),
|
params.size() > 6 ? std::optional(params[6].get_str()) : std::nullopt),
|
||||||
InterpretLegacyCompat(std::nullopt, {recipient.first}));
|
InterpretLegacyCompat(std::nullopt, {recipient.first}));
|
||||||
|
|
||||||
bool isSproutShielded = allInputs.sproutNoteEntries.size() > 0;
|
|
||||||
// Contextual transaction we will build on
|
|
||||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
|
|
||||||
chainparams.GetConsensus(),
|
|
||||||
nextBlockHeight,
|
|
||||||
isSproutShielded || nPreferredTxVersion < ZIP225_MIN_TX_VERSION);
|
|
||||||
if (contextualTx.nVersion == 1 && isSproutShielded) {
|
|
||||||
contextualTx.nVersion = 2; // Tx format should support vJoinSplit
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isToSaplingZaddr || allInputs.saplingNoteEntries.size() > 0) {
|
|
||||||
std::optional<uint256> orchardAnchor;
|
|
||||||
if (!isSproutShielded && nPreferredTxVersion >= ZIP225_MIN_TX_VERSION && nAnchorConfirmations > 0) {
|
|
||||||
// Allow Orchard recipients by setting an Orchard anchor.
|
|
||||||
auto orchardAnchorHeight = nextBlockHeight - nAnchorConfirmations;
|
|
||||||
if (chainparams.GetConsensus().NetworkUpgradeActive(orchardAnchorHeight, Consensus::UPGRADE_NU5)) {
|
|
||||||
auto anchorBlockIndex = chainActive[orchardAnchorHeight];
|
|
||||||
assert(anchorBlockIndex != nullptr);
|
|
||||||
orchardAnchor = anchorBlockIndex->hashFinalOrchardRoot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletTxBuilder builder(Params(), minRelayTxFee);
|
WalletTxBuilder builder(Params(), minRelayTxFee);
|
||||||
|
|
||||||
// This currently isn’t too critical since we don’t yet use it for note selection and we never
|
// The ZTXOSelector here is only used to determine whether or not Sprout can be selected. It
|
||||||
// need a change address. The one thing this does is indicate whether or not Sprout can be
|
// should not be used for other purposes (e.g., note selection or finding a change address).
|
||||||
// selected. However, we eventually want a `ZTXOSelector` that can support this operation fully.
|
// TODO: Add a ZTXOSelector that can support the full range of `z_mergetoaddress` behavior and
|
||||||
// I.e., be able to select from multiple pools in the legacy account.
|
// use it instead of `GetFilteredNotes`.
|
||||||
std::optional<ZTXOSelector> ztxoSelector;
|
std::optional<ZTXOSelector> ztxoSelector;
|
||||||
if (isSproutShielded) {
|
if (allInputs.sproutNoteEntries.size() > 0) {
|
||||||
ztxoSelector = pwalletMain->ZTXOSelectorForAddress(
|
ztxoSelector = pwalletMain->ZTXOSelectorForAddress(
|
||||||
allInputs.sproutNoteEntries[0].address,
|
allInputs.sproutNoteEntries[0].address,
|
||||||
true,
|
true,
|
||||||
|
@ -5820,20 +5782,23 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create operation and add to global queue
|
// Create operation and add to global queue
|
||||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
auto effects = builder.PrepareTransaction(
|
||||||
auto mergeOp = new AsyncRPCOperation_mergetoaddress(
|
*pwalletMain,
|
||||||
std::move(builder),
|
ztxoSelector.value(),
|
||||||
ztxoSelector.value(),
|
allInputs,
|
||||||
allInputs,
|
recipient,
|
||||||
recipient,
|
chainActive,
|
||||||
strategy,
|
strategy,
|
||||||
nFee,
|
nFee,
|
||||||
contextInfo);
|
nAnchorConfirmations)
|
||||||
mergeOp->prepare(chainparams, *pwalletMain).map_error([&](const InputSelectionError& err) {
|
.map_error([&](const auto& err) {
|
||||||
ThrowInputSelectionError(err, ztxoSelector.value(), strategy);
|
ThrowInputSelectionError(err, ztxoSelector.value(), strategy);
|
||||||
});
|
})
|
||||||
|
.value();
|
||||||
|
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(mergeOp);
|
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||||
|
std::shared_ptr<AsyncRPCOperation> operation(
|
||||||
|
new AsyncRPCOperation_mergetoaddress(*pwalletMain, strategy, effects, contextInfo));
|
||||||
q->addOperation(operation);
|
q->addOperation(operation);
|
||||||
AsyncRPCOperationId operationId = operation->getId();
|
AsyncRPCOperationId operationId = operation->getId();
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include "asyncrpcqueue.h"
|
#include "asyncrpcqueue.h"
|
||||||
#include "asyncrpcoperation.h"
|
#include "asyncrpcoperation.h"
|
||||||
#include "wallet/asyncrpcoperation_common.h"
|
#include "wallet/asyncrpcoperation_common.h"
|
||||||
#include "wallet/asyncrpcoperation_mergetoaddress.h"
|
|
||||||
#include "wallet/asyncrpcoperation_sendmany.h"
|
#include "wallet/asyncrpcoperation_sendmany.h"
|
||||||
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
||||||
#include "wallet/memo.h"
|
#include "wallet/memo.h"
|
||||||
|
@ -1226,6 +1225,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
||||||
SelectParams(CBaseChainParams::TESTNET);
|
SelectParams(CBaseChainParams::TESTNET);
|
||||||
const Consensus::Params& consensusParams = Params().GetConsensus();
|
const Consensus::Params& consensusParams = Params().GetConsensus();
|
||||||
KeyIO keyIO(Params());
|
KeyIO keyIO(Params());
|
||||||
|
WalletTxBuilder builder(Params(), minRelayTxFee);
|
||||||
|
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
|
@ -1255,7 +1255,6 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
||||||
// confirm this.
|
// confirm this.
|
||||||
TransparentCoinbasePolicy::Allow,
|
TransparentCoinbasePolicy::Allow,
|
||||||
false).value();
|
false).value();
|
||||||
WalletTxBuilder builder(Params(), minRelayTxFee);
|
|
||||||
std::vector<Payment> recipients = { Payment(zaddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
|
std::vector<Payment> recipients = { Payment(zaddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
|
||||||
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedSenders);
|
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedSenders);
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy, std::nullopt));
|
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy, std::nullopt));
|
||||||
|
@ -1272,7 +1271,6 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
||||||
true,
|
true,
|
||||||
TransparentCoinbasePolicy::Disallow,
|
TransparentCoinbasePolicy::Disallow,
|
||||||
false).value();
|
false).value();
|
||||||
WalletTxBuilder builder(Params(), minRelayTxFee);
|
|
||||||
std::vector<Payment> recipients = { Payment(taddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
|
std::vector<Payment> recipients = { Payment(taddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
|
||||||
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedRecipients);
|
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedRecipients);
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy, std::nullopt));
|
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy, std::nullopt));
|
||||||
|
@ -1530,6 +1528,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_parameters)
|
||||||
|
|
||||||
// Test constructor of AsyncRPCOperation_shieldcoinbase
|
// Test constructor of AsyncRPCOperation_shieldcoinbase
|
||||||
KeyIO keyIO(Params());
|
KeyIO keyIO(Params());
|
||||||
|
WalletTxBuilder builder(Params(), minRelayTxFee);
|
||||||
UniValue retValue;
|
UniValue retValue;
|
||||||
BOOST_CHECK_NO_THROW(retValue = CallRPC("getnewaddress"));
|
BOOST_CHECK_NO_THROW(retValue = CallRPC("getnewaddress"));
|
||||||
auto taddr2 = std::get<CKeyID>(keyIO.DecodePaymentAddress(retValue.get_str()).value());
|
auto taddr2 = std::get<CKeyID>(keyIO.DecodePaymentAddress(retValue.get_str()).value());
|
||||||
|
@ -1542,7 +1541,6 @@ BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_parameters)
|
||||||
// confirm this.
|
// confirm this.
|
||||||
TransparentCoinbasePolicy::Allow,
|
TransparentCoinbasePolicy::Allow,
|
||||||
false).value();
|
false).value();
|
||||||
WalletTxBuilder builder(Params(), minRelayTxFee);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_shieldcoinbase(std::move(builder), selector, testnetzaddr, PrivacyPolicy::AllowRevealedSenders, SHIELD_COINBASE_DEFAULT_LIMIT, 1));
|
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_shieldcoinbase(std::move(builder), selector, testnetzaddr, PrivacyPolicy::AllowRevealedSenders, SHIELD_COINBASE_DEFAULT_LIMIT, 1));
|
||||||
|
@ -1634,29 +1632,44 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_parameters)
|
||||||
int nHeight = retValue.get_int();
|
int nHeight = retValue.get_int();
|
||||||
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1, false);
|
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1, false);
|
||||||
|
|
||||||
// Test constructor of AsyncRPCOperation_mergetoaddress
|
|
||||||
KeyIO keyIO(Params());
|
KeyIO keyIO(Params());
|
||||||
MergeToAddressRecipient testnetzaddr(
|
WalletTxBuilder builder(Params(), minRelayTxFee);
|
||||||
|
NetAmountRecipient testnetzaddr(
|
||||||
keyIO.DecodePaymentAddress("ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP").value(),
|
keyIO.DecodePaymentAddress("ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP").value(),
|
||||||
Memo());
|
Memo());
|
||||||
WalletTxBuilder builder(Params(), minRelayTxFee);
|
|
||||||
auto selector = CWallet::LegacyTransparentZTXOSelector(
|
auto selector = CWallet::LegacyTransparentZTXOSelector(
|
||||||
true,
|
true,
|
||||||
TransparentCoinbasePolicy::Disallow);
|
TransparentCoinbasePolicy::Disallow);
|
||||||
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedRecipients);
|
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedRecipients);
|
||||||
|
|
||||||
try {
|
builder.PrepareTransaction(*pwalletMain, selector, {}, testnetzaddr, chainActive, strategy, -1, 1)
|
||||||
auto operation = AsyncRPCOperation_mergetoaddress(builder, selector, {}, testnetzaddr, strategy, -1);
|
.map_error([&](const auto& err) {
|
||||||
BOOST_FAIL("Fee value of -1 expected to be out of the valid range of values.");
|
// TODO: Provide `operator==` on `InputSelectionError` and use that here.
|
||||||
} catch (const UniValue& objError) {
|
BOOST_CHECK(examine(err, match {
|
||||||
BOOST_CHECK( find_error(objError, "Fee is out of range"));
|
[](AddressResolutionError are) {
|
||||||
}
|
// FIXME: Should be failing because of negative fee – change recipient address to not be Sprout.
|
||||||
|
return are == AddressResolutionError::SproutRecipientsNotSupported;
|
||||||
|
},
|
||||||
|
[](const auto&) { return false; },
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.map([](const auto&) {
|
||||||
|
BOOST_FAIL("Fee value of -1 expected to be out of the valid range of values.");
|
||||||
|
});
|
||||||
|
|
||||||
{
|
builder.PrepareTransaction(*pwalletMain, selector, {}, testnetzaddr, chainActive, strategy, 1, 1)
|
||||||
auto operation = AsyncRPCOperation_mergetoaddress(builder, selector, {}, testnetzaddr, strategy, 1);
|
.map_error([&](const auto& err) {
|
||||||
operation.main();
|
// TODO: Provide `operator==` on `InputSelectionError` and use that here.
|
||||||
BOOST_CHECK_EQUAL(operation.getErrorMessage(), "Insufficient funds: have -0.00000001, need 0.00000001; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.");
|
BOOST_CHECK(examine(err, match {
|
||||||
}
|
[](const InvalidFundsError& ife) {
|
||||||
|
return std::holds_alternative<InsufficientFundsError>(ife.reason);
|
||||||
|
},
|
||||||
|
[](const auto&) { return false; },
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.map([](const auto&) {
|
||||||
|
BOOST_FAIL("Expected an error.");
|
||||||
|
});
|
||||||
|
|
||||||
auto wtx = FakeWalletTx();
|
auto wtx = FakeWalletTx();
|
||||||
SpendableInputs inputs;
|
SpendableInputs inputs;
|
||||||
|
@ -1664,11 +1677,20 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_parameters)
|
||||||
inputs.sproutNoteEntries.emplace_back(SproutNoteEntry {JSOutPoint(), SproutPaymentAddress(), SproutNote(), "", 0});
|
inputs.sproutNoteEntries.emplace_back(SproutNoteEntry {JSOutPoint(), SproutPaymentAddress(), SproutNote(), "", 0});
|
||||||
inputs.saplingNoteEntries.emplace_back(SaplingNoteEntry {SaplingOutPoint(), SaplingPaymentAddress(), SaplingNote({}, uint256(), 0, uint256(), Zip212Enabled::BeforeZip212), "", 0});
|
inputs.saplingNoteEntries.emplace_back(SaplingNoteEntry {SaplingOutPoint(), SaplingPaymentAddress(), SaplingNote({}, uint256(), 0, uint256(), Zip212Enabled::BeforeZip212), "", 0});
|
||||||
|
|
||||||
{
|
builder.PrepareTransaction(*pwalletMain, selector, inputs, testnetzaddr, chainActive, strategy, 1, 1)
|
||||||
auto operation = AsyncRPCOperation_mergetoaddress(builder, selector, inputs, testnetzaddr, strategy, 1);
|
.map_error([&](const auto& err) {
|
||||||
operation.main();
|
// TODO: Provide `operator==` on `InputSelectionError` and use that here.
|
||||||
BOOST_CHECK_EQUAL(operation.getErrorMessage(), "Insufficient funds: have 0.00, need 0.00000001; note that coinbase outputs will not be selected if you specify ANY_TADDR, any transparent recipients are included, or if the `privacyPolicy` parameter is not set to `AllowRevealedSenders` or weaker.");
|
BOOST_CHECK(examine(err, match {
|
||||||
}
|
[](AddressResolutionError are) {
|
||||||
|
// FIXME: Should be failing because of insufficient funds – change recipient address to not be Sprout.
|
||||||
|
return are == AddressResolutionError::SproutRecipientsNotSupported;
|
||||||
|
},
|
||||||
|
[](const auto&) { return false; },
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.map([](const auto&) {
|
||||||
|
BOOST_FAIL("Expected an error.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestWTxStatus(const Consensus::Params consensusParams, const int delta) {
|
void TestWTxStatus(const Consensus::Params consensusParams, const int delta) {
|
||||||
|
|
|
@ -260,7 +260,7 @@ tl::expected<ChangeAddress, AddressResolutionError>
|
||||||
WalletTxBuilder::GetChangeAddress(
|
WalletTxBuilder::GetChangeAddress(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
SpendableInputs& spendable,
|
const SpendableInputs& spendable,
|
||||||
const Payments& resolvedPayments,
|
const Payments& resolvedPayments,
|
||||||
const TransactionStrategy& strategy,
|
const TransactionStrategy& strategy,
|
||||||
bool afterNU5) const
|
bool afterNU5) const
|
||||||
|
@ -385,11 +385,11 @@ tl::expected<TransactionEffects, InputSelectionError>
|
||||||
WalletTxBuilder::PrepareTransaction(
|
WalletTxBuilder::PrepareTransaction(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
SpendableInputs& spendable,
|
const SpendableInputs& spendable,
|
||||||
const Recipients& payments,
|
const Recipients& payments,
|
||||||
const CChain& chain,
|
const CChain& chain,
|
||||||
TransactionStrategy strategy,
|
const TransactionStrategy& strategy,
|
||||||
std::optional<CAmount> fee,
|
const std::optional<CAmount>& fee,
|
||||||
uint32_t anchorConfirmations) const
|
uint32_t anchorConfirmations) const
|
||||||
{
|
{
|
||||||
if (fee.has_value() && maxTxFee < fee.value()) {
|
if (fee.has_value() && maxTxFee < fee.value()) {
|
||||||
|
@ -521,7 +521,7 @@ tl::expected<
|
||||||
WalletTxBuilder::IterateLimit(
|
WalletTxBuilder::IterateLimit(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
const TransactionStrategy strategy,
|
const TransactionStrategy& strategy,
|
||||||
CAmount sendAmount,
|
CAmount sendAmount,
|
||||||
CAmount dustThreshold,
|
CAmount dustThreshold,
|
||||||
const SpendableInputs& spendable,
|
const SpendableInputs& spendable,
|
||||||
|
@ -615,11 +615,11 @@ tl::expected<InputSelection, InputSelectionError>
|
||||||
WalletTxBuilder::ResolveInputsAndPayments(
|
WalletTxBuilder::ResolveInputsAndPayments(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
SpendableInputs& spendableMut,
|
SpendableInputs spendableMut,
|
||||||
const std::vector<Payment>& payments,
|
const std::vector<Payment>& payments,
|
||||||
const CChain& chain,
|
const CChain& chain,
|
||||||
const TransactionStrategy& strategy,
|
const TransactionStrategy& strategy,
|
||||||
std::optional<CAmount> fee,
|
const std::optional<CAmount>& fee,
|
||||||
bool afterNU5) const
|
bool afterNU5) const
|
||||||
{
|
{
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
|
|
|
@ -376,7 +376,7 @@ private:
|
||||||
GetChangeAddress(
|
GetChangeAddress(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
SpendableInputs& spendable,
|
const SpendableInputs& spendable,
|
||||||
const Payments& resolvedPayments,
|
const Payments& resolvedPayments,
|
||||||
const TransactionStrategy& strategy,
|
const TransactionStrategy& strategy,
|
||||||
bool afterNU5) const;
|
bool afterNU5) const;
|
||||||
|
@ -387,7 +387,7 @@ private:
|
||||||
IterateLimit(
|
IterateLimit(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
const TransactionStrategy strategy,
|
const TransactionStrategy& strategy,
|
||||||
CAmount sendAmount,
|
CAmount sendAmount,
|
||||||
CAmount dustThreshold,
|
CAmount dustThreshold,
|
||||||
const SpendableInputs& spendable,
|
const SpendableInputs& spendable,
|
||||||
|
@ -403,11 +403,11 @@ private:
|
||||||
ResolveInputsAndPayments(
|
ResolveInputsAndPayments(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
SpendableInputs& spendable,
|
SpendableInputs spendable,
|
||||||
const std::vector<Payment>& payments,
|
const std::vector<Payment>& payments,
|
||||||
const CChain& chain,
|
const CChain& chain,
|
||||||
const TransactionStrategy& strategy,
|
const TransactionStrategy& strategy,
|
||||||
std::optional<CAmount> fee,
|
const std::optional<CAmount>& fee,
|
||||||
bool afterNU5) const;
|
bool afterNU5) const;
|
||||||
/**
|
/**
|
||||||
* Compute the internal and external OVKs to use in transaction construction, given
|
* Compute the internal and external OVKs to use in transaction construction, given
|
||||||
|
@ -431,12 +431,12 @@ public:
|
||||||
PrepareTransaction(
|
PrepareTransaction(
|
||||||
CWallet& wallet,
|
CWallet& wallet,
|
||||||
const ZTXOSelector& selector,
|
const ZTXOSelector& selector,
|
||||||
SpendableInputs& spendable,
|
const SpendableInputs& spendable,
|
||||||
const Recipients& payments,
|
const Recipients& payments,
|
||||||
const CChain& chain,
|
const CChain& chain,
|
||||||
TransactionStrategy strategy,
|
const TransactionStrategy& strategy,
|
||||||
/// A fixed fee is used if provided, otherwise it is calculated based on ZIP 317.
|
/// A fixed fee is used if provided, otherwise it is calculated based on ZIP 317.
|
||||||
std::optional<CAmount> fee,
|
const std::optional<CAmount>& fee,
|
||||||
uint32_t anchorConfirmations) const;
|
uint32_t anchorConfirmations) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue