Add RPC test for wallet_listunspent changes
This commit is contained in:
parent
b745992489
commit
2c487c9430
|
@ -62,6 +62,7 @@ BASE_SCRIPTS= [
|
|||
'wallet_overwintertx.py',
|
||||
'wallet_persistence.py',
|
||||
'wallet_listnotes.py',
|
||||
'wallet_listunspent.py',
|
||||
# vv Tests less than 60s vv
|
||||
'orchard_reorg.py',
|
||||
'fundrawtransaction.py',
|
||||
|
|
|
@ -56,17 +56,18 @@ class BitcoinTestFramework(object):
|
|||
# Connect the nodes as a "chain". This allows us
|
||||
# to split the network between nodes 1 and 2 to get
|
||||
# two halves that can work on competing chains.
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
|
||||
# If we joined network halves, connect the nodes from the joint
|
||||
# on outward. This ensures that chains are properly reorganised.
|
||||
if not split:
|
||||
connect_nodes_bi(self.nodes, 1, 2)
|
||||
sync_blocks(self.nodes[1:3])
|
||||
if do_mempool_sync:
|
||||
sync_mempools(self.nodes[1:3])
|
||||
if len(self.nodes) >= 4:
|
||||
connect_nodes_bi(self.nodes, 2, 3)
|
||||
if not split:
|
||||
connect_nodes_bi(self.nodes, 1, 2)
|
||||
sync_blocks(self.nodes[1:3])
|
||||
if do_mempool_sync:
|
||||
sync_mempools(self.nodes[1:3])
|
||||
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
connect_nodes_bi(self.nodes, 2, 3)
|
||||
self.is_network_split = split
|
||||
self.sync_all(do_mempool_sync)
|
||||
|
||||
|
|
|
@ -307,15 +307,15 @@ def initialize_chain(test_dir, num_nodes, cachedir, cache_behavior='current'):
|
|||
wait_bitcoinds()
|
||||
for i in range(MAX_NODES):
|
||||
# record the system time at which the cache was regenerated
|
||||
with open(log_filename(cachedir, i, 'cache_config.json'), "w", encoding="utf8") as cache_conf_file:
|
||||
with open(node_file(cachedir, i, 'cache_config.json'), "w", encoding="utf8") as cache_conf_file:
|
||||
cache_config = { "cache_time": time.time() }
|
||||
cache_conf_json = json.dumps(cache_config, indent=4)
|
||||
cache_conf_file.write(cache_conf_json)
|
||||
|
||||
os.remove(log_filename(cachedir, i, "debug.log"))
|
||||
os.remove(log_filename(cachedir, i, "db.log"))
|
||||
os.remove(log_filename(cachedir, i, "peers.dat"))
|
||||
os.remove(log_filename(cachedir, i, "fee_estimates.dat"))
|
||||
os.remove(node_file(cachedir, i, "debug.log"))
|
||||
os.remove(node_file(cachedir, i, "db.log"))
|
||||
os.remove(node_file(cachedir, i, "peers.dat"))
|
||||
os.remove(node_file(cachedir, i, "fee_estimates.dat"))
|
||||
|
||||
def init_from_cache():
|
||||
for i in range(num_nodes):
|
||||
|
@ -351,7 +351,7 @@ def initialize_chain(test_dir, num_nodes, cachedir, cache_behavior='current'):
|
|||
for i in range(MAX_NODES):
|
||||
node_path = os.path.join(cachedir, 'node'+str(i))
|
||||
if os.path.isdir(node_path):
|
||||
if not os.path.isfile(log_filename(cachedir, i, 'cache_config.json')):
|
||||
if not os.path.isfile(node_file(cachedir, i, 'cache_config.json')):
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
@ -461,8 +461,8 @@ def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, binary=None):
|
|||
raise
|
||||
return rpcs
|
||||
|
||||
def log_filename(dirname, n_node, logname):
|
||||
return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
|
||||
def node_file(dirname, n_node, filename):
|
||||
return os.path.join(dirname, "node"+str(n_node), "regtest", filename)
|
||||
|
||||
def check_node(i):
|
||||
bitcoind_processes[i].poll()
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2022 The Zcash developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
get_coinbase_address,
|
||||
nuparams,
|
||||
start_nodes,
|
||||
wait_and_assert_operationid_status,
|
||||
NU5_BRANCH_ID
|
||||
)
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
class WalletListUnspent(BitcoinTestFramework):
|
||||
def setup_nodes(self):
|
||||
return start_nodes(4, self.options.tmpdir, [[
|
||||
nuparams(NU5_BRANCH_ID, 201),
|
||||
]] * 4)
|
||||
|
||||
def run_test(self):
|
||||
assert_equal(self.nodes[0].getbalance(), 250)
|
||||
assert_equal(self.nodes[1].getbalance(), 250)
|
||||
|
||||
# Activate NU5
|
||||
self.nodes[1].generate(1) # height 201
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getbalance(), 260) # additional 10 ZEC matured
|
||||
|
||||
# Shield some coinbase funds so that they become spendable
|
||||
n1acct = self.nodes[1].z_getnewaccount()['account']
|
||||
n1uaddr = self.nodes[1].z_getaddressforaccount(n1acct)['address']
|
||||
opid = self.nodes[0].z_sendmany(
|
||||
get_coinbase_address(self.nodes[0]),
|
||||
[{'address': n1uaddr, 'amount': 10}],
|
||||
1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[1].generate(2)
|
||||
self.sync_all() # height 203
|
||||
assert_equal(self.nodes[0].getbalance(), 270) # 260 - 10 (spent) + 20 (matured)
|
||||
|
||||
assert_equal(
|
||||
self.nodes[1].z_getbalanceforaccount(n1acct, 1)['pools']['orchard']['valueZat'],
|
||||
Decimal('1000000000'))
|
||||
|
||||
# Get a bare legacy transparent address for node 0
|
||||
n0addr = self.nodes[0].getnewaddress()
|
||||
|
||||
# Send funds to the node 0 address so we have transparent funds to spend.
|
||||
opid = self.nodes[1].z_sendmany(
|
||||
n1uaddr,
|
||||
[{'address': n0addr, 'amount': 10}],
|
||||
1, 0, 'AllowRevealedRecipients')
|
||||
wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
|
||||
self.sync_all()
|
||||
self.nodes[1].generate(2)
|
||||
self.sync_all() # height 205
|
||||
assert_equal(self.nodes[0].getbalance(), 300) # 270 + 20 (matured) + 10 (received)
|
||||
|
||||
print("----------------------------------------------------------------")
|
||||
unspent_205 = self.nodes[0].listunspent(0, 999999, [])
|
||||
for item in sorted(unspent_205, key=lambda item: item['confirmations']):
|
||||
print(str(item))
|
||||
|
||||
# We will then perform several spends, and then check the list of
|
||||
# unspent notes as of various heights.
|
||||
|
||||
opid = self.nodes[0].z_sendmany(
|
||||
'ANY_TADDR',
|
||||
[{'address': n1uaddr, 'amount': 2}],
|
||||
1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(2)
|
||||
self.sync_all() # height 207
|
||||
assert_equal(self.nodes[0].getbalance(), 318) # 300 + 20 (matured) - 2 (sent)
|
||||
|
||||
opid = self.nodes[0].z_sendmany(
|
||||
'ANY_TADDR',
|
||||
[{'address': n1uaddr, 'amount': 3}],
|
||||
1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(2)
|
||||
self.sync_all() # height 209
|
||||
assert_equal(self.nodes[0].getbalance(), 335) # 318 + 20 (matured) - 3 (sent)
|
||||
|
||||
opid = self.nodes[0].z_sendmany(
|
||||
'ANY_TADDR',
|
||||
[{'address': n1uaddr, 'amount': 5}],
|
||||
1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(2)
|
||||
self.sync_all() # height 211
|
||||
assert_equal(self.nodes[0].getbalance(), 350) # 325 + 20 (matured) - 5 (sent)
|
||||
|
||||
def unspent_total(unspent):
|
||||
total = 0
|
||||
for item in unspent:
|
||||
total += item['amount']
|
||||
return total
|
||||
|
||||
unspent_205 = self.nodes[0].listunspent(0, 999999, [], 205)
|
||||
print("----------------------------------------------------------------")
|
||||
for item in sorted(unspent_205, key=lambda item: item['confirmations']):
|
||||
print(str(item))
|
||||
assert_equal(unspent_total(self.nodes[0].listunspent(0, 999999, [], 205)), 300)
|
||||
assert_equal(unspent_total(self.nodes[0].listunspent(0, 999999, [], 207)), 318)
|
||||
assert_equal(unspent_total(self.nodes[0].listunspent(0, 999999, [], 209)), 335)
|
||||
assert_equal(unspent_total(self.nodes[0].listunspent(0, 999999, [], 211)), 350)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletListUnspent().main()
|
|
@ -2396,7 +2396,7 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
|
||||
if (fHelp || params.size() > 4)
|
||||
throw runtime_error(
|
||||
"listunspent ( minconf maxconf [\"address\",...] unspent_as_of)\n"
|
||||
"listunspent ( minconf maxconf [\"address\",...] as_of_height)\n"
|
||||
"\nReturns array of unspent transparent transaction outputs with between minconf and\n"
|
||||
"maxconf (inclusive) confirmations. Use `z_listunspent` instead to see information\n"
|
||||
"related to unspent shielded notes. Results may be optionally filtered to only include\n"
|
||||
|
@ -2462,16 +2462,9 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
std::optional<int> unspentAsOfDepth;
|
||||
std::optional<int> asOfHeight;
|
||||
if (params.size() > 3) {
|
||||
auto asOfHeight = params[3].get_int();
|
||||
// it's fine to specify a height that's greater than the current height
|
||||
if (chainActive.Height() > asOfHeight) {
|
||||
unspentAsOfDepth = chainActive.Height() - asOfHeight;
|
||||
if (unspentAsOfDepth.value() > nMinDepth) {
|
||||
nMinDepth = unspentAsOfDepth.value();
|
||||
}
|
||||
}
|
||||
asOfHeight = params[3].get_int();
|
||||
}
|
||||
|
||||
UniValue results(UniValue::VARR);
|
||||
|
@ -2485,7 +2478,7 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
false, // fOnlySpendable
|
||||
nMinDepth,
|
||||
destinations.empty() ? nullptr : &destinations,
|
||||
unspentAsOfDepth);
|
||||
asOfHeight);
|
||||
for (const COutput& out : vecOutputs) {
|
||||
if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth)
|
||||
continue;
|
||||
|
|
|
@ -2413,7 +2413,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
* Outpoint is spent if any non-conflicted transaction
|
||||
* spends it:
|
||||
*/
|
||||
bool CWallet::IsSpent(const uint256& hash, unsigned int n, std::optional<int> unspentAsOfDepth) const
|
||||
bool CWallet::IsSpent(const uint256& hash, unsigned int n, std::optional<int> asOfDepth) const
|
||||
{
|
||||
const COutPoint outpoint(hash, n);
|
||||
pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
|
||||
|
@ -2423,8 +2423,11 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n, std::optional<int> un
|
|||
{
|
||||
const uint256& wtxid = it->second;
|
||||
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= unspentAsOfDepth.value_or(0))
|
||||
return true; // Spent
|
||||
if (mit != mapWallet.end()) {
|
||||
auto confirmations = mit->second.GetDepthInMainChain();
|
||||
if (confirmations >= asOfDepth.value_or(0))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -5247,7 +5250,7 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins,
|
|||
bool fOnlySpendable,
|
||||
int nMinDepth,
|
||||
std::set<CTxDestination>* onlyFilterByDests,
|
||||
std::optional<int> unspentAsOfDepth) const
|
||||
const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
assert(nMinDepth >= 0);
|
||||
AssertLockHeld(cs_main);
|
||||
|
@ -5256,30 +5259,26 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins,
|
|||
vCoins.clear();
|
||||
|
||||
{
|
||||
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
|
||||
{
|
||||
const uint256& wtxid = it->first;
|
||||
const CWalletTx* pcoin = &(*it).second;
|
||||
|
||||
if (!CheckFinalTx(*pcoin))
|
||||
for (const auto& [wtxid, pcoin] : mapWallet) {
|
||||
if (!CheckFinalTx(pcoin))
|
||||
continue;
|
||||
|
||||
if (fOnlyConfirmed && !pcoin->IsTrusted())
|
||||
if (fOnlyConfirmed && !pcoin.IsTrusted())
|
||||
continue;
|
||||
|
||||
if (pcoin->IsCoinBase() && !fIncludeCoinBase)
|
||||
if (pcoin.IsCoinBase() && !fIncludeCoinBase)
|
||||
continue;
|
||||
|
||||
bool isCoinbase = pcoin->IsCoinBase();
|
||||
if (isCoinbase && pcoin->GetBlocksToMaturity() > 0)
|
||||
bool isCoinbase = pcoin.IsCoinBase();
|
||||
if (isCoinbase && pcoin.GetBlocksToMaturity(asOfHeight) > 0)
|
||||
continue;
|
||||
|
||||
int nDepth = pcoin->GetDepthInMainChain();
|
||||
int nDepth = pcoin.GetDepthInMainChain();
|
||||
if (nDepth < nMinDepth)
|
||||
continue;
|
||||
|
||||
for (unsigned int i = 0; i < pcoin->vout.size(); i++) {
|
||||
const auto& output = pcoin->vout[i];
|
||||
for (unsigned int i = 0; i < pcoin.vout.size(); i++) {
|
||||
const auto& output = pcoin.vout[i];
|
||||
isminetype mine = IsMine(output);
|
||||
|
||||
bool isSpendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
|
||||
|
@ -5296,10 +5295,10 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins,
|
|||
}
|
||||
}
|
||||
|
||||
if (!(IsSpent(wtxid, i, unspentAsOfDepth)) && mine != ISMINE_NO &&
|
||||
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
|
||||
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i)))
|
||||
vCoins.push_back(COutput(pcoin, i, nDepth, isSpendable, isCoinbase));
|
||||
if (!(IsSpent(wtxid, i, asOfHeight)) && mine != ISMINE_NO &&
|
||||
!IsLockedCoin(wtxid, i) && (pcoin.vout[i].nValue > 0 || fIncludeZeroValue) &&
|
||||
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected(wtxid, i)))
|
||||
vCoins.push_back(COutput(&pcoin, i, nDepth, isSpendable, isCoinbase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6985,7 +6984,7 @@ void CMerkleTx::SetMerkleBranch(const CBlock& block)
|
|||
}
|
||||
}
|
||||
|
||||
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
|
||||
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet, const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
if (hashBlock.IsNull() || nIndex == -1)
|
||||
return 0;
|
||||
|
@ -6996,8 +6995,11 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
|
|||
if (mi == mapBlockIndex.end())
|
||||
return 0;
|
||||
CBlockIndex* pindex = (*mi).second;
|
||||
if (!pindex || !chainActive.Contains(pindex))
|
||||
if (!pindex ||
|
||||
!chainActive.Contains(pindex) ||
|
||||
(asOfHeight.has_value() && pindex->nHeight > asOfHeight.value())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pindexRet = pindex;
|
||||
return chainActive.Height() - pindex->nHeight + 1;
|
||||
|
@ -7013,7 +7015,7 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
|
|||
return nResult;
|
||||
}
|
||||
|
||||
int CMerkleTx::GetBlocksToMaturity() const
|
||||
int CMerkleTx::GetBlocksToMaturity(const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
if (!IsCoinBase())
|
||||
return 0;
|
||||
|
|
|
@ -399,7 +399,7 @@ struct SaplingNoteEntry
|
|||
class CMerkleTx : public CTransaction
|
||||
{
|
||||
private:
|
||||
int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const;
|
||||
int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet, const std::optional<int>& asOfHeight = std::nullopt) const;
|
||||
|
||||
public:
|
||||
uint256 hashBlock;
|
||||
|
@ -444,7 +444,7 @@ public:
|
|||
int GetDepthInMainChain(const CBlockIndex* &pindexRet) const;
|
||||
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
|
||||
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
|
||||
int GetBlocksToMaturity() const;
|
||||
int GetBlocksToMaturity(const std::optional<int>& asOfHeight = std::nullopt) const;
|
||||
/** Pass this transaction to the mempool. Fails if absolute fee exceeds maxTxFee. */
|
||||
bool AcceptToMemoryPool(CValidationState& state, bool fLimitFree=true, bool fRejectAbsurdFee=true);
|
||||
};
|
||||
|
@ -1437,7 +1437,7 @@ public:
|
|||
bool fOnlySpendable=false,
|
||||
int nMinDepth = 0,
|
||||
std::set<CTxDestination>* onlyFilterByDests = nullptr,
|
||||
std::optional<int> unspentAsOfDepth = std::nullopt) const;
|
||||
const std::optional<int>& unspentAsOfDepth = std::nullopt) const;
|
||||
|
||||
/**
|
||||
* Shuffle and select coins until nTargetValue is reached while avoiding
|
||||
|
|
Loading…
Reference in New Issue