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:
duke
2025-12-25 12:02:58 -05:00
6 changed files with 343 additions and 58 deletions

View File

@@ -54,6 +54,9 @@ void TransactionBuilder::AddSaplingOutput(
CAmount value,
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);
outputs.emplace_back(ovk, note, memo);
mtx.valueBalance -= value;

View File

@@ -54,8 +54,9 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
std::vector<ShieldCoinbaseUTXO> inputs,
std::string toAddress,
CAmount fee,
uint8_t donation,
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
@@ -67,6 +68,10 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
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
auto address = DecodePaymentAddress(toAddress);
if (IsValidPaymentAddress(address)) {
@@ -161,24 +166,7 @@ void AsyncRPCOperation_shieldcoinbase::main() {
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
CAmount minersFee = fee_;
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));
}
*/
size_t numInputs = inputs_.size();
CAmount targetAmount = 0;
for (ShieldCoinbaseUTXO & utxo : inputs_) {
@@ -192,10 +180,10 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
}
CAmount sendAmount = targetAmount - minersFee;
LogPrint("zrpc", "%s: spending %s to shield %s with fee %s\n",
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee));
LogPrint("zrpc", "%s: spending %s to shield %s with fee %s, donation=%d\n",
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);
@@ -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);
}
}
//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
m_op->builder_.SendChangeTo(zaddr, ovk);
if(donation) {
//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
auto maybe_tx = m_op->builder_.Build();

View File

@@ -49,6 +49,7 @@ public:
std::vector<ShieldCoinbaseUTXO> inputs,
std::string toAddress,
CAmount fee = SHIELD_COINBASE_DEFAULT_MINERS_FEE,
uint8_t donation = 0,
UniValue contextInfo = NullUniValue);
virtual ~AsyncRPCOperation_shieldcoinbase();
@@ -63,7 +64,6 @@ public:
virtual UniValue getStatus() const;
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:
friend class ShieldToAddress;
@@ -76,6 +76,9 @@ private:
std::vector<ShieldCoinbaseUTXO> inputs_;
// this is a donation % between 0 and 10, not a CAmount
uint8_t donation_;
TransactionBuilder builder_;
CTransaction tx_;
@@ -92,15 +95,15 @@ class ShieldToAddress : public boost::static_visitor<bool>
private:
AsyncRPCOperation_shieldcoinbase *m_op;
CAmount sendAmount;
uint8_t donation = 0;
public:
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount) :
m_op(op), sendAmount(sendAmount) {}
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount, uint8_t donation) :
m_op(op), sendAmount(sendAmount), donation(donation) {}
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};
// To test private methods, a friend class can act as a proxy
class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase {
public:
@@ -131,6 +134,4 @@ public:
}
};
#endif /* ASYNCRPCOPERATION_SHIELDCOINBASE_H */

View File

@@ -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"
+ HelpExampleCli("sendfrom", "\"\" \"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" 0.01") +
"\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"
+ 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 )
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);
std::string strAccount = AccountFromValue(params[0]);
CTxDestination dest = DecodeDestination(params[1].get_str());
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]);
if (nAmount <= 0)
@@ -4828,7 +4828,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp, const CPubKey& my
UniValue entry(UniValue::VOBJ);
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];
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))
return NullUniValue;
if (fHelp || params.size() < 2 || params.size() > 4)
if (fHelp || params.size() < 2 || params.size() > 5)
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"
"\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"
@@ -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"
"3. fee (numeric, optional, default="
+ 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"
"5. donation (integer, optional, default=0) Percentage of coinbase funds to donate. Must be between 0 and 10 inclusive.\n"
"\nResult:\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"
" \"shieldingUTXOs\": xxx (numeric) Number 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"
" \"shieldingUTXOs\": xxx (integer) Number 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"
"}\n"
"\nExamples:\n"
+ 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;
bool overwinterActive = nextBlockHeight>=1 ? true : false; // NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
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("toaddress", params[1]));
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
int blockHeight = chainActive.LastTip()->GetHeight();
nextBlockHeight = blockHeight + 1;
// (used if no Sapling addresses are involved)
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
Params().GetConsensus(), nextBlockHeight);
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
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);
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("shieldingUTXOs", static_cast<uint64_t>(numUtxos)));
o.push_back(Pair("shieldingValue", ValueFromAmount(shieldedValue)));
o.push_back(Pair("donation", donation));
o.push_back(Pair("opid", operationId));
return o;
}