aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--depends/funcs.mk13
-rw-r--r--depends/packages/libmultiprocess.mk2
-rw-r--r--depends/packages/native_libmultiprocess.mk6
-rw-r--r--depends/packages/qt.mk4
-rw-r--r--doc/release-notes-26213.md8
-rw-r--r--doc/release-notes-26628.md4
-rw-r--r--doc/release-notes/release-notes-22.1.md128
-rw-r--r--doc/release-notes/release-notes-23.1.md90
-rw-r--r--src/httpserver.cpp14
-rw-r--r--src/net_processing.cpp85
-rw-r--r--src/primitives/transaction.h7
-rw-r--r--src/qt/transactionrecord.h4
-rw-r--r--src/qt/transactionview.cpp3
-rw-r--r--src/rpc/client.cpp9
-rw-r--r--src/rpc/net.cpp6
-rw-r--r--src/rpc/rawtransaction.cpp116
-rw-r--r--src/rpc/server.cpp5
-rw-r--r--src/test/rpc_tests.cpp8
-rw-r--r--src/test/system_tests.cpp4
-rw-r--r--src/univalue/include/univalue.h1
-rw-r--r--src/univalue/lib/univalue_get.cpp2
-rw-r--r--src/univalue/test/object.cpp4
-rw-r--r--src/validation.cpp20
-rw-r--r--src/validation.h3
-rw-r--r--src/wallet/rpc/spend.cpp5
-rw-r--r--src/wallet/spend.cpp46
-rw-r--r--src/wallet/spend.h2
-rwxr-xr-xtest/functional/feature_assumevalid.py18
-rwxr-xr-xtest/functional/feature_maxtipage.py23
-rwxr-xr-xtest/functional/feature_txindex_compatibility.py6
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py4
-rwxr-xr-xtest/functional/mempool_compatibility.py5
-rwxr-xr-xtest/functional/rpc_net.py3
-rwxr-xr-xtest/functional/rpc_rawtransaction.py81
-rwxr-xr-xtest/functional/test_framework/test_node.py2
-rw-r--r--test/functional/test_framework/test_shell.py3
-rwxr-xr-xtest/functional/test_runner.py8
-rwxr-xr-xtest/functional/wallet_backwards_compatibility.py (renamed from test/functional/feature_backwards_compatibility.py)5
-rwxr-xr-xtest/functional/wallet_fundrawtransaction.py (renamed from test/functional/rpc_fundrawtransaction.py)0
39 files changed, 565 insertions, 192 deletions
diff --git a/depends/funcs.mk b/depends/funcs.mk
index a00f380236..2b21d053b1 100644
--- a/depends/funcs.mk
+++ b/depends/funcs.mk
@@ -87,9 +87,9 @@ $(1)_download_path_fixed=$(subst :,\:,$$($(1)_download_path))
$(1)_fetch_cmds ?= $(call fetch_file,$(1),$(subst \:,:,$$($(1)_download_path_fixed)),$$($(1)_download_file),$($(1)_file_name),$($(1)_sha256_hash))
$(1)_extract_cmds ?= mkdir -p $$($(1)_extract_dir) && echo "$$($(1)_sha256_hash) $$($(1)_source)" > $$($(1)_extract_dir)/.$$($(1)_file_name).hash && $(build_SHA256SUM) -c $$($(1)_extract_dir)/.$$($(1)_file_name).hash && $(build_TAR) --no-same-owner --strip-components=1 -xf $$($(1)_source)
$(1)_preprocess_cmds ?= true
-$(1)_build_cmds ?=
-$(1)_config_cmds ?=
-$(1)_stage_cmds ?=
+$(1)_build_cmds ?= true
+$(1)_config_cmds ?= true
+$(1)_stage_cmds ?= true
$(1)_set_vars ?=
@@ -137,6 +137,7 @@ $(1)_config_env+=$($(1)_config_env_$(host_arch)_$(host_os)) $($(1)_config_env_$(
$(1)_config_env+=PKG_CONFIG_LIBDIR=$($($(1)_type)_prefix)/lib/pkgconfig
$(1)_config_env+=PKG_CONFIG_PATH=$($($(1)_type)_prefix)/share/pkgconfig
+$(1)_config_env+=PKG_CONFIG_SYSROOT_DIR=/
$(1)_config_env+=CMAKE_MODULE_PATH=$($($(1)_type)_prefix)/lib/cmake
$(1)_config_env+=PATH=$(build_prefix)/bin:$(PATH)
$(1)_build_env+=PATH=$(build_prefix)/bin:$(PATH)
@@ -214,17 +215,17 @@ $($(1)_configured): | $($(1)_dependencies) $($(1)_preprocessed)
echo Configuring $(1)...
rm -rf $(host_prefix); mkdir -p $(host_prefix)/lib; cd $(host_prefix); $(foreach package,$($(1)_all_dependencies), $(build_TAR) --no-same-owner -xf $($(package)_cached); )
mkdir -p $$(@D)
- +{ cd $$(@D); $($(1)_config_env) $($(1)_config_cmds); } $$($(1)_logging)
+ +{ cd $$(@D); export $($(1)_config_env); $($(1)_config_cmds); } $$($(1)_logging)
touch $$@
$($(1)_built): | $($(1)_configured)
echo Building $(1)...
mkdir -p $$(@D)
- +{ cd $$(@D); $($(1)_build_env) $($(1)_build_cmds); } $$($(1)_logging)
+ +{ cd $$(@D); export $($(1)_build_env); $($(1)_build_cmds); } $$($(1)_logging)
touch $$@
$($(1)_staged): | $($(1)_built)
echo Staging $(1)...
mkdir -p $($(1)_staging_dir)/$(host_prefix)
- +{ cd $($(1)_build_dir); $($(1)_stage_env) $($(1)_stage_cmds); } $$($(1)_logging)
+ +{ cd $($(1)_build_dir); export $($(1)_stage_env); $($(1)_stage_cmds); } $$($(1)_logging)
rm -rf $($(1)_extract_dir)
touch $$@
$($(1)_postprocessed): | $($(1)_staged)
diff --git a/depends/packages/libmultiprocess.mk b/depends/packages/libmultiprocess.mk
index 9b66207fc5..6da5693b3f 100644
--- a/depends/packages/libmultiprocess.mk
+++ b/depends/packages/libmultiprocess.mk
@@ -24,5 +24,5 @@ define $(package)_build_cmds
endef
define $(package)_stage_cmds
- $(MAKE) DESTDIR=$($(package)_staging_dir) install
+ $(MAKE) DESTDIR=$($(package)_staging_dir) install-lib
endef
diff --git a/depends/packages/native_libmultiprocess.mk b/depends/packages/native_libmultiprocess.mk
index 6e600c5720..e647afba5f 100644
--- a/depends/packages/native_libmultiprocess.mk
+++ b/depends/packages/native_libmultiprocess.mk
@@ -1,8 +1,8 @@
package=native_libmultiprocess
-$(package)_version=d576d975debdc9090bd2582f83f49c76c0061698
+$(package)_version=1af83d15239ccfa7e47b8764029320953dd7fdf1
$(package)_download_path=https://github.com/chaincodelabs/libmultiprocess/archive
$(package)_file_name=$($(package)_version).tar.gz
-$(package)_sha256_hash=9f8b055c8bba755dc32fe799b67c20b91e7b13e67cadafbc54c0f1def057a370
+$(package)_sha256_hash=e5587d3feedc7f8473f178a89b94163a11076629825d664964799bbbd5844da5
$(package)_dependencies=native_capnp
define $(package)_config_cmds
@@ -14,5 +14,5 @@ define $(package)_build_cmds
endef
define $(package)_stage_cmds
- $(MAKE) DESTDIR=$($(package)_staging_dir) install
+ $(MAKE) DESTDIR=$($(package)_staging_dir) install-bin
endef
diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk
index d9ae918d71..2f7ddf6a5f 100644
--- a/depends/packages/qt.mk
+++ b/depends/packages/qt.mk
@@ -33,6 +33,7 @@ $(package)_extra_sources = $($(package)_qttranslations_file_name)
$(package)_extra_sources += $($(package)_qttools_file_name)
define $(package)_set_vars
+$(package)_config_env = QT_MAC_SDK_NO_VERSION_CHECK=1
$(package)_config_opts_release = -release
$(package)_config_opts_release += -silent
$(package)_config_opts_debug = -debug
@@ -261,9 +262,6 @@ define $(package)_preprocess_cmds
endef
define $(package)_config_cmds
- export PKG_CONFIG_SYSROOT_DIR=/ && \
- export PKG_CONFIG_LIBDIR=$(host_prefix)/lib/pkgconfig && \
- export QT_MAC_SDK_NO_VERSION_CHECK=1 && \
cd qtbase && \
./configure -top-level $($(package)_config_opts)
endef
diff --git a/doc/release-notes-26213.md b/doc/release-notes-26213.md
new file mode 100644
index 0000000000..e78d718ca9
--- /dev/null
+++ b/doc/release-notes-26213.md
@@ -0,0 +1,8 @@
+Low-level changes
+=================
+
+- Previously `setban`, `addpeeraddress`, `walletcreatefundedpsbt`, methods
+ allowed non-boolean and non-null values to be passed as boolean parameters.
+ Any string, number, array, or object value that was passed would be treated
+ as false. After this change, passing any value except `true`, `false`, or
+ `null` now triggers a JSON value is not of expected type error. (#26213)
diff --git a/doc/release-notes-26628.md b/doc/release-notes-26628.md
new file mode 100644
index 0000000000..48a07c1e81
--- /dev/null
+++ b/doc/release-notes-26628.md
@@ -0,0 +1,4 @@
+JSON-RPC
+---
+
+The JSON-RPC server now rejects requests where a parameter is specified multiple times with the same name, instead of silently overwriting earlier parameter values with later ones. (#26628)
diff --git a/doc/release-notes/release-notes-22.1.md b/doc/release-notes/release-notes-22.1.md
new file mode 100644
index 0000000000..d304b7e57a
--- /dev/null
+++ b/doc/release-notes/release-notes-22.1.md
@@ -0,0 +1,128 @@
+22.1 Release Notes
+==================
+
+Bitcoin Core version 22.1 is now available from:
+
+ <https://bitcoincore.org/bin/bitcoin-core-22.1/>
+
+This release includes new features, various bug fixes and performance
+improvements, as well as updated translations.
+
+Please report bugs using the issue tracker at GitHub:
+
+ <https://github.com/bitcoin/bitcoin/issues>
+
+To receive security and update notifications, please subscribe to:
+
+ <https://bitcoincore.org/en/list/announcements/join/>
+
+How to Upgrade
+==============
+
+If you are running an older version, shut it down. Wait until it has completely
+shut down (which might take a few minutes in some cases), then run the
+installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on macOS)
+or `bitcoind`/`bitcoin-qt` (on Linux).
+
+Upgrading directly from a version of Bitcoin Core that has reached its EOL is
+possible, but it might take some time if the data directory needs to be migrated. Old
+wallet versions of Bitcoin Core are generally supported.
+
+Compatibility
+==============
+
+Bitcoin Core is supported and extensively tested on operating systems
+using the Linux kernel, macOS 10.14+, and Windows 7 and newer. Bitcoin
+Core should also work on most other Unix-like systems but is not as
+frequently tested on them. It is not recommended to use Bitcoin Core on
+unsupported systems.
+
+From Bitcoin Core 22.0 onwards, macOS versions earlier than 10.14 are no longer supported.
+
+Notable changes
+===============
+
+Updated settings
+----------------
+
+- In previous releases, the meaning of the command line option
+ `-persistmempool` (without a value provided) incorrectly disabled mempool
+ persistence. `-persistmempool` is now treated like other boolean options to
+ mean `-persistmempool=1`. Passing `-persistmempool=0`, `-persistmempool=1`
+ and `-nopersistmempool` is unaffected. (#23061)
+
+### P2P
+
+### RPC and other APIs
+
+- #25237 rpc: Capture UniValue by ref for rpcdoccheck
+- #25983 Prevent data race for pathHandlers
+- #26275 Fix crash on deriveaddresses when index is 2147483647 (2^31-1)
+
+### Wallet
+
+- #22781 wallet: fix the behavior of IsHDEnabled
+- #22949 fee: Round up fee calculation to avoid a lower than expected feerate
+- #23333 wallet: fix segfault by avoiding invalid default-ctored external_spk_managers entry
+
+### Build system
+
+- #22820 build, qt: Fix typo in QtInputSupport check
+- #23045 build: Restrict check for CRC32C intrinsic to aarch64
+- #23148 build: Fix guix linker-loader path and add check_ELF_interpreter
+- #23314 build: explicitly disable libsecp256k1 openssl based tests
+- #23580 build: patch qt to explicitly define previously implicit header include
+- #24215 guix: ignore additional failing certvalidator test
+- #24256 build: Bump depends packages (zmq, libXau)
+- #25201 windeploy: Renewed windows code signing certificate
+- #25985 Revert "build: Use Homebrew's sqlite package if it is available"
+- #26633 depends: update qt 5.12 url to archive location
+
+### GUI
+
+- #gui631 Disallow encryption of watchonly wallets
+- #gui680 Fixes MacOS 13 segfault by preventing certain notifications
+- #24498 qt: Avoid crash on startup if int specified in settings.json
+
+### Tests
+
+- #23716 test: replace hashlib.ripemd160 with an own implementation
+- #24239 test: fix ceildiv division by using integers
+
+### Utilities
+
+- #22390 system: skip trying to set the locale on NetBSD
+- #22895 don't call GetBlockPos in ReadBlockFromDisk without cs_main lock
+- #24104 fs: Make compatible with boost 1.78
+
+### Miscellaneous
+
+- #23335 refactor: include a missing <limits> header in fs.cpp
+- #23504 ci: Replace soon EOL hirsute with jammy
+- #26321 Adjust .tx/config for new Transifex CLI
+
+Credits
+=======
+
+Thanks to everyone who directly contributed to this release:
+
+- Andrew Chow
+- BlackcoinDev
+- Carl Dong
+- Hennadii Stepanov
+- Joan Karadimov
+- John Moffett
+- Jon Atack
+- Kittywhiskers Van Gogh
+- Marco Falke
+- Martin Zumsande
+- Michael Ford
+- muxator
+- Pieter Wuille
+- Ryan Ofsky
+- Saibato
+- Sebastian Falbesoner
+- W. J. van der Laan
+
+As well as to everyone that helped with translations on
+[Transifex](https://www.transifex.com/bitcoin/bitcoin/).
diff --git a/doc/release-notes/release-notes-23.1.md b/doc/release-notes/release-notes-23.1.md
new file mode 100644
index 0000000000..31d9b7f068
--- /dev/null
+++ b/doc/release-notes/release-notes-23.1.md
@@ -0,0 +1,90 @@
+23.1 Release Notes
+==================
+
+Bitcoin Core version 23.1 is now available from:
+
+ <https://bitcoincore.org/bin/bitcoin-core-23.1/>
+
+This release includes new features, various bug fixes and performance
+improvements, as well as updated translations.
+
+Please report bugs using the issue tracker at GitHub:
+
+ <https://github.com/bitcoin/bitcoin/issues>
+
+To receive security and update notifications, please subscribe to:
+
+ <https://bitcoincore.org/en/list/announcements/join/>
+
+How to Upgrade
+==============
+
+If you are running an older version, shut it down. Wait until it has completely
+shut down (which might take a few minutes in some cases), then run the
+installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on macOS)
+or `bitcoind`/`bitcoin-qt` (on Linux).
+
+Upgrading directly from a version of Bitcoin Core that has reached its EOL is
+possible, but it might take some time if the data directory needs to be migrated. Old
+wallet versions of Bitcoin Core are generally supported.
+
+Compatibility
+==============
+
+Bitcoin Core is supported and extensively tested on operating systems
+using the Linux kernel, macOS 10.15+, and Windows 7 and newer. Bitcoin
+Core should also work on most other Unix-like systems but is not as
+frequently tested on them. It is not recommended to use Bitcoin Core on
+unsupported systems.
+
+### P2P
+
+- #25314 p2p: always set nTime for self-advertisements
+
+### RPC and other APIs
+
+- #25220 rpc: fix incorrect warning for address type p2sh-segwit in createmultisig
+- #25237 rpc: Capture UniValue by ref for rpcdoccheck
+- #25983 Prevent data race for pathHandlers
+- #26275 Fix crash on deriveaddresses when index is 2147483647 (2^31-1)
+
+### Build system
+
+- #25201 windeploy: Renewed windows code signing certificate
+- #25788 guix: patch NSIS to remove .reloc sections from installer stubs
+- #25861 guix: use --build={arch}-guix-linux-gnu in cross toolchain
+- #25985 Revert "build: Use Homebrew's sqlite package if it is available"
+
+### GUI
+
+- #24668 build, qt: bump Qt5 version to 5.15.3
+- gui#631 Disallow encryption of watchonly wallets
+- gui#680 Fixes MacOS 13 segfault by preventing certain notifications
+
+### Tests
+
+- #24454 tests: Fix calculation of external input weights
+
+### Miscellaneous
+
+- #26321 Adjust .tx/config for new Transifex CLI
+
+Credits
+=======
+
+Thanks to everyone who directly contributed to this release:
+
+- Andrew Chow
+- brunoerg
+- Hennadii Stepanov
+- John Moffett
+- MacroFake
+- Martin Zumsande
+- Michael Ford
+- muxator
+- Pavol Rusnak
+- Sebastian Falbesoner
+- W. J. van der Laan
+
+As well as to everyone that helped with translations on
+[Transifex](https://www.transifex.com/bitcoin/bitcoin/).
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
index 1a19555f76..6f84d5c83b 100644
--- a/src/httpserver.cpp
+++ b/src/httpserver.cpp
@@ -192,19 +192,16 @@ std::string RequestMethodString(HTTPRequest::RequestMethod m)
switch (m) {
case HTTPRequest::GET:
return "GET";
- break;
case HTTPRequest::POST:
return "POST";
- break;
case HTTPRequest::HEAD:
return "HEAD";
- break;
case HTTPRequest::PUT:
return "PUT";
- break;
- default:
+ case HTTPRequest::UNKNOWN:
return "unknown";
- }
+ } // no default case, so the compiler can warn about missing cases
+ assert(false);
}
/** HTTP request callback */
@@ -626,19 +623,14 @@ HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() const
switch (evhttp_request_get_command(req)) {
case EVHTTP_REQ_GET:
return GET;
- break;
case EVHTTP_REQ_POST:
return POST;
- break;
case EVHTTP_REQ_HEAD:
return HEAD;
- break;
case EVHTTP_REQ_PUT:
return PUT;
- break;
default:
return UNKNOWN;
- break;
}
}
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index e31ca81a08..4dfd77c6cf 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -661,9 +661,9 @@ private:
*/
bool MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
/** Potentially fetch blocks from this peer upon receipt of a new headers tip */
- void HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex* pindexLast);
+ void HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex& last_header);
/** Update peer state based on received headers message */
- void UpdatePeerStateForReceivedHeaders(CNode& pfrom, const CBlockIndex *pindexLast, bool received_new_header, bool may_have_more_headers);
+ void UpdatePeerStateForReceivedHeaders(CNode& pfrom, const CBlockIndex& last_header, bool received_new_header, bool may_have_more_headers);
void SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlock& block, const BlockTransactionsRequest& req);
@@ -2622,22 +2622,21 @@ bool PeerManagerImpl::MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& loc
}
/*
- * Given a new headers tip ending in pindexLast, potentially request blocks towards that tip.
+ * Given a new headers tip ending in last_header, potentially request blocks towards that tip.
* We require that the given tip have at least as much work as our tip, and for
* our current tip to be "close to synced" (see CanDirectFetch()).
*/
-void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex* pindexLast)
+void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex& last_header)
{
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
LOCK(cs_main);
CNodeState *nodestate = State(pfrom.GetId());
- if (CanDirectFetch() && pindexLast->IsValid(BLOCK_VALID_TREE) && m_chainman.ActiveChain().Tip()->nChainWork <= pindexLast->nChainWork) {
-
+ if (CanDirectFetch() && last_header.IsValid(BLOCK_VALID_TREE) && m_chainman.ActiveChain().Tip()->nChainWork <= last_header.nChainWork) {
std::vector<const CBlockIndex*> vToFetch;
- const CBlockIndex *pindexWalk = pindexLast;
- // Calculate all the blocks we'd need to switch to pindexLast, up to a limit.
+ const CBlockIndex* pindexWalk{&last_header};
+ // Calculate all the blocks we'd need to switch to last_header, up to a limit.
while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) &&
!IsBlockRequested(pindexWalk->GetBlockHash()) &&
@@ -2653,8 +2652,8 @@ void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, c
// direct fetch and rely on parallel download instead.
if (!m_chainman.ActiveChain().Contains(pindexWalk)) {
LogPrint(BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n",
- pindexLast->GetBlockHash().ToString(),
- pindexLast->nHeight);
+ last_header.GetBlockHash().ToString(),
+ last_header.nHeight);
} else {
std::vector<CInv> vGetData;
// Download as much as possible, from earliest to latest.
@@ -2671,14 +2670,15 @@ void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, c
}
if (vGetData.size() > 1) {
LogPrint(BCLog::NET, "Downloading blocks toward %s (%d) via headers direct fetch\n",
- pindexLast->GetBlockHash().ToString(), pindexLast->nHeight);
+ last_header.GetBlockHash().ToString(),
+ last_header.nHeight);
}
if (vGetData.size() > 0) {
if (!m_ignore_incoming_txs &&
nodestate->m_provides_cmpctblocks &&
vGetData.size() == 1 &&
mapBlocksInFlight.size() == 1 &&
- pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) {
+ last_header.pprev->IsValid(BLOCK_VALID_CHAIN)) {
// In any case, we want to download using a compact block, not a regular one
vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash);
}
@@ -2689,12 +2689,12 @@ void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, c
}
/**
- * Given receipt of headers from a peer ending in pindexLast, along with
+ * Given receipt of headers from a peer ending in last_header, along with
* whether that header was new and whether the headers message was full,
* update the state we keep for the peer.
*/
void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom,
- const CBlockIndex *pindexLast, bool received_new_header, bool may_have_more_headers)
+ const CBlockIndex& last_header, bool received_new_header, bool may_have_more_headers)
{
LOCK(cs_main);
CNodeState *nodestate = State(pfrom.GetId());
@@ -2703,14 +2703,13 @@ void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom,
}
nodestate->nUnconnectingHeaders = 0;
- assert(pindexLast);
- UpdateBlockAvailability(pfrom.GetId(), pindexLast->GetBlockHash());
+ UpdateBlockAvailability(pfrom.GetId(), last_header.GetBlockHash());
// From here, pindexBestKnownBlock should be guaranteed to be non-null,
// because it is set in UpdateBlockAvailability. Some nullptr checks
// are still present, however, as belt-and-suspenders.
- if (received_new_header && pindexLast->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) {
+ if (received_new_header && last_header.nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) {
nodestate->m_last_block_announcement = GetTime();
}
@@ -2876,7 +2875,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
return;
}
}
- Assume(pindexLast);
+ assert(pindexLast);
// Consider fetching more headers if we are not using our headers-sync mechanism.
if (nCount == MAX_HEADERS_RESULTS && !have_headers_sync) {
@@ -2887,10 +2886,10 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
}
}
- UpdatePeerStateForReceivedHeaders(pfrom, pindexLast, received_new_header, nCount == MAX_HEADERS_RESULTS);
+ UpdatePeerStateForReceivedHeaders(pfrom, *pindexLast, received_new_header, nCount == MAX_HEADERS_RESULTS);
// Consider immediately downloading blocks.
- HeadersDirectFetchBlocks(pfrom, peer, pindexLast);
+ HeadersDirectFetchBlocks(pfrom, peer, *pindexLast);
return;
}
@@ -3304,39 +3303,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
m_num_preferred_download_peers += state->fPreferredDownload;
}
- // Self advertisement & GETADDR logic
- if (!pfrom.IsInboundConn() && SetupAddressRelay(pfrom, *peer)) {
- // For outbound peers, we try to relay our address (so that other
- // nodes can try to find us more quickly, as we have no guarantee
- // that an outbound peer is even aware of how to reach us) and do a
- // one-time address fetch (to help populate/update our addrman). If
- // we're starting up for the first time, our addrman may be pretty
- // empty and no one will know who we are, so these mechanisms are
- // important to help us connect to the network.
- //
+ // Attempt to initialize address relay for outbound peers and use result
+ // to decide whether to send GETADDR, so that we don't send it to
+ // inbound or outbound block-relay-only peers.
+ bool send_getaddr{false};
+ if (!pfrom.IsInboundConn()) {
+ send_getaddr = SetupAddressRelay(pfrom, *peer);
+ }
+ if (send_getaddr) {
+ // Do a one-time address fetch to help populate/update our addrman.
+ // If we're starting up for the first time, our addrman may be pretty
+ // empty, so this mechanism is important to help us connect to the network.
// We skip this for block-relay-only peers. We want to avoid
// potentially leaking addr information and we do not want to
// indicate to the peer that we will participate in addr relay.
- if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload())
- {
- CAddress addr{GetLocalAddress(pfrom.addr), peer->m_our_services, Now<NodeSeconds>()};
- FastRandomContext insecure_rand;
- if (addr.IsRoutable())
- {
- LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
- PushAddress(*peer, addr, insecure_rand);
- } else if (IsPeerAddrLocalGood(&pfrom)) {
- // Override just the address with whatever the peer sees us as.
- // Leave the port in addr as it was returned by GetLocalAddress()
- // above, as this is an outbound connection and the peer cannot
- // observe our listening port.
- addr.SetIP(addrMe);
- LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
- PushAddress(*peer, addr, insecure_rand);
- }
- }
-
- // Get recent addresses
m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR));
peer->m_getaddr_sent = true;
// When requesting a getaddr, accept an additional MAX_ADDR_TO_SEND addresses in response
@@ -5343,8 +5323,9 @@ bool PeerManagerImpl::SetupAddressRelay(const CNode& node, Peer& peer)
if (node.IsBlockOnlyConn()) return false;
if (!peer.m_addr_relay_enabled.exchange(true)) {
- // First addr message we have received from the peer, initialize
- // m_addr_known
+ // During version message processing (non-block-relay-only outbound peers)
+ // or on first addr-related message we have received (inbound peers), initialize
+ // m_addr_known.
peer.m_addr_known = std::make_unique<CRollingBloomFilter>(5000, 0.001);
}
diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h
index f496ea022e..6b4a6335a1 100644
--- a/src/primitives/transaction.h
+++ b/src/primitives/transaction.h
@@ -17,6 +17,7 @@
#include <ios>
#include <limits>
#include <memory>
+#include <numeric>
#include <string>
#include <tuple>
#include <utility>
@@ -280,6 +281,12 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) {
s << tx.nLockTime;
}
+template<typename TxType>
+inline CAmount CalculateOutputValue(const TxType& tx)
+{
+ return std::accumulate(tx.vout.cbegin(), tx.vout.cend(), CAmount{0}, [](CAmount sum, const auto& txout) { return sum + txout.nValue; });
+}
+
/** The basic transaction that is broadcasted on the network and contained in
* blocks. A transaction can contain multiple inputs and outputs.
diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h
index d8748d7dc9..fe9c85279e 100644
--- a/src/qt/transactionrecord.h
+++ b/src/qt/transactionrecord.h
@@ -76,12 +76,12 @@ public:
static const int RecommendedNumConfirmations = 6;
TransactionRecord():
- hash(), time(0), type(Other), address(""), debit(0), credit(0), idx(0)
+ hash(), time(0), type(Other), debit(0), credit(0), idx(0)
{
}
TransactionRecord(uint256 _hash, qint64 _time):
- hash(_hash), time(_time), type(Other), address(""), debit(0),
+ hash(_hash), time(_time), type(Other), debit(0),
credit(0), idx(0)
{
}
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index b7432a0d77..b1c722388c 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -421,9 +421,6 @@ void TransactionView::abandonTx()
// Abandon the wallet transaction over the walletModel
model->wallet().abandonTransaction(hash);
-
- // Update the table
- model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
}
void TransactionView::bumpFee([[maybe_unused]] bool checked)
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index b3434b80c7..a795296f35 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -102,6 +102,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getchaintxstats", 0, "nblocks" },
{ "gettransaction", 1, "include_watchonly" },
{ "gettransaction", 2, "verbose" },
+ { "getrawtransaction", 1, "verbosity" },
{ "getrawtransaction", 1, "verbose" },
{ "createrawtransaction", 0, "inputs" },
{ "createrawtransaction", 1, "outputs" },
@@ -289,6 +290,9 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s
std::string name = s.substr(0, pos);
std::string value = s.substr(pos+1);
+ // Intentionally overwrite earlier named values with later ones as a
+ // convenience for scripts and command line users that want to merge
+ // options.
if (!rpcCvtTable.convert(strMethod, name)) {
// insert string value directly
params.pushKV(name, value);
@@ -299,7 +303,10 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s
}
if (!positional_args.empty()) {
- params.pushKV("args", positional_args);
+ // Use __pushKV instead of pushKV to avoid overwriting an explicit
+ // "args" value with an implicit one. Let the RPC server handle the
+ // request as given.
+ params.__pushKV("args", positional_args);
}
return params;
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 8d7f4e7f5b..1a4cd09284 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -731,9 +731,7 @@ static RPCHelpMan setban()
if (!request.params[2].isNull())
banTime = request.params[2].getInt<int64_t>();
- bool absolute = false;
- if (request.params[3].isTrue())
- absolute = true;
+ const bool absolute{request.params[3].isNull() ? false : request.params[3].get_bool()};
if (isSubnet) {
node.banman->Ban(subNet, banTime, absolute);
@@ -942,7 +940,7 @@ static RPCHelpMan addpeeraddress()
const std::string& addr_string{request.params[0].get_str()};
const auto port{request.params[1].getInt<uint16_t>()};
- const bool tried{request.params[2].isTrue()};
+ const bool tried{request.params[2].isNull() ? false : request.params[2].get_bool()};
UniValue obj(UniValue::VOBJ);
CNetAddr net_addr;
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index d654de1862..df30eaaa97 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -32,6 +32,7 @@
#include <script/signingprovider.h>
#include <script/standard.h>
#include <uint256.h>
+#include <undo.h>
#include <util/bip32.h>
#include <util/check.h>
#include <util/strencodings.h>
@@ -50,15 +51,17 @@ using node::FindCoins;
using node::GetTransaction;
using node::NodeContext;
using node::PSBTAnalysis;
+using node::ReadBlockFromDisk;
+using node::UndoReadFromDisk;
-static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate)
+static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_TXID)
{
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
//
// Blockchain contextual information (confirmations and blocktime) is not
// available to code in bitcoin-common, so we query them here and push the
// data into the returned UniValue.
- TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags());
+ TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity);
if (!hashBlock.IsNull()) {
LOCK(cs_main);
@@ -166,26 +169,27 @@ static RPCHelpMan getrawtransaction()
{
return RPCHelpMan{
"getrawtransaction",
- "Return the raw transaction data.\n"
- "\nBy default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
+ "By default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
"and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n"
"If a blockhash argument is passed, it will return the transaction if\n"
- "the specified block is available and the transaction is in that block.\n"
- "\nHint: Use gettransaction for wallet transactions.\n"
+ "the specified block is available and the transaction is in that block.\n\n"
+ "Hint: Use gettransaction for wallet transactions.\n\n"
- "\nIf verbose is 'true', returns an Object with information about 'txid'.\n"
- "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.",
+ "If verbosity is 0 or omitted, returns the serialized transaction as a hex-encoded string.\n"
+ "If verbosity is 1, returns a JSON Object with information about transaction.\n"
+ "If verbosity is 2, returns a JSON Object with information about transaction, including fee and prevout information.",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If false, return a string, otherwise return a json object"},
+ {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout"},
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The block in which to look for the transaction"},
},
{
- RPCResult{"if verbose is not set or set to false",
- RPCResult::Type::STR, "data", "The serialized, hex-encoded data for 'txid'"
+ RPCResult{"if verbosity is not set or set to 0",
+ RPCResult::Type::STR, "data", "The serialized transaction as a hex-encoded string for 'txid'"
},
- RPCResult{"if verbose is set to true",
+ RPCResult{"if verbosity is set to 1",
+ // When updating this documentation, update `decoderawtransaction` in the same way.
RPCResult::Type::OBJ, "", "",
Cat<std::vector<RPCResult>>(
{
@@ -198,20 +202,47 @@ static RPCHelpMan getrawtransaction()
},
DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")),
},
+ RPCResult{"for verbosity = 2",
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
+ {RPCResult::Type::NUM, "fee", /* optional */ true, "transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
+ {RPCResult::Type::ARR, "vin", "",
+ {
+ {RPCResult::Type::OBJ, "", /* optional */ true, "utxo being spent, omitted if block undo data is not available",
+ {
+ {RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
+ {RPCResult::Type::OBJ, "prevout", "Only if undo information is available)",
+ {
+ {RPCResult::Type::BOOL, "generated", "Coinbase or not"},
+ {RPCResult::Type::NUM, "height", "The height of the prevout"},
+ {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::OBJ, "scriptPubKey", "",
+ {
+ {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
+ }},
+ }},
+ }},
+ }},
+ }},
},
RPCExamples{
HelpExampleCli("getrawtransaction", "\"mytxid\"")
- + HelpExampleCli("getrawtransaction", "\"mytxid\" true")
- + HelpExampleRpc("getrawtransaction", "\"mytxid\", true")
- + HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"")
- + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"")
+ + HelpExampleCli("getrawtransaction", "\"mytxid\" 1")
+ + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1")
+ + HelpExampleCli("getrawtransaction", "\"mytxid\" 0 \"myblockhash\"")
+ + HelpExampleCli("getrawtransaction", "\"mytxid\" 1 \"myblockhash\"")
+ + HelpExampleCli("getrawtransaction", "\"mytxid\" 2 \"myblockhash\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
- bool in_active_chain = true;
uint256 hash = ParseHashV(request.params[0], "parameter 1");
const CBlockIndex* blockindex = nullptr;
@@ -220,10 +251,14 @@ static RPCHelpMan getrawtransaction()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved");
}
- // Accept either a bool (true) or a num (>=1) to indicate verbose output.
- bool fVerbose = false;
+ // Accept either a bool (true) or a num (>=0) to indicate verbosity.
+ int verbosity{0};
if (!request.params[1].isNull()) {
- fVerbose = request.params[1].isNum() ? (request.params[1].getInt<int>() != 0) : request.params[1].get_bool();
+ if (request.params[1].isBool()) {
+ verbosity = request.params[1].get_bool();
+ } else {
+ verbosity = request.params[1].getInt<int>();
+ }
}
if (!request.params[2].isNull()) {
@@ -234,7 +269,6 @@ static RPCHelpMan getrawtransaction()
if (!blockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found");
}
- in_active_chain = chainman.ActiveChain().Contains(blockindex);
}
bool f_txindex_ready = false;
@@ -262,13 +296,43 @@ static RPCHelpMan getrawtransaction()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errmsg + ". Use gettransaction for wallet transactions.");
}
- if (!fVerbose) {
+ if (verbosity <= 0) {
return EncodeHexTx(*tx, RPCSerializationFlags());
}
UniValue result(UniValue::VOBJ);
- if (blockindex) result.pushKV("in_active_chain", in_active_chain);
- TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
+ if (blockindex) {
+ LOCK(cs_main);
+ result.pushKV("in_active_chain", chainman.ActiveChain().Contains(blockindex));
+ }
+ // If request is verbosity >= 1 but no blockhash was given, then look up the blockindex
+ if (request.params[2].isNull()) {
+ LOCK(cs_main);
+ blockindex = chainman.m_blockman.LookupBlockIndex(hash_block);
+ }
+ if (verbosity == 1) {
+ TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
+ return result;
+ }
+
+ CBlockUndo blockUndo;
+ CBlock block;
+ const bool is_block_pruned{WITH_LOCK(cs_main, return chainman.m_blockman.IsBlockPruned(blockindex))};
+
+ if (tx->IsCoinBase() ||
+ !blockindex || is_block_pruned ||
+ !(UndoReadFromDisk(blockUndo, blockindex) && ReadBlockFromDisk(block, blockindex, Params().GetConsensus()))) {
+ TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
+ return result;
+ }
+
+ CTxUndo* undoTX {nullptr};
+ auto it = std::find_if(block.vtx.begin(), block.vtx.end(), [tx](CTransactionRef t){ return *t == *tx; });
+ if (it != block.vtx.end()) {
+ // -1 as blockundo does not have coinbase tx
+ undoTX = &blockUndo.vtxundo.at(it - block.vtx.begin() - 1);
+ }
+ TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate(), undoTX, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
return result;
},
};
@@ -304,7 +368,7 @@ static RPCHelpMan createrawtransaction()
std::optional<bool> rbf;
if (!request.params[3].isNull()) {
- rbf = request.params[3].isTrue();
+ rbf = request.params[3].get_bool();
}
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
@@ -1449,7 +1513,7 @@ static RPCHelpMan createpsbt()
std::optional<bool> rbf;
if (!request.params[3].isNull()) {
- rbf = request.params[3].isTrue();
+ rbf = request.params[3].get_bool();
}
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 80a22ff8ca..a026b7adfa 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -399,7 +399,10 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
const std::vector<UniValue>& values = in.params.getValues();
std::unordered_map<std::string, const UniValue*> argsIn;
for (size_t i=0; i<keys.size(); ++i) {
- argsIn[keys[i]] = &values[i];
+ auto [_, inserted] = argsIn.emplace(keys[i], &values[i]);
+ if (!inserted) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + keys[i] + " specified multiple times");
+ }
}
// Process expected parameters. If any parameters were left unspecified in
// the request before a parameter that was specified, null values need to be
diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp
index 21ccbe9648..f9b8a47330 100644
--- a/src/test/rpc_tests.cpp
+++ b/src/test/rpc_tests.cpp
@@ -84,11 +84,15 @@ BOOST_FIXTURE_TEST_SUITE(rpc_tests, RPCTestingSetup)
BOOST_AUTO_TEST_CASE(rpc_namedparams)
{
- const std::vector<std::string> arg_names{{"arg1", "arg2", "arg3", "arg4", "arg5"}};
+ const std::vector<std::string> arg_names{"arg1", "arg2", "arg3", "arg4", "arg5"};
// Make sure named arguments are transformed into positional arguments in correct places separated by nulls
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg2": 2, "arg4": 4})"), arg_names).write(), "[null,2,null,4]");
+ // Make sure named argument specified multiple times raises an exception
+ BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"arg2": 2, "arg2": 4})"), arg_names), UniValue,
+ HasJSON(R"({"code":-8,"message":"Parameter arg2 specified multiple times"})"));
+
// Make sure named and positional arguments can be combined.
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg5": 5, "args": [1, 2], "arg4": 4})"), arg_names).write(), "[1,2,null,4,5]");
@@ -100,7 +104,7 @@ BOOST_AUTO_TEST_CASE(rpc_namedparams)
BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"args": [1,2,3], "arg4": 4, "arg2": 2})"), arg_names), UniValue,
HasJSON(R"({"code":-8,"message":"Parameter arg2 specified twice both as positional and named argument"})"));
- // Make sure extra positional arguments can be passed through to the method implemenation, as long as they don't overlap with named arguments.
+ // Make sure extra positional arguments can be passed through to the method implementation, as long as they don't overlap with named arguments.
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"args": [1,2,3,4,5,6,7,8,9,10]})"), arg_names).write(), "[1,2,3,4,5,6,7,8,9,10]");
BOOST_CHECK_EQUAL(TransformParams(JSON(R"([1,2,3,4,5,6,7,8,9,10])"), arg_names).write(), "[1,2,3,4,5,6,7,8,9,10]");
}
diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp
index d5b65b9c08..472b58b4d5 100644
--- a/src/test/system_tests.cpp
+++ b/src/test/system_tests.cpp
@@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(run_command)
BOOST_CHECK(result.isObject());
const UniValue& success = find_value(result, "success");
BOOST_CHECK(!success.isNull());
- BOOST_CHECK_EQUAL(success.getBool(), true);
+ BOOST_CHECK_EQUAL(success.get_bool(), true);
}
{
// An invalid command is handled by Boost
@@ -95,7 +95,7 @@ BOOST_AUTO_TEST_CASE(run_command)
BOOST_CHECK(result.isObject());
const UniValue& success = find_value(result, "success");
BOOST_CHECK(!success.isNull());
- BOOST_CHECK_EQUAL(success.getBool(), true);
+ BOOST_CHECK_EQUAL(success.get_bool(), true);
}
#endif
}
diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h
index 1af7df079e..16853260b8 100644
--- a/src/univalue/include/univalue.h
+++ b/src/univalue/include/univalue.h
@@ -66,7 +66,6 @@ public:
size_t size() const { return values.size(); }
- bool getBool() const { return isTrue(); }
void getObjMap(std::map<std::string,UniValue>& kv) const;
bool checkObject(const std::map<std::string,UniValue::VType>& memberTypes) const;
const UniValue& operator[](const std::string& key) const;
diff --git a/src/univalue/lib/univalue_get.cpp b/src/univalue/lib/univalue_get.cpp
index 5c58f388dd..037449ca08 100644
--- a/src/univalue/lib/univalue_get.cpp
+++ b/src/univalue/lib/univalue_get.cpp
@@ -60,7 +60,7 @@ const std::vector<UniValue>& UniValue::getValues() const
bool UniValue::get_bool() const
{
checkType(VBOOL);
- return getBool();
+ return isTrue();
}
const std::string& UniValue::get_str() const
diff --git a/src/univalue/test/object.cpp b/src/univalue/test/object.cpp
index 65e82543e4..eeaadae3e2 100644
--- a/src/univalue/test/object.cpp
+++ b/src/univalue/test/object.cpp
@@ -193,13 +193,13 @@ void univalue_set()
BOOST_CHECK_EQUAL(v.isBool(), true);
BOOST_CHECK_EQUAL(v.isTrue(), false);
BOOST_CHECK_EQUAL(v.isFalse(), true);
- BOOST_CHECK_EQUAL(v.getBool(), false);
+ BOOST_CHECK_EQUAL(v.get_bool(), false);
v.setBool(true);
BOOST_CHECK_EQUAL(v.isBool(), true);
BOOST_CHECK_EQUAL(v.isTrue(), true);
BOOST_CHECK_EQUAL(v.isFalse(), false);
- BOOST_CHECK_EQUAL(v.getBool(), true);
+ BOOST_CHECK_EQUAL(v.get_bool(), true);
BOOST_CHECK_THROW(v.setNumStr("zombocom"), std::runtime_error);
diff --git a/src/validation.cpp b/src/validation.cpp
index 2380c5b925..76bea97341 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1542,7 +1542,7 @@ bool Chainstate::IsInitialBlockDownload() const
if (m_chain.Tip()->nChainWork < m_chainman.MinimumChainWork()) {
return true;
}
- if (m_chain.Tip()->Time() < NodeClock::now() - m_chainman.m_options.max_tip_age) {
+ if (m_chain.Tip()->Time() < Now<NodeSeconds>() - m_chainman.m_options.max_tip_age) {
return true;
}
LogPrintf("Leaving InitialBlockDownload (latching to false)\n");
@@ -2360,8 +2360,6 @@ bool Chainstate::FlushStateToDisk(
{
LOCK(cs_main);
assert(this->CanFlushToDisk());
- static std::chrono::microseconds nLastWrite{0};
- static std::chrono::microseconds nLastFlush{0};
std::set<int> setFilesToPrune;
bool full_flush_completed = false;
@@ -2415,20 +2413,20 @@ bool Chainstate::FlushStateToDisk(
}
const auto nNow = GetTime<std::chrono::microseconds>();
// Avoid writing/flushing immediately after startup.
- if (nLastWrite.count() == 0) {
- nLastWrite = nNow;
+ if (m_last_write.count() == 0) {
+ m_last_write = nNow;
}
- if (nLastFlush.count() == 0) {
- nLastFlush = nNow;
+ if (m_last_flush.count() == 0) {
+ m_last_flush = nNow;
}
// The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).
bool fCacheLarge = mode == FlushStateMode::PERIODIC && cache_state >= CoinsCacheSizeState::LARGE;
// The cache is over the limit, we have to write now.
bool fCacheCritical = mode == FlushStateMode::IF_NEEDED && cache_state >= CoinsCacheSizeState::CRITICAL;
// It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash.
- bool fPeriodicWrite = mode == FlushStateMode::PERIODIC && nNow > nLastWrite + DATABASE_WRITE_INTERVAL;
+ bool fPeriodicWrite = mode == FlushStateMode::PERIODIC && nNow > m_last_write + DATABASE_WRITE_INTERVAL;
// It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage.
- bool fPeriodicFlush = mode == FlushStateMode::PERIODIC && nNow > nLastFlush + DATABASE_FLUSH_INTERVAL;
+ bool fPeriodicFlush = mode == FlushStateMode::PERIODIC && nNow > m_last_flush + DATABASE_FLUSH_INTERVAL;
// Combine all conditions that result in a full cache flush.
fDoFullFlush = (mode == FlushStateMode::ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicFlush || fFlushForPrune;
// Write blocks and block index to disk.
@@ -2458,7 +2456,7 @@ bool Chainstate::FlushStateToDisk(
UnlinkPrunedFiles(setFilesToPrune);
}
- nLastWrite = nNow;
+ m_last_write = nNow;
}
// Flush best chain related state. This can only be done if the blocks / block index write was also done.
if (fDoFullFlush && !CoinsTip().GetBestBlock().IsNull()) {
@@ -2476,7 +2474,7 @@ bool Chainstate::FlushStateToDisk(
// Flush the chainstate (which may refer to block index entries).
if (!CoinsTip().Flush())
return AbortNode(state, "Failed to write to coin database");
- nLastFlush = nNow;
+ m_last_flush = nNow;
full_flush_completed = true;
TRACE5(utxocache, flush,
(int64_t)(GetTimeMicros() - nNow.count()), // in microseconds (µs)
diff --git a/src/validation.h b/src/validation.h
index a080d12fe2..00f7265793 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -743,6 +743,9 @@ private:
void UpdateTip(const CBlockIndex* pindexNew)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ std::chrono::microseconds m_last_write{0};
+ std::chrono::microseconds m_last_flush{0};
+
friend ChainstateManager;
};
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 7ab4044bf5..fb5e9a425e 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -1651,11 +1651,8 @@ RPCHelpMan walletcreatefundedpsbt()
CAmount fee;
int change_position;
- bool rbf{wallet.m_signal_rbf};
const UniValue &replaceable_arg = options["replaceable"];
- if (!replaceable_arg.isNull()) {
- rbf = replaceable_arg.isTrue();
- }
+ const bool rbf{replaceable_arg.isNull() ? wallet.m_signal_rbf : replaceable_arg.get_bool()};
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
CCoinControl coin_control;
// Automatically select coins, unless at least one is manually selected. Can
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 3ced3ebeb5..33289c1d2f 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -8,6 +8,7 @@
#include <interfaces/chain.h>
#include <numeric>
#include <policy/policy.h>
+#include <primitives/transaction.h>
#include <script/signingprovider.h>
#include <util/check.h>
#include <util/fees.h>
@@ -524,8 +525,9 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
if (results.size() > 0) return *std::min_element(results.begin(), results.end());
// If we can't fund the transaction from any individual OutputType, run coin selection one last time
- // over all available coins, which would allow mixing
- if (allow_mixed_output_types) {
+ // over all available coins, which would allow mixing.
+ // If TypesCount() <= 1, there is nothing to mix.
+ if (allow_mixed_output_types && available_coins.TypesCount() > 1) {
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params)}) {
return result;
}
@@ -778,7 +780,6 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
AssertLockHeld(wallet.cs_wallet);
// out variables, to be packed into returned result structure
- CAmount nFeeRet;
int nChangePosInOut = change_pos;
FastRandomContext rng_fast;
@@ -960,24 +961,28 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
return util::Error{_("Missing solving data for estimating transaction size")};
}
CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
- nFeeRet = result->GetSelectedValue() - recipients_sum - change_amount;
+ const CAmount output_value = CalculateOutputValue(txNew);
+ Assume(recipients_sum + change_amount == output_value);
+ CAmount current_fee = result->GetSelectedValue() - output_value;
- // The only time that fee_needed should be less than the amount available for fees is when
- // we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
- if (!coin_selection_params.m_subtract_fee_outputs && fee_needed > nFeeRet) {
- return util::Error{Untranslated(STR_INTERNAL_BUG("Fee needed > fee paid"))};
+ // Sanity check that the fee cannot be negative as that means we have more output value than input value
+ if (current_fee < 0) {
+ return util::Error{Untranslated(STR_INTERNAL_BUG("Fee paid < 0"))};
}
// If there is a change output and we overpay the fees then increase the change to match the fee needed
- if (nChangePosInOut != -1 && fee_needed < nFeeRet) {
+ if (nChangePosInOut != -1 && fee_needed < current_fee) {
auto& change = txNew.vout.at(nChangePosInOut);
- change.nValue += nFeeRet - fee_needed;
- nFeeRet = fee_needed;
+ change.nValue += current_fee - fee_needed;
+ current_fee = result->GetSelectedValue() - CalculateOutputValue(txNew);
+ if (fee_needed != current_fee) {
+ return util::Error{Untranslated(STR_INTERNAL_BUG("Change adjustment: Fee needed != fee paid"))};
+ }
}
// Reduce output values for subtractFeeFromAmount
if (coin_selection_params.m_subtract_fee_outputs) {
- CAmount to_reduce = fee_needed - nFeeRet;
+ CAmount to_reduce = fee_needed - current_fee;
int i = 0;
bool fFirst = true;
for (const auto& recipient : vecSend)
@@ -1008,7 +1013,16 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
}
++i;
}
- nFeeRet = fee_needed;
+ current_fee = result->GetSelectedValue() - CalculateOutputValue(txNew);
+ if (fee_needed != current_fee) {
+ return util::Error{Untranslated(STR_INTERNAL_BUG("SFFO: Fee needed != fee paid"))};
+ }
+ }
+
+ // fee_needed should now always be less than or equal to the current fees that we pay.
+ // If it is not, it is a bug.
+ if (fee_needed > current_fee) {
+ return util::Error{Untranslated(STR_INTERNAL_BUG("Fee needed > fee paid"))};
}
// Give up if change keypool ran out and change is required
@@ -1030,7 +1044,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
return util::Error{_("Transaction too large")};
}
- if (nFeeRet > wallet.m_default_max_tx_fee) {
+ if (current_fee > wallet.m_default_max_tx_fee) {
return util::Error{TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)};
}
@@ -1046,14 +1060,14 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
reservedest.KeepDestination();
wallet.WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
- nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
+ current_fee, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
feeCalc.est.pass.start, feeCalc.est.pass.end,
(feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0,
feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
feeCalc.est.fail.start, feeCalc.est.fail.end,
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
- return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc);
+ return CreatedTransactionResult(tx, current_fee, nChangePosInOut, feeCalc);
}
util::Result<CreatedTransactionResult> CreateTransaction(
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index 2b861c2361..46144d4c92 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -46,6 +46,8 @@ struct CoinsResult {
/** The following methods are provided so that CoinsResult can mimic a vector,
* i.e., methods can work with individual OutputType vectors or on the entire object */
size_t Size() const;
+ /** Return how many different output types this struct stores */
+ size_t TypesCount() const { return coins.size(); }
void Clear();
void Erase(const std::unordered_set<COutPoint, SaltedOutpointHasher>& coins_to_remove);
void Shuffle(FastRandomContext& rng_fast);
diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py
index 67cacaa9ce..482c29c994 100755
--- a/test/functional/feature_assumevalid.py
+++ b/test/functional/feature_assumevalid.py
@@ -83,8 +83,6 @@ class AssumeValidTest(BitcoinTestFramework):
break
def run_test(self):
- p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
-
# Build the blockchain
self.tip = int(self.nodes[0].getbestblockhash(), 16)
self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
@@ -139,28 +137,23 @@ class AssumeValidTest(BitcoinTestFramework):
self.block_time += 1
height += 1
- self.nodes[0].disconnect_p2ps()
-
# Start node1 and node2 with assumevalid so they accept a block with a bad signature.
self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)])
self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)])
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
- p2p1 = self.nodes[1].add_p2p_connection(BaseNode())
- p2p2 = self.nodes[2].add_p2p_connection(BaseNode())
-
- # send header lists to all three nodes
p2p0.send_header_for_blocks(self.blocks[0:2000])
p2p0.send_header_for_blocks(self.blocks[2000:])
- p2p1.send_header_for_blocks(self.blocks[0:2000])
- p2p1.send_header_for_blocks(self.blocks[2000:])
- p2p2.send_header_for_blocks(self.blocks[0:200])
# Send blocks to node0. Block 102 will be rejected.
self.send_blocks_until_disconnected(p2p0)
self.wait_until(lambda: self.nodes[0].getblockcount() >= COINBASE_MATURITY + 1)
assert_equal(self.nodes[0].getblockcount(), COINBASE_MATURITY + 1)
+ p2p1 = self.nodes[1].add_p2p_connection(BaseNode())
+ p2p1.send_header_for_blocks(self.blocks[0:2000])
+ p2p1.send_header_for_blocks(self.blocks[2000:])
+
# Send all blocks to node1. All blocks will be accepted.
for i in range(2202):
p2p1.send_message(msg_block(self.blocks[i]))
@@ -168,6 +161,9 @@ class AssumeValidTest(BitcoinTestFramework):
p2p1.sync_with_ping(960)
assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202)
+ p2p2 = self.nodes[2].add_p2p_connection(BaseNode())
+ p2p2.send_header_for_blocks(self.blocks[0:200])
+
# Send blocks to node2. Block 102 will be rejected.
self.send_blocks_until_disconnected(p2p2)
self.wait_until(lambda: self.nodes[2].getblockcount() >= COINBASE_MATURITY + 1)
diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py
index ddc2102542..51f37ef1e0 100755
--- a/test/functional/feature_maxtipage.py
+++ b/test/functional/feature_maxtipage.py
@@ -22,23 +22,24 @@ class MaxTipAgeTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 2
- def test_maxtipage(self, maxtipage, set_parameter=True):
+ def test_maxtipage(self, maxtipage, set_parameter=True, test_deltas=True):
node_miner = self.nodes[0]
node_ibd = self.nodes[1]
self.restart_node(1, [f'-maxtipage={maxtipage}'] if set_parameter else None)
self.connect_nodes(0, 1)
-
- # tips older than maximum age -> stay in IBD
cur_time = int(time.time())
- node_ibd.setmocktime(cur_time)
- for delta in [5, 4, 3, 2, 1]:
- node_miner.setmocktime(cur_time - maxtipage - delta)
- self.generate(node_miner, 1)
- assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], True)
+
+ if test_deltas:
+ # tips older than maximum age -> stay in IBD
+ node_ibd.setmocktime(cur_time)
+ for delta in [5, 4, 3, 2, 1]:
+ node_miner.setmocktime(cur_time - maxtipage - delta)
+ self.generate(node_miner, 1)
+ assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], True)
# tip within maximum age -> leave IBD
- node_miner.setmocktime(cur_time - maxtipage)
+ node_miner.setmocktime(max(cur_time - maxtipage, 0))
self.generate(node_miner, 1)
assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], False)
@@ -51,6 +52,10 @@ class MaxTipAgeTest(BitcoinTestFramework):
self.log.info(f"Test IBD with maximum tip age of {hours} hours (-maxtipage={maxtipage}).")
self.test_maxtipage(maxtipage)
+ max_long_val = 9223372036854775807
+ self.log.info(f"Test IBD with highest allowable maximum tip age ({max_long_val}).")
+ self.test_maxtipage(max_long_val, test_deltas=False)
+
if __name__ == '__main__':
MaxTipAgeTest().main()
diff --git a/test/functional/feature_txindex_compatibility.py b/test/functional/feature_txindex_compatibility.py
index 20b023d82c..13dbdfce71 100755
--- a/test/functional/feature_txindex_compatibility.py
+++ b/test/functional/feature_txindex_compatibility.py
@@ -14,7 +14,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.wallet import MiniWallet
-class MempoolCompatibilityTest(BitcoinTestFramework):
+class TxindexCompatibilityTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.extra_args = [
@@ -33,7 +33,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
versions=[
160300, # Last release with legacy txindex
None, # For MiniWallet, without migration code
- 200100, # Any release with migration code (0.17.x - 22.x)
+ 220000, # Last release with migration code (0.17.x - 22.x)
],
)
self.start_nodes()
@@ -89,4 +89,4 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
if __name__ == "__main__":
- MempoolCompatibilityTest().main()
+ TxindexCompatibilityTest().main()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index b1369c2615..90a543b51b 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -90,6 +90,10 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Parameter arg1 specified twice both as positional and named argument", self.nodes[0].cli.echo, 0, 1, arg1=1)
assert_raises_rpc_error(-8, "Parameter arg1 specified twice both as positional and named argument", self.nodes[0].cli.echo, 0, None, 2, arg1=1)
+ self.log.info("Test that later cli named arguments values silently overwrite earlier ones")
+ assert_equal(self.nodes[0].cli("-named", "echo", "arg0=0", "arg1=1", "arg2=2", "arg1=3").send_cli(), ['0', '3', '2'])
+ assert_raises_rpc_error(-8, "Parameter args specified multiple times", self.nodes[0].cli("-named", "echo", "args=[0,1,2,3]", "4", "5", "6", ).send_cli)
+
user, password = get_auth_cookie(self.nodes[0].datadir, self.chain)
self.log.info("Test -stdinrpcpass option")
diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py
index f789a19ef3..c9233d6133 100755
--- a/test/functional/mempool_compatibility.py
+++ b/test/functional/mempool_compatibility.py
@@ -7,7 +7,7 @@
NOTE: The test is designed to prevent cases when compatibility is broken accidentally.
In case we need to break mempool compatibility we can continue to use the test by just bumping the version number.
-The previous release v0.19.1 is required by this test, see test/README.md.
+Previous releases are required by this test, see test/README.md.
"""
import os
@@ -29,7 +29,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
def setup_network(self):
self.add_nodes(self.num_nodes, versions=[
- 190100, # oldest version with getmempoolinfo.loaded (used to avoid intermittent issues)
+ 200100, # Last release with previous mempool format
None,
])
self.start_nodes()
@@ -38,6 +38,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
self.log.info("Test that mempool.dat is compatible between versions")
old_node, new_node = self.nodes
+ assert "unbroadcastcount" not in old_node.getmempoolinfo()
new_wallet = MiniWallet(new_node, mode=MiniWalletMode.RAW_P2PK)
self.generate(new_wallet, 1, sync_fun=self.no_op)
self.generate(new_node, COINBASE_MATURITY, sync_fun=self.no_op)
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 0501befe0f..06e76c4f92 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -307,6 +307,9 @@ class NetTest(BitcoinTestFramework):
assert_equal(node.addpeeraddress(address="", port=8333), {"success": False})
assert_equal(node.getnodeaddresses(count=0), [])
+ self.log.debug("Test that non-bool tried fails")
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[0].addpeeraddress, address="1.2.3.4", tried="True", port=1234)
+
self.log.debug("Test that adding an address with invalid port fails")
assert_raises_rpc_error(-1, "JSON integer out of range", self.nodes[0].addpeeraddress, address="1.2.3.4", port=-1)
assert_raises_rpc_error(-1, "JSON integer out of range", self.nodes[0].addpeeraddress,address="1.2.3.4", port=65536)
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 15fc947eef..b87f3ad6f3 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -14,6 +14,7 @@ Test the following RPCs:
from collections import OrderedDict
from decimal import Decimal
+from itertools import product
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import (
@@ -81,6 +82,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.generate(self.nodes[0], COINBASE_MATURITY + 1)
self.getrawtransaction_tests()
+ self.getrawtransaction_verbosity_tests()
self.createrawtransaction_tests()
self.sendrawtransaction_tests()
self.sendrawtransaction_testmempoolaccept_tests()
@@ -116,6 +118,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# 4. valid parameters - supply txid and 1 for verbose.
# We only check the "hex" field of the output so we don't need to update this test every time the output format changes.
assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], tx['hex'])
+ assert_equal(self.nodes[n].getrawtransaction(txId, 2)["hex"], tx['hex'])
# 5. valid parameters - supply txid and True for non-verbose
assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], tx['hex'])
@@ -126,13 +129,14 @@ class RawTransactionsTest(BitcoinTestFramework):
# 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose
for value in ["True", "False"]:
- assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txid=txId, verbose=value)
+ assert_raises_rpc_error(-3, "not of expected type number", self.nodes[n].getrawtransaction, txid=txId, verbose=value)
+ assert_raises_rpc_error(-3, "not of expected type number", self.nodes[n].getrawtransaction, txid=txId, verbosity=value)
# 7. invalid parameters - supply txid and empty array
- assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, [])
+ assert_raises_rpc_error(-3, "not of expected type number", self.nodes[n].getrawtransaction, txId, [])
# 8. invalid parameters - supply txid and empty dict
- assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, {})
+ assert_raises_rpc_error(-3, "not of expected type number", self.nodes[n].getrawtransaction, txId, {})
# Make a tx by sending, then generate 2 blocks; block1 has the tx in it
tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid']
@@ -145,9 +149,10 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(gottx['in_active_chain'], True)
if n == 0:
self.log.info("Test getrawtransaction with -txindex, without blockhash: 'in_active_chain' should be absent")
- gottx = self.nodes[n].getrawtransaction(txid=tx, verbose=True)
- assert_equal(gottx['txid'], tx)
- assert 'in_active_chain' not in gottx
+ for v in [1,2]:
+ gottx = self.nodes[n].getrawtransaction(txid=tx, verbosity=v)
+ assert_equal(gottx['txid'], tx)
+ assert 'in_active_chain' not in gottx
else:
self.log.info("Test getrawtransaction without -txindex, without blockhash: expect the call to raise")
assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txid=tx, verbose=True)
@@ -172,6 +177,70 @@ class RawTransactionsTest(BitcoinTestFramework):
block = self.nodes[0].getblock(self.nodes[0].getblockhash(0))
assert_raises_rpc_error(-5, "The genesis block coinbase is not considered an ordinary transaction", self.nodes[0].getrawtransaction, block['merkleroot'])
+ def getrawtransaction_verbosity_tests(self):
+ tx = self.wallet.send_self_transfer(from_node=self.nodes[1])['txid']
+ [block1] = self.generate(self.nodes[1], 1)
+ fields = [
+ 'blockhash',
+ 'blocktime',
+ 'confirmations',
+ 'hash',
+ 'hex',
+ 'in_active_chain',
+ 'locktime',
+ 'size',
+ 'time',
+ 'txid',
+ 'vin',
+ 'vout',
+ 'vsize',
+ 'weight',
+ ]
+ prevout_fields = [
+ 'generated',
+ 'height',
+ 'value',
+ 'scriptPubKey',
+ ]
+ script_pub_key_fields = [
+ 'address',
+ 'asm',
+ 'hex',
+ 'type',
+ ]
+ # node 0 & 2 with verbosity 1 & 2
+ for n, v in product([0, 2], [1, 2]):
+ self.log.info(f"Test getrawtransaction_verbosity {v} {'with' if n == 0 else 'without'} -txindex, with blockhash")
+ gottx = self.nodes[n].getrawtransaction(txid=tx, verbosity=v, blockhash=block1)
+ missing_fields = set(fields).difference(gottx.keys())
+ if missing_fields:
+ raise AssertionError(f"fields {', '.join(missing_fields)} are not in transaction")
+
+ assert(len(gottx['vin']) > 0)
+ if v == 1:
+ assert('fee' not in gottx)
+ assert('prevout' not in gottx['vin'][0])
+ if v == 2:
+ assert(isinstance(gottx['fee'], Decimal))
+ assert('prevout' in gottx['vin'][0])
+ prevout = gottx['vin'][0]['prevout']
+ script_pub_key = prevout['scriptPubKey']
+
+ missing_fields = set(prevout_fields).difference(prevout.keys())
+ if missing_fields:
+ raise AssertionError(f"fields {', '.join(missing_fields)} are not in transaction")
+
+ missing_fields = set(script_pub_key_fields).difference(script_pub_key.keys())
+ if missing_fields:
+ raise AssertionError(f"fields {', '.join(missing_fields)} are not in transaction")
+
+ # check verbosity 2 without blockhash but with txindex
+ assert('fee' in self.nodes[0].getrawtransaction(txid=tx, verbosity=2))
+ # check that coinbase has no fee or does not throw any errors for verbosity 2
+ coin_base = self.nodes[1].getblock(block1)['tx'][0]
+ gottx = self.nodes[1].getrawtransaction(txid=coin_base, verbosity=2, blockhash=block1)
+ assert('fee' not in gottx)
+
def createrawtransaction_tests(self):
self.log.info("Test createrawtransaction")
# Test `createrawtransaction` required parameters
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index fc93940b63..2d3b105fa2 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -337,7 +337,7 @@ class TestNode():
return
self.log.debug("Stopping node")
try:
- # Do not use wait argument when testing older nodes, e.g. in feature_backwards_compatibility.py
+ # Do not use wait argument when testing older nodes, e.g. in wallet_backwards_compatibility.py
if self.version_is_at_least(180000):
self.stop(wait=wait)
else:
diff --git a/test/functional/test_framework/test_shell.py b/test/functional/test_framework/test_shell.py
index 26df128f1f..2d8935dfe6 100644
--- a/test/functional/test_framework/test_shell.py
+++ b/test/functional/test_framework/test_shell.py
@@ -16,6 +16,9 @@ class TestShell:
start a single TestShell at a time."""
class __TestShell(BitcoinTestFramework):
+ def add_options(self, parser):
+ self.add_wallet_options(parser)
+
def set_test_params(self):
pass
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index f814b870cd..ed547b68a8 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -99,8 +99,8 @@ BASE_SCRIPTS = [
'mining_getblocktemplate_longpoll.py',
'feature_maxuploadtarget.py',
'feature_block.py',
- 'rpc_fundrawtransaction.py --legacy-wallet',
- 'rpc_fundrawtransaction.py --descriptors',
+ 'wallet_fundrawtransaction.py --legacy-wallet',
+ 'wallet_fundrawtransaction.py --descriptors',
'p2p_compactblocks.py',
'p2p_compactblocks_blocksonly.py',
'feature_segwit.py --legacy-wallet',
@@ -227,8 +227,8 @@ BASE_SCRIPTS = [
'wallet_txn_doublespend.py --legacy-wallet',
'wallet_multisig_descriptor_psbt.py --descriptors',
'wallet_txn_doublespend.py --descriptors',
- 'feature_backwards_compatibility.py --legacy-wallet',
- 'feature_backwards_compatibility.py --descriptors',
+ 'wallet_backwards_compatibility.py --legacy-wallet',
+ 'wallet_backwards_compatibility.py --descriptors',
'wallet_txn_clone.py --mineblock',
'feature_notifications.py',
'rpc_getblockfilter.py',
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/wallet_backwards_compatibility.py
index 5fe4a95f6f..c9cb3285fb 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/wallet_backwards_compatibility.py
@@ -7,10 +7,6 @@
Test various backwards compatibility scenarios. Requires previous releases binaries,
see test/README.md.
-v0.15.2 is not required by this test, but it is used in wallet_upgradewallet.py.
-Due to a hardfork in regtest, it can't be used to sync nodes.
-
-
Due to RPC changes introduced in various versions the below tests
won't work for older versions without some patches or workarounds.
@@ -274,6 +270,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
assert_equal(info["desc"], descsum_create(descriptor))
# Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
+ node_master.unloadwallet("u1_v16")
os.remove(os.path.join(node_v16_wallets_dir, "wallets/u1_v16"))
shutil.copyfile(
os.path.join(node_master_wallets_dir, "u1_v16"),
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py
index bf218bfee9..bf218bfee9 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/wallet_fundrawtransaction.py