Merge pull request #5919 from nuttycom/feature/z_sendmany_tx_limit

Add -orchardactionlimit parameter to guard against memory exhaustion.
This commit is contained in:
Kris Nuttycombe 2022-05-09 08:39:09 -06:00 committed by GitHub
commit 8ca33e06f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 1 deletions

View File

@ -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
-------------

View File

@ -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);

View File

@ -5064,6 +5064,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
std::set<RecipientAddress> recipientAddrs;
std::vector<SendManyRecipient> 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<libzcash::OrchardRawAddress>(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;
}

View File

@ -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=<zaddr>", _("Set the Sapling migration address"));
strUsage += HelpMessageOpt("-mintxfee=<amt>", 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=<n>", strprintf(_("Set the maximum number of Orchard actions permitted in a transaction (default %u)"), DEFAULT_ORCHARD_ACTION_LIMIT));
strUsage += HelpMessageOpt("-paytxfee=<amt>", 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;
}

View File

@ -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;