diff --git a/doc/release-notes.md b/doc/release-notes.md index 410c9b8d4..2b47c198e 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -61,6 +61,12 @@ Option handling to specify the number of blocks back from the chain tip that anchors will be selected from when spending notes. By default, anchors will now be selected to have 3 confirmations. Values greater than 100 are not supported. +- A new `-orchardactionlimit` option has been added to allow the user to + override the default maximum of 50 Orchard actions per transaction. + Transactions that contain large numbers of Orchard actions can use + large amounts of memory for proving, so the 50-action default limit is + imposed to guard against memory exhaustion. Systems with more than 16G + of memory can safely set this parameter to allow 200 actions or more. RPC Interface ------------- diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 4615ea5a3..8d708b21b 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -290,9 +290,23 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { "Resubmit with the `privacyPolicy` parameter set to `AllowRevealedAmounts` " "or weaker if you wish to allow this transaction to proceed anyway."); } + // Sending from Orchard to transparent will be caught above in the // AllowRevealedRecipients check; sending to Sprout is disallowed // entirely. + + if (spendable.orchardNoteMetadata.size() > nOrchardActionLimit) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + strprintf( + "Attempting to spend %u Orchard notes would exceed the current limit " + "of %u notes, which exists to prevent memory exhaustion. Restart with " + "`-orchardactionlimit=N` where N >= %u to allow the wallet to attempt " + "to construct this transaction.", + spendable.orchardNoteMetadata.size(), + nOrchardActionLimit, + spendable.orchardNoteMetadata.size())); + } } spendable.LogInputs(getId()); @@ -553,7 +567,6 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { [&](const libzcash::OrchardRawAddress& addr) { auto value = r.amount; auto memo = r.memo.has_value() ? std::optional(get_memo_from_hex_string(r.memo.value())) : std::nullopt; - builder_.AddOrchardOutput(ovks.second, addr, value, memo); } }, r.address); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 8c27d3562..35711751e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5064,6 +5064,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) std::set recipientAddrs; std::vector recipients; CAmount nTotalOut = 0; + size_t nOrchardOutputs = 0; for (const UniValue& o : outputs.getValues()) { if (!o.isObject()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); @@ -5129,6 +5130,22 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) involvesUnifiedAddress = true; } + if (std::holds_alternative(addr.value())) { + nOrchardOutputs += 1; + if (nOrchardOutputs > nOrchardActionLimit) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + strprintf( + "Attempting to create %u Orchard outputs would exceed the current limit " + "of %u notes, which exists to prevent memory exhaustion. Restart with " + "`-orchardactionlimit=N` where N >= %u to allow the wallet to attempt " + "to construct this transaction.", + nOrchardOutputs, + nOrchardActionLimit, + nOrchardOutputs)); + } + } + recipients.push_back(SendManyRecipient(ua, addr.value(), nAmount, memo)); nTotalOut += nAmount; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6ffd58379..203f42da3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -52,6 +52,7 @@ bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS; bool fPayAtLeastCustomFee = true; unsigned int nAnchorConfirmations = DEFAULT_ANCHOR_CONFIRMATIONS; +unsigned int nOrchardActionLimit = DEFAULT_ORCHARD_ACTION_LIMIT; const char * DEFAULT_WALLET_DAT = "wallet.dat"; @@ -6333,6 +6334,7 @@ std::string CWallet::GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-migrationdestaddress=", _("Set the Sapling migration address")); strUsage += HelpMessageOpt("-mintxfee=", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE))); + strUsage += HelpMessageOpt("-orchardactionlimit=", strprintf(_("Set the maximum number of Orchard actions permitted in a transaction (default %u)"), DEFAULT_ORCHARD_ACTION_LIMIT)); strUsage += HelpMessageOpt("-paytxfee=", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions on startup")); @@ -6603,6 +6605,13 @@ bool CWallet::ParameterInteraction(const CChainParams& params) } nAnchorConfirmations = confirmations; } + if (mapArgs.count("-orchardactionlimit")) { + int64_t limit = atoi64(mapArgs["-orchardactionlimit"]); + if (limit < 1) { + return UIError(strprintf(_("Invalid value for -orchardactionlimit='%u' (must be least 1)"), limit)); + } + nOrchardActionLimit = limit; + } return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 02de77c26..0da133126 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -54,6 +54,9 @@ extern bool bSpendZeroConfChange; extern bool fSendFreeTransactions; extern bool fPayAtLeastCustomFee; extern unsigned int nAnchorConfirmations; +// The maximum number of Orchard actions permitted within a single transaction. +// This can be overridden with the -orchardactionlimit config option +extern unsigned int nOrchardActionLimit; static const unsigned int DEFAULT_KEYPOOL_SIZE = 100; //! -paytxfee default @@ -82,6 +85,8 @@ static const size_t WALLET_MNEMONIC_ENTROPY_LENGTH = 32; static const unsigned int DEFAULT_ANCHOR_CONFIRMATIONS = 3; // Default minimum number of confirmations for note selection static const unsigned int DEFAULT_NOTE_CONFIRMATIONS = 10; +//! -orchardactionlimit default +static const unsigned int DEFAULT_ORCHARD_ACTION_LIMIT = 50; extern const char * DEFAULT_WALLET_DAT;