Merge pull request #6122 from nuttycom/wallet/listunspent_as_of
Add `asOfHeight` argument across the RPC API
This commit is contained in:
commit
f6a4f68115
|
@ -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',
|
||||
|
|
|
@ -7,9 +7,17 @@
|
|||
# Exercise the listtransactions API
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
def count_array_matches(object_array, to_match):
|
||||
num_matched = 0
|
||||
for item in object_array:
|
||||
if all((item[key] == value for key,value in to_match.items())):
|
||||
num_matched = num_matched+1
|
||||
return num_matched
|
||||
|
||||
def check_array_result(object_array, to_match, expected):
|
||||
"""
|
||||
Pass in array of JSON objects, a dictionary with key/value pairs
|
||||
|
@ -27,7 +35,7 @@ def check_array_result(object_array, to_match, expected):
|
|||
for key,value in expected.items():
|
||||
if item[key] != value:
|
||||
raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value)))
|
||||
num_matched = num_matched+1
|
||||
num_matched = num_matched+1
|
||||
if num_matched == 0:
|
||||
raise AssertionError("No objects matched %s"%(str(to_match)))
|
||||
|
||||
|
@ -45,6 +53,7 @@ class ListTransactionsTest(BitcoinTestFramework):
|
|||
{"category":"receive","amount":Decimal("0.1"),"amountZat":10000000,"confirmations":0})
|
||||
|
||||
# mine a block, confirmations should change:
|
||||
old_block = self.nodes[0].getblockcount()
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
check_array_result(self.nodes[0].listtransactions(),
|
||||
|
@ -53,6 +62,16 @@ class ListTransactionsTest(BitcoinTestFramework):
|
|||
check_array_result(self.nodes[1].listtransactions(),
|
||||
{"txid":txid},
|
||||
{"category":"receive","amount":Decimal("0.1"),"amountZat":10000000,"confirmations":1})
|
||||
# Confirmations here are -1 instead of 0 because while we only went back
|
||||
# 1 block, “0” means the tx is in the mempool, but the tx has already
|
||||
# been mined and `asOfHeight` ignores the mempool regardless.
|
||||
check_array_result(self.nodes[0].listtransactions("*", 10, 0, False, old_block),
|
||||
{"txid":txid},
|
||||
{"category":"send","amount":Decimal("-0.1"),"amountZat":-10000000,"confirmations":-1})
|
||||
# And while we can still see the tx we sent before the block where they
|
||||
# got mined, we don’t have access to ones we’ll receive after the
|
||||
# specified block.
|
||||
assert_equal(0, count_array_matches(self.nodes[1].listtransactions("*", 10, 0, False, old_block), {"txid":txid}))
|
||||
|
||||
# send-to-self:
|
||||
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2)
|
||||
|
|
|
@ -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,130 @@
|
|||
#!/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
|
||||
|
||||
def unspent_total(unspent):
|
||||
return sum((item['amount'] for item in unspent))
|
||||
|
||||
class WalletListUnspent(BitcoinTestFramework):
|
||||
def setup_nodes(self):
|
||||
return start_nodes(4, self.options.tmpdir, [[
|
||||
nuparams(NU5_BRANCH_ID, 201),
|
||||
]] * 4)
|
||||
|
||||
def matured_at_height(self, height):
|
||||
return unspent_total(self.nodes[0].listunspent(1, 999999, [], False, {}, height))
|
||||
|
||||
def run_test(self):
|
||||
def expected_matured_at_height(height):
|
||||
return (height-200)*10 + 250
|
||||
|
||||
assert_equal(self.nodes[0].getbalance(), expected_matured_at_height(200))
|
||||
assert_equal(self.nodes[1].getbalance(), expected_matured_at_height(200))
|
||||
|
||||
# Activate NU5
|
||||
self.nodes[1].generate(1) # height 201
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getbalance(), expected_matured_at_height(201))
|
||||
# check balances from before the latest tx
|
||||
assert_equal(self.nodes[0].getbalance("", 1, False, False, 200), expected_matured_at_height(200))
|
||||
assert_equal(self.matured_at_height(200), expected_matured_at_height(200))
|
||||
|
||||
# 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(), expected_matured_at_height(203) - 10)
|
||||
|
||||
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(), expected_matured_at_height(205) - 10 + 10)
|
||||
|
||||
# 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',
|
||||
# FIXME: #6262 The amount here _should_ be 2, but because of a
|
||||
# bug in the selector for `ANY_TADDR`, it’s selecting
|
||||
# transparent coinbase, which also means we can’t have
|
||||
# change. When that bug is fixed, the test should fail
|
||||
# here, and we can switch it back to 2 (and cascade the
|
||||
# corrected amounts mentioned below.
|
||||
[{'address': n1uaddr, 'amount': 10}],
|
||||
1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(2)
|
||||
self.sync_all() # height 207
|
||||
# FIXME: #6262, should be `expected_matured_at_height(207) - 10 + 10 - 2`
|
||||
assert_equal(self.nodes[0].getbalance(), expected_matured_at_height(207) - 10 + 10 - 10)
|
||||
|
||||
opid = self.nodes[0].z_sendmany(
|
||||
'ANY_TADDR',
|
||||
# FIXME: Should be 3 (see above)
|
||||
[{'address': n1uaddr, 'amount': 10}],
|
||||
1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(2)
|
||||
self.sync_all() # height 209
|
||||
# FIXME: #6262, should be `expected_matured_at_height(209) - 10 + 10 - 2 - 3`
|
||||
assert_equal(self.nodes[0].getbalance(), expected_matured_at_height(209) - 10 + 10 - 10 - 10)
|
||||
|
||||
opid = self.nodes[0].z_sendmany(
|
||||
'ANY_TADDR',
|
||||
# FIXME: Should be 5 (see above)
|
||||
[{'address': n1uaddr, 'amount': 10}],
|
||||
1, 0, 'AllowRevealedSenders')
|
||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||
|
||||
self.nodes[0].generate(2)
|
||||
self.sync_all() # height 211
|
||||
# FIXME: #6262, should be `expected_matured_at_height(211) - 10 + 10 - 2 - 3 - 5`
|
||||
assert_equal(self.nodes[0].getbalance(), expected_matured_at_height(211) - 10 + 10 - 10 - 10 - 10)
|
||||
|
||||
# check balances at various past points in the chain
|
||||
# FIXME: #6262, change the comparison amounts when the above changes are made.
|
||||
assert_equal(self.matured_at_height(205), expected_matured_at_height(205) - 10 + 10)
|
||||
assert_equal(self.matured_at_height(207), expected_matured_at_height(207) - 10 + 10 - 10)
|
||||
assert_equal(self.matured_at_height(209), expected_matured_at_height(209) - 10 + 10 - 10 - 10)
|
||||
assert_equal(self.matured_at_height(211), expected_matured_at_height(211) - 10 + 10 - 10 - 10 - 10)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletListUnspent().main()
|
|
@ -93,7 +93,7 @@ UniValue getinfo(const UniValue& params, bool fHelp)
|
|||
#ifdef ENABLE_WALLET
|
||||
if (pwalletMain) {
|
||||
obj.pushKV("walletversion", pwalletMain->GetVersion());
|
||||
obj.pushKV("balance", ValueFromAmount(pwalletMain->GetBalance()));
|
||||
obj.pushKV("balance", ValueFromAmount(pwalletMain->GetBalance(std::nullopt)));
|
||||
}
|
||||
#endif
|
||||
obj.pushKV("blocks", (int)chainActive.Height());
|
||||
|
|
|
@ -550,6 +550,55 @@ std::string experimentalDisabledHelpMsg(const std::string& rpc, const std::vecto
|
|||
+ config;
|
||||
}
|
||||
|
||||
std::string asOfHeightMessage(bool hasMinconf) {
|
||||
std::string minconfInteraction = hasMinconf
|
||||
? " `minconf` must be at least 1 when `asOfHeight` is provided.\n"
|
||||
: "";
|
||||
return
|
||||
"asOfHeight (numeric, optional, default=-1) Execute the query as if it\n"
|
||||
" were run when the blockchain was at the height specified by\n"
|
||||
" this argument. The default is to use the entire blockchain\n"
|
||||
" that the node is aware of. -1 can be used as in other RPC\n"
|
||||
" calls to indicate the current height (including the\n"
|
||||
" mempool), but this does not support negative values in\n"
|
||||
" general. A “future” height will fall back to the current\n"
|
||||
" height. Any explicit value will cause the mempool to be\n"
|
||||
" ignored, meaning no unconfirmed tx will be considered.\n"
|
||||
+ minconfInteraction;
|
||||
}
|
||||
|
||||
std::optional<int> parseAsOfHeight(const UniValue& params, int index) {
|
||||
std::optional<int> asOfHeight;
|
||||
if (params.size() > index) {
|
||||
auto requestedHeight = params[index].get_int();
|
||||
if (requestedHeight == -1) {
|
||||
// the default, do nothing
|
||||
} else if (requestedHeight < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Can not perform the query as of a negative block height");
|
||||
} else if (requestedHeight == 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Can not perform the query as of the genesis block");
|
||||
} else {
|
||||
asOfHeight = requestedHeight;
|
||||
}
|
||||
}
|
||||
return asOfHeight;
|
||||
}
|
||||
|
||||
int parseMinconf(int defaultValue, const UniValue& params, int index, const std::optional<int>& asOfHeight) {
|
||||
int nMinDepth = defaultValue;
|
||||
if (params.size() > index) {
|
||||
auto requestedDepth = params[index].get_int();
|
||||
if (requestedDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
} else if (requestedDepth == 0 && asOfHeight.has_value()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Require a minimum of 1 confirmation when `asOfHeight` is provided");
|
||||
} else {
|
||||
nMinDepth = requestedDepth;
|
||||
}
|
||||
}
|
||||
return nMinDepth;
|
||||
}
|
||||
|
||||
void RPCRegisterTimerInterface(RPCTimerInterface *iface)
|
||||
{
|
||||
timerInterfaces.push_back(iface);
|
||||
|
|
|
@ -192,6 +192,10 @@ std::string JSONRPCExecBatch(const UniValue& vReq);
|
|||
|
||||
extern std::string experimentalDisabledHelpMsg(const std::string& rpc, const std::vector<std::string>& enableArgs);
|
||||
|
||||
std::string asOfHeightMessage(bool hasMinconf);
|
||||
std::optional<int> parseAsOfHeight(const UniValue& params, int index);
|
||||
int parseMinconf(int defaultValue, const UniValue& params, int index, const std::optional<int>& asOfHeight);
|
||||
|
||||
extern int interpretHeightArg(int nHeight, int currentHeight);
|
||||
extern int parseHeightArg(const std::string& strHeight, int currentHeight);
|
||||
|
||||
|
|
|
@ -639,7 +639,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
|||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
|
||||
}
|
||||
wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight;
|
||||
wtxDepth = wtx.GetDepthInMainChain();
|
||||
wtxDepth = wtx.GetDepthInMainChain(std::nullopt);
|
||||
}
|
||||
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vJoinSplit=%d, jsoutindex=%d, amount=%s, height=%d, confirmations=%d)\n",
|
||||
getId(),
|
||||
|
|
|
@ -86,7 +86,7 @@ bool AsyncRPCOperation_saplingmigration::main_impl() {
|
|||
// We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
|
||||
// an anchor at height N-10 for each Sprout JoinSplit description
|
||||
// Consider, should notes be sorted?
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 11);
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 11);
|
||||
}
|
||||
CAmount availableFunds = 0;
|
||||
for (const SproutNoteEntry& sproutEntry : sproutEntries) {
|
||||
|
|
|
@ -192,7 +192,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
|||
SpendableInputs spendable;
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
spendable = pwalletMain->FindSpendableInputs(ztxoSelector_, allowTransparentCoinbase, mindepth_);
|
||||
spendable = pwalletMain->FindSpendableInputs(ztxoSelector_, allowTransparentCoinbase, mindepth_, std::nullopt);
|
||||
}
|
||||
if (!spendable.LimitToAmount(targetAmount, dustThreshold, recipientPools_)) {
|
||||
CAmount changeAmount{spendable.Total() - targetAmount};
|
||||
|
|
|
@ -214,18 +214,18 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
|||
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
wallet.LoadWalletTx(wtx);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
|
||||
std::vector<SproutNoteEntry> sproutEntries;
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 0);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, -1);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, -1);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
@ -244,21 +244,21 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
|||
|
||||
wtx.SetMerkleBranch(block);
|
||||
wallet.LoadWalletTx(wtx);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
|
||||
// We now have an unspent and confirmed note in the wallet (depth of 1)
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 0);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 1);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 1);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 2);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
@ -268,7 +268,7 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
|||
// Let's spend the note.
|
||||
auto wtx2 = GetValidSproutSpend(sk, note, 5);
|
||||
wallet.LoadWalletTx(wtx2);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
// Fake-mine a spend transaction
|
||||
EXPECT_EQ(0, chainActive.Height());
|
||||
|
@ -286,22 +286,22 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
|||
|
||||
wtx2.SetMerkleBranch(block2);
|
||||
wallet.LoadWalletTx(wtx2);
|
||||
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_TRUE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
// The note has been spent. By default, GetFilteredNotes() ignores spent notes.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 0);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
// Let's include spent notes to retrieve it.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0, INT_MAX, false);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 0, INT_MAX, false);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
// The spent note has two confirmations.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2, INT_MAX, false);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 2, INT_MAX, false);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
@ -328,7 +328,7 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
|||
|
||||
wtx.SetSproutNoteData(noteData);
|
||||
wallet.LoadWalletTx(wtx);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
wtx3 = wtx;
|
||||
}
|
||||
|
@ -351,25 +351,25 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
|||
wallet.LoadWalletTx(wtx3);
|
||||
|
||||
// We now have an unspent note which has one confirmation, in addition to our spent note.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 1);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 1);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
// Let's return the spent note too.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 1, INT_MAX, false);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 1, INT_MAX, false);
|
||||
EXPECT_EQ(2, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
// Increasing number of confirmations will exclude our new unspent note.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2, INT_MAX, false);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 2, INT_MAX, false);
|
||||
EXPECT_EQ(1, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
orchardEntries.clear();
|
||||
// If we also ignore spent notes at this depth, we won't find any notes.
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2, INT_MAX, true);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, 2, INT_MAX, true);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
sproutEntries.clear();
|
||||
saplingEntries.clear();
|
||||
|
@ -877,7 +877,7 @@ TEST(WalletTests, GetConflictedOrchardNotes) {
|
|||
std::vector<SproutNoteEntry> sproutEntries;
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, -1);
|
||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, std::nullopt, -1);
|
||||
EXPECT_EQ(0, sproutEntries.size());
|
||||
EXPECT_EQ(0, saplingEntries.size());
|
||||
EXPECT_EQ(1, orchardEntries.size());
|
||||
|
@ -957,14 +957,14 @@ TEST(WalletTests, SproutNullifierIsSpent) {
|
|||
auto note = GetSproutNote(sk, wtx, 0, 1);
|
||||
auto nullifier = note.nullifier(sk);
|
||||
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
wallet.LoadWalletTx(wtx);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
auto wtx2 = GetValidSproutSpend(sk, note, 5);
|
||||
wallet.LoadWalletTx(wtx2);
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
// Fake-mine the transaction
|
||||
EXPECT_EQ(-1, chainActive.Height());
|
||||
|
@ -980,7 +980,7 @@ TEST(WalletTests, SproutNullifierIsSpent) {
|
|||
|
||||
wtx2.SetMerkleBranch(block);
|
||||
wallet.LoadWalletTx(wtx2);
|
||||
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
|
||||
EXPECT_TRUE(wallet.IsSproutSpent(nullifier, std::nullopt));
|
||||
|
||||
// Tear down
|
||||
chainActive.SetTip(NULL);
|
||||
|
@ -1018,7 +1018,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
|
|||
uint256 nullifier = nf.value();
|
||||
|
||||
// Verify note has not been spent
|
||||
EXPECT_FALSE(wallet.IsSaplingSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSaplingSpent(nullifier, std::nullopt));
|
||||
|
||||
// Fake-mine the transaction
|
||||
EXPECT_EQ(-1, chainActive.Height());
|
||||
|
@ -1036,7 +1036,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
|
|||
wallet.LoadWalletTx(wtx);
|
||||
|
||||
// Verify note has been spent
|
||||
EXPECT_TRUE(wallet.IsSaplingSpent(nullifier));
|
||||
EXPECT_TRUE(wallet.IsSaplingSpent(nullifier, std::nullopt));
|
||||
|
||||
// Tear down
|
||||
chainActive.SetTip(NULL);
|
||||
|
@ -1107,7 +1107,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
|||
MerkleFrontiers frontiers = { .sapling = testNote.tree };
|
||||
|
||||
// Verify dummy note is unspent
|
||||
EXPECT_FALSE(wallet.IsSaplingSpent(nullifier));
|
||||
EXPECT_FALSE(wallet.IsSaplingSpent(nullifier, std::nullopt));
|
||||
|
||||
// Fake-mine the transaction
|
||||
EXPECT_EQ(-1, chainActive.Height());
|
||||
|
@ -1129,7 +1129,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
|||
wallet.LoadWalletTx(wtx);
|
||||
|
||||
// Verify dummy note is now spent, as AddToWallet invokes AddToSpends()
|
||||
EXPECT_TRUE(wallet.IsSaplingSpent(nullifier));
|
||||
EXPECT_TRUE(wallet.IsSaplingSpent(nullifier, std::nullopt));
|
||||
|
||||
// Test invariant: no witnesses means no nullifier.
|
||||
EXPECT_EQ(0, wallet.mapSaplingNullifiersToNotes.size());
|
||||
|
@ -1334,7 +1334,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
|||
wallet.LoadWalletTx(wtx2);
|
||||
|
||||
// Verify note B is spent. LoadWalletTx invokes AddToSpends which updates mapTxSaplingNullifiers
|
||||
EXPECT_TRUE(wallet.IsSaplingSpent(nullifier2));
|
||||
EXPECT_TRUE(wallet.IsSaplingSpent(nullifier2, std::nullopt));
|
||||
|
||||
// Verify note B belongs to wallet.
|
||||
EXPECT_TRUE(wallet.IsFromMe(wtx2));
|
||||
|
|
|
@ -115,9 +115,9 @@ void ThrowIfInitialBlockDownload()
|
|||
}
|
||||
}
|
||||
|
||||
void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
|
||||
void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry, const std::optional<int>& asOfHeight)
|
||||
{
|
||||
int confirms = wtx.GetDepthInMainChain();
|
||||
int confirms = wtx.GetDepthInMainChain(asOfHeight);
|
||||
std::string status = "waiting";
|
||||
|
||||
entry.pushKV("confirmations", confirms);
|
||||
|
@ -264,7 +264,7 @@ UniValue getrawchangeaddress(const UniValue& params, bool fHelp)
|
|||
|
||||
static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
|
||||
{
|
||||
CAmount curBalance = pwalletMain->GetBalance();
|
||||
CAmount curBalance = pwalletMain->GetBalance(std::nullopt);
|
||||
|
||||
// Check amount
|
||||
if (nValue <= 0)
|
||||
|
@ -285,7 +285,7 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr
|
|||
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
|
||||
vecSend.push_back(recipient);
|
||||
if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
|
||||
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance())
|
||||
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance(std::nullopt))
|
||||
strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
||||
}
|
||||
|
@ -484,7 +484,7 @@ UniValue listaddresses(const UniValue& params, bool fHelp)
|
|||
// with respect to the entries in the address book for addresses generated by this wallet,
|
||||
// there is not a guarantee that an externally generated address (such as one associated with
|
||||
// a future unified incoming viewing key) will have been added to the address book.
|
||||
for (const std::pair<CTxDestination, CAmount>& item : pwalletMain->GetAddressBalances()) {
|
||||
for (const std::pair<CTxDestination, CAmount>& item : pwalletMain->GetAddressBalances(std::nullopt)) {
|
||||
if (t_generated_dests.count(item.first) == 0 &&
|
||||
t_mnemonic_dests.count(item.first) == 0 &&
|
||||
t_imported_dests.count(item.first) == 0 &&
|
||||
|
@ -841,11 +841,13 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp)
|
||||
if (fHelp || params.size() > 1)
|
||||
throw runtime_error(
|
||||
"listaddressgroupings\n"
|
||||
"listaddressgroupings ( asOfHeight )\n"
|
||||
"\nLists groups of transparent addresses which have had their common ownership\n"
|
||||
"made public by common use as inputs or as the resulting change in past transactions.\n"
|
||||
"\nArguments:\n"
|
||||
"1. " + asOfHeightMessage(false) +
|
||||
"\nResult:\n"
|
||||
"[\n"
|
||||
" [\n"
|
||||
|
@ -857,6 +859,8 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp)
|
|||
" ]\n"
|
||||
" ,...\n"
|
||||
"]\n"
|
||||
"\nBitcoin compatibility:\n"
|
||||
"The zero-argument form is compatible."
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("listaddressgroupings", "")
|
||||
+ HelpExampleRpc("listaddressgroupings", "")
|
||||
|
@ -864,9 +868,11 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp)
|
|||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 0);
|
||||
|
||||
KeyIO keyIO(Params());
|
||||
UniValue jsonGroupings(UniValue::VARR);
|
||||
std::map<CTxDestination, CAmount> balances = pwalletMain->GetAddressBalances();
|
||||
std::map<CTxDestination, CAmount> balances = pwalletMain->GetAddressBalances(asOfHeight);
|
||||
for (const std::set<CTxDestination>& grouping : pwalletMain->GetAddressGroupings()) {
|
||||
UniValue jsonGrouping(UniValue::VARR);
|
||||
for (const CTxDestination& address : grouping)
|
||||
|
@ -952,16 +958,19 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() < 1 || params.size() > 3)
|
||||
if (fHelp || params.size() < 1 || params.size() > 4)
|
||||
throw runtime_error(
|
||||
"getreceivedbyaddress \"zcashaddress\" ( minconf ) ( inZat )\n"
|
||||
"getreceivedbyaddress \"zcashaddress\" ( minconf inZat asOfHeight )\n"
|
||||
"\nReturns the total amount received by the given transparent Zcash address in transactions with at least minconf confirmations.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"zcashaddress\" (string, required) The Zcash address for transactions.\n"
|
||||
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
|
||||
"3. inZat (bool, optional, default=false) Get the result amount in " + MINOR_CURRENCY_UNIT + " (as an integer).\n"
|
||||
"4. " + asOfHeightMessage(true) +
|
||||
"\nResult:\n"
|
||||
"amount (numeric) The total amount in " + CURRENCY_UNIT + "(or " + MINOR_CURRENCY_UNIT + " if inZat is true) received at this address.\n"
|
||||
"\nBitcoin compatibility:\n"
|
||||
"Compatible with up to two arguments."
|
||||
"\nExamples:\n"
|
||||
"\nThe amount from transactions with at least 1 confirmation\n"
|
||||
+ HelpExampleCli("getreceivedbyaddress", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\"") +
|
||||
|
@ -987,10 +996,10 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
return ValueFromAmount(0);
|
||||
}
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 3);
|
||||
|
||||
// Minimum confirmations
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 1)
|
||||
nMinDepth = params[1].get_int();
|
||||
int nMinDepth = parseMinconf(1, params, 1, asOfHeight);
|
||||
|
||||
// Tally
|
||||
CAmount nAmount = 0;
|
||||
|
@ -1002,7 +1011,7 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
|
||||
for (const CTxOut& txout : wtx.vout)
|
||||
if (txout.scriptPubKey == scriptPubKey)
|
||||
if (wtx.GetDepthInMainChain() >= nMinDepth)
|
||||
if (wtx.GetDepthInMainChain(asOfHeight) >= nMinDepth)
|
||||
nAmount += txout.nValue;
|
||||
}
|
||||
|
||||
|
@ -1019,19 +1028,22 @@ UniValue getbalance(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() > 4)
|
||||
if (fHelp || params.size() > 5)
|
||||
throw runtime_error(
|
||||
"getbalance ( \"(dummy)\" minconf includeWatchonly inZat )\n"
|
||||
"getbalance ( \"(dummy)\" minconf includeWatchonly inZat asOfHeight )\n"
|
||||
"\nReturns the wallet's available transparent balance. This total\n"
|
||||
"currently includes transparent balances associated with unified\n"
|
||||
"accounts. Prefer to use `z_getbalanceforaccount` instead.\n"
|
||||
"\nArguments:\n"
|
||||
"1. (dummy) (string, optional) Remains for backward compatibility. Must be excluded or set to \"*\" or \"\".\n"
|
||||
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
|
||||
"2. minconf (numeric, optional, default=0) Only include transactions confirmed at least this many times.\n"
|
||||
"3. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress')\n"
|
||||
"4. inZat (bool, optional, default=false) Get the result amount in " + MINOR_CURRENCY_UNIT + " (as an integer).\n"
|
||||
"5. " + asOfHeightMessage(true) +
|
||||
"\nResult:\n"
|
||||
"amount (numeric) The total amount in " + CURRENCY_UNIT + "(or " + MINOR_CURRENCY_UNIT + " if inZat is true) received.\n"
|
||||
"\nBitcoin compatibility:\n"
|
||||
"Compatible with up to three arguments."
|
||||
"\nExamples:\n"
|
||||
"\nThe total amount in the wallet\n"
|
||||
+ HelpExampleCli("getbalance", "*") +
|
||||
|
@ -1048,17 +1060,16 @@ UniValue getbalance(const UniValue& params, bool fHelp)
|
|||
throw JSONRPCError(RPC_INVALID_PARAMETER, "dummy first argument must be excluded or set to \"*\" or \"\".");
|
||||
}
|
||||
|
||||
int min_depth = 0;
|
||||
if (!params[1].isNull()) {
|
||||
min_depth = params[1].get_int();
|
||||
}
|
||||
auto asOfHeight = parseAsOfHeight(params, 4);
|
||||
|
||||
int min_depth = parseMinconf(0, params, 1, asOfHeight);
|
||||
|
||||
isminefilter filter = ISMINE_SPENDABLE;
|
||||
if (!params[2].isNull() && params[2].get_bool()) {
|
||||
filter = filter | ISMINE_WATCH_ONLY;
|
||||
}
|
||||
|
||||
CAmount nBalance = pwalletMain->GetBalance(filter, min_depth);
|
||||
CAmount nBalance = pwalletMain->GetBalance(asOfHeight, filter, min_depth);
|
||||
if (!params[3].isNull() && params[3].get_bool()) {
|
||||
return nBalance;
|
||||
} else {
|
||||
|
@ -1073,12 +1084,12 @@ UniValue getunconfirmedbalance(const UniValue ¶ms, bool fHelp)
|
|||
|
||||
if (fHelp || params.size() > 0)
|
||||
throw runtime_error(
|
||||
"getunconfirmedbalance\n"
|
||||
"Returns the server's total unconfirmed transparent balance\n");
|
||||
"getunconfirmedbalance\n"
|
||||
"Returns the server's total unconfirmed transparent balance\n");
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
return ValueFromAmount(pwalletMain->GetUnconfirmedBalance());
|
||||
return ValueFromAmount(pwalletMain->GetUnconfirmedTransparentBalance());
|
||||
}
|
||||
|
||||
|
||||
|
@ -1134,9 +1145,7 @@ UniValue sendmany(const UniValue& params, bool fHelp)
|
|||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\"");
|
||||
}
|
||||
UniValue sendTo = params[1].get_obj();
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 2)
|
||||
nMinDepth = params[2].get_int();
|
||||
int nMinDepth = parseMinconf(1, params, 2, std::nullopt);
|
||||
|
||||
CWalletTx wtx;
|
||||
if (params.size() > 3 && !params[3].isNull() && !params[3].get_str().empty())
|
||||
|
@ -1274,10 +1283,18 @@ struct tallyitem
|
|||
|
||||
UniValue ListReceived(const UniValue& params)
|
||||
{
|
||||
if (params.size() > 3 && params[3].get_str() != "") {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "addressFilter must be set to \"\"");
|
||||
}
|
||||
|
||||
if (params.size() > 4 && params[4].get_bool() != false) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "includeImmatureCoinbase must be set to false");
|
||||
}
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 5);
|
||||
|
||||
// Minimum confirmations
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 0)
|
||||
nMinDepth = params[0].get_int();
|
||||
int nMinDepth = parseMinconf(1, params, 0, asOfHeight);
|
||||
|
||||
// Whether to include empty accounts
|
||||
bool fIncludeEmpty = false;
|
||||
|
@ -1297,7 +1314,7 @@ UniValue ListReceived(const UniValue& params)
|
|||
if (wtx.IsCoinBase() || !CheckFinalTx(wtx))
|
||||
continue;
|
||||
|
||||
int nDepth = wtx.GetDepthInMainChain();
|
||||
int nDepth = wtx.GetDepthInMainChain(asOfHeight);
|
||||
if (nDepth < nMinDepth)
|
||||
continue;
|
||||
|
||||
|
@ -1367,9 +1384,9 @@ UniValue listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() > 3)
|
||||
if (fHelp || params.size() > 6)
|
||||
throw runtime_error(
|
||||
"listreceivedbyaddress ( minconf includeempty includeWatchonly)\n"
|
||||
"listreceivedbyaddress ( minconf includeempty includeWatchonly addressFilter includeImmatureCoinbase asOfHeight )\n"
|
||||
"\nList balances by transparent receiving address. This API does not provide\n"
|
||||
"any information for associated with shielded addresses and should only be used\n"
|
||||
"in circumstances where it is necessary to interoperate with legacy Bitcoin\n"
|
||||
|
@ -1378,7 +1395,9 @@ UniValue listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
"1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
|
||||
"2. includeempty (numeric, optional, default=false) Whether to include addresses that haven't received any payments.\n"
|
||||
"3. includeWatchonly (bool, optional, default=false) Whether to include watchonly addresses (see 'importaddress').\n"
|
||||
|
||||
"4. addressFilter (string, optional, default=\"\") If present and non-empty, only return information on this address. Currently, only the default value is supported.\n"
|
||||
"5. includeImmatureCoinbase (bool, optional, default=false) Include immature coinbase transactions. Currently, only the default value is supported.\n"
|
||||
"6. " + asOfHeightMessage(true) +
|
||||
"\nResult:\n"
|
||||
"[\n"
|
||||
" {\n"
|
||||
|
@ -1390,7 +1409,8 @@ UniValue listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
" }\n"
|
||||
" ,...\n"
|
||||
"]\n"
|
||||
|
||||
"\nBitcoin compatibility:\n"
|
||||
"Compatible up to five arguments, but can only use the default value for `addressFilter` and `includeImmatureCoinbase`."
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("listreceivedbyaddress", "")
|
||||
+ HelpExampleCli("listreceivedbyaddress", "6 true")
|
||||
|
@ -1418,8 +1438,9 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
|
|||
* @param fLong Whether to include the JSON version of the transaction.
|
||||
* @param ret The UniValue into which the result is stored.
|
||||
* @param filter The "is mine" filter flags.
|
||||
* @param asOfHeight The last block to look at.
|
||||
*/
|
||||
void ListTransactions(const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter)
|
||||
void ListTransactions(const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter, const std::optional<int>& asOfHeight)
|
||||
{
|
||||
CAmount nFee;
|
||||
std::list<COutputEntry> listReceived;
|
||||
|
@ -1445,14 +1466,14 @@ void ListTransactions(const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue&
|
|||
entry.pushKV("vout", s.vout);
|
||||
entry.pushKV("fee", ValueFromAmount(-nFee));
|
||||
if (fLong)
|
||||
WalletTxToJSON(wtx, entry);
|
||||
WalletTxToJSON(wtx, entry, asOfHeight);
|
||||
entry.pushKV("size", static_cast<uint64_t>(GetSerializeSize(static_cast<CTransaction>(wtx), SER_NETWORK, PROTOCOL_VERSION)));
|
||||
ret.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Received
|
||||
if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth)
|
||||
if (listReceived.size() > 0 && wtx.GetDepthInMainChain(asOfHeight) >= nMinDepth)
|
||||
{
|
||||
for (const COutputEntry& r : listReceived)
|
||||
{
|
||||
|
@ -1467,9 +1488,9 @@ void ListTransactions(const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue&
|
|||
MaybePushAddress(entry, r.destination);
|
||||
if (wtx.IsCoinBase())
|
||||
{
|
||||
if (wtx.GetDepthInMainChain() < 1)
|
||||
if (wtx.GetDepthInMainChain(asOfHeight) < 1)
|
||||
entry.pushKV("category", "orphan");
|
||||
else if (wtx.GetBlocksToMaturity() > 0)
|
||||
else if (wtx.GetBlocksToMaturity(asOfHeight) > 0)
|
||||
entry.pushKV("category", "immature");
|
||||
else
|
||||
entry.pushKV("category", "generate");
|
||||
|
@ -1482,7 +1503,7 @@ void ListTransactions(const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue&
|
|||
entry.pushKV("amountZat", r.amount);
|
||||
entry.pushKV("vout", r.vout);
|
||||
if (fLong)
|
||||
WalletTxToJSON(wtx, entry);
|
||||
WalletTxToJSON(wtx, entry, asOfHeight);
|
||||
entry.pushKV("size", static_cast<uint64_t>(GetSerializeSize(static_cast<CTransaction>(wtx), SER_NETWORK, PROTOCOL_VERSION)));
|
||||
ret.push_back(entry);
|
||||
}
|
||||
|
@ -1494,9 +1515,9 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() > 4)
|
||||
if (fHelp || params.size() > 5)
|
||||
throw runtime_error(
|
||||
"listtransactions ( \"dummy\" count from includeWatchonly)\n"
|
||||
"listtransactions ( \"dummy\" count from includeWatchonly asOfHeight)\n"
|
||||
"\nReturns up to 'count' of the most recent transactions associated with legacy transparent\n"
|
||||
"addresses of this wallet, skipping the first 'from' transactions.\n"
|
||||
"\nThis API does not provide any information about transactions containing shielded inputs\n"
|
||||
|
@ -1508,6 +1529,7 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
|
|||
"2. count (numeric, optional, default=10) The number of transactions to return\n"
|
||||
"3. from (numeric, optional, default=0) The number of transactions to skip\n"
|
||||
"4. includeWatchonly (bool, optional, default=false) Include transactions to watchonly addresses (see 'importaddress')\n"
|
||||
"5. " + asOfHeightMessage(false) +
|
||||
"\nResult:\n"
|
||||
"[\n"
|
||||
" {\n"
|
||||
|
@ -1562,6 +1584,8 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
|
|||
if(params[3].get_bool())
|
||||
filter = filter | ISMINE_WATCH_ONLY;
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 4);
|
||||
|
||||
if (nCount < 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
|
||||
if (nFrom < 0)
|
||||
|
@ -1577,7 +1601,7 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
|
|||
for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
|
||||
{
|
||||
CWalletTx *const pwtx = (*it).second;
|
||||
ListTransactions(*pwtx, 0, true, ret, filter);
|
||||
ListTransactions(*pwtx, 0, true, ret, filter, asOfHeight);
|
||||
if ((int)ret.size() >= (nCount+nFrom)) break;
|
||||
}
|
||||
}
|
||||
|
@ -1614,14 +1638,17 @@ UniValue listsinceblock(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp)
|
||||
if (fHelp || params.size() > 6)
|
||||
throw runtime_error(
|
||||
"listsinceblock ( \"blockhash\" target-confirmations includeWatchonly)\n"
|
||||
"listsinceblock ( \"blockhash\" target-confirmations includeWatchonly includeRemoved includeChange asOfHeight )\n"
|
||||
"\nGet all transactions in blocks since block [blockhash], or all transactions if omitted\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"blockhash\" (string, optional) The block hash to list transactions since\n"
|
||||
"2. target-confirmations: (numeric, optional) The confirmations required, must be 1 or more\n"
|
||||
"3. includeWatchonly: (bool, optional, default=false) Include transactions to watchonly addresses (see 'importaddress')"
|
||||
"4. includeRemoved (bool, optional, default=true) Show transactions that were removed due to a reorg in the \"removed\" array (not guaranteed to work on pruned nodes)\n"
|
||||
"5. includeChange (bool, optional, default=false) Also add entries for change outputs. Currently, only the default value is supported.\n"
|
||||
"6. " + asOfHeightMessage(false) +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"transactions\": [\n"
|
||||
|
@ -1643,9 +1670,13 @@ UniValue listsinceblock(const UniValue& params, bool fHelp)
|
|||
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n"
|
||||
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
|
||||
" \"to\": \"...\", (string) If a comment to is associated with the transaction.\n"
|
||||
" ],\n"
|
||||
" ],\n"
|
||||
" \"removed\": [...] (array of objects, optional) structure is the same as \"transactions\" above, only present if includeRemoved=true\n"
|
||||
" Note: currently this only returns an empty array.\n"
|
||||
" \"lastblock\": \"lastblockhash\" (string) The hash of the last block\n"
|
||||
"}\n"
|
||||
"\nBitcoin compatibility:\n"
|
||||
"Compatible up to five arguments, but can only use the default value for `includeChange`, and only returns an empty array for \"removed\"."
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("listsinceblock", "")
|
||||
+ HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6")
|
||||
|
@ -1680,6 +1711,17 @@ UniValue listsinceblock(const UniValue& params, bool fHelp)
|
|||
if(params[2].get_bool())
|
||||
filter = filter | ISMINE_WATCH_ONLY;
|
||||
|
||||
bool includeRemoved = true;
|
||||
if (params.size() > 3) {
|
||||
includeRemoved = params[3].get_bool();
|
||||
}
|
||||
|
||||
if (params.size() > 4 && params[4].get_bool() != false) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "includeChange must be set to false");
|
||||
}
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 5);
|
||||
|
||||
int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1;
|
||||
|
||||
UniValue transactions(UniValue::VARR);
|
||||
|
@ -1687,8 +1729,8 @@ UniValue listsinceblock(const UniValue& params, bool fHelp)
|
|||
for (const std::pair<const uint256, CWalletTx>& pairWtx : pwalletMain->mapWallet) {
|
||||
CWalletTx tx = pairWtx.second;
|
||||
|
||||
if (depth == -1 || tx.GetDepthInMainChain() < depth) {
|
||||
ListTransactions(tx, 0, true, transactions, filter);
|
||||
if (depth == -1 || tx.GetDepthInMainChain(std::nullopt) < depth) {
|
||||
ListTransactions(tx, 0, true, transactions, filter, asOfHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1697,6 +1739,9 @@ UniValue listsinceblock(const UniValue& params, bool fHelp)
|
|||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.pushKV("transactions", transactions);
|
||||
if (includeRemoved) {
|
||||
ret.pushKV("removed", UniValue(UniValue::VARR));
|
||||
}
|
||||
ret.pushKV("lastblock", lastblock.GetHex());
|
||||
|
||||
return ret;
|
||||
|
@ -1707,15 +1752,17 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() < 1 || params.size() > 2)
|
||||
if (fHelp || params.size() < 1 || params.size() > 4)
|
||||
throw runtime_error(
|
||||
"gettransaction \"txid\" ( includeWatchonly )\n"
|
||||
"gettransaction \"txid\" ( includeWatchonly verbose asOfHeight )\n"
|
||||
"\nReturns detailed information about in-wallet transaction <txid>. This does not\n"
|
||||
"include complete information about shielded components of the transaction; to obtain\n"
|
||||
"details about shielded components of the transaction use `z_viewtransaction`.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"txid\" (string, required) The transaction id\n"
|
||||
"2. \"includeWatchonly\" (bool, optional, default=false) Whether to include watchonly addresses in balance calculation and details[]\n"
|
||||
"2. includeWatchonly (bool, optional, default=false) Whether to include watchonly addresses in balance calculation and details[]\n"
|
||||
"3. verbose (bool, optional, default=false) Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction). Currently, only the default value is supported.\n"
|
||||
"4. " + asOfHeightMessage(false) +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"status\" : \"mined|waiting|expiringsoon|expired\", (string) The transaction status, can be 'mined', 'waiting', 'expiringsoon' or 'expired'\n"
|
||||
|
@ -1752,7 +1799,8 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
|
|||
" ],\n"
|
||||
" \"hex\" : \"data\" (string) Raw data for transaction\n"
|
||||
"}\n"
|
||||
|
||||
"\nBitcoin compatibility:\n"
|
||||
"Compatible up to three arguments, but can only use the default value for `verbose`."
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
+ HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
|
||||
|
@ -1769,12 +1817,18 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
|
|||
if(params[1].get_bool())
|
||||
filter = filter | ISMINE_WATCH_ONLY;
|
||||
|
||||
if (params.size() > 2 && params[2].get_bool() != false) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "verbose must be set to false");
|
||||
}
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 3);
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
if (!pwalletMain->mapWallet.count(hash))
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
|
||||
|
||||
CAmount nCredit = wtx.GetCredit(filter);
|
||||
CAmount nCredit = wtx.GetCredit(asOfHeight, filter);
|
||||
CAmount nDebit = wtx.GetDebit(filter);
|
||||
CAmount nNet = nCredit - nDebit;
|
||||
CAmount nFee = (wtx.IsFromMe(filter) ? wtx.GetValueOut() - nDebit : 0);
|
||||
|
@ -1785,10 +1839,10 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
|
|||
if (wtx.IsFromMe(filter))
|
||||
entry.pushKV("fee", ValueFromAmount(nFee));
|
||||
|
||||
WalletTxToJSON(wtx, entry);
|
||||
WalletTxToJSON(wtx, entry, asOfHeight);
|
||||
|
||||
UniValue details(UniValue::VARR);
|
||||
ListTransactions(wtx, 0, false, details, filter);
|
||||
ListTransactions(wtx, 0, false, details, filter, asOfHeight);
|
||||
entry.pushKV("details", details);
|
||||
|
||||
string strHex = EncodeHexTx(static_cast<CTransaction>(wtx));
|
||||
|
@ -2303,25 +2357,29 @@ UniValue settxfee(const UniValue& params, bool fHelp)
|
|||
return true;
|
||||
}
|
||||
|
||||
CAmount getBalanceZaddr(std::optional<libzcash::PaymentAddress> address, int minDepth = 1, int maxDepth = INT_MAX, bool ignoreUnspendable=true);
|
||||
CAmount getBalanceZaddr(std::optional<libzcash::PaymentAddress> address, const std::optional<int>& asOfHeight, int minDepth = 1, int maxDepth = INT_MAX, bool ignoreUnspendable=true);
|
||||
|
||||
UniValue getwalletinfo(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() != 0)
|
||||
if (fHelp || params.size() > 1)
|
||||
throw runtime_error(
|
||||
"getwalletinfo\n"
|
||||
"getwalletinfo ( asOfHeight )\n"
|
||||
"Returns wallet state information.\n"
|
||||
"\nArguments:\n"
|
||||
"1. " + asOfHeightMessage(false) +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"walletversion\": xxxxx, (numeric) the wallet version\n"
|
||||
" \"balance\": xxxxxxx, (numeric) the total confirmed transparent balance of the wallet in " + CURRENCY_UNIT + "\n"
|
||||
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed transparent balance of the wallet in " + CURRENCY_UNIT + "\n"
|
||||
" \"unconfirmed_balance\": xxx, (numeric, optional) the total unconfirmed transparent balance of the wallet in " + CURRENCY_UNIT + ".\n"
|
||||
" Not included if `asOfHeight` is specified.\n"
|
||||
" \"immature_balance\": xxxxxx, (numeric) the total immature transparent balance of the wallet in " + CURRENCY_UNIT + "\n"
|
||||
" \"shielded_balance\": xxxxxxx, (numeric) the total confirmed shielded balance of the wallet in " + CURRENCY_UNIT + "\n"
|
||||
" \"shielded_unconfirmed_balance\": xxx, (numeric) the total unconfirmed shielded balance of the wallet in " + CURRENCY_UNIT + "\n"
|
||||
" \"shielded_unconfirmed_balance\": xxx, (numeric, optional) the total unconfirmed shielded balance of the wallet in " + CURRENCY_UNIT + ".\n"
|
||||
" Not included if `asOfHeight` is specified.\n"
|
||||
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
|
||||
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n"
|
||||
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
|
||||
|
@ -2337,15 +2395,21 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp)
|
|||
+ HelpExampleRpc("getwalletinfo", "")
|
||||
);
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 0);
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("walletversion", pwalletMain->GetVersion());
|
||||
obj.pushKV("balance", ValueFromAmount(pwalletMain->GetBalance()));
|
||||
obj.pushKV("unconfirmed_balance", ValueFromAmount(pwalletMain->GetUnconfirmedBalance()));
|
||||
obj.pushKV("immature_balance", ValueFromAmount(pwalletMain->GetImmatureBalance()));
|
||||
obj.pushKV("shielded_balance", FormatMoney(getBalanceZaddr(std::nullopt, 1, INT_MAX)));
|
||||
obj.pushKV("shielded_unconfirmed_balance", FormatMoney(getBalanceZaddr(std::nullopt, 0, 0)));
|
||||
obj.pushKV("balance", ValueFromAmount(pwalletMain->GetBalance(asOfHeight)));
|
||||
if (!asOfHeight.has_value()) {
|
||||
obj.pushKV("unconfirmed_balance", ValueFromAmount(pwalletMain->GetUnconfirmedTransparentBalance()));
|
||||
}
|
||||
obj.pushKV("immature_balance", ValueFromAmount(pwalletMain->GetImmatureBalance(asOfHeight)));
|
||||
obj.pushKV("shielded_balance", FormatMoney(getBalanceZaddr(std::nullopt, asOfHeight, 1, INT_MAX)));
|
||||
if (!asOfHeight.has_value()) {
|
||||
obj.pushKV("shielded_unconfirmed_balance", FormatMoney(getBalanceZaddr(std::nullopt, asOfHeight, 0, 0)));
|
||||
}
|
||||
obj.pushKV("txcount", (int)pwalletMain->mapWallet.size());
|
||||
obj.pushKV("keypoololdest", pwalletMain->GetOldestKeyPoolTime());
|
||||
obj.pushKV("keypoolsize", (int)pwalletMain->GetKeyPoolSize());
|
||||
|
@ -2394,9 +2458,9 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() > 3)
|
||||
if (fHelp || params.size() > 6)
|
||||
throw runtime_error(
|
||||
"listunspent ( minconf maxconf [\"address\",...] )\n"
|
||||
"listunspent ( minconf maxconf [\"address\",...] includeUnsafe queryOptions asOfHeight )\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"
|
||||
|
@ -2409,6 +2473,9 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
" \"address\" (string) Zcash address\n"
|
||||
" ,...\n"
|
||||
" ]\n"
|
||||
"4. includeUnsafe (bool, optional, default=true) Include outputs that are not safe to spend. Currently, only the default value is supported.\n"
|
||||
"5. queryOptions (object, optional, default={}) JSON with query options. Currently, only the default value is supported.\n"
|
||||
"6. " + asOfHeightMessage(true) +
|
||||
"\nResult\n"
|
||||
"[ (array of json object)\n"
|
||||
" {\n"
|
||||
|
@ -2425,7 +2492,8 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
" }\n"
|
||||
" ,...\n"
|
||||
"]\n"
|
||||
|
||||
"\nBitcoin compatibility:\n"
|
||||
"Compatible up to five arguments, but can only use the default value for `includeUnsafe` and `queryOptions`."
|
||||
"\nExamples\n"
|
||||
+ HelpExampleCli("listunspent", "")
|
||||
+ HelpExampleCli("listunspent", "6 9999999 \"[\\\"t1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"t1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"")
|
||||
|
@ -2434,9 +2502,9 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
|
||||
RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VARR));
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 0)
|
||||
nMinDepth = params[0].get_int();
|
||||
auto asOfHeight = parseAsOfHeight(params, 5);
|
||||
|
||||
int nMinDepth = parseMinconf(1, params, 0, asOfHeight);
|
||||
|
||||
int nMaxDepth = 9999999;
|
||||
if (params.size() > 1)
|
||||
|
@ -2458,10 +2526,28 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
}
|
||||
}
|
||||
|
||||
if (params.size() > 3 && params[3].get_bool() != false) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "includeUnsafe must be set to false");
|
||||
}
|
||||
|
||||
if (params.size() > 4 && !params[4].get_obj().empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "queryOptions must be set to {}");
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
UniValue results(UniValue::VARR);
|
||||
vector<COutput> vecOutputs;
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
pwalletMain->AvailableCoins(vecOutputs, false, NULL, true);
|
||||
pwalletMain->AvailableCoins(
|
||||
vecOutputs,
|
||||
asOfHeight,
|
||||
false, // fOnlyConfirmed
|
||||
nullptr, // coinControl
|
||||
true, // fIncludeZeroValue
|
||||
true, // fIncludeCoinBase
|
||||
false, // fOnlySpendable
|
||||
nMinDepth,
|
||||
destinations);
|
||||
for (const COutput& out : vecOutputs) {
|
||||
if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth)
|
||||
continue;
|
||||
|
@ -2470,9 +2556,6 @@ UniValue listunspent(const UniValue& params, bool fHelp)
|
|||
const CScript& scriptPubKey = out.tx->vout[out.i].scriptPubKey;
|
||||
bool fValidAddress = ExtractDestination(scriptPubKey, address);
|
||||
|
||||
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
|
||||
continue;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.pushKV("txid", out.tx->GetHash().GetHex());
|
||||
entry.pushKV("vout", out.i);
|
||||
|
@ -2506,9 +2589,9 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() > 4)
|
||||
if (fHelp || params.size() > 5)
|
||||
throw runtime_error(
|
||||
"z_listunspent ( minconf maxconf includeWatchonly [\"zaddr\",...] )\n"
|
||||
"z_listunspent ( minconf maxconf includeWatchonly [\"zaddr\",...] asOfHeight )\n"
|
||||
"\nReturns an array of unspent shielded notes with between minconf and maxconf (inclusive)\n"
|
||||
"confirmations. Results may be optionally filtered to only include notes sent to specified\n"
|
||||
"addresses.\n"
|
||||
|
@ -2523,6 +2606,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
|||
" \"address\" (string) Sprout, Sapling, or Unified address\n"
|
||||
" ,...\n"
|
||||
" ]\n"
|
||||
"5. " + asOfHeightMessage(true) +
|
||||
"\nResult (output indices for only one value pool will be present):\n"
|
||||
"[ (array of json object)\n"
|
||||
" {\n"
|
||||
|
@ -2550,13 +2634,9 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
|||
|
||||
RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VBOOL)(UniValue::VARR));
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 0) {
|
||||
nMinDepth = params[0].get_int();
|
||||
}
|
||||
if (nMinDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
auto asOfHeight = parseAsOfHeight(params, 4);
|
||||
|
||||
int nMinDepth = parseMinconf(1, params, 0, asOfHeight);
|
||||
|
||||
int nMaxDepth = 9999999;
|
||||
if (params.size() > 1) {
|
||||
|
@ -2626,7 +2706,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
|||
std::vector<SproutNoteEntry> sproutEntries;
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, asOfHeight, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
|
||||
|
||||
for (auto & entry : sproutEntries) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
|
@ -3759,13 +3839,13 @@ UniValue z_listunifiedreceivers(const UniValue& params, bool fHelp)
|
|||
return result;
|
||||
}
|
||||
|
||||
CAmount getBalanceTaddr(const std::optional<CTxDestination>& taddr, int minDepth=1, bool ignoreUnspendable=true) {
|
||||
CAmount getBalanceTaddr(const std::optional<CTxDestination>& taddr, const std::optional<int>& asOfHeight, int minDepth=1, bool ignoreUnspendable=true) {
|
||||
vector<COutput> vecOutputs;
|
||||
CAmount balance = 0;
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
pwalletMain->AvailableCoins(vecOutputs, false, NULL, true);
|
||||
pwalletMain->AvailableCoins(vecOutputs, asOfHeight, false, NULL, true);
|
||||
for (const COutput& out : vecOutputs) {
|
||||
if (out.nDepth < minDepth) {
|
||||
continue;
|
||||
|
@ -3792,7 +3872,7 @@ CAmount getBalanceTaddr(const std::optional<CTxDestination>& taddr, int minDepth
|
|||
return balance;
|
||||
}
|
||||
|
||||
CAmount getBalanceZaddr(std::optional<libzcash::PaymentAddress> address, int minDepth, int maxDepth, bool ignoreUnspendable) {
|
||||
CAmount getBalanceZaddr(std::optional<libzcash::PaymentAddress> address, const std::optional<int>& asOfHeight, int minDepth, int maxDepth, bool ignoreUnspendable) {
|
||||
CAmount balance = 0;
|
||||
std::vector<SproutNoteEntry> sproutEntries;
|
||||
std::vector<SaplingNoteEntry> saplingEntries;
|
||||
|
@ -3804,7 +3884,7 @@ CAmount getBalanceZaddr(std::optional<libzcash::PaymentAddress> address, int min
|
|||
noteFilter = NoteFilter::ForPaymentAddresses(std::vector({address.value()}));
|
||||
}
|
||||
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, minDepth, maxDepth, true, ignoreUnspendable);
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, asOfHeight, minDepth, maxDepth, true, ignoreUnspendable);
|
||||
for (auto & entry : sproutEntries) {
|
||||
balance += CAmount(entry.note.value());
|
||||
}
|
||||
|
@ -3840,13 +3920,14 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size()==0 || params.size() >2)
|
||||
if (fHelp || params.size() == 0 || params.size() > 3)
|
||||
throw runtime_error(
|
||||
"z_listreceivedbyaddress \"address\" ( minconf )\n"
|
||||
"z_listreceivedbyaddress \"address\" ( minconf asOfHeight )\n"
|
||||
"\nReturn a list of amounts received by a zaddr belonging to the node's wallet.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"address\" (string) The shielded address.\n"
|
||||
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
|
||||
"3. " + asOfHeightMessage(true) +
|
||||
"\nResult (output indices for only one value pool will be present):\n"
|
||||
"[\n"
|
||||
" {\n"
|
||||
|
@ -3873,13 +3954,10 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 1) {
|
||||
nMinDepth = params[1].get_int();
|
||||
}
|
||||
if (nMinDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
auto asOfHeight = parseAsOfHeight(params, 2);
|
||||
|
||||
int nMinDepth = parseMinconf(1, params, 1, asOfHeight);
|
||||
|
||||
UniValue result(UniValue::VARR);
|
||||
|
||||
// Check that the from address is valid.
|
||||
|
@ -3925,7 +4003,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||
|
||||
auto noteFilter = NoteFilter::ForPaymentAddresses(std::vector({decoded.value()}));
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, nMinDepth, INT_MAX, false, false);
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, asOfHeight, nMinDepth, INT_MAX, false, false);
|
||||
|
||||
auto push_transparent_result = [&](const CTxDestination& dest) -> void {
|
||||
const CScript scriptPubKey{GetScriptForDestination(dest)};
|
||||
|
@ -3933,7 +4011,7 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
|||
if (!CheckFinalTx(wtx))
|
||||
continue;
|
||||
|
||||
int nDepth = wtx.GetDepthInMainChain();
|
||||
int nDepth = wtx.GetDepthInMainChain(asOfHeight);
|
||||
if (nDepth < nMinDepth) continue;
|
||||
for (size_t i = 0; i < wtx.vout.size(); ++i) {
|
||||
const CTxOut& txout{wtx.vout[i]};
|
||||
|
@ -4115,13 +4193,7 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
|
|||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 1) {
|
||||
nMinDepth = params[1].get_int();
|
||||
}
|
||||
if (nMinDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
int nMinDepth = parseMinconf(1, params, 1, std::nullopt);
|
||||
|
||||
KeyIO keyIO(Params());
|
||||
// Check that the from address is valid.
|
||||
|
@ -4138,16 +4210,16 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
|
|||
CAmount nBalance = 0;
|
||||
std::visit(match {
|
||||
[&](const CKeyID& addr) {
|
||||
nBalance = getBalanceTaddr(addr, nMinDepth, false);
|
||||
nBalance = getBalanceTaddr(addr, std::nullopt, nMinDepth, false);
|
||||
},
|
||||
[&](const CScriptID& addr) {
|
||||
nBalance = getBalanceTaddr(addr, nMinDepth, false);
|
||||
nBalance = getBalanceTaddr(addr, std::nullopt, nMinDepth, false);
|
||||
},
|
||||
[&](const libzcash::SproutPaymentAddress& addr) {
|
||||
nBalance = getBalanceZaddr(addr, nMinDepth, INT_MAX, false);
|
||||
nBalance = getBalanceZaddr(addr, std::nullopt, nMinDepth, INT_MAX, false);
|
||||
},
|
||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||
nBalance = getBalanceZaddr(addr, nMinDepth, INT_MAX, false);
|
||||
nBalance = getBalanceZaddr(addr, std::nullopt, nMinDepth, INT_MAX, false);
|
||||
},
|
||||
[&](const libzcash::UnifiedAddress& addr) {
|
||||
auto selector = pwalletMain->ZTXOSelectorForAddress(addr, true, false);
|
||||
|
@ -4156,7 +4228,7 @@ UniValue z_getbalance(const UniValue& params, bool fHelp)
|
|||
RPC_INVALID_ADDRESS_OR_KEY,
|
||||
"Unified address does not correspond to an account in the wallet");
|
||||
}
|
||||
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, nMinDepth);
|
||||
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, nMinDepth, std::nullopt);
|
||||
|
||||
for (const auto& t : spendableInputs.utxos) {
|
||||
nBalance += t.Value();
|
||||
|
@ -4183,15 +4255,16 @@ UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() < 1 || params.size() > 2)
|
||||
if (fHelp || params.size() < 1 || params.size() > 3)
|
||||
throw runtime_error(
|
||||
"z_getbalanceforviewingkey \"fvk\" ( minconf )\n"
|
||||
"z_getbalanceforviewingkey \"fvk\" ( minconf asOfHeight )\n"
|
||||
"\nReturns the balance viewable by a full viewing key known to the node's wallet"
|
||||
"\nfor each value pool. Sprout viewing keys may be used only if the wallet controls"
|
||||
"\nthe corresponding spending key."
|
||||
"\nArguments:\n"
|
||||
"1. \"fvk\" (string) The selected full viewing key.\n"
|
||||
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
|
||||
"3. " + asOfHeightMessage(true) +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"pools\": {\n"
|
||||
|
@ -4228,13 +4301,9 @@ UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
|
|||
}
|
||||
auto fvk = decoded.value();
|
||||
|
||||
int minconf = 1;
|
||||
if (params.size() > 1) {
|
||||
minconf = params[1].get_int();
|
||||
if (minconf < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
}
|
||||
auto asOfHeight = parseAsOfHeight(params, 2);
|
||||
|
||||
int minconf = parseMinconf(1, params, 1, asOfHeight);
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
|
@ -4251,7 +4320,7 @@ UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
|
|||
"Error: the wallet does not recognize the specified viewing key.");
|
||||
}
|
||||
|
||||
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf);
|
||||
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf, asOfHeight);
|
||||
|
||||
CAmount transparentBalance = 0;
|
||||
CAmount sproutBalance = 0;
|
||||
|
@ -4295,13 +4364,14 @@ UniValue z_getbalanceforaccount(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() < 1 || params.size() > 2)
|
||||
if (fHelp || params.size() < 1 || params.size() > 3)
|
||||
throw runtime_error(
|
||||
"z_getbalanceforaccount account ( minconf )\n"
|
||||
"z_getbalanceforaccount account ( minconf asOfHeight )\n"
|
||||
"\nReturns the account's spendable balance for each value pool (\"transparent\", \"sapling\", and \"orchard\")."
|
||||
"\nArguments:\n"
|
||||
"1. account (numeric) The account number.\n"
|
||||
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
|
||||
"3. " + asOfHeightMessage(true) +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"pools\": {\n"
|
||||
|
@ -4334,13 +4404,9 @@ UniValue z_getbalanceforaccount(const UniValue& params, bool fHelp)
|
|||
}
|
||||
libzcash::AccountId account = accountInt;
|
||||
|
||||
int minconf = 1;
|
||||
if (params.size() > 1) {
|
||||
minconf = params[1].get_int();
|
||||
if (minconf < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
}
|
||||
auto asOfHeight = parseAsOfHeight(params, 2);
|
||||
|
||||
int minconf = parseMinconf(1, params, 1, asOfHeight);
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
|
@ -4352,7 +4418,7 @@ UniValue z_getbalanceforaccount(const UniValue& params, bool fHelp)
|
|||
tfm::format("Error: account %d has not been generated by z_getnewaccount.", account));
|
||||
}
|
||||
|
||||
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf);
|
||||
auto spendableInputs = pwalletMain->FindSpendableInputs(selector.value(), true, minconf, asOfHeight);
|
||||
// Accounts never contain Sprout notes.
|
||||
assert(spendableInputs.sproutNoteEntries.empty());
|
||||
|
||||
|
@ -4428,13 +4494,7 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
|
|||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 0) {
|
||||
nMinDepth = params[0].get_int();
|
||||
}
|
||||
if (nMinDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
int nMinDepth = parseMinconf(1, params, 0, std::nullopt);
|
||||
|
||||
bool fIncludeWatchonly = false;
|
||||
if (params.size() > 1) {
|
||||
|
@ -4445,8 +4505,8 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
|
|||
// but they don't because wtx.GetAmounts() does not handle tx where there are no outputs
|
||||
// pwalletMain->GetBalance() does not accept min depth parameter
|
||||
// so we use our own method to get balance of utxos.
|
||||
CAmount nBalance = getBalanceTaddr(std::nullopt, nMinDepth, !fIncludeWatchonly);
|
||||
CAmount nPrivateBalance = getBalanceZaddr(std::nullopt, nMinDepth, INT_MAX, !fIncludeWatchonly);
|
||||
CAmount nBalance = getBalanceTaddr(std::nullopt, std::nullopt, nMinDepth, !fIncludeWatchonly);
|
||||
CAmount nPrivateBalance = getBalanceZaddr(std::nullopt, std::nullopt, nMinDepth, INT_MAX, !fIncludeWatchonly);
|
||||
CAmount nTotalBalance = nBalance + nPrivateBalance;
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("transparent", FormatMoney(nBalance));
|
||||
|
@ -5258,13 +5318,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
}
|
||||
|
||||
// Minimum confirmations
|
||||
int nMinDepth = DEFAULT_NOTE_CONFIRMATIONS;
|
||||
if (params.size() > 2) {
|
||||
nMinDepth = params[2].get_int();
|
||||
}
|
||||
if (nMinDepth < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||
}
|
||||
int nMinDepth = parseMinconf(DEFAULT_NOTE_CONFIRMATIONS, params, 2, std::nullopt);
|
||||
|
||||
// Fee in Zatoshis, not currency format)
|
||||
CAmount nFee = DEFAULT_FEE;
|
||||
|
@ -5354,13 +5408,15 @@ UniValue z_setmigration(const UniValue& params, bool fHelp) {
|
|||
UniValue z_getmigrationstatus(const UniValue& params, bool fHelp) {
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
if (fHelp || params.size() != 0)
|
||||
if (fHelp || params.size() > 1)
|
||||
throw runtime_error(
|
||||
"z_getmigrationstatus\n"
|
||||
"z_getmigrationstatus ( asOfHeight )\n"
|
||||
"Returns information about the status of the Sprout to Sapling migration.\n"
|
||||
"Note: A transaction is defined as finalized if it has at least ten confirmations.\n"
|
||||
"Also, it is possible that manually created transactions involving this wallet\n"
|
||||
"will be included in the result.\n"
|
||||
"\nArguments:\n"
|
||||
"1. " + asOfHeightMessage(false) +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"enabled\": true|false, (boolean) Whether or not migration is enabled\n"
|
||||
|
@ -5374,6 +5430,9 @@ UniValue z_getmigrationstatus(const UniValue& params, bool fHelp) {
|
|||
"}\n"
|
||||
);
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
auto asOfHeight = parseAsOfHeight(params, 0);
|
||||
|
||||
UniValue migrationStatus(UniValue::VOBJ);
|
||||
migrationStatus.pushKV("enabled", pwalletMain->fSaplingMigrationEnabled);
|
||||
// The "destination_address" field MAY be omitted if the "-migrationdestaddress"
|
||||
|
@ -5391,7 +5450,7 @@ UniValue z_getmigrationstatus(const UniValue& params, bool fHelp) {
|
|||
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||
// Here we are looking for any and all Sprout notes for which we have the spending key, including those
|
||||
// which are locked and/or only exist in the mempool, as they should be included in the unmigrated amount.
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0, INT_MAX, true, true, false);
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, asOfHeight, 0, INT_MAX, true, true, false);
|
||||
CAmount unmigratedAmount = 0;
|
||||
for (const auto& sproutEntry : sproutEntries) {
|
||||
unmigratedAmount += sproutEntry.note.value();
|
||||
|
@ -5426,7 +5485,7 @@ UniValue z_getmigrationstatus(const UniValue& params, bool fHelp) {
|
|||
migrationTxids.push_back(txPair.first.ToString());
|
||||
// A transaction is "finalized" iff it has at least 10 confirmations.
|
||||
// TODO: subject to change, if the recommended number of confirmations changes.
|
||||
if (tx.GetDepthInMainChain() >= 10) {
|
||||
if (tx.GetDepthInMainChain(asOfHeight) >= 10) {
|
||||
finalizedMigratedAmount -= tx.GetValueBalanceSapling();
|
||||
++numFinalizedMigrationTxs;
|
||||
} else {
|
||||
|
@ -5603,7 +5662,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
|||
|
||||
// Get available utxos
|
||||
vector<COutput> vecOutputs;
|
||||
pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, true);
|
||||
pwalletMain->AvailableCoins(vecOutputs, std::nullopt, true, NULL, false, true);
|
||||
|
||||
// Find unspent coinbase utxos and update estimated size
|
||||
for (const COutput& out : vecOutputs) {
|
||||
|
@ -5965,7 +6024,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
|||
if (useAnyUTXO || taddrs.size() > 0) {
|
||||
// Get available utxos
|
||||
vector<COutput> vecOutputs;
|
||||
pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, false);
|
||||
pwalletMain->AvailableCoins(vecOutputs, std::nullopt, true, NULL, false, false);
|
||||
|
||||
// Find unspent utxos and update estimated size
|
||||
for (const COutput& out : vecOutputs) {
|
||||
|
@ -6016,7 +6075,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
|||
useAnySprout || useAnySapling ?
|
||||
std::nullopt :
|
||||
std::optional(NoteFilter::ForPaymentAddresses(zaddrs));
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, nAnchorConfirmations);
|
||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, std::nullopt, nAnchorConfirmations);
|
||||
|
||||
// If Sapling is not active, do not allow sending from a sapling addresses.
|
||||
if (!saplingActive && saplingEntries.size() > 0) {
|
||||
|
@ -6241,12 +6300,13 @@ UniValue z_getnotescount(const UniValue& params, bool fHelp)
|
|||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() > 1)
|
||||
if (fHelp || params.size() > 2)
|
||||
throw runtime_error(
|
||||
"z_getnotescount\n"
|
||||
"z_getnotescount ( minconf asOfHeight )\n"
|
||||
"\nReturns the number of notes available in the wallet for each shielded value pool.\n"
|
||||
"\nArguments:\n"
|
||||
"1. minconf (numeric, optional, default=1) Only include notes in transactions confirmed at least this many times.\n"
|
||||
"\nReturns the number of notes available in the wallet for each shielded value pool.\n"
|
||||
"2. " + asOfHeightMessage(true) +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"sprout\" (numeric) the number of Sprout notes in the wallet\n"
|
||||
|
@ -6260,15 +6320,15 @@ UniValue z_getnotescount(const UniValue& params, bool fHelp)
|
|||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (params.size() > 0)
|
||||
nMinDepth = params[0].get_int();
|
||||
auto asOfHeight = parseAsOfHeight(params, 1);
|
||||
|
||||
int nMinDepth = parseMinconf(1, params, 0, asOfHeight);
|
||||
|
||||
int sprout = 0;
|
||||
int sapling = 0;
|
||||
int orchard = 0;
|
||||
for (auto& wtx : pwalletMain->mapWallet) {
|
||||
if (wtx.second.GetDepthInMainChain() >= nMinDepth) {
|
||||
if (wtx.second.GetDepthInMainChain(asOfHeight) >= nMinDepth) {
|
||||
sprout += wtx.second.mapSproutNoteData.size();
|
||||
sapling += wtx.second.mapSaplingNoteData.size();
|
||||
orchard += wtx.second.orchardTxMeta.GetMyActionIVKs().size();
|
||||
|
|
|
@ -876,7 +876,7 @@ std::pair<PaymentAddress, RecipientType> CWallet::GetPaymentAddressForRecipient(
|
|||
auto wtxPtr = mapWallet.find(txid);
|
||||
if (wtxPtr != mapWallet.end()) {
|
||||
const CBlockIndex* pTxIndex{nullptr};
|
||||
if (wtxPtr->second.GetDepthInMainChain(pTxIndex) > 0) {
|
||||
if (wtxPtr->second.GetDepthInMainChain(pTxIndex, std::nullopt) > 0) {
|
||||
nHeight = pTxIndex->nHeight;
|
||||
}
|
||||
}
|
||||
|
@ -2206,7 +2206,8 @@ std::optional<RecipientAddress> CWallet::GenerateChangeAddressForAccount(
|
|||
SpendableInputs CWallet::FindSpendableInputs(
|
||||
ZTXOSelector selector,
|
||||
bool allowTransparentCoinbase,
|
||||
uint32_t minDepth) const {
|
||||
uint32_t minDepth,
|
||||
const std::optional<int>& asOfHeight) const {
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
|
@ -2220,7 +2221,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
SpendableInputs unspent;
|
||||
for (auto const& [wtxid, wtx] : mapWallet) {
|
||||
bool isCoinbase = wtx.IsCoinBase();
|
||||
auto nDepth = wtx.GetDepthInMainChain();
|
||||
auto nDepth = wtx.GetDepthInMainChain(asOfHeight);
|
||||
|
||||
// Filter the transactions before checking for coins
|
||||
if (!CheckFinalTx(wtx)) continue;
|
||||
|
@ -2228,14 +2229,14 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
|
||||
if (selectTransparent &&
|
||||
// skip transparent utxo selection if coinbase spend restrictions are not met
|
||||
(!isCoinbase || (allowTransparentCoinbase && wtx.GetBlocksToMaturity() <= 0))) {
|
||||
(!isCoinbase || (allowTransparentCoinbase && wtx.GetBlocksToMaturity(asOfHeight) <= 0))) {
|
||||
|
||||
for (int i = 0; i < wtx.vout.size(); i++) {
|
||||
const auto& output = wtx.vout[i];
|
||||
isminetype mine = IsMine(output);
|
||||
|
||||
// skip spent utxos
|
||||
if (IsSpent(wtxid, i)) continue;
|
||||
if (IsSpent(wtxid, i, asOfHeight)) continue;
|
||||
// skip utxos that don't belong to the wallet
|
||||
if (mine == ISMINE_NO) continue;
|
||||
// skip utxos that for which we don't have the spending keys, if
|
||||
|
@ -2263,7 +2264,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
SproutPaymentAddress pa = nd.address;
|
||||
|
||||
// skip note which has been spent
|
||||
if (nd.nullifier.has_value() && IsSproutSpent(nd.nullifier.value())) continue;
|
||||
if (nd.nullifier.has_value() && IsSproutSpent(nd.nullifier.value(), asOfHeight)) continue;
|
||||
// skip notes which don't match the source
|
||||
if (!this->SelectorMatchesAddress(selector, pa)) continue;
|
||||
// skip notes for which we don't have the spending key
|
||||
|
@ -2297,7 +2298,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
(unsigned char) j);
|
||||
|
||||
unspent.sproutNoteEntries.push_back(SproutNoteEntry {
|
||||
jsop, pa, plaintext.note(pa), plaintext.memo(), wtx.GetDepthInMainChain() });
|
||||
jsop, pa, plaintext.note(pa), plaintext.memo(), nDepth });
|
||||
|
||||
} catch (const note_decryption_failed &err) {
|
||||
// Couldn't decrypt with this spending key
|
||||
|
@ -2327,7 +2328,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
auto pa = maybe_pa.value();
|
||||
|
||||
// skip notes which have been spent
|
||||
if (nd.nullifier.has_value() && IsSaplingSpent(nd.nullifier.value())) continue;
|
||||
if (nd.nullifier.has_value() && IsSaplingSpent(nd.nullifier.value(), asOfHeight)) continue;
|
||||
// skip notes which do not match the source
|
||||
if (!this->SelectorMatchesAddress(selector, pa)) continue;
|
||||
// skip notes if we don't have the spending key
|
||||
|
@ -2337,7 +2338,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
|
||||
auto note = notePt.note(nd.ivk).value();
|
||||
unspent.saplingNoteEntries.push_back(SaplingNoteEntry {
|
||||
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() });
|
||||
op, pa, note, notePt.memo(), nDepth });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2386,7 +2387,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
orchardWallet.GetFilteredNotes(incomingNotes, ivk, true, true);
|
||||
|
||||
for (auto& noteMeta : incomingNotes) {
|
||||
if (IsOrchardSpent(noteMeta.GetOutPoint())) {
|
||||
if (IsOrchardSpent(noteMeta.GetOutPoint(), asOfHeight)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2396,7 +2397,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
// the transaction does not exist in the main wallet.
|
||||
assert(mit != mapWallet.end());
|
||||
|
||||
int confirmations = mit->second.GetDepthInMainChain();
|
||||
int confirmations = mit->second.GetDepthInMainChain(asOfHeight);
|
||||
if (confirmations < 0) continue;
|
||||
if (confirmations >= minDepth) {
|
||||
noteMeta.SetConfirmations(confirmations);
|
||||
|
@ -2413,7 +2414,7 @@ SpendableInputs CWallet::FindSpendableInputs(
|
|||
* Outpoint is spent if any non-conflicted transaction
|
||||
* spends it:
|
||||
*/
|
||||
bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
|
||||
bool CWallet::IsSpent(const uint256& hash, unsigned int n, const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
const COutPoint outpoint(hash, n);
|
||||
pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
|
||||
|
@ -2423,8 +2424,9 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
|
|||
{
|
||||
const uint256& wtxid = it->second;
|
||||
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0)
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain(asOfHeight) >= 0) {
|
||||
return true; // Spent
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -2433,7 +2435,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
|
|||
* Note is spent if any non-conflicted transaction
|
||||
* spends it:
|
||||
*/
|
||||
bool CWallet::IsSproutSpent(const uint256& nullifier) const {
|
||||
bool CWallet::IsSproutSpent(const uint256& nullifier, const std::optional<int>& asOfHeight) const {
|
||||
LOCK(cs_main);
|
||||
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
|
||||
range = mapTxSproutNullifiers.equal_range(nullifier);
|
||||
|
@ -2441,14 +2443,14 @@ bool CWallet::IsSproutSpent(const uint256& nullifier) const {
|
|||
for (TxNullifiers::const_iterator it = range.first; it != range.second; ++it) {
|
||||
const uint256& wtxid = it->second;
|
||||
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) {
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain(asOfHeight) >= 0) {
|
||||
return true; // Spent
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CWallet::IsSaplingSpent(const uint256& nullifier) const {
|
||||
bool CWallet::IsSaplingSpent(const uint256& nullifier, const std::optional<int>& asOfHeight) const {
|
||||
LOCK(cs_main);
|
||||
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
|
||||
range = mapTxSaplingNullifiers.equal_range(nullifier);
|
||||
|
@ -2456,17 +2458,17 @@ bool CWallet::IsSaplingSpent(const uint256& nullifier) const {
|
|||
for (TxNullifiers::const_iterator it = range.first; it != range.second; ++it) {
|
||||
const uint256& wtxid = it->second;
|
||||
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) {
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain(asOfHeight) >= 0) {
|
||||
return true; // Spent
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CWallet::IsOrchardSpent(const OrchardOutPoint& outpoint) const {
|
||||
bool CWallet::IsOrchardSpent(const OrchardOutPoint& outpoint, const std::optional<int>& asOfHeight) const {
|
||||
for (const auto& txid : orchardWallet.GetPotentialSpends(outpoint)) {
|
||||
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(txid);
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) {
|
||||
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain(asOfHeight) >= 0) {
|
||||
return true; // Spent
|
||||
}
|
||||
}
|
||||
|
@ -4834,7 +4836,7 @@ void CWallet::ReacceptWalletTransactions()
|
|||
CWalletTx& wtx = item.second;
|
||||
assert(wtx.GetHash() == wtxid);
|
||||
|
||||
int nDepth = wtx.GetDepthInMainChain();
|
||||
int nDepth = wtx.GetDepthInMainChain(std::nullopt);
|
||||
|
||||
if (!wtx.IsCoinBase() && nDepth < 0) {
|
||||
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
|
||||
|
@ -4854,7 +4856,7 @@ bool CWalletTx::RelayWalletTransaction()
|
|||
assert(pwallet->GetBroadcastTransactions());
|
||||
if (!IsCoinBase())
|
||||
{
|
||||
if (GetDepthInMainChain() == 0) {
|
||||
if (GetDepthInMainChain(std::nullopt) == 0) {
|
||||
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
|
||||
RelayTransaction((CTransaction)*this);
|
||||
return true;
|
||||
|
@ -4906,10 +4908,10 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
|
|||
return debit;
|
||||
}
|
||||
|
||||
CAmount CWalletTx::GetCredit(const isminefilter& filter) const
|
||||
CAmount CWalletTx::GetCredit(const std::optional<int>& asOfHeight, const isminefilter& filter) const
|
||||
{
|
||||
// Must wait until coinbase is safely deep enough in the chain before valuing it
|
||||
if (IsCoinBase() && GetBlocksToMaturity() > 0)
|
||||
if (IsCoinBase() && GetBlocksToMaturity(asOfHeight) > 0)
|
||||
return 0;
|
||||
|
||||
int64_t credit = 0;
|
||||
|
@ -4939,9 +4941,9 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const
|
|||
return credit;
|
||||
}
|
||||
|
||||
CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
|
||||
CAmount CWalletTx::GetImmatureCredit(const std::optional<int>& asOfHeight, bool fUseCache) const
|
||||
{
|
||||
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
|
||||
if (IsCoinBase() && GetBlocksToMaturity(asOfHeight) > 0 && IsInMainChain(asOfHeight))
|
||||
{
|
||||
if (fUseCache && fImmatureCreditCached)
|
||||
return nImmatureCreditCached;
|
||||
|
@ -4953,13 +4955,13 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const
|
||||
CAmount CWalletTx::GetAvailableCredit(const std::optional<int>& asOfHeight, bool fUseCache, const isminefilter& filter) const
|
||||
{
|
||||
if (pwallet == nullptr)
|
||||
return 0;
|
||||
|
||||
// Must wait until coinbase is safely deep enough in the chain before valuing it
|
||||
if (IsCoinBase() && GetBlocksToMaturity() > 0)
|
||||
if (IsCoinBase() && GetBlocksToMaturity(asOfHeight) > 0)
|
||||
return 0;
|
||||
|
||||
CAmount* cache = nullptr;
|
||||
|
@ -4981,7 +4983,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter
|
|||
uint256 hashTx = GetHash();
|
||||
for (unsigned int i = 0; i < vout.size(); i++)
|
||||
{
|
||||
if (!pwallet->IsSpent(hashTx, i))
|
||||
if (!pwallet->IsSpent(hashTx, i, asOfHeight))
|
||||
{
|
||||
const CTxOut &txout = vout[i];
|
||||
nCredit += pwallet->GetCredit(txout, filter);
|
||||
|
@ -4997,9 +4999,9 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter
|
|||
return nCredit;
|
||||
}
|
||||
|
||||
CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
|
||||
CAmount CWalletTx::GetImmatureWatchOnlyCredit(const std::optional<int>& asOfHeight, const bool fUseCache) const
|
||||
{
|
||||
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
|
||||
if (IsCoinBase() && GetBlocksToMaturity(asOfHeight) > 0 && IsInMainChain(asOfHeight))
|
||||
{
|
||||
if (fUseCache && fImmatureWatchCreditCached)
|
||||
return nImmatureWatchCreditCached;
|
||||
|
@ -5040,14 +5042,17 @@ bool CWalletTx::IsFromMe(const isminefilter& filter) const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool CWalletTx::IsTrusted() const
|
||||
bool CWalletTx::IsTrusted(const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
// Quick answer in most cases
|
||||
if (!CheckFinalTx(*this))
|
||||
return false;
|
||||
int nDepth = GetDepthInMainChain();
|
||||
int nDepth = GetDepthInMainChain(asOfHeight);
|
||||
if (nDepth >= 1)
|
||||
return true;
|
||||
if (asOfHeight.has_value() && nDepth == 0)
|
||||
// don’t trust mempool tx if using `asOfHeight`
|
||||
return false;
|
||||
if (nDepth < 0)
|
||||
return false;
|
||||
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
|
||||
|
@ -5125,7 +5130,7 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime)
|
|||
*/
|
||||
|
||||
|
||||
CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const
|
||||
CAmount CWallet::GetBalance(const std::optional<int>& asOfHeight, const isminefilter& filter, const int min_depth) const
|
||||
{
|
||||
CAmount nTotal = 0;
|
||||
{
|
||||
|
@ -5133,8 +5138,8 @@ CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) con
|
|||
for (const auto& entry : mapWallet)
|
||||
{
|
||||
const CWalletTx* pcoin = &entry.second;
|
||||
if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) {
|
||||
nTotal += pcoin->GetAvailableCredit(true, filter);
|
||||
if (pcoin->IsTrusted(asOfHeight) && pcoin->GetDepthInMainChain(asOfHeight) >= min_depth) {
|
||||
nTotal += pcoin->GetAvailableCredit(asOfHeight, true, filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5142,7 +5147,7 @@ CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) con
|
|||
return nTotal;
|
||||
}
|
||||
|
||||
CAmount CWallet::GetUnconfirmedBalance() const
|
||||
CAmount CWallet::GetUnconfirmedTransparentBalance() const
|
||||
{
|
||||
CAmount nTotal = 0;
|
||||
{
|
||||
|
@ -5150,14 +5155,14 @@ CAmount CWallet::GetUnconfirmedBalance() const
|
|||
for (const auto& entry : mapWallet)
|
||||
{
|
||||
const CWalletTx* pcoin = &entry.second;
|
||||
if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
|
||||
nTotal += pcoin->GetAvailableCredit();
|
||||
if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted(std::nullopt) && pcoin->GetDepthInMainChain(std::nullopt) == 0))
|
||||
nTotal += pcoin->GetAvailableCredit(std::nullopt);
|
||||
}
|
||||
}
|
||||
return nTotal;
|
||||
}
|
||||
|
||||
CAmount CWallet::GetImmatureBalance() const
|
||||
CAmount CWallet::GetImmatureBalance(const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
CAmount nTotal = 0;
|
||||
{
|
||||
|
@ -5165,36 +5170,7 @@ CAmount CWallet::GetImmatureBalance() const
|
|||
for (const auto& entry : mapWallet)
|
||||
{
|
||||
const CWalletTx* pcoin = &entry.second;
|
||||
nTotal += pcoin->GetImmatureCredit();
|
||||
}
|
||||
}
|
||||
return nTotal;
|
||||
}
|
||||
|
||||
CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
|
||||
{
|
||||
CAmount nTotal = 0;
|
||||
{
|
||||
LOCK2(cs_main, cs_wallet);
|
||||
for (const auto& entry : mapWallet)
|
||||
{
|
||||
const CWalletTx* pcoin = &entry.second;
|
||||
if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
|
||||
nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY);
|
||||
}
|
||||
}
|
||||
return nTotal;
|
||||
}
|
||||
|
||||
CAmount CWallet::GetImmatureWatchOnlyBalance() const
|
||||
{
|
||||
CAmount nTotal = 0;
|
||||
{
|
||||
LOCK2(cs_main, cs_wallet);
|
||||
for (const auto& entry : mapWallet)
|
||||
{
|
||||
const CWalletTx* pcoin = &entry.second;
|
||||
nTotal += pcoin->GetImmatureWatchOnlyCredit();
|
||||
nTotal += pcoin->GetImmatureCredit(asOfHeight);
|
||||
}
|
||||
}
|
||||
return nTotal;
|
||||
|
@ -5213,8 +5189,8 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) cons
|
|||
CAmount balance = 0;
|
||||
for (const auto& entry : mapWallet) {
|
||||
const CWalletTx& wtx = entry.second;
|
||||
const int depth = wtx.GetDepthInMainChain();
|
||||
if (depth < 0 || !CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0) {
|
||||
const int depth = wtx.GetDepthInMainChain(std::nullopt);
|
||||
if (depth < 0 || !CheckFinalTx(wtx) || wtx.GetBlocksToMaturity(std::nullopt) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -5240,45 +5216,43 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) cons
|
|||
}
|
||||
|
||||
void CWallet::AvailableCoins(vector<COutput>& vCoins,
|
||||
const std::optional<int>& asOfHeight,
|
||||
bool fOnlyConfirmed,
|
||||
const CCoinControl *coinControl,
|
||||
bool fIncludeZeroValue,
|
||||
bool fIncludeCoinBase,
|
||||
bool fOnlySpendable,
|
||||
int nMinDepth,
|
||||
std::set<CTxDestination>* onlyFilterByDests) const
|
||||
const std::set<CTxDestination>& onlyFilterByDests) const
|
||||
{
|
||||
assert(nMinDepth >= 0);
|
||||
assert(!asOfHeight.has_value() || nMinDepth > 0);
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
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(asOfHeight))
|
||||
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(asOfHeight);
|
||||
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) ||
|
||||
|
@ -5288,17 +5262,17 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins,
|
|||
continue;
|
||||
|
||||
// Filter by specific destinations if needed
|
||||
if (onlyFilterByDests && !onlyFilterByDests->empty()) {
|
||||
if (!onlyFilterByDests.empty()) {
|
||||
CTxDestination address;
|
||||
if (!ExtractDestination(output.scriptPubKey, address) || onlyFilterByDests->count(address) == 0) {
|
||||
if (!ExtractDestination(output.scriptPubKey, address) || onlyFilterByDests.count(address) == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(IsSpent(wtxid, i)) && 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5455,8 +5429,8 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
|
|||
{
|
||||
// Output parameter fOnlyCoinbaseCoinsRet is set to true when the only available coins are coinbase utxos.
|
||||
vector<COutput> vCoinsNoCoinbase, vCoinsWithCoinbase;
|
||||
AvailableCoins(vCoinsNoCoinbase, true, coinControl, false, false);
|
||||
AvailableCoins(vCoinsWithCoinbase, true, coinControl, false, true);
|
||||
AvailableCoins(vCoinsNoCoinbase, std::nullopt, true, coinControl, false, false);
|
||||
AvailableCoins(vCoinsWithCoinbase, std::nullopt, true, coinControl, false, true);
|
||||
fOnlyCoinbaseCoinsRet = vCoinsNoCoinbase.size() == 0 && vCoinsWithCoinbase.size() > 0;
|
||||
|
||||
// If coinbase utxos can only be sent to zaddrs, exclude any coinbase utxos from coin selection.
|
||||
|
@ -5725,7 +5699,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
|
|||
//reflecting an assumption the user would accept a bit more delay for
|
||||
//a chance at a free transaction.
|
||||
//But mempool inputs might still be in the mempool, so their age stays 0
|
||||
int age = pcoin.first->GetDepthInMainChain();
|
||||
int age = pcoin.first->GetDepthInMainChain(std::nullopt);
|
||||
if (age != 0)
|
||||
age += 1;
|
||||
dPriority += (double)nCredit * age;
|
||||
|
@ -6220,7 +6194,7 @@ int64_t CWallet::GetOldestKeyPoolTime()
|
|||
return keypool.nTime;
|
||||
}
|
||||
|
||||
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
|
||||
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(const std::optional<int>& asOfHeight)
|
||||
{
|
||||
map<CTxDestination, CAmount> balances;
|
||||
|
||||
|
@ -6230,13 +6204,13 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
|
|||
{
|
||||
CWalletTx *pcoin = &walletEntry.second;
|
||||
|
||||
if (!CheckFinalTx(*pcoin) || !pcoin->IsTrusted())
|
||||
if (!CheckFinalTx(*pcoin) || !pcoin->IsTrusted(asOfHeight))
|
||||
continue;
|
||||
|
||||
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
|
||||
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity(asOfHeight) > 0)
|
||||
continue;
|
||||
|
||||
int nDepth = pcoin->GetDepthInMainChain();
|
||||
int nDepth = pcoin->GetDepthInMainChain(asOfHeight);
|
||||
if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1))
|
||||
continue;
|
||||
|
||||
|
@ -6248,7 +6222,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
|
|||
if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr))
|
||||
continue;
|
||||
|
||||
CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue;
|
||||
CAmount n = IsSpent(walletEntry.first, i, asOfHeight) ? 0 : pcoin->vout[i].nValue;
|
||||
|
||||
if (!balances.count(addr))
|
||||
balances[addr] = 0;
|
||||
|
@ -6984,39 +6958,43 @@ 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;
|
||||
AssertLockHeld(cs_main);
|
||||
int effectiveChainHeight = min(chainActive.Height(), asOfHeight.value_or(chainActive.Height()));
|
||||
|
||||
// Find the block it claims to be in
|
||||
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
|
||||
if (mi == mapBlockIndex.end())
|
||||
return 0;
|
||||
CBlockIndex* pindex = (*mi).second;
|
||||
if (!pindex || !chainActive.Contains(pindex))
|
||||
if (!pindex ||
|
||||
!chainActive.Contains(pindex) ||
|
||||
pindex->nHeight > effectiveChainHeight) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pindexRet = pindex;
|
||||
return chainActive.Height() - pindex->nHeight + 1;
|
||||
return effectiveChainHeight - pindex->nHeight + 1;
|
||||
}
|
||||
|
||||
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
|
||||
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet, const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
int nResult = GetDepthInMainChainINTERNAL(pindexRet);
|
||||
if (nResult == 0 && !mempool.exists(GetHash()))
|
||||
int nResult = GetDepthInMainChainINTERNAL(pindexRet, asOfHeight);
|
||||
if (nResult == 0 && (asOfHeight.has_value() || !mempool.exists(GetHash())))
|
||||
return -1; // Not in chain, not in mempool
|
||||
|
||||
return nResult;
|
||||
}
|
||||
|
||||
int CMerkleTx::GetBlocksToMaturity() const
|
||||
int CMerkleTx::GetBlocksToMaturity(const std::optional<int>& asOfHeight) const
|
||||
{
|
||||
if (!IsCoinBase())
|
||||
return 0;
|
||||
return max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain());
|
||||
return max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain(asOfHeight));
|
||||
}
|
||||
|
||||
|
||||
|
@ -7098,6 +7076,7 @@ void CWallet::GetFilteredNotes(
|
|||
std::vector<SaplingNoteEntry>& saplingEntriesRet,
|
||||
std::vector<OrchardNoteMetadata>& orchardNotesRet,
|
||||
const std::optional<NoteFilter>& noteFilter,
|
||||
const std::optional<int>& asOfHeight,
|
||||
int minDepth,
|
||||
int maxDepth,
|
||||
bool ignoreSpent,
|
||||
|
@ -7116,8 +7095,8 @@ void CWallet::GetFilteredNotes(
|
|||
|
||||
// Filter the transactions before checking for notes
|
||||
if (!CheckFinalTx(wtx) ||
|
||||
wtx.GetDepthInMainChain() < minDepth ||
|
||||
wtx.GetDepthInMainChain() > maxDepth) {
|
||||
wtx.GetDepthInMainChain(asOfHeight) < minDepth ||
|
||||
wtx.GetDepthInMainChain(asOfHeight) > maxDepth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -7137,7 +7116,7 @@ void CWallet::GetFilteredNotes(
|
|||
}
|
||||
|
||||
// skip note which has been spent
|
||||
if (ignoreSpent && nd.nullifier && IsSproutSpent(*nd.nullifier)) {
|
||||
if (ignoreSpent && nd.nullifier && IsSproutSpent(*nd.nullifier, asOfHeight)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -7175,7 +7154,7 @@ void CWallet::GetFilteredNotes(
|
|||
(unsigned char) j);
|
||||
|
||||
sproutEntriesRet.push_back(SproutNoteEntry {
|
||||
jsop, pa, plaintext.note(pa), plaintext.memo(), wtx.GetDepthInMainChain() });
|
||||
jsop, pa, plaintext.note(pa), plaintext.memo(), wtx.GetDepthInMainChain(asOfHeight) });
|
||||
|
||||
} catch (const note_decryption_failed &err) {
|
||||
// Couldn't decrypt with this spending key
|
||||
|
@ -7206,7 +7185,7 @@ void CWallet::GetFilteredNotes(
|
|||
continue;
|
||||
}
|
||||
|
||||
if (ignoreSpent && nd.nullifier.has_value() && IsSaplingSpent(nd.nullifier.value())) {
|
||||
if (ignoreSpent && nd.nullifier.has_value() && IsSaplingSpent(nd.nullifier.value(), asOfHeight)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -7222,7 +7201,7 @@ void CWallet::GetFilteredNotes(
|
|||
|
||||
auto note = notePt.note(nd.ivk).value();
|
||||
saplingEntriesRet.push_back(SaplingNoteEntry {
|
||||
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() });
|
||||
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain(asOfHeight) });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7248,13 +7227,13 @@ void CWallet::GetFilteredNotes(
|
|||
}
|
||||
|
||||
for (auto& noteMeta : orchardNotes) {
|
||||
if (ignoreSpent && IsOrchardSpent(noteMeta.GetOutPoint())) {
|
||||
if (ignoreSpent && IsOrchardSpent(noteMeta.GetOutPoint(), asOfHeight)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto wtx = GetWalletTx(noteMeta.GetOutPoint().hash);
|
||||
if (wtx) {
|
||||
auto confirmations = wtx->GetDepthInMainChain();
|
||||
auto confirmations = wtx->GetDepthInMainChain(asOfHeight);
|
||||
if (confirmations >= minDepth && confirmations <= maxDepth) {
|
||||
noteMeta.SetConfirmations(confirmations);
|
||||
orchardNotesRet.push_back(noteMeta);
|
||||
|
|
|
@ -399,7 +399,11 @@ struct SaplingNoteEntry
|
|||
class CMerkleTx : public CTransaction
|
||||
{
|
||||
private:
|
||||
int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const;
|
||||
/**
|
||||
* **NB**: Unlike `GetDepthInMainChain`, this returns 0 for any case where
|
||||
* it’s not in the chain (including if it’s not in the mempool).
|
||||
*/
|
||||
int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet, const std::optional<int>& asOfHeight) const;
|
||||
|
||||
public:
|
||||
uint256 hashBlock;
|
||||
|
@ -438,13 +442,16 @@ public:
|
|||
/**
|
||||
* Return depth of transaction in blockchain:
|
||||
* -1 : not in blockchain, and not in memory pool (conflicted transaction)
|
||||
* 0 : in memory pool, waiting to be included in a block
|
||||
* 0 : in memory pool, waiting to be included in a block (never returned if `asOfHeight` is set)
|
||||
* >=1 : this many blocks deep in the main chain
|
||||
*/
|
||||
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 GetDepthInMainChain(const CBlockIndex* &pindexRet, const std::optional<int>& asOfHeight) const;
|
||||
int GetDepthInMainChain(const std::optional<int>& asOfHeight) const {
|
||||
const CBlockIndex *pindexRet;
|
||||
return GetDepthInMainChain(pindexRet, asOfHeight);
|
||||
}
|
||||
bool IsInMainChain(const std::optional<int>& asOfHeight) const { return GetDepthInMainChain(asOfHeight) > 0; }
|
||||
int GetBlocksToMaturity(const std::optional<int>& asOfHeight) const;
|
||||
/** Pass this transaction to the mempool. Fails if absolute fee exceeds maxTxFee. */
|
||||
bool AcceptToMemoryPool(CValidationState& state, bool fLimitFree=true, bool fRejectAbsurdFee=true);
|
||||
};
|
||||
|
@ -670,10 +677,10 @@ public:
|
|||
|
||||
//! filter decides which addresses will count towards the debit
|
||||
CAmount GetDebit(const isminefilter& filter) const;
|
||||
CAmount GetCredit(const isminefilter& filter) const;
|
||||
CAmount GetImmatureCredit(bool fUseCache=true) const;
|
||||
CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const;
|
||||
CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const;
|
||||
CAmount GetCredit(const std::optional<int>& asOfHeight, const isminefilter& filter) const;
|
||||
CAmount GetImmatureCredit(const std::optional<int>& asOfHeight, bool fUseCache=true) const;
|
||||
CAmount GetAvailableCredit(const std::optional<int>& asOfHeight, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const;
|
||||
CAmount GetImmatureWatchOnlyCredit(const std::optional<int>& asOfHeight, const bool fUseCache=true) const;
|
||||
CAmount GetChange() const;
|
||||
|
||||
void GetAmounts(std::list<COutputEntry>& listReceived,
|
||||
|
@ -681,7 +688,7 @@ public:
|
|||
|
||||
bool IsFromMe(const isminefilter& filter) const;
|
||||
|
||||
bool IsTrusted() const;
|
||||
bool IsTrusted(const std::optional<int>& asOfHeight) const;
|
||||
|
||||
int64_t GetTxTime() const;
|
||||
int GetRequestCount() const;
|
||||
|
@ -1428,15 +1435,18 @@ public:
|
|||
|
||||
/**
|
||||
* populate vCoins with vector of available COutputs.
|
||||
*
|
||||
* **NB**: If `asOfHeight` is specified, then `nMinDepth` must be `> 0`.
|
||||
*/
|
||||
void AvailableCoins(std::vector<COutput>& vCoins,
|
||||
const std::optional<int>& asOfHeight,
|
||||
bool fOnlyConfirmed=true,
|
||||
const CCoinControl *coinControl = NULL,
|
||||
bool fIncludeZeroValue=false,
|
||||
bool fIncludeCoinBase=true,
|
||||
bool fOnlySpendable=false,
|
||||
int nMinDepth = 0,
|
||||
std::set<CTxDestination>* onlyFilterByDests = nullptr) const;
|
||||
const std::set<CTxDestination>& onlyFilterByDests = std::set<CTxDestination>()) const;
|
||||
|
||||
/**
|
||||
* Shuffle and select coins until nTargetValue is reached while avoiding
|
||||
|
@ -1516,16 +1526,17 @@ public:
|
|||
SpendableInputs FindSpendableInputs(
|
||||
ZTXOSelector paymentSource,
|
||||
bool allowTransparentCoinbase,
|
||||
uint32_t minDepth) const;
|
||||
uint32_t minDepth,
|
||||
const std::optional<int>& asOfHeight) const;
|
||||
|
||||
bool SelectorMatchesAddress(const ZTXOSelector& source, const CTxDestination& a0) const;
|
||||
bool SelectorMatchesAddress(const ZTXOSelector& source, const libzcash::SproutPaymentAddress& a0) const;
|
||||
bool SelectorMatchesAddress(const ZTXOSelector& source, const libzcash::SaplingPaymentAddress& a0) const;
|
||||
|
||||
bool IsSpent(const uint256& hash, unsigned int n) const;
|
||||
bool IsSproutSpent(const uint256& nullifier) const;
|
||||
bool IsSaplingSpent(const uint256& nullifier) const;
|
||||
bool IsOrchardSpent(const OrchardOutPoint& outpoint) const;
|
||||
bool IsSpent(const uint256& hash, unsigned int n, const std::optional<int>& asOfHeight) const;
|
||||
bool IsSproutSpent(const uint256& nullifier, const std::optional<int>& asOfHeight) const;
|
||||
bool IsSaplingSpent(const uint256& nullifier, const std::optional<int>& asOfHeight) const;
|
||||
bool IsOrchardSpent(const OrchardOutPoint& outpoint, const std::optional<int>& asOfHeight) const;
|
||||
|
||||
bool IsLockedCoin(uint256 hash, unsigned int n) const;
|
||||
void LockCoin(COutPoint& output);
|
||||
|
@ -1793,11 +1804,14 @@ public:
|
|||
void ReacceptWalletTransactions();
|
||||
void ResendWalletTransactions(int64_t nBestBlockTime);
|
||||
std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime);
|
||||
CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const;
|
||||
CAmount GetUnconfirmedBalance() const;
|
||||
CAmount GetImmatureBalance() const;
|
||||
CAmount GetUnconfirmedWatchOnlyBalance() const;
|
||||
CAmount GetImmatureWatchOnlyBalance() const;
|
||||
CAmount GetBalance(const std::optional<int>& asOfHeight,
|
||||
const isminefilter& filter=ISMINE_SPENDABLE,
|
||||
const int min_depth=0) const;
|
||||
/**
|
||||
* Returns the balance taking into account _only_ transactions in the mempool.
|
||||
*/
|
||||
CAmount GetUnconfirmedTransparentBalance() const;
|
||||
CAmount GetImmatureBalance(const std::optional<int>& asOfHeight) const;
|
||||
CAmount GetLegacyBalance(const isminefilter& filter, int minDepth) const;
|
||||
|
||||
/**
|
||||
|
@ -1872,7 +1886,7 @@ public:
|
|||
void GetAllReserveKeys(std::set<CKeyID>& setAddress) const;
|
||||
|
||||
std::set< std::set<CTxDestination> > GetAddressGroupings();
|
||||
std::map<CTxDestination, CAmount> GetAddressBalances();
|
||||
std::map<CTxDestination, CAmount> GetAddressBalances(const std::optional<int>& asOfHeight);
|
||||
|
||||
std::optional<uint256> GetSproutNoteNullifier(
|
||||
const JSDescription& jsdesc,
|
||||
|
@ -2079,6 +2093,7 @@ public:
|
|||
std::vector<SaplingNoteEntry>& saplingEntriesRet,
|
||||
std::vector<OrchardNoteMetadata>& orchardNotesRet,
|
||||
const std::optional<NoteFilter>& noteFilter,
|
||||
const std::optional<int>& asOfHeight,
|
||||
int minDepth,
|
||||
int maxDepth=INT_MAX,
|
||||
bool ignoreSpent=true,
|
||||
|
|
Loading…
Reference in New Issue