Skip to content

Commit 0fb2e69

Browse files
committed
CreateTransaction: Assume minimum p2sh-p2wpkh spend size for unknown change
1 parent b06483c commit 0fb2e69

File tree

4 files changed

+59
-1
lines changed

4 files changed

+59
-1
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2753,7 +2753,14 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
27532753
if (pick_new_inputs) {
27542754
nValueIn = 0;
27552755
setCoins.clear();
2756-
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+
}
27572764
coin_selection_params.effective_fee = nFeeRateNeeded;
27582765
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
27592766
{

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
@@ -207,6 +207,10 @@ def run_test(self):
207207
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
208208
assert_equal(decoded_psbt["tx"]["locktime"], 0)
209209

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

0 commit comments

Comments
 (0)