Merge bitcoin/bitcoin#25481: wallet: unify max signature logic

d54c5c8b1b wallet: use CCoinControl to estimate signature size (S3RK)
a94659c84e wallet: replace GetTxSpendSize with CalculateMaximumSignedInputSize (S3RK)

Pull request description:

  Currently `DummySignTx` and `DummySignInput` use different ways to determine signature size.
  This PR unifies the way wallet estimates signature size for various inputs.
  Instead of passing boolean flags from calling code the `use_max_sig` is now calculated at the place of signature creation using information available in `CCoinControl`

ACKs for top commit:
  achow101:
    ACK d54c5c8b1b
  theStack:
    Code-review ACK d54c5c8b1b

Tree-SHA512: e790903ad4683067070aa7dbf7434a1bd142282a5bc425112e64d88d27559f1a2cd60c68d6022feaf6b845237035cb18ece10f6243d719ba28173b69bd99110a
This commit is contained in:
Andrew Chow 2022-07-08 10:16:45 -04:00
commit 194710d8ff
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
6 changed files with 27 additions and 36 deletions

View file

@ -58,7 +58,8 @@ static void CoinSelection(benchmark::Bench& bench)
// Create coins // Create coins
std::vector<COutput> coins; std::vector<COutput> coins;
for (const auto& wtx : wtxs) { for (const auto& wtx : wtxs) {
coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0); const auto txout = wtx->tx->vout.at(0);
coins.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
} }
const CoinEligibilityFilter filter_standard(1, 6, 0); const CoinEligibilityFilter filter_standard(1, 6, 0);

View file

@ -27,25 +27,20 @@ using interfaces::FoundBlock;
namespace wallet { namespace wallet {
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig) int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, const CCoinControl* coin_control)
{
return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
}
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
{ {
CMutableTransaction txn; CMutableTransaction txn;
txn.vin.push_back(CTxIn(COutPoint())); txn.vin.push_back(CTxIn(outpoint));
if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) { if (!provider || !DummySignInput(*provider, txn.vin[0], txout, coin_control)) {
return -1; return -1;
} }
return GetVirtualTransactionInputSize(txn.vin[0]); return GetVirtualTransactionInputSize(txn.vin[0]);
} }
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, const CCoinControl* coin_control)
{ {
const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey); const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig); return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), coin_control);
} }
// txouts needs to be in the order of tx.vin // txouts needs to be in the order of tx.vin
@ -198,7 +193,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
// Filter by spendable outputs only // Filter by spendable outputs only
if (!spendable && only_spendable) continue; if (!spendable && only_spendable) continue;
int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly)); int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
result.coins.emplace_back(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); result.coins.emplace_back(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
result.total_amount += output.nValue; result.total_amount += output.nValue;
@ -289,8 +284,9 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
) { ) {
CTxDestination address; CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) { if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) {
const auto out = wtx.tx->vout.at(output.n);
result[address].emplace_back( result[address].emplace_back(
COutPoint(wtx.GetHash(), output.n), wtx.tx->vout.at(output.n), depth, GetTxSpendSize(wallet, wtx, output.n), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)); COutPoint(wtx.GetHash(), output.n), out, depth, CalculateMaximumSignedInputSize(out, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
} }
} }
} }
@ -450,14 +446,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
if (ptr_wtx->tx->vout.size() <= outpoint.n) { if (ptr_wtx->tx->vout.size() <= outpoint.n) {
return std::nullopt; return std::nullopt;
} }
input_bytes = GetTxSpendSize(wallet, *ptr_wtx, outpoint.n, false);
txout = ptr_wtx->tx->vout.at(outpoint.n); txout = ptr_wtx->tx->vout.at(outpoint.n);
input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
} else { } else {
// The input is external. We did not find the tx in mapWallet. // The input is external. We did not find the tx in mapWallet.
if (!coin_control.GetExternalOutput(outpoint, txout)) { if (!coin_control.GetExternalOutput(outpoint, txout)) {
return std::nullopt; return std::nullopt;
} }
input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /*use_max_sig=*/true); input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
} }
// If available, override calculated size with coin control specified size // If available, override calculated size with coin control specified size
if (coin_control.HasInputWeight(outpoint)) { if (coin_control.HasInputWeight(outpoint)) {

View file

@ -14,23 +14,16 @@
namespace wallet { namespace wallet {
/** Get the marginal bytes if spending the specified output from this transaction. /** Get the marginal bytes if spending the specified output from this transaction.
* use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the * Use CoinControl to determine whether to expect signature grinding when calculating the size of the input spend. */
* size of the input spend. This should only be set when watch-only outputs are allowed */ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control = nullptr);
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false); int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, const CCoinControl* coin_control = nullptr);
//Get the marginal bytes of spending the specified output
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
struct TxSize { struct TxSize {
int64_t vsize{-1}; int64_t vsize{-1};
int64_t weight{-1}; int64_t weight{-1};
}; };
/** Calculate the size of the transaction assuming all signatures are max size /** Calculate the size of the transaction using CoinControl to determine
* Use DummySignatureCreator, which inserts 71 byte signatures everywhere. * whether to expect signature grinding when calculating the size of the input spend. */
* NOTE: this requires that all inputs must be in mapWallet (eg the tx should
* be AllInputsMine). */
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr); TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);

View file

@ -86,7 +86,8 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{})); auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
assert(ret.second); assert(ret.second);
CWalletTx& wtx = (*ret.first).second; CWalletTx& wtx = (*ret.first).second;
coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate); const auto& txout = wtx.tx->vout.at(nInput);
coins.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
} }
/** Check if SelectionResult a is equivalent to SelectionResult b. /** Check if SelectionResult a is equivalent to SelectionResult b.

View file

@ -1504,13 +1504,16 @@ bool CWallet::AddWalletFlags(uint64_t flags)
} }
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes) // Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true // or a max-sized low-S signature (e.g. 72 bytes) depending on coin_control
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control)
{ {
// Fill in dummy signatures for fee calculation. // Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey; const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata; SignatureData sigdata;
// Use max sig if watch only inputs were used or if this particular input is an external input
// to ensure a sufficient fee is attained for the requested feerate.
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout));
if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false; return false;
} }
@ -1577,12 +1580,9 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
nIn++; nIn++;
continue; continue;
} }
// Use max sig if watch only inputs were used or if this particular input is an external input
// to ensure a sufficient fee is attained for the requested feerate.
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));
const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey); const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
if (!provider || !DummySignInput(*provider, txin, txout, use_max_sig)) { if (!provider || !DummySignInput(*provider, txin, txout, coin_control)) {
if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, use_max_sig)) { if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, coin_control)) {
return false; return false;
} }
} }

View file

@ -955,7 +955,7 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
//! Remove wallet name from persistent configuration so it will not be loaded on startup. //! Remove wallet name from persistent configuration so it will not be loaded on startup.
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name); bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig); bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control = nullptr);
bool FillInputToWeight(CTxIn& txin, int64_t target_weight); bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
} // namespace wallet } // namespace wallet