Merge pull request 'Sync danger to dev' (#479) from danger into dev
Reviewed-on: https://git.hush.is/hush/hush3/pulls/479
This commit is contained in:
247
qa/rpc-tests/shieldcoinbase_donation.py
Executable file
247
qa/rpc-tests/shieldcoinbase_donation.py
Executable file
@@ -0,0 +1,247 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2016-2025 The Hush developers
|
||||||
|
# Distributed under the GPLv3 software license, see the accompanying
|
||||||
|
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.authproxy import JSONRPCException
|
||||||
|
from test_framework.util import assert_equal, assert_greater_than, assert_greater_than_or_equal, \
|
||||||
|
initialize_chain_clean, initialize_chain, start_nodes, start_node, connect_nodes_bi, \
|
||||||
|
stop_nodes, sync_blocks, sync_mempools, wait_bitcoinds, rpc_port, assert_raises, assert_true, \
|
||||||
|
wait_and_assert_operationid_status
|
||||||
|
|
||||||
|
import time
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
def assert_success(result):
|
||||||
|
assert_equal(result['result'], 'success')
|
||||||
|
|
||||||
|
def assert_error(result):
|
||||||
|
assert_equal(result['result'], 'error')
|
||||||
|
|
||||||
|
class ShieldCoinbaseDonationTest (BitcoinTestFramework):
|
||||||
|
def setup_chain(self):
|
||||||
|
print("Initializing test directory "+self.options.tmpdir)
|
||||||
|
self.num_nodes = 1
|
||||||
|
self.options.nocleanup = 1 # do not delete datadir after test run
|
||||||
|
#self.options.nocleanup = 0
|
||||||
|
initialize_chain_clean(self.options.tmpdir, self.num_nodes)
|
||||||
|
|
||||||
|
def setup_network(self, split = False):
|
||||||
|
print("Setting up network...")
|
||||||
|
self.supply = 555
|
||||||
|
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
|
||||||
|
extra_args=[[
|
||||||
|
# always give -ac_name as first extra_arg and port as third
|
||||||
|
'-ac_name=ZZZ',
|
||||||
|
#'-ac_algo=randomx',
|
||||||
|
#'-testnode=1', # why does this make the test node hang before run_test() ?
|
||||||
|
'-conf='+self.options.tmpdir+'/node0/regtest/ZZZ.conf',
|
||||||
|
'-port=63367',
|
||||||
|
'-rpcport=63368',
|
||||||
|
'-ac_supply=' + str(self.supply),
|
||||||
|
'-ac_reward=300000000',
|
||||||
|
'-ac_private=1',
|
||||||
|
'-allowlist=127.0.0.1',
|
||||||
|
'-regtest',
|
||||||
|
'--daemon',
|
||||||
|
#'-debug',
|
||||||
|
'-zrpc',
|
||||||
|
'-zdebug',
|
||||||
|
'-zrpcunsafe'
|
||||||
|
]]
|
||||||
|
)
|
||||||
|
self.is_network_split = False
|
||||||
|
self.rpc = self.nodes[0]
|
||||||
|
self.sync_all()
|
||||||
|
print("Done setting up network")
|
||||||
|
|
||||||
|
def run_test (self):
|
||||||
|
# NOTE: order of these tests is important
|
||||||
|
self.run_test_default()
|
||||||
|
self.run_test_custom()
|
||||||
|
self.run_test_custom_nondefault_fee()
|
||||||
|
|
||||||
|
def run_test_default(self):
|
||||||
|
rpc = self.nodes[0]
|
||||||
|
|
||||||
|
# mine initial ac_supply
|
||||||
|
rpc.generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
zaddr1 = rpc.z_getnewaddress()
|
||||||
|
#rpc.z_exportkey(zaddr1)
|
||||||
|
|
||||||
|
# first we test the default situation where no donation is given and
|
||||||
|
# it defaults to 0
|
||||||
|
response = rpc.z_shieldcoinbase('*', zaddr1, 0, 1)
|
||||||
|
opid = response['opid']
|
||||||
|
shieldingValue = response['shieldingValue']
|
||||||
|
|
||||||
|
assert_true( shieldingValue >= self.supply )
|
||||||
|
|
||||||
|
print("opid=" + opid)
|
||||||
|
|
||||||
|
time.sleep(2) # give some time for the ztx to complete
|
||||||
|
|
||||||
|
# 555 supply plus magic utxo = 555.05537304
|
||||||
|
# NOTE: if any consensus params for this testcoin are changed above,
|
||||||
|
# the magic utxo will change and this value will need to be updated
|
||||||
|
totalSupply = 55505537304 # in puposhis
|
||||||
|
expectedAmount = totalSupply
|
||||||
|
|
||||||
|
json = rpc.z_getoperationstatus()
|
||||||
|
txid = json[0]['result']['txid']
|
||||||
|
|
||||||
|
wait_and_assert_operationid_status(rpc, opid)
|
||||||
|
|
||||||
|
rawtx0 = rpc.z_viewtransaction(txid)
|
||||||
|
assert_equal( rawtx0['outputs'][0]['valueZat'] , expectedAmount, '5% donation sends correct sendAmount')
|
||||||
|
|
||||||
|
def run_test_custom_nondefault_fee(self):
|
||||||
|
rpc = self.nodes[0]
|
||||||
|
zaddr1 = rpc.z_getnewaddress()
|
||||||
|
|
||||||
|
donation = 5
|
||||||
|
|
||||||
|
# donation zaddr is already imported from previous test
|
||||||
|
# NOTE: goal here is to test a situation where
|
||||||
|
# sendAmount/donationAmount arithmetic leads to a situation where the
|
||||||
|
# exact amount in satoshis must deal with truncation/rounding
|
||||||
|
|
||||||
|
# shield funds to a new zaddr in this wallet with non-default fee
|
||||||
|
fee = 0.00000001 # 1 puposhi fee will lead to some kind of rounding/truncation arithmetic
|
||||||
|
response = rpc.z_shieldcoinbase('*', zaddr1, fee, 1, donation)
|
||||||
|
opid = response['opid']
|
||||||
|
print("opid=" + opid)
|
||||||
|
|
||||||
|
shieldingValue = response['shieldingValue']
|
||||||
|
# sanity check. None of the expected values below will be correct if
|
||||||
|
# this is different
|
||||||
|
assert_equal( str(shieldingValue) , "3.00010000" )
|
||||||
|
|
||||||
|
# TODO: this might not be enough time for slow machines, better
|
||||||
|
# solution would be to wait until the opid finishes
|
||||||
|
time.sleep(2) # give some time for the ztx to complete
|
||||||
|
|
||||||
|
# confirm tx from above
|
||||||
|
rpc.generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# get the txid
|
||||||
|
json = rpc.z_getoperationstatus()
|
||||||
|
# NOTE: this is index 1 because this test runs after the above test
|
||||||
|
# It would be better to specifically find the data for our opid
|
||||||
|
txid = json[1]['result']['txid']
|
||||||
|
print("txid=" + txid)
|
||||||
|
|
||||||
|
rpc.z_listunspent()
|
||||||
|
|
||||||
|
# (300010000 - 1)*.05
|
||||||
|
# 15000499.95
|
||||||
|
# (300010000 - 1) - 15000499
|
||||||
|
# 285009500
|
||||||
|
# The above value will be truncated (not rounded) by casting from
|
||||||
|
# double to CAmount/int64_t which means the donation will be 15000499
|
||||||
|
# and the sendAmount will be 300010000 - 1 (the fee) - 15000499 = 285009500
|
||||||
|
|
||||||
|
|
||||||
|
# these values assume that 3.0001 was shielded
|
||||||
|
expectedSendAmount = 285009500
|
||||||
|
expectedDonationAmount = 15000499
|
||||||
|
|
||||||
|
# lookup txid
|
||||||
|
rawtx1 = rpc.z_viewtransaction(txid)
|
||||||
|
|
||||||
|
# TODO: set this up once for all tests since they all use the same zaddr
|
||||||
|
donation_zaddr = "zregtestsapling1y30nwg0clsu6gcyrnvht8hdyfk3vwtszlh6kc4z5hv9hmpxzg2g0nx7c60xeecggm9x9gma96t4"
|
||||||
|
|
||||||
|
# there should be two outputs to different addresses, order is nondeterministic
|
||||||
|
if rawtx1['outputs'][0]['address'] == donation_zaddr:
|
||||||
|
donation_zout = 0
|
||||||
|
other_zout = 1
|
||||||
|
else:
|
||||||
|
donation_zout = 1
|
||||||
|
other_zout = 0
|
||||||
|
|
||||||
|
assert_equal( rawtx1['outputs'][donation_zout]['address'] , donation_zaddr, 'correct zaddr gets donation')
|
||||||
|
assert_equal( rawtx1['outputs'][donation_zout]['valueZat'] , expectedDonationAmount, '5% donation sends correct donationAmount')
|
||||||
|
|
||||||
|
assert_equal( rawtx1['outputs'][other_zout]['address'] , zaddr1, 'correct zaddr gets main amount')
|
||||||
|
assert_equal( rawtx1['outputs'][other_zout]['valueZat'] , expectedSendAmount, '5% donation sends correct sendAmount')
|
||||||
|
|
||||||
|
#TODO: assert sum = 3
|
||||||
|
|
||||||
|
def run_test_custom(self):
|
||||||
|
rpc = self.nodes[0]
|
||||||
|
zaddr1 = rpc.z_getnewaddress()
|
||||||
|
|
||||||
|
# generate some new coinbase funds
|
||||||
|
rpc.generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
testing_zaddr = "zregtestsapling1y30nwg0clsu6gcyrnvht8hdyfk3vwtszlh6kc4z5hv9hmpxzg2g0nx7c60xeecggm9x9gma96t4"
|
||||||
|
testing_privkey = "secret-extended-key-regtest1q0hgrms7qqqqpqrsz6myrtnh3ccp8uzp0kgxj6029wr6vq5hqvyccdlz7a745pgm5eeaamxqp9rxll2xctfrlw2l8xhxsc7zsut2tyz0374rrlk8srjswx7rhm6hcf2d7fuwajazvjesafduzxyka4w02tqjxdehzvghyrsd2zll90k3g2ckdvc5kqd6r7r7nglrtj0ej5a40d6lh8zxrvdlxrpuc59y5m8n9tekdxh4wpqn3smv5nxu4vvu58f8dgwn92qfqrvxqlscchtyh"
|
||||||
|
|
||||||
|
# import zaddr that receives donation , no rescan
|
||||||
|
rpc.z_importkey(testing_privkey, "no")
|
||||||
|
|
||||||
|
rpc.z_listaddresses()
|
||||||
|
|
||||||
|
# now we test giving a donation parameter
|
||||||
|
donation = 5
|
||||||
|
|
||||||
|
# shield funds to a new zaddr in this wallet with default fee
|
||||||
|
fee = 0.0001
|
||||||
|
response = rpc.z_shieldcoinbase('*', zaddr1, fee, 1, donation)
|
||||||
|
opid = response['opid']
|
||||||
|
print("opid=" + opid)
|
||||||
|
#wait_and_assert_operationid_status(rpc, opid)
|
||||||
|
shieldingValue = response['shieldingValue']
|
||||||
|
|
||||||
|
assert_greater_than_or_equal( shieldingValue , 3.0 )
|
||||||
|
|
||||||
|
time.sleep(2) # give some time for the ztx to complete
|
||||||
|
|
||||||
|
rpc.getinfo()
|
||||||
|
|
||||||
|
rpc.generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# get the txid
|
||||||
|
json = rpc.z_getoperationstatus()
|
||||||
|
txid = json[0]['result']['txid']
|
||||||
|
print("txid=" + txid)
|
||||||
|
|
||||||
|
rpc.z_listunspent()
|
||||||
|
|
||||||
|
# (3 - fee)*0.05
|
||||||
|
expectedAmount1 = 14999500
|
||||||
|
|
||||||
|
# lookup txid
|
||||||
|
rawtx1 = rpc.z_viewtransaction(txid)
|
||||||
|
# there should be two outputs to different addresses, order is nondeterministic
|
||||||
|
if rawtx1['outputs'][0]['address'] == testing_zaddr:
|
||||||
|
donation_zout = 0
|
||||||
|
other_zout = 1
|
||||||
|
else:
|
||||||
|
donation_zout = 1
|
||||||
|
other_zout = 0
|
||||||
|
|
||||||
|
# print("donation_zout=%d other_zout=%d" % (donation_zout,other_zout) )
|
||||||
|
|
||||||
|
assert_equal( rawtx1['outputs'][donation_zout]['address'] , testing_zaddr, 'correct zaddr gets donation')
|
||||||
|
assert_equal( rawtx1['outputs'][donation_zout]['valueZat'] , expectedAmount1, '5% donation sends correct donationAmount')
|
||||||
|
|
||||||
|
# (3 - fee)*0.95
|
||||||
|
expectedAmount2 = 284990500
|
||||||
|
assert_equal( rawtx1['outputs'][other_zout]['address'] , zaddr1, 'correct zaddr gets main amount')
|
||||||
|
assert_equal( rawtx1['outputs'][other_zout]['valueZat'] , expectedAmount2, '5% donation sends correct sendAmount')
|
||||||
|
|
||||||
|
|
||||||
|
assert_equal( expectedAmount1 + expectedAmount2, 299990000, 'sendAmount+donationAmount = targetAmount - fee' )
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ShieldCoinbaseDonationTest().main()
|
||||||
|
|
||||||
@@ -54,6 +54,9 @@ void TransactionBuilder::AddSaplingOutput(
|
|||||||
CAmount value,
|
CAmount value,
|
||||||
std::array<unsigned char, HUSH_MEMO_SIZE> memo)
|
std::array<unsigned char, HUSH_MEMO_SIZE> memo)
|
||||||
{
|
{
|
||||||
|
if(fZdebug) {
|
||||||
|
LogPrintf("%s: adding output with value=%ld\n", __func__, value);
|
||||||
|
}
|
||||||
auto note = libzcash::SaplingNote(to, value);
|
auto note = libzcash::SaplingNote(to, value);
|
||||||
outputs.emplace_back(ovk, note, memo);
|
outputs.emplace_back(ovk, note, memo);
|
||||||
mtx.valueBalance -= value;
|
mtx.valueBalance -= value;
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
|||||||
std::vector<ShieldCoinbaseUTXO> inputs,
|
std::vector<ShieldCoinbaseUTXO> inputs,
|
||||||
std::string toAddress,
|
std::string toAddress,
|
||||||
CAmount fee,
|
CAmount fee,
|
||||||
|
uint8_t donation,
|
||||||
UniValue contextInfo) :
|
UniValue contextInfo) :
|
||||||
builder_(builder), tx_(contextualTx), inputs_(inputs), fee_(fee), contextinfo_(contextInfo)
|
builder_(builder), tx_(contextualTx), inputs_(inputs), fee_(fee), donation_(donation), contextinfo_(contextInfo)
|
||||||
{
|
{
|
||||||
assert(contextualTx.nVersion >= 2); // transaction format version must support vjoinsplit
|
assert(contextualTx.nVersion >= 2); // transaction format version must support vjoinsplit
|
||||||
|
|
||||||
@@ -67,6 +68,10 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
|||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Empty inputs");
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Empty inputs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (donation < 0 || donation > 10 ) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid donation percentage, must be an integer between 0 and 10");
|
||||||
|
}
|
||||||
|
|
||||||
// Check the destination address is valid for this network i.e. not testnet being used on mainnet
|
// Check the destination address is valid for this network i.e. not testnet being used on mainnet
|
||||||
auto address = DecodePaymentAddress(toAddress);
|
auto address = DecodePaymentAddress(toAddress);
|
||||||
if (IsValidPaymentAddress(address)) {
|
if (IsValidPaymentAddress(address)) {
|
||||||
@@ -161,24 +166,7 @@ void AsyncRPCOperation_shieldcoinbase::main() {
|
|||||||
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
||||||
|
|
||||||
CAmount minersFee = fee_;
|
CAmount minersFee = fee_;
|
||||||
|
size_t numInputs = inputs_.size();
|
||||||
size_t numInputs = inputs_.size();
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects
|
|
||||||
size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0);
|
|
||||||
{
|
|
||||||
LOCK(cs_main);
|
|
||||||
if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) {
|
|
||||||
limit = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (limit>0 && numInputs > limit) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR,
|
|
||||||
strprintf("Number of inputs %d is greater than mempooltxinputlimit of %d",
|
|
||||||
numInputs, limit));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
CAmount targetAmount = 0;
|
CAmount targetAmount = 0;
|
||||||
for (ShieldCoinbaseUTXO & utxo : inputs_) {
|
for (ShieldCoinbaseUTXO & utxo : inputs_) {
|
||||||
@@ -192,10 +180,10 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CAmount sendAmount = targetAmount - minersFee;
|
CAmount sendAmount = targetAmount - minersFee;
|
||||||
LogPrint("zrpc", "%s: spending %s to shield %s with fee %s\n",
|
LogPrint("zrpc", "%s: spending %s to shield %s with fee %s, donation=%d\n",
|
||||||
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee));
|
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee), donation_);
|
||||||
|
|
||||||
return boost::apply_visitor(ShieldToAddress(this, sendAmount), tozaddr_);
|
return boost::apply_visitor(ShieldToAddress(this, sendAmount, donation_), tozaddr_);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
@@ -228,20 +216,55 @@ bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) c
|
|||||||
m_op->builder_.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount);
|
m_op->builder_.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//TODO: TESTING zaddr only, only use on regtest
|
||||||
|
//TODO: randomly select from a set
|
||||||
|
auto dzaddr = "zregtestsapling1y30nwg0clsu6gcyrnvht8hdyfk3vwtszlh6kc4z5hv9hmpxzg2g0nx7c60xeecggm9x9gma96t4";
|
||||||
|
auto donationZaddr = DecodePaymentAddress(dzaddr);
|
||||||
|
if (!IsValidPaymentAddress(donationZaddr)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid donation zaddr, Unknown address format: ") + dzaddr);
|
||||||
|
}
|
||||||
|
|
||||||
// Send all value to the target z-addr
|
if(donation) {
|
||||||
m_op->builder_.SendChangeTo(zaddr, ovk);
|
//if donation>0, send X% of value to zaddr and Y% of of value to donatezaddr where X+Y=100%
|
||||||
|
|
||||||
|
// calculate donation as a double then convert to CAmount
|
||||||
|
double amount = (static_cast<double>(donation)/100)*static_cast<double>(sendAmount);
|
||||||
|
CAmount donationAmount = static_cast<CAmount>(amount);
|
||||||
|
|
||||||
|
// add original recipient as first output, with sendAmount less the donation
|
||||||
|
CAmount newAmount = sendAmount - donationAmount;
|
||||||
|
m_op->builder_.AddSaplingOutput(ovk, zaddr, newAmount);
|
||||||
|
|
||||||
|
auto donationZout = boost::get<libzcash::SaplingPaymentAddress>(donationZaddr);
|
||||||
|
m_op->builder_.AddSaplingOutput(ovk, donationZout, donationAmount);
|
||||||
|
|
||||||
|
if(fZdebug) {
|
||||||
|
LogPrintf("%s: donation=%ld, sendAmount=%ld, newAmount=%ld, donationAmount=%ld, newAmount+donationAmount=%ld fee=%ld\n", __func__,
|
||||||
|
donation, sendAmount, newAmount, donationAmount, newAmount+donationAmount, m_op->fee_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// zdust as third output, so donation txs are indistinguishable from
|
||||||
|
// non-donation z_shieldcoinbase txs
|
||||||
|
auto zdust1 = DecodePaymentAddress(randomSietchZaddr());
|
||||||
|
auto sietchZout1 = boost::get<libzcash::SaplingPaymentAddress>(zdust1);
|
||||||
|
m_op->builder_.AddSaplingOutput(ovk, sietchZout1, 0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Send all value to the target z-addr
|
||||||
|
m_op->builder_.SendChangeTo(zaddr, ovk);
|
||||||
|
|
||||||
|
// Sietchified Shielding of Coinbase Funds
|
||||||
|
// Add Sietch zouts so it's unclear which zout contains value :)
|
||||||
|
// This reduces metadata leakage of coinbase t=>z tx's
|
||||||
|
CAmount amount = 0;
|
||||||
|
auto zdust1 = DecodePaymentAddress(randomSietchZaddr());
|
||||||
|
auto zdust2 = DecodePaymentAddress(randomSietchZaddr());
|
||||||
|
auto sietchZout1 = boost::get<libzcash::SaplingPaymentAddress>(zdust1);
|
||||||
|
auto sietchZout2 = boost::get<libzcash::SaplingPaymentAddress>(zdust2);
|
||||||
|
m_op->builder_.AddSaplingOutput(ovk, sietchZout1, amount);
|
||||||
|
m_op->builder_.AddSaplingOutput(ovk, sietchZout2, amount);
|
||||||
|
}
|
||||||
|
|
||||||
// Sietchified Shielding of Coinbase Funds
|
|
||||||
// Add Sietch zouts so it's unclear which zout contains value :)
|
|
||||||
// This reduces metadata leakage of coinbase t=>z tx's
|
|
||||||
CAmount amount = 0;
|
|
||||||
auto zdust1 = DecodePaymentAddress(randomSietchZaddr());
|
|
||||||
auto zdust2 = DecodePaymentAddress(randomSietchZaddr());
|
|
||||||
auto sietchZout1 = boost::get<libzcash::SaplingPaymentAddress>(zdust1);
|
|
||||||
auto sietchZout2 = boost::get<libzcash::SaplingPaymentAddress>(zdust2);
|
|
||||||
m_op->builder_.AddSaplingOutput(ovk, sietchZout1, amount);
|
|
||||||
m_op->builder_.AddSaplingOutput(ovk, sietchZout2, amount);
|
|
||||||
|
|
||||||
// Build the transaction
|
// Build the transaction
|
||||||
auto maybe_tx = m_op->builder_.Build();
|
auto maybe_tx = m_op->builder_.Build();
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public:
|
|||||||
std::vector<ShieldCoinbaseUTXO> inputs,
|
std::vector<ShieldCoinbaseUTXO> inputs,
|
||||||
std::string toAddress,
|
std::string toAddress,
|
||||||
CAmount fee = SHIELD_COINBASE_DEFAULT_MINERS_FEE,
|
CAmount fee = SHIELD_COINBASE_DEFAULT_MINERS_FEE,
|
||||||
|
uint8_t donation = 0,
|
||||||
UniValue contextInfo = NullUniValue);
|
UniValue contextInfo = NullUniValue);
|
||||||
virtual ~AsyncRPCOperation_shieldcoinbase();
|
virtual ~AsyncRPCOperation_shieldcoinbase();
|
||||||
|
|
||||||
@@ -63,7 +64,6 @@ public:
|
|||||||
virtual UniValue getStatus() const;
|
virtual UniValue getStatus() const;
|
||||||
|
|
||||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||||
bool cheatSpend = false; // set when this is shielding a cheating coinbase
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class ShieldToAddress;
|
friend class ShieldToAddress;
|
||||||
@@ -76,6 +76,9 @@ private:
|
|||||||
|
|
||||||
std::vector<ShieldCoinbaseUTXO> inputs_;
|
std::vector<ShieldCoinbaseUTXO> inputs_;
|
||||||
|
|
||||||
|
// this is a donation % between 0 and 10, not a CAmount
|
||||||
|
uint8_t donation_;
|
||||||
|
|
||||||
TransactionBuilder builder_;
|
TransactionBuilder builder_;
|
||||||
CTransaction tx_;
|
CTransaction tx_;
|
||||||
|
|
||||||
@@ -92,15 +95,15 @@ class ShieldToAddress : public boost::static_visitor<bool>
|
|||||||
private:
|
private:
|
||||||
AsyncRPCOperation_shieldcoinbase *m_op;
|
AsyncRPCOperation_shieldcoinbase *m_op;
|
||||||
CAmount sendAmount;
|
CAmount sendAmount;
|
||||||
|
uint8_t donation = 0;
|
||||||
public:
|
public:
|
||||||
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount) :
|
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount, uint8_t donation) :
|
||||||
m_op(op), sendAmount(sendAmount) {}
|
m_op(op), sendAmount(sendAmount), donation(donation) {}
|
||||||
|
|
||||||
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
|
||||||
bool operator()(const libzcash::InvalidEncoding& no) const;
|
bool operator()(const libzcash::InvalidEncoding& no) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// To test private methods, a friend class can act as a proxy
|
// To test private methods, a friend class can act as a proxy
|
||||||
class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase {
|
class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase {
|
||||||
public:
|
public:
|
||||||
@@ -131,6 +134,4 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCRPCOPERATION_SHIELDCOINBASE_H */
|
#endif /* ASYNCRPCOPERATION_SHIELDCOINBASE_H */
|
||||||
|
|
||||||
|
|||||||
@@ -1341,19 +1341,19 @@ UniValue sendfrom(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
|||||||
"\nSend 0.01 " + strprintf("%s",hush_chainname()) + " from the default account to the address, must have at least 1 confirmation\n"
|
"\nSend 0.01 " + strprintf("%s",hush_chainname()) + " from the default account to the address, must have at least 1 confirmation\n"
|
||||||
+ HelpExampleCli("sendfrom", "\"\" \"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" 0.01") +
|
+ HelpExampleCli("sendfrom", "\"\" \"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" 0.01") +
|
||||||
"\nSend 0.01 from the tabby account to the given address, funds must have at least 6 confirmations\n"
|
"\nSend 0.01 from the tabby account to the given address, funds must have at least 6 confirmations\n"
|
||||||
+ HelpExampleCli("sendfrom", "\"tabby\" \"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" 0.01 6 \"donation\" \"seans outpost\"") +
|
+ HelpExampleCli("sendfrom", "\"tabby\" \"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" 0.01 6 \"donation\" \"Hush Puppy Freedom Fund\"") +
|
||||||
"\nAs a json rpc call\n"
|
"\nAs a json rpc call\n"
|
||||||
+ HelpExampleRpc("sendfrom", "\"tabby\", \"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", 0.01, 6, \"donation\", \"seans outpost\"")
|
+ HelpExampleRpc("sendfrom", "\"tabby\", \"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", 0.01, 6, \"donation\", \"Hush Puppy Freedom Fund\"")
|
||||||
);
|
);
|
||||||
if ( ASSETCHAINS_PRIVATE != 0 )
|
if ( ASSETCHAINS_PRIVATE != 0 )
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "cant use transparent addresses in private chain");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use transparent addresses in private chain");
|
||||||
|
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
std::string strAccount = AccountFromValue(params[0]);
|
std::string strAccount = AccountFromValue(params[0]);
|
||||||
CTxDestination dest = DecodeDestination(params[1].get_str());
|
CTxDestination dest = DecodeDestination(params[1].get_str());
|
||||||
if (!IsValidDestination(dest)) {
|
if (!IsValidDestination(dest)) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address!");
|
||||||
}
|
}
|
||||||
CAmount nAmount = AmountFromValue(params[2]);
|
CAmount nAmount = AmountFromValue(params[2]);
|
||||||
if (nAmount <= 0)
|
if (nAmount <= 0)
|
||||||
@@ -4828,7 +4828,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp, const CPubKey& my
|
|||||||
UniValue entry(UniValue::VOBJ);
|
UniValue entry(UniValue::VOBJ);
|
||||||
|
|
||||||
if (!pwalletMain->mapWallet.count(hash))
|
if (!pwalletMain->mapWallet.count(hash))
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet Hush transaction id!");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id!");
|
||||||
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
|
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
|
||||||
|
|
||||||
entry.push_back(Pair("txid", hash.GetHex()));
|
entry.push_back(Pair("txid", hash.GetHex()));
|
||||||
@@ -5487,9 +5487,9 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp
|
|||||||
if (!EnsureWalletIsAvailable(fHelp))
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
|
|
||||||
if (fHelp || params.size() < 2 || params.size() > 4)
|
if (fHelp || params.size() < 2 || params.size() > 5)
|
||||||
throw runtime_error(
|
throw runtime_error(
|
||||||
"z_shieldcoinbase \"fromaddress\" \"tozaddress\" ( fee ) ( limit )\n"
|
"z_shieldcoinbase \"fromaddress\" \"tozaddress\" ( fee ) ( limit ) ( donation )\n"
|
||||||
"\nShield transparent coinbase funds by sending to a shielded zaddr. This is an asynchronous operation and utxos"
|
"\nShield transparent coinbase funds by sending to a shielded zaddr. This is an asynchronous operation and utxos"
|
||||||
"\nselected for shielding will be locked. If there is an error, they are unlocked. The RPC call `listlockunspent`"
|
"\nselected for shielding will be locked. If there is an error, they are unlocked. The RPC call `listlockunspent`"
|
||||||
"\ncan be used to return a list of locked utxos. The number of coinbase utxos selected for shielding can be limited"
|
"\ncan be used to return a list of locked utxos. The number of coinbase utxos selected for shielding can be limited"
|
||||||
@@ -5502,15 +5502,16 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp
|
|||||||
"2. \"toaddress\" (string, required) The address is a zaddr.\n"
|
"2. \"toaddress\" (string, required) The address is a zaddr.\n"
|
||||||
"3. fee (numeric, optional, default="
|
"3. fee (numeric, optional, default="
|
||||||
+ strprintf("%s", FormatMoney(SHIELD_COINBASE_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
|
+ strprintf("%s", FormatMoney(SHIELD_COINBASE_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
|
||||||
"4. limit (numeric, optional, default="
|
"4. limit (integer, optional, default="
|
||||||
+ strprintf("%d", SHIELD_COINBASE_DEFAULT_LIMIT) + ") Limit on the maximum number of utxos to shield. Set to 0 to use as many as will fit in the transaction.\n"
|
+ strprintf("%d", SHIELD_COINBASE_DEFAULT_LIMIT) + ") Limit on the maximum number of utxos to shield. Set to 0 to use as many as will fit in the transaction.\n"
|
||||||
|
"5. donation (integer, optional, default=0) Percentage of coinbase funds to donate. Must be between 0 and 10 inclusive.\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
" \"remainingUTXOs\": xxx (numeric) Number of coinbase utxos still available for shielding.\n"
|
" \"remainingUTXOs\": xxx (integer) Number of coinbase utxos still available for shielding.\n"
|
||||||
" \"remainingValue\": xxx (numeric) Value of coinbase utxos still available for shielding.\n"
|
" \"remainingValue\": xxx (numeric) Value of coinbase utxos still available for shielding.\n"
|
||||||
" \"shieldingUTXOs\": xxx (numeric) Number of coinbase utxos being shielded.\n"
|
" \"shieldingUTXOs\": xxx (integer) Number of coinbase utxos being shielded.\n"
|
||||||
" \"shieldingValue\": xxx (numeric) Value of coinbase utxos being shielded.\n"
|
" \"shieldingValue\": xxx (numeric) Value of coinbase utxos being shielded.\n"
|
||||||
" \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
|
" \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
|
+ HelpExampleCli("z_shieldcoinbase", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\"")
|
||||||
@@ -5559,6 +5560,16 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t donation = 0;
|
||||||
|
if (params.size() > 4) {
|
||||||
|
donation = params[4].get_int();
|
||||||
|
if (donation < 0 || donation > 10 ) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid donation percentage, must be an integer between 0 and 10");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPrintf("%s: donation=%d\n", __func__, donation);
|
||||||
|
|
||||||
int nextBlockHeight = chainActive.Height() + 1;
|
int nextBlockHeight = chainActive.Height() + 1;
|
||||||
bool overwinterActive = nextBlockHeight>=1 ? true : false; // NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
|
bool overwinterActive = nextBlockHeight>=1 ? true : false; // NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
|
||||||
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
|
unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING;
|
||||||
@@ -5659,15 +5670,13 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp
|
|||||||
contextInfo.push_back(Pair("fromaddress", params[0]));
|
contextInfo.push_back(Pair("fromaddress", params[0]));
|
||||||
contextInfo.push_back(Pair("toaddress", params[1]));
|
contextInfo.push_back(Pair("toaddress", params[1]));
|
||||||
contextInfo.push_back(Pair("fee", ValueFromAmount(nFee)));
|
contextInfo.push_back(Pair("fee", ValueFromAmount(nFee)));
|
||||||
|
contextInfo.push_back(Pair("donation", donation));
|
||||||
|
|
||||||
// Builder (used if Sapling addresses are involved)
|
TransactionBuilder builder = TransactionBuilder( Params().GetConsensus(), nextBlockHeight, pwalletMain);
|
||||||
TransactionBuilder builder = TransactionBuilder(
|
|
||||||
Params().GetConsensus(), nextBlockHeight, pwalletMain);
|
|
||||||
|
|
||||||
// Contextual transaction we will build on
|
// Contextual transaction we will build on
|
||||||
int blockHeight = chainActive.LastTip()->GetHeight();
|
int blockHeight = chainActive.LastTip()->GetHeight();
|
||||||
nextBlockHeight = blockHeight + 1;
|
nextBlockHeight = blockHeight + 1;
|
||||||
// (used if no Sapling addresses are involved)
|
|
||||||
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
|
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
|
||||||
Params().GetConsensus(), nextBlockHeight);
|
Params().GetConsensus(), nextBlockHeight);
|
||||||
contextualTx.nLockTime = chainActive.LastTip()->GetHeight();
|
contextualTx.nLockTime = chainActive.LastTip()->GetHeight();
|
||||||
@@ -5678,7 +5687,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp
|
|||||||
|
|
||||||
// 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_shieldcoinbase(builder, contextualTx, inputs, destaddress, nFee, contextInfo) );
|
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(builder, contextualTx, inputs, destaddress, nFee, donation, contextInfo) );
|
||||||
q->addOperation(operation);
|
q->addOperation(operation);
|
||||||
AsyncRPCOperationId operationId = operation->getId();
|
AsyncRPCOperationId operationId = operation->getId();
|
||||||
|
|
||||||
@@ -5688,6 +5697,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp
|
|||||||
o.push_back(Pair("remainingValue", ValueFromAmount(remainingValue)));
|
o.push_back(Pair("remainingValue", ValueFromAmount(remainingValue)));
|
||||||
o.push_back(Pair("shieldingUTXOs", static_cast<uint64_t>(numUtxos)));
|
o.push_back(Pair("shieldingUTXOs", static_cast<uint64_t>(numUtxos)));
|
||||||
o.push_back(Pair("shieldingValue", ValueFromAmount(shieldedValue)));
|
o.push_back(Pair("shieldingValue", ValueFromAmount(shieldedValue)));
|
||||||
|
o.push_back(Pair("donation", donation));
|
||||||
o.push_back(Pair("opid", operationId));
|
o.push_back(Pair("opid", operationId));
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|||||||
3
test.sh
3
test.sh
@@ -7,4 +7,5 @@
|
|||||||
export PYTHONPATH=./qa/rpc-tests/test_framework/
|
export PYTHONPATH=./qa/rpc-tests/test_framework/
|
||||||
|
|
||||||
#./qa/rpc-tests/ac_private.py
|
#./qa/rpc-tests/ac_private.py
|
||||||
./qa/rpc-tests/lockzins.py --tracerpc
|
# ./qa/rpc-tests/lockzins.py --tracerpc
|
||||||
|
./qa/rpc-tests/shieldcoinbase_donation.py --tracerpc
|
||||||
|
|||||||
Reference in New Issue
Block a user