Skip to content

Commit f0f6a35

Browse files
committed
RPC: listunspent, add "include immature coinbase" flag
so we can return the immature coinbase UTXOs as well.
1 parent 4f270d2 commit f0f6a35

File tree

5 files changed

+32
-7
lines changed

5 files changed

+32
-7
lines changed

doc/release-notes-25730.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
RPC Wallet
2+
----------
3+
4+
- RPC `listunspent` now has a new argument `include_immature_coinbase`
5+
to include coinbase UTXOs that don't meet the minimum spendability
6+
depth requirement (which before were silently skipped). (#25730)

src/wallet/rpc/coins.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ RPCHelpMan listunspent()
515515
{"maximumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Maximum value of each UTXO in " + CURRENCY_UNIT + ""},
516516
{"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"},
517517
{"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""},
518+
{"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase UTXOs"}
518519
},
519520
RPCArgOptions{.oneline_description="query_options"}},
520521
},
@@ -594,6 +595,7 @@ RPCHelpMan listunspent()
594595
CAmount nMaximumAmount = MAX_MONEY;
595596
CAmount nMinimumSumAmount = MAX_MONEY;
596597
uint64_t nMaximumCount = 0;
598+
bool include_immature_coinbase{false};
597599

598600
if (!request.params[4].isNull()) {
599601
const UniValue& options = request.params[4].get_obj();
@@ -604,6 +606,7 @@ RPCHelpMan listunspent()
604606
{"maximumAmount", UniValueType()},
605607
{"minimumSumAmount", UniValueType()},
606608
{"maximumCount", UniValueType(UniValue::VNUM)},
609+
{"include_immature_coinbase", UniValueType(UniValue::VBOOL)}
607610
},
608611
true, true);
609612

@@ -618,6 +621,10 @@ RPCHelpMan listunspent()
618621

619622
if (options.exists("maximumCount"))
620623
nMaximumCount = options["maximumCount"].getInt<int64_t>();
624+
625+
if (options.exists("include_immature_coinbase")) {
626+
include_immature_coinbase = options["include_immature_coinbase"].get_bool();
627+
}
621628
}
622629

623630
// Make sure the results are valid at least up to the most recent block
@@ -633,7 +640,7 @@ RPCHelpMan listunspent()
633640
cctl.m_max_depth = nMaxDepth;
634641
cctl.m_include_unsafe_inputs = include_unsafe;
635642
LOCK(pwallet->cs_wallet);
636-
vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All();
643+
vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, include_immature_coinbase).All();
637644
}
638645

639646
LOCK(pwallet->cs_wallet);

src/wallet/spend.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ CoinsResult AvailableCoins(const CWallet& wallet,
195195
const CAmount& nMaximumAmount,
196196
const CAmount& nMinimumSumAmount,
197197
const uint64_t nMaximumCount,
198-
bool only_spendable)
198+
bool only_spendable,
199+
bool include_immature_coinbase)
199200
{
200201
AssertLockHeld(wallet.cs_wallet);
201202

@@ -213,7 +214,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
213214
const uint256& wtxid = entry.first;
214215
const CWalletTx& wtx = entry.second;
215216

216-
if (wallet.IsTxImmatureCoinBase(wtx))
217+
if (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)
217218
continue;
218219

219220
int nDepth = wallet.GetTxDepthInMainChain(wtx);
@@ -344,9 +345,9 @@ CoinsResult AvailableCoins(const CWallet& wallet,
344345
return result;
345346
}
346347

347-
CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
348+
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)
348349
{
349-
return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false);
350+
return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false, include_immature_coinbase);
350351
}
351352

352353
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)

src/wallet/spend.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ CoinsResult AvailableCoins(const CWallet& wallet,
6565
const CAmount& nMaximumAmount = MAX_MONEY,
6666
const CAmount& nMinimumSumAmount = MAX_MONEY,
6767
const uint64_t nMaximumCount = 0,
68-
bool only_spendable = true) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
68+
bool only_spendable = true,
69+
bool include_immature_coinbase = false) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
6970

7071
/**
7172
* Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function
7273
* to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction.
7374
*/
74-
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);
75+
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);
7576

7677
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
7778

test/functional/wallet_balance.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,18 @@ def run_test(self):
7777
self.log.info("Mining blocks ...")
7878
self.generate(self.nodes[0], 1)
7979
self.generate(self.nodes[1], 1)
80+
81+
# Verify listunspent returns immature coinbase if 'include_immature_coinbase' is set
82+
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1)
83+
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 0)
84+
8085
self.generatetoaddress(self.nodes[1], COINBASE_MATURITY + 1, ADDRESS_WATCHONLY)
8186

87+
# Verify listunspent returns all immature coinbases if 'include_immature_coinbase' is set
88+
# For now, only the legacy wallet will see the coinbases going to the imported 'ADDRESS_WATCHONLY'
89+
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': False})), 1 if self.options.descriptors else 2)
90+
assert_equal(len(self.nodes[0].listunspent(query_options={'include_immature_coinbase': True})), 1 if self.options.descriptors else COINBASE_MATURITY + 2)
91+
8292
if not self.options.descriptors:
8393
# Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets
8494
assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)

0 commit comments

Comments
 (0)