Add Sapling support to z_mergetoaddress

This commit is contained in:
Eirik Ogilvie-Wigley 2018-10-18 15:37:58 -06:00
parent e0c491c073
commit 487c9020bd
7 changed files with 404 additions and 177 deletions

View File

@ -26,6 +26,16 @@ def assert_mergetoaddress_exception(expected_error_msg, merge_to_address_lambda)
class MergeToAddressHelper:
def __init__(self, addr_type, any_zaddr, utxos_to_generate, utxos_in_tx1, utxos_in_tx2, test_mempooltxinputlimit):
self.addr_type = addr_type
self.any_zaddr = [any_zaddr]
self.any_zaddr_or_utxo = [any_zaddr, "ANY_TADDR"]
# utxos_to_generate, utxos_in_tx1, utxos_in_tx2 have to do with testing transaction size limits
self.utxos_to_generate = utxos_to_generate
self.utxos_in_tx1 = utxos_in_tx1
self.utxos_in_tx2 = utxos_in_tx2
self.test_mempooltxinputlimit = test_mempooltxinputlimit
def setup_chain(self, test):
print("Initializing test directory "+test.options.tmpdir)
initialize_chain_clean(test.options.tmpdir, 4)
@ -45,7 +55,7 @@ class MergeToAddressHelper:
test.is_network_split = False
test.sync_all()
def run_test(self, test, addr_type):
def run_test(self, test):
print "Mining blocks..."
test.nodes[0].generate(1)
@ -69,7 +79,7 @@ class MergeToAddressHelper:
assert_equal(test.nodes[2].getbalance(), 30)
# Shield the coinbase
myzaddr = test.nodes[0].z_getnewaddress(addr_type)
myzaddr = test.nodes[0].z_getnewaddress(self.addr_type)
result = test.nodes[0].z_shieldcoinbase("*", myzaddr, 0)
wait_and_assert_operationid_status(test.nodes[0], result['opid'])
test.sync_all()
@ -94,7 +104,7 @@ class MergeToAddressHelper:
# Merging will fail because from arguments need to be in an array
assert_mergetoaddress_exception(
"JSON value is not an array as expected",
lambda: test.nodes[0].z_mergetoaddress("*", myzaddr))
lambda: test.nodes[0].z_mergetoaddress("notanarray", myzaddr))
# Merging will fail when trying to spend from watch-only address
test.nodes[2].importaddress(mytaddr)
@ -105,37 +115,37 @@ class MergeToAddressHelper:
# Merging will fail because fee is negative
assert_mergetoaddress_exception(
"Amount out of range",
lambda: test.nodes[0].z_mergetoaddress(["*"], myzaddr, -1))
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, -1))
# Merging will fail because fee is larger than MAX_MONEY
assert_mergetoaddress_exception(
"Amount out of range",
lambda: test.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('21000000.00000001')))
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, Decimal('21000000.00000001')))
# Merging will fail because fee is larger than sum of UTXOs
assert_mergetoaddress_exception(
"Insufficient funds, have 50.00, which is less than miners fee 999.00",
lambda: test.nodes[0].z_mergetoaddress(["*"], myzaddr, 999))
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, 999))
# Merging will fail because transparent limit parameter must be at least 0
assert_mergetoaddress_exception(
"Limit on maximum number of UTXOs cannot be negative",
lambda: test.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), -1))
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, Decimal('0.001'), -1))
# Merging will fail because transparent limit parameter is absurdly large
assert_mergetoaddress_exception(
"JSON integer out of range",
lambda: test.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 99999999999999))
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, Decimal('0.001'), 99999999999999))
# Merging will fail because shielded limit parameter must be at least 0
assert_mergetoaddress_exception(
"Limit on maximum number of notes cannot be negative",
lambda: test.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, -1))
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, Decimal('0.001'), 50, -1))
# Merging will fail because shielded limit parameter is absurdly large
assert_mergetoaddress_exception(
"JSON integer out of range",
lambda: test.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, 99999999999999))
lambda: test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, Decimal('0.001'), 50, 99999999999999))
# Merging will fail for this specific case where it would spend a fee and do nothing
assert_mergetoaddress_exception(
@ -157,8 +167,8 @@ class MergeToAddressHelper:
assert_equal(test.nodes[2].getbalance(), 30)
# Shield all notes to another z-addr
myzaddr2 = test.nodes[0].z_getnewaddress(addr_type)
result = test.nodes[0].z_mergetoaddress(["ANY_ZADDR"], myzaddr2, 0)
myzaddr2 = test.nodes[0].z_getnewaddress(self.addr_type)
result = test.nodes[0].z_mergetoaddress(self.any_zaddr, myzaddr2, 0)
assert_equal(result["mergingUTXOs"], Decimal('0'))
assert_equal(result["remainingUTXOs"], Decimal('0'))
assert_equal(result["mergingNotes"], Decimal('2'))
@ -186,7 +196,7 @@ class MergeToAddressHelper:
assert_equal(test.nodes[2].getbalance(), 0)
# Merge all notes from node 0 into a node 0 taddr, and set fee to 0
result = test.nodes[0].z_mergetoaddress(["ANY_ZADDR"], mytaddr, 0)
result = test.nodes[0].z_mergetoaddress(self.any_zaddr, mytaddr, 0)
wait_and_assert_operationid_status(test.nodes[0], result['opid'])
test.sync_all()
test.nodes[1].generate(1)
@ -217,46 +227,44 @@ class MergeToAddressHelper:
assert_equal(test.nodes[1].z_getbalance(n1taddr), Decimal('79.99990000'))
assert_equal(test.nodes[2].getbalance(), 0)
# Generate 800 regular UTXOs on node 0, and 20 regular UTXOs on node 2
# Generate self.utxos_to_generate regular UTXOs on node 0, and 20 regular UTXOs on node 2
mytaddr = test.nodes[0].getnewaddress()
n2taddr = test.nodes[2].getnewaddress()
test.nodes[1].generate(1000)
test.sync_all()
for i in range(800):
for i in range(self.utxos_to_generate):
test.nodes[1].sendtoaddress(mytaddr, 1)
for i in range(20):
test.nodes[1].sendtoaddress(n2taddr, 1)
test.nodes[1].generate(1)
test.sync_all()
# Merging the 800 UTXOs will occur over two transactions, since max tx size is 100,000 bytes.
# Merging the UTXOs will conditionally occur over two transactions, since max tx size is 100,000 bytes before Sapling and 2,000,000 after.
# We don't verify mergingTransparentValue as UTXOs are not selected in any specific order, so value can change on each test run.
# We set an unrealistically high limit parameter of 99999, to verify that max tx size will constrain the number of UTXOs.
result = test.nodes[0].z_mergetoaddress([mytaddr], myzaddr, 0, 99999)
assert_equal(result["mergingUTXOs"], Decimal('662'))
assert_equal(result["remainingUTXOs"], Decimal('138'))
assert_equal(result["mergingUTXOs"], self.utxos_in_tx1)
assert_equal(result["remainingUTXOs"], self.utxos_in_tx2)
assert_equal(result["mergingNotes"], Decimal('0'))
assert_equal(result["mergingShieldedValue"], Decimal('0'))
assert_equal(result["remainingNotes"], Decimal('0'))
assert_equal(result["remainingShieldedValue"], Decimal('0'))
remainingTransparentValue = result["remainingTransparentValue"]
opid1 = result['opid']
wait_and_assert_operationid_status(test.nodes[0], result['opid'])
# Verify that UTXOs are locked (not available for selection) by queuing up another merging operation
result = test.nodes[0].z_mergetoaddress([mytaddr], myzaddr, 0, 0)
assert_equal(result["mergingUTXOs"], Decimal('138'))
assert_equal(result["mergingTransparentValue"], Decimal(remainingTransparentValue))
assert_equal(result["remainingUTXOs"], Decimal('0'))
assert_equal(result["remainingTransparentValue"], Decimal('0'))
assert_equal(result["mergingNotes"], Decimal('0'))
assert_equal(result["mergingShieldedValue"], Decimal('0'))
assert_equal(result["remainingNotes"], Decimal('0'))
assert_equal(result["remainingShieldedValue"], Decimal('0'))
opid2 = result['opid']
# wait for both aysnc operations to complete
wait_and_assert_operationid_status(test.nodes[0], opid1)
wait_and_assert_operationid_status(test.nodes[0], opid2)
# For sapling we do not check that this occurs over two transactions because of the time that it would take
if self.utxos_in_tx2 > 0:
# Verify that UTXOs are locked (not available for selection) by queuing up another merging operation
result = test.nodes[0].z_mergetoaddress([mytaddr], myzaddr, 0, 0)
assert_equal(result["mergingUTXOs"], self.utxos_in_tx2)
assert_equal(result["mergingTransparentValue"], Decimal(remainingTransparentValue))
assert_equal(result["remainingUTXOs"], Decimal('0'))
assert_equal(result["remainingTransparentValue"], Decimal('0'))
assert_equal(result["mergingNotes"], Decimal('0'))
assert_equal(result["mergingShieldedValue"], Decimal('0'))
assert_equal(result["remainingNotes"], Decimal('0'))
assert_equal(result["remainingShieldedValue"], Decimal('0'))
wait_and_assert_operationid_status(test.nodes[0], result['opid'])
# sync_all() invokes sync_mempool() but node 2's mempool limit will cause tx1 and tx2 to be rejected.
# So instead, we sync on blocks and mempool for node 0 and node 1, and after a new block is generated
@ -270,9 +278,18 @@ class MergeToAddressHelper:
# Verify maximum number of UTXOs which node 2 can shield is limited by option -mempooltxinputlimit
# This option is used when the limit parameter is set to 0.
# -mempooltxinputlimit is not used after overwinter activation
if self.test_mempooltxinputlimit:
expected_to_merge = 7
expected_remaining = 13
else:
expected_to_merge = 20
expected_remaining = 0
result = test.nodes[2].z_mergetoaddress([n2taddr], myzaddr, Decimal('0.0001'), 0)
assert_equal(result["mergingUTXOs"], Decimal('7'))
assert_equal(result["remainingUTXOs"], Decimal('13'))
assert_equal(result["mergingUTXOs"], expected_to_merge)
assert_equal(result["remainingUTXOs"], expected_remaining)
assert_equal(result["mergingNotes"], Decimal('0'))
assert_equal(result["remainingNotes"], Decimal('0'))
wait_and_assert_operationid_status(test.nodes[2], result['opid'])
@ -311,7 +328,8 @@ class MergeToAddressHelper:
# Verify maximum number of notes which node 0 can shield can be set by the limit parameter
# Also check that we can set off a second merge before the first one is complete
# myzaddr has 5 notes at this point
# myzaddr will have 5 notes if testing before to Sapling activation and 4 otherwise
num_notes = len(test.nodes[0].z_listunspent(0))
result1 = test.nodes[0].z_mergetoaddress([myzaddr], myzaddr, 0.0001, 50, 2)
result2 = test.nodes[0].z_mergetoaddress([myzaddr], myzaddr, 0.0001, 50, 2)
@ -320,13 +338,13 @@ class MergeToAddressHelper:
# Remaining UTXOs are only counted if we are trying to merge any UTXOs
assert_equal(result1["remainingUTXOs"], Decimal('0'))
assert_equal(result1["mergingNotes"], Decimal('2'))
assert_equal(result1["remainingNotes"], Decimal('3'))
assert_equal(result1["remainingNotes"], num_notes - 2)
# Second merge should ignore locked notes
assert_equal(result2["mergingUTXOs"], Decimal('0'))
assert_equal(result2["remainingUTXOs"], Decimal('0'))
assert_equal(result2["mergingNotes"], Decimal('2'))
assert_equal(result2["remainingNotes"], Decimal('1'))
assert_equal(result2["remainingNotes"], num_notes - 4)
wait_and_assert_operationid_status(test.nodes[0], result1['opid'])
wait_and_assert_operationid_status(test.nodes[0], result2['opid'])
@ -335,7 +353,7 @@ class MergeToAddressHelper:
test.sync_all()
# Shield both UTXOs and notes to a z-addr
result = test.nodes[0].z_mergetoaddress(["*"], myzaddr, 0, 10, 2)
result = test.nodes[0].z_mergetoaddress(self.any_zaddr_or_utxo, myzaddr, 0, 10, 2)
assert_equal(result["mergingUTXOs"], Decimal('10'))
assert_equal(result["remainingUTXOs"], Decimal('7'))
assert_equal(result["mergingNotes"], Decimal('2'))

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python2
# Copyright (c) 2017 The Zcash developers
# 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.
@ -8,7 +8,9 @@ from mergetoaddress_helper import MergeToAddressHelper
class MergeToAddressSapling (BitcoinTestFramework):
helper = MergeToAddressHelper()
# 13505 would be the maximum number of utxos based on the transaction size limits for Sapling
# but testing this causes the test to take an indeterminately long time to run.
helper = MergeToAddressHelper('sapling', 'ANY_SAPLING', 800, 800, 0, False)
def setup_chain(self):
self.helper.setup_chain(self)
@ -20,7 +22,7 @@ class MergeToAddressSapling (BitcoinTestFramework):
])
def run_test(self):
self.helper.run_test(self, 'sapling')
self.helper.run_test(self)
if __name__ == '__main__':

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python2
# Copyright (c) 2017 The Zcash developers
# 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.
@ -8,7 +8,7 @@ from mergetoaddress_helper import MergeToAddressHelper
class MergeToAddressSprout (BitcoinTestFramework):
helper = MergeToAddressHelper()
helper = MergeToAddressHelper('sprout', 'ANY_SPROUT', 800, 662, 138, True)
def setup_chain(self):
self.helper.setup_chain(self)
@ -17,7 +17,7 @@ class MergeToAddressSprout (BitcoinTestFramework):
self.helper.setup_network(self)
def run_test(self):
self.helper.run_test(self, 'sprout')
self.helper.run_test(self)
if __name__ == '__main__':

View File

@ -1765,33 +1765,53 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_parameters)
"mainnet memo");
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(mtx, {}, {}, testnetzaddr, -1 ));
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(boost::none, mtx, {}, {}, {}, testnetzaddr, -1 ));
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Fee is out of range"));
}
try {
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(mtx, {}, {}, testnetzaddr, 1));
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(boost::none, mtx, {}, {}, {}, testnetzaddr, 1));
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "No inputs"));
}
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{ COutPoint{uint256(), 0}, 0} };
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{ COutPoint{uint256(), 0}, 0, CScript()} };
try {
MergeToAddressRecipient badaddr("", "memo");
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, badaddr, 1));
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, {}, {}, badaddr, 1));
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Recipient parameter missing"));
}
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs =
{MergeToAddressInputSproutNote{JSOutPoint(), SproutNote(), 0, SproutSpendingKey()}};
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs =
{MergeToAddressInputSaplingNote{SaplingOutPoint(), SaplingNote(), 0, SaplingExpandedSpendingKey()}};
// Sprout and Sapling inputs -> throw
try {
auto operation = new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, sproutNoteInputs, saplingNoteInputs, testnetzaddr, 1);
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK(find_error(objError, "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress"));
}
// Sprout inputs and TransactionBuilder -> throw
try {
auto operation = new AsyncRPCOperation_mergetoaddress(TransactionBuilder(), mtx, inputs, sproutNoteInputs, {}, testnetzaddr, 1);
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK(find_error(objError, "Sprout notes are not supported by the TransactionBuilder"));
}
// Testnet payment addresses begin with 'zt'. This test detects an incorrect prefix.
try {
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{ COutPoint{uint256(), 0}, 0} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, mainnetzaddr, 1) );
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{ COutPoint{uint256(), 0}, 0, CScript()} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, {}, {}, mainnetzaddr, 1) );
BOOST_FAIL("Should have caused an error");
} catch (const UniValue& objError) {
BOOST_CHECK( find_error(objError, "Invalid recipient address"));
@ -1827,10 +1847,10 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals)
// Supply 2 inputs when mempool limit is 1
{
std::vector<MergeToAddressInputUTXO> inputs = {
MergeToAddressInputUTXO{COutPoint{uint256(),0},0},
MergeToAddressInputUTXO{COutPoint{uint256(),0},0}
MergeToAddressInputUTXO{COutPoint{uint256(),0},0, CScript()},
MergeToAddressInputUTXO{COutPoint{uint256(),0},0, CScript()}
};
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, {}, {}, zaddr1) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
@ -1839,8 +1859,8 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals)
// Insufficient funds
{
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},0} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},0, CScript()} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, {}, {}, zaddr1) );
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
@ -1849,8 +1869,8 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals)
// get_memo_from_hex_string())
{
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000, CScript()} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, {}, {}, zaddr1) );
std::shared_ptr<AsyncRPCOperation_mergetoaddress> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_mergetoaddress> (operation);
TEST_FRIEND_AsyncRPCOperation_mergetoaddress proxy(ptr);
@ -1903,8 +1923,8 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals)
// Test the perform_joinsplit methods.
{
// Dummy input so the operation object can be instantiated.
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000, CScript()} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, {}, {}, zaddr1) );
std::shared_ptr<AsyncRPCOperation_mergetoaddress> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_mergetoaddress> (operation);
TEST_FRIEND_AsyncRPCOperation_mergetoaddress proxy(ptr);
@ -1964,8 +1984,8 @@ BOOST_AUTO_TEST_CASE(rpc_z_mergetoaddress_internals)
obj.push_back(Pair("rawtxn", raw));
// we have the spending key for the dummy recipient zaddr1
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(mtx, inputs, {}, zaddr1) );
std::vector<MergeToAddressInputUTXO> inputs = { MergeToAddressInputUTXO{COutPoint{uint256(),0},100000, CScript()} };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_mergetoaddress(boost::none, mtx, inputs, {}, {}, zaddr1) );
std::shared_ptr<AsyncRPCOperation_mergetoaddress> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_mergetoaddress> (operation);
TEST_FRIEND_AsyncRPCOperation_mergetoaddress proxy(ptr);

View File

@ -34,6 +34,8 @@
using namespace libzcash;
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
int mta_find_output(UniValue obj, int n)
{
UniValue outputMapValue = find_value(obj, "outputmap");
@ -53,20 +55,22 @@ int mta_find_output(UniValue obj, int n)
}
AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputNote> noteInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee,
UniValue contextInfo) :
tx_(contextualTx), utxoInputs_(utxoInputs), noteInputs_(noteInputs),
recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
tx_(contextualTx), utxoInputs_(utxoInputs), sproutNoteInputs_(sproutNoteInputs),
saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
{
if (fee < 0 || fee > MAX_MONEY) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range");
}
if (utxoInputs.empty() && noteInputs.empty()) {
if (utxoInputs.empty() && sproutNoteInputs.empty() && saplingNoteInputs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs");
}
@ -74,6 +78,20 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing");
}
if (sproutNoteInputs.size() > 0 && saplingNoteInputs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
if (sproutNoteInputs.size() > 0 && builder) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Sprout notes are not supported by the TransactionBuilder");
}
isUsingBuilder_ = false;
if (builder) {
isUsingBuilder_ = true;
builder_ = builder.get();
}
toTaddr_ = DecodeDestination(std::get<0>(recipient));
isToTaddr_ = IsValidDestination(toTaddr_);
isToZaddr_ = false;
@ -82,10 +100,6 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
auto address = DecodePaymentAddress(std::get<0>(recipient));
if (IsValidPaymentAddress(address)) {
isToZaddr_ = true;
// TODO: Add Sapling support. For now, return an error to the user.
if (boost::get<libzcash::SproutPaymentAddress>(&address) == nullptr) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout zaddrs are supported");
}
toPaymentAddress_ = address;
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid recipient address");
@ -203,7 +217,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
{
assert(isToTaddr_ != isToZaddr_);
bool isPureTaddrOnlyTx = (noteInputs_.empty() && isToTaddr_);
bool isPureTaddrOnlyTx = (sproutNoteInputs_.empty() && saplingNoteInputs_.empty() && isToTaddr_);
CAmount minersFee = fee_;
size_t numInputs = utxoInputs_.size();
@ -228,7 +242,11 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
}
CAmount z_inputs_total = 0;
for (MergeToAddressInputNote& t : noteInputs_) {
for (const MergeToAddressInputSproutNote& t : sproutNoteInputs_) {
z_inputs_total += std::get<2>(t);
}
for (const MergeToAddressInputSaplingNote& t : saplingNoteInputs_) {
z_inputs_total += std::get<2>(t);
}
@ -243,17 +261,19 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
CAmount sendAmount = targetAmount - minersFee;
// update the transaction with the UTXO inputs and output (if any)
CMutableTransaction rawTx(tx_);
for (MergeToAddressInputUTXO& t : utxoInputs_) {
CTxIn in(std::get<0>(t));
rawTx.vin.push_back(in);
if (!isUsingBuilder_) {
CMutableTransaction rawTx(tx_);
for (const MergeToAddressInputUTXO& t : utxoInputs_) {
CTxIn in(std::get<0>(t));
rawTx.vin.push_back(in);
}
if (isToTaddr_) {
CScript scriptPubKey = GetScriptForDestination(toTaddr_);
CTxOut out(sendAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
tx_ = CTransaction(rawTx);
}
if (isToTaddr_) {
CScript scriptPubKey = GetScriptForDestination(toTaddr_);
CTxOut out(sendAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
tx_ = CTransaction(rawTx);
LogPrint(isPureTaddrOnlyTx ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee));
@ -272,6 +292,127 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
}
/**
* SCENARIO #0
*
* Sprout not involved, so we just use the TransactionBuilder and we're done.
*
* This is based on code from AsyncRPCOperation_sendmany::main_impl() and should be refactored.
*/
if (isUsingBuilder_) {
builder_.SetFee(minersFee);
for (const MergeToAddressInputUTXO& t : utxoInputs_) {
COutPoint outPoint = std::get<0>(t);
CAmount amount = std::get<1>(t);
CScript scriptPubKey = std::get<2>(t);
builder_.AddTransparentInput(outPoint, scriptPubKey, amount);
}
boost::optional<uint256> ovk;
// Select Sapling notes
std::vector<SaplingOutPoint> saplingOPs;
std::vector<SaplingNote> saplingNotes;
std::vector<SaplingExpandedSpendingKey> expsks;
for (const MergeToAddressInputSaplingNote& saplingNoteInput: saplingNoteInputs_) {
saplingOPs.push_back(std::get<0>(saplingNoteInput));
saplingNotes.push_back(std::get<1>(saplingNoteInput));
auto expsk = std::get<3>(saplingNoteInput);
expsks.push_back(expsk);
if (!ovk) {
ovk = expsk.full_viewing_key().ovk;
}
}
// Fetch Sapling anchor and witnesses
uint256 anchor;
std::vector<boost::optional<SaplingWitness>> witnesses;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetSaplingNoteWitnesses(saplingOPs, witnesses, anchor);
}
// Add Sapling spends
for (size_t i = 0; i < saplingNotes.size(); i++) {
if (!witnesses[i]) {
throw JSONRPCError(RPC_WALLET_ERROR, "Missing witness for Sapling note");
}
assert(builder_.AddSaplingSpend(expsks[i], saplingNotes[i], anchor, witnesses[i].get()));
}
if (isToTaddr_) {
if (!builder_.AddTransparentOutput(toTaddr_, sendAmount)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr.");
}
} else {
std::string zaddr = std::get<0>(recipient_);
std::string memo = std::get<1>(recipient_);
std::array<unsigned char, ZC_MEMO_SIZE> hexMemo = get_memo_from_hex_string(memo);
auto saplingPaymentAddress = boost::get<libzcash::SaplingPaymentAddress>(&toPaymentAddress_);
if (saplingPaymentAddress == nullptr) {
// This should never happen as we have already determined that the payment is to sapling
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not get Sapling payment address.");
}
if (saplingNoteInputs_.size() == 0 && utxoInputs_.size() > 0) {
// Sending from t-addresses, which we don't have ovks for. Instead,
// generate a common one from the HD seed. This ensures the data is
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"AsyncRPCOperation_sendmany: HD seed not found");
}
ovk = ovkForShieldingFromTaddr(seed);
}
if (!ovk) {
throw JSONRPCError(RPC_WALLET_ERROR, "Sending to a Sapling address requires an ovk.");
}
builder_.AddSaplingOutput(ovk.get(), *saplingPaymentAddress, sendAmount, hexMemo);
}
// Build the transaction
auto maybe_tx = builder_.Build();
if (!maybe_tx) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction.");
}
tx_ = maybe_tx.get();
// Send the transaction
// TODO: Use CWallet::CommitTransaction instead of sendrawtransaction
auto signedtxn = EncodeHexTx(tx_);
if (!testmode) {
UniValue params = UniValue(UniValue::VARR);
params.push_back(signedtxn);
UniValue sendResultValue = sendrawtransaction(params, false);
if (sendResultValue.isNull()) {
throw JSONRPCError(RPC_WALLET_ERROR, "sendrawtransaction did not return an error or a txid.");
}
auto txid = sendResultValue.get_str();
UniValue o(UniValue::VOBJ);
o.push_back(Pair("txid", txid));
set_result(o);
} else {
// Test mode does not send the transaction to the network.
UniValue o(UniValue::VOBJ);
o.push_back(Pair("test", 1));
o.push_back(Pair("txid", tx_.GetHash().ToString()));
o.push_back(Pair("hex", signedtxn));
set_result(o);
}
return true;
}
/**
* END SCENARIO #0
*/
/**
* SCENARIO #1
*
@ -305,7 +446,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
*
* We only need a single JoinSplit.
*/
if (noteInputs_.empty() && isToZaddr_) {
if (sproutNoteInputs_.empty() && isToZaddr_) {
// Create JoinSplit to target z-addr.
MergeToAddressJSInfo info;
info.vpub_old = sendAmount;
@ -328,12 +469,8 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// Copy zinputs to more flexible containers
std::deque<MergeToAddressInputNote> zInputsDeque;
for (auto o : noteInputs_) {
// TODO: Add Sapling support. For now, return an error to the user.
if (boost::get<libzcash::SproutSpendingKey>(&std::get<3>(o)) == nullptr) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Currently, only Sprout zaddrs are supported");
}
std::deque<MergeToAddressInputSproutNote> zInputsDeque;
for (const auto& o : sproutNoteInputs_) {
zInputsDeque.push_back(o);
}
@ -342,7 +479,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
{
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto t : noteInputs_) {
for (auto t : sproutNoteInputs_) {
JSOutPoint jso = std::get<0>(t);
std::vector<JSOutPoint> vOutPoints = {jso};
uint256 inputAnchor;
@ -373,7 +510,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// At this point, we are guaranteed to have at least one input note.
// Use address of first input note as the temporary change address.
SproutSpendingKey changeKey = boost::get<libzcash::SproutSpendingKey>(std::get<3>(zInputsDeque.front()));
SproutSpendingKey changeKey = std::get<3>(zInputsDeque.front());
SproutPaymentAddress changeAddress = changeKey.address();
CAmount vpubOldTarget = 0;
@ -495,11 +632,11 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
uint256 inputAnchor;
int numInputsNeeded = (jsChange > 0) ? 1 : 0;
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
MergeToAddressInputNote t = zInputsDeque.front();
MergeToAddressInputSproutNote t = zInputsDeque.front();
JSOutPoint jso = std::get<0>(t);
SproutNote note = std::get<1>(t);
CAmount noteFunds = std::get<2>(t);
SproutSpendingKey zkey = boost::get<libzcash::SproutSpendingKey>(std::get<3>(t));
SproutSpendingKey zkey = std::get<3>(t);
zInputsDeque.pop_front();
MergeToAddressWitnessAnchorData wad = jsopWitnessAnchorMap[jso.ToString()];
@ -628,7 +765,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp);
/**
* Sign and send a raw transaction.
@ -947,7 +1083,8 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
*/
void AsyncRPCOperation_mergetoaddress::lock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : noteInputs_) {
// TODO Sapling
for (auto note : sproutNoteInputs_) {
pwalletMain->LockNote(std::get<0>(note));
}
}
@ -957,7 +1094,8 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
*/
void AsyncRPCOperation_mergetoaddress::unlock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : noteInputs_) {
// TODO Sapling
for (auto note : sproutNoteInputs_) {
pwalletMain->UnlockNote(std::get<0>(note));
}
}

View File

@ -9,6 +9,7 @@
#include "asyncrpcoperation.h"
#include "paymentdisclosure.h"
#include "primitives/transaction.h"
#include "transaction_builder.h"
#include "wallet.h"
#include "zcash/Address.hpp"
#include "zcash/JoinSplit.hpp"
@ -24,11 +25,13 @@
using namespace libzcash;
// Input UTXO is a tuple of txid, vout, amount
typedef std::tuple<COutPoint, CAmount> MergeToAddressInputUTXO;
// Input UTXO is a tuple of txid, vout, amount, script
typedef std::tuple<COutPoint, CAmount, CScript> MergeToAddressInputUTXO;
// Input JSOP is a tuple of JSOutpoint, note, amount, spending key
typedef std::tuple<JSOutPoint, SproutNote, CAmount, SpendingKey> MergeToAddressInputNote;
typedef std::tuple<JSOutPoint, SproutNote, CAmount, SproutSpendingKey> MergeToAddressInputSproutNote;
typedef std::tuple<SaplingOutPoint, SaplingNote, CAmount, SaplingExpandedSpendingKey> MergeToAddressInputSaplingNote;
// A recipient is a tuple of address, memo (optional if zaddr)
typedef std::tuple<std::string, std::string> MergeToAddressRecipient;
@ -53,9 +56,11 @@ class AsyncRPCOperation_mergetoaddress : public AsyncRPCOperation
{
public:
AsyncRPCOperation_mergetoaddress(
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputNote> noteInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE,
UniValue contextInfo = NullUniValue);
@ -79,7 +84,8 @@ private:
friend class TEST_FRIEND_AsyncRPCOperation_mergetoaddress; // class for unit testing
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_;
@ -96,8 +102,10 @@ private:
std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap;
std::vector<MergeToAddressInputUTXO> utxoInputs_;
std::vector<MergeToAddressInputNote> noteInputs_;
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs_;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs_;
TransactionBuilder builder_;
CTransaction tx_;
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);

View File

@ -4130,9 +4130,12 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
#define MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT 50
#define MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT 10
#define MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT 20
#define MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT 200
#define JOINSPLIT_SIZE GetSerializeSize(JSDescription(), SER_NETWORK, PROTOCOL_VERSION)
#define OUTPUTDESCRIPTION_SIZE GetSerializeSize(OutputDescription(), SER_NETWORK, PROTOCOL_VERSION)
#define SPENDDESCRIPTION_SIZE GetSerializeSize(SpendDescription(), SER_NETWORK, PROTOCOL_VERSION)
UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
{
@ -4162,9 +4165,9 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
"\nArguments:\n"
"1. fromaddresses (string, required) A JSON array with addresses.\n"
" The following special strings are accepted inside the array:\n"
" - \"*\": Merge both UTXOs and notes from all addresses belonging to the wallet.\n"
" - \"ANY_TADDR\": Merge UTXOs from all t-addrs belonging to the wallet.\n"
" - \"ANY_ZADDR\": Merge notes from all z-addrs belonging to the wallet.\n"
" - \"ANY_TADDR\": Merge UTXOs from any t-addrs belonging to the wallet.\n"
" - \"ANY_SPROUT\": Merge notes from any Sprout z-addrs belonging to the wallet.\n"
" - \"ANY_SAPLING\": Merge notes from any Sapling z-addrs belonging to the wallet.\n"
" If a special string is given, any given addresses of that type will be ignored.\n"
" [\n"
" \"address\" (string) Can be a t-addr or a z-addr\n"
@ -4176,7 +4179,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
"4. transparent_limit (numeric, optional, default="
+ strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT) + ") Limit on the maximum number of UTXOs to merge. Set to 0 to use node option -mempooltxinputlimit (before Overwinter), or as many as will fit in the transaction (after Overwinter).\n"
"4. shielded_limit (numeric, optional, default="
+ strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT) + ") Limit on the maximum number of notes to merge. Set to 0 to merge as many as will fit in the transaction.\n"
+ strprintf("%d Sprout or %d Sapling Notes", MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT, MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT) + ") Limit on the maximum number of notes to merge. Set to 0 to merge as many as will fit in the transaction.\n"
"5. \"memo\" (string, optional) Encoded as hex. When toaddress is a z-addr, this will be stored in the memo field of the new note.\n"
"\nResult:\n"
"{\n"
@ -4201,9 +4204,9 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet);
bool useAny = false;
bool useAnyUTXO = false;
bool useAnyNote = false;
bool useAnySprout = false;
bool useAnySapling = false;
std::set<CTxDestination> taddrs = {};
std::set<libzcash::PaymentAddress> zaddrs = {};
@ -4215,39 +4218,28 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
std::set<std::string> setAddress;
// Sources
bool containsSaplingZaddrSource = false;
for (const UniValue& o : addresses.getValues()) {
if (!o.isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected string");
std::string address = o.get_str();
if (address == "*") {
useAny = true;
} else if (address == "ANY_TADDR") {
if (address == "ANY_TADDR") {
useAnyUTXO = true;
} else if (address == "ANY_ZADDR") {
useAnyNote = true;
} else if (address == "ANY_SPROUT") {
useAnySprout = true;
} else if (address == "ANY_SAPLING") {
useAnySapling = true;
} else {
CTxDestination taddr = DecodeDestination(address);
if (IsValidDestination(taddr)) {
// Ignore any listed t-addrs if we are using all of them
if (!(useAny || useAnyUTXO)) {
taddrs.insert(taddr);
}
taddrs.insert(taddr);
} else {
auto zaddr = DecodePaymentAddress(address);
if (IsValidPaymentAddress(zaddr)) {
// Ignore listed z-addrs if we are using all of them
if (!(useAny || useAnyNote)) {
zaddrs.insert(zaddr);
}
// Check if z-addr is Sapling
bool isSapling = boost::get<libzcash::SaplingPaymentAddress>(&zaddr) != nullptr;
containsSaplingZaddrSource |= isSapling;
zaddrs.insert(zaddr);
} else {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
string("Invalid parameter, unknown address format: ") + address);
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unknown address format: ") + address);
}
}
}
@ -4257,21 +4249,33 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
setAddress.insert(address);
}
if (useAnyUTXO && taddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific t-addrs when using \"ANY_TADDR\"");
}
if ((useAnySprout || useAnySapling) && zaddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific z-addrs when using \"ANY_SPROUT\" or \"ANY_SAPLING\"");
}
const int nextBlockHeight = chainActive.Height() + 1;
const bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
const bool saplingActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING);
// Validate the destination address
auto destaddress = params[1].get_str();
bool isToZaddr = false;
bool isToSproutZaddr = false;
bool isToSaplingZaddr = false;
CTxDestination taddr = DecodeDestination(destaddress);
if (!IsValidDestination(taddr)) {
if (IsValidPaymentAddressString(destaddress)) {
isToZaddr = true;
// Is this a Sapling address?
auto res = DecodePaymentAddress(destaddress);
if (IsValidPaymentAddress(res)) {
isToSaplingZaddr = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
auto decodeAddr = DecodePaymentAddress(destaddress);
if (IsValidPaymentAddress(decodeAddr)) {
if (boost::get<libzcash::SaplingPaymentAddress>(&decodeAddr) != nullptr) {
isToSaplingZaddr = true;
// If Sapling is not active, do not allow sending to a sapling addresses.
if (!saplingActive) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress );
isToSproutZaddr = true;
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress );
@ -4296,18 +4300,21 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
}
}
int nNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SHIELDED_LIMIT;
int sproutNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT;
int saplingNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT;
if (params.size() > 4) {
nNoteLimit = params[4].get_int();
int nNoteLimit = params[4].get_int();
if (nNoteLimit < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative");
}
sproutNoteLimit = nNoteLimit;
saplingNoteLimit = nNoteLimit;
}
std::string memo;
if (params.size() > 5) {
memo = params[5].get_str();
if (!isToZaddr) {
if (!(isToSproutZaddr || isToSaplingZaddr)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo can not be used with a taddr. It can only be used with a zaddr.");
} else if (!IsHex(memo)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format.");
@ -4319,29 +4326,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
MergeToAddressRecipient recipient(destaddress, memo);
int nextBlockHeight = chainActive.Height() + 1;
bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING;
}
// This RPC does not support Sapling yet.
if (isToSaplingZaddr || containsSaplingZaddrSource) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling is not supported yet by z_mergetoadress");
}
// If this RPC does support Sapling...
// If Sapling is not active, do not allow sending from or sending to Sapling addresses.
if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
if (isToSaplingZaddr || containsSaplingZaddrSource) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
}
// Prepare to get UTXOs and notes
std::vector<MergeToAddressInputUTXO> utxoInputs;
std::vector<MergeToAddressInputNote> noteInputs;
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs;
CAmount mergedUTXOValue = 0;
CAmount mergedNoteValue = 0;
CAmount remainingUTXOValue = 0;
@ -4352,12 +4340,15 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
bool maxedOutNotesFlag = false;
size_t mempoolLimit = (nUTXOLimit != 0) ? nUTXOLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0));
unsigned int max_tx_size = saplingActive ? MAX_TX_SIZE_AFTER_SAPLING : MAX_TX_SIZE_BEFORE_SAPLING;
size_t estimatedTxSize = 200; // tx overhead + wiggle room
if (isToZaddr) {
if (isToSproutZaddr) {
estimatedTxSize += JOINSPLIT_SIZE;
} else if (isToSaplingZaddr) {
estimatedTxSize += OUTPUTDESCRIPTION_SIZE;
}
if (useAny || useAnyUTXO || taddrs.size() > 0) {
if (useAnyUTXO || taddrs.size() > 0) {
// Get available utxos
vector<COutput> vecOutputs;
pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, false);
@ -4368,8 +4359,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
continue;
}
CScript scriptPubKey = out.tx->vout[out.i].scriptPubKey;
CTxDestination address;
if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) {
if (!ExtractDestination(scriptPubKey, address)) {
continue;
}
// If taddr is not wildcard "*", filter utxos
@ -4389,7 +4382,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
} else {
estimatedTxSize += increase;
COutPoint utxo(out.tx->GetHash(), out.i);
utxoInputs.emplace_back(utxo, nValue);
utxoInputs.emplace_back(utxo, nValue, scriptPubKey);
mergedUTXOValue += nValue;
}
}
@ -4400,23 +4393,42 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
}
}
if (useAny || useAnyNote || zaddrs.size() > 0) {
if (useAnySprout || useAnySapling || zaddrs.size() > 0) {
// Get available notes
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs);
// If Sapling is not active, do not allow sending from a sapling addresses.
if (!saplingActive && saplingEntries.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
if (sproutEntries.size() > 0 && saplingEntries.size() > 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
} else if (saplingEntries.size() > 0 && isToSproutZaddr) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send from Sprout to Sapling addresses using z_mergetoaddress");
} else if (sproutEntries.size() > 0 && isToSaplingZaddr) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send from Sapling to Sprout addresses using z_mergetoaddress");
}
// Find unspent notes and update estimated size
for (CSproutNotePlaintextEntry& entry : sproutEntries) {
for (const CSproutNotePlaintextEntry& entry : sproutEntries) {
noteCounter++;
CAmount nValue = entry.plaintext.value();
if (!maxedOutNotesFlag) {
// If we haven't added any notes yet and the merge is to a
// z-address, we have already accounted for the first JoinSplit.
size_t increase = (noteInputs.empty() && !isToZaddr) || (noteInputs.size() % 2 == 0) ? JOINSPLIT_SIZE : 0;
size_t increase = (sproutNoteInputs.empty() && !isToSproutZaddr) || (sproutNoteInputs.size() % 2 == 0) ? JOINSPLIT_SIZE : 0;
if (estimatedTxSize + increase >= max_tx_size ||
(nNoteLimit > 0 && noteCounter > nNoteLimit))
(sproutNoteLimit > 0 && noteCounter > sproutNoteLimit))
{
maxedOutNotesFlag = true;
} else {
@ -4424,7 +4436,32 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
auto zaddr = entry.address;
SproutSpendingKey zkey;
pwalletMain->GetSproutSpendingKey(zaddr, zkey);
noteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey);
sproutNoteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey);
mergedNoteValue += nValue;
}
}
if (maxedOutNotesFlag) {
remainingNoteValue += nValue;
}
}
for (const SaplingNoteEntry& entry : saplingEntries) {
noteCounter++;
CAmount nValue = entry.note.value();
if (!maxedOutNotesFlag) {
size_t increase = SPENDDESCRIPTION_SIZE;
if (estimatedTxSize + increase >= max_tx_size ||
(saplingNoteLimit > 0 && noteCounter > saplingNoteLimit))
{
maxedOutNotesFlag = true;
} else {
estimatedTxSize += increase;
libzcash::SaplingExtendedSpendingKey extsk;
if (!pwalletMain->GetSaplingExtendedSpendingKey(entry.address, extsk)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find spending key for payment address.");
}
saplingNoteInputs.emplace_back(entry.op, entry.note, nValue, extsk.expsk);
mergedNoteValue += nValue;
}
}
@ -4433,11 +4470,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
remainingNoteValue += nValue;
}
}
// TODO: Add Sapling support
}
size_t numUtxos = utxoInputs.size();
size_t numNotes = noteInputs.size();
size_t numNotes = sproutNoteInputs.size() + saplingNoteInputs.size();
if (numUtxos == 0 && numNotes == 0) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any funds to merge.");
@ -4474,15 +4510,20 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
Params().GetConsensus(),
nextBlockHeight);
bool isShielded = numNotes > 0 || isToZaddr;
if (contextualTx.nVersion == 1 && isShielded) {
bool isSproutShielded = sproutNoteInputs.size() > 0 || isToSproutZaddr;
if (contextualTx.nVersion == 1 && isSproutShielded) {
contextualTx.nVersion = 2; // Tx format should support vjoinsplit
}
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
}
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(
new AsyncRPCOperation_mergetoaddress(contextualTx, utxoInputs, noteInputs, recipient, nFee, contextInfo) );
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();