Merge branch 'master' into fetch-params-lock

This commit is contained in:
Greg Pfeil 2022-12-06 17:02:35 -07:00
commit 4cd627a985
58 changed files with 1258 additions and 504 deletions

12
Cargo.lock generated
View File

@ -50,6 +50,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.65"
@ -964,6 +973,7 @@ dependencies = [
"memuse",
"metrics",
"metrics-exporter-prometheus",
"metrics-util",
"nonempty",
"orchard",
"rand 0.8.5",
@ -977,6 +987,7 @@ dependencies = [
"subtle",
"thiserror",
"time",
"tokio",
"tracing",
"tracing-appender",
"tracing-core",
@ -1108,6 +1119,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d24dc2dbae22bff6f1f9326ffce828c9f07ef9cc1e8002e5279f845432a30a"
dependencies = [
"aho-corasick",
"crossbeam-epoch",
"crossbeam-utils",
"hashbrown",

View File

@ -77,6 +77,8 @@ rayon = "1.5"
ipnet = "2"
metrics = "0.20"
metrics-exporter-prometheus = "0.11"
metrics-util = { version = "0.14", default-features = false, features = ["layer-filter"] }
tokio = { version = "1", features = ["rt", "net", "time"] }
# General tool dependencies
gumdrop = "0.8"

View File

@ -1,4 +1,4 @@
Zcash 5.3.0
Zcash 5.3.2
<img align="right" width="120" height="80" src="doc/imgs/logo.png">
===========

View File

@ -2,7 +2,7 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
AC_PREREQ([2.60])
define(_CLIENT_VERSION_MAJOR, 5)
define(_CLIENT_VERSION_MINOR, 3)
define(_CLIENT_VERSION_REVISION, 0)
define(_CLIENT_VERSION_REVISION, 2)
define(_CLIENT_VERSION_BUILD, 50)
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))

View File

@ -1,3 +1,21 @@
zcash (5.3.2) stable; urgency=medium
* 5.3.2 release.
-- Electric Coin Company <team@electriccoin.co> Sat, 03 Dec 2022 19:58:44 +0000
zcash (5.3.1) stable; urgency=medium
* 5.3.1 release.
-- Electric Coin Company <team@electriccoin.co> Fri, 02 Dec 2022 02:46:42 +0000
zcash (5.3.1~rc1) stable; urgency=medium
* 5.3.1-rc1 release.
-- Electric Coin Company <team@electriccoin.co> Wed, 23 Nov 2022 22:18:55 -0700
zcash (5.3.0) stable; urgency=medium
* 5.3.0 release.

View File

@ -55,4 +55,5 @@ fi
zcash-fetch-params
touch .zcash/zcash.conf
echo "Starting: ${ZCASHD_CMD}"
# shellcheck disable=SC2294 # see #6297
eval exec "${ZCASHD_CMD}" "${@}"

View File

@ -1,5 +1,5 @@
---
name: "zcash-5.3.0"
name: "zcash-5.3.2"
enable_cache: true
distro: "debian"
suites:

View File

@ -1,5 +1,5 @@
---
name: "zcash-5.3.0"
name: "zcash-5.3.2"
enable_cache: true
distro: "debian"
suites:

View File

@ -20,6 +20,7 @@ $(package)_download_path_freebsd=https://github.com/llvm/llvm-project/releases/d
$(package)_download_file_freebsd=clang+llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
$(package)_file_name_freebsd=clang-llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
$(package)_sha256_hash_freebsd=b0a7b86dacb12afb8dd2ca99ea1b894d9cce84aab7711cb1964b3005dfb09af3
$(package)_download_path_aarch64_linux=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
$(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
$(package)_sha256_hash_aarch64_linux=1a81fda984f5e607584916fdf69cf41e5385b219b983544d2c1a14950d5a65cf

View File

@ -6,5 +6,6 @@ $(package)_download_file=v$($(package)_version).tar.gz
$(package)_sha256_hash=8d6aa7d77ad0abb35bb6139cb9a33597ac4c5b33da6a004ae42429b8598c9605
define $(package)_stage_cmds
cp -a ./source $($(package)_staging_dir)$(host_prefix)/include
mkdir -p $($(package)_staging_dir)$(host_prefix)/include && \
cp -a ./source $($(package)_staging_dir)$(host_prefix)/include/utf8cpp
endef

View File

@ -1,11 +1,11 @@
Zcash Contributors
==================
Jack Grigg (1243)
Kris Nuttycombe (582)
Jack Grigg (1244)
Kris Nuttycombe (585)
Simon Liu (460)
Sean Bowe (389)
Daira Hopwood (343)
Daira Hopwood (369)
Eirik Ogilvie-Wigley (216)
Wladimir J. van der Laan (158)
Pieter Wuille (143)
@ -18,8 +18,9 @@ Jay Graber (89)
Larry Ruane (88)
Marco Falke (86)
Cory Fields (78)
sasha (62)
Matt Corallo (60)
sasha (59)
Greg Pfeil (60)
Nathan Wilcox (57)
practicalswift (42)
Kevin Gallagher (38)
@ -30,7 +31,6 @@ Carl Dong (26)
Gregory Maxwell (24)
John Newbery (23)
Jorge Timón (22)
Greg Pfeil (20)
furszy (18)
Jonathan "Duke" Leto (18)
syd (16)
@ -38,13 +38,13 @@ Patick Strateman (16)
Charlie O'Keefe (15)
avnish (14)
Per Grön (14)
Suhas Daftuar (13)
Benjamin Winston (13)
Steven Smith (12)
Pavel Janík (12)
Patrick Strateman (12)
Jeremy Rubin (12)
Ariel Gabizon (12)
Suhas Daftuar (11)
Paige Peterson (11)
Kaz Wesley (11)
João Barbosa (11)
@ -123,6 +123,7 @@ Pejvan (2)
Pavol Rusnak (2)
Pavel Vasin (2)
Mustafa (2)
Miodrag Popović (2)
Matthew King (2)
Mary Moore-Simmons (2)
Marek (2)
@ -159,6 +160,7 @@ kirkalx (1)
kazcw (1)
jeff-liang (1)
jc (1)
idm (1)
glowang (1)
ewillbefull@gmail.com (1)
emilrus (1)

View File

@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
.TH ZCASH-CLI "1" "October 2022" "zcash-cli v5.3.0" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2.
.TH ZCASH-CLI "1" "December 2022" "zcash-cli v5.3.2" "User Commands"
.SH NAME
zcash-cli \- manual page for zcash-cli v5.3.0
zcash-cli \- manual page for zcash-cli v5.3.2
.SH DESCRIPTION
Zcash RPC client version v5.3.0
Zcash RPC client version v5.3.2
.PP
In order to ensure you are adequately protecting your privacy when using Zcash,
please see <https://z.cash/support/security/>.

View File

@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
.TH ZCASH-TX "1" "October 2022" "zcash-tx v5.3.0" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2.
.TH ZCASH-TX "1" "December 2022" "zcash-tx v5.3.2" "User Commands"
.SH NAME
zcash-tx \- manual page for zcash-tx v5.3.0
zcash-tx \- manual page for zcash-tx v5.3.2
.SH DESCRIPTION
Zcash zcash\-tx utility version v5.3.0
Zcash zcash\-tx utility version v5.3.2
.SS "Usage:"
.TP
zcash\-tx [options] <hex\-tx> [commands]

View File

@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
.TH ZCASHD-WALLET-TOOL "1" "October 2022" "zcashd-wallet-tool v5.3.0" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2.
.TH ZCASHD-WALLET-TOOL "1" "December 2022" "zcashd-wallet-tool v5.3.2" "User Commands"
.SH NAME
zcashd-wallet-tool \- manual page for zcashd-wallet-tool v5.3.0
zcashd-wallet-tool \- manual page for zcashd-wallet-tool v5.3.2
.SH SYNOPSIS
.B zcashd-wallet-tool
[\fI\,OPTIONS\/\fR]

View File

@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
.TH ZCASHD "1" "October 2022" "zcashd v5.3.0" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2.
.TH ZCASHD "1" "December 2022" "zcashd v5.3.2" "User Commands"
.SH NAME
zcashd \- manual page for zcashd v5.3.0
zcashd \- manual page for zcashd v5.3.2
.SH DESCRIPTION
Zcash Daemon version v5.3.0
Zcash Daemon version v5.3.2
.PP
In order to ensure you are adequately protecting your privacy when using Zcash,
please see <https://z.cash/support/security/>.
@ -94,7 +94,7 @@ Keep at most <n> unconnectable transactions in memory (default: 100)
.HP
\fB\-par=\fR<n>
.IP
Set the number of script verification threads (\fB\-8\fR to 16, 0 = auto, <0 =
Set the number of script verification threads (\fB\-6\fR to 16, 0 = auto, <0 =
leave that many cores free, default: 0)
.HP
\fB\-pid=\fR<file>
@ -416,6 +416,10 @@ Expose node metrics in the Prometheus exposition format. An HTTP
listener will be started on <port>, which responds to GET requests on
any request path. Use \fB\-metricsallowip\fR and \fB\-metricsbind\fR to control
access.
.HP
\fB\-debugmetrics\fR
.IP
Include debug metrics in exposed node metrics.
.PP
Debugging/Testing options:
.HP

View File

@ -4,8 +4,3 @@ release-notes at release time)
Notable changes
===============
Fixed
-----
This release fixes an error "Assertion `uResultHeight == rewindHeight` failed" (#5958)
that could sometimes happen when restarting a node.

View File

@ -0,0 +1,99 @@
Notable changes
===============
Fixed
-----
This release fixes an error "Assertion `uResultHeight == rewindHeight` failed" (#5958)
that could sometimes happen when restarting a node.
Memory Usage Improvement
------------------------
The memory usage of zcashd has been reduced by not keeping Equihash solutions for all
block headers in memory.
Changelog
=========
Daira Hopwood (14):
Always use a tuple as right argument of % in new Python code.
Report the prevout for each transparent input as it is being checked
Update authors of librustzcash to include Greg Pfeil.
Ensure that the optimization of not scanning blocks prior to the wallet's birthday does not cause us to try to "rewind" the Orchard wallet to a height after its current checkpoint.
Improve a comment about the wallet birthday scanning optimization.
Add release notes for the fix to #5958.
Fix a Markdown syntax error
Error reporting improvements.
Fix a dependency of the `show_help` RPC test on the number of cores, and an incompatibility with Python 3.9 in the test framework that affected the `receivedby` extended RPC test.
Avoid storing the Equihash solution in a CBlockIndex object once it has been written to the leveldb database.
Improve handling of database read errors.
Add Prometheus metrics so we have more visibility into what is going on with the Equihash solution trimming:
Declare `CBlockTreeDB::Read*` methods as `const` when they are trivially so.
Update constants
Greg Pfeil (39):
Fix display of binary name in error messages.
Address review feedback and fixed test failures
Check dependency updates on the correct branch
updatecheck: fix GitHub auth
updatecheck: simplify token handling
updatecheck: support XDG-based token location
`zcash --help` test improvements
Remove the PR template
Apply suggestions from code review
Small formatting change
Improve z_sendmany documentation
Avoid inconsistent Python lookup
Propagate asOfHeight to all relevant RPC calls
Implement `asOfHeight`
Add additional asOfHeight tests
Dont ignore asOfHeight in IsSpent calls
Extract asOfHeight info from RPC calls
Ignore mempool when asOfHeight is set
Fix calls that should have specified asOfHeight
GetUnconfirmedBalance should not take asOfHeight
Require minconf > 0 when asOfHeight is provided
Add error cases and default to `asOfHeight`
Work around #6262 in wallet_listunspent
Dont trust mempool tx when using `asOfHeight`
Apply suggestions from code review
Add matured_at_height test helper
Add FIXMEs to repair comments after #6262 is fixed
Update src/rpc/server.cpp
Apply suggestions from code review
Fix small error in code review suggestions
Revert change to getbalance minconf
Revert getinfo support of asOfHeight
Change asOfHeight to use -1 as default
Change asOfHeight to preserve Bitcoin compat
Apply suggestions from code review
Simplify filtering AvailableCoins by destination
Postpone dependency updates for v5.3.1
make-release.py: Versioning changes for 5.3.1-rc1.
make-release.py: Updated manpages for 5.3.1-rc1.
Jack Grigg (1):
Place zcashd.debug.* metrics behind a -debugmetrics config option
Kris Nuttycombe (3):
Add extra detail related to transparent inputs and outputs.
Add `unspent_as_of` argument to `listunspent`
Add RPC test for wallet_listunspent changes
Miodrag Popović (2):
FindNextBlocksToDownload(): Fetch active consensus params to read nMinimumChainWork
Headers sync timeout: Use EstimateNetHeight() for closer approximation of remaining headers to download
Suhas Daftuar (2):
Delay parallel block download until chain has sufficient work
Add timeout for headers sync
idm (1):
fix aarch64 dependency native clang download URL
sasha (3):
Update gitian-linux-parallel.yml
Fix gitian version string issue by reverting the GIT_DIR backport commit
Remove `git_check_in_repo` from genbuild.sh to fix gitian version string

View File

@ -0,0 +1,135 @@
Notable changes
===============
Fixed
-----
This release fixes an error "Assertion `uResultHeight == rewindHeight` failed" (#5958)
that could sometimes happen when restarting a node.
Memory Usage Improvement
------------------------
The memory usage of zcashd has been reduced by not keeping Equihash solutions for all
block headers in memory.
RPC changes
-----------
The following RPC methods that query wallet state now support an optional `asOfHeight`
parameter, to execute the query as if it were run when the blockchain was at the height
specified by this argument:
* `getbalance`
* `getreceivedbyaddress`
* `gettransaction` (*)
* `getwalletinfo`
* `listaddressgroupings`
* `listreceivedbyaddress` (*)
* `listsinceblock` (*)
* `listtransactions`
* `listunspent` (*)
* `z_getbalanceforaccount`
* `z_getbalanceforviewingkey`
* `z_getmigrationstatus`
* `z_getnotescount`
* `z_listreceivedbyaddress`
* `z_listunspent`
(*) For these methods, additional parameters have been added to maintain
compatibility of parameter lists with Bitcoin Core. Default values should be
passed for these additional parameters in order to use `asOfHeight`. See the
[RPC documentation](https://zcash.github.io/) for details.
Changelog
=========
Daira Hopwood (21):
Always use a tuple as right argument of % in new Python code.
Report the prevout for each transparent input as it is being checked
Update authors of librustzcash to include Greg Pfeil.
Ensure that the optimization of not scanning blocks prior to the wallet's birthday does not cause us to try to "rewind" the Orchard wallet to a height after its current checkpoint.
Improve a comment about the wallet birthday scanning optimization.
Add release notes for the fix to #5958.
Fix a Markdown syntax error
Error reporting improvements.
Fix a dependency of the `show_help` RPC test on the number of cores, and an incompatibility with Python 3.9 in the test framework that affected the `receivedby` extended RPC test.
Avoid storing the Equihash solution in a CBlockIndex object once it has been written to the leveldb database.
Improve handling of database read errors.
Add Prometheus metrics so we have more visibility into what is going on with the Equihash solution trimming:
Declare `CBlockTreeDB::Read*` methods as `const` when they are trivially so.
Update constants
Add release notes for #6122 (`asOfHeight` RPC parameters).
Update release notes: z_getnotescount already had the minconf parameter
Add release notes for #6122 (`asOfHeight` RPC parameters).
Update release notes: z_getnotescount already had the minconf parameter
Bump timestamps and add libcxx/native_clang 15.0.6 in `postponed-updates.txt`.
make-release.py: Versioning changes for 5.3.1.
make-release.py: Updated manpages for 5.3.1.
Greg Pfeil (40):
Fix display of binary name in error messages.
Address review feedback and fixed test failures
Check dependency updates on the correct branch
updatecheck: fix GitHub auth
updatecheck: simplify token handling
updatecheck: support XDG-based token location
`zcash --help` test improvements
Remove the PR template
Apply suggestions from code review
Small formatting change
Improve z_sendmany documentation
Avoid inconsistent Python lookup
Propagate asOfHeight to all relevant RPC calls
Implement `asOfHeight`
Add additional asOfHeight tests
Dont ignore asOfHeight in IsSpent calls
Extract asOfHeight info from RPC calls
Ignore mempool when asOfHeight is set
Fix calls that should have specified asOfHeight
GetUnconfirmedBalance should not take asOfHeight
Require minconf > 0 when asOfHeight is provided
Add error cases and default to `asOfHeight`
Work around #6262 in wallet_listunspent
Dont trust mempool tx when using `asOfHeight`
Apply suggestions from code review
Add matured_at_height test helper
Add FIXMEs to repair comments after #6262 is fixed
Update src/rpc/server.cpp
Apply suggestions from code review
Fix small error in code review suggestions
Revert change to getbalance minconf
Revert getinfo support of asOfHeight
Change asOfHeight to use -1 as default
Change asOfHeight to preserve Bitcoin compat
Apply suggestions from code review
Simplify filtering AvailableCoins by destination
Postpone dependency updates for v5.3.1
make-release.py: Versioning changes for 5.3.1-rc1.
make-release.py: Updated manpages for 5.3.1-rc1.
make-release.py: Updated release notes and changelog for 5.3.1-rc1.
Jack Grigg (1):
Place zcashd.debug.* metrics behind a -debugmetrics config option
Kris Nuttycombe (3):
Add extra detail related to transparent inputs and outputs.
Add `unspent_as_of` argument to `listunspent`
Add RPC test for wallet_listunspent changes
Miodrag Popović (2):
FindNextBlocksToDownload(): Fetch active consensus params to read nMinimumChainWork
Headers sync timeout: Use EstimateNetHeight() for closer approximation of remaining headers to download
Suhas Daftuar (2):
Delay parallel block download until chain has sufficient work
Add timeout for headers sync
idm (1):
fix aarch64 dependency native clang download URL
sasha (3):
Update gitian-linux-parallel.yml
Fix gitian version string issue by reverting the GIT_DIR backport commit
Remove `git_check_in_repo` from genbuild.sh to fix gitian version string

View File

@ -0,0 +1,26 @@
Notable changes
===============
Fixed
-----
This is a hotfix release that fixes a regression in memory usage during
Initial Block Download. The regression was indirectly caused by a change
to prioritize downloading headers (PR #6231), introduced in release 5.3.1.
It caused memory usage for new nodes to spike to roughly 11 GiB about an
hour after starting Initial Block Download.
The issue fixed by this release does not affect nodes that start from
a fully synced chain, or that had sufficient memory available to get
past the memory usage spike.
Changelog
=========
Daira Hopwood (5):
Revert "Headers-first fix"
Add release notes for the IBD memory spike issue.
Postpone updates.
make-release.py: Versioning changes for 5.3.2.
make-release.py: Updated manpages for 5.3.2.

View File

@ -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',

View File

@ -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 dont have access to ones well 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)

View File

@ -14,7 +14,7 @@ from decimal import Decimal
def get_sub_array_from_array(object_array, to_match):
'''
Finds and returns a sub array from an array of arrays.
to_match should be a unique idetifier of a sub array
to_match should be a unique identifier of a sub array
'''
for item in object_array:
all_match = True

View File

@ -13,7 +13,7 @@ from test_framework.util import assert_equal, assert_true, zcashd_binary
import subprocess
import tempfile
help_message = """
help_message_1 = """
In order to ensure you are adequately protecting your privacy when using Zcash,
please see <https://z.cash/support/security/>.
@ -85,7 +85,8 @@ Options:
Keep at most <n> unconnectable transactions in memory (default: 100)
-par=<n>
Set the number of script verification threads (-8 to 16, 0 = auto, <0 =
Set the number of script verification threads (""" # nondeterministic part here
help_message_2 = """, 0 = auto, <0 =
leave that many cores free, default: 0)
-pid=<file>
@ -342,6 +343,9 @@ Monitoring options:
any request path. Use -metricsallowip and -metricsbind to control
access.
-debugmetrics
Include debug metrics in exposed node metrics.
Debugging/Testing options:
-debug=<category>
@ -492,7 +496,8 @@ class ShowHelpTest(BitcoinTestFramework):
assert_equal(process.returncode, 0)
log_stdout.seek(0)
stdout = log_stdout.read().decode('utf-8')
assert_true(help_message in stdout)
assert_true(help_message_1 in stdout)
assert_true(help_message_2 in stdout)
def run_test(self):
self.show_help()

View File

@ -107,7 +107,7 @@ def all_interfaces():
max_possible *= 2
else:
break
namestr = names.tostring()
namestr = names.tobytes()
return [(namestr[i:i+16].split(b'\0', 1)[0],
socket.inet_ntoa(namestr[i+20:i+24]))
for i in range(0, outbytes, struct_size)]

View File

@ -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)

View File

@ -214,7 +214,7 @@ def wait_for_bitcoind_start(process, url, i):
'''
while True:
if process.poll() is not None:
raise Exception('%s exited with status %i during initialization' % (zcashd_binary(), process.returncode))
raise Exception('%s node %d exited with status %i during initialization' % (zcashd_binary(), i, process.returncode))
try:
rpc = get_rpc_proxy(url, i)
rpc.getblockcount()
@ -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
@ -433,7 +433,7 @@ def assert_start_raises_init_error(i, dirname, extra_args=None, expected_msg=Non
node = start_node(i, dirname, extra_args, stderr=log_stderr)
stop_node(node, i)
except Exception as e:
assert ("%s exited" % (zcashd_binary(),)) in str(e) #node must have shutdown
assert ("%s node %d exited" % (zcashd_binary(), i)) in str(e) # node must have shutdown
if expected_msg is not None:
log_stderr.seek(0)
stderr = log_stderr.read().decode('utf-8')
@ -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()

View File

@ -126,7 +126,7 @@ class WalletAccountsTest(BitcoinTestFramework):
# The UA contains the expected receiver kinds.
self.check_receiver_types(ua0, ['p2pkh', 'sapling', 'orchard'])
self.check_receiver_types(ua0_2, ['p2pkh', 'sapling', 'orchard'])
self.check_receiver_types(ua0_3, [ 'sapling', 'orchard'])
self.check_receiver_types(ua0_3, [ 'sapling', 'orchard'])
self.check_receiver_types(ua0_4, ['p2pkh', 'orchard'])
self.check_receiver_types(ua1, ['p2pkh', 'sapling', 'orchard'])

View File

@ -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`, its selecting
# transparent coinbase, which also means we cant 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()

View File

@ -51,6 +51,10 @@ criteria = "safe-to-deploy"
version = "0.7.6"
criteria = "safe-to-deploy"
[[exemptions.aho-corasick]]
version = "0.7.19"
criteria = "safe-to-deploy"
[[exemptions.anyhow]]
version = "1.0.56"
criteria = "safe-to-deploy"

View File

@ -137,12 +137,8 @@ def ensure_no_dot_so_in_depends():
return exit_code == 0
def util_test():
python = []
if os.path.isfile('/usr/local/bin/python3'):
python = ['/usr/local/bin/python3']
return subprocess.call(
python + [repofile('src/test/bitcoin-util-test.py')],
[sys.executable, repofile('src/test/bitcoin-util-test.py')],
cwd=repofile('src'),
env={'PYTHONPATH': repofile('src/test'), 'srcdir': repofile('src')}
) == 0

View File

@ -4,38 +4,59 @@
# bdb 18.1.40 2020-09-01
#
native_cxxbridge 1.0.80 2022-12-10
native_cxxbridge 1.0.81 2022-12-10
native_cxxbridge 1.0.82 2022-12-10
native_cxxbridge 1.0.83 2022-12-10
native_rust 1.65.0 2022-12-10
rustcxx 1.0.80 2022-12-10
rustcxx 1.0.81 2022-12-10
rustcxx 1.0.82 2022-12-10
rustcxx 1.0.83 2022-12-10
utfcpp 3.2.2 2022-12-10
# Ccache 4.0 requires adding CMake to the depends system.
native_ccache 4.0 2022-11-01
native_ccache 4.1 2022-11-01
native_ccache 4.2 2022-11-01
native_ccache 4.2.1 2022-11-01
native_ccache 4.3 2022-11-01
native_ccache 4.4 2022-11-01
native_ccache 4.4.1 2022-11-01
native_ccache 4.4.2 2022-11-01
native_ccache 4.5 2022-11-01
native_ccache 4.5.1 2022-11-01
native_ccache 4.6 2022-11-01
native_ccache 4.6.1 2022-11-01
native_ccache 4.6.2 2022-11-01
native_ccache 4.6.3 2022-11-01
native_ccache 4.7 2022-11-01
native_ccache 4.0 2022-12-10
native_ccache 4.1 2022-12-10
native_ccache 4.2 2022-12-10
native_ccache 4.2.1 2022-12-10
native_ccache 4.3 2022-12-10
native_ccache 4.4 2022-12-10
native_ccache 4.4.1 2022-12-10
native_ccache 4.4.2 2022-12-10
native_ccache 4.5 2022-12-10
native_ccache 4.5.1 2022-12-10
native_ccache 4.6 2022-12-10
native_ccache 4.6.1 2022-12-10
native_ccache 4.6.2 2022-12-10
native_ccache 4.6.3 2022-12-10
native_ccache 4.7 2022-12-10
native_ccache 4.7.1 2022-12-10
native_ccache 4.7.2 2022-12-10
native_ccache 4.7.3 2022-12-10
native_ccache 4.7.4 2022-12-10
# Clang and Rust are currently pinned to LLVM 14
libcxx 15.0.0 2022-11-01
libcxx 15.0.1 2022-11-01
libcxx 15.0.2 2022-11-01
libcxx 15.0.3 2022-11-01
native_clang 15.0.0 2022-11-01
native_clang 15.0.1 2022-11-01
native_clang 15.0.2 2022-11-01
native_clang 15.0.3 2022-11-01
libcxx 15.0.0 2022-12-10
libcxx 15.0.1 2022-12-10
libcxx 15.0.2 2022-12-10
libcxx 15.0.3 2022-12-10
libcxx 15.0.4 2022-12-10
libcxx 15.0.5 2022-12-10
libcxx 15.0.6 2022-12-10
native_clang 15.0.0 2022-12-10
native_clang 15.0.1 2022-12-10
native_clang 15.0.2 2022-12-10
native_clang 15.0.3 2022-12-10
native_clang 15.0.4 2022-12-10
native_clang 15.0.5 2022-12-10
native_clang 15.0.6 2022-12-10
# We're never updating to this version
bdb 18.1.40 2024-02-01
# Google Test 1.10.0 requires adding CMake to the depends system.
googletest 1.10.0 2022-11-01
googletest 1.11.0 2022-11-01
googletest 1.12.0 2022-11-01
googletest 1.12.1 2022-11-01
googletest 1.10.0 2022-12-10
googletest 1.11.0 2022-12-10
googletest 1.12.0 2022-12-10
googletest 1.12.1 2022-12-10

View File

@ -6,6 +6,11 @@
#include "chain.h"
#include "main.h"
#include "txdb.h"
#include <rust/metrics.h>
/**
* CChain implementation
*/
@ -58,6 +63,50 @@ const CBlockIndex *CChain::FindFork(const CBlockIndex *pindex) const {
return pindex;
}
void CBlockIndex::TrimSolution()
{
AssertLockHeld(cs_main);
// We can correctly trim a solution as soon as the block index entry has been added
// to leveldb. Updates to the block index entry (to update validity status) will be
// handled by re-reading the solution from the existing db entry. It does not help to
// try to avoid these reads by gating trimming on the validity status: the re-reads are
// efficient anyway because of caching in leveldb, and most of them are unavoidable.
if (HasSolution()) {
MetricsIncrementCounter("zcashd.debug.memory.trimmed_equihash_solutions");
std::vector<unsigned char> empty;
nSolution.swap(empty);
}
}
CBlockHeader CBlockIndex::GetBlockHeader() const
{
AssertLockHeld(cs_main);
CBlockHeader header;
header.nVersion = nVersion;
if (pprev) {
header.hashPrevBlock = pprev->GetBlockHash();
}
header.hashMerkleRoot = hashMerkleRoot;
header.hashBlockCommitments = hashBlockCommitments;
header.nTime = nTime;
header.nBits = nBits;
header.nNonce = nNonce;
if (HasSolution()) {
header.nSolution = nSolution;
} else {
MetricsIncrementCounter("zcashd.debug.blocktree.trimmed_equihash_read_dbindex");
CDiskBlockIndex dbindex;
if (!pblocktree->ReadDiskBlockIndex(GetBlockHash(), dbindex)) {
LogPrintf("%s: Failed to read index entry", __func__);
throw std::runtime_error("Failed to read index entry");
}
header.nSolution = dbindex.GetSolution();
}
return header;
}
/** Turn the lowest '1' bit in the binary representation of a number into a '0'. */
int static inline InvertLowestOne(int n) { return n & (n - 1); }

View File

@ -12,10 +12,13 @@
#include "pow.h"
#include "tinyformat.h"
#include "uint256.h"
#include "util/strencodings.h"
#include <optional>
#include <vector>
#include <rust/metrics.h>
static const int SPROUT_VALUE_VERSION = 1001400;
static const int SAPLING_VALUE_VERSION = 1010100;
static const int CHAIN_HISTORY_ROOT_VERSION = 2010200;
@ -308,8 +311,13 @@ public:
unsigned int nTime;
unsigned int nBits;
uint256 nNonce;
protected:
// The Equihash solution, if it is stored. Once we know that the block index
// entry is present in leveldb, this field can be cleared via the TrimSolution
// method to save memory.
std::vector<unsigned char> nSolution;
public:
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
uint32_t nSequenceId;
@ -366,6 +374,7 @@ public:
nBits = block.nBits;
nNonce = block.nNonce;
nSolution = block.nSolution;
MetricsIncrementCounter("zcashd.debug.memory.allocated_equihash_solutions");
}
CDiskBlockPos GetBlockPos() const {
@ -386,23 +395,15 @@ public:
return ret;
}
CBlockHeader GetBlockHeader() const
{
CBlockHeader block;
block.nVersion = nVersion;
if (pprev)
block.hashPrevBlock = pprev->GetBlockHash();
block.hashMerkleRoot = hashMerkleRoot;
block.hashBlockCommitments = hashBlockCommitments;
block.nTime = nTime;
block.nBits = nBits;
block.nNonce = nNonce;
block.nSolution = nSolution;
return block;
}
//! Get the block header for this block index. Requires cs_main.
CBlockHeader GetBlockHeader() const;
//! Clear the Equihash solution to save memory. Requires cs_main.
void TrimSolution();
uint256 GetBlockHash() const
{
assert(phashBlock);
return *phashBlock;
}
@ -429,10 +430,11 @@ public:
std::string ToString() const
{
return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)",
return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s, HasSolution=%s)",
pprev, nHeight,
hashMerkleRoot.ToString(),
GetBlockHash().ToString());
phashBlock ? GetBlockHash().ToString() : "(nil)",
HasSolution());
}
//! Check whether this block index entry is valid up to the passed validity level.
@ -444,6 +446,12 @@ public:
return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
}
//! Is the Equihash solution stored?
bool HasSolution() const
{
return !nSolution.empty();
}
//! Raise the validity level of this block index entry.
//! Returns true if the validity was changed.
bool RaiseValidity(enum BlockStatus nUpTo)
@ -482,8 +490,11 @@ public:
hashPrev = uint256();
}
explicit CDiskBlockIndex(const CBlockIndex* pindex) : CBlockIndex(*pindex) {
explicit CDiskBlockIndex(const CBlockIndex* pindex, std::function<std::vector<unsigned char>()> getSolution) : CBlockIndex(*pindex) {
hashPrev = (pprev ? pprev->GetBlockHash() : uint256());
if (!HasSolution()) {
nSolution = getSolution();
}
}
ADD_SERIALIZE_METHODS;
@ -564,20 +575,31 @@ public:
// them to CBlockTreeDB::LoadBlockIndexGuts() in txdb.cpp :)
}
uint256 GetBlockHash() const
//! Get the block header for this block index.
CBlockHeader GetBlockHeader() const
{
CBlockHeader block;
block.nVersion = nVersion;
block.hashPrevBlock = hashPrev;
block.hashMerkleRoot = hashMerkleRoot;
block.hashBlockCommitments = hashBlockCommitments;
block.nTime = nTime;
block.nBits = nBits;
block.nNonce = nNonce;
block.nSolution = nSolution;
return block.GetHash();
CBlockHeader header;
header.nVersion = nVersion;
header.hashPrevBlock = hashPrev;
header.hashMerkleRoot = hashMerkleRoot;
header.hashBlockCommitments = hashBlockCommitments;
header.nTime = nTime;
header.nBits = nBits;
header.nNonce = nNonce;
header.nSolution = nSolution;
return header;
}
uint256 GetBlockHash() const
{
return GetBlockHeader().GetHash();
}
std::vector<unsigned char> GetSolution() const
{
assert(HasSolution());
return nSolution;
}
std::string ToString() const
{
@ -588,6 +610,13 @@ public:
hashPrev.ToString());
return str;
}
private:
//! This method should not be called on a CDiskBlockIndex.
void TrimSolution()
{
assert(!"called CDiskBlockIndex::TrimSolution");
}
};
/** An in-memory indexed chain of blocks. */

View File

@ -17,7 +17,7 @@
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 3
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_REVISION 2
#define CLIENT_VERSION_BUILD 50
//! Set to true for release, false for prerelease or test build

View File

@ -281,7 +281,7 @@ private:
*
* First, epoch_check decrements and checks the cheap heuristic, and then does
* a more expensive scan if the cheap heuristic runs out. If the expensive
* scan suceeds, the epochs are aged and old elements are allow_erased. The
* scan succeeds, the epochs are aged and old elements are allow_erased. The
* cheap heuristic is reset to retrigger after the worst case growth of the
* current epoch's elements would exceed the epoch_size.
*/

View File

@ -10,7 +10,7 @@
// Per https://zips.z.cash/zip-0200
// Shut down nodes running this version of code, 16 weeks' worth of blocks after the estimated
// release block height. A warning is shown during the 14 days' worth of blocks prior to shut down.
static const int APPROX_RELEASE_HEIGHT = 1849900;
static const int APPROX_RELEASE_HEIGHT = 1900500;
static const int RELEASE_TO_DEPRECATION_WEEKS = 16;
static const int EXPECTED_BLOCKS_PER_HOUR = 3600 / Consensus::POST_BLOSSOM_POW_TARGET_SPACING;
static_assert(EXPECTED_BLOCKS_PER_HOUR == 48, "The value of Consensus::POST_BLOSSOM_POW_TARGET_SPACING was chosen such that this assertion holds.");

View File

@ -438,6 +438,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-prometheusport=<port>", _("Expose node metrics in the Prometheus exposition format. "
"An HTTP listener will be started on <port>, which responds to GET requests on any request path. "
"Use -metricsallowip and -metricsbind to control access."));
strUsage += HelpMessageOpt("-debugmetrics", _("Include debug metrics in exposed node metrics."));
strUsage += HelpMessageGroup(_("Debugging/Testing options:"));
if (showDebug)
@ -1471,10 +1472,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
metricsBindCstr = metricsBind.c_str();
}
bool debugMetrics = GetBoolArg("-debugmetrics", false);
// Start up the metrics runtime. This spins off a Rust thread that runs
// the Prometheus exporter. We just let this thread die at process end.
LogPrintf("metrics thread start");
if (!metrics_run(metricsBindCstr, vAllowCstr.data(), vAllowCstr.size(), prometheusPort)) {
if (!metrics_run(metricsBindCstr, vAllowCstr.data(), vAllowCstr.size(), prometheusPort, debugMetrics)) {
return InitError(strprintf(_("Failed to start Prometheus metrics exporter")));
}
}

View File

@ -3715,7 +3715,7 @@ bool static FlushStateToDisk(
vFiles.push_back(make_pair(*it, &vinfoBlockFile[*it]));
it = setDirtyFileInfo.erase(it);
}
std::vector<const CBlockIndex*> vBlocks;
std::vector<CBlockIndex*> vBlocks;
vBlocks.reserve(setDirtyBlockIndex.size());
for (set<CBlockIndex*>::iterator it = setDirtyBlockIndex.begin(); it != setDirtyBlockIndex.end(); ) {
vBlocks.push_back(*it);
@ -3724,6 +3724,12 @@ bool static FlushStateToDisk(
if (!pblocktree->WriteBatchSync(vFiles, nLastBlockFile, vBlocks)) {
return AbortNode(state, "Files to write to block index database");
}
// Now that we have written the block indices to the database, we do not
// need to store solutions for these CBlockIndex objects in memory.
// cs_main must be held here.
for (CBlockIndex *pblockindex : vBlocks) {
pblockindex->TrimSolution();
}
}
// Finally remove any pruned files
if (fFlushForPrune)
@ -6128,7 +6134,11 @@ void static CheckBlockIndex(const Consensus::Params& consensusParams)
}
}
}
// assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash()); // Perhaps too slow
// try {
// assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash()); // Perhaps too slow
// } catch (const runtime_error&) {
// assert(!"Failed to read index entry");
// }
// End: actual consistency checks.
// Try descending into the first subnode.

View File

@ -141,6 +141,7 @@ static bool rest_headers(HTTPRequest* req,
std::vector<const CBlockIndex *> headers;
headers.reserve(count);
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
{
LOCK(cs_main);
BlockMap::const_iterator it = mapBlockIndex.find(hash);
@ -151,11 +152,16 @@ static bool rest_headers(HTTPRequest* req,
break;
pindex = chainActive.Next(pindex);
}
}
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
if (rf == RF_BINARY || rf == RF_HEX) {
try {
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
}
} catch (const std::runtime_error&) {
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Failed to read index entry");
}
}
}
switch (rf) {

View File

@ -118,7 +118,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex)
result.pushKV("finalsaplingroot", blockindex->hashFinalSaplingRoot.GetHex());
result.pushKV("time", (int64_t)blockindex->nTime);
result.pushKV("nonce", blockindex->nNonce.GetHex());
result.pushKV("solution", HexStr(blockindex->nSolution));
result.pushKV("solution", HexStr(blockindex->GetBlockHeader().nSolution));
result.pushKV("bits", strprintf("%08x", blockindex->nBits));
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
@ -684,15 +684,18 @@ UniValue getblockheader(const UniValue& params, bool fHelp)
CBlockIndex* pblockindex = mapBlockIndex[hash];
if (!fVerbose)
{
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << pblockindex->GetBlockHeader();
std::string strHex = HexStr(ssBlock.begin(), ssBlock.end());
return strHex;
try {
if (!fVerbose) {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << pblockindex->GetBlockHeader();
std::string strHex = HexStr(ssBlock.begin(), ssBlock.end());
return strHex;
} else {
return blockheaderToJSON(pblockindex);
}
} catch (const runtime_error&) {
throw JSONRPCError(RPC_DATABASE_ERROR, "Failed to read index entry");
}
return blockheaderToJSON(pblockindex);
}
UniValue getblock(const UniValue& params, bool fHelp)

View File

@ -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());

View File

@ -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);

View File

@ -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);

View File

@ -24,7 +24,8 @@ bool metrics_run(
const char* bind_address,
const char* const* allow_ips,
size_t allow_ips_len,
uint16_t prometheus_port);
uint16_t prometheus_port,
bool debug_metrics);
struct MetricsCallsite;
typedef struct MetricsCallsite MetricsCallsite;

View File

@ -1,18 +1,48 @@
use libc::{c_char, c_double};
use metrics::{try_recorder, Key, Label};
use metrics_exporter_prometheus::PrometheusBuilder;
use metrics_exporter_prometheus::{BuildError, PrometheusBuilder};
use metrics_util::layers::{FilterLayer, Stack};
use std::ffi::CStr;
use std::net::{IpAddr, SocketAddr};
use std::ptr;
use std::slice;
use std::thread;
use tracing::error;
/// Builds the recorder and exporter, applies the given filters to the recorder, and
/// installs both of them globally.
fn metrics_install(builder: PrometheusBuilder, filters: &[&str]) -> Result<(), BuildError> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| BuildError::FailedToCreateRuntime(e.to_string()))?;
let (recorder, exporter) = {
let _g = runtime.enter();
builder.build()?
};
thread::Builder::new()
.name("zc-metrics".to_string())
.spawn(move || runtime.block_on(exporter))
.map_err(|e| BuildError::FailedToCreateRuntime(e.to_string()))?;
Stack::new(recorder)
.push(FilterLayer::from_patterns(filters))
.install()?;
Ok(())
}
#[no_mangle]
pub extern "C" fn metrics_run(
bind_address: *const c_char,
allow_ips: *const *const c_char,
allow_ips_len: usize,
prometheus_port: u16,
debug_metrics: bool,
) -> bool {
// Convert the C string IPs to Rust strings.
let allow_ips = unsafe { slice::from_raw_parts(allow_ips, allow_ips_len) };
@ -54,6 +84,13 @@ pub extern "C" fn metrics_run(
prometheus_port,
);
// Metrics matching any of these filters will be discarded.
let filters = if debug_metrics {
&[][..]
} else {
&["zcashd.debug."]
};
allow_ips
.into_iter()
.try_fold(
@ -66,7 +103,7 @@ pub extern "C" fn metrics_run(
},
)
.and_then(|builder| {
builder.install().map_err(|e| {
metrics_install(builder, filters).map_err(|e| {
error!("Could not install Prometheus exporter: {}", e);
e
})

View File

@ -235,7 +235,7 @@ BOOST_AUTO_TEST_CASE(merkle_test)
// If no mutation was done (once for every ntx value), try up to 16 branches.
if (mutate == 0) {
for (int loop = 0; loop < std::min(ntx, 16); loop++) {
// If ntx <= 16, try all branches. Otherise, try 16 random ones.
// If ntx <= 16, try all branches. Otherwise, try 16 random ones.
int mtx = loop;
if (ntx > 16) {
mtx = InsecureRandRange(ntx);

View File

@ -17,6 +17,8 @@
#include <boost/thread.hpp>
#include <rust/metrics.h>
using namespace std;
// NOTE: Per issue #3277, do not use the prefix 'X' or 'x' as they were
@ -316,7 +318,7 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins,
CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) {
}
bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) {
bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) const {
return Read(make_pair(DB_BLOCK_FILES, nFile), info);
}
@ -327,12 +329,12 @@ bool CBlockTreeDB::WriteReindexing(bool fReindexing) {
return Erase(DB_REINDEX_FLAG);
}
bool CBlockTreeDB::ReadReindexing(bool &fReindexing) {
bool CBlockTreeDB::ReadReindexing(bool &fReindexing) const {
fReindexing = Exists(DB_REINDEX_FLAG);
return true;
}
bool CBlockTreeDB::ReadLastBlockFile(int &nFile) {
bool CBlockTreeDB::ReadLastBlockFile(int &nFile) const {
return Read(DB_LAST_BLOCK, nFile);
}
@ -382,14 +384,31 @@ bool CCoinsViewDB::GetStats(CCoinsStats &stats) const {
return true;
}
bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) {
bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<CBlockIndex*>& blockinfo) {
MetricsIncrementCounter("zcashd.debug.blocktree.write_batch");
CDBBatch batch(*this);
for (std::vector<std::pair<int, const CBlockFileInfo*> >::const_iterator it=fileInfo.begin(); it != fileInfo.end(); it++) {
batch.Write(make_pair(DB_BLOCK_FILES, it->first), *it->second);
}
batch.Write(DB_LAST_BLOCK, nLastFile);
for (std::vector<const CBlockIndex*>::const_iterator it=blockinfo.begin(); it != blockinfo.end(); it++) {
batch.Write(make_pair(DB_BLOCK_INDEX, (*it)->GetBlockHash()), CDiskBlockIndex(*it));
std::pair<char, uint256> key = make_pair(DB_BLOCK_INDEX, (*it)->GetBlockHash());
try {
CDiskBlockIndex dbindex {*it, [this, &key]() {
MetricsIncrementCounter("zcashd.debug.blocktree.write_batch_read_dbindex");
// It can happen that the index entry is written, then the Equihash solution is cleared from memory,
// then the index entry is rewritten. In that case we must read the solution from the old entry.
CDiskBlockIndex dbindex_old;
if (!Read(key, dbindex_old)) {
LogPrintf("%s: Failed to read index entry", __func__);
throw runtime_error("Failed to read index entry");
}
return dbindex_old.GetSolution();
}};
batch.Write(key, dbindex);
} catch (const runtime_error&) {
return false;
}
}
return WriteBatch(batch, true);
}
@ -402,7 +421,11 @@ bool CBlockTreeDB::EraseBatchSync(const std::vector<const CBlockIndex*>& blockin
return WriteBatch(batch, true);
}
bool CBlockTreeDB::ReadTxIndex(const uint256 &txid, CDiskTxPos &pos) {
bool CBlockTreeDB::ReadDiskBlockIndex(const uint256 &blockhash, CDiskBlockIndex &dbindex) const {
return Read(make_pair(DB_BLOCK_INDEX, blockhash), dbindex);
}
bool CBlockTreeDB::ReadTxIndex(const uint256 &txid, CDiskTxPos &pos) const {
return Read(make_pair(DB_TXINDEX, txid), pos);
}
@ -491,7 +514,7 @@ bool CBlockTreeDB::ReadAddressIndex(
return true;
}
bool CBlockTreeDB::ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) {
bool CBlockTreeDB::ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) const {
return Read(make_pair(DB_SPENTINDEX, key), value);
}
@ -547,7 +570,7 @@ bool CBlockTreeDB::WriteTimestampBlockIndex(const CTimestampBlockIndexKey &block
return WriteBatch(batch);
}
bool CBlockTreeDB::ReadTimestampBlockIndex(const uint256 &hash, unsigned int &ltimestamp)
bool CBlockTreeDB::ReadTimestampBlockIndex(const uint256 &hash, unsigned int &ltimestamp) const
{
CTimestampBlockIndexValue(lts);
if (!Read(std::make_pair(DB_BLOCKHASHINDEX, hash), lts))
@ -562,7 +585,7 @@ bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) {
return Write(std::make_pair(DB_FLAG, name), fValue ? '1' : '0');
}
bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) {
bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) const {
char ch;
if (!Read(std::make_pair(DB_FLAG, name), ch))
return false;
@ -599,7 +622,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(
pindexNew->nTime = diskindex.nTime;
pindexNew->nBits = diskindex.nBits;
pindexNew->nNonce = diskindex.nNonce;
pindexNew->nSolution = diskindex.nSolution;
// the Equihash solution will be loaded lazily from the dbindex entry
pindexNew->nStatus = diskindex.nStatus;
pindexNew->nCachedBranchId = diskindex.nCachedBranchId;
pindexNew->nTx = diskindex.nTx;
@ -612,10 +635,22 @@ bool CBlockTreeDB::LoadBlockIndexGuts(
pindexNew->hashAuthDataRoot = diskindex.hashAuthDataRoot;
// Consistency checks
auto header = pindexNew->GetBlockHeader();
CBlockHeader header;
{
LOCK(cs_main);
try {
header = pindexNew->GetBlockHeader();
} catch (const runtime_error&) {
return error("LoadBlockIndex(): failed to read index entry: diskindex hash = %s",
diskindex.GetBlockHash().ToString());
}
}
if (header.GetHash() != diskindex.GetBlockHash())
return error("LoadBlockIndex(): inconsistent header vs diskindex hash: header hash = %s, diskindex hash = %s",
header.GetHash().ToString(), diskindex.GetBlockHash().ToString());
if (header.GetHash() != pindexNew->GetBlockHash())
return error("LoadBlockIndex(): block header inconsistency detected: on-disk = %s, in-memory = %s",
diskindex.ToString(), pindexNew->ToString());
diskindex.ToString(), pindexNew->ToString());
if (!CheckProofOfWork(pindexNew->GetBlockHash(), pindexNew->nBits, Params().GetConsensus()))
return error("LoadBlockIndex(): CheckProofOfWork failed: %s", pindexNew->ToString());

View File

@ -117,13 +117,14 @@ private:
CBlockTreeDB(const CBlockTreeDB&);
void operator=(const CBlockTreeDB&);
public:
bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo);
bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<CBlockIndex*>& blockinfo);
bool EraseBatchSync(const std::vector<const CBlockIndex*>& blockinfo);
bool ReadBlockFileInfo(int nFile, CBlockFileInfo &info);
bool ReadLastBlockFile(int &nFile);
bool ReadBlockFileInfo(int nFile, CBlockFileInfo &info) const;
bool ReadLastBlockFile(int &nFile) const;
bool WriteReindexing(bool fReindexing);
bool ReadReindexing(bool &fReindexing);
bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos);
bool ReadReindexing(bool &fReindexing) const;
bool ReadDiskBlockIndex(const uint256 &blockhash, CDiskBlockIndex &dbindex) const;
bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos) const;
bool WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos> > &vect);
// START insightexplorer
@ -132,18 +133,18 @@ public:
bool WriteAddressIndex(const std::vector<CAddressIndexDbEntry> &vect);
bool EraseAddressIndex(const std::vector<CAddressIndexDbEntry> &vect);
bool ReadAddressIndex(uint160 addressHash, int type, std::vector<CAddressIndexDbEntry> &addressIndex, int start = 0, int end = 0);
bool ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value);
bool ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) const;
bool UpdateSpentIndex(const std::vector<CSpentIndexDbEntry> &vect);
bool WriteTimestampIndex(const CTimestampIndexKey &timestampIndex);
bool ReadTimestampIndex(unsigned int high, unsigned int low,
const bool fActiveOnly, std::vector<std::pair<uint256, unsigned int> > &vect);
bool WriteTimestampBlockIndex(const CTimestampBlockIndexKey &blockhashIndex,
const CTimestampBlockIndexValue &logicalts);
bool ReadTimestampBlockIndex(const uint256 &hash, unsigned int &logicalTS);
bool ReadTimestampBlockIndex(const uint256 &hash, unsigned int &logicalTS) const;
// END insightexplorer
bool WriteFlag(const std::string &name, bool fValue);
bool ReadFlag(const std::string &name, bool &fValue);
bool ReadFlag(const std::string &name, bool &fValue) const;
bool LoadBlockIndexGuts(
std::function<CBlockIndex*(const uint256&)> insertBlockIndex,
const CChainParams& chainParams);

View File

@ -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(),

View File

@ -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) {

View File

@ -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};

View File

@ -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));

View File

@ -46,7 +46,7 @@
#include <boost/assign/list_of.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <utf8.h>
#include <utf8cpp/utf8.h>
#include <univalue.h>
@ -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 &params, 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));
@ -5004,11 +5064,11 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
if (fHelp || params.size() < 2 || params.size() > 5)
throw runtime_error(
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) ( privacyPolicy )\n"
"\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision."
"\nChange generated from one or more transparent addresses flows to a new transparent"
"\naddress, while change generated from a legacy Sapling address returns to itself."
"\nWhen sending from a unified address, change is returned to the internal-only address"
"\nfor the associated unified account."
"\nSend a transaction with multiple recipients. Amounts are decimal numbers with at"
"\nmost 8 digits of precision. Change generated from one or more transparent"
"\naddresses flows to a new transparent address, while change generated from a"
"\nlegacy Sapling address returns to itself. When sending from a unified address,"
"\nchange is returned to the internal-only address for the associated unified account."
"\nWhen spending coinbase UTXOs, only shielded recipients are permitted and change is not allowed;"
"\nthe entire value of the coinbase UTXO(s) must be consumed."
+ HelpRequiringPassphrase() + "\n"
@ -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();

View File

@ -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)
// dont 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);

View File

@ -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
* its not in the chain (including if its 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,

View File

@ -13,14 +13,14 @@
# one. Any remaining diff signals an error.
export LC_ALL=C
if test "x$1" = "x"; then
if test -z "$1"; then
echo "Usage: $0 <commit>..."
exit 1
fi
# Check that the working tree is clean (as this script performs hard resets).
TREE_CHANGES=$(git diff HEAD | wc -l)
if test "x$TREE_CHANGES" != "x0"; then
if test "$TREE_CHANGES" != "0"; then
echo "The working tree is not clean."
exit 1
fi
@ -32,7 +32,7 @@ for commit in $(git rev-list --reverse $1); do
if git rev-list -n 1 --pretty="%s" $commit | grep -q "^scripted-diff:"; then
git checkout --quiet $commit^ || exit
SCRIPT="$(git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d')"
if test "x$SCRIPT" = "x"; then
if test -z "$SCRIPT"; then
echo "Error: missing script for: $commit"
echo "Failed"
RET=1

View File

@ -40,7 +40,7 @@ if ! command -v shellcheck > /dev/null; then
fi
EXCLUDE="--exclude=$(IFS=','; echo "${disabled[*]}")"
if ! shellcheck "$EXCLUDE" $(git ls-files -- '*.sh' | grep -vE 'src/(leveldb|secp256k1|univalue)/'); then
if ! shellcheck "$EXCLUDE" $(git ls-files -- '*.sh' | grep -vE '(qa/zcash/checksec\.sh|src/(|leveldb|secp256k1|univalue)/)'); then
EXIT_CODE=1
fi

View File

@ -40,7 +40,7 @@ if [ -z "${CONFIGURE_FLAGS-}" ]; then
CONFIGURE_FLAGS=""
fi
if [ "x$*" = 'x--help' ]
if [ "$*" = '--help' ]
then
cat <<EOF
Usage: