RPC: listunspent, add "include immature coinbase" flag

so we can return the immature coinbase UTXOs as well.
This commit is contained in:
furszy 2022-07-28 10:25:29 -03:00
parent 4f270d2b63
commit f0f6a3577b
No known key found for this signature in database
GPG key ID: 5DD23CCC686AA623
5 changed files with 32 additions and 7 deletions

View file

@ -0,0 +1,6 @@
RPC Wallet
----------
- RPC `listunspent` now has a new argument `include_immature_coinbase`
to include coinbase UTXOs that don't meet the minimum spendability
depth requirement (which before were silently skipped). (#25730)

View file

@ -515,6 +515,7 @@ RPCHelpMan listunspent()
{"maximumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Maximum value of each UTXO in " + CURRENCY_UNIT + ""},
{"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"},
{"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""},
{"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase UTXOs"}
},
RPCArgOptions{.oneline_description="query_options"}},
},
@ -594,6 +595,7 @@ RPCHelpMan listunspent()
CAmount nMaximumAmount = MAX_MONEY;
CAmount nMinimumSumAmount = MAX_MONEY;
uint64_t nMaximumCount = 0;
bool include_immature_coinbase{false};
if (!request.params[4].isNull()) {
const UniValue& options = request.params[4].get_obj();
@ -604,6 +606,7 @@ RPCHelpMan listunspent()
{"maximumAmount", UniValueType()},
{"minimumSumAmount", UniValueType()},
{"maximumCount", UniValueType(UniValue::VNUM)},
{"include_immature_coinbase", UniValueType(UniValue::VBOOL)}
},
true, true);
@ -618,6 +621,10 @@ RPCHelpMan listunspent()
if (options.exists("maximumCount"))
nMaximumCount = options["maximumCount"].getInt<int64_t>();
if (options.exists("include_immature_coinbase")) {
include_immature_coinbase = options["include_immature_coinbase"].get_bool();
}
}
// Make sure the results are valid at least up to the most recent block
@ -633,7 +640,7 @@ RPCHelpMan listunspent()
cctl.m_max_depth = nMaxDepth;
cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet);
vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All();
vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, include_immature_coinbase).All();
}
LOCK(pwallet->cs_wallet);

View file

@ -195,7 +195,8 @@ CoinsResult AvailableCoins(const CWallet& wallet,
const CAmount& nMaximumAmount,
const CAmount& nMinimumSumAmount,
const uint64_t nMaximumCount,
bool only_spendable)
bool only_spendable,
bool include_immature_coinbase)
{
AssertLockHeld(wallet.cs_wallet);
@ -213,7 +214,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
const uint256& wtxid = entry.first;
const CWalletTx& wtx = entry.second;
if (wallet.IsTxImmatureCoinBase(wtx))
if (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)
continue;
int nDepth = wallet.GetTxDepthInMainChain(wtx);
@ -344,9 +345,9 @@ CoinsResult AvailableCoins(const CWallet& wallet,
return result;
}
CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount, bool include_immature_coinbase)
{
return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false);
return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false, include_immature_coinbase);
}
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)

View file

@ -65,13 +65,14 @@ CoinsResult AvailableCoins(const CWallet& wallet,
const CAmount& nMaximumAmount = MAX_MONEY,
const CAmount& nMinimumSumAmount = MAX_MONEY,
const uint64_t nMaximumCount = 0,
bool only_spendable = true) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
bool only_spendable = true,
bool include_immature_coinbase = false) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/**
* Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function
* to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction.
*/
CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, bool include_immature_coinbase = false) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);

View file

@ -77,8 +77,18 @@ class WalletTest(BitcoinTestFramework):
self.log.info("Mining blocks ...")
self.generate(self.nodes[0], 1)
self.generate(self.nodes[1], 1)
# Verify listunspent returns immature coinbase if 'include_immature_coinbase' is set
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1)
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 0)
self.generatetoaddress(self.nodes[1], COINBASE_MATURITY + 1, ADDRESS_WATCHONLY)
# Verify listunspent returns all immature coinbases if 'include_immature_coinbase' is set
# For now, only the legacy wallet will see the coinbases going to the imported 'ADDRESS_WATCHONLY'
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 1 if self.options.descriptors else 2)
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1 if self.options.descriptors else COINBASE_MATURITY + 2)
if not self.options.descriptors:
# Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets
assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)