Merge #14380: fix assert crash when specified change output spend size is unknown
0fb2e69815
CreateTransaction: Assume minimum p2sh-p2wpkh spend size for unknown change (Gregory Sanders)b06483c96a
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 in6a34ff5335
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
This commit is contained in:
commit
13a7454fbd
4 changed files with 59 additions and 3 deletions
|
@ -17,6 +17,7 @@
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
#include <wallet/coincontrol.h>
|
#include <wallet/coincontrol.h>
|
||||||
#include <wallet/test/wallet_test_fixture.h>
|
#include <wallet/test/wallet_test_fixture.h>
|
||||||
|
#include <policy/policy.h>
|
||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <univalue.h>
|
#include <univalue.h>
|
||||||
|
@ -394,4 +395,47 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
||||||
BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false));
|
BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicit calculation which is used to test the wallet constant
|
||||||
|
// We get the same virtual size due to rounding(weight/4) for both use_max_sig values
|
||||||
|
static size_t CalculateNestedKeyhashInputSize(bool use_max_sig)
|
||||||
|
{
|
||||||
|
// Generate ephemeral valid pubkey
|
||||||
|
CKey key;
|
||||||
|
key.MakeNewKey(true);
|
||||||
|
CPubKey pubkey = key.GetPubKey();
|
||||||
|
|
||||||
|
// Generate pubkey hash
|
||||||
|
uint160 key_hash(Hash160(pubkey.begin(), pubkey.end()));
|
||||||
|
|
||||||
|
// Create inner-script to enter into keystore. Key hash can't be 0...
|
||||||
|
CScript inner_script = CScript() << OP_0 << std::vector<unsigned char>(key_hash.begin(), key_hash.end());
|
||||||
|
|
||||||
|
// Create outer P2SH script for the output
|
||||||
|
uint160 script_id(Hash160(inner_script.begin(), inner_script.end()));
|
||||||
|
CScript script_pubkey = CScript() << OP_HASH160 << std::vector<unsigned char>(script_id.begin(), script_id.end()) << OP_EQUAL;
|
||||||
|
|
||||||
|
// Add inner-script to key store and key to watchonly
|
||||||
|
CBasicKeyStore keystore;
|
||||||
|
keystore.AddCScript(inner_script);
|
||||||
|
keystore.AddKeyPubKey(key, pubkey);
|
||||||
|
|
||||||
|
// Fill in dummy signatures for fee calculation.
|
||||||
|
SignatureData sig_data;
|
||||||
|
|
||||||
|
if (!ProduceSignature(keystore, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, script_pubkey, sig_data)) {
|
||||||
|
// We're hand-feeding it correct arguments; shouldn't happen
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
CTxIn tx_in;
|
||||||
|
UpdateInput(tx_in, sig_data);
|
||||||
|
return (size_t)GetVirtualTransactionInputSize(tx_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(dummy_input_size_test, TestChain100Setup)
|
||||||
|
{
|
||||||
|
BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(false), DUMMY_NESTED_P2WPKH_INPUT_SIZE);
|
||||||
|
BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(true), DUMMY_NESTED_P2WPKH_INPUT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
@ -1530,8 +1530,6 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet,
|
||||||
CMutableTransaction txn;
|
CMutableTransaction txn;
|
||||||
txn.vin.push_back(CTxIn(COutPoint()));
|
txn.vin.push_back(CTxIn(COutPoint()));
|
||||||
if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
|
if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
|
||||||
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
|
|
||||||
// implies that we can sign for every input.
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return GetVirtualTransactionInputSize(txn.vin[0]);
|
return GetVirtualTransactionInputSize(txn.vin[0]);
|
||||||
|
@ -2755,7 +2753,14 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
||||||
if (pick_new_inputs) {
|
if (pick_new_inputs) {
|
||||||
nValueIn = 0;
|
nValueIn = 0;
|
||||||
setCoins.clear();
|
setCoins.clear();
|
||||||
coin_selection_params.change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
|
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
|
||||||
|
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
|
||||||
|
// as lower-bound to allow BnB to do it's thing
|
||||||
|
if (change_spend_size == -1) {
|
||||||
|
coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
|
||||||
|
} else {
|
||||||
|
coin_selection_params.change_spend_size = (size_t)change_spend_size;
|
||||||
|
}
|
||||||
coin_selection_params.effective_fee = nFeeRateNeeded;
|
coin_selection_params.effective_fee = nFeeRateNeeded;
|
||||||
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
|
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
|
||||||
{
|
{
|
||||||
|
|
|
@ -85,6 +85,9 @@ static const bool DEFAULT_WALLET_RBF = false;
|
||||||
static const bool DEFAULT_WALLETBROADCAST = true;
|
static const bool DEFAULT_WALLETBROADCAST = true;
|
||||||
static const bool DEFAULT_DISABLE_WALLET = false;
|
static const bool DEFAULT_DISABLE_WALLET = false;
|
||||||
|
|
||||||
|
//! Pre-calculated constants for input size estimation in *virtual size*
|
||||||
|
static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91;
|
||||||
|
|
||||||
class CBlockIndex;
|
class CBlockIndex;
|
||||||
class CCoinControl;
|
class CCoinControl;
|
||||||
class COutput;
|
class COutput;
|
||||||
|
|
|
@ -210,6 +210,10 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
|
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
|
||||||
assert_equal(decoded_psbt["tx"]["locktime"], 0)
|
assert_equal(decoded_psbt["tx"]["locktime"], 0)
|
||||||
|
|
||||||
|
# Make sure change address wallet does not have P2SH innerscript access to results in success
|
||||||
|
# when attempting BnB coin selection
|
||||||
|
self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False)
|
||||||
|
|
||||||
# Regression test for 14473 (mishandling of already-signed witness transaction):
|
# Regression test for 14473 (mishandling of already-signed witness transaction):
|
||||||
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
|
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
|
||||||
complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])
|
complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])
|
||||||
|
|
Loading…
Reference in a new issue