Auto merge of #3489 - str4d:3215-z_sendmany, r=str4d
Add Sapling support to z_sendmany Closes #3215.
This commit is contained in:
commit
edd321609c
|
@ -24,6 +24,7 @@ testScripts=(
|
|||
'wallet_nullifiers.py'
|
||||
'wallet_1941.py'
|
||||
'wallet_addresses.py'
|
||||
'wallet_sapling.py'
|
||||
'listtransactions.py'
|
||||
'mempool_resurrect_test.py'
|
||||
'txn_doublespend.py'
|
||||
|
|
|
@ -262,7 +262,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
|||
myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient transparent funds, have 10.00, need 10000.0001")
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient protected funds, have 9.9998, need 10000.0001")
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient shielded funds, have 9.9998, need 10000.0001")
|
||||
|
||||
# Send will fail because of insufficient funds unless sender uses coinbase utxos
|
||||
try:
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env python2
|
||||
# Copyright (c) 2018 The Zcash developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
start_nodes,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
# Test wallet behaviour with Sapling addresses
|
||||
class WalletSaplingTest(BitcoinTestFramework):
|
||||
|
||||
def setup_nodes(self):
|
||||
return start_nodes(4, self.options.tmpdir, [[
|
||||
'-nuparams=5ba81b19:201', # Overwinter
|
||||
'-nuparams=76b809bb:201', # Sapling
|
||||
]] * 4)
|
||||
|
||||
def run_test(self):
|
||||
# Sanity-check the test harness
|
||||
assert_equal(self.nodes[0].getblockcount(), 200)
|
||||
|
||||
# Activate Sapling
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
taddr0 = self.nodes[0].getnewaddress()
|
||||
# Skip over the address containing node 1's coinbase
|
||||
self.nodes[1].getnewaddress()
|
||||
taddr1 = self.nodes[1].getnewaddress()
|
||||
saplingAddr0 = self.nodes[0].z_getnewaddress('sapling')
|
||||
saplingAddr1 = self.nodes[1].z_getnewaddress('sapling')
|
||||
|
||||
# Verify addresses
|
||||
assert(saplingAddr0 in self.nodes[0].z_listaddresses())
|
||||
assert(saplingAddr1 in self.nodes[1].z_listaddresses())
|
||||
assert_equal(self.nodes[0].z_validateaddress(saplingAddr0)['type'], 'sapling')
|
||||
assert_equal(self.nodes[0].z_validateaddress(saplingAddr1)['type'], 'sapling')
|
||||
|
||||
# Verify balance
|
||||
assert_equal(self.nodes[0].z_getbalance(saplingAddr0), Decimal('0'))
|
||||
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal('0'))
|
||||
assert_equal(self.nodes[1].z_getbalance(taddr1), Decimal('0'))
|
||||
|
||||
# Node 0 shields some funds
|
||||
# taddr -> Sapling
|
||||
# -> taddr (change)
|
||||
recipients = []
|
||||
recipients.append({"address": saplingAddr0, "amount": Decimal('20')})
|
||||
myopid = self.nodes[0].z_sendmany(taddr0, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# Verify balance
|
||||
assert_equal(self.nodes[0].z_getbalance(saplingAddr0), Decimal('20'))
|
||||
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal('0'))
|
||||
assert_equal(self.nodes[1].z_getbalance(taddr1), Decimal('0'))
|
||||
|
||||
# Node 0 sends some shielded funds to node 1
|
||||
# Sapling -> Sapling
|
||||
# -> Sapling (change)
|
||||
recipients = []
|
||||
recipients.append({"address": saplingAddr1, "amount": Decimal('15')})
|
||||
myopid = self.nodes[0].z_sendmany(saplingAddr0, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# Verify balance
|
||||
assert_equal(self.nodes[0].z_getbalance(saplingAddr0), Decimal('5'))
|
||||
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal('15'))
|
||||
assert_equal(self.nodes[1].z_getbalance(taddr1), Decimal('0'))
|
||||
|
||||
# Node 1 sends some shielded funds to node 0, as well as unshielding
|
||||
# Sapling -> Sapling
|
||||
# -> taddr
|
||||
# -> Sapling (change)
|
||||
recipients = []
|
||||
recipients.append({"address": saplingAddr0, "amount": Decimal('5')})
|
||||
recipients.append({"address": taddr1, "amount": Decimal('5')})
|
||||
myopid = self.nodes[1].z_sendmany(saplingAddr1, recipients, 1, 0)
|
||||
wait_and_assert_operationid_status(self.nodes[1], myopid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# Verify balance
|
||||
assert_equal(self.nodes[0].z_getbalance(saplingAddr0), Decimal('10'))
|
||||
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal('5'))
|
||||
assert_equal(self.nodes[1].z_getbalance(taddr1), Decimal('5'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletSaplingTest().main()
|
|
@ -970,26 +970,26 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
|
|||
|
||||
// Test constructor of AsyncRPCOperation_sendmany
|
||||
try {
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(mtx, "",{}, {}, -1));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(boost::none, mtx, "",{}, {}, -1));
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "Minconf cannot be negative"));
|
||||
}
|
||||
|
||||
try {
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(mtx, "",{}, {}, 1));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(boost::none, mtx, "",{}, {}, 1));
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "From address parameter missing"));
|
||||
}
|
||||
|
||||
try {
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, "tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ", {}, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ", {}, {}, 1) );
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "No recipients"));
|
||||
}
|
||||
|
||||
try {
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, "INVALID", recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "INVALID", recipients, {}, 1) );
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "Invalid from address"));
|
||||
}
|
||||
|
@ -997,7 +997,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
|
|||
// Testnet payment addresses begin with 'zt'. This test detects an incorrect prefix.
|
||||
try {
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, "zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U", recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U", recipients, {}, 1) );
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "Invalid from address"));
|
||||
}
|
||||
|
@ -1006,7 +1006,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
|
|||
// invokes a method on pwalletMain, which is undefined in the google test environment.
|
||||
try {
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, "ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP", recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, "ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP", recipients, {}, 1) );
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK( find_error(objError, "no spending key found for zaddr"));
|
||||
}
|
||||
|
@ -1039,7 +1039,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// there are no utxos to spend
|
||||
{
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, taddr1, {}, recipients, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, taddr1, {}, recipients, 1) );
|
||||
operation->main();
|
||||
BOOST_CHECK(operation->isFailed());
|
||||
std::string msg = operation->getErrorMessage();
|
||||
|
@ -1050,7 +1050,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
{
|
||||
try {
|
||||
std::vector<SendManyRecipient> recipients = {SendManyRecipient(taddr1, 100.0, "DEADBEEF")};
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(mtx, zaddr1, recipients, {}, 0));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 0));
|
||||
BOOST_CHECK(false); // Fail test if an exception is not thrown
|
||||
} catch (const UniValue& objError) {
|
||||
BOOST_CHECK(find_error(objError, "Minconf cannot be zero when sending from zaddr"));
|
||||
|
@ -1061,7 +1061,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// there are no unspent notes to spend
|
||||
{
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1,100.0, "DEADBEEF") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, zaddr1, recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
|
||||
operation->main();
|
||||
BOOST_CHECK(operation->isFailed());
|
||||
std::string msg = operation->getErrorMessage();
|
||||
|
@ -1071,7 +1071,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// get_memo_from_hex_string())
|
||||
{
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, zaddr1, recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||
|
||||
|
@ -1122,7 +1122,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// add_taddr_change_output_to_tx() will append a vout to a raw transaction
|
||||
{
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, zaddr1, recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||
|
||||
|
@ -1151,7 +1151,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
SendManyRecipient("tmUSbHz3vxnwLvRyNDXbwkZxjVyDodMJEhh",CAmount(4.56), ""),
|
||||
SendManyRecipient("tmYZAXYPCP56Xa5JQWWPZuK7o7bfUQW6kkd",CAmount(7.89), ""),
|
||||
};
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, zaddr1, recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, recipients, {}, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||
|
||||
|
@ -1174,7 +1174,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// we have the spending key for the dummy recipient zaddr1
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") };
|
||||
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, zaddr1, {}, recipients, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, {}, recipients, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||
|
||||
|
@ -1199,7 +1199,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// Dummy input so the operation object can be instantiated.
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") };
|
||||
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(mtx, zaddr1, {}, recipients, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(boost::none, mtx, zaddr1, {}, recipients, 1) );
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ private:
|
|||
boost::optional<CTxDestination> tChangeAddr;
|
||||
|
||||
public:
|
||||
TransactionBuilder() {}
|
||||
TransactionBuilder(const Consensus::Params& consensusParams, int nHeight, CKeyStore* keyStore = nullptr);
|
||||
|
||||
void SetFee(CAmount fee);
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
|
||||
using namespace libzcash;
|
||||
|
||||
extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
|
||||
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
|
||||
|
||||
int find_output(UniValue obj, int n) {
|
||||
UniValue outputMapValue = find_value(obj, "outputmap");
|
||||
if (!outputMapValue.isArray()) {
|
||||
|
@ -53,6 +56,7 @@ int find_output(UniValue obj, int n) {
|
|||
}
|
||||
|
||||
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||
boost::optional<TransactionBuilder> builder,
|
||||
CMutableTransaction contextualTx,
|
||||
std::string fromAddress,
|
||||
std::vector<SendManyRecipient> tOutputs,
|
||||
|
@ -75,7 +79,13 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||
if (tOutputs.size() == 0 && zOutputs.size() == 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "No recipients");
|
||||
}
|
||||
|
||||
|
||||
isUsingBuilder_ = false;
|
||||
if (builder) {
|
||||
isUsingBuilder_ = true;
|
||||
builder_ = builder.get();
|
||||
}
|
||||
|
||||
fromtaddr_ = DecodeDestination(fromAddress);
|
||||
isfromtaddr_ = IsValidDestination(fromtaddr_);
|
||||
isfromzaddr_ = false;
|
||||
|
@ -83,19 +93,14 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||
if (!isfromtaddr_) {
|
||||
auto address = DecodePaymentAddress(fromAddress);
|
||||
if (IsValidPaymentAddress(address)) {
|
||||
// TODO: Add Sapling support. For now, ensure we can freely convert.
|
||||
assert(boost::get<libzcash::SproutPaymentAddress>(&address) != nullptr);
|
||||
SproutPaymentAddress addr = boost::get<libzcash::SproutPaymentAddress>(address);
|
||||
|
||||
// We don't need to lock on the wallet as spending key related methods are thread-safe
|
||||
SproutSpendingKey key;
|
||||
if (!pwalletMain->GetSproutSpendingKey(addr, key)) {
|
||||
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), address)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr");
|
||||
}
|
||||
|
||||
|
||||
isfromzaddr_ = true;
|
||||
frompaymentaddress_ = addr;
|
||||
spendingkey_ = key;
|
||||
frompaymentaddress_ = address;
|
||||
spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address).get();
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address");
|
||||
}
|
||||
|
@ -235,15 +240,21 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address.");
|
||||
}
|
||||
|
||||
// At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design
|
||||
assert(z_sprout_inputs_.empty() || z_sapling_inputs_.empty());
|
||||
|
||||
CAmount t_inputs_total = 0;
|
||||
for (SendManyInputUTXO & t : t_inputs_) {
|
||||
t_inputs_total += std::get<2>(t);
|
||||
}
|
||||
|
||||
CAmount z_inputs_total = 0;
|
||||
for (SendManyInputJSOP & t : z_inputs_) {
|
||||
for (SendManyInputJSOP & t : z_sprout_inputs_) {
|
||||
z_inputs_total += std::get<2>(t);
|
||||
}
|
||||
for (auto t : z_sapling_inputs_) {
|
||||
z_inputs_total += t.note.value();
|
||||
}
|
||||
|
||||
CAmount t_outputs_total = 0;
|
||||
for (SendManyRecipient & t : t_outputs_) {
|
||||
|
@ -269,7 +280,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
|
||||
if (isfromzaddr_ && (z_inputs_total < targetAmount)) {
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
|
||||
strprintf("Insufficient protected funds, have %s, need %s",
|
||||
strprintf("Insufficient shielded funds, have %s, need %s",
|
||||
FormatMoney(z_inputs_total), FormatMoney(targetAmount)));
|
||||
}
|
||||
|
||||
|
@ -328,15 +339,25 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
}
|
||||
|
||||
// update the transaction with these inputs
|
||||
CMutableTransaction rawTx(tx_);
|
||||
for (SendManyInputUTXO & t : t_inputs_) {
|
||||
uint256 txid = std::get<0>(t);
|
||||
int vout = std::get<1>(t);
|
||||
CAmount amount = std::get<2>(t);
|
||||
CTxIn in(COutPoint(txid, vout));
|
||||
rawTx.vin.push_back(in);
|
||||
if (isUsingBuilder_) {
|
||||
CScript scriptPubKey = GetScriptForDestination(fromtaddr_);
|
||||
for (auto t : t_inputs_) {
|
||||
uint256 txid = std::get<0>(t);
|
||||
int vout = std::get<1>(t);
|
||||
CAmount amount = std::get<2>(t);
|
||||
builder_.AddTransparentInput(COutPoint(txid, vout), scriptPubKey, amount);
|
||||
}
|
||||
} else {
|
||||
CMutableTransaction rawTx(tx_);
|
||||
for (SendManyInputUTXO & t : t_inputs_) {
|
||||
uint256 txid = std::get<0>(t);
|
||||
int vout = std::get<1>(t);
|
||||
CAmount amount = std::get<2>(t);
|
||||
CTxIn in(COutPoint(txid, vout));
|
||||
rawTx.vin.push_back(in);
|
||||
}
|
||||
tx_ = CTransaction(rawTx);
|
||||
}
|
||||
tx_ = CTransaction(rawTx);
|
||||
}
|
||||
|
||||
LogPrint((isfromtaddr_) ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
|
||||
|
@ -347,6 +368,141 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total));
|
||||
LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee));
|
||||
|
||||
|
||||
/**
|
||||
* SCENARIO #0
|
||||
*
|
||||
* Sprout not involved, so we just use the TransactionBuilder and we're done.
|
||||
* We added the transparent inputs to the builder earlier.
|
||||
*/
|
||||
if (isUsingBuilder_) {
|
||||
builder_.SetFee(minersFee);
|
||||
|
||||
// Get various necessary keys
|
||||
SaplingExpandedSpendingKey expsk;
|
||||
SaplingFullViewingKey from;
|
||||
if (isfromzaddr_) {
|
||||
auto sk = boost::get<libzcash::SaplingSpendingKey>(spendingkey_);
|
||||
expsk = sk.expanded_spending_key();
|
||||
from = expsk.full_viewing_key();
|
||||
} else {
|
||||
// TODO: Set "from" to something!
|
||||
}
|
||||
|
||||
// Set change address if we are using transparent funds
|
||||
// TODO: Should we just use fromtaddr_ as the change address?
|
||||
if (isfromtaddr_) {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
CReserveKey keyChange(pwalletMain);
|
||||
CPubKey vchPubKey;
|
||||
bool ret = keyChange.GetReservedKey(vchPubKey);
|
||||
if (!ret) {
|
||||
// should never fail, as we just unlocked
|
||||
throw JSONRPCError(
|
||||
RPC_WALLET_KEYPOOL_RAN_OUT,
|
||||
"Could not generate a taddr to use as a change address");
|
||||
}
|
||||
|
||||
CTxDestination changeAddr = vchPubKey.GetID();
|
||||
assert(builder_.SendChangeTo(changeAddr));
|
||||
}
|
||||
|
||||
// Select Sapling notes
|
||||
std::vector<SaplingOutPoint> ops;
|
||||
std::vector<SaplingNote> notes;
|
||||
CAmount sum = 0;
|
||||
for (auto t : z_sapling_inputs_) {
|
||||
ops.push_back(t.op);
|
||||
notes.push_back(t.note);
|
||||
sum += t.note.value();
|
||||
if (sum >= targetAmount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Sapling anchor and witnesses
|
||||
uint256 anchor;
|
||||
std::vector<boost::optional<SaplingWitness>> witnesses;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
pwalletMain->GetSaplingNoteWitnesses(ops, witnesses, anchor);
|
||||
}
|
||||
|
||||
// Add Sapling spends
|
||||
for (size_t i = 0; i < notes.size(); i++) {
|
||||
if (!witnesses[i]) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note");
|
||||
}
|
||||
assert(builder_.AddSaplingSpend(expsk, notes[i], anchor, witnesses[i].get()));
|
||||
}
|
||||
|
||||
// Add Sapling outputs
|
||||
for (auto r : z_outputs_) {
|
||||
auto address = std::get<0>(r);
|
||||
auto value = std::get<1>(r);
|
||||
auto hexMemo = std::get<2>(r);
|
||||
|
||||
auto addr = DecodePaymentAddress(address);
|
||||
assert(boost::get<libzcash::SaplingPaymentAddress>(&addr) != nullptr);
|
||||
auto to = boost::get<libzcash::SaplingPaymentAddress>(addr);
|
||||
|
||||
auto memo = get_memo_from_hex_string(hexMemo);
|
||||
|
||||
builder_.AddSaplingOutput(from, to, value, memo);
|
||||
}
|
||||
|
||||
// Add transparent outputs
|
||||
for (auto r : t_outputs_) {
|
||||
auto outputAddress = std::get<0>(r);
|
||||
auto amount = std::get<1>(r);
|
||||
|
||||
auto address = DecodeDestination(outputAddress);
|
||||
if (!builder_.AddTransparentOutput(address, amount)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr.");
|
||||
}
|
||||
}
|
||||
|
||||
// Build the transaction
|
||||
auto maybe_tx = builder_.Build();
|
||||
if (!maybe_tx) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction.");
|
||||
}
|
||||
tx_ = maybe_tx.get();
|
||||
|
||||
// Send the transaction
|
||||
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction
|
||||
auto signedtxn = EncodeHexTx(tx_);
|
||||
if (!testmode) {
|
||||
UniValue params = UniValue(UniValue::VARR);
|
||||
params.push_back(signedtxn);
|
||||
UniValue sendResultValue = sendrawtransaction(params, false);
|
||||
if (sendResultValue.isNull()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "sendrawtransaction did not return an error or a txid.");
|
||||
}
|
||||
|
||||
auto txid = sendResultValue.get_str();
|
||||
|
||||
UniValue o(UniValue::VOBJ);
|
||||
o.push_back(Pair("txid", txid));
|
||||
set_result(o);
|
||||
} else {
|
||||
// Test mode does not send the transaction to the network.
|
||||
UniValue o(UniValue::VOBJ);
|
||||
o.push_back(Pair("test", 1));
|
||||
o.push_back(Pair("txid", tx_.GetHash().ToString()));
|
||||
o.push_back(Pair("hex", signedtxn));
|
||||
set_result(o);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* END SCENARIO #0
|
||||
*/
|
||||
|
||||
|
||||
// Grab the current consensus branch ID
|
||||
{
|
||||
LOCK(cs_main);
|
||||
|
@ -395,7 +551,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
// Copy zinputs and zoutputs to more flexible containers
|
||||
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
|
||||
CAmount tmp = 0;
|
||||
for (auto o : z_inputs_) {
|
||||
for (auto o : z_sprout_inputs_) {
|
||||
zInputsDeque.push_back(o);
|
||||
tmp += std::get<2>(o);
|
||||
if (tmp >= targetAmount) {
|
||||
|
@ -404,18 +560,15 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
}
|
||||
std::deque<SendManyRecipient> zOutputsDeque;
|
||||
for (auto o : z_outputs_) {
|
||||
// TODO: Add Sapling support. For now, ensure we can later convert freely.
|
||||
auto addr = DecodePaymentAddress(std::get<0>(o));
|
||||
assert(boost::get<libzcash::SproutPaymentAddress>(&addr) != nullptr);
|
||||
zOutputsDeque.push_back(o);
|
||||
}
|
||||
|
||||
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
|
||||
// change upon arrival of new blocks which contain joinsplit transactions. This is likely
|
||||
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
|
||||
if (z_inputs_.size() > 0) {
|
||||
if (z_sprout_inputs_.size() > 0) {
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
for (auto t : z_inputs_) {
|
||||
for (auto t : z_sprout_inputs_) {
|
||||
JSOutPoint jso = std::get<0>(t);
|
||||
std::vector<JSOutPoint> vOutPoints = { jso };
|
||||
uint256 inputAnchor;
|
||||
|
@ -448,7 +601,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
if (selectedUTXOCoinbase) {
|
||||
assert(isSingleZaddrOutput);
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf(
|
||||
"Change %s not allowed. When protecting coinbase funds, the wallet does not "
|
||||
"Change %s not allowed. When shielding coinbase funds, the wallet does not "
|
||||
"allow any change as there is currently no way to specify a change address "
|
||||
"in z_sendmany.", FormatMoney(change)));
|
||||
} else {
|
||||
|
@ -734,6 +887,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
|
||||
} else {
|
||||
PaymentAddress pa = DecodePaymentAddress(address);
|
||||
// If we are here, we know we have no Sapling outputs.
|
||||
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
|
||||
if (hexMemo.size() > 0) {
|
||||
jso.memo = get_memo_from_hex_string(hexMemo);
|
||||
|
@ -768,9 +922,6 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||
}
|
||||
|
||||
|
||||
extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
|
||||
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
|
||||
|
||||
/**
|
||||
* Sign and send a raw transaction.
|
||||
* Raw transaction as hex string should be in object field "rawtxn"
|
||||
|
@ -872,7 +1023,7 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
|
|||
if (isCoinbase && fAcceptCoinbase==false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
CAmount nValue = out.tx->vout[out.i].nValue;
|
||||
SendManyInputUTXO utxo(out.tx->GetHash(), out.i, nValue, isCoinbase);
|
||||
t_inputs_.push_back(utxo);
|
||||
|
@ -895,10 +1046,19 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
|||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_);
|
||||
}
|
||||
|
||||
// If using the TransactionBuilder, we only want Sapling notes.
|
||||
// If not using it, we only want Sprout notes.
|
||||
// TODO: Refactor `GetFilteredNotes()` so we only fetch what we need.
|
||||
if (isUsingBuilder_) {
|
||||
sproutEntries.clear();
|
||||
} else {
|
||||
saplingEntries.clear();
|
||||
}
|
||||
|
||||
for (CSproutNotePlaintextEntry & entry : sproutEntries) {
|
||||
z_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_)), CAmount(entry.plaintext.value())));
|
||||
z_sprout_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_)), CAmount(entry.plaintext.value())));
|
||||
std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end());
|
||||
LogPrint("zrpcunsafe", "%s: found unspent note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n",
|
||||
LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n",
|
||||
getId(),
|
||||
entry.jsop.hash.ToString().substr(0, 10),
|
||||
entry.jsop.js,
|
||||
|
@ -907,16 +1067,31 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
|||
HexStr(data).substr(0, 10)
|
||||
);
|
||||
}
|
||||
// TODO: Do something with Sapling notes
|
||||
|
||||
if (z_inputs_.size() == 0) {
|
||||
|
||||
for (auto entry : saplingEntries) {
|
||||
z_sapling_inputs_.push_back(entry);
|
||||
std::string data(entry.memo.begin(), entry.memo.end());
|
||||
LogPrint("zrpcunsafe", "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n",
|
||||
getId(),
|
||||
entry.op.hash.ToString().substr(0, 10),
|
||||
entry.op.n,
|
||||
FormatMoney(entry.note.value()),
|
||||
HexStr(data).substr(0, 10));
|
||||
}
|
||||
|
||||
if (z_sprout_inputs_.empty() && z_sapling_inputs_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// sort in descending order, so big notes appear first
|
||||
std::sort(z_inputs_.begin(), z_inputs_.end(), [](SendManyInputJSOP i, SendManyInputJSOP j) -> bool {
|
||||
return ( std::get<2>(i) > std::get<2>(j));
|
||||
});
|
||||
std::sort(z_sprout_inputs_.begin(), z_sprout_inputs_.end(),
|
||||
[](SendManyInputJSOP i, SendManyInputJSOP j) -> bool {
|
||||
return std::get<2>(i) > std::get<2>(j);
|
||||
});
|
||||
std::sort(z_sapling_inputs_.begin(), z_sapling_inputs_.end(),
|
||||
[](SaplingNoteEntry i, SaplingNoteEntry j) -> bool {
|
||||
return i.note.value() > j.note.value();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "asyncrpcoperation.h"
|
||||
#include "amount.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "zcash/JoinSplit.hpp"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "wallet.h"
|
||||
|
@ -51,7 +52,15 @@ struct WitnessAnchorData {
|
|||
|
||||
class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
||||
public:
|
||||
AsyncRPCOperation_sendmany(CMutableTransaction contextualTx, std::string fromAddress, std::vector<SendManyRecipient> tOutputs, std::vector<SendManyRecipient> zOutputs, int minDepth, CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE, UniValue contextInfo = NullUniValue);
|
||||
AsyncRPCOperation_sendmany(
|
||||
boost::optional<TransactionBuilder> builder,
|
||||
CMutableTransaction contextualTx,
|
||||
std::string fromAddress,
|
||||
std::vector<SendManyRecipient> tOutputs,
|
||||
std::vector<SendManyRecipient> zOutputs,
|
||||
int minDepth,
|
||||
CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE,
|
||||
UniValue contextInfo = NullUniValue);
|
||||
virtual ~AsyncRPCOperation_sendmany();
|
||||
|
||||
// We don't want to be copied or moved around
|
||||
|
@ -73,6 +82,7 @@ private:
|
|||
|
||||
UniValue contextinfo_; // optional data to include in return value from getStatus()
|
||||
|
||||
bool isUsingBuilder_; // Indicates that no Sprout addresses are involved
|
||||
uint32_t consensusBranchId_;
|
||||
CAmount fee_;
|
||||
int mindepth_;
|
||||
|
@ -92,8 +102,10 @@ private:
|
|||
std::vector<SendManyRecipient> t_outputs_;
|
||||
std::vector<SendManyRecipient> z_outputs_;
|
||||
std::vector<SendManyInputUTXO> t_inputs_;
|
||||
std::vector<SendManyInputJSOP> z_inputs_;
|
||||
|
||||
std::vector<SendManyInputJSOP> z_sprout_inputs_;
|
||||
std::vector<SaplingNoteEntry> z_sapling_inputs_;
|
||||
|
||||
TransactionBuilder builder_;
|
||||
CTransaction tx_;
|
||||
|
||||
void add_taddr_change_output_to_tx(CAmount amount);
|
||||
|
|
|
@ -784,41 +784,6 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp)
|
|||
return NullUniValue;
|
||||
}
|
||||
|
||||
class GetSpendingKeyForPaymentAddress : public boost::static_visitor<libzcash::SpendingKey>
|
||||
{
|
||||
private:
|
||||
CWallet *m_wallet;
|
||||
public:
|
||||
GetSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
libzcash::SpendingKey operator()(const libzcash::SproutPaymentAddress &zaddr) const
|
||||
{
|
||||
libzcash::SproutSpendingKey k;
|
||||
if (!pwalletMain->GetSproutSpendingKey(zaddr, k)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr");
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
libzcash::SpendingKey operator()(const libzcash::SaplingPaymentAddress &zaddr) const
|
||||
{
|
||||
libzcash::SaplingIncomingViewingKey ivk;
|
||||
libzcash::SaplingFullViewingKey fvk;
|
||||
libzcash::SaplingSpendingKey sk;
|
||||
|
||||
if (!pwalletMain->GetSaplingIncomingViewingKey(zaddr, ivk) ||
|
||||
!pwalletMain->GetSaplingFullViewingKey(ivk, fvk) ||
|
||||
!pwalletMain->GetSaplingSpendingKey(fvk, sk)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr");
|
||||
}
|
||||
return sk;
|
||||
}
|
||||
|
||||
libzcash::SpendingKey operator()(const libzcash::InvalidEncoding& no) const {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
|
||||
}
|
||||
};
|
||||
|
||||
UniValue z_exportkey(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
|
@ -852,7 +817,10 @@ UniValue z_exportkey(const UniValue& params, bool fHelp)
|
|||
|
||||
// Sapling support
|
||||
auto sk = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address);
|
||||
return EncodeSpendingKey(sk);
|
||||
if (!sk) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr");
|
||||
}
|
||||
return EncodeSpendingKey(sk.get());
|
||||
}
|
||||
|
||||
UniValue z_exportviewingkey(const UniValue& params, bool fHelp)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "netbase.h"
|
||||
#include "rpc/server.h"
|
||||
#include "timedata.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "util.h"
|
||||
#include "utilmoneystr.h"
|
||||
#include "wallet.h"
|
||||
|
@ -3609,26 +3610,26 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
// Check that the from address is valid.
|
||||
auto fromaddress = params[0].get_str();
|
||||
bool fromTaddr = false;
|
||||
bool fromSapling = false;
|
||||
CTxDestination taddr = DecodeDestination(fromaddress);
|
||||
fromTaddr = IsValidDestination(taddr);
|
||||
libzcash::SproutPaymentAddress zaddr;
|
||||
if (!fromTaddr) {
|
||||
auto res = DecodePaymentAddress(fromaddress);
|
||||
if (!IsValidPaymentAddress(res)) {
|
||||
// invalid
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr.");
|
||||
}
|
||||
// TODO: Add Sapling support. For now, ensure we can freely convert.
|
||||
assert(boost::get<libzcash::SproutPaymentAddress>(&res) != nullptr);
|
||||
zaddr = boost::get<libzcash::SproutPaymentAddress>(res);
|
||||
}
|
||||
|
||||
// Check that we have the spending key
|
||||
if (!fromTaddr) {
|
||||
if (!pwalletMain->HaveSproutSpendingKey(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 Sprout or Sapling address
|
||||
fromSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
|
||||
}
|
||||
// This logic will need to be updated if we add a new shielded pool
|
||||
bool fromSprout = !(fromTaddr || fromSapling);
|
||||
|
||||
UniValue outputs = params[1].get_array();
|
||||
|
||||
|
@ -3638,6 +3639,9 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
// Keep track of addresses to spot duplicates
|
||||
set<std::string> setAddress;
|
||||
|
||||
// Track whether we see any Sprout addresses
|
||||
bool noSproutAddrs = !fromSprout;
|
||||
|
||||
// Recipients
|
||||
std::vector<SendManyRecipient> taddrRecipients;
|
||||
std::vector<SendManyRecipient> zaddrRecipients;
|
||||
|
@ -3658,8 +3662,26 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
bool isZaddr = false;
|
||||
CTxDestination taddr = DecodeDestination(address);
|
||||
if (!IsValidDestination(taddr)) {
|
||||
if (IsValidPaymentAddressString(address)) {
|
||||
auto res = DecodePaymentAddress(address);
|
||||
if (IsValidPaymentAddress(res)) {
|
||||
isZaddr = true;
|
||||
|
||||
bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
|
||||
bool toSprout = !toSapling;
|
||||
noSproutAddrs = noSproutAddrs && toSapling;
|
||||
|
||||
// If we are sending from a shielded address, all recipient
|
||||
// shielded addresses must be of the same type.
|
||||
if (fromSprout && toSapling) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_PARAMETER,
|
||||
"Cannot send from a Sprout address to a Sapling address using z_sendmany");
|
||||
}
|
||||
if (fromSapling && toSprout) {
|
||||
throw JSONRPCError(
|
||||
RPC_INVALID_PARAMETER,
|
||||
"Cannot send from a Sapling address to a Sprout address using z_sendmany");
|
||||
}
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address );
|
||||
}
|
||||
|
@ -3787,7 +3809,14 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
o.push_back(Pair("fee", std::stod(FormatMoney(nFee))));
|
||||
UniValue contextInfo = o;
|
||||
|
||||
// Builder (used if Sapling addresses are involved)
|
||||
boost::optional<TransactionBuilder> builder;
|
||||
if (noSproutAddrs) {
|
||||
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
|
||||
}
|
||||
|
||||
// Contextual transaction we will build on
|
||||
// (used if no Sapling addresses are involved)
|
||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
|
||||
bool isShielded = !fromTaddr || zaddrRecipients.size() > 0;
|
||||
if (contextualTx.nVersion == 1 && isShielded) {
|
||||
|
@ -3796,7 +3825,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo) );
|
||||
q->addOperation(operation);
|
||||
AsyncRPCOperationId operationId = operation->getId();
|
||||
return operationId;
|
||||
|
|
|
@ -4373,3 +4373,57 @@ bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding&
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) const
|
||||
{
|
||||
return m_wallet->HaveSproutSpendingKey(zaddr);
|
||||
}
|
||||
|
||||
bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
|
||||
{
|
||||
libzcash::SaplingIncomingViewingKey ivk;
|
||||
libzcash::SaplingFullViewingKey fvk;
|
||||
|
||||
return m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
|
||||
m_wallet->GetSaplingFullViewingKey(ivk, fvk) &&
|
||||
m_wallet->HaveSaplingSpendingKey(fvk);
|
||||
}
|
||||
|
||||
bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::InvalidEncoding& no) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
|
||||
const libzcash::SproutPaymentAddress &zaddr) const
|
||||
{
|
||||
libzcash::SproutSpendingKey k;
|
||||
if (m_wallet->GetSproutSpendingKey(zaddr, k)) {
|
||||
return libzcash::SpendingKey(k);
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
|
||||
const libzcash::SaplingPaymentAddress &zaddr) const
|
||||
{
|
||||
libzcash::SaplingIncomingViewingKey ivk;
|
||||
libzcash::SaplingFullViewingKey fvk;
|
||||
libzcash::SaplingSpendingKey sk;
|
||||
|
||||
if (m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
|
||||
m_wallet->GetSaplingFullViewingKey(ivk, fvk) &&
|
||||
m_wallet->GetSaplingSpendingKey(fvk, sk)) {
|
||||
return libzcash::SpendingKey(sk);
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
|
||||
const libzcash::InvalidEncoding& no) const
|
||||
{
|
||||
// Defaults to InvalidEncoding
|
||||
return libzcash::SpendingKey();
|
||||
}
|
||||
|
|
|
@ -1319,4 +1319,28 @@ public:
|
|||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
|
||||
class HaveSpendingKeyForPaymentAddress : public boost::static_visitor<bool>
|
||||
{
|
||||
private:
|
||||
CWallet *m_wallet;
|
||||
public:
|
||||
HaveSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
|
||||
class GetSpendingKeyForPaymentAddress : public boost::static_visitor<boost::optional<libzcash::SpendingKey>>
|
||||
{
|
||||
private:
|
||||
CWallet *m_wallet;
|
||||
public:
|
||||
GetSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
|
||||
|
||||
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SproutPaymentAddress &zaddr) const;
|
||||
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||
boost::optional<libzcash::SpendingKey> operator()(const libzcash::InvalidEncoding& no) const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_WALLET_WALLET_H
|
||||
|
|
Loading…
Reference in New Issue