mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
RPC: listunspent, add "include immature coinbase" flag
so we can return the immature coinbase UTXOs as well.
This commit is contained in:
parent
4f270d2b63
commit
f0f6a3577b
5 changed files with 32 additions and 7 deletions
6
doc/release-notes-25730.md
Normal file
6
doc/release-notes-25730.md
Normal 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)
|
|
@ -515,6 +515,7 @@ RPCHelpMan listunspent()
|
||||||
{"maximumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Maximum value of each UTXO in " + CURRENCY_UNIT + ""},
|
{"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"},
|
{"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 + ""},
|
{"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"}},
|
RPCArgOptions{.oneline_description="query_options"}},
|
||||||
},
|
},
|
||||||
|
@ -594,6 +595,7 @@ RPCHelpMan listunspent()
|
||||||
CAmount nMaximumAmount = MAX_MONEY;
|
CAmount nMaximumAmount = MAX_MONEY;
|
||||||
CAmount nMinimumSumAmount = MAX_MONEY;
|
CAmount nMinimumSumAmount = MAX_MONEY;
|
||||||
uint64_t nMaximumCount = 0;
|
uint64_t nMaximumCount = 0;
|
||||||
|
bool include_immature_coinbase{false};
|
||||||
|
|
||||||
if (!request.params[4].isNull()) {
|
if (!request.params[4].isNull()) {
|
||||||
const UniValue& options = request.params[4].get_obj();
|
const UniValue& options = request.params[4].get_obj();
|
||||||
|
@ -604,6 +606,7 @@ RPCHelpMan listunspent()
|
||||||
{"maximumAmount", UniValueType()},
|
{"maximumAmount", UniValueType()},
|
||||||
{"minimumSumAmount", UniValueType()},
|
{"minimumSumAmount", UniValueType()},
|
||||||
{"maximumCount", UniValueType(UniValue::VNUM)},
|
{"maximumCount", UniValueType(UniValue::VNUM)},
|
||||||
|
{"include_immature_coinbase", UniValueType(UniValue::VBOOL)}
|
||||||
},
|
},
|
||||||
true, true);
|
true, true);
|
||||||
|
|
||||||
|
@ -618,6 +621,10 @@ RPCHelpMan listunspent()
|
||||||
|
|
||||||
if (options.exists("maximumCount"))
|
if (options.exists("maximumCount"))
|
||||||
nMaximumCount = options["maximumCount"].getInt<int64_t>();
|
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
|
// 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_max_depth = nMaxDepth;
|
||||||
cctl.m_include_unsafe_inputs = include_unsafe;
|
cctl.m_include_unsafe_inputs = include_unsafe;
|
||||||
LOCK(pwallet->cs_wallet);
|
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);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
|
@ -195,7 +195,8 @@ CoinsResult AvailableCoins(const CWallet& wallet,
|
||||||
const CAmount& nMaximumAmount,
|
const CAmount& nMaximumAmount,
|
||||||
const CAmount& nMinimumSumAmount,
|
const CAmount& nMinimumSumAmount,
|
||||||
const uint64_t nMaximumCount,
|
const uint64_t nMaximumCount,
|
||||||
bool only_spendable)
|
bool only_spendable,
|
||||||
|
bool include_immature_coinbase)
|
||||||
{
|
{
|
||||||
AssertLockHeld(wallet.cs_wallet);
|
AssertLockHeld(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -213,7 +214,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
|
||||||
const uint256& wtxid = entry.first;
|
const uint256& wtxid = entry.first;
|
||||||
const CWalletTx& wtx = entry.second;
|
const CWalletTx& wtx = entry.second;
|
||||||
|
|
||||||
if (wallet.IsTxImmatureCoinBase(wtx))
|
if (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int nDepth = wallet.GetTxDepthInMainChain(wtx);
|
int nDepth = wallet.GetTxDepthInMainChain(wtx);
|
||||||
|
@ -344,9 +345,9 @@ CoinsResult AvailableCoins(const CWallet& wallet,
|
||||||
return result;
|
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)
|
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
|
||||||
|
|
|
@ -65,13 +65,14 @@ CoinsResult AvailableCoins(const CWallet& wallet,
|
||||||
const CAmount& nMaximumAmount = MAX_MONEY,
|
const CAmount& nMaximumAmount = MAX_MONEY,
|
||||||
const CAmount& nMinimumSumAmount = MAX_MONEY,
|
const CAmount& nMinimumSumAmount = MAX_MONEY,
|
||||||
const uint64_t nMaximumCount = 0,
|
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
|
* 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.
|
* 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);
|
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
|
||||||
|
|
||||||
|
|
|
@ -77,8 +77,18 @@ class WalletTest(BitcoinTestFramework):
|
||||||
self.log.info("Mining blocks ...")
|
self.log.info("Mining blocks ...")
|
||||||
self.generate(self.nodes[0], 1)
|
self.generate(self.nodes[0], 1)
|
||||||
self.generate(self.nodes[1], 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)
|
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:
|
if not self.options.descriptors:
|
||||||
# Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets
|
# 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)
|
assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)
|
||||||
|
|
Loading…
Add table
Reference in a new issue