Auto merge of #3489 - str4d:3215-z_sendmany, r=str4d
Add Sapling support to z_sendmany Closes #3215.
This commit is contained in:
@@ -24,6 +24,7 @@ testScripts=(
|
|||||||
'wallet_nullifiers.py'
|
'wallet_nullifiers.py'
|
||||||
'wallet_1941.py'
|
'wallet_1941.py'
|
||||||
'wallet_addresses.py'
|
'wallet_addresses.py'
|
||||||
|
'wallet_sapling.py'
|
||||||
'listtransactions.py'
|
'listtransactions.py'
|
||||||
'mempool_resurrect_test.py'
|
'mempool_resurrect_test.py'
|
||||||
'txn_doublespend.py'
|
'txn_doublespend.py'
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
|||||||
myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
|
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")
|
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)
|
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
|
# Send will fail because of insufficient funds unless sender uses coinbase utxos
|
||||||
try:
|
try:
|
||||||
|
|||||||
104
qa/rpc-tests/wallet_sapling.py
Executable file
104
qa/rpc-tests/wallet_sapling.py
Executable file
@@ -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
|
// Test constructor of AsyncRPCOperation_sendmany
|
||||||
try {
|
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) {
|
} catch (const UniValue& objError) {
|
||||||
BOOST_CHECK( find_error(objError, "Minconf cannot be negative"));
|
BOOST_CHECK( find_error(objError, "Minconf cannot be negative"));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (const UniValue& objError) {
|
||||||
BOOST_CHECK( find_error(objError, "From address parameter missing"));
|
BOOST_CHECK( find_error(objError, "From address parameter missing"));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (const UniValue& objError) {
|
||||||
BOOST_CHECK( find_error(objError, "No recipients"));
|
BOOST_CHECK( find_error(objError, "No recipients"));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
|
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) {
|
} catch (const UniValue& objError) {
|
||||||
BOOST_CHECK( find_error(objError, "Invalid from address"));
|
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.
|
// Testnet payment addresses begin with 'zt'. This test detects an incorrect prefix.
|
||||||
try {
|
try {
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
|
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) {
|
} catch (const UniValue& objError) {
|
||||||
BOOST_CHECK( find_error(objError, "Invalid from address"));
|
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.
|
// invokes a method on pwalletMain, which is undefined in the google test environment.
|
||||||
try {
|
try {
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient("dummy",1.0, "") };
|
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) {
|
} catch (const UniValue& objError) {
|
||||||
BOOST_CHECK( find_error(objError, "no spending key found for zaddr"));
|
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
|
// there are no utxos to spend
|
||||||
{
|
{
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
|
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();
|
operation->main();
|
||||||
BOOST_CHECK(operation->isFailed());
|
BOOST_CHECK(operation->isFailed());
|
||||||
std::string msg = operation->getErrorMessage();
|
std::string msg = operation->getErrorMessage();
|
||||||
@@ -1050,7 +1050,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
std::vector<SendManyRecipient> recipients = {SendManyRecipient(taddr1, 100.0, "DEADBEEF")};
|
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
|
BOOST_CHECK(false); // Fail test if an exception is not thrown
|
||||||
} catch (const UniValue& objError) {
|
} catch (const UniValue& objError) {
|
||||||
BOOST_CHECK(find_error(objError, "Minconf cannot be zero when sending from zaddr"));
|
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
|
// there are no unspent notes to spend
|
||||||
{
|
{
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1,100.0, "DEADBEEF") };
|
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();
|
operation->main();
|
||||||
BOOST_CHECK(operation->isFailed());
|
BOOST_CHECK(operation->isFailed());
|
||||||
std::string msg = operation->getErrorMessage();
|
std::string msg = operation->getErrorMessage();
|
||||||
@@ -1071,7 +1071,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||||||
// get_memo_from_hex_string())
|
// get_memo_from_hex_string())
|
||||||
{
|
{
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
|
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);
|
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
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
|
// add_taddr_change_output_to_tx() will append a vout to a raw transaction
|
||||||
{
|
{
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") };
|
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);
|
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||||
|
|
||||||
@@ -1151,7 +1151,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||||||
SendManyRecipient("tmUSbHz3vxnwLvRyNDXbwkZxjVyDodMJEhh",CAmount(4.56), ""),
|
SendManyRecipient("tmUSbHz3vxnwLvRyNDXbwkZxjVyDodMJEhh",CAmount(4.56), ""),
|
||||||
SendManyRecipient("tmYZAXYPCP56Xa5JQWWPZuK7o7bfUQW6kkd",CAmount(7.89), ""),
|
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);
|
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
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
|
// we have the spending key for the dummy recipient zaddr1
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") };
|
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);
|
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
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.
|
// Dummy input so the operation object can be instantiated.
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") };
|
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);
|
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ private:
|
|||||||
boost::optional<CTxDestination> tChangeAddr;
|
boost::optional<CTxDestination> tChangeAddr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
TransactionBuilder() {}
|
||||||
TransactionBuilder(const Consensus::Params& consensusParams, int nHeight, CKeyStore* keyStore = nullptr);
|
TransactionBuilder(const Consensus::Params& consensusParams, int nHeight, CKeyStore* keyStore = nullptr);
|
||||||
|
|
||||||
void SetFee(CAmount fee);
|
void SetFee(CAmount fee);
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
|
|
||||||
using namespace libzcash;
|
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) {
|
int find_output(UniValue obj, int n) {
|
||||||
UniValue outputMapValue = find_value(obj, "outputmap");
|
UniValue outputMapValue = find_value(obj, "outputmap");
|
||||||
if (!outputMapValue.isArray()) {
|
if (!outputMapValue.isArray()) {
|
||||||
@@ -53,6 +56,7 @@ int find_output(UniValue obj, int n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||||
|
boost::optional<TransactionBuilder> builder,
|
||||||
CMutableTransaction contextualTx,
|
CMutableTransaction contextualTx,
|
||||||
std::string fromAddress,
|
std::string fromAddress,
|
||||||
std::vector<SendManyRecipient> tOutputs,
|
std::vector<SendManyRecipient> tOutputs,
|
||||||
@@ -76,6 +80,12 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "No recipients");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No recipients");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsingBuilder_ = false;
|
||||||
|
if (builder) {
|
||||||
|
isUsingBuilder_ = true;
|
||||||
|
builder_ = builder.get();
|
||||||
|
}
|
||||||
|
|
||||||
fromtaddr_ = DecodeDestination(fromAddress);
|
fromtaddr_ = DecodeDestination(fromAddress);
|
||||||
isfromtaddr_ = IsValidDestination(fromtaddr_);
|
isfromtaddr_ = IsValidDestination(fromtaddr_);
|
||||||
isfromzaddr_ = false;
|
isfromzaddr_ = false;
|
||||||
@@ -83,19 +93,14 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||||||
if (!isfromtaddr_) {
|
if (!isfromtaddr_) {
|
||||||
auto address = DecodePaymentAddress(fromAddress);
|
auto address = DecodePaymentAddress(fromAddress);
|
||||||
if (IsValidPaymentAddress(address)) {
|
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
|
// We don't need to lock on the wallet as spending key related methods are thread-safe
|
||||||
SproutSpendingKey key;
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), address)) {
|
||||||
if (!pwalletMain->GetSproutSpendingKey(addr, key)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr");
|
||||||
}
|
}
|
||||||
|
|
||||||
isfromzaddr_ = true;
|
isfromzaddr_ = true;
|
||||||
frompaymentaddress_ = addr;
|
frompaymentaddress_ = address;
|
||||||
spendingkey_ = key;
|
spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address).get();
|
||||||
} else {
|
} else {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address");
|
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.");
|
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;
|
CAmount t_inputs_total = 0;
|
||||||
for (SendManyInputUTXO & t : t_inputs_) {
|
for (SendManyInputUTXO & t : t_inputs_) {
|
||||||
t_inputs_total += std::get<2>(t);
|
t_inputs_total += std::get<2>(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAmount z_inputs_total = 0;
|
CAmount z_inputs_total = 0;
|
||||||
for (SendManyInputJSOP & t : z_inputs_) {
|
for (SendManyInputJSOP & t : z_sprout_inputs_) {
|
||||||
z_inputs_total += std::get<2>(t);
|
z_inputs_total += std::get<2>(t);
|
||||||
}
|
}
|
||||||
|
for (auto t : z_sapling_inputs_) {
|
||||||
|
z_inputs_total += t.note.value();
|
||||||
|
}
|
||||||
|
|
||||||
CAmount t_outputs_total = 0;
|
CAmount t_outputs_total = 0;
|
||||||
for (SendManyRecipient & t : t_outputs_) {
|
for (SendManyRecipient & t : t_outputs_) {
|
||||||
@@ -269,7 +280,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
|
|
||||||
if (isfromzaddr_ && (z_inputs_total < targetAmount)) {
|
if (isfromzaddr_ && (z_inputs_total < targetAmount)) {
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
|
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)));
|
FormatMoney(z_inputs_total), FormatMoney(targetAmount)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,6 +339,15 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update the transaction with these inputs
|
// update the transaction with these inputs
|
||||||
|
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_);
|
CMutableTransaction rawTx(tx_);
|
||||||
for (SendManyInputUTXO & t : t_inputs_) {
|
for (SendManyInputUTXO & t : t_inputs_) {
|
||||||
uint256 txid = std::get<0>(t);
|
uint256 txid = std::get<0>(t);
|
||||||
@@ -338,6 +358,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
}
|
}
|
||||||
tx_ = CTransaction(rawTx);
|
tx_ = CTransaction(rawTx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LogPrint((isfromtaddr_) ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
|
LogPrint((isfromtaddr_) ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
|
||||||
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee));
|
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee));
|
||||||
@@ -347,6 +368,141 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total));
|
LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total));
|
||||||
LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee));
|
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
|
// Grab the current consensus branch ID
|
||||||
{
|
{
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
@@ -395,7 +551,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
// Copy zinputs and zoutputs to more flexible containers
|
// Copy zinputs and zoutputs to more flexible containers
|
||||||
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
|
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
|
||||||
CAmount tmp = 0;
|
CAmount tmp = 0;
|
||||||
for (auto o : z_inputs_) {
|
for (auto o : z_sprout_inputs_) {
|
||||||
zInputsDeque.push_back(o);
|
zInputsDeque.push_back(o);
|
||||||
tmp += std::get<2>(o);
|
tmp += std::get<2>(o);
|
||||||
if (tmp >= targetAmount) {
|
if (tmp >= targetAmount) {
|
||||||
@@ -404,18 +560,15 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
}
|
}
|
||||||
std::deque<SendManyRecipient> zOutputsDeque;
|
std::deque<SendManyRecipient> zOutputsDeque;
|
||||||
for (auto o : z_outputs_) {
|
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);
|
zOutputsDeque.push_back(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
|
// 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
|
// 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.
|
// 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);
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
for (auto t : z_inputs_) {
|
for (auto t : z_sprout_inputs_) {
|
||||||
JSOutPoint jso = std::get<0>(t);
|
JSOutPoint jso = std::get<0>(t);
|
||||||
std::vector<JSOutPoint> vOutPoints = { jso };
|
std::vector<JSOutPoint> vOutPoints = { jso };
|
||||||
uint256 inputAnchor;
|
uint256 inputAnchor;
|
||||||
@@ -448,7 +601,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
if (selectedUTXOCoinbase) {
|
if (selectedUTXOCoinbase) {
|
||||||
assert(isSingleZaddrOutput);
|
assert(isSingleZaddrOutput);
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf(
|
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 "
|
"allow any change as there is currently no way to specify a change address "
|
||||||
"in z_sendmany.", FormatMoney(change)));
|
"in z_sendmany.", FormatMoney(change)));
|
||||||
} else {
|
} 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
|
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
|
||||||
} else {
|
} else {
|
||||||
PaymentAddress pa = DecodePaymentAddress(address);
|
PaymentAddress pa = DecodePaymentAddress(address);
|
||||||
|
// If we are here, we know we have no Sapling outputs.
|
||||||
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
|
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
|
||||||
if (hexMemo.size() > 0) {
|
if (hexMemo.size() > 0) {
|
||||||
jso.memo = get_memo_from_hex_string(hexMemo);
|
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.
|
* Sign and send a raw transaction.
|
||||||
* Raw transaction as hex string should be in object field "rawtxn"
|
* Raw transaction as hex string should be in object field "rawtxn"
|
||||||
@@ -895,10 +1046,19 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
|||||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_);
|
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) {
|
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());
|
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(),
|
getId(),
|
||||||
entry.jsop.hash.ToString().substr(0, 10),
|
entry.jsop.hash.ToString().substr(0, 10),
|
||||||
entry.jsop.js,
|
entry.jsop.js,
|
||||||
@@ -907,15 +1067,30 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
|||||||
HexStr(data).substr(0, 10)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort in descending order, so big notes appear first
|
// sort in descending order, so big notes appear first
|
||||||
std::sort(z_inputs_.begin(), z_inputs_.end(), [](SendManyInputJSOP i, SendManyInputJSOP j) -> bool {
|
std::sort(z_sprout_inputs_.begin(), z_sprout_inputs_.end(),
|
||||||
return ( std::get<2>(i) > std::get<2>(j));
|
[](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;
|
return true;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "asyncrpcoperation.h"
|
#include "asyncrpcoperation.h"
|
||||||
#include "amount.h"
|
#include "amount.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
|
#include "transaction_builder.h"
|
||||||
#include "zcash/JoinSplit.hpp"
|
#include "zcash/JoinSplit.hpp"
|
||||||
#include "zcash/Address.hpp"
|
#include "zcash/Address.hpp"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
@@ -51,7 +52,15 @@ struct WitnessAnchorData {
|
|||||||
|
|
||||||
class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
||||||
public:
|
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();
|
virtual ~AsyncRPCOperation_sendmany();
|
||||||
|
|
||||||
// We don't want to be copied or moved around
|
// 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()
|
UniValue contextinfo_; // optional data to include in return value from getStatus()
|
||||||
|
|
||||||
|
bool isUsingBuilder_; // Indicates that no Sprout addresses are involved
|
||||||
uint32_t consensusBranchId_;
|
uint32_t consensusBranchId_;
|
||||||
CAmount fee_;
|
CAmount fee_;
|
||||||
int mindepth_;
|
int mindepth_;
|
||||||
@@ -92,8 +102,10 @@ private:
|
|||||||
std::vector<SendManyRecipient> t_outputs_;
|
std::vector<SendManyRecipient> t_outputs_;
|
||||||
std::vector<SendManyRecipient> z_outputs_;
|
std::vector<SendManyRecipient> z_outputs_;
|
||||||
std::vector<SendManyInputUTXO> t_inputs_;
|
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_;
|
CTransaction tx_;
|
||||||
|
|
||||||
void add_taddr_change_output_to_tx(CAmount amount);
|
void add_taddr_change_output_to_tx(CAmount amount);
|
||||||
|
|||||||
@@ -784,41 +784,6 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp)
|
|||||||
return NullUniValue;
|
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)
|
UniValue z_exportkey(const UniValue& params, bool fHelp)
|
||||||
{
|
{
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
@@ -852,7 +817,10 @@ UniValue z_exportkey(const UniValue& params, bool fHelp)
|
|||||||
|
|
||||||
// Sapling support
|
// Sapling support
|
||||||
auto sk = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), address);
|
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)
|
UniValue z_exportviewingkey(const UniValue& params, bool fHelp)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "netbase.h"
|
#include "netbase.h"
|
||||||
#include "rpc/server.h"
|
#include "rpc/server.h"
|
||||||
#include "timedata.h"
|
#include "timedata.h"
|
||||||
|
#include "transaction_builder.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "utilmoneystr.h"
|
#include "utilmoneystr.h"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
@@ -3609,26 +3610,26 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||||||
// Check that the from address is valid.
|
// Check that the from address is valid.
|
||||||
auto fromaddress = params[0].get_str();
|
auto fromaddress = params[0].get_str();
|
||||||
bool fromTaddr = false;
|
bool fromTaddr = false;
|
||||||
|
bool fromSapling = false;
|
||||||
CTxDestination taddr = DecodeDestination(fromaddress);
|
CTxDestination taddr = DecodeDestination(fromaddress);
|
||||||
fromTaddr = IsValidDestination(taddr);
|
fromTaddr = IsValidDestination(taddr);
|
||||||
libzcash::SproutPaymentAddress zaddr;
|
|
||||||
if (!fromTaddr) {
|
if (!fromTaddr) {
|
||||||
auto res = DecodePaymentAddress(fromaddress);
|
auto res = DecodePaymentAddress(fromaddress);
|
||||||
if (!IsValidPaymentAddress(res)) {
|
if (!IsValidPaymentAddress(res)) {
|
||||||
// invalid
|
// invalid
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr.");
|
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
|
// Check that we have the spending key
|
||||||
if (!fromTaddr) {
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) {
|
||||||
if (!pwalletMain->HaveSproutSpendingKey(zaddr)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found.");
|
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();
|
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
|
// Keep track of addresses to spot duplicates
|
||||||
set<std::string> setAddress;
|
set<std::string> setAddress;
|
||||||
|
|
||||||
|
// Track whether we see any Sprout addresses
|
||||||
|
bool noSproutAddrs = !fromSprout;
|
||||||
|
|
||||||
// Recipients
|
// Recipients
|
||||||
std::vector<SendManyRecipient> taddrRecipients;
|
std::vector<SendManyRecipient> taddrRecipients;
|
||||||
std::vector<SendManyRecipient> zaddrRecipients;
|
std::vector<SendManyRecipient> zaddrRecipients;
|
||||||
@@ -3658,8 +3662,26 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||||||
bool isZaddr = false;
|
bool isZaddr = false;
|
||||||
CTxDestination taddr = DecodeDestination(address);
|
CTxDestination taddr = DecodeDestination(address);
|
||||||
if (!IsValidDestination(taddr)) {
|
if (!IsValidDestination(taddr)) {
|
||||||
if (IsValidPaymentAddressString(address)) {
|
auto res = DecodePaymentAddress(address);
|
||||||
|
if (IsValidPaymentAddress(res)) {
|
||||||
isZaddr = true;
|
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 {
|
} else {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address );
|
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address );
|
||||||
}
|
}
|
||||||
@@ -3787,7 +3809,14 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||||||
o.push_back(Pair("fee", std::stod(FormatMoney(nFee))));
|
o.push_back(Pair("fee", std::stod(FormatMoney(nFee))));
|
||||||
UniValue contextInfo = o;
|
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
|
// Contextual transaction we will build on
|
||||||
|
// (used if no Sapling addresses are involved)
|
||||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
|
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight);
|
||||||
bool isShielded = !fromTaddr || zaddrRecipients.size() > 0;
|
bool isShielded = !fromTaddr || zaddrRecipients.size() > 0;
|
||||||
if (contextualTx.nVersion == 1 && isShielded) {
|
if (contextualTx.nVersion == 1 && isShielded) {
|
||||||
@@ -3796,7 +3825,7 @@ UniValue z_sendmany(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();
|
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);
|
q->addOperation(operation);
|
||||||
AsyncRPCOperationId operationId = operation->getId();
|
AsyncRPCOperationId operationId = operation->getId();
|
||||||
return operationId;
|
return operationId;
|
||||||
|
|||||||
@@ -4373,3 +4373,57 @@ bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding&
|
|||||||
{
|
{
|
||||||
return false;
|
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;
|
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
|
#endif // BITCOIN_WALLET_WALLET_H
|
||||||
|
|||||||
Reference in New Issue
Block a user