Skip to content

Commit 13a7454

Browse files
author
MarcoFalke
committed
Merge #14380: fix assert crash when specified change output spend size is unknown
0fb2e69 CreateTransaction: Assume minimum p2sh-p2wpkh spend size for unknown change (Gregory Sanders) b06483c Remove stale comment in CalculateMaximumSignedInputSize (Gregory Sanders) Pull request description: This is triggered anytime a fundraw type call(psbt or legacy) is used with a change output address that the wallet doesn't know how to sign for. This regression was added in 6a34ff5 since BnB coin selection actually cares about this. The fix is to assume the smallest typical spend, a P2SH-P2WPKH, which is calculated using a "prototype" dummy signature flow. Future work could generalize this infrastructure to get estimated sizes of inputs for a variety of types. I also removed a comment which I believe is stale and misleading. Tree-SHA512: c7e2be189e524f81a7aa4454ad9370cefba715e3781f1e462c8bab77e4d27540191419029e3ebda11e3744c0703271e479dcd560d05e4d470048d9633e34da16
2 parents 74254fe + 0fb2e69 commit 13a7454

File tree

4 files changed

+59
-3
lines changed

4 files changed

+59
-3
lines changed

src/wallet/test/wallet_tests.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <validation.h>
1818
#include <wallet/coincontrol.h>
1919
#include <wallet/test/wallet_test_fixture.h>
20+
#include <policy/policy.h>
2021

2122
#include <boost/test/unit_test.hpp>
2223
#include <univalue.h>
@@ -394,4 +395,47 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
394395
BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false));
395396
}
396397

398+
// Explicit calculation which is used to test the wallet constant
399+
// We get the same virtual size due to rounding(weight/4) for both use_max_sig values
400+
static size_t CalculateNestedKeyhashInputSize(bool use_max_sig)
401+
{
402+
// Generate ephemeral valid pubkey
403+
CKey key;
404+
key.MakeNewKey(true);
405+
CPubKey pubkey = key.GetPubKey();
406+
407+
// Generate pubkey hash
408+
uint160 key_hash(Hash160(pubkey.begin(), pubkey.end()));
409+
410+
// Create inner-script to enter into keystore. Key hash can't be 0...
411+
CScript inner_script = CScript() << OP_0 << std::vector<unsigned char>(key_hash.begin(), key_hash.end());
412+
413+
// Create outer P2SH script for the output
414+
uint160 script_id(Hash160(inner_script.begin(), inner_script.end()));
415+
CScript script_pubkey = CScript() << OP_HASH160 << std::vector<unsigned char>(script_id.begin(), script_id.end()) << OP_EQUAL;
416+
417+
// Add inner-script to key store and key to watchonly
418+
CBasicKeyStore keystore;
419+
keystore.AddCScript(inner_script);
420+
keystore.AddKeyPubKey(key, pubkey);
421+
422+
// Fill in dummy signatures for fee calculation.
423+
SignatureData sig_data;
424+
425+
if (!ProduceSignature(keystore, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, script_pubkey, sig_data)) {
426+
// We're hand-feeding it correct arguments; shouldn't happen
427+
assert(false);
428+
}
429+
430+
CTxIn tx_in;
431+
UpdateInput(tx_in, sig_data);
432+
return (size_t)GetVirtualTransactionInputSize(tx_in);
433+
}
434+
435+
BOOST_FIXTURE_TEST_CASE(dummy_input_size_test, TestChain100Setup)
436+
{
437+
BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(false), DUMMY_NESTED_P2WPKH_INPUT_SIZE);
438+
BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(true), DUMMY_NESTED_P2WPKH_INPUT_SIZE);
439+
}
440+
397441
BOOST_AUTO_TEST_SUITE_END()

src/wallet/wallet.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,8 +1530,6 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet,
15301530
CMutableTransaction txn;
15311531
txn.vin.push_back(CTxIn(COutPoint()));
15321532
if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
1533-
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
1534-
// implies that we can sign for every input.
15351533
return -1;
15361534
}
15371535
return GetVirtualTransactionInputSize(txn.vin[0]);
@@ -2755,7 +2753,14 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
27552753
if (pick_new_inputs) {
27562754
nValueIn = 0;
27572755
setCoins.clear();
2758-
coin_selection_params.change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
2756+
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
2757+
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
2758+
// as lower-bound to allow BnB to do it's thing
2759+
if (change_spend_size == -1) {
2760+
coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
2761+
} else {
2762+
coin_selection_params.change_spend_size = (size_t)change_spend_size;
2763+
}
27592764
coin_selection_params.effective_fee = nFeeRateNeeded;
27602765
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
27612766
{

src/wallet/wallet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ static const bool DEFAULT_WALLET_RBF = false;
8585
static const bool DEFAULT_WALLETBROADCAST = true;
8686
static const bool DEFAULT_DISABLE_WALLET = false;
8787

88+
//! Pre-calculated constants for input size estimation in *virtual size*
89+
static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91;
90+
8891
class CBlockIndex;
8992
class CCoinControl;
9093
class COutput;

test/functional/rpc_psbt.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ def run_test(self):
210210
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
211211
assert_equal(decoded_psbt["tx"]["locktime"], 0)
212212

213+
# Make sure change address wallet does not have P2SH innerscript access to results in success
214+
# when attempting BnB coin selection
215+
self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False)
216+
213217
# Regression test for 14473 (mishandling of already-signed witness transaction):
214218
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
215219
complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])

0 commit comments

Comments
 (0)