Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13711085
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
495 KB
Subscribers
None
View Options
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bc133efbe..58d122996 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,782 +1,783 @@
# Copyright (c) 2017 The Bitcoin developers
project(bitcoind)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Default visibility is hidden on all targets.
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
option(BUILD_BITCOIN_WALLET "Activate the wallet functionality" ON)
option(BUILD_BITCOIN_ZMQ "Activate the ZeroMQ functionalities" ON)
option(BUILD_BITCOIN_CLI "Build bitcoin-cli" ON)
option(BUILD_BITCOIN_TX "Build bitcoin-tx" ON)
option(BUILD_BITCOIN_QT "Build bitcoin-qt" ON)
option(BUILD_BITCOIN_SEEDER "Build bitcoin-seeder" ON)
option(BUILD_LIBBITCOINCONSENSUS "Build the bitcoinconsenus shared library" ON)
option(ENABLE_BIP70 "Enable BIP70 (payment protocol) support in GUI" ON)
option(ENABLE_HARDENING "Harden the executables" ON)
option(ENABLE_REDUCE_EXPORTS "Reduce the amount of exported symbols" OFF)
option(ENABLE_STATIC_LIBSTDCXX "Statically link libstdc++" OFF)
option(ENABLE_GLIBC_BACK_COMPAT "Enable Glibc compatibility features" OFF)
option(ENABLE_QRCODE "Enable QR code display" ON)
option(ENABLE_UPNP "Enable UPnP support" ON)
option(START_WITH_UPNP "Make UPnP the default to map ports" OFF)
option(ENABLE_NATPMP "Enable NAT-PMP support" ON)
option(START_WITH_NATPMP "Make NAT-PMP the default to map ports" OFF)
option(ENABLE_CLANG_TIDY "Enable clang-tidy checks for Bitcoin ABC" OFF)
option(ENABLE_PROFILING "Select the profiling tool to use" OFF)
option(ENABLE_TRACING "Enable eBPF user static defined tracepoints" OFF)
# Linker option
if(CMAKE_CROSSCOMPILING)
set(DEFAULT_LINKER "")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(DEFAULT_LINKER lld)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(DEFAULT_LINKER gold)
else()
set(DEFAULT_LINKER "")
endif()
set(USE_LINKER "${DEFAULT_LINKER}" CACHE STRING "Linker to be used (default: ${DEFAULT_LINKER}). Set to empty string to use the system's default.")
set(OS_WITH_JEMALLOC_AS_SYSTEM_DEFAULT
"Android"
"FreeBSD"
"NetBSD"
)
if(NOT CMAKE_SYSTEM_NAME IN_LIST OS_WITH_JEMALLOC_AS_SYSTEM_DEFAULT)
set(USE_JEMALLOC_DEFAULT ON)
endif()
# FIXME: Building against jemalloc causes the software to segfault on OSX.
# See https://github.com/Bitcoin-ABC/bitcoin-abc/issues/401
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT CMAKE_CROSSCOMPILING)
set(USE_JEMALLOC_DEFAULT OFF)
endif()
option(USE_JEMALLOC "Use jemalloc as an allocation library" ${USE_JEMALLOC_DEFAULT})
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set(DEFAULT_ENABLE_DBUS_NOTIFICATIONS ON)
endif()
option(ENABLE_DBUS_NOTIFICATIONS "Enable DBus desktop notifications. Linux only." ${DEFAULT_ENABLE_DBUS_NOTIFICATIONS})
# If ccache is available, then use it.
find_program(CCACHE ccache)
if(CCACHE)
message(STATUS "Using ccache: ${CCACHE}")
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
endif(CCACHE)
# Disable what we do not need for the native build.
include(NativeExecutable)
native_add_cmake_flags(
"-DBUILD_BITCOIN_WALLET=OFF"
"-DBUILD_BITCOIN_CHRONIK=OFF"
"-DBUILD_BITCOIN_QT=OFF"
"-DBUILD_BITCOIN_ZMQ=OFF"
"-DENABLE_QRCODE=OFF"
"-DENABLE_NATPMP=OFF"
"-DENABLE_UPNP=OFF"
"-DUSE_JEMALLOC=OFF"
"-DENABLE_CLANG_TIDY=OFF"
"-DENABLE_BIP70=OFF"
)
if(ENABLE_CLANG_TIDY)
include(ClangTidy)
endif()
if(ENABLE_SANITIZERS)
include(Sanitizers)
enable_sanitizers(${ENABLE_SANITIZERS})
endif()
include(AddCompilerFlags)
if(USE_LINKER)
set(LINKER_FLAG "-fuse-ld=${USE_LINKER}")
custom_check_linker_flag(IS_LINKER_SUPPORTED ${LINKER_FLAG})
if(NOT IS_LINKER_SUPPORTED)
message(FATAL_ERROR "The ${USE_LINKER} linker is not supported, make sure ${USE_LINKER} is properly installed or use -DUSE_LINKER= to use the system's linker")
endif()
add_linker_flags(${LINKER_FLAG})
endif()
# Prefer -g3, defaults to -g if unavailable
foreach(LANGUAGE C CXX)
set(COMPILER_DEBUG_LEVEL -g)
check_compiler_flags(G3_IS_SUPPORTED ${LANGUAGE} -g3)
if(${G3_IS_SUPPORTED})
set(COMPILER_DEBUG_LEVEL -g3)
endif()
add_compile_options_to_configuration_for_language(Debug ${LANGUAGE} ${COMPILER_DEBUG_LEVEL})
endforeach()
# Define some debugging symbols when the Debug build type is selected.
add_compile_definitions_to_configuration(Debug DEBUG DEBUG_LOCKORDER ABORT_ON_FAILED_ASSUME)
# Add -ftrapv when building in Debug
add_compile_options_to_configuration(Debug -ftrapv)
# All versions of gcc that we commonly use for building are subject to bug
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90348. To work around that, set
# -fstack-reuse=none for all gcc builds. (Only gcc understands this flag)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compiler_flags(-fstack-reuse=none)
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
# Ensure that WINDRES_PREPROC is enabled when using windres.
list(APPEND CMAKE_RC_FLAGS "-DWINDRES_PREPROC")
# Build all static so there is no dll file to distribute.
add_linker_flags(-static)
add_compile_definitions(
# Windows 7
_WIN32_WINNT=0x0601
# Internet Explorer 5.01 (!)
_WIN32_IE=0x0501
# Define WIN32_LEAN_AND_MEAN to exclude APIs such as Cryptography, DDE,
# RPC, Shell, and Windows Sockets.
WIN32_LEAN_AND_MEAN
)
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_compile_definitions(MAC_OSX OBJC_OLD_DISPATCH_PROTOTYPES=0)
add_linker_flags(-Wl,-dead_strip_dylibs)
endif()
if(ENABLE_REDUCE_EXPORTS)
# Default visibility is set by CMAKE_<LANG>_VISIBILITY_PRESET, but this
# doesn't tell if the visibility set is effective.
# Check if the flag -fvisibility=hidden is supported, as using the hidden
# visibility is a requirement to reduce exports.
check_compiler_flags(HAS_CXX_FVISIBILITY CXX -fvisibility=hidden)
if(NOT HAS_CXX_FVISIBILITY)
message(FATAL_ERROR "Cannot set default symbol visibility. Use -DENABLE_REDUCE_EXPORTS=OFF.")
endif()
# Also hide symbols from static libraries
add_linker_flags(-Wl,--exclude-libs,libstdc++)
endif()
# Enable statically linking libstdc++
if(ENABLE_STATIC_LIBSTDCXX)
add_linker_flags(-static-libstdc++)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(ENABLE_HARDENING)
# Enable stack protection
add_cxx_compiler_flags(-fstack-protector-all -Wstack-protector)
# Enable some buffer overflow checking, except in -O0 builds which
# do not support them
add_compiler_flags(-U_FORTIFY_SOURCE)
add_compile_options($<$<NOT:$<CONFIG:Debug>>:-D_FORTIFY_SOURCE=2>)
# Enable ASLR (these flags are primarily targeting MinGw)
add_linker_flags(-Wl,--enable-reloc-section -Wl,--dynamicbase -Wl,--nxcompat -Wl,--high-entropy-va)
# Make the relocated sections read-only
add_linker_flags(-Wl,-z,relro -Wl,-z,now)
# CMake provides the POSITION_INDEPENDENT_CODE property to set PIC/PIE.
cmake_policy(SET CMP0083 NEW)
include(CheckPIESupported)
check_pie_supported()
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
# MinGw provides its own libssp for stack smashing protection
link_libraries(ssp)
endif()
endif()
if(ENABLE_PROFILING MATCHES "gprof")
message(STATUS "Enable profiling with gprof")
# -pg is incompatible with -pie. Since hardening and profiling together
# doesn't make sense, we simply make them mutually exclusive here.
# Additionally, hardened toolchains may force -pie by default, in which
# case it needs to be turned off with -no-pie.
if(ENABLE_HARDENING)
message(FATAL_ERROR "Profiling with gprof requires disabling hardening with -DENABLE_HARDENING=OFF.")
endif()
add_linker_flags(-no-pie)
add_compiler_flags(-pg)
add_linker_flags(-pg)
endif()
# Enable warning
add_c_compiler_flags(-Wnested-externs -Wstrict-prototypes)
add_compiler_flags(
-Wall
-Wextra
-Wformat
-Wgnu
-Wvla
-Wcast-align
-Wunused-parameter
-Wmissing-braces
-Wthread-safety
-Wrange-loop-analysis
-Wredundant-decls
-Wunreachable-code-loop-increment
-Wsign-compare
-Wconditional-uninitialized
-Wduplicated-branches
-Wduplicated-cond
-Wlogical-op
-Wdocumentation
)
add_compiler_flag_group(-Wformat -Wformat-security)
add_cxx_compiler_flags(
-Wredundant-move
-Woverloaded-virtual
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# GCC has no flag variant which is granular enough to avoid raising the clang
# -Wshadow-uncaptured-local equivalent. This is causing a lot of warnings
# on serialize.h which cannot be disabled locally, so drop the flag.
add_compiler_flags(
-Wshadow
-Wshadow-field
)
endif()
option(EXTRA_WARNINGS "Enable extra warnings" OFF)
if(EXTRA_WARNINGS)
add_cxx_compiler_flags(-Wsuggest-override)
else()
add_compiler_flags(-Wno-unused-parameter)
add_compiler_flags(-Wno-implicit-fallthrough)
endif()
# libtool style configure
add_subdirectory(config)
# Enable LFS (Large File Support) on targets that don't have it natively.
# This should be defined before the libraries are included as leveldb need the
# definition to be set.
if(NOT HAVE_LARGE_FILE_SUPPORT)
add_compile_definitions(_FILE_OFFSET_BITS=64)
add_linker_flags(-Wl,--large-address-aware)
endif()
if(ENABLE_GLIBC_BACK_COMPAT)
# Wrap some glibc functions with ours
add_linker_flags(-Wl,--wrap=__divmoddi4)
add_linker_flags(-Wl,--wrap=log2f)
add_linker_flags(-Wl,--wrap=exp)
add_linker_flags(-Wl,--wrap=log)
add_linker_flags(-Wl,--wrap=pow)
if(NOT HAVE_LARGE_FILE_SUPPORT)
add_linker_flags(-Wl,--wrap=fcntl -Wl,--wrap=fcntl64)
endif()
endif()
if(USE_JEMALLOC)
# Most of the sanitizers require their instrumented allocation functions to
# be fully functional. This is obviously the case for all the memory related
# sanitizers (asan, lsan, msan) but not only.
if(ENABLE_SANITIZERS)
message(WARNING "Jemalloc is incompatible with the sanitizers and has been disabled.")
else()
find_package(Jemalloc 3.6.0 REQUIRED)
link_libraries(Jemalloc::jemalloc)
endif()
endif()
# Make sure that all the global compiler and linker flags are set BEFORE
# including the libraries so they apply as needed.
# libraries
add_subdirectory(crypto)
add_subdirectory(leveldb)
add_subdirectory(secp256k1)
add_subdirectory(univalue)
# Find the git root, and returns the full path to the .git/logs/HEAD file if
# it exists.
function(find_git_head_logs_file RESULT)
find_package(Git)
if(GIT_FOUND)
execute_process(
COMMAND "${GIT_EXECUTABLE}" "rev-parse" "--show-toplevel"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_ROOT
RESULT_VARIABLE GIT_RESULT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(GIT_RESULT EQUAL 0)
set(GIT_LOGS_DIR "${GIT_ROOT}/.git/logs")
set(GIT_HEAD_LOGS_FILE "${GIT_LOGS_DIR}/HEAD")
# If the .git/logs/HEAD does not exist, create it
if(NOT EXISTS "${GIT_HEAD_LOGS_FILE}")
file(MAKE_DIRECTORY "${GIT_LOGS_DIR}")
file(TOUCH "${GIT_HEAD_LOGS_FILE}")
endif()
set(${RESULT} "${GIT_HEAD_LOGS_FILE}" PARENT_SCOPE)
endif()
endif()
endfunction()
find_git_head_logs_file(GIT_HEAD_LOGS_FILE)
set(OBJ_DIR "${CMAKE_CURRENT_BINARY_DIR}/obj")
file(MAKE_DIRECTORY "${OBJ_DIR}")
set(BUILD_HEADER "${OBJ_DIR}/build.h")
set(BUILD_HEADER_TMP "${BUILD_HEADER}.tmp")
add_custom_command(
DEPENDS
"${GIT_HEAD_LOGS_FILE}"
"${CMAKE_SOURCE_DIR}/share/genbuild.sh"
OUTPUT
"${BUILD_HEADER}"
COMMAND
"${CMAKE_SOURCE_DIR}/share/genbuild.sh"
"${BUILD_HEADER_TMP}"
"${CMAKE_SOURCE_DIR}"
COMMAND
${CMAKE_COMMAND} -E copy_if_different "${BUILD_HEADER_TMP}" "${BUILD_HEADER}"
COMMAND
${CMAKE_COMMAND} -E remove "${BUILD_HEADER_TMP}"
)
# Because the Bitcoin ABc source code is disorganised, we
# end up with a bunch of libraries without any apparent
# cohesive structure. This is inherited from Bitcoin Core
# and reflecting this.
# TODO: Improve the structure once cmake is rocking.
# Various completely unrelated features shared by all executables.
add_library(util
chainparamsbase.cpp
clientversion.cpp
compat/glibcxx_sanity.cpp
compat/strnlen.cpp
currencyunit.cpp
fs.cpp
interfaces/handler.cpp
logging.cpp
random.cpp
randomenv.cpp
rcu.cpp
rpc/request.cpp
support/cleanse.cpp
support/lockedpool.cpp
sync.cpp
threadinterrupt.cpp
uint256.cpp
util/asmap.cpp
util/bip32.cpp
util/bytevectorhash.cpp
util/hasher.cpp
util/error.cpp
util/getuniquepath.cpp
util/message.cpp
util/moneystr.cpp
util/readwritefile.cpp
util/settings.cpp
util/sock.cpp
util/spanparsing.cpp
util/strencodings.cpp
util/string.cpp
util/system.cpp
util/thread.cpp
util/threadnames.cpp
util/time.cpp
util/tokenpipe.cpp
util/url.cpp
# obj/build.h
"${BUILD_HEADER}"
)
target_compile_definitions(util PUBLIC HAVE_CONFIG_H HAVE_BUILD_INFO)
target_include_directories(util
PUBLIC
.
# To access the config/ and obj/ directories
${CMAKE_CURRENT_BINARY_DIR}
)
if(ENABLE_GLIBC_BACK_COMPAT)
target_sources(util PRIVATE compat/glibc_compat.cpp)
endif()
# Target specific configs
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON)
set(Boost_THREADAPI win32)
find_package(SHLWAPI REQUIRED)
target_link_libraries(util SHLWAPI::shlwapi)
find_library(WS2_32_LIBRARY NAMES ws2_32)
target_link_libraries(util ${WS2_32_LIBRARY})
target_compile_definitions(util PUBLIC BOOST_THREAD_USE_LIB)
endif()
target_link_libraries(util univalue crypto)
macro(link_event TARGET)
non_native_target_link_libraries(${TARGET} Event 2.0.22 ${ARGN})
endmacro()
link_event(util event)
macro(link_boost TARGET)
non_native_target_link_libraries(${TARGET} Boost 1.59 ${ARGN})
endmacro()
link_boost(util filesystem thread)
# Make sure boost uses std::atomic (it doesn't before 1.63)
target_compile_definitions(util PUBLIC BOOST_SP_USE_STD_ATOMIC BOOST_AC_USE_STD_ATOMIC)
function(add_network_sources NETWORK_SOURCES)
set(NETWORK_DIR abc)
list(TRANSFORM
ARGN
PREPEND "networks/${NETWORK_DIR}/"
OUTPUT_VARIABLE NETWORK_SOURCES
)
set(NETWORK_SOURCES ${NETWORK_SOURCES} PARENT_SCOPE)
endfunction()
add_network_sources(NETWORK_SOURCES
checkpoints.cpp
chainparamsconstants.cpp
)
# More completely unrelated features shared by all executables.
# Because nothing says this is different from util than "common"
add_library(common
base58.cpp
bloom.cpp
cashaddr.cpp
cashaddrenc.cpp
chainparams.cpp
config.cpp
consensus/merkle.cpp
coins.cpp
compressor.cpp
deploymentinfo.cpp
eventloop.cpp
feerate.cpp
core_read.cpp
core_write.cpp
key.cpp
key_io.cpp
merkleblock.cpp
net_permissions.cpp
netaddress.cpp
netbase.cpp
outputtype.cpp
policy/policy.cpp
primitives/block.cpp
protocol.cpp
psbt.cpp
rpc/rawtransaction_util.cpp
rpc/util.cpp
scheduler.cpp
warnings.cpp
${NETWORK_SOURCES}
)
target_link_libraries(common bitcoinconsensus util secp256k1 script)
# script library
add_library(script
script/bitfield.cpp
script/descriptor.cpp
script/interpreter.cpp
script/script.cpp
script/script_error.cpp
script/sigencoding.cpp
script/sign.cpp
script/signingprovider.cpp
script/standard.cpp
)
target_link_libraries(script common)
# libbitcoinconsensus
add_library(bitcoinconsensus
arith_uint256.cpp
hash.cpp
primitives/transaction.cpp
pubkey.cpp
uint256.cpp
util/strencodings.cpp
consensus/amount.cpp
consensus/tx_check.cpp
)
target_link_libraries(bitcoinconsensus script)
include(InstallationHelper)
if(BUILD_LIBBITCOINCONSENSUS)
target_compile_definitions(bitcoinconsensus
PUBLIC
BUILD_BITCOIN_INTERNAL
HAVE_CONSENSUS_LIB
)
install_shared_library(bitcoinconsensus
script/bitcoinconsensus.cpp
PUBLIC_HEADER script/bitcoinconsensus.h
)
endif()
# Bitcoin server facilities
add_library(server
addrdb.cpp
addrman.cpp
avalanche/avalanche.cpp
avalanche/compactproofs.cpp
avalanche/delegation.cpp
avalanche/delegationbuilder.cpp
avalanche/peermanager.cpp
avalanche/processor.cpp
avalanche/proof.cpp
avalanche/proofid.cpp
avalanche/proofbuilder.cpp
avalanche/proofpool.cpp
avalanche/voterecord.cpp
banman.cpp
blockencodings.cpp
blockfilter.cpp
blockindex.cpp
chain.cpp
checkpoints.cpp
config.cpp
consensus/activation.cpp
consensus/tx_verify.cpp
dbwrapper.cpp
deploymentstatus.cpp
dnsseeds.cpp
flatfile.cpp
httprpc.cpp
httpserver.cpp
i2p.cpp
index/base.cpp
index/blockfilterindex.cpp
index/coinstatsindex.cpp
index/txindex.cpp
init.cpp
init/common.cpp
interfaces/chain.cpp
interfaces/node.cpp
invrequest.cpp
mapport.cpp
miner.cpp
minerfund.cpp
net.cpp
net_processing.cpp
node/blockstorage.cpp
node/caches.cpp
node/chainstate.cpp
node/coin.cpp
node/coinstats.cpp
node/context.cpp
node/psbt.cpp
node/transaction.cpp
node/ui_interface.cpp
noui.cpp
policy/fees.cpp
policy/packages.cpp
policy/settings.cpp
pow/aserti32d.cpp
pow/daa.cpp
pow/eda.cpp
pow/grasberg.cpp
pow/pow.cpp
rest.cpp
rpc/abc.cpp
rpc/avalanche.cpp
rpc/blockchain.cpp
rpc/command.cpp
rpc/mining.cpp
rpc/misc.cpp
rpc/net.cpp
rpc/rawtransaction.cpp
rpc/server.cpp
+ rpc/server_util.cpp
script/scriptcache.cpp
script/sigcache.cpp
shutdown.cpp
timedata.cpp
torcontrol.cpp
txdb.cpp
txmempool.cpp
txorphanage.cpp
validation.cpp
validationinterface.cpp
versionbits.cpp
)
target_include_directories(server PRIVATE leveldb/helpers/memenv)
target_link_libraries(server
bitcoinconsensus
leveldb
memenv
)
link_event(server event)
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Windows")
link_event(server pthreads)
endif()
if(ENABLE_UPNP)
find_package(MiniUPnPc 1.9 REQUIRED)
target_link_libraries(server MiniUPnPc::miniupnpc)
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
# TODO: check if we are really using a static library. Assume this is
# the one from the depends for now since the native windows build is not
# supported.
target_compile_definitions(server
PUBLIC -DSTATICLIB
PUBLIC -DMINIUPNP_STATICLIB
)
endif()
endif()
if(ENABLE_NATPMP)
find_package(NATPMP REQUIRED)
target_link_libraries(server NATPMP::natpmp)
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
target_compile_definitions(server
PUBLIC -DSTATICLIB
PUBLIC -DNATPMP_STATICLIB
)
endif()
endif()
# Test suites.
add_subdirectory(test)
add_subdirectory(avalanche/test)
add_subdirectory(pow/test)
# Benchmark suite.
add_subdirectory(bench)
include(BinaryTest)
include(WindowsVersionInfo)
# Wallet
if(BUILD_BITCOIN_WALLET)
add_subdirectory(wallet)
target_link_libraries(server wallet)
# bitcoin-wallet
add_executable(bitcoin-wallet bitcoin-wallet.cpp)
generate_windows_version_info(bitcoin-wallet
DESCRIPTION "CLI tool for ${PACKAGE_NAME} wallets"
)
target_link_libraries(bitcoin-wallet wallet-tool common util)
add_to_symbols_check(bitcoin-wallet)
add_to_security_check(bitcoin-wallet)
install_target(bitcoin-wallet)
install_manpages(bitcoin-wallet)
else()
target_sources(server PRIVATE dummywallet.cpp)
endif()
# ZeroMQ
if(BUILD_BITCOIN_ZMQ)
add_subdirectory(zmq)
target_link_libraries(server zmq)
# FIXME: This is needed because of an unwanted dependency:
# zmqpublishnotifier.cpp -> blockstorage.h -> txdb.h -> dbwrapper.h -> leveldb/db.h
target_link_libraries(zmq leveldb)
endif()
# RPC client support
add_library(rpcclient
compat/stdin.cpp
rpc/client.cpp
)
target_link_libraries(rpcclient univalue util)
# bitcoin-seeder
if(BUILD_BITCOIN_SEEDER)
add_subdirectory(seeder)
endif()
# bitcoin-cli
if(BUILD_BITCOIN_CLI)
add_executable(bitcoin-cli bitcoin-cli.cpp)
generate_windows_version_info(bitcoin-cli
DESCRIPTION "JSON-RPC client for ${PACKAGE_NAME}"
)
target_link_libraries(bitcoin-cli common rpcclient)
link_event(bitcoin-cli event)
add_to_symbols_check(bitcoin-cli)
add_to_security_check(bitcoin-cli)
install_target(bitcoin-cli)
install_manpages(bitcoin-cli)
endif()
# bitcoin-tx
if(BUILD_BITCOIN_TX)
add_executable(bitcoin-tx bitcoin-tx.cpp)
generate_windows_version_info(bitcoin-tx
DESCRIPTION "CLI Bitcoin transaction editor utility"
)
target_link_libraries(bitcoin-tx bitcoinconsensus)
add_to_symbols_check(bitcoin-tx)
add_to_security_check(bitcoin-tx)
install_target(bitcoin-tx)
install_manpages(bitcoin-tx)
endif()
# bitcoind
add_executable(bitcoind bitcoind.cpp)
target_link_libraries(bitcoind server)
generate_windows_version_info(bitcoind
DESCRIPTION "Bitcoin node with a JSON-RPC server"
)
add_to_symbols_check(bitcoind)
add_to_security_check(bitcoind)
install_target(bitcoind)
install_manpages(bitcoind)
# Bitcoin-qt
if(BUILD_BITCOIN_QT)
add_subdirectory(qt)
endif()
diff --git a/src/rest.cpp b/src/rest.cpp
index a5ee78c45..56951956b 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -1,848 +1,849 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chain.h>
#include <chainparams.h>
#include <config.h>
#include <core_io.h>
#include <httpserver.h>
#include <index/txindex.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <streams.h>
#include <sync.h>
#include <txmempool.h>
#include <util/system.h>
#include <validation.h>
#include <version.h>
#include <boost/algorithm/string.hpp>
#include <univalue.h>
#include <any>
// Allow a max of 15 outpoints to be queried at once.
static const size_t MAX_GETUTXOS_OUTPOINTS = 15;
enum class RetFormat {
UNDEF,
BINARY,
HEX,
JSON,
};
static const struct {
RetFormat rf;
const char *name;
} rf_names[] = {
{RetFormat::UNDEF, ""},
{RetFormat::BINARY, "bin"},
{RetFormat::HEX, "hex"},
{RetFormat::JSON, "json"},
};
struct CCoin {
uint32_t nHeight;
CTxOut out;
CCoin() : nHeight(0) {}
explicit CCoin(Coin in)
: nHeight(in.GetHeight()), out(std::move(in.GetTxOut())) {}
SERIALIZE_METHODS(CCoin, obj) {
uint32_t nTxVerDummy = 0;
READWRITE(nTxVerDummy, obj.nHeight, obj.out);
}
};
static bool RESTERR(HTTPRequest *req, enum HTTPStatusCode status,
std::string message) {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(status, message + "\r\n");
return false;
}
/**
* Get the node context.
*
* @param[in] req The HTTP request, whose status code will be set if node
* context is not found.
* @returns Pointer to the node context or nullptr if not found.
*/
static NodeContext *GetNodeContext(const std::any &context, HTTPRequest *req) {
auto node_context = util::AnyPtr<NodeContext>(context);
if (!node_context) {
RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
strprintf("%s:%d (%s)\n"
"Internal bug detected: Node context not found!\n"
"You may report this issue here: %s\n",
__FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
return nullptr;
}
return node_context;
}
/**
* Get the node context mempool.
*
* @param[in] req The HTTP request, whose status code will be set if node
* context mempool is not found.
* @returns Pointer to the mempool or nullptr if no mempool found.
*/
static CTxMemPool *GetMemPool(const std::any &context, HTTPRequest *req) {
auto node_context = util::AnyPtr<NodeContext>(context);
if (!node_context || !node_context->mempool) {
RESTERR(req, HTTP_NOT_FOUND, "Mempool disabled or instance not found");
return nullptr;
}
return node_context->mempool.get();
}
/**
* Get the node context chainstatemanager.
*
* @param[in] req The HTTP request, whose status code will be set if node
* context chainstatemanager is not found.
* @returns Pointer to the chainstatemanager or nullptr if none found.
*/
static ChainstateManager *GetChainman(const std::any &context,
HTTPRequest *req) {
auto node_context = util::AnyPtr<NodeContext>(context);
if (!node_context || !node_context->chainman) {
RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
strprintf("%s:%d (%s)\n"
"Internal bug detected: Chainman disabled or instance"
" not found!\n"
"You may report this issue here: %s\n",
__FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
return nullptr;
}
return node_context->chainman.get();
}
static RetFormat ParseDataFormat(std::string ¶m,
const std::string &strReq) {
const std::string::size_type pos = strReq.rfind('.');
if (pos == std::string::npos) {
param = strReq;
return rf_names[0].rf;
}
param = strReq.substr(0, pos);
const std::string suff(strReq, pos + 1);
for (const auto &rf_name : rf_names) {
if (suff == rf_name.name) {
return rf_name.rf;
}
}
/* If no suffix is found, return original string. */
param = strReq;
return rf_names[0].rf;
}
static std::string AvailableDataFormatsString() {
std::string formats;
for (const auto &rf_name : rf_names) {
if (strlen(rf_name.name) > 0) {
formats.append(".");
formats.append(rf_name.name);
formats.append(", ");
}
}
if (formats.length() > 0) {
return formats.substr(0, formats.length() - 2);
}
return formats;
}
static bool CheckWarmup(HTTPRequest *req) {
std::string statusmessage;
if (RPCIsInWarmup(&statusmessage)) {
return RESTERR(req, HTTP_SERVICE_UNAVAILABLE,
"Service temporarily unavailable: " + statusmessage);
}
return true;
}
static bool rest_headers(Config &config, const std::any &context,
HTTPRequest *req, const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> path;
boost::split(path, param, boost::is_any_of("/"));
if (path.size() != 2) {
return RESTERR(req, HTTP_BAD_REQUEST,
"No header count specified. Use "
"/rest/headers/<count>/<hash>.<ext>.");
}
long count = strtol(path[0].c_str(), nullptr, 10);
if (count < 1 || count > 2000) {
return RESTERR(req, HTTP_BAD_REQUEST,
"Header count out of range: " + path[0]);
}
std::string hashStr = path[1];
uint256 rawHash;
if (!ParseHashStr(hashStr, rawHash)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
const BlockHash hash(rawHash);
const CBlockIndex *tip = nullptr;
std::vector<const CBlockIndex *> headers;
headers.reserve(count);
{
ChainstateManager *maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) {
return false;
}
ChainstateManager &chainman = *maybe_chainman;
LOCK(cs_main);
CChain &active_chain = chainman.ActiveChain();
tip = active_chain.Tip();
const CBlockIndex *pindex = chainman.m_blockman.LookupBlockIndex(hash);
while (pindex != nullptr && active_chain.Contains(pindex)) {
headers.push_back(pindex);
if (headers.size() == size_t(count)) {
break;
}
pindex = active_chain.Next(pindex);
}
}
switch (rf) {
case RetFormat::BINARY: {
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
}
std::string binaryHeader = ssHeader.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryHeader);
return true;
}
case RetFormat::HEX: {
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
}
std::string strHex = HexStr(ssHeader) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue jsonHeaders(UniValue::VARR);
for (const CBlockIndex *pindex : headers) {
jsonHeaders.push_back(blockheaderToJSON(tip, pindex));
}
std::string strJSON = jsonHeaders.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(
req, HTTP_NOT_FOUND,
"output format not found (available: .bin, .hex, .json)");
}
}
}
static bool rest_block(const Config &config, const std::any &context,
HTTPRequest *req, const std::string &strURIPart,
bool showTxDetails) {
if (!CheckWarmup(req)) {
return false;
}
std::string hashStr;
const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 rawHash;
if (!ParseHashStr(hashStr, rawHash)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
const BlockHash hash(rawHash);
CBlock block;
CBlockIndex *pblockindex = nullptr;
CBlockIndex *tip = nullptr;
{
ChainstateManager *maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) {
return false;
}
ChainstateManager &chainman = *maybe_chainman;
LOCK(cs_main);
tip = chainman.ActiveTip();
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
if (IsBlockPruned(pblockindex)) {
return RESTERR(req, HTTP_NOT_FOUND,
hashStr + " not available (pruned data)");
}
if (!ReadBlockFromDisk(block, pblockindex,
config.GetChainParams().GetConsensus())) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
}
switch (rf) {
case RetFormat::BINARY: {
CDataStream ssBlock(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string binaryBlock = ssBlock.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryBlock);
return true;
}
case RetFormat::HEX: {
CDataStream ssBlock(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string strHex = HexStr(ssBlock) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objBlock =
blockToJSON(block, tip, pblockindex, showTxDetails);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
}
static bool rest_block_extended(Config &config, const std::any &context,
HTTPRequest *req,
const std::string &strURIPart) {
return rest_block(config, context, req, strURIPart, true);
}
static bool rest_block_notxdetails(Config &config, const std::any &context,
HTTPRequest *req,
const std::string &strURIPart) {
return rest_block(config, context, req, strURIPart, false);
}
static bool rest_chaininfo(Config &config, const std::any &context,
HTTPRequest *req, const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
JSONRPCRequest jsonRequest;
jsonRequest.context = context;
jsonRequest.params = UniValue(UniValue::VARR);
UniValue chainInfoObject =
getblockchaininfo().HandleRequest(config, jsonRequest);
std::string strJSON = chainInfoObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: json)");
}
}
}
static bool rest_mempool_info(Config &config, const std::any &context,
HTTPRequest *req, const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
const CTxMemPool *mempool = GetMemPool(context, req);
if (!mempool) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool);
std::string strJSON = mempoolInfoObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: json)");
}
}
}
static bool rest_mempool_contents(Config &config, const std::any &context,
HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
const CTxMemPool *mempool = GetMemPool(context, req);
if (!mempool) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
UniValue mempoolObject = MempoolToJSON(*mempool, true);
std::string strJSON = mempoolObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: json)");
}
}
}
static bool rest_tx(Config &config, const std::any &context, HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string hashStr;
const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash;
if (!ParseHashStr(hashStr, hash)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
const TxId txid(hash);
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
const NodeContext *const node = GetNodeContext(context, req);
if (!node) {
return false;
}
BlockHash hashBlock;
const CTransactionRef tx =
GetTransaction(/* block_index */ nullptr, node->mempool.get(), txid,
Params().GetConsensus(), hashBlock);
if (!tx) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
switch (rf) {
case RetFormat::BINARY: {
CDataStream ssTx(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
std::string binaryTx = ssTx.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryTx);
return true;
}
case RetFormat::HEX: {
CDataStream ssTx(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
std::string strHex = HexStr(ssTx) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, hashBlock, objTx);
std::string strJSON = objTx.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
}
static bool rest_getutxos(Config &config, const std::any &context,
HTTPRequest *req, const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> uriParts;
if (param.length() > 1) {
std::string strUriParams = param.substr(1);
boost::split(uriParts, strUriParams, boost::is_any_of("/"));
}
// throw exception in case of an empty request
std::string strRequestMutable = req->ReadBody();
if (strRequestMutable.length() == 0 && uriParts.size() == 0) {
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
}
bool fInputParsed = false;
bool fCheckMemPool = false;
std::vector<COutPoint> vOutPoints;
// parse/deserialize input
// input-format = output-format, rest/getutxos/bin requires binary input,
// gives binary output, ...
if (uriParts.size() > 0) {
// inputs is sent over URI scheme
// (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
if (uriParts[0] == "checkmempool") {
fCheckMemPool = true;
}
for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++) {
int32_t nOutput;
std::string strTxid = uriParts[i].substr(0, uriParts[i].find('-'));
std::string strOutput =
uriParts[i].substr(uriParts[i].find('-') + 1);
if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
}
TxId txid;
txid.SetHex(strTxid);
vOutPoints.push_back(COutPoint(txid, uint32_t(nOutput)));
}
if (vOutPoints.size() > 0) {
fInputParsed = true;
} else {
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
}
}
switch (rf) {
case RetFormat::HEX: {
// convert hex to bin, continue then with bin part
std::vector<uint8_t> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
}
// FALLTHROUGH
case RetFormat::BINARY: {
try {
// deserialize only if user sent a request
if (strRequestMutable.size() > 0) {
// don't allow sending input over URI and HTTP RAW DATA
if (fInputParsed) {
return RESTERR(req, HTTP_BAD_REQUEST,
"Combination of URI scheme inputs and "
"raw post data is not allowed");
}
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
oss << strRequestMutable;
oss >> fCheckMemPool;
oss >> vOutPoints;
}
} catch (const std::ios_base::failure &) {
// abort in case of unreadable binary data
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
}
break;
}
case RetFormat::JSON: {
if (!fInputParsed) {
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
}
break;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
// limit max outpoints
if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS) {
return RESTERR(
req, HTTP_BAD_REQUEST,
strprintf("Error: max outpoints exceeded (max: %d, tried: %d)",
MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
}
// check spentness and form a bitmap (as well as a JSON capable
// human-readable string representation)
std::vector<uint8_t> bitmap;
std::vector<CCoin> outs;
std::string bitmapStringRepresentation;
std::vector<bool> hits;
bitmap.resize((vOutPoints.size() + 7) / 8);
ChainstateManager *maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) {
return false;
}
ChainstateManager &chainman = *maybe_chainman;
{
auto process_utxos = [&vOutPoints, &outs,
&hits](const CCoinsView &view,
const CTxMemPool &mempool) {
for (const COutPoint &vOutPoint : vOutPoints) {
Coin coin;
bool hit = !mempool.isSpent(vOutPoint) &&
view.GetCoin(vOutPoint, coin);
hits.push_back(hit);
if (hit) {
outs.emplace_back(std::move(coin));
}
}
};
if (fCheckMemPool) {
const CTxMemPool *mempool = GetMemPool(context, req);
if (!mempool) {
return false;
}
// use db+mempool as cache backend in case user likes to query
// mempool
LOCK2(cs_main, mempool->cs);
CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, *mempool);
process_utxos(viewMempool, *mempool);
} else {
// no need to lock mempool!
LOCK(cs_main);
process_utxos(chainman.ActiveChainstate().CoinsTip(), CTxMemPool());
}
for (size_t i = 0; i < hits.size(); ++i) {
const bool hit = hits[i];
// form a binary string representation (human-readable for json
// output)
bitmapStringRepresentation.append(hit ? "1" : "0");
bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
}
}
switch (rf) {
case RetFormat::BINARY: {
// serialize data
// use exact same output as mentioned in Bip64
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainman.ActiveHeight()
<< chainman.ActiveTip()->GetBlockHash() << bitmap
<< outs;
std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
return true;
}
case RetFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainman.ActiveHeight()
<< chainman.ActiveTip()->GetBlockHash() << bitmap
<< outs;
std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objGetUTXOResponse(UniValue::VOBJ);
// pack in some essentials
// use more or less the same output as mentioned in Bip64
objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveHeight());
objGetUTXOResponse.pushKV(
"chaintipHash", chainman.ActiveTip()->GetBlockHash().GetHex());
objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
UniValue utxos(UniValue::VARR);
for (const CCoin &coin : outs) {
UniValue utxo(UniValue::VOBJ);
utxo.pushKV("height", int32_t(coin.nHeight));
utxo.pushKV("value", coin.out.nValue);
// include the script in a json output
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
utxo.pushKV("scriptPubKey", o);
utxos.push_back(utxo);
}
objGetUTXOResponse.pushKV("utxos", utxos);
// return json string
std::string strJSON = objGetUTXOResponse.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
}
static bool rest_blockhash_by_height(Config &config, const std::any &context,
HTTPRequest *req,
const std::string &str_uri_part) {
if (!CheckWarmup(req)) {
return false;
}
std::string height_str;
const RetFormat rf = ParseDataFormat(height_str, str_uri_part);
int32_t blockheight;
if (!ParseInt32(height_str, &blockheight) || blockheight < 0) {
return RESTERR(req, HTTP_BAD_REQUEST,
"Invalid height: " + SanitizeString(height_str));
}
CBlockIndex *pblockindex = nullptr;
{
ChainstateManager *maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) {
return false;
}
ChainstateManager &chainman = *maybe_chainman;
LOCK(cs_main);
const CChain &active_chain = chainman.ActiveChain();
if (blockheight > active_chain.Height()) {
return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range");
}
pblockindex = active_chain[blockheight];
}
switch (rf) {
case RetFormat::BINARY: {
CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION);
ss_blockhash << pblockindex->GetBlockHash();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, ss_blockhash.str());
return true;
}
case RetFormat::HEX: {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK,
pblockindex->GetBlockHash().GetHex() + "\n");
return true;
}
case RetFormat::JSON: {
req->WriteHeader("Content-Type", "application/json");
UniValue resp = UniValue(UniValue::VOBJ);
resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());
req->WriteReply(HTTP_OK, resp.write() + "\n");
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
}
static const struct {
const char *prefix;
bool (*handler)(Config &config, const std::any &context, HTTPRequest *req,
const std::string &strReq);
} uri_prefixes[] = {
{"/rest/tx/", rest_tx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
{"/rest/block/", rest_block_extended},
{"/rest/chaininfo", rest_chaininfo},
{"/rest/mempool/info", rest_mempool_info},
{"/rest/mempool/contents", rest_mempool_contents},
{"/rest/headers/", rest_headers},
{"/rest/getutxos", rest_getutxos},
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
};
void StartREST(const std::any &context) {
for (const auto &up : uri_prefixes) {
auto handler = [context, up](Config &config, HTTPRequest *req,
const std::string &prefix) {
return up.handler(config, context, req, prefix);
};
RegisterHTTPHandler(up.prefix, false, handler);
}
}
void InterruptREST() {}
void StopREST() {
for (const auto &up : uri_prefixes) {
UnregisterHTTPHandler(up.prefix, false);
}
}
diff --git a/src/rpc/avalanche.cpp b/src/rpc/avalanche.cpp
index cca4b8e59..3b86b3938 100644
--- a/src/rpc/avalanche.cpp
+++ b/src/rpc/avalanche.cpp
@@ -1,1336 +1,1337 @@
// Copyright (c) 2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <avalanche/avalanche.h>
#include <avalanche/delegation.h>
#include <avalanche/delegationbuilder.h>
#include <avalanche/peermanager.h>
#include <avalanche/processor.h>
#include <avalanche/proof.h>
#include <avalanche/proofbuilder.h>
#include <avalanche/validation.h>
#include <config.h>
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
#include <net_processing.h>
#include <node/context.h>
#include <rpc/blockchain.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <univalue.h>
static RPCHelpMan getavalanchekey() {
return RPCHelpMan{
"getavalanchekey",
"Returns the key used to sign avalanche messages.\n",
{},
RPCResult{RPCResult::Type::STR_HEX, "", ""},
RPCExamples{HelpExampleRpc("getavalanchekey", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
return HexStr(g_avalanche->getSessionPubKey());
},
};
}
static CPubKey ParsePubKey(const UniValue ¶m) {
const std::string keyHex = param.get_str();
if ((keyHex.length() != 2 * CPubKey::COMPRESSED_SIZE &&
keyHex.length() != 2 * CPubKey::SIZE) ||
!IsHex(keyHex)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Invalid public key: %s\n", keyHex));
}
return HexToPubKey(keyHex);
}
static bool registerProofIfNeeded(avalanche::ProofRef proof,
avalanche::ProofRegistrationState &state) {
auto localProof = g_avalanche->getLocalProof();
if (localProof && localProof->getId() == proof->getId()) {
return true;
}
return g_avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
return pm.getProof(proof->getId()) ||
pm.registerProof(std::move(proof), state);
});
}
static bool registerProofIfNeeded(avalanche::ProofRef proof) {
avalanche::ProofRegistrationState state;
return registerProofIfNeeded(std::move(proof), state);
}
static void verifyDelegationOrThrow(avalanche::Delegation &dg,
const std::string &dgHex, CPubKey &auth) {
bilingual_str error;
if (!avalanche::Delegation::FromHex(dg, dgHex, error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, error.original);
}
avalanche::DelegationState state;
if (!dg.verify(state, auth)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"The delegation is invalid: " + state.ToString());
}
}
static void verifyProofOrThrow(const NodeContext &node, avalanche::Proof &proof,
const std::string &proofHex) {
bilingual_str error;
if (!avalanche::Proof::FromHex(proof, proofHex, error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, error.original);
}
Amount stakeUtxoDustThreshold = avalanche::PROOF_DUST_THRESHOLD;
if (g_avalanche) {
// If Avalanche is enabled, use the configured dust threshold
g_avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
stakeUtxoDustThreshold = pm.getStakeUtxoDustThreshold();
});
}
avalanche::ProofValidationState state;
{
LOCK(cs_main);
if (!proof.verify(stakeUtxoDustThreshold, *Assert(node.chainman),
state)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"The proof is invalid: " + state.ToString());
}
}
}
static RPCHelpMan addavalanchenode() {
return RPCHelpMan{
"addavalanchenode",
"Add a node in the set of peers to poll for avalanche.\n",
{
{"nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO,
"Node to be added to avalanche."},
{"publickey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The public key of the node."},
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"Proof that the node is not a sybil."},
{"delegation", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"The proof delegation the the node public key"},
},
RPCResult{RPCResult::Type::BOOL, "success",
"Whether the addition succeeded or not."},
RPCExamples{
HelpExampleRpc("addavalanchenode", "5, \"<pubkey>\", \"<proof>\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params,
{UniValue::VNUM, UniValue::VSTR, UniValue::VSTR});
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
const NodeId nodeid = request.params[0].get_int64();
CPubKey key = ParsePubKey(request.params[1]);
auto proof = RCUPtr<avalanche::Proof>::make();
NodeContext &node = EnsureAnyNodeContext(request.context);
verifyProofOrThrow(node, *proof, request.params[2].get_str());
const avalanche::ProofId &proofid = proof->getId();
if (key != proof->getMaster()) {
if (request.params.size() < 4 || request.params[3].isNull()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"The public key does not match the proof");
}
avalanche::Delegation dg;
CPubKey auth;
verifyDelegationOrThrow(dg, request.params[3].get_str(), auth);
if (dg.getProofId() != proofid) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"The delegation does not match the proof");
}
if (key != auth) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"The public key does not match the delegation");
}
}
if (!registerProofIfNeeded(proof)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"The proof has conflicting utxos");
}
if (!node.connman->ForNode(nodeid, [&](CNode *pnode) {
bool expected = false;
if (pnode->m_avalanche_enabled.compare_exchange_strong(
expected, true)) {
pnode->m_avalanche_pubkey = std::move(key);
}
return true;
})) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("The node does not exist: %d", nodeid));
;
}
return g_avalanche->withPeerManager(
[&](avalanche::PeerManager &pm) {
if (!pm.addNode(nodeid, proofid)) {
return false;
}
pm.addUnbroadcastProof(proofid);
return true;
});
},
};
}
static RPCHelpMan buildavalancheproof() {
return RPCHelpMan{
"buildavalancheproof",
"Build a proof for avalanche's sybil resistance.\n",
{
{"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The proof's sequence"},
{"expiration", RPCArg::Type::NUM, RPCArg::Optional::NO,
"A timestamp indicating when the proof expire"},
{"master", RPCArg::Type::STR, RPCArg::Optional::NO,
"The master private key in base58-encoding"},
{
"stakes",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The stakes to be signed and associated private keys",
{
{
"stake",
RPCArg::Type::OBJ,
RPCArg::Optional::NO,
"A stake to be attached to this proof",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"amount", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO, "The amount in this UTXO"},
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The height at which this UTXO was mined"},
{"iscoinbase", RPCArg::Type::BOOL,
/* default */ "false",
"Indicate wether the UTXO is a coinbase"},
{"privatekey", RPCArg::Type::STR,
RPCArg::Optional::NO,
"private key in base58-encoding"},
},
},
},
},
{"payoutAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
"A payout address"},
},
RPCResult{RPCResult::Type::STR_HEX, "proof",
"A string that is a serialized, hex-encoded proof data."},
RPCExamples{HelpExampleRpc("buildavalancheproof",
"0 1234567800 \"<master>\" []")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM,
UniValue::VSTR, UniValue::VARR});
const uint64_t sequence = request.params[0].get_int64();
const int64_t expiration = request.params[1].get_int64();
CKey masterKey = DecodeSecret(request.params[2].get_str());
if (!masterKey.IsValid()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid master key");
}
CTxDestination payoutAddress = DecodeDestination(
request.params[4].get_str(), config.GetChainParams());
if (!IsValidDestination(payoutAddress)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid payout address");
}
avalanche::ProofBuilder pb(sequence, expiration, masterKey,
GetScriptForDestination(payoutAddress));
const UniValue &stakes = request.params[3].get_array();
for (size_t i = 0; i < stakes.size(); i++) {
const UniValue &stake = stakes[i];
RPCTypeCheckObj(
stake,
{
{"txid", UniValue::VSTR},
{"vout", UniValue::VNUM},
// "amount" is also required but check is done below
// due to UniValue::VNUM erroneously not accepting
// quoted numerics (which are valid JSON)
{"height", UniValue::VNUM},
{"privatekey", UniValue::VSTR},
});
int nOut = find_value(stake, "vout").get_int();
if (nOut < 0) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"vout cannot be negative");
}
const int height = find_value(stake, "height").get_int();
if (height < 1) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"height must be positive");
}
const TxId txid(ParseHashO(stake, "txid"));
const COutPoint utxo(txid, nOut);
if (!stake.exists("amount")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing amount");
}
const Amount amount =
AmountFromValue(find_value(stake, "amount"));
const UniValue &iscbparam = find_value(stake, "iscoinbase");
const bool iscoinbase =
iscbparam.isNull() ? false : iscbparam.get_bool();
CKey key =
DecodeSecret(find_value(stake, "privatekey").get_str());
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid private key");
}
if (!pb.addUTXO(utxo, amount, uint32_t(height), iscoinbase,
std::move(key))) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Duplicated stake");
}
}
const avalanche::ProofRef proof = pb.build();
return proof->ToHex();
},
};
}
static RPCHelpMan decodeavalancheproof() {
return RPCHelpMan{
"decodeavalancheproof",
"Convert a serialized, hex-encoded proof, into JSON object. "
"The validity of the proof is not verified.\n",
{
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The proof hex string"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "sequence",
"The proof's sequential number"},
{RPCResult::Type::NUM, "expiration",
"A timestamp indicating when the proof expires"},
{RPCResult::Type::STR_HEX, "master", "The master public key"},
{RPCResult::Type::STR, "signature",
"The proof signature (base64 encoded)"},
{RPCResult::Type::OBJ,
"payoutscript",
"The proof payout script",
{
{RPCResult::Type::STR, "asm", "Decoded payout script"},
{RPCResult::Type::STR_HEX, "hex",
"Raw payout script in hex format"},
{RPCResult::Type::STR, "type",
"The output type (e.g. " + GetAllOutputTypes() + ")"},
{RPCResult::Type::NUM, "reqSigs",
"The required signatures"},
{RPCResult::Type::ARR,
"addresses",
"",
{
{RPCResult::Type::STR, "address", "eCash address"},
}},
}},
{RPCResult::Type::STR_HEX, "limitedid",
"A hash of the proof data excluding the master key."},
{RPCResult::Type::STR_HEX, "proofid",
"A hash of the limitedid and master key."},
{RPCResult::Type::STR_AMOUNT, "staked_amount",
"The total staked amount of this proof in " +
Currency::get().ticker + "."},
{RPCResult::Type::NUM, "score", "The score of this proof."},
{RPCResult::Type::ARR,
"stakes",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::NUM, "vout", "The output number"},
{RPCResult::Type::STR_AMOUNT, "amount",
"The amount in this UTXO"},
{RPCResult::Type::NUM, "height",
"The height at which this UTXO was mined"},
{RPCResult::Type::BOOL, "iscoinbase",
"Indicate whether the UTXO is a coinbase"},
{RPCResult::Type::STR_HEX, "pubkey",
"This UTXO's public key"},
{RPCResult::Type::STR, "signature",
"Signature of the proofid with this UTXO's private "
"key (base64 encoded)"},
}},
}},
}},
RPCExamples{HelpExampleCli("decodeavalancheproof", "\"<hex proof>\"") +
HelpExampleRpc("decodeavalancheproof", "\"<hex proof>\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
avalanche::Proof proof;
bilingual_str error;
if (!avalanche::Proof::FromHex(proof, request.params[0].get_str(),
error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, error.original);
}
UniValue result(UniValue::VOBJ);
result.pushKV("sequence", proof.getSequence());
result.pushKV("expiration", proof.getExpirationTime());
result.pushKV("master", HexStr(proof.getMaster()));
result.pushKV("signature", EncodeBase64(proof.getSignature()));
const auto payoutScript = proof.getPayoutScript();
UniValue payoutScriptObj(UniValue::VOBJ);
ScriptPubKeyToUniv(payoutScript, payoutScriptObj,
/* fIncludeHex */ true);
result.pushKV("payoutscript", payoutScriptObj);
result.pushKV("limitedid", proof.getLimitedId().ToString());
result.pushKV("proofid", proof.getId().ToString());
result.pushKV("staked_amount", proof.getStakedAmount());
result.pushKV("score", uint64_t(proof.getScore()));
UniValue stakes(UniValue::VARR);
for (const avalanche::SignedStake &s : proof.getStakes()) {
const COutPoint &utxo = s.getStake().getUTXO();
UniValue stake(UniValue::VOBJ);
stake.pushKV("txid", utxo.GetTxId().ToString());
stake.pushKV("vout", uint64_t(utxo.GetN()));
stake.pushKV("amount", s.getStake().getAmount());
stake.pushKV("height", uint64_t(s.getStake().getHeight()));
stake.pushKV("iscoinbase", s.getStake().isCoinbase());
stake.pushKV("pubkey", HexStr(s.getStake().getPubkey()));
// Only PKHash destination is supported, so this is safe
stake.pushKV("address",
EncodeDestination(PKHash(s.getStake().getPubkey()),
config));
stake.pushKV("signature", EncodeBase64(s.getSignature()));
stakes.push_back(stake);
}
result.pushKV("stakes", stakes);
return result;
},
};
}
static RPCHelpMan delegateavalancheproof() {
return RPCHelpMan{
"delegateavalancheproof",
"Delegate the avalanche proof to another public key.\n",
{
{"limitedproofid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The limited id of the proof to be delegated."},
{"privatekey", RPCArg::Type::STR, RPCArg::Optional::NO,
"The private key in base58-encoding. Must match the proof master "
"public key or the upper level parent delegation public key if "
" supplied."},
{"publickey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The public key to delegate the proof to."},
{"delegation", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"A string that is the serialized, hex-encoded delegation for the "
"proof and which is a parent for the delegation to build."},
},
RPCResult{RPCResult::Type::STR_HEX, "delegation",
"A string that is a serialized, hex-encoded delegation."},
RPCExamples{
HelpExampleRpc("delegateavalancheproof",
"\"<limitedproofid>\" \"<privkey>\" \"<pubkey>\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params,
{UniValue::VSTR, UniValue::VSTR, UniValue::VSTR});
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
avalanche::LimitedProofId limitedProofId{
ParseHashV(request.params[0], "limitedproofid")};
const CKey privkey = DecodeSecret(request.params[1].get_str());
if (!privkey.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"The private key is invalid");
}
const CPubKey pubkey = ParsePubKey(request.params[2]);
std::unique_ptr<avalanche::DelegationBuilder> dgb;
if (request.params.size() >= 4 && !request.params[3].isNull()) {
avalanche::Delegation dg;
CPubKey auth;
verifyDelegationOrThrow(dg, request.params[3].get_str(), auth);
if (dg.getProofId() !=
limitedProofId.computeProofId(dg.getProofMaster())) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"The delegation does not match the proof");
}
if (privkey.GetPubKey() != auth) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"The private key does not match the delegation");
}
dgb = std::make_unique<avalanche::DelegationBuilder>(dg);
} else {
dgb = std::make_unique<avalanche::DelegationBuilder>(
limitedProofId, privkey.GetPubKey());
}
if (!dgb->addLevel(privkey, pubkey)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Unable to build the delegation");
}
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << dgb->build();
return HexStr(ss);
},
};
}
static RPCHelpMan decodeavalanchedelegation() {
return RPCHelpMan{
"decodeavalanchedelegation",
"Convert a serialized, hex-encoded avalanche proof delegation, into "
"JSON object. \n"
"The validity of the delegation is not verified.\n",
{
{"delegation", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The delegation hex string"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "pubkey",
"The public key the proof is delegated to."},
{RPCResult::Type::STR_HEX, "proofmaster",
"The delegated proof master public key."},
{RPCResult::Type::STR_HEX, "delegationid",
"The identifier of this delegation."},
{RPCResult::Type::STR_HEX, "limitedid",
"A delegated proof data hash excluding the master key."},
{RPCResult::Type::STR_HEX, "proofid",
"A hash of the delegated proof limitedid and master key."},
{RPCResult::Type::NUM, "depth",
"The number of delegation levels."},
{RPCResult::Type::ARR,
"levels",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "index",
"The index of this delegation level."},
{RPCResult::Type::STR_HEX, "pubkey",
"This delegated public key for this level"},
{RPCResult::Type::STR, "signature",
"Signature of this delegation level (base64 "
"encoded)"},
}},
}},
}},
RPCExamples{HelpExampleCli("decodeavalanchedelegation",
"\"<hex delegation>\"") +
HelpExampleRpc("decodeavalanchedelegation",
"\"<hex delegation>\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
avalanche::Delegation delegation;
bilingual_str error;
if (!avalanche::Delegation::FromHex(
delegation, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, error.original);
}
UniValue result(UniValue::VOBJ);
result.pushKV("pubkey", HexStr(delegation.getDelegatedPubkey()));
result.pushKV("proofmaster", HexStr(delegation.getProofMaster()));
result.pushKV("delegationid", delegation.getId().ToString());
result.pushKV("limitedid",
delegation.getLimitedProofId().ToString());
result.pushKV("proofid", delegation.getProofId().ToString());
auto levels = delegation.getLevels();
result.pushKV("depth", uint64_t(levels.size()));
UniValue levelsArray(UniValue::VARR);
for (auto &level : levels) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("pubkey", HexStr(level.pubkey));
obj.pushKV("signature", EncodeBase64(level.sig));
levelsArray.push_back(std::move(obj));
}
result.pushKV("levels", levelsArray);
return result;
},
};
}
static RPCHelpMan getavalancheinfo() {
return RPCHelpMan{
"getavalancheinfo",
"Returns an object containing various state info regarding avalanche "
"networking.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "ready_to_poll",
"Whether the node is ready to start polling and voting."},
{RPCResult::Type::OBJ,
"local",
"Only available if -avaproof has been supplied to the node",
{
{RPCResult::Type::BOOL, "verified",
"Whether the node local proof has been locally verified "
"or not."},
{RPCResult::Type::STR_HEX, "proofid",
"The node local proof id."},
{RPCResult::Type::STR_HEX, "limited_proofid",
"The node local limited proof id."},
{RPCResult::Type::STR_HEX, "master",
"The node local proof master public key."},
{RPCResult::Type::STR, "payout_address",
"The node local proof payout address. This might be "
"omitted if the payout script is not one of P2PK, P2PKH "
"or P2SH, in which case decodeavalancheproof can be used "
"to get more details."},
{RPCResult::Type::STR_AMOUNT, "stake_amount",
"The node local proof staked amount."},
}},
{RPCResult::Type::OBJ,
"network",
"",
{
{RPCResult::Type::NUM, "proof_count",
"The number of valid avalanche proofs we know exist "
"(including this node's local proof if applicable)."},
{RPCResult::Type::NUM, "connected_proof_count",
"The number of avalanche proofs with at least one node "
"we are connected to (including this node's local proof "
"if applicable)."},
{RPCResult::Type::NUM, "dangling_proof_count",
"The number of avalanche proofs with no node attached."},
{RPCResult::Type::NUM, "finalized_proof_count",
"The number of known avalanche proofs that have been "
"finalized by avalanche."},
{RPCResult::Type::NUM, "conflicting_proof_count",
"The number of known avalanche proofs that conflict with "
"valid proofs."},
{RPCResult::Type::NUM, "immature_proof_count",
"The number of known avalanche proofs that have immature "
"utxos."},
{RPCResult::Type::STR_AMOUNT, "total_stake_amount",
"The total staked amount over all the valid proofs in " +
Currency::get().ticker +
" (including this node's local proof if "
"applicable)."},
{RPCResult::Type::STR_AMOUNT, "connected_stake_amount",
"The total staked amount over all the connected proofs "
"in " +
Currency::get().ticker +
" (including this node's local proof if "
"applicable)."},
{RPCResult::Type::STR_AMOUNT, "dangling_stake_amount",
"The total staked amount over all the dangling proofs "
"in " +
Currency::get().ticker +
" (including this node's local proof if "
"applicable)."},
{RPCResult::Type::STR_AMOUNT, "immature_stake_amount",
"The total staked amount over all the immature proofs "
"in " +
Currency::get().ticker +
" (including this node's local proof if "
"applicable)."},
{RPCResult::Type::NUM, "node_count",
"The number of avalanche nodes we are connected to "
"(including this node if a local proof is set)."},
{RPCResult::Type::NUM, "connected_node_count",
"The number of avalanche nodes associated with an "
"avalanche proof (including this node if a local proof "
"is set)."},
{RPCResult::Type::NUM, "pending_node_count",
"The number of avalanche nodes pending for a proof."},
}},
},
},
RPCExamples{HelpExampleCli("getavalancheinfo", "") +
HelpExampleRpc("getavalancheinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("ready_to_poll", g_avalanche->isQuorumEstablished());
auto localProof = g_avalanche->getLocalProof();
if (localProof != nullptr) {
UniValue local(UniValue::VOBJ);
local.pushKV("verified",
g_avalanche->withPeerManager(
[&](const avalanche::PeerManager &pm) {
return pm.isBoundToPeer(
localProof->getId());
}));
local.pushKV("proofid", localProof->getId().ToString());
local.pushKV("limited_proofid",
localProof->getLimitedId().ToString());
local.pushKV("master", HexStr(localProof->getMaster()));
CTxDestination destination;
if (ExtractDestination(localProof->getPayoutScript(),
destination)) {
local.pushKV("payout_address",
EncodeDestination(destination, config));
}
local.pushKV("stake_amount", localProof->getStakedAmount());
ret.pushKV("local", local);
}
g_avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
UniValue network(UniValue::VOBJ);
uint64_t proofCount{0};
uint64_t connectedProofCount{0};
uint64_t finalizedProofCount{0};
uint64_t connectedNodeCount{0};
Amount totalStakes = Amount::zero();
Amount connectedStakes = Amount::zero();
pm.forEachPeer([&](const avalanche::Peer &peer) {
CHECK_NONFATAL(peer.proof != nullptr);
const bool isLocalProof = peer.proof == localProof;
++proofCount;
const Amount proofStake = peer.proof->getStakedAmount();
totalStakes += proofStake;
if (peer.hasFinalized) {
++finalizedProofCount;
}
if (peer.node_count > 0 || isLocalProof) {
++connectedProofCount;
connectedStakes += proofStake;
}
connectedNodeCount += peer.node_count + isLocalProof;
});
Amount immatureStakes = Amount::zero();
pm.getImmatureProofPool().forEachProof(
[&](const avalanche::ProofRef &proof) {
immatureStakes += proof->getStakedAmount();
});
network.pushKV("proof_count", proofCount);
network.pushKV("connected_proof_count", connectedProofCount);
network.pushKV("dangling_proof_count",
proofCount - connectedProofCount);
network.pushKV("finalized_proof_count", finalizedProofCount);
network.pushKV(
"conflicting_proof_count",
uint64_t(pm.getConflictingProofPool().countProofs()));
network.pushKV(
"immature_proof_count",
uint64_t(pm.getImmatureProofPool().countProofs()));
network.pushKV("total_stake_amount", totalStakes);
network.pushKV("connected_stake_amount", connectedStakes);
network.pushKV("dangling_stake_amount",
totalStakes - connectedStakes);
network.pushKV("immature_stake_amount", immatureStakes);
const uint64_t pendingNodes = pm.getPendingNodeCount();
network.pushKV("node_count", connectedNodeCount + pendingNodes);
network.pushKV("connected_node_count", connectedNodeCount);
network.pushKV("pending_node_count", pendingNodes);
ret.pushKV("network", network);
});
return ret;
},
};
}
static RPCHelpMan getavalanchepeerinfo() {
return RPCHelpMan{
"getavalanchepeerinfo",
"Returns data about an avalanche peer as a json array of objects. If "
"no proofid is provided, returns data about all the peers.\n",
{
{"proofid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"The hex encoded avalanche proof identifier."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{
RPCResult::Type::OBJ,
"",
"",
{{
{RPCResult::Type::NUM, "avalanche_peerid",
"The avalanche internal peer identifier"},
{RPCResult::Type::STR_HEX, "proofid",
"The avalanche proof id used by this peer"},
{RPCResult::Type::STR_HEX, "proof",
"The avalanche proof used by this peer"},
{RPCResult::Type::NUM, "nodecount",
"The number of nodes for this peer"},
{RPCResult::Type::ARR,
"node_list",
"",
{
{RPCResult::Type::NUM, "nodeid",
"Node id, as returned by getpeerinfo"},
}},
}},
}},
},
RPCExamples{HelpExampleCli("getavalanchepeerinfo", "") +
HelpExampleCli("getavalanchepeerinfo", "\"proofid\"") +
HelpExampleRpc("getavalanchepeerinfo", "") +
HelpExampleRpc("getavalanchepeerinfo", "\"proofid\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
auto peerToUniv = [](const avalanche::PeerManager &pm,
const avalanche::Peer &peer) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("avalanche_peerid", uint64_t(peer.peerid));
obj.pushKV("proofid", peer.getProofId().ToString());
obj.pushKV("proof", peer.proof->ToHex());
UniValue nodes(UniValue::VARR);
pm.forEachNode(peer, [&](const avalanche::Node &n) {
nodes.push_back(n.nodeid);
});
obj.pushKV("nodecount", uint64_t(peer.node_count));
obj.pushKV("node_list", nodes);
return obj;
};
UniValue ret(UniValue::VARR);
g_avalanche->withPeerManager([&](const avalanche::PeerManager &pm) {
// If a proofid is provided, only return the associated peer
if (!request.params[0].isNull()) {
const avalanche::ProofId proofid =
avalanche::ProofId::fromHex(
request.params[0].get_str());
if (!pm.isBoundToPeer(proofid)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Proofid not found");
}
pm.forPeer(proofid, [&](const avalanche::Peer &peer) {
return ret.push_back(peerToUniv(pm, peer));
});
return;
}
// If no proofid is provided, return all the peers
pm.forEachPeer([&](const avalanche::Peer &peer) {
ret.push_back(peerToUniv(pm, peer));
});
});
return ret;
},
};
}
static RPCHelpMan getavalancheproofs() {
return RPCHelpMan{
"getavalancheproofs",
"Returns an object containing all tracked proofids.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ARR,
"valid",
"",
{
{RPCResult::Type::STR_HEX, "proofid",
"Avalanche proof id"},
}},
{RPCResult::Type::ARR,
"conflicting",
"",
{
{RPCResult::Type::STR_HEX, "proofid",
"Avalanche proof id"},
}},
{RPCResult::Type::ARR,
"immature",
"",
{
{RPCResult::Type::STR_HEX, "proofid",
"Avalanche proof id"},
}},
},
},
RPCExamples{HelpExampleCli("getavalancheproofs", "") +
HelpExampleRpc("getavalancheproofs", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
UniValue ret(UniValue::VOBJ);
g_avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
auto appendProofIds = [&ret](const avalanche::ProofPool &pool,
const std::string &key) {
UniValue arrOut(UniValue::VARR);
for (const avalanche::ProofId &proofid :
pool.getProofIds()) {
arrOut.push_back(proofid.ToString());
}
ret.pushKV(key, arrOut);
};
appendProofIds(pm.getValidProofPool(), "valid");
appendProofIds(pm.getConflictingProofPool(), "conflicting");
appendProofIds(pm.getImmatureProofPool(), "immature");
});
return ret;
},
};
}
static RPCHelpMan getrawavalancheproof() {
return RPCHelpMan{
"getrawavalancheproof",
"Lookup for a known avalanche proof by id.\n",
{
{"proofid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex encoded avalanche proof identifier."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{{
{RPCResult::Type::STR_HEX, "proof",
"The hex encoded proof matching the identifier."},
{RPCResult::Type::BOOL, "immature",
"Whether the proof has immature utxos."},
{RPCResult::Type::BOOL, "boundToPeer",
"Whether the proof is bound to an avalanche peer."},
{RPCResult::Type::BOOL, "conflicting",
"Whether the proof has a conflicting UTXO with an avalanche "
"peer."},
{RPCResult::Type::BOOL, "finalized",
"Whether the proof is finalized by vote."},
}},
},
RPCExamples{HelpExampleRpc("getrawavalancheproof", "<proofid>")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
const avalanche::ProofId proofid =
avalanche::ProofId::fromHex(request.params[0].get_str());
bool isImmature = false;
bool isBoundToPeer = false;
bool conflicting = false;
bool finalized = false;
auto proof = g_avalanche->withPeerManager(
[&](const avalanche::PeerManager &pm) {
isImmature = pm.isImmature(proofid);
isBoundToPeer = pm.isBoundToPeer(proofid);
conflicting = pm.isInConflictingPool(proofid);
finalized =
pm.forPeer(proofid, [&](const avalanche::Peer &p) {
return p.hasFinalized;
});
return pm.getProof(proofid);
});
if (!proof) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Proof not found");
}
UniValue ret(UniValue::VOBJ);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << *proof;
ret.pushKV("proof", HexStr(ss));
ret.pushKV("immature", isImmature);
ret.pushKV("boundToPeer", isBoundToPeer);
ret.pushKV("conflicting", conflicting);
ret.pushKV("finalized", finalized);
return ret;
},
};
}
static RPCHelpMan isfinalblock() {
return RPCHelpMan{
"isfinalblock",
"Check if a block has been finalized by avalanche votes.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hash of the block."},
},
RPCResult{RPCResult::Type::BOOL, "success",
"Whether the block has been finalized by avalanche votes."},
RPCExamples{HelpExampleRpc("isfinalblock", "<block hash>") +
HelpExampleCli("isfinalblock", "<block hash>")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
// Deprecated since 0.26.2
if (!IsDeprecatedRPCEnabled(gArgs, "isfinalblock_noerror") &&
!g_avalanche->isQuorumEstablished()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Avalanche is not ready to poll yet.");
}
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const BlockHash blockhash(
ParseHashV(request.params[0], "blockhash"));
const CBlockIndex *pindex;
{
LOCK(cs_main);
pindex = chainman.m_blockman.LookupBlockIndex(blockhash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block not found");
}
}
return chainman.ActiveChainstate().IsBlockAvalancheFinalized(
pindex);
},
};
}
static RPCHelpMan isfinaltransaction() {
return RPCHelpMan{
"isfinaltransaction",
"Check if a transaction has been finalized by avalanche votes.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The id of the transaction."},
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"The block in which to look for the transaction"},
},
RPCResult{
RPCResult::Type::BOOL, "success",
"Whether the transaction has been finalized by avalanche votes."},
RPCExamples{HelpExampleRpc("isfinaltransaction", "<txid> <blockhash>") +
HelpExampleCli("isfinaltransaction", "<txid> <blockhash>")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
const NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
const TxId txid = TxId(ParseHashV(request.params[0], "txid"));
CBlockIndex *pindex = nullptr;
if (!request.params[1].isNull()) {
const BlockHash blockhash(
ParseHashV(request.params[1], "blockhash"));
LOCK(cs_main);
pindex = chainman.m_blockman.LookupBlockIndex(blockhash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block not found");
}
}
bool f_txindex_ready = false;
if (g_txindex && !pindex) {
f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain();
}
BlockHash hash_block;
const CTransactionRef tx = GetTransaction(
pindex, node.mempool.get(), txid,
config.GetChainParams().GetConsensus(), hash_block);
// Deprecated since 0.26.2
if (!IsDeprecatedRPCEnabled(gArgs, "isfinaltransaction_noerror")) {
if (!g_avalanche->isQuorumEstablished()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Avalanche is not ready to poll yet.");
}
if (!tx) {
std::string errmsg;
if (pindex) {
if (!pindex->nStatus.hasData()) {
throw JSONRPCError(
RPC_MISC_ERROR,
"Block data not downloaded yet.");
}
errmsg =
"No such transaction found in the provided block.";
} else if (!g_txindex) {
errmsg =
"No such transaction. Use -txindex or provide a "
"block "
"hash to enable blockchain transaction queries.";
} else if (!f_txindex_ready) {
errmsg =
"No such transaction. Blockchain transactions are "
"still in the process of being indexed.";
} else {
errmsg = "No such mempool or blockchain transaction.";
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errmsg);
}
}
if (!pindex) {
LOCK(cs_main);
pindex = chainman.m_blockman.LookupBlockIndex(hash_block);
}
// The first 2 checks are redundant as we expect to throw a JSON RPC
// error for these cases, but they are almost free so they are kept
// as a safety net.
return tx != nullptr && !node.mempool->exists(txid) &&
chainman.ActiveChainstate().IsBlockAvalancheFinalized(
pindex);
},
};
}
static RPCHelpMan sendavalancheproof() {
return RPCHelpMan{
"sendavalancheproof",
"Broadcast an avalanche proof.\n",
{
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The avalanche proof to broadcast."},
},
RPCResult{RPCResult::Type::BOOL, "success",
"Whether the proof was sent successfully or not."},
RPCExamples{HelpExampleRpc("sendavalancheproof", "<proof>")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!g_avalanche) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Avalanche is not initialized");
}
auto proof = RCUPtr<avalanche::Proof>::make();
NodeContext &node = EnsureAnyNodeContext(request.context);
// Verify the proof. Note that this is redundant with the
// verification done when adding the proof to the pool, but we get a
// chance to give a better error message.
verifyProofOrThrow(node, *proof, request.params[0].get_str());
// Add the proof to the pool if we don't have it already. Since the
// proof verification has already been done, a failure likely
// indicates that there already is a proof with conflicting utxos.
const avalanche::ProofId &proofid = proof->getId();
avalanche::ProofRegistrationState state;
if (!registerProofIfNeeded(proof, state)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("%s (%s)\n",
state.GetRejectReason(),
state.GetDebugMessage()));
}
g_avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
pm.addUnbroadcastProof(proofid);
});
if (node.peerman) {
node.peerman->RelayProof(proofid);
}
return true;
},
};
}
static RPCHelpMan verifyavalancheproof() {
return RPCHelpMan{
"verifyavalancheproof",
"Verify an avalanche proof is valid and return the error otherwise.\n",
{
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"Proof to verify."},
},
RPCResult{RPCResult::Type::BOOL, "success",
"Whether the proof is valid or not."},
RPCExamples{HelpExampleRpc("verifyavalancheproof", "\"<proof>\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
avalanche::Proof proof;
verifyProofOrThrow(EnsureAnyNodeContext(request.context), proof,
request.params[0].get_str());
return true;
},
};
}
static RPCHelpMan verifyavalanchedelegation() {
return RPCHelpMan{
"verifyavalanchedelegation",
"Verify an avalanche delegation is valid and return the error "
"otherwise.\n",
{
{"delegation", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The avalanche proof delegation to verify."},
},
RPCResult{RPCResult::Type::BOOL, "success",
"Whether the delegation is valid or not."},
RPCExamples{HelpExampleRpc("verifyavalanchedelegation", "\"<proof>\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
avalanche::Delegation delegation;
CPubKey dummy;
verifyDelegationOrThrow(delegation, request.params[0].get_str(),
dummy);
return true;
},
};
}
void RegisterAvalancheRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ----------------- --------------------
{ "avalanche", getavalanchekey, },
{ "avalanche", addavalanchenode, },
{ "avalanche", buildavalancheproof, },
{ "avalanche", decodeavalancheproof, },
{ "avalanche", delegateavalancheproof, },
{ "avalanche", decodeavalanchedelegation, },
{ "avalanche", getavalancheinfo, },
{ "avalanche", getavalanchepeerinfo, },
{ "avalanche", getavalancheproofs, },
{ "avalanche", getrawavalancheproof, },
{ "avalanche", isfinalblock, },
{ "avalanche", isfinaltransaction, },
{ "avalanche", sendavalancheproof, },
{ "avalanche", verifyavalancheproof, },
{ "avalanche", verifyavalanchedelegation, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 5a07190f6..50aa69133 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1,3382 +1,3351 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <rpc/blockchain.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
#include <config.h>
#include <consensus/amount.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <deploymentinfo.h>
#include <deploymentstatus.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
#include <node/blockstorage.h>
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <streams.h>
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
#include <util/strencodings.h>
-#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <versionbits.h>
#include <warnings.h>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
struct CUpdatedBlock {
BlockHash hash;
int height;
};
static Mutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
-NodeContext &EnsureAnyNodeContext(const std::any &context) {
- auto node_context = util::AnyPtr<NodeContext>(context);
- if (!node_context) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found");
- }
- return *node_context;
-}
-
-CTxMemPool &EnsureMemPool(const NodeContext &node) {
- if (!node.mempool) {
- throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED,
- "Mempool disabled or instance not found");
- }
- return *node.mempool;
-}
-
-CTxMemPool &EnsureAnyMemPool(const std::any &context) {
- return EnsureMemPool(EnsureAnyNodeContext(context));
-}
-
-ChainstateManager &EnsureChainman(const NodeContext &node) {
- if (!node.chainman) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found");
- }
- return *node.chainman;
-}
-
-ChainstateManager &EnsureAnyChainman(const std::any &context) {
- return EnsureChainman(EnsureAnyNodeContext(context));
-}
-
/**
* Calculate the difficulty for a given block index.
*/
double GetDifficulty(const CBlockIndex *blockindex) {
CHECK_NONFATAL(blockindex);
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff);
while (nShift < 29) {
dDiff *= 256.0;
nShift++;
}
while (nShift > 29) {
dDiff /= 256.0;
nShift--;
}
return dDiff;
}
static int ComputeNextBlockAndDepth(const CBlockIndex *tip,
const CBlockIndex *blockindex,
const CBlockIndex *&next) {
next = tip->GetAncestor(blockindex->nHeight + 1);
if (next && next->pprev == blockindex) {
return tip->nHeight - blockindex->nHeight + 1;
}
next = nullptr;
return blockindex == tip ? 1 : -1;
}
static CBlockIndex *ParseHashOrHeight(const UniValue ¶m,
ChainstateManager &chainman) {
LOCK(::cs_main);
CChain &active_chain = chainman.ActiveChain();
if (param.isNum()) {
const int height{param.get_int()};
if (height < 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d is negative", height));
}
const int current_tip{active_chain.Height()};
if (height > current_tip) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d after current tip %d", height,
current_tip));
}
return active_chain[height];
} else {
const BlockHash hash{ParseHashV(param, "hash_or_height")};
CBlockIndex *pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
return pindex;
}
}
UniValue blockheaderToJSON(const CBlockIndex *tip,
const CBlockIndex *blockindex) {
// Serialize passed information without accessing chain state of the active
// chain!
// For performance reasons
AssertLockNotHeld(cs_main);
UniValue result(UniValue::VOBJ);
result.pushKV("hash", blockindex->GetBlockHash().GetHex());
const CBlockIndex *pnext;
int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
result.pushKV("confirmations", confirmations);
result.pushKV("height", blockindex->nHeight);
result.pushKV("version", blockindex->nVersion);
result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion));
result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex());
result.pushKV("time", int64_t(blockindex->nTime));
result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast()));
result.pushKV("nonce", uint64_t(blockindex->nNonce));
result.pushKV("bits", strprintf("%08x", blockindex->nBits));
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
result.pushKV("nTx", uint64_t(blockindex->nTx));
if (blockindex->pprev) {
result.pushKV("previousblockhash",
blockindex->pprev->GetBlockHash().GetHex());
}
if (pnext) {
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
}
return result;
}
UniValue blockToJSON(const CBlock &block, const CBlockIndex *tip,
const CBlockIndex *blockindex, bool txDetails) {
UniValue result = blockheaderToJSON(tip, blockindex);
result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
UniValue txs(UniValue::VARR);
if (txDetails) {
CBlockUndo blockUndo;
const bool have_undo = !IsBlockPruned(blockindex) &&
UndoReadFromDisk(blockUndo, blockindex);
for (size_t i = 0; i < block.vtx.size(); ++i) {
const CTransactionRef &tx = block.vtx.at(i);
// coinbase transaction (i == 0) doesn't have undo data
const CTxUndo *txundo =
(have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, BlockHash(), objTx, true, RPCSerializationFlags(),
txundo);
txs.push_back(objTx);
}
} else {
for (const CTransactionRef &tx : block.vtx) {
txs.push_back(tx->GetId().GetHex());
}
}
result.pushKV("tx", txs);
return result;
}
static RPCHelpMan getblockcount() {
return RPCHelpMan{
"getblockcount",
"Returns the height of the most-work fully-validated chain.\n"
"The genesis block has height 0.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The current block count"},
RPCExamples{HelpExampleCli("getblockcount", "") +
HelpExampleRpc("getblockcount", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return chainman.ActiveHeight();
},
};
}
static RPCHelpMan getbestblockhash() {
return RPCHelpMan{
"getbestblockhash",
"Returns the hash of the best (tip) block in the "
"most-work fully-validated chain.\n",
{},
RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
RPCExamples{HelpExampleCli("getbestblockhash", "") +
HelpExampleRpc("getbestblockhash", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return chainman.ActiveTip()->GetBlockHash().GetHex();
},
};
}
RPCHelpMan getfinalizedblockhash() {
return RPCHelpMan{
"getfinalizedblockhash",
"Returns the hash of the currently finalized block\n",
{},
RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
RPCExamples{HelpExampleCli("getfinalizedblockhash", "") +
HelpExampleRpc("getfinalizedblockhash", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChainState &active_chainstate = chainman.ActiveChainstate();
const CBlockIndex *blockIndexFinalized =
active_chainstate.GetFinalizedBlock();
if (blockIndexFinalized) {
return blockIndexFinalized->GetBlockHash().GetHex();
}
return UniValue(UniValue::VSTR);
},
};
}
void RPCNotifyBlockChange(const CBlockIndex *pindex) {
if (pindex) {
LOCK(cs_blockchange);
latestblock.hash = pindex->GetBlockHash();
latestblock.height = pindex->nHeight;
}
cond_blockchange.notify_all();
}
static RPCHelpMan waitfornewblock() {
return RPCHelpMan{
"waitfornewblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"timeout", RPCArg::Type::NUM, /* default */ "0",
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitfornewblock", "1000") +
HelpExampleRpc("waitfornewblock", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
if (!request.params[0].isNull()) {
timeout = request.params[0].get_int();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
block = latestblock;
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash ||
!IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash ||
!IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan waitforblock() {
return RPCHelpMan{
"waitforblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"Block hash to wait for."},
{"timeout", RPCArg::Type::NUM, /* default */ "0",
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\" 1000") +
HelpExampleRpc("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\", 1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
if (!request.params[1].isNull()) {
timeout = request.params[1].get_int();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan waitforblockheight() {
return RPCHelpMan{
"waitforblockheight",
"Waits for (at least) block height and returns the height and "
"hash\nof the current tip.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"Block height to wait for."},
{"timeout", RPCArg::Type::NUM, /* default */ "0",
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblockheight", "100 1000") +
HelpExampleRpc("waitforblockheight", "100, 1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
int height = request.params[0].get_int();
if (!request.params[1].isNull()) {
timeout = request.params[1].get_int();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height ||
!IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height ||
!IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan syncwithvalidationinterfacequeue() {
return RPCHelpMan{
"syncwithvalidationinterfacequeue",
"Waits for the validation interface queue to catch up on everything "
"that was there when we entered this function.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("syncwithvalidationinterfacequeue", "") +
HelpExampleRpc("syncwithvalidationinterfacequeue", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static RPCHelpMan getdifficulty() {
return RPCHelpMan{
"getdifficulty",
"Returns the proof-of-work difficulty as a multiple of the minimum "
"difficulty.\n",
{},
RPCResult{RPCResult::Type::NUM, "",
"the proof-of-work difficulty as a multiple of the minimum "
"difficulty."},
RPCExamples{HelpExampleCli("getdifficulty", "") +
HelpExampleRpc("getdifficulty", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return GetDifficulty(chainman.ActiveTip());
},
};
}
static std::vector<RPCResult> MempoolEntryDescription() {
const auto &ticker = Currency::get().ticker;
return {
RPCResult{RPCResult::Type::NUM, "size", "transaction size."},
RPCResult{RPCResult::Type::STR_AMOUNT, "fee",
"transaction fee in " + ticker + " (DEPRECATED)"},
RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee",
"transaction fee with fee deltas used for mining priority "
"(DEPRECATED)"},
RPCResult{RPCResult::Type::NUM_TIME, "time",
"local time transaction entered pool in seconds since 1 Jan "
"1970 GMT"},
RPCResult{RPCResult::Type::NUM, "height",
"block height when transaction entered pool"},
RPCResult{RPCResult::Type::NUM, "descendantcount",
"number of in-mempool descendant transactions (including "
"this one)"},
RPCResult{RPCResult::Type::NUM, "descendantsize",
"transaction size of in-mempool descendants "
"(including this one)"},
RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees",
"modified fees (see above) of in-mempool descendants "
"(including this one) (DEPRECATED)"},
RPCResult{
RPCResult::Type::NUM, "ancestorcount",
"number of in-mempool ancestor transactions (including this one)"},
RPCResult{
RPCResult::Type::NUM, "ancestorsize",
"transaction size of in-mempool ancestors (including this one)"},
RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees",
"modified fees (see above) of in-mempool ancestors "
"(including this one) (DEPRECATED)"},
RPCResult{RPCResult::Type::OBJ,
"fees",
"",
{
RPCResult{RPCResult::Type::STR_AMOUNT, "base",
"transaction fee in " + ticker},
RPCResult{RPCResult::Type::STR_AMOUNT, "modified",
"transaction fee with fee deltas used for "
"mining priority in " +
ticker},
RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor",
"modified fees (see above) of in-mempool "
"ancestors (including this one) in " +
ticker},
RPCResult{RPCResult::Type::STR_AMOUNT, "descendant",
"modified fees (see above) of in-mempool "
"descendants (including this one) in " +
ticker},
}},
RPCResult{
RPCResult::Type::ARR,
"depends",
"unconfirmed transactions used as inputs for this transaction",
{RPCResult{RPCResult::Type::STR_HEX, "transactionid",
"parent transaction id"}}},
RPCResult{
RPCResult::Type::ARR,
"spentby",
"unconfirmed transactions spending outputs from this transaction",
{RPCResult{RPCResult::Type::STR_HEX, "transactionid",
"child transaction id"}}},
RPCResult{RPCResult::Type::BOOL, "unbroadcast",
"Whether this transaction is currently unbroadcast (initial "
"broadcast not yet acknowledged by any peers)"},
};
}
static void entryToJSON(const CTxMemPool &pool, UniValue &info,
const CTxMemPoolEntry &e)
EXCLUSIVE_LOCKS_REQUIRED(pool.cs) {
AssertLockHeld(pool.cs);
UniValue fees(UniValue::VOBJ);
fees.pushKV("base", e.GetFee());
fees.pushKV("modified", e.GetModifiedFee());
fees.pushKV("ancestor", e.GetModFeesWithAncestors());
fees.pushKV("descendant", e.GetModFeesWithDescendants());
info.pushKV("fees", fees);
info.pushKV("size", (int)e.GetTxSize());
info.pushKV("fee", e.GetFee());
info.pushKV("modifiedfee", e.GetModifiedFee());
info.pushKV("time", count_seconds(e.GetTime()));
info.pushKV("height", (int)e.GetHeight());
info.pushKV("descendantcount", e.GetCountWithDescendants());
info.pushKV("descendantsize", e.GetSizeWithDescendants());
info.pushKV("descendantfees", e.GetModFeesWithDescendants() / SATOSHI);
info.pushKV("ancestorcount", e.GetCountWithAncestors());
info.pushKV("ancestorsize", e.GetSizeWithAncestors());
info.pushKV("ancestorfees", e.GetModFeesWithAncestors() / SATOSHI);
const CTransaction &tx = e.GetTx();
std::set<std::string> setDepends;
for (const CTxIn &txin : tx.vin) {
if (pool.exists(txin.prevout.GetTxId())) {
setDepends.insert(txin.prevout.GetTxId().ToString());
}
}
UniValue depends(UniValue::VARR);
for (const std::string &dep : setDepends) {
depends.push_back(dep);
}
info.pushKV("depends", depends);
UniValue spent(UniValue::VARR);
const CTxMemPool::txiter &it = pool.mapTx.find(tx.GetId());
const CTxMemPoolEntry::Children &children = it->GetMemPoolChildrenConst();
for (const CTxMemPoolEntry &child : children) {
spent.push_back(child.GetTx().GetId().ToString());
}
info.pushKV("spentby", spent);
info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetId()));
}
UniValue MempoolToJSON(const CTxMemPool &pool, bool verbose,
bool include_mempool_sequence) {
if (verbose) {
if (include_mempool_sequence) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Verbose results cannot contain mempool sequence values.");
}
LOCK(pool.cs);
UniValue o(UniValue::VOBJ);
for (const CTxMemPoolEntry &e : pool.mapTx) {
const uint256 &txid = e.GetTx().GetId();
UniValue info(UniValue::VOBJ);
entryToJSON(pool, info, e);
// Mempool has unique entries so there is no advantage in using
// UniValue::pushKV, which checks if the key already exists in O(N).
// UniValue::__pushKV is used instead which currently is O(1).
o.__pushKV(txid.ToString(), info);
}
return o;
} else {
uint64_t mempool_sequence;
std::vector<uint256> vtxids;
{
LOCK(pool.cs);
pool.queryHashes(vtxids);
mempool_sequence = pool.GetSequence();
}
UniValue a(UniValue::VARR);
for (const uint256 &txid : vtxids) {
a.push_back(txid.ToString());
}
if (!include_mempool_sequence) {
return a;
} else {
UniValue o(UniValue::VOBJ);
o.pushKV("txids", a);
o.pushKV("mempool_sequence", mempool_sequence);
return o;
}
}
}
static RPCHelpMan getrawmempool() {
return RPCHelpMan{
"getrawmempool",
"Returns all transaction ids in memory pool as a json array of "
"string transaction ids.\n"
"\nHint: use getmempoolentry to fetch a specific transaction from the "
"mempool.\n",
{
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"True for a json object, false for array of transaction ids"},
{"mempool_sequence", RPCArg::Type::BOOL, /* default */ "false",
"If verbose=false, returns a json object with transaction list "
"and mempool sequence number attached."},
},
{
RPCResult{"for verbose = false",
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR_HEX, "", "The transaction id"},
}},
RPCResult{"for verbose = true",
RPCResult::Type::OBJ_DYN,
"",
"",
{
{RPCResult::Type::OBJ, "transactionid", "",
MempoolEntryDescription()},
}},
RPCResult{
"for verbose = false and mempool_sequence = true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ARR,
"txids",
"",
{
{RPCResult::Type::STR_HEX, "", "The transaction id"},
}},
{RPCResult::Type::NUM, "mempool_sequence",
"The mempool sequence value."},
}},
},
RPCExamples{HelpExampleCli("getrawmempool", "true") +
HelpExampleRpc("getrawmempool", "true")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
bool fVerbose = false;
if (!request.params[0].isNull()) {
fVerbose = request.params[0].get_bool();
}
bool include_mempool_sequence = false;
if (!request.params[1].isNull()) {
include_mempool_sequence = request.params[1].get_bool();
}
return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose,
include_mempool_sequence);
},
};
}
static RPCHelpMan getmempoolancestors() {
return RPCHelpMan{
"getmempoolancestors",
"If txid is in the mempool, returns all in-mempool ancestors.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id (must be in mempool)"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"True for a json object, false for array of transaction ids"},
},
{
RPCResult{
"for verbose = false",
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::STR_HEX, "",
"The transaction id of an in-mempool ancestor transaction"}}},
RPCResult{"for verbose = true",
RPCResult::Type::OBJ_DYN,
"",
"",
{
{RPCResult::Type::OBJ, "transactionid", "",
MempoolEntryDescription()},
}},
},
RPCExamples{HelpExampleCli("getmempoolancestors", "\"mytxid\"") +
HelpExampleRpc("getmempoolancestors", "\"mytxid\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
bool fVerbose = false;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
TxId txid(ParseHashV(request.params[0], "parameter 1"));
const CTxMemPool &mempool = EnsureAnyMemPool(request.context);
LOCK(mempool.cs);
CTxMemPool::txiter it = mempool.mapTx.find(txid);
if (it == mempool.mapTx.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not in mempool");
}
CTxMemPool::setEntries setAncestors;
uint64_t noLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit,
noLimit, noLimit, noLimit, dummy,
false);
if (!fVerbose) {
UniValue o(UniValue::VARR);
for (CTxMemPool::txiter ancestorIt : setAncestors) {
o.push_back(ancestorIt->GetTx().GetId().ToString());
}
return o;
} else {
UniValue o(UniValue::VOBJ);
for (CTxMemPool::txiter ancestorIt : setAncestors) {
const CTxMemPoolEntry &e = *ancestorIt;
const TxId &_txid = e.GetTx().GetId();
UniValue info(UniValue::VOBJ);
entryToJSON(mempool, info, e);
o.pushKV(_txid.ToString(), info);
}
return o;
}
},
};
}
static RPCHelpMan getmempooldescendants() {
return RPCHelpMan{
"getmempooldescendants",
"If txid is in the mempool, returns all in-mempool descendants.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id (must be in mempool)"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"True for a json object, false for array of transaction ids"},
},
{
RPCResult{"for verbose = false",
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::STR_HEX, "",
"The transaction id of an in-mempool descendant "
"transaction"}}},
RPCResult{"for verbose = true",
RPCResult::Type::OBJ_DYN,
"",
"",
{
{RPCResult::Type::OBJ, "transactionid", "",
MempoolEntryDescription()},
}},
},
RPCExamples{HelpExampleCli("getmempooldescendants", "\"mytxid\"") +
HelpExampleRpc("getmempooldescendants", "\"mytxid\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
bool fVerbose = false;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
TxId txid(ParseHashV(request.params[0], "parameter 1"));
const CTxMemPool &mempool = EnsureAnyMemPool(request.context);
LOCK(mempool.cs);
CTxMemPool::txiter it = mempool.mapTx.find(txid);
if (it == mempool.mapTx.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not in mempool");
}
CTxMemPool::setEntries setDescendants;
mempool.CalculateDescendants(it, setDescendants);
// CTxMemPool::CalculateDescendants will include the given tx
setDescendants.erase(it);
if (!fVerbose) {
UniValue o(UniValue::VARR);
for (CTxMemPool::txiter descendantIt : setDescendants) {
o.push_back(descendantIt->GetTx().GetId().ToString());
}
return o;
} else {
UniValue o(UniValue::VOBJ);
for (CTxMemPool::txiter descendantIt : setDescendants) {
const CTxMemPoolEntry &e = *descendantIt;
const TxId &_txid = e.GetTx().GetId();
UniValue info(UniValue::VOBJ);
entryToJSON(mempool, info, e);
o.pushKV(_txid.ToString(), info);
}
return o;
}
},
};
}
static RPCHelpMan getmempoolentry() {
return RPCHelpMan{
"getmempoolentry",
"Returns mempool data for given transaction\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id (must be in mempool)"},
},
RPCResult{RPCResult::Type::OBJ, "", "", MempoolEntryDescription()},
RPCExamples{HelpExampleCli("getmempoolentry", "\"mytxid\"") +
HelpExampleRpc("getmempoolentry", "\"mytxid\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
TxId txid(ParseHashV(request.params[0], "parameter 1"));
const CTxMemPool &mempool = EnsureAnyMemPool(request.context);
LOCK(mempool.cs);
CTxMemPool::txiter it = mempool.mapTx.find(txid);
if (it == mempool.mapTx.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not in mempool");
}
const CTxMemPoolEntry &e = *it;
UniValue info(UniValue::VOBJ);
entryToJSON(mempool, info, e);
return info;
},
};
}
static RPCHelpMan getblockhash() {
return RPCHelpMan{
"getblockhash",
"Returns hash of block in best-block-chain at height provided.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The height index"},
},
RPCResult{RPCResult::Type::STR_HEX, "", "The block hash"},
RPCExamples{HelpExampleCli("getblockhash", "1000") +
HelpExampleRpc("getblockhash", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
const CChain &active_chain = chainman.ActiveChain();
int nHeight = request.params[0].get_int();
if (nHeight < 0 || nHeight > active_chain.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block height out of range");
}
CBlockIndex *pblockindex = active_chain[nHeight];
return pblockindex->GetBlockHash().GetHex();
},
};
}
static RPCHelpMan getblockheader() {
return RPCHelpMan{
"getblockheader",
"If verbose is false, returns a string that is serialized, hex-encoded "
"data for blockheader 'hash'.\n"
"If verbose is true, returns an Object with information about "
"blockheader <hash>.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbose", RPCArg::Type::BOOL, /* default */ "true",
"true for a json object, false for the hex-encoded data"},
},
{
RPCResult{
"for verbose = true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the "
"current chain"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
/* optional */ true,
"The hash of the previous block (if available)"},
{RPCResult::Type::STR_HEX, "nextblockhash",
/* optional */ true,
"The hash of the next block (if available)"},
}},
RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
},
RPCExamples{HelpExampleCli("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "hash"));
bool fVerbose = true;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
{
ChainstateManager &chainman =
EnsureAnyChainman(request.context);
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveTip();
}
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!fVerbose) {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << pblockindex->GetBlockHeader();
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockheaderToJSON(tip, pblockindex);
},
};
}
static CBlock GetBlockChecked(const Config &config,
const CBlockIndex *pblockindex) {
CBlock block;
if (IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
}
if (!ReadBlockFromDisk(block, pblockindex,
config.GetChainParams().GetConsensus())) {
// Block not found on disk. This could be because we have the block
// header in our index but not yet have the block or did not accept the
// block.
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
}
return block;
}
static CBlockUndo GetUndoChecked(const CBlockIndex *pblockindex) {
CBlockUndo blockUndo;
if (IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Undo data not available (pruned data)");
}
if (!UndoReadFromDisk(blockUndo, pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
}
return blockUndo;
}
static RPCHelpMan getblock() {
return RPCHelpMan{
"getblock",
"If verbosity is 0 or false, returns a string that is serialized, "
"hex-encoded data for block 'hash'.\n"
"If verbosity is 1 or true, returns an Object with information about "
"block <hash>.\n"
"If verbosity is 2, returns an Object with information about block "
"<hash> and information about each transaction.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbosity|verbose", RPCArg::Type::NUM, /* default */ "1",
"0 for hex-encoded data, 1 for a json object, and 2 for json "
"object with transaction data"},
},
{
RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
RPCResult{
"for verbosity = 1",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "size", "The block size"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::ARR,
"tx",
"The transaction ids",
{{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the chain "
"up to this block (in hex)"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
/* optional */ true,
"The hash of the previous block (if available)"},
{RPCResult::Type::STR_HEX, "nextblockhash",
/* optional */ true,
"The hash of the next block (if available)"},
}},
RPCResult{"for verbosity = 2",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"Same output as verbosity = 1"},
{RPCResult::Type::ARR,
"tx",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"The transactions in the format of the "
"getrawtransaction RPC. Different from "
"verbosity = 1 \"tx\" result"},
{RPCResult::Type::STR_AMOUNT, "fee",
"The transaction fee in " +
Currency::get().ticker +
", omitted if block undo data is not "
"available"},
}},
}},
}},
},
RPCExamples{
HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
int verbosity = 1;
if (!request.params[1].isNull()) {
if (request.params[1].isNum()) {
verbosity = request.params[1].get_int();
} else {
verbosity = request.params[1].get_bool() ? 1 : 0;
}
}
CBlock block;
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
{
ChainstateManager &chainman =
EnsureAnyChainman(request.context);
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveTip();
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
block = GetBlockChecked(config, pblockindex);
}
if (verbosity <= 0) {
CDataStream ssBlock(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockToJSON(block, tip, pblockindex, verbosity >= 2);
},
};
}
static RPCHelpMan pruneblockchain() {
return RPCHelpMan{
"pruneblockchain",
"",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The block height to prune up to. May be set to a discrete "
"height, or to a " +
UNIX_EPOCH_TIME +
"\n"
" to prune blocks whose block time is at "
"least 2 hours older than the provided timestamp."},
},
RPCResult{RPCResult::Type::NUM, "", "Height of the last block pruned"},
RPCExamples{HelpExampleCli("pruneblockchain", "1000") +
HelpExampleRpc("pruneblockchain", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!fPruneMode) {
throw JSONRPCError(
RPC_MISC_ERROR,
"Cannot prune blocks because node is not in prune mode.");
}
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChainState &active_chainstate = chainman.ActiveChainstate();
CChain &active_chain = active_chainstate.m_chain;
int heightParam = request.params[0].get_int();
if (heightParam < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Negative block height.");
}
// Height value more than a billion is too high to be a block
// height, and too low to be a block time (corresponds to timestamp
// from Sep 2001).
if (heightParam > 1000000000) {
// Add a 2 hour buffer to include blocks which might have had
// old timestamps
CBlockIndex *pindex = active_chain.FindEarliestAtLeast(
heightParam - TIMESTAMP_WINDOW, 0);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Could not find block with at least the "
"specified timestamp.");
}
heightParam = pindex->nHeight;
}
unsigned int height = (unsigned int)heightParam;
unsigned int chainHeight = (unsigned int)active_chain.Height();
if (chainHeight < config.GetChainParams().PruneAfterHeight()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Blockchain is too short for pruning.");
} else if (height > chainHeight) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Blockchain is shorter than the attempted prune height.");
} else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
LogPrint(BCLog::RPC,
"Attempt to prune blocks close to the tip. "
"Retaining the minimum number of blocks.\n");
height = chainHeight - MIN_BLOCKS_TO_KEEP;
}
PruneBlockFilesManual(active_chainstate, height);
const CBlockIndex *block = active_chain.Tip();
CHECK_NONFATAL(block);
while (block->pprev && (block->pprev->nStatus.hasData())) {
block = block->pprev;
}
return uint64_t(block->nHeight);
},
};
}
static CoinStatsHashType ParseHashType(const std::string &hash_type_input) {
if (hash_type_input == "hash_serialized") {
return CoinStatsHashType::HASH_SERIALIZED;
} else if (hash_type_input == "muhash") {
return CoinStatsHashType::MUHASH;
} else if (hash_type_input == "none") {
return CoinStatsHashType::NONE;
} else {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("%s is not a valid hash_type", hash_type_input));
}
}
static RPCHelpMan gettxoutsetinfo() {
return RPCHelpMan{
"gettxoutsetinfo",
"Returns statistics about the unspent transaction output set.\n"
"Note this call may take some time if you are not using "
"coinstatsindex.\n",
{
{"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized",
"Which UTXO set hash should be calculated. Options: "
"'hash_serialized' (the legacy algorithm), 'muhash', 'none'."},
{"hash_or_height",
RPCArg::Type::NUM,
RPCArg::Optional::OMITTED,
"The block hash or height of the target height (only available "
"with coinstatsindex).",
"",
{"", "string or numeric"}},
{"use_index", RPCArg::Type::BOOL, /* default */ "true",
"Use coinstatsindex, if available."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs"},
{RPCResult::Type::NUM, "bogosize",
"Database-independent, meaningless metric indicating "
"the UTXO set size"},
{RPCResult::Type::STR_HEX, "hash_serialized",
/* optional */ true,
"The serialized hash (only present if 'hash_serialized' "
"hash_type is chosen)"},
{RPCResult::Type::STR_HEX, "muhash", /* optional */ true,
"The serialized hash (only present if 'muhash' "
"hash_type is chosen)"},
{RPCResult::Type::NUM, "transactions",
"The number of transactions with unspent outputs (not "
"available when coinstatsindex is used)"},
{RPCResult::Type::NUM, "disk_size",
"The estimated size of the chainstate on disk (not "
"available when coinstatsindex is used)"},
{RPCResult::Type::STR_AMOUNT, "total_amount",
"The total amount"},
{RPCResult::Type::STR_AMOUNT, "total_unspendable_amount",
"The total amount of coins permanently excluded from the UTXO "
"set (only available if coinstatsindex is used)"},
{RPCResult::Type::OBJ,
"block_info",
"Info on amounts in the block at this block height (only "
"available if coinstatsindex is used)",
{{RPCResult::Type::STR_AMOUNT, "prevout_spent", ""},
{RPCResult::Type::STR_AMOUNT, "coinbase", ""},
{RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", ""},
{RPCResult::Type::STR_AMOUNT, "unspendable", ""},
{RPCResult::Type::OBJ,
"unspendables",
"Detailed view of the unspendable categories",
{
{RPCResult::Type::STR_AMOUNT, "genesis_block", ""},
{RPCResult::Type::STR_AMOUNT, "bip30",
"Transactions overridden by duplicates (no longer "
"possible with BIP30)"},
{RPCResult::Type::STR_AMOUNT, "scripts",
"Amounts sent to scripts that are unspendable (for "
"example OP_RETURN outputs)"},
{RPCResult::Type::STR_AMOUNT, "unclaimed_rewards",
"Fee rewards that miners did not claim in their "
"coinbase transaction"},
}}}},
}},
RPCExamples{
HelpExampleCli("gettxoutsetinfo", "") +
HelpExampleCli("gettxoutsetinfo", R"("none")") +
HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
HelpExampleCli(
"gettxoutsetinfo",
R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
HelpExampleRpc("gettxoutsetinfo", "") +
HelpExampleRpc("gettxoutsetinfo", R"("none")") +
HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
HelpExampleRpc(
"gettxoutsetinfo",
R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
UniValue ret(UniValue::VOBJ);
CBlockIndex *pindex{nullptr};
const CoinStatsHashType hash_type{
request.params[0].isNull()
? CoinStatsHashType::HASH_SERIALIZED
: ParseHashType(request.params[0].get_str())};
CCoinsStats stats{hash_type};
stats.index_requested =
request.params[2].isNull() || request.params[2].get_bool();
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
CChainState &active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
CCoinsView *coins_view;
BlockManager *blockman;
{
LOCK(::cs_main);
coins_view = &active_chainstate.CoinsDB();
blockman = &active_chainstate.m_blockman;
pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
}
if (!request.params[1].isNull()) {
if (!g_coin_stats_index) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Querying specific block heights "
"requires coinstatsindex");
}
if (stats.m_hash_type == CoinStatsHashType::HASH_SERIALIZED) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"hash_serialized hash type cannot be "
"queried for a specific block");
}
pindex = ParseHashOrHeight(request.params[1], chainman);
}
if (GetUTXOStats(coins_view, *blockman, stats,
node.rpc_interruption_point, pindex)) {
ret.pushKV("height", int64_t(stats.nHeight));
ret.pushKV("bestblock", stats.hashBlock.GetHex());
ret.pushKV("txouts", int64_t(stats.nTransactionOutputs));
ret.pushKV("bogosize", int64_t(stats.nBogoSize));
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
ret.pushKV("hash_serialized",
stats.hashSerialized.GetHex());
}
if (hash_type == CoinStatsHashType::MUHASH) {
ret.pushKV("muhash", stats.hashSerialized.GetHex());
}
ret.pushKV("total_amount", stats.nTotalAmount);
if (!stats.index_used) {
ret.pushKV("transactions",
static_cast<int64_t>(stats.nTransactions));
ret.pushKV("disk_size", stats.nDiskSize);
} else {
ret.pushKV("total_unspendable_amount",
stats.total_unspendable_amount);
CCoinsStats prev_stats{hash_type};
if (pindex->nHeight > 0) {
GetUTXOStats(coins_view, *blockman, prev_stats,
node.rpc_interruption_point,
pindex->pprev);
}
UniValue block_info(UniValue::VOBJ);
block_info.pushKV(
"prevout_spent",
stats.total_prevout_spent_amount -
prev_stats.total_prevout_spent_amount);
block_info.pushKV("coinbase",
stats.total_coinbase_amount -
prev_stats.total_coinbase_amount);
block_info.pushKV(
"new_outputs_ex_coinbase",
stats.total_new_outputs_ex_coinbase_amount -
prev_stats.total_new_outputs_ex_coinbase_amount);
block_info.pushKV("unspendable",
stats.total_unspendable_amount -
prev_stats.total_unspendable_amount);
UniValue unspendables(UniValue::VOBJ);
unspendables.pushKV(
"genesis_block",
stats.total_unspendables_genesis_block -
prev_stats.total_unspendables_genesis_block);
unspendables.pushKV(
"bip30", stats.total_unspendables_bip30 -
prev_stats.total_unspendables_bip30);
unspendables.pushKV(
"scripts", stats.total_unspendables_scripts -
prev_stats.total_unspendables_scripts);
unspendables.pushKV(
"unclaimed_rewards",
stats.total_unspendables_unclaimed_rewards -
prev_stats.total_unspendables_unclaimed_rewards);
block_info.pushKV("unspendables", unspendables);
ret.pushKV("block_info", block_info);
}
} else {
if (g_coin_stats_index) {
const IndexSummary summary{
g_coin_stats_index->GetSummary()};
if (!summary.synced) {
throw JSONRPCError(
RPC_INTERNAL_ERROR,
strprintf("Unable to read UTXO set because "
"coinstatsindex is still syncing. "
"Current height: %d",
summary.best_block_height));
}
}
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to read UTXO set");
}
return ret;
},
};
}
RPCHelpMan gettxout() {
return RPCHelpMan{
"gettxout",
"Returns details about an unspent transaction output.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
{"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
{"include_mempool", RPCArg::Type::BOOL, /* default */ "true",
"Whether to include the mempool. Note that an unspent output that "
"is spent in the mempool won't appear."},
},
{
RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "",
""},
RPCResult{
"Otherwise",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations"},
{RPCResult::Type::STR_AMOUNT, "value",
"The transaction value in " + Currency::get().ticker},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR_HEX, "asm", ""},
{RPCResult::Type::STR_HEX, "hex", ""},
{RPCResult::Type::NUM, "reqSigs",
"Number of required signatures"},
{RPCResult::Type::STR_HEX, "type",
"The type, eg pubkeyhash"},
{RPCResult::Type::ARR,
"addresses",
"array of eCash addresses",
{{RPCResult::Type::STR, "address", "eCash address"}}},
}},
{RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
}},
},
RPCExamples{"\nGet unspent transactions\n" +
HelpExampleCli("listunspent", "") + "\nView the details\n" +
HelpExampleCli("gettxout", "\"txid\" 1") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("gettxout", "\"txid\", 1")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
TxId txid(ParseHashV(request.params[0], "txid"));
int n = request.params[1].get_int();
COutPoint out(txid, n);
bool fMempool = true;
if (!request.params[2].isNull()) {
fMempool = request.params[2].get_bool();
}
Coin coin;
CChainState &active_chainstate = chainman.ActiveChainstate();
CCoinsViewCache *coins_view = &active_chainstate.CoinsTip();
if (fMempool) {
const CTxMemPool &mempool = EnsureMemPool(node);
LOCK(mempool.cs);
CCoinsViewMemPool view(coins_view, mempool);
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
return NullUniValue;
}
} else {
if (!coins_view->GetCoin(out, coin)) {
return NullUniValue;
}
}
const CBlockIndex *pindex =
active_chainstate.m_blockman.LookupBlockIndex(
coins_view->GetBestBlock());
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
if (coin.GetHeight() == MEMPOOL_HEIGHT) {
ret.pushKV("confirmations", 0);
} else {
ret.pushKV("confirmations",
int64_t(pindex->nHeight - coin.GetHeight() + 1));
}
ret.pushKV("value", coin.GetTxOut().nValue);
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.GetTxOut().scriptPubKey, o, true);
ret.pushKV("scriptPubKey", o);
ret.pushKV("coinbase", coin.IsCoinBase());
return ret;
},
};
}
static RPCHelpMan verifychain() {
return RPCHelpMan{
"verifychain",
"Verifies blockchain database.\n",
{
{"checklevel", RPCArg::Type::NUM,
/* default */ strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL),
strprintf("How thorough the block verification is:\n - %s",
Join(CHECKLEVEL_DOC, "\n- "))},
{"nblocks", RPCArg::Type::NUM,
/* default */ strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS),
"The number of blocks to check."},
},
RPCResult{RPCResult::Type::BOOL, "", "Verified or not"},
RPCExamples{HelpExampleCli("verifychain", "") +
HelpExampleRpc("verifychain", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const int check_level(request.params[0].isNull()
? DEFAULT_CHECKLEVEL
: request.params[0].get_int());
const int check_depth{request.params[1].isNull()
? DEFAULT_CHECKBLOCKS
: request.params[1].get_int()};
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChainState &active_chainstate = chainman.ActiveChainstate();
return CVerifyDB().VerifyDB(active_chainstate, config,
active_chainstate.CoinsTip(),
check_level, check_depth);
},
};
}
[[maybe_unused]] static void
SoftForkDescPushBack(const CBlockIndex *active_chain_tip, UniValue &softforks,
const Consensus::Params ¶ms,
Consensus::BuriedDeployment dep) {
// For buried deployments.
// A buried deployment is one where the height of the activation has been
// hardcoded into the client implementation long after the consensus change
// has activated. See BIP 90. Buried deployments with activation height
// value of std::numeric_limits<int>::max() are disabled and thus hidden.
if (!DeploymentEnabled(params, dep)) {
return;
}
UniValue rv(UniValue::VOBJ);
rv.pushKV("type", "buried");
// getblockchaininfo reports the softfork as active from when the chain
// height is one below the activation height
rv.pushKV("active", DeploymentActiveAfter(active_chain_tip, params, dep));
rv.pushKV("height", params.DeploymentHeight(dep));
softforks.pushKV(DeploymentName(dep), rv);
}
static void SoftForkDescPushBack(const CBlockIndex *active_chain_tip,
UniValue &softforks,
const Consensus::Params &consensusParams,
Consensus::DeploymentPos id) {
// For BIP9 deployments.
// Deployments (e.g. testdummy) with timeout value before Jan 1, 2009 are
// hidden. A timeout value of 0 guarantees a softfork will never be
// activated. This is used when merging logic to implement a proposed
// softfork without a specified deployment schedule.
if (consensusParams.vDeployments[id].nTimeout <= 1230768000) {
return;
}
UniValue bip9(UniValue::VOBJ);
const ThresholdState thresholdState =
g_versionbitscache.State(active_chain_tip, consensusParams, id);
switch (thresholdState) {
case ThresholdState::DEFINED:
bip9.pushKV("status", "defined");
break;
case ThresholdState::STARTED:
bip9.pushKV("status", "started");
break;
case ThresholdState::LOCKED_IN:
bip9.pushKV("status", "locked_in");
break;
case ThresholdState::ACTIVE:
bip9.pushKV("status", "active");
break;
case ThresholdState::FAILED:
bip9.pushKV("status", "failed");
break;
}
if (ThresholdState::STARTED == thresholdState) {
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
}
bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
int64_t since_height = g_versionbitscache.StateSinceHeight(
active_chain_tip, consensusParams, id);
bip9.pushKV("since", since_height);
if (ThresholdState::STARTED == thresholdState) {
UniValue statsUV(UniValue::VOBJ);
BIP9Stats statsStruct = g_versionbitscache.Statistics(
active_chain_tip, consensusParams, id);
statsUV.pushKV("period", statsStruct.period);
statsUV.pushKV("threshold", statsStruct.threshold);
statsUV.pushKV("elapsed", statsStruct.elapsed);
statsUV.pushKV("count", statsStruct.count);
statsUV.pushKV("possible", statsStruct.possible);
bip9.pushKV("statistics", statsUV);
}
UniValue rv(UniValue::VOBJ);
rv.pushKV("type", "bip9");
rv.pushKV("bip9", bip9);
if (ThresholdState::ACTIVE == thresholdState) {
rv.pushKV("height", since_height);
}
rv.pushKV("active", ThresholdState::ACTIVE == thresholdState);
softforks.pushKV(DeploymentName(id), rv);
}
RPCHelpMan getblockchaininfo() {
return RPCHelpMan{
"getblockchaininfo",
"Returns an object containing various state info regarding blockchain "
"processing.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "chain",
"current network name (main, test, regtest)"},
{RPCResult::Type::NUM, "blocks",
"the height of the most-work fully-validated chain. The "
"genesis block has height 0"},
{RPCResult::Type::NUM, "headers",
"the current number of headers we have validated"},
{RPCResult::Type::STR, "bestblockhash",
"the hash of the currently best block"},
{RPCResult::Type::NUM, "difficulty", "the current difficulty"},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "verificationprogress",
"estimate of verification progress [0..1]"},
{RPCResult::Type::BOOL, "initialblockdownload",
"(debug information) estimate of whether this node is in "
"Initial Block Download mode"},
{RPCResult::Type::STR_HEX, "chainwork",
"total amount of work in active chain, in hexadecimal"},
{RPCResult::Type::NUM, "size_on_disk",
"the estimated size of the block and undo files on disk"},
{RPCResult::Type::BOOL, "pruned",
"if the blocks are subject to pruning"},
{RPCResult::Type::NUM, "pruneheight",
"lowest-height complete block stored (only present if pruning "
"is enabled)"},
{RPCResult::Type::BOOL, "automatic_pruning",
"whether automatic pruning is enabled (only present if "
"pruning is enabled)"},
{RPCResult::Type::NUM, "prune_target_size",
"the target size used by pruning (only present if automatic "
"pruning is enabled)"},
{RPCResult::Type::OBJ_DYN,
"softforks",
"status of softforks",
{
{RPCResult::Type::OBJ,
"xxxx",
"name of the softfork",
{
{RPCResult::Type::STR, "type",
"one of \"buried\", \"bip9\""},
{RPCResult::Type::OBJ,
"bip9",
"status of bip9 softforks (only for \"bip9\" type)",
{
{RPCResult::Type::STR, "status",
"one of \"defined\", \"started\", "
"\"locked_in\", \"active\", \"failed\""},
{RPCResult::Type::NUM, "bit",
"the bit (0-28) in the block version field "
"used to signal this softfork (only for "
"\"started\" status)"},
{RPCResult::Type::NUM_TIME, "start_time",
"the minimum median time past of a block at "
"which the bit gains its meaning"},
{RPCResult::Type::NUM_TIME, "timeout",
"the median time past of a block at which the "
"deployment is considered failed if not yet "
"locked in"},
{RPCResult::Type::NUM, "since",
"height of the first block to which the status "
"applies"},
{RPCResult::Type::OBJ,
"statistics",
"numeric statistics about BIP9 signalling for "
"a softfork",
{
{RPCResult::Type::NUM, "period",
"the length in blocks of the BIP9 "
"signalling period"},
{RPCResult::Type::NUM, "threshold",
"the number of blocks with the version "
"bit set required to activate the "
"feature"},
{RPCResult::Type::NUM, "elapsed",
"the number of blocks elapsed since the "
"beginning of the current period"},
{RPCResult::Type::NUM, "count",
"the number of blocks with the version "
"bit set in the current period"},
{RPCResult::Type::BOOL, "possible",
"returns false if there are not enough "
"blocks left in this period to pass "
"activation threshold"},
}},
}},
{RPCResult::Type::NUM, "height",
"height of the first block which the rules are or "
"will be enforced (only for \"buried\" type, or "
"\"bip9\" type with \"active\" status)"},
{RPCResult::Type::BOOL, "active",
"true if the rules are enforced for the mempool and "
"the next block"},
}},
}},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getblockchaininfo", "") +
HelpExampleRpc("getblockchaininfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const CChainParams &chainparams = config.GetChainParams();
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChainState &active_chainstate = chainman.ActiveChainstate();
const CBlockIndex *tip = active_chainstate.m_chain.Tip();
CHECK_NONFATAL(tip);
const int height = tip->nHeight;
UniValue obj(UniValue::VOBJ);
obj.pushKV("chain", chainparams.NetworkIDString());
obj.pushKV("blocks", height);
obj.pushKV("headers",
pindexBestHeader ? pindexBestHeader->nHeight : -1);
obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
obj.pushKV("difficulty", double(GetDifficulty(tip)));
obj.pushKV("time", tip->GetBlockTime());
obj.pushKV("mediantime", tip->GetMedianTimePast());
obj.pushKV("verificationprogress",
GuessVerificationProgress(Params().TxData(), tip));
obj.pushKV("initialblockdownload",
active_chainstate.IsInitialBlockDownload());
obj.pushKV("chainwork", tip->nChainWork.GetHex());
obj.pushKV("size_on_disk",
chainman.m_blockman.CalculateCurrentUsage());
obj.pushKV("pruned", fPruneMode);
if (fPruneMode) {
const CBlockIndex *block = tip;
CHECK_NONFATAL(block);
while (block->pprev && (block->pprev->nStatus.hasData())) {
block = block->pprev;
}
obj.pushKV("pruneheight", block->nHeight);
// if 0, execution bypasses the whole if block.
bool automatic_pruning = (gArgs.GetIntArg("-prune", 0) != 1);
obj.pushKV("automatic_pruning", automatic_pruning);
if (automatic_pruning) {
obj.pushKV("prune_target_size", nPruneTarget);
}
}
UniValue softforks(UniValue::VOBJ);
for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS;
i++) {
SoftForkDescPushBack(tip, softforks, chainparams.GetConsensus(),
Consensus::DeploymentPos(i));
}
obj.pushKV("softforks", softforks);
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
};
}
/** Comparison function for sorting the getchaintips heads. */
struct CompareBlocksByHeight {
bool operator()(const CBlockIndex *a, const CBlockIndex *b) const {
// Make sure that unequal blocks with the same height do not compare
// equal. Use the pointers themselves to make a distinction.
if (a->nHeight != b->nHeight) {
return (a->nHeight > b->nHeight);
}
return a < b;
}
};
static RPCHelpMan getchaintips() {
return RPCHelpMan{
"getchaintips",
"Return information about all known tips in the block tree, including "
"the main chain as well as orphaned branches.\n",
{},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height", "height of the chain tip"},
{RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
{RPCResult::Type::NUM, "branchlen",
"zero for main chain, otherwise length of branch connecting "
"the tip to the main chain"},
{RPCResult::Type::STR, "status",
"status of the chain, \"active\" for the main chain\n"
"Possible values for status:\n"
"1. \"invalid\" This branch contains at "
"least one invalid block\n"
"2. \"parked\" This branch contains at "
"least one parked block\n"
"3. \"headers-only\" Not all blocks for this "
"branch are available, but the headers are valid\n"
"4. \"valid-headers\" All blocks are available for "
"this branch, but they were never fully validated\n"
"5. \"valid-fork\" This branch is not part of "
"the active chain, but is fully validated\n"
"6. \"active\" This is the tip of the "
"active main chain, which is certainly valid"},
}}}},
RPCExamples{HelpExampleCli("getchaintips", "") +
HelpExampleRpc("getchaintips", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChain &active_chain = chainman.ActiveChain();
/**
* Idea: The set of chain tips is the active chain tip, plus orphan
* blocks which do not have another orphan building off of them.
* Algorithm:
* - Make one pass through BlockIndex(), picking out the orphan
* blocks, and also storing a set of the orphan block's pprev
* pointers.
* - Iterate through the orphan blocks. If the block isn't pointed
* to by another orphan, it is a chain tip.
* - Add the active chain tip
*/
std::set<const CBlockIndex *, CompareBlocksByHeight> setTips;
std::set<const CBlockIndex *> setOrphans;
std::set<const CBlockIndex *> setPrevs;
for (const std::pair<const BlockHash, CBlockIndex *> &item :
chainman.BlockIndex()) {
if (!active_chain.Contains(item.second)) {
setOrphans.insert(item.second);
setPrevs.insert(item.second->pprev);
}
}
for (std::set<const CBlockIndex *>::iterator it =
setOrphans.begin();
it != setOrphans.end(); ++it) {
if (setPrevs.erase(*it) == 0) {
setTips.insert(*it);
}
}
// Always report the currently active tip.
setTips.insert(active_chain.Tip());
/* Construct the output array. */
UniValue res(UniValue::VARR);
for (const CBlockIndex *block : setTips) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("height", block->nHeight);
obj.pushKV("hash", block->phashBlock->GetHex());
const int branchLen =
block->nHeight - active_chain.FindFork(block)->nHeight;
obj.pushKV("branchlen", branchLen);
std::string status;
if (active_chain.Contains(block)) {
// This block is part of the currently active chain.
status = "active";
} else if (block->nStatus.isInvalid()) {
// This block or one of its ancestors is invalid.
status = "invalid";
} else if (block->nStatus.isOnParkedChain()) {
// This block or one of its ancestors is parked.
status = "parked";
} else if (!block->HaveTxsDownloaded()) {
// This block cannot be connected because full block data
// for it or one of its parents is missing.
status = "headers-only";
} else if (block->IsValid(BlockValidity::SCRIPTS)) {
// This block is fully validated, but no longer part of the
// active chain. It was probably the active block once, but
// was reorganized.
status = "valid-fork";
} else if (block->IsValid(BlockValidity::TREE)) {
// The headers for this block are valid, but it has not been
// validated. It was probably never part of the most-work
// chain.
status = "valid-headers";
} else {
// No clue.
status = "unknown";
}
obj.pushKV("status", status);
res.push_back(obj);
}
return res;
},
};
}
UniValue MempoolInfoToJSON(const CTxMemPool &pool) {
// Make sure this call is atomic in the pool.
LOCK(pool.cs);
UniValue ret(UniValue::VOBJ);
ret.pushKV("loaded", pool.IsLoaded());
ret.pushKV("size", (int64_t)pool.size());
ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
ret.pushKV("total_fee", pool.GetTotalFee());
size_t maxmempool =
gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
ret.pushKV("maxmempool", (int64_t)maxmempool);
ret.pushKV(
"mempoolminfee",
std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK());
ret.pushKV("minrelaytxfee", ::minRelayTxFee.GetFeePerK());
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
return ret;
}
static RPCHelpMan getmempoolinfo() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"getmempoolinfo",
"Returns details on the active state of the TX memory pool.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "loaded",
"True if the mempool is fully loaded"},
{RPCResult::Type::NUM, "size", "Current tx count"},
{RPCResult::Type::NUM, "bytes", "Sum of all transaction sizes"},
{RPCResult::Type::NUM, "usage",
"Total memory usage for the mempool"},
{RPCResult::Type::NUM, "maxmempool",
"Maximum memory usage for the mempool"},
{RPCResult::Type::STR_AMOUNT, "total_fee",
"Total fees for the mempool in " + ticker +
", ignoring modified fees through prioritizetransaction"},
{RPCResult::Type::STR_AMOUNT, "mempoolminfee",
"Minimum fee rate in " + ticker +
"/kB for tx to be accepted. Is the maximum of "
"minrelaytxfee and minimum mempool fee"},
{RPCResult::Type::STR_AMOUNT, "minrelaytxfee",
"Current minimum relay fee for transactions"},
{RPCResult::Type::NUM, "unbroadcastcount",
"Current number of transactions that haven't passed initial "
"broadcast yet"},
}},
RPCExamples{HelpExampleCli("getmempoolinfo", "") +
HelpExampleRpc("getmempoolinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
return MempoolInfoToJSON(EnsureAnyMemPool(request.context));
},
};
}
static RPCHelpMan preciousblock() {
return RPCHelpMan{
"preciousblock",
"Treats a block as if it were received before others with the same "
"work.\n"
"\nA later preciousblock call can override the effect of an earlier "
"one.\n"
"\nThe effects of preciousblock are not retained across restarts.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as precious"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("preciousblock", "\"blockhash\"") +
HelpExampleRpc("preciousblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
CBlockIndex *pblockindex;
ChainstateManager &chainman = EnsureAnyChainman(request.context);
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
BlockValidationState state;
chainman.ActiveChainstate().PreciousBlock(config, state,
pblockindex);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
return NullUniValue;
},
};
}
RPCHelpMan finalizeblock() {
return RPCHelpMan{
"finalizeblock",
"Treats a block as final. It cannot be reorged. Any chain\n"
"that does not contain this block is invalid. Used on a less\n"
"work chain, it can effectively PUT YOU OUT OF CONSENSUS.\n"
"USE WITH CAUTION!\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as invalid"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("finalizeblock", "\"blockhash\"") +
HelpExampleRpc("finalizeblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::string strHash = request.params[0].get_str();
BlockHash hash(uint256S(strHash));
BlockValidationState state;
ChainstateManager &chainman = EnsureAnyChainman(request.context);
CBlockIndex *pblockindex = nullptr;
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
} // end of locked cs_main scope
chainman.ActiveChainstate().FinalizeBlock(config, state,
pblockindex);
if (state.IsValid()) {
chainman.ActiveChainstate().ActivateBestChain(config, state);
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
return NullUniValue;
},
};
}
static RPCHelpMan invalidateblock() {
return RPCHelpMan{
"invalidateblock",
"Permanently marks a block as invalid, as if it violated a consensus "
"rule.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as invalid"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("invalidateblock", "\"blockhash\"") +
HelpExampleRpc("invalidateblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
BlockValidationState state;
ChainstateManager &chainman = EnsureAnyChainman(request.context);
CBlockIndex *pblockindex;
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
chainman.ActiveChainstate().InvalidateBlock(config, state,
pblockindex);
if (state.IsValid()) {
chainman.ActiveChainstate().ActivateBestChain(config, state);
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
return NullUniValue;
},
};
}
RPCHelpMan parkblock() {
return RPCHelpMan{
"parkblock",
"Marks a block as parked.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to park"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("parkblock", "\"blockhash\"") +
HelpExampleRpc("parkblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const std::string strHash = request.params[0].get_str();
const BlockHash hash(uint256S(strHash));
BlockValidationState state;
ChainstateManager &chainman = EnsureAnyChainman(request.context);
CBlockIndex *pblockindex = nullptr;
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
chainman.ActiveChainstate().ParkBlock(config, state, pblockindex);
if (state.IsValid()) {
chainman.ActiveChainstate().ActivateBestChain(config, state);
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
return NullUniValue;
},
};
}
static RPCHelpMan reconsiderblock() {
return RPCHelpMan{
"reconsiderblock",
"Removes invalidity status of a block, its ancestors and its"
"descendants, reconsider them for activation.\n"
"This can be used to undo the effects of invalidateblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to reconsider"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("reconsiderblock", "\"blockhash\"") +
HelpExampleRpc("reconsiderblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
{
LOCK(cs_main);
CBlockIndex *pblockindex =
chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
}
BlockValidationState state;
chainman.ActiveChainstate().ActivateBestChain(config, state);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
return NullUniValue;
},
};
}
RPCHelpMan unparkblock() {
return RPCHelpMan{
"unparkblock",
"Removes parked status of a block and its descendants, reconsider "
"them for activation.\n"
"This can be used to undo the effects of parkblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to unpark"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("unparkblock", "\"blockhash\"") +
HelpExampleRpc("unparkblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const std::string strHash = request.params[0].get_str();
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const BlockHash hash(uint256S(strHash));
{
LOCK(cs_main);
CBlockIndex *pblockindex =
chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
chainman.ActiveChainstate().UnparkBlockAndChildren(pblockindex);
}
BlockValidationState state;
chainman.ActiveChainstate().ActivateBestChain(config, state);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
return NullUniValue;
},
};
}
static RPCHelpMan getchaintxstats() {
return RPCHelpMan{
"getchaintxstats",
"Compute statistics about the total number and rate of transactions "
"in the chain.\n",
{
{"nblocks", RPCArg::Type::NUM, /* default */ "one month",
"Size of the window in number of blocks"},
{"blockhash", RPCArg::Type::STR_HEX, /* default */ "chain tip",
"The hash of the block that ends the window."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The timestamp for the final block in the window, "
"expressed in " +
UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "txcount",
"The total number of transactions in the chain up to "
"that point"},
{RPCResult::Type::STR_HEX, "window_final_block_hash",
"The hash of the final block in the window"},
{RPCResult::Type::NUM, "window_final_block_height",
"The height of the final block in the window."},
{RPCResult::Type::NUM, "window_block_count",
"Size of the window in number of blocks"},
{RPCResult::Type::NUM, "window_tx_count",
"The number of transactions in the window. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "window_interval",
"The elapsed time in the window in seconds. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "txrate",
"The average rate of transactions per second in the "
"window. Only returned if \"window_interval\" is > 0"},
}},
RPCExamples{HelpExampleCli("getchaintxstats", "") +
HelpExampleRpc("getchaintxstats", "2016")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const CBlockIndex *pindex;
// By default: 1 month
int blockcount =
30 * 24 * 60 * 60 /
config.GetChainParams().GetConsensus().nPowTargetSpacing;
if (request.params[1].isNull()) {
LOCK(cs_main);
pindex = chainman.ActiveTip();
} else {
BlockHash hash(ParseHashV(request.params[1], "blockhash"));
LOCK(cs_main);
pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!chainman.ActiveChain().Contains(pindex)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block is not in main chain");
}
}
CHECK_NONFATAL(pindex != nullptr);
if (request.params[0].isNull()) {
blockcount =
std::max(0, std::min(blockcount, pindex->nHeight - 1));
} else {
blockcount = request.params[0].get_int();
if (blockcount < 0 ||
(blockcount > 0 && blockcount >= pindex->nHeight)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid block count: "
"should be between 0 and "
"the block's height - 1");
}
}
const CBlockIndex *pindexPast =
pindex->GetAncestor(pindex->nHeight - blockcount);
int nTimeDiff =
pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast();
int nTxDiff =
pindex->GetChainTxCount() - pindexPast->GetChainTxCount();
UniValue ret(UniValue::VOBJ);
ret.pushKV("time", pindex->GetBlockTime());
ret.pushKV("txcount", pindex->GetChainTxCount());
ret.pushKV("window_final_block_hash",
pindex->GetBlockHash().GetHex());
ret.pushKV("window_final_block_height", pindex->nHeight);
ret.pushKV("window_block_count", blockcount);
if (blockcount > 0) {
ret.pushKV("window_tx_count", nTxDiff);
ret.pushKV("window_interval", nTimeDiff);
if (nTimeDiff > 0) {
ret.pushKV("txrate", double(nTxDiff) / nTimeDiff);
}
}
return ret;
},
};
}
template <typename T>
static T CalculateTruncatedMedian(std::vector<T> &scores) {
size_t size = scores.size();
if (size == 0) {
return T();
}
std::sort(scores.begin(), scores.end());
if (size % 2 == 0) {
return (scores[size / 2 - 1] + scores[size / 2]) / 2;
} else {
return scores[size / 2];
}
}
template <typename T> static inline bool SetHasKeys(const std::set<T> &set) {
return false;
}
template <typename T, typename Tk, typename... Args>
static inline bool SetHasKeys(const std::set<T> &set, const Tk &key,
const Args &...args) {
return (set.count(key) != 0) || SetHasKeys(set, args...);
}
// outpoint (needed for the utxo index) + nHeight + fCoinBase
static constexpr size_t PER_UTXO_OVERHEAD =
sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
static RPCHelpMan getblockstats() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"getblockstats",
"Compute per block statistics for a given window. All amounts are "
"in " +
ticker +
".\n"
"It won't work for some heights with pruning.\n",
{
{"hash_or_height",
RPCArg::Type::NUM,
RPCArg::Optional::NO,
"The block hash or height of the target block",
"",
{"", "string or numeric"}},
{"stats",
RPCArg::Type::ARR,
/* default */ "all values",
"Values to plot (see result below)",
{
{"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
{"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
},
"stats"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "avgfee", "Average fee in the block"},
{RPCResult::Type::NUM, "avgfeerate",
"Average feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "avgtxsize", "Average transaction size"},
{RPCResult::Type::STR_HEX, "blockhash",
"The block hash (to check for potential reorgs)"},
{RPCResult::Type::NUM, "height", "The height of the block"},
{RPCResult::Type::NUM, "ins",
"The number of inputs (excluding coinbase)"},
{RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"},
{RPCResult::Type::NUM, "maxfeerate",
"Maximum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"},
{RPCResult::Type::NUM, "medianfee",
"Truncated median fee in the block"},
{RPCResult::Type::NUM, "medianfeerate",
"Truncated median feerate (in " + ticker + " per byte)"},
{RPCResult::Type::NUM, "mediantime",
"The block median time past"},
{RPCResult::Type::NUM, "mediantxsize",
"Truncated median transaction size"},
{RPCResult::Type::NUM, "minfee", "Minimum fee in the block"},
{RPCResult::Type::NUM, "minfeerate",
"Minimum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"},
{RPCResult::Type::NUM, "outs", "The number of outputs"},
{RPCResult::Type::NUM, "subsidy", "The block subsidy"},
{RPCResult::Type::NUM, "time", "The block time"},
{RPCResult::Type::NUM, "total_out",
"Total amount in all outputs (excluding coinbase and thus "
"reward [ie subsidy + totalfee])"},
{RPCResult::Type::NUM, "total_size",
"Total size of all non-coinbase transactions"},
{RPCResult::Type::NUM, "totalfee", "The fee total"},
{RPCResult::Type::NUM, "txs",
"The number of transactions (including coinbase)"},
{RPCResult::Type::NUM, "utxo_increase",
"The increase/decrease in the number of unspent outputs"},
{RPCResult::Type::NUM, "utxo_size_inc",
"The increase/decrease in size for the utxo index (not "
"discounting op_return and similar)"},
}},
RPCExamples{
HelpExampleCli(
"getblockstats",
R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
HelpExampleCli("getblockstats",
R"(1000 '["minfeerate","avgfeerate"]')") +
HelpExampleRpc(
"getblockstats",
R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
HelpExampleRpc("getblockstats",
R"(1000, ["minfeerate","avgfeerate"])")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CBlockIndex *pindex{ParseHashOrHeight(request.params[0], chainman)};
CHECK_NONFATAL(pindex != nullptr);
std::set<std::string> stats;
if (!request.params[1].isNull()) {
const UniValue stats_univalue = request.params[1].get_array();
for (unsigned int i = 0; i < stats_univalue.size(); i++) {
const std::string stat = stats_univalue[i].get_str();
stats.insert(stat);
}
}
const CBlock block = GetBlockChecked(config, pindex);
const CBlockUndo blockUndo = GetUndoChecked(pindex);
// Calculate everything if nothing selected (default)
const bool do_all = stats.size() == 0;
const bool do_mediantxsize =
do_all || stats.count("mediantxsize") != 0;
const bool do_medianfee = do_all || stats.count("medianfee") != 0;
const bool do_medianfeerate =
do_all || stats.count("medianfeerate") != 0;
const bool loop_inputs =
do_all || do_medianfee || do_medianfeerate ||
SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee",
"avgfeerate", "minfee", "maxfee", "minfeerate",
"maxfeerate");
const bool loop_outputs =
do_all || loop_inputs || stats.count("total_out");
const bool do_calculate_size =
do_mediantxsize || loop_inputs ||
SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize",
"maxtxsize");
const int64_t blockMaxSize = config.GetMaxBlockSize();
Amount maxfee = Amount::zero();
Amount maxfeerate = Amount::zero();
Amount minfee = MAX_MONEY;
Amount minfeerate = MAX_MONEY;
Amount total_out = Amount::zero();
Amount totalfee = Amount::zero();
int64_t inputs = 0;
int64_t maxtxsize = 0;
int64_t mintxsize = blockMaxSize;
int64_t outputs = 0;
int64_t total_size = 0;
int64_t utxo_size_inc = 0;
std::vector<Amount> fee_array;
std::vector<Amount> feerate_array;
std::vector<int64_t> txsize_array;
for (size_t i = 0; i < block.vtx.size(); ++i) {
const auto &tx = block.vtx.at(i);
outputs += tx->vout.size();
Amount tx_total_out = Amount::zero();
if (loop_outputs) {
for (const CTxOut &out : tx->vout) {
tx_total_out += out.nValue;
utxo_size_inc +=
GetSerializeSize(out, PROTOCOL_VERSION) +
PER_UTXO_OVERHEAD;
}
}
if (tx->IsCoinBase()) {
continue;
}
// Don't count coinbase's fake input
inputs += tx->vin.size();
// Don't count coinbase reward
total_out += tx_total_out;
int64_t tx_size = 0;
if (do_calculate_size) {
tx_size = tx->GetTotalSize();
if (do_mediantxsize) {
txsize_array.push_back(tx_size);
}
maxtxsize = std::max(maxtxsize, tx_size);
mintxsize = std::min(mintxsize, tx_size);
total_size += tx_size;
}
if (loop_inputs) {
Amount tx_total_in = Amount::zero();
const auto &txundo = blockUndo.vtxundo.at(i - 1);
for (const Coin &coin : txundo.vprevout) {
const CTxOut &prevoutput = coin.GetTxOut();
tx_total_in += prevoutput.nValue;
utxo_size_inc -=
GetSerializeSize(prevoutput, PROTOCOL_VERSION) +
PER_UTXO_OVERHEAD;
}
Amount txfee = tx_total_in - tx_total_out;
CHECK_NONFATAL(MoneyRange(txfee));
if (do_medianfee) {
fee_array.push_back(txfee);
}
maxfee = std::max(maxfee, txfee);
minfee = std::min(minfee, txfee);
totalfee += txfee;
Amount feerate = txfee / tx_size;
if (do_medianfeerate) {
feerate_array.push_back(feerate);
}
maxfeerate = std::max(maxfeerate, feerate);
minfeerate = std::min(minfeerate, feerate);
}
}
UniValue ret_all(UniValue::VOBJ);
ret_all.pushKV("avgfee",
block.vtx.size() > 1
? (totalfee / int((block.vtx.size() - 1)))
: Amount::zero());
ret_all.pushKV("avgfeerate", total_size > 0
? (totalfee / total_size)
: Amount::zero());
ret_all.pushKV("avgtxsize",
(block.vtx.size() > 1)
? total_size / (block.vtx.size() - 1)
: 0);
ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex());
ret_all.pushKV("height", (int64_t)pindex->nHeight);
ret_all.pushKV("ins", inputs);
ret_all.pushKV("maxfee", maxfee);
ret_all.pushKV("maxfeerate", maxfeerate);
ret_all.pushKV("maxtxsize", maxtxsize);
ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
ret_all.pushKV("medianfeerate",
CalculateTruncatedMedian(feerate_array));
ret_all.pushKV("mediantime", pindex->GetMedianTimePast());
ret_all.pushKV("mediantxsize",
CalculateTruncatedMedian(txsize_array));
ret_all.pushKV("minfee",
minfee == MAX_MONEY ? Amount::zero() : minfee);
ret_all.pushKV("minfeerate", minfeerate == MAX_MONEY
? Amount::zero()
: minfeerate);
ret_all.pushKV("mintxsize",
mintxsize == blockMaxSize ? 0 : mintxsize);
ret_all.pushKV("outs", outputs);
ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight,
Params().GetConsensus()));
ret_all.pushKV("time", pindex->GetBlockTime());
ret_all.pushKV("total_out", total_out);
ret_all.pushKV("total_size", total_size);
ret_all.pushKV("totalfee", totalfee);
ret_all.pushKV("txs", (int64_t)block.vtx.size());
ret_all.pushKV("utxo_increase", outputs - inputs);
ret_all.pushKV("utxo_size_inc", utxo_size_inc);
if (do_all) {
return ret_all;
}
UniValue ret(UniValue::VOBJ);
for (const std::string &stat : stats) {
const UniValue &value = ret_all[stat];
if (value.isNull()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid selected statistic %s", stat));
}
ret.pushKV(stat, value);
}
return ret;
},
};
}
static RPCHelpMan savemempool() {
return RPCHelpMan{
"savemempool",
"Dumps the mempool to disk. It will fail until the previous dump is "
"fully loaded.\n",
{},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "filename",
"the directory and file where the mempool was saved"},
}},
RPCExamples{HelpExampleCli("savemempool", "") +
HelpExampleRpc("savemempool", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const CTxMemPool &mempool = EnsureAnyMemPool(request.context);
const NodeContext &node = EnsureAnyNodeContext(request.context);
if (!mempool.IsLoaded()) {
throw JSONRPCError(RPC_MISC_ERROR,
"The mempool was not loaded yet");
}
if (!DumpMempool(mempool)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Unable to dump mempool to disk");
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("filename",
fs::path((node.args->GetDataDirNet() / "mempool.dat"))
.u8string());
return ret;
},
};
}
namespace {
//! Search for a given set of pubkey scripts
static bool FindScriptPubKey(std::atomic<int> &scan_progress,
const std::atomic<bool> &should_abort,
int64_t &count, CCoinsViewCursor *cursor,
const std::set<CScript> &needles,
std::map<COutPoint, Coin> &out_results,
std::function<void()> &interruption_point) {
scan_progress = 0;
count = 0;
while (cursor->Valid()) {
COutPoint key;
Coin coin;
if (!cursor->GetKey(key) || !cursor->GetValue(coin)) {
return false;
}
if (++count % 8192 == 0) {
interruption_point();
if (should_abort) {
// allow to abort the scan via the abort reference
return false;
}
}
if (count % 256 == 0) {
// update progress reference every 256 item
const TxId &txid = key.GetTxId();
uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1);
scan_progress = int(high * 100.0 / 65536.0 + 0.5);
}
if (needles.count(coin.GetTxOut().scriptPubKey)) {
out_results.emplace(key, coin);
}
cursor->Next();
}
scan_progress = 100;
return true;
}
} // namespace
/** RAII object to prevent concurrency issue when scanning the txout set */
static std::atomic<int> g_scan_progress;
static std::atomic<bool> g_scan_in_progress;
static std::atomic<bool> g_should_abort_scan;
class CoinsViewScanReserver {
private:
bool m_could_reserve;
public:
explicit CoinsViewScanReserver() : m_could_reserve(false) {}
bool reserve() {
CHECK_NONFATAL(!m_could_reserve);
if (g_scan_in_progress.exchange(true)) {
return false;
}
m_could_reserve = true;
return true;
}
~CoinsViewScanReserver() {
if (m_could_reserve) {
g_scan_in_progress = false;
}
}
};
static RPCHelpMan scantxoutset() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"scantxoutset",
"Scans the unspent transaction output set for entries that match "
"certain output descriptors.\n"
"Examples of output descriptors are:\n"
" addr(<address>) Outputs whose scriptPubKey "
"corresponds to the specified address (does not include P2PK)\n"
" raw(<hex script>) Outputs whose scriptPubKey "
"equals the specified hex scripts\n"
" combo(<pubkey>) P2PK and P2PKH outputs for "
"the given pubkey\n"
" pkh(<pubkey>) P2PKH outputs for the given "
"pubkey\n"
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for "
"the given threshold and pubkeys\n"
"\nIn the above, <pubkey> either refers to a fixed public key in "
"hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
"or more path elements separated by \"/\", and optionally ending in "
"\"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
"unhardened or hardened child keys.\n"
"In the latter case, a range needs to be specified by below if "
"different from 1000.\n"
"For more information on output descriptors, see the documentation in "
"the doc/descriptors.md file.\n",
{
{"action", RPCArg::Type::STR, RPCArg::Optional::NO,
"The action to execute\n"
" \"start\" for starting a "
"scan\n"
" \"abort\" for aborting the "
"current scan (returns true when abort was successful)\n"
" \"status\" for "
"progress report (in %) of the current scan"},
{"scanobjects",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED,
"Array of scan objects. Required for \"start\" action\n"
" Every scan object is either a "
"string descriptor or an object:",
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"An output descriptor"},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"An object with output descriptor and metadata",
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
"An output descriptor"},
{"range", RPCArg::Type::RANGE, /* default */ "1000",
"The range of HD chain indexes to explore (either "
"end or [begin,end])"},
},
},
},
"[scanobjects,...]"},
},
{
RPCResult{"When action=='abort'", RPCResult::Type::BOOL, "", ""},
RPCResult{"When action=='status' and no scan is in progress",
RPCResult::Type::NONE, "", ""},
RPCResult{
"When action=='status' and scan is in progress",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "progress", "The scan progress"},
}},
RPCResult{
"When action=='start'",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "success",
"Whether the scan was completed"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs scanned"},
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::ARR,
"unspents",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::NUM, "vout", "The vout value"},
{RPCResult::Type::STR_HEX, "scriptPubKey",
"The script key"},
{RPCResult::Type::STR, "desc",
"A specialized descriptor for the matched "
"scriptPubKey"},
{RPCResult::Type::STR_AMOUNT, "amount",
"The total amount in " + ticker +
" of the unspent output"},
{RPCResult::Type::NUM, "height",
"Height of the unspent transaction output"},
}},
}},
{RPCResult::Type::STR_AMOUNT, "total_amount",
"The total amount of all found unspent outputs in " +
ticker},
}},
},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
UniValue result(UniValue::VOBJ);
if (request.params[0].get_str() == "status") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
return NullUniValue;
}
result.pushKV("progress", g_scan_progress.load());
return result;
} else if (request.params[0].get_str() == "abort") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// reserve was possible which means no scan was running
return false;
}
// set the abort flag
g_should_abort_scan = true;
return true;
} else if (request.params[0].get_str() == "start") {
CoinsViewScanReserver reserver;
if (!reserver.reserve()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Scan already in progress, use action "
"\"abort\" or \"status\"");
}
if (request.params.size() < 2) {
throw JSONRPCError(RPC_MISC_ERROR,
"scanobjects argument is required for "
"the start action");
}
std::set<CScript> needles;
std::map<CScript, std::string> descriptors;
Amount total_in = Amount::zero();
// loop through the scan objects
for (const UniValue &scanobject :
request.params[1].get_array().getValues()) {
FlatSigningProvider provider;
auto scripts =
EvalDescriptorStringOrObject(scanobject, provider);
for (const auto &script : scripts) {
std::string inferred =
InferDescriptor(script, provider)->ToString();
needles.emplace(script);
descriptors.emplace(std::move(script),
std::move(inferred));
}
}
// Scan the unspent transaction output set for inputs
UniValue unspents(UniValue::VARR);
std::vector<CTxOut> input_txos;
std::map<COutPoint, Coin> coins;
g_should_abort_scan = false;
g_scan_progress = 0;
int64_t count = 0;
std::unique_ptr<CCoinsViewCursor> pcursor;
CBlockIndex *tip;
NodeContext &node = EnsureAnyNodeContext(request.context);
{
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
CChainState &active_chainstate =
chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
pcursor = std::unique_ptr<CCoinsViewCursor>(
active_chainstate.CoinsDB().Cursor());
CHECK_NONFATAL(pcursor);
tip = active_chainstate.m_chain.Tip();
CHECK_NONFATAL(tip);
}
bool res = FindScriptPubKey(
g_scan_progress, g_should_abort_scan, count, pcursor.get(),
needles, coins, node.rpc_interruption_point);
result.pushKV("success", res);
result.pushKV("txouts", count);
result.pushKV("height", tip->nHeight);
result.pushKV("bestblock", tip->GetBlockHash().GetHex());
for (const auto &it : coins) {
const COutPoint &outpoint = it.first;
const Coin &coin = it.second;
const CTxOut &txo = coin.GetTxOut();
input_txos.push_back(txo);
total_in += txo.nValue;
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.GetTxId().GetHex());
unspent.pushKV("vout", int32_t(outpoint.GetN()));
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", txo.nValue);
unspent.pushKV("height", int32_t(coin.GetHeight()));
unspents.push_back(unspent);
}
result.pushKV("unspents", unspents);
result.pushKV("total_amount", total_in);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
}
return result;
},
};
}
static RPCHelpMan getblockfilter() {
return RPCHelpMan{
"getblockfilter",
"Retrieve a BIP 157 content filter for a particular block.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hash of the block"},
{"filtertype", RPCArg::Type::STR, /*default*/ "basic",
"The type name of the filter"},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "filter",
"the hex-encoded filter data"},
{RPCResult::Type::STR_HEX, "header",
"the hex-encoded filter header"},
}},
RPCExamples{
HelpExampleCli("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" \"basic\"") +
HelpExampleRpc("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214adbda81d7"
"e2a3dd146f6ed09\", \"basic\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const BlockHash block_hash(
ParseHashV(request.params[0], "blockhash"));
std::string filtertype_name = "basic";
if (!request.params[1].isNull()) {
filtertype_name = request.params[1].get_str();
}
BlockFilterType filtertype;
if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Unknown filtertype");
}
BlockFilterIndex *index = GetBlockFilterIndex(filtertype);
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR,
"Index is not enabled for filtertype " +
filtertype_name);
}
const CBlockIndex *block_index;
bool block_was_connected;
{
ChainstateManager &chainman =
EnsureAnyChainman(request.context);
LOCK(cs_main);
block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
if (!block_index) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
block_was_connected =
block_index->IsValid(BlockValidity::SCRIPTS);
}
bool index_ready = index->BlockUntilSyncedToCurrentChain();
BlockFilter filter;
uint256 filter_header;
if (!index->LookupFilter(block_index, filter) ||
!index->LookupFilterHeader(block_index, filter_header)) {
int err_code;
std::string errmsg = "Filter not found.";
if (!block_was_connected) {
err_code = RPC_INVALID_ADDRESS_OR_KEY;
errmsg += " Block was not connected to active chain.";
} else if (!index_ready) {
err_code = RPC_MISC_ERROR;
errmsg += " Block filters are still in the process of "
"being indexed.";
} else {
err_code = RPC_INTERNAL_ERROR;
errmsg += " This error is unexpected and indicates index "
"corruption.";
}
throw JSONRPCError(err_code, errmsg);
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
ret.pushKV("header", filter_header.GetHex());
return ret;
},
};
}
/**
* Serialize the UTXO set to a file for loading elsewhere.
*
* @see SnapshotMetadata
*/
static RPCHelpMan dumptxoutset() {
return RPCHelpMan{
"dumptxoutset",
"Write the serialized UTXO set to disk.\n",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO,
"path to the output file. If relative, will be prefixed by "
"datadir."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "coins_written",
"the number of coins written in the snapshot"},
{RPCResult::Type::STR_HEX, "base_hash",
"the hash of the base of the snapshot"},
{RPCResult::Type::NUM, "base_height",
"the height of the base of the snapshot"},
{RPCResult::Type::STR, "path",
"the absolute path that the snapshot was written to"},
}},
RPCExamples{HelpExampleCli("dumptxoutset", "utxo.dat")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const fs::path path = fsbridge::AbsPathJoin(
gArgs.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
// Write to a temporary path and then move into `path` on completion
// to avoid confusion due to an interruption.
const fs::path temppath = fsbridge::AbsPathJoin(
gArgs.GetDataDirNet(),
fs::u8path(request.params[0].get_str() + ".incomplete"));
if (fs::exists(path)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
path.u8string() +
" already exists. If you are sure this "
"is what you want, "
"move it out of the way first");
}
FILE *file{fsbridge::fopen(temppath, "wb")};
CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
NodeContext &node = EnsureAnyNodeContext(request.context);
UniValue result = CreateUTXOSnapshot(
node, node.chainman->ActiveChainstate(), afile);
fs::rename(temppath, path);
result.pushKV("path", path.u8string());
return result;
},
};
}
UniValue CreateUTXOSnapshot(NodeContext &node, CChainState &chainstate,
CAutoFile &afile) {
std::unique_ptr<CCoinsViewCursor> pcursor;
CCoinsStats stats{CoinStatsHashType::NONE};
CBlockIndex *tip;
{
// We need to lock cs_main to ensure that the coinsdb isn't
// written to between (i) flushing coins cache to disk
// (coinsdb), (ii) getting stats based upon the coinsdb, and
// (iii) constructing a cursor to the coinsdb for use below this
// block.
//
// Cursors returned by leveldb iterate over snapshots, so the
// contents of the pcursor will not be affected by simultaneous
// writes during use below this block.
//
// See discussion here:
// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
//
LOCK(::cs_main);
chainstate.ForceFlushStateToDisk();
if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats,
node.rpc_interruption_point)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
pcursor =
std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor());
tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock);
CHECK_NONFATAL(tip);
}
SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count,
uint64_t(tip->GetChainTxCount())};
afile << metadata;
COutPoint key;
Coin coin;
unsigned int iter{0};
while (pcursor->Valid()) {
if (iter % 5000 == 0) {
node.rpc_interruption_point();
}
++iter;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
afile << key;
afile << coin;
}
pcursor->Next();
}
afile.fclose();
UniValue result(UniValue::VOBJ);
result.pushKV("coins_written", stats.coins_count);
result.pushKV("base_hash", tip->GetBlockHash().ToString());
result.pushKV("base_height", tip->nHeight);
return result;
}
void RegisterBlockchainRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ------------------ ----------------------
{ "blockchain", getbestblockhash, },
{ "blockchain", getblock, },
{ "blockchain", getblockchaininfo, },
{ "blockchain", getblockcount, },
{ "blockchain", getblockhash, },
{ "blockchain", getblockheader, },
{ "blockchain", getblockstats, },
{ "blockchain", getchaintips, },
{ "blockchain", getchaintxstats, },
{ "blockchain", getdifficulty, },
{ "blockchain", getmempoolancestors, },
{ "blockchain", getmempooldescendants, },
{ "blockchain", getmempoolentry, },
{ "blockchain", getmempoolinfo, },
{ "blockchain", getrawmempool, },
{ "blockchain", gettxout, },
{ "blockchain", gettxoutsetinfo, },
{ "blockchain", pruneblockchain, },
{ "blockchain", savemempool, },
{ "blockchain", verifychain, },
{ "blockchain", preciousblock, },
{ "blockchain", scantxoutset, },
{ "blockchain", getblockfilter, },
/* Not shown in help */
{ "hidden", getfinalizedblockhash, },
{ "hidden", finalizeblock, },
{ "hidden", invalidateblock, },
{ "hidden", parkblock, },
{ "hidden", reconsiderblock, },
{ "hidden", syncwithvalidationinterfacequeue, },
{ "hidden", dumptxoutset, },
{ "hidden", unparkblock, },
{ "hidden", waitfornewblock, },
{ "hidden", waitforblock, },
{ "hidden", waitforblockheight, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index 47aa1f00f..5dcab93c4 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -1,68 +1,62 @@
// Copyright (c) 2017-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_RPC_BLOCKCHAIN_H
#define BITCOIN_RPC_BLOCKCHAIN_H
#include <streams.h>
#include <sync.h>
#include <univalue.h>
#include <any>
class CBlock;
class CBlockIndex;
class CChainState;
class ChainstateManager;
class CTxMemPool;
class RPCHelpMan;
struct NodeContext;
extern RecursiveMutex cs_main;
RPCHelpMan getblockchaininfo();
/**
* Get the required difficulty of the next block w/r/t the given block index.
*
* @return A floating point number that is a multiple of the main net minimum
* difficulty (4295032833 hashes).
*/
double GetDifficulty(const CBlockIndex *blockindex);
/** Callback for when block tip changed. */
void RPCNotifyBlockChange(const CBlockIndex *pindex);
/** Block description to JSON */
UniValue blockToJSON(const CBlock &block, const CBlockIndex *tip,
const CBlockIndex *blockindex, bool txDetails = false)
LOCKS_EXCLUDED(cs_main);
/** Mempool information to JSON */
UniValue MempoolInfoToJSON(const CTxMemPool &pool);
/** Mempool to JSON */
UniValue MempoolToJSON(const CTxMemPool &pool, bool verbose = false,
bool include_mempool_sequence = false);
/** Block header to JSON */
UniValue blockheaderToJSON(const CBlockIndex *tip,
const CBlockIndex *blockindex)
LOCKS_EXCLUDED(cs_main);
-NodeContext &EnsureAnyNodeContext(const std::any &context);
-CTxMemPool &EnsureMemPool(const NodeContext &node);
-CTxMemPool &EnsureAnyMemPool(const std::any &context);
-ChainstateManager &EnsureChainman(const NodeContext &node);
-ChainstateManager &EnsureAnyChainman(const std::any &context);
-
/**
* Helper to create UTXO snapshots given a chainstate and a file handle.
* @return a UniValue map containing metadata about the snapshot.
*/
UniValue CreateUTXOSnapshot(NodeContext &node, CChainState &chainstate,
CAutoFile &afile);
#endif // BITCOIN_RPC_BLOCKCHAIN_H
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index d1e4d5247..1b546bdee 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -1,1247 +1,1247 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <blockvalidity.h>
#include <cashaddrenc.h>
#include <chain.h>
#include <chainparams.h>
#include <config.h>
#include <consensus/activation.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <key_io.h>
#include <miner.h>
#include <minerfund.h>
#include <net.h>
#include <node/context.h>
#include <policy/policy.h>
#include <pow/pow.h>
#include <rpc/blockchain.h>
#include <rpc/mining.h>
-#include <rpc/net.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <shutdown.h>
#include <txmempool.h>
#include <univalue.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <warnings.h>
#include <cstdint>
/**
* Return average network hashes per second based on the last 'lookup' blocks,
* or from the last difficulty change if 'lookup' is nonpositive. If 'height' is
* nonnegative, compute the estimate at the time when a given block was found.
*/
static UniValue GetNetworkHashPS(int lookup, int height,
const CChain &active_chain) {
const CBlockIndex *pb = active_chain.Tip();
if (height >= 0 && height < active_chain.Height()) {
pb = active_chain[height];
}
if (pb == nullptr || !pb->nHeight) {
return 0;
}
// If lookup is -1, then use blocks since last difficulty change.
if (lookup <= 0) {
lookup = pb->nHeight %
Params().GetConsensus().DifficultyAdjustmentInterval() +
1;
}
// If lookup is larger than chain, then set it to chain length.
if (lookup > pb->nHeight) {
lookup = pb->nHeight;
}
const CBlockIndex *pb0 = pb;
int64_t minTime = pb0->GetBlockTime();
int64_t maxTime = minTime;
for (int i = 0; i < lookup; i++) {
pb0 = pb0->pprev;
int64_t time = pb0->GetBlockTime();
minTime = std::min(time, minTime);
maxTime = std::max(time, maxTime);
}
// In case there's a situation where minTime == maxTime, we don't want a
// divide by zero exception.
if (minTime == maxTime) {
return 0;
}
arith_uint256 workDiff = pb->nChainWork - pb0->nChainWork;
int64_t timeDiff = maxTime - minTime;
return workDiff.getdouble() / timeDiff;
}
static RPCHelpMan getnetworkhashps() {
return RPCHelpMan{
"getnetworkhashps",
"Returns the estimated network hashes per second based on the last n "
"blocks.\n"
"Pass in [blocks] to override # of blocks, -1 specifies since last "
"difficulty change.\n"
"Pass in [height] to estimate the network speed at the time when a "
"certain block was found.\n",
{
{"nblocks", RPCArg::Type::NUM, /* default */ "120",
"The number of blocks, or -1 for blocks since last difficulty "
"change."},
{"height", RPCArg::Type::NUM, /* default */ "-1",
"To estimate at the time of the given height."},
},
RPCResult{RPCResult::Type::NUM, "", "Hashes per second estimated"},
RPCExamples{HelpExampleCli("getnetworkhashps", "") +
HelpExampleRpc("getnetworkhashps", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return GetNetworkHashPS(
!request.params[0].isNull() ? request.params[0].get_int() : 120,
!request.params[1].isNull() ? request.params[1].get_int() : -1,
chainman.ActiveChain());
},
};
}
static bool GenerateBlock(const Config &config, ChainstateManager &chainman,
CBlock &block, uint64_t &max_tries,
unsigned int &extra_nonce, BlockHash &block_hash) {
block_hash.SetNull();
const uint64_t nExcessiveBlockSize = config.GetMaxBlockSize();
{
LOCK(cs_main);
IncrementExtraNonce(&block, chainman.ActiveTip(), nExcessiveBlockSize,
extra_nonce);
}
const Consensus::Params ¶ms = config.GetChainParams().GetConsensus();
while (max_tries > 0 &&
block.nNonce < std::numeric_limits<uint32_t>::max() &&
!CheckProofOfWork(block.GetHash(), block.nBits, params) &&
!ShutdownRequested()) {
++block.nNonce;
--max_tries;
}
if (max_tries == 0 || ShutdownRequested()) {
return false;
}
if (block.nNonce == std::numeric_limits<uint32_t>::max()) {
return true;
}
std::shared_ptr<const CBlock> shared_pblock =
std::make_shared<const CBlock>(block);
if (!chainman.ProcessNewBlock(config, shared_pblock, true, nullptr)) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"ProcessNewBlock, block not accepted");
}
block_hash = block.GetHash();
return true;
}
static UniValue generateBlocks(const Config &config,
ChainstateManager &chainman,
const CTxMemPool &mempool,
const CScript &coinbase_script, int nGenerate,
uint64_t nMaxTries) {
int nHeightEnd = 0;
int nHeight = 0;
{
// Don't keep cs_main locked.
LOCK(cs_main);
nHeight = chainman.ActiveHeight();
nHeightEnd = nHeight + nGenerate;
}
unsigned int nExtraNonce = 0;
UniValue blockHashes(UniValue::VARR);
while (nHeight < nHeightEnd && !ShutdownRequested()) {
std::unique_ptr<CBlockTemplate> pblocktemplate(
BlockAssembler(config, chainman.ActiveChainstate(), mempool)
.CreateNewBlock(coinbase_script));
if (!pblocktemplate.get()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
}
CBlock *pblock = &pblocktemplate->block;
BlockHash block_hash;
if (!GenerateBlock(config, chainman, *pblock, nMaxTries, nExtraNonce,
block_hash)) {
break;
}
if (!block_hash.IsNull()) {
++nHeight;
blockHashes.push_back(block_hash.GetHex());
}
}
return blockHashes;
}
static bool getScriptFromDescriptor(const std::string &descriptor,
CScript &script, std::string &error) {
FlatSigningProvider key_provider;
const auto desc =
Parse(descriptor, key_provider, error, /* require_checksum = */ false);
if (desc) {
if (desc->IsRange()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Ranged descriptor not accepted. Maybe pass "
"through deriveaddresses first?");
}
FlatSigningProvider provider;
std::vector<CScript> scripts;
if (!desc->Expand(0, key_provider, scripts, provider)) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Cannot derive script without private keys"));
}
// Combo descriptors can have 2 scripts, so we can't just check
// scripts.size() == 1
CHECK_NONFATAL(scripts.size() > 0 && scripts.size() <= 2);
if (scripts.size() == 1) {
script = scripts.at(0);
} else {
// Else take the 2nd script, since it is p2pkh
script = scripts.at(1);
}
return true;
}
return false;
}
static RPCHelpMan generatetodescriptor() {
return RPCHelpMan{
"generatetodescriptor",
"Mine blocks immediately to a specified descriptor (before the RPC "
"call returns)\n",
{
{"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO,
"How many blocks are generated immediately."},
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO,
"The descriptor to send the newly generated bitcoin to."},
{"maxtries", RPCArg::Type::NUM,
/* default */ ToString(DEFAULT_MAX_TRIES),
"How many iterations to try."},
},
RPCResult{RPCResult::Type::ARR,
"",
"hashes of blocks generated",
{
{RPCResult::Type::STR_HEX, "", "blockhash"},
}},
RPCExamples{"\nGenerate 11 blocks to mydesc\n" +
HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const int num_blocks{request.params[0].get_int()};
const uint64_t max_tries{request.params[2].isNull()
? DEFAULT_MAX_TRIES
: request.params[2].get_int()};
CScript coinbase_script;
std::string error;
if (!getScriptFromDescriptor(request.params[1].get_str(),
coinbase_script, error)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}
NodeContext &node = EnsureAnyNodeContext(request.context);
const CTxMemPool &mempool = EnsureMemPool(node);
ChainstateManager &chainman = EnsureChainman(node);
return generateBlocks(config, chainman, mempool, coinbase_script,
num_blocks, max_tries);
},
};
}
static RPCHelpMan generate() {
return RPCHelpMan{"generate",
"has been replaced by the -generate cli option. Refer to "
"-help for more information.",
{},
{},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
throw JSONRPCError(RPC_METHOD_NOT_FOUND,
self.ToString());
}};
}
static RPCHelpMan generatetoaddress() {
return RPCHelpMan{
"generatetoaddress",
"Mine blocks immediately to a specified address before the "
"RPC call returns)\n",
{
{"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO,
"How many blocks are generated immediately."},
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The address to send the newly generated bitcoin to."},
{"maxtries", RPCArg::Type::NUM,
/* default */ ToString(DEFAULT_MAX_TRIES),
"How many iterations to try."},
},
RPCResult{RPCResult::Type::ARR,
"",
"hashes of blocks generated",
{
{RPCResult::Type::STR_HEX, "", "blockhash"},
}},
RPCExamples{
"\nGenerate 11 blocks to myaddress\n" +
HelpExampleCli("generatetoaddress", "11 \"myaddress\"") +
"If you are using the " PACKAGE_NAME " wallet, you can "
"get a new address to send the newly generated bitcoin to with:\n" +
HelpExampleCli("getnewaddress", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const int num_blocks{request.params[0].get_int()};
const uint64_t max_tries{request.params[2].isNull()
? DEFAULT_MAX_TRIES
: request.params[2].get_int64()};
CTxDestination destination = DecodeDestination(
request.params[1].get_str(), config.GetChainParams());
if (!IsValidDestination(destination)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Error: Invalid address");
}
NodeContext &node = EnsureAnyNodeContext(request.context);
const CTxMemPool &mempool = EnsureMemPool(node);
ChainstateManager &chainman = EnsureChainman(node);
CScript coinbase_script = GetScriptForDestination(destination);
return generateBlocks(config, chainman, mempool, coinbase_script,
num_blocks, max_tries);
},
};
}
static RPCHelpMan generateblock() {
return RPCHelpMan{
"generateblock",
"Mine a block with a set of ordered transactions immediately to a "
"specified address or descriptor (before the RPC call returns)\n",
{
{"output", RPCArg::Type::STR, RPCArg::Optional::NO,
"The address or descriptor to send the newly generated bitcoin "
"to."},
{
"transactions",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"An array of hex strings which are either txids or raw "
"transactions.\n"
"Txids must reference transactions currently in the mempool.\n"
"All transactions must be valid and in valid order, otherwise "
"the block will be rejected.",
{
{"rawtx/txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED, ""},
},
},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "hash of generated block"},
}},
RPCExamples{
"\nGenerate a block to myaddress, with txs rawtx and "
"mempool_txid\n" +
HelpExampleCli("generateblock",
R"("myaddress" '["rawtx", "mempool_txid"]')")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const auto address_or_descriptor = request.params[0].get_str();
CScript coinbase_script;
std::string error;
const CChainParams &chainparams = config.GetChainParams();
if (!getScriptFromDescriptor(address_or_descriptor, coinbase_script,
error)) {
const auto destination =
DecodeDestination(address_or_descriptor, chainparams);
if (!IsValidDestination(destination)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Error: Invalid address or descriptor");
}
coinbase_script = GetScriptForDestination(destination);
}
NodeContext &node = EnsureAnyNodeContext(request.context);
const CTxMemPool &mempool = EnsureMemPool(node);
std::vector<CTransactionRef> txs;
const auto raw_txs_or_txids = request.params[1].get_array();
for (size_t i = 0; i < raw_txs_or_txids.size(); i++) {
const auto str(raw_txs_or_txids[i].get_str());
uint256 hash;
CMutableTransaction mtx;
if (ParseHashStr(str, hash)) {
const auto tx = mempool.get(TxId(hash));
if (!tx) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Transaction %s not in mempool.", str));
}
txs.emplace_back(tx);
} else if (DecodeHexTx(mtx, str)) {
txs.push_back(MakeTransactionRef(std::move(mtx)));
} else {
throw JSONRPCError(
RPC_DESERIALIZATION_ERROR,
strprintf("Transaction decode failed for %s", str));
}
}
CBlock block;
ChainstateManager &chainman = EnsureChainman(node);
{
LOCK(cs_main);
CTxMemPool empty_mempool;
std::unique_ptr<CBlockTemplate> blocktemplate(
BlockAssembler(config, chainman.ActiveChainstate(),
empty_mempool)
.CreateNewBlock(coinbase_script));
if (!blocktemplate) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Couldn't create new block");
}
block = blocktemplate->block;
}
CHECK_NONFATAL(block.vtx.size() == 1);
// Add transactions
block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
{
LOCK(cs_main);
BlockValidationState state;
if (!TestBlockValidity(state, chainparams,
chainman.ActiveChainstate(), block,
chainman.m_blockman.LookupBlockIndex(
block.hashPrevBlock),
BlockValidationOptions(config)
.withCheckPoW(false)
.withCheckMerkleRoot(false))) {
throw JSONRPCError(RPC_VERIFY_ERROR,
strprintf("TestBlockValidity failed: %s",
state.ToString()));
}
}
BlockHash block_hash;
uint64_t max_tries{DEFAULT_MAX_TRIES};
unsigned int extra_nonce{0};
if (!GenerateBlock(config, chainman, block, max_tries, extra_nonce,
block_hash) ||
block_hash.IsNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block.");
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("hash", block_hash.GetHex());
return obj;
},
};
}
static RPCHelpMan getmininginfo() {
return RPCHelpMan{
"getmininginfo",
"Returns a json object containing mining-related "
"information.",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "blocks", "The current block"},
{RPCResult::Type::NUM, "currentblocksize", /* optional */ true,
"The block size of the last assembled block (only present if "
"a block was ever assembled)"},
{RPCResult::Type::NUM, "currentblocktx", /* optional */ true,
"The number of block transactions of the last assembled block "
"(only present if a block was ever assembled)"},
{RPCResult::Type::NUM, "difficulty", "The current difficulty"},
{RPCResult::Type::NUM, "networkhashps",
"The network hashes per second"},
{RPCResult::Type::NUM, "pooledtx", "The size of the mempool"},
{RPCResult::Type::STR, "chain",
"current network name (main, test, regtest)"},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getmininginfo", "") +
HelpExampleRpc("getmininginfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
const CTxMemPool &mempool = EnsureMemPool(node);
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
const CChain &active_chain = chainman.ActiveChain();
UniValue obj(UniValue::VOBJ);
obj.pushKV("blocks", active_chain.Height());
if (BlockAssembler::m_last_block_size) {
obj.pushKV("currentblocksize",
*BlockAssembler::m_last_block_size);
}
if (BlockAssembler::m_last_block_num_txs) {
obj.pushKV("currentblocktx",
*BlockAssembler::m_last_block_num_txs);
}
obj.pushKV("difficulty", double(GetDifficulty(active_chain.Tip())));
obj.pushKV("networkhashps",
getnetworkhashps().HandleRequest(config, request));
obj.pushKV("pooledtx", uint64_t(mempool.size()));
obj.pushKV("chain", config.GetChainParams().NetworkIDString());
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
};
}
// NOTE: Unlike wallet RPC (which use XEC values), mining RPCs follow GBT (BIP
// 22) in using satoshi amounts
static RPCHelpMan prioritisetransaction() {
return RPCHelpMan{
"prioritisetransaction",
"Accepts the transaction into mined blocks at a higher "
"(or lower) priority\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id."},
{"dummy", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG,
"API-Compatibility for previous API. Must be zero or null.\n"
" DEPRECATED. For forward compatibility "
"use named arguments and omit this parameter."},
{"fee_delta", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The fee value (in satoshis) to add (or subtract, if negative).\n"
" The fee is not actually paid, only the "
"algorithm for selecting transactions into a block\n"
" considers the transaction as it would "
"have paid a higher (or lower) fee."},
},
RPCResult{RPCResult::Type::BOOL, "", "Returns true"},
RPCExamples{
HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") +
HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
LOCK(cs_main);
TxId txid(ParseHashV(request.params[0], "txid"));
Amount nAmount = request.params[2].get_int64() * SATOSHI;
if (!(request.params[1].isNull() ||
request.params[1].get_real() == 0)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Priority is no longer supported, dummy argument to "
"prioritisetransaction must be 0.");
}
EnsureAnyMemPool(request.context)
.PrioritiseTransaction(txid, nAmount);
return true;
},
};
}
// NOTE: Assumes a conclusive result; if result is inconclusive, it must be
// handled by caller
static UniValue BIP22ValidationResult(const Config &config,
const BlockValidationState &state) {
if (state.IsValid()) {
return NullUniValue;
}
if (state.IsError()) {
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
}
if (state.IsInvalid()) {
std::string strRejectReason = state.GetRejectReason();
if (strRejectReason.empty()) {
return "rejected";
}
return strRejectReason;
}
// Should be impossible.
return "valid?";
}
static RPCHelpMan getblocktemplate() {
return RPCHelpMan{
"getblocktemplate",
"If the request parameters include a 'mode' key, that is used to "
"explicitly select between the default 'template' request or a "
"'proposal'.\n"
"It returns data needed to construct a block to work on.\n"
"For full specification, see BIPs 22, 23, 9, and 145:\n"
" "
"https://github.com/bitcoin/bips/blob/master/"
"bip-0022.mediawiki\n"
" "
"https://github.com/bitcoin/bips/blob/master/"
"bip-0023.mediawiki\n"
" "
"https://github.com/bitcoin/bips/blob/master/"
"bip-0009.mediawiki#getblocktemplate_changes\n"
" ",
{
{"template_request",
RPCArg::Type::OBJ,
"{}",
"Format of the template",
{
{"mode", RPCArg::Type::STR, /* treat as named arg */
RPCArg::Optional::OMITTED_NAMED_ARG,
"This must be set to \"template\", \"proposal\" (see BIP "
"23), or omitted"},
{
"capabilities",
RPCArg::Type::ARR,
/* treat as named arg */
RPCArg::Optional::OMITTED_NAMED_ARG,
"A list of strings",
{
{"support", RPCArg::Type::STR,
RPCArg::Optional::OMITTED,
"client side supported feature, 'longpoll', "
"'coinbasetxn', 'coinbasevalue', 'proposal', "
"'serverlist', 'workid'"},
},
},
},
"\"template_request\""},
},
{
RPCResult{"If the proposal was accepted with mode=='proposal'",
RPCResult::Type::NONE, "", ""},
RPCResult{"If the proposal was not accepted with mode=='proposal'",
RPCResult::Type::STR, "", "According to BIP22"},
RPCResult{
"Otherwise",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "version",
"The preferred block version"},
{RPCResult::Type::STR, "previousblockhash",
"The hash of current highest block"},
{RPCResult::Type::ARR,
"transactions",
"contents of non-coinbase transactions that should be "
"included in the next block",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "data",
"transaction data encoded in hexadecimal "
"(byte-for-byte)"},
{RPCResult::Type::STR_HEX, "txid",
"transaction id encoded in little-endian "
"hexadecimal"},
{RPCResult::Type::STR_HEX, "hash",
"hash encoded in little-endian hexadecimal"},
{RPCResult::Type::ARR,
"depends",
"array of numbers",
{
{RPCResult::Type::NUM, "",
"transactions before this one (by 1-based "
"index in 'transactions' list) that must "
"be present in the final block if this one "
"is"},
}},
{RPCResult::Type::NUM, "fee",
"difference in value between transaction inputs "
"and outputs (in satoshis); for coinbase "
"transactions, this is a negative Number of the "
"total collected block fees (ie, not including "
"the block subsidy); "
"if key is not present, fee is unknown and "
"clients MUST NOT assume there isn't one"},
{RPCResult::Type::NUM, "sigops",
"total SigOps cost, as counted for purposes of "
"block limits; if key is not present, sigop "
"cost is unknown and clients MUST NOT assume it "
"is zero"},
}},
}},
{RPCResult::Type::OBJ,
"coinbaseaux",
"data that should be included in the coinbase's scriptSig "
"content",
{
{RPCResult::Type::ELISION, "", ""},
}},
{RPCResult::Type::NUM, "coinbasevalue",
"maximum allowable input to coinbase transaction, "
"including the generation award and transaction fees (in "
"satoshis)"},
{RPCResult::Type::OBJ,
"coinbasetxn",
"information for coinbase transaction",
{
{RPCResult::Type::OBJ,
"minerfund",
"information related to the coinbase miner fund",
{
{RPCResult::Type::ARR,
"addresses",
"List of valid addresses for the miner fund "
"output",
{
{RPCResult::Type::ELISION, "", ""},
}},
{RPCResult::Type::STR_AMOUNT, "minimumvalue",
"The minimum value the miner fund output must "
"pay"},
}},
{RPCResult::Type::ELISION, "", ""},
}},
{RPCResult::Type::STR, "target", "The hash target"},
{RPCResult::Type::NUM_TIME, "mintime",
"The minimum timestamp appropriate for the next block "
"time, expressed in " +
UNIX_EPOCH_TIME},
{RPCResult::Type::ARR,
"mutable",
"list of ways the block template may be changed",
{
{RPCResult::Type::STR, "value",
"A way the block template may be changed, e.g. "
"'time', 'transactions', 'prevblock'"},
}},
{RPCResult::Type::STR_HEX, "noncerange",
"A range of valid nonces"},
{RPCResult::Type::NUM, "sigoplimit",
"limit of sigops in blocks"},
{RPCResult::Type::NUM, "sizelimit", "limit of block size"},
{RPCResult::Type::NUM_TIME, "curtime",
"current timestamp in " + UNIX_EPOCH_TIME},
{RPCResult::Type::STR, "bits",
"compressed target of next block"},
{RPCResult::Type::NUM, "height",
"The height of the next block"},
}},
},
RPCExamples{HelpExampleCli("getblocktemplate", "") +
HelpExampleRpc("getblocktemplate", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
const CChainParams &chainparams = config.GetChainParams();
std::string strMode = "template";
UniValue lpval = NullUniValue;
std::set<std::string> setClientRules;
CChainState &active_chainstate = chainman.ActiveChainstate();
CChain &active_chain = active_chainstate.m_chain;
if (!request.params[0].isNull()) {
const UniValue &oparam = request.params[0].get_obj();
const UniValue &modeval = find_value(oparam, "mode");
if (modeval.isStr()) {
strMode = modeval.get_str();
} else if (modeval.isNull()) {
/* Do nothing */
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
}
lpval = find_value(oparam, "longpollid");
if (strMode == "proposal") {
const UniValue &dataval = find_value(oparam, "data");
if (!dataval.isStr()) {
throw JSONRPCError(
RPC_TYPE_ERROR,
"Missing data String key for proposal");
}
CBlock block;
if (!DecodeHexBlk(block, dataval.get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Block decode failed");
}
const BlockHash hash = block.GetHash();
const CBlockIndex *pindex =
chainman.m_blockman.LookupBlockIndex(hash);
if (pindex) {
if (pindex->IsValid(BlockValidity::SCRIPTS)) {
return "duplicate";
}
if (pindex->nStatus.isInvalid()) {
return "duplicate-invalid";
}
return "duplicate-inconclusive";
}
CBlockIndex *const pindexPrev = active_chain.Tip();
// TestBlockValidity only supports blocks built on the
// current Tip
if (block.hashPrevBlock != pindexPrev->GetBlockHash()) {
return "inconclusive-not-best-prevblk";
}
BlockValidationState state;
TestBlockValidity(state, chainparams, active_chainstate,
block, pindexPrev,
BlockValidationOptions(config)
.withCheckPoW(false)
.withCheckMerkleRoot(true));
return BIP22ValidationResult(config, state);
}
}
if (strMode != "template") {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
}
const CConnman &connman = EnsureConnman(node);
if (connman.GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) {
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED,
"Bitcoin is not connected!");
}
if (active_chainstate.IsInitialBlockDownload()) {
throw JSONRPCError(
RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME
" is in initial sync and waiting for blocks...");
}
static unsigned int nTransactionsUpdatedLast;
const CTxMemPool &mempool = EnsureMemPool(node);
if (!lpval.isNull()) {
// Wait to respond until either the best block changes, OR a
// minute has passed and there are more transactions
uint256 hashWatchedChain;
std::chrono::steady_clock::time_point checktxtime;
unsigned int nTransactionsUpdatedLastLP;
if (lpval.isStr()) {
// Format: <hashBestChain><nTransactionsUpdatedLast>
std::string lpstr = lpval.get_str();
hashWatchedChain =
ParseHashV(lpstr.substr(0, 64), "longpollid");
nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64));
} else {
// NOTE: Spec does not specify behaviour for non-string
// longpollid, but this makes testing easier
hashWatchedChain = active_chain.Tip()->GetBlockHash();
nTransactionsUpdatedLastLP = nTransactionsUpdatedLast;
}
// Release lock while waiting
LEAVE_CRITICAL_SECTION(cs_main);
{
checktxtime = std::chrono::steady_clock::now() +
std::chrono::minutes(1);
WAIT_LOCK(g_best_block_mutex, lock);
while (g_best_block == hashWatchedChain && IsRPCRunning()) {
if (g_best_block_cv.wait_until(lock, checktxtime) ==
std::cv_status::timeout) {
// Timeout: Check transactions for update
// without holding the mempool look to avoid
// deadlocks
if (mempool.GetTransactionsUpdated() !=
nTransactionsUpdatedLastLP) {
break;
}
checktxtime += std::chrono::seconds(10);
}
}
}
ENTER_CRITICAL_SECTION(cs_main);
if (!IsRPCRunning()) {
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED,
"Shutting down");
}
// TODO: Maybe recheck connections/IBD and (if something wrong)
// send an expires-immediately template to stop miners?
}
// Update block
static CBlockIndex *pindexPrev;
static int64_t nStart;
static std::unique_ptr<CBlockTemplate> pblocktemplate;
if (pindexPrev != active_chain.Tip() ||
(mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast &&
GetTime() - nStart > 5)) {
// Clear pindexPrev so future calls make a new block, despite
// any failures from here on
pindexPrev = nullptr;
// Store the pindexBest used before CreateNewBlock, to avoid
// races
nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
CBlockIndex *pindexPrevNew = active_chain.Tip();
nStart = GetTime();
// Create new block
CScript scriptDummy = CScript() << OP_TRUE;
pblocktemplate =
BlockAssembler(config, active_chainstate, mempool)
.CreateNewBlock(scriptDummy);
if (!pblocktemplate) {
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
}
// Need to update only after we know CreateNewBlock succeeded
pindexPrev = pindexPrevNew;
}
CHECK_NONFATAL(pindexPrev);
// pointer for convenience
CBlock *pblock = &pblocktemplate->block;
// Update nTime
UpdateTime(pblock, chainparams, pindexPrev);
pblock->nNonce = 0;
UniValue aCaps(UniValue::VARR);
aCaps.push_back("proposal");
Amount coinbasevalue = Amount::zero();
UniValue transactions(UniValue::VARR);
transactions.reserve(pblock->vtx.size());
int index_in_template = 0;
for (const auto &it : pblock->vtx) {
const CTransaction &tx = *it;
const TxId txId = tx.GetId();
if (tx.IsCoinBase()) {
index_in_template++;
for (const auto &o : pblock->vtx[0]->vout) {
coinbasevalue += o.nValue;
}
continue;
}
UniValue entry(UniValue::VOBJ);
entry.reserve(5);
entry.__pushKV("data", EncodeHexTx(tx));
entry.__pushKV("txid", txId.GetHex());
entry.__pushKV("hash", tx.GetHash().GetHex());
entry.__pushKV("fee",
pblocktemplate->entries[index_in_template].fees /
SATOSHI);
int64_t nTxSigOps =
pblocktemplate->entries[index_in_template].sigOpCount;
entry.__pushKV("sigops", nTxSigOps);
transactions.push_back(entry);
index_in_template++;
}
UniValue aux(UniValue::VOBJ);
UniValue minerFundList(UniValue::VARR);
const Consensus::Params &consensusParams =
chainparams.GetConsensus();
for (auto fundDestination :
GetMinerFundWhitelist(consensusParams, pindexPrev)) {
minerFundList.push_back(
EncodeDestination(fundDestination, config));
}
int64_t minerFundMinValue = 0;
if (IsAxionEnabled(consensusParams, pindexPrev)) {
minerFundMinValue =
int64_t(GetMinerFundAmount(coinbasevalue) / SATOSHI);
}
UniValue minerFund(UniValue::VOBJ);
minerFund.pushKV("addresses", minerFundList);
minerFund.pushKV("minimumvalue", minerFundMinValue);
UniValue coinbasetxn(UniValue::VOBJ);
coinbasetxn.pushKV("minerfund", minerFund);
arith_uint256 hashTarget =
arith_uint256().SetCompact(pblock->nBits);
UniValue aMutable(UniValue::VARR);
aMutable.push_back("time");
aMutable.push_back("transactions");
aMutable.push_back("prevblock");
UniValue result(UniValue::VOBJ);
result.pushKV("capabilities", aCaps);
result.pushKV("version", pblock->nVersion);
result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex());
result.pushKV("transactions", transactions);
result.pushKV("coinbaseaux", aux);
result.pushKV("coinbasetxn", coinbasetxn);
result.pushKV("coinbasevalue", int64_t(coinbasevalue / SATOSHI));
result.pushKV("longpollid",
active_chain.Tip()->GetBlockHash().GetHex() +
ToString(nTransactionsUpdatedLast));
result.pushKV("target", hashTarget.GetHex());
result.pushKV("mintime",
int64_t(pindexPrev->GetMedianTimePast()) + 1);
result.pushKV("mutable", aMutable);
result.pushKV("noncerange", "00000000ffffffff");
result.pushKV("sigoplimit",
GetMaxBlockSigChecksCount(DEFAULT_MAX_BLOCK_SIZE));
result.pushKV("sizelimit", DEFAULT_MAX_BLOCK_SIZE);
result.pushKV("curtime", pblock->GetBlockTime());
result.pushKV("bits", strprintf("%08x", pblock->nBits));
result.pushKV("height", int64_t(pindexPrev->nHeight) + 1);
return result;
},
};
}
class submitblock_StateCatcher final : public CValidationInterface {
public:
uint256 hash;
bool found;
BlockValidationState state;
explicit submitblock_StateCatcher(const uint256 &hashIn)
: hash(hashIn), found(false), state() {}
protected:
void BlockChecked(const CBlock &block,
const BlockValidationState &stateIn) override {
if (block.GetHash() != hash) {
return;
}
found = true;
state = stateIn;
}
};
static RPCHelpMan submitblock() {
// We allow 2 arguments for compliance with BIP22. Argument 2 is ignored.
return RPCHelpMan{
"submitblock",
"Attempts to submit new block to network.\n"
"See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n",
{
{"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hex-encoded block data to submit"},
{"dummy", RPCArg::Type::STR, /* default */ "ignored",
"dummy value, for compatibility with BIP22. This value is "
"ignored."},
},
{
RPCResult{"If the block was accepted", RPCResult::Type::NONE, "",
""},
RPCResult{"Otherwise", RPCResult::Type::STR, "",
"According to BIP22"},
},
RPCExamples{HelpExampleCli("submitblock", "\"mydata\"") +
HelpExampleRpc("submitblock", "\"mydata\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>();
CBlock &block = *blockptr;
if (!DecodeHexBlk(block, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Block decode failed");
}
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Block does not start with a coinbase");
}
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const BlockHash hash = block.GetHash();
{
LOCK(cs_main);
const CBlockIndex *pindex =
chainman.m_blockman.LookupBlockIndex(hash);
if (pindex) {
if (pindex->IsValid(BlockValidity::SCRIPTS)) {
return "duplicate";
}
if (pindex->nStatus.isInvalid()) {
return "duplicate-invalid";
}
}
}
bool new_block;
auto sc =
std::make_shared<submitblock_StateCatcher>(block.GetHash());
RegisterSharedValidationInterface(sc);
bool accepted =
chainman.ProcessNewBlock(config, blockptr,
/* fForceProcessing */ true,
/* fNewBlock */ &new_block);
UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
return "duplicate";
}
if (!sc->found) {
return "inconclusive";
}
return BIP22ValidationResult(config, sc->state);
},
};
}
static RPCHelpMan submitheader() {
return RPCHelpMan{
"submitheader",
"Decode the given hexdata as a header and submit it as a candidate "
"chain tip if valid."
"\nThrows when the header is invalid.\n",
{
{"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hex-encoded block header data"},
},
RPCResult{RPCResult::Type::NONE, "", "None"},
RPCExamples{HelpExampleCli("submitheader", "\"aabbcc\"") +
HelpExampleRpc("submitheader", "\"aabbcc\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
CBlockHeader h;
if (!DecodeHexBlockHeader(h, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Block header decode failed");
}
ChainstateManager &chainman = EnsureAnyChainman(request.context);
{
LOCK(cs_main);
if (!chainman.m_blockman.LookupBlockIndex(h.hashPrevBlock)) {
throw JSONRPCError(RPC_VERIFY_ERROR,
"Must submit previous header (" +
h.hashPrevBlock.GetHex() +
") first");
}
}
BlockValidationState state;
chainman.ProcessNewBlockHeaders(config, {h}, state);
if (state.IsValid()) {
return NullUniValue;
}
if (state.IsError()) {
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
}
throw JSONRPCError(RPC_VERIFY_ERROR, state.GetRejectReason());
},
};
}
static RPCHelpMan estimatefee() {
return RPCHelpMan{
"estimatefee",
"Estimates the approximate fee per kilobyte needed for a "
"transaction\n",
{},
RPCResult{RPCResult::Type::NUM, "", "estimated fee-per-kilobyte"},
RPCExamples{HelpExampleCli("estimatefee", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const CTxMemPool &mempool = EnsureAnyMemPool(request.context);
return mempool.estimateFee().GetFeePerK();
},
};
}
void RegisterMiningRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ---------- ----------------------
{"mining", getnetworkhashps, },
{"mining", getmininginfo, },
{"mining", prioritisetransaction, },
{"mining", getblocktemplate, },
{"mining", submitblock, },
{"mining", submitheader, },
{"generating", generatetoaddress, },
{"generating", generatetodescriptor, },
{"generating", generateblock, },
{"util", estimatefee, },
{"hidden", generate, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index fb7006013..1a03d02cd 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -1,920 +1,921 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
#include <config.h>
#include <consensus/amount.h>
#include <httpserver.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
#include <key_io.h>
#include <logging.h>
#include <node/context.h>
#include <outputtype.h>
#include <rpc/blockchain.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <scheduler.h>
#include <script/descriptor.h>
#include <util/check.h>
#include <util/message.h> // For MessageSign(), MessageVerify()
#include <util/strencodings.h>
#include <util/system.h>
#include <univalue.h>
#include <cstdint>
#include <tuple>
#ifdef HAVE_MALLOC_INFO
#include <malloc.h>
#endif
static RPCHelpMan validateaddress() {
return RPCHelpMan{
"validateaddress",
"Return information about the given bitcoin address.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The bitcoin address to validate"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "isvalid",
"If the address is valid or not. If not, this is the only "
"property returned."},
{RPCResult::Type::STR, "address",
"The bitcoin address validated"},
{RPCResult::Type::STR_HEX, "scriptPubKey",
"The hex-encoded scriptPubKey generated by the address"},
{RPCResult::Type::BOOL, "isscript", "If the key is a script"},
}},
RPCExamples{HelpExampleCli("validateaddress", EXAMPLE_ADDRESS) +
HelpExampleRpc("validateaddress", EXAMPLE_ADDRESS)},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
CTxDestination dest = DecodeDestination(request.params[0].get_str(),
config.GetChainParams());
bool isValid = IsValidDestination(dest);
UniValue ret(UniValue::VOBJ);
ret.pushKV("isvalid", isValid);
if (isValid) {
if (ret["address"].isNull()) {
std::string currentAddress =
EncodeDestination(dest, config);
ret.pushKV("address", currentAddress);
CScript scriptPubKey = GetScriptForDestination(dest);
ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
UniValue detail = DescribeAddress(dest);
ret.pushKVs(detail);
}
}
return ret;
},
};
}
static RPCHelpMan createmultisig() {
return RPCHelpMan{
"createmultisig",
"Creates a multi-signature address with n signature of m keys "
"required.\n"
"It returns a json object with the address and redeemScript.\n",
{
{"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The number of required signatures out of the n keys."},
{"keys",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The hex-encoded public keys.",
{
{"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"The hex-encoded public key"},
}},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"The value of the new multisig address."},
{RPCResult::Type::STR_HEX, "redeemScript",
"The string value of the hex-encoded redemption script."},
{RPCResult::Type::STR, "descriptor",
"The descriptor for this multisig"},
}},
RPCExamples{
"\nCreate a multisig address from 2 public keys\n" +
HelpExampleCli("createmultisig",
"2 "
"\"["
"\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd3"
"42cf11ae157a7ace5fd\\\","
"\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e1"
"7e107ef3f6aa5a61626\\\"]\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("createmultisig",
"2, "
"\"["
"\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd3"
"42cf11ae157a7ace5fd\\\","
"\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e1"
"7e107ef3f6aa5a61626\\\"]\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int required = request.params[0].get_int();
// Get the public keys
const UniValue &keys = request.params[1].get_array();
std::vector<CPubKey> pubkeys;
for (size_t i = 0; i < keys.size(); ++i) {
if ((keys[i].get_str().length() ==
2 * CPubKey::COMPRESSED_SIZE ||
keys[i].get_str().length() == 2 * CPubKey::SIZE) &&
IsHex(keys[i].get_str())) {
pubkeys.push_back(HexToPubKey(keys[i].get_str()));
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Invalid public key: %s\n",
keys[i].get_str()));
}
}
// Get the output type
OutputType output_type = OutputType::LEGACY;
// Construct using pay-to-script-hash:
FillableSigningProvider keystore;
CScript inner;
const CTxDestination dest = AddAndGetMultisigDestination(
required, pubkeys, output_type, keystore, inner);
// Make the descriptor
std::unique_ptr<Descriptor> descriptor =
InferDescriptor(GetScriptForDestination(dest), keystore);
UniValue result(UniValue::VOBJ);
result.pushKV("address", EncodeDestination(dest, config));
result.pushKV("redeemScript", HexStr(inner));
result.pushKV("descriptor", descriptor->ToString());
return result;
},
};
}
static RPCHelpMan getdescriptorinfo() {
return RPCHelpMan{
"getdescriptorinfo",
{"Analyses a descriptor.\n"},
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO,
"The descriptor."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "descriptor",
"The descriptor in canonical form, without private keys"},
{RPCResult::Type::STR, "checksum",
"The checksum for the input descriptor"},
{RPCResult::Type::BOOL, "isrange",
"Whether the descriptor is ranged"},
{RPCResult::Type::BOOL, "issolvable",
"Whether the descriptor is solvable"},
{RPCResult::Type::BOOL, "hasprivatekeys",
"Whether the input descriptor contained at least one private "
"key"},
}},
RPCExamples{"Analyse a descriptor\n" +
HelpExampleCli("getdescriptorinfo",
"\"pkh([d34db33f/84h/0h/"
"0h]"
"0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2"
"dce28d959f2815b16f81798)\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
FlatSigningProvider provider;
std::string error;
auto desc = Parse(request.params[0].get_str(), provider, error);
if (!desc) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}
UniValue result(UniValue::VOBJ);
result.pushKV("descriptor", desc->ToString());
result.pushKV("checksum",
GetDescriptorChecksum(request.params[0].get_str()));
result.pushKV("isrange", desc->IsRange());
result.pushKV("issolvable", desc->IsSolvable());
result.pushKV("hasprivatekeys", provider.keys.size() > 0);
return result;
},
};
}
static RPCHelpMan deriveaddresses() {
return RPCHelpMan{
"deriveaddresses",
{"Derives one or more addresses corresponding to an output "
"descriptor.\n"
"Examples of output descriptors are:\n"
" pkh(<pubkey>) P2PKH outputs for the given "
"pubkey\n"
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for "
"the given threshold and pubkeys\n"
" raw(<hex script>) Outputs whose scriptPubKey "
"equals the specified hex scripts\n"
"\nIn the above, <pubkey> either refers to a fixed public key in "
"hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
"or more path elements separated by \"/\", where \"h\" represents a "
"hardened child key.\n"
"For more information on output descriptors, see the documentation in "
"the doc/descriptors.md file.\n"},
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO,
"The descriptor."},
{"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG,
"If a ranged descriptor is used, this specifies the end or the "
"range (in [begin,end] notation) to derive."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR, "address", "the derived addresses"},
}},
RPCExamples{"First three pkh receive addresses\n" +
HelpExampleCli(
"deriveaddresses",
"\"pkh([d34db33f/84h/0h/0h]"
"xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8P"
"hqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKE"
"u3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#3vhfv5h5\" \"[0,2]\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
// Range argument is checked later
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()});
const std::string desc_str = request.params[0].get_str();
int64_t range_begin = 0;
int64_t range_end = 0;
if (request.params.size() >= 2 && !request.params[1].isNull()) {
std::tie(range_begin, range_end) =
ParseDescriptorRange(request.params[1]);
}
FlatSigningProvider key_provider;
std::string error;
auto desc = Parse(desc_str, key_provider, error,
/* require_checksum = */ true);
if (!desc) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}
if (!desc->IsRange() && request.params.size() > 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Range should not be specified for an "
"un-ranged descriptor");
}
if (desc->IsRange() && request.params.size() == 1) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Range must be specified for a ranged descriptor");
}
UniValue addresses(UniValue::VARR);
for (int i = range_begin; i <= range_end; ++i) {
FlatSigningProvider provider;
std::vector<CScript> scripts;
if (!desc->Expand(i, key_provider, scripts, provider)) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Cannot derive script without private keys"));
}
for (const CScript &script : scripts) {
CTxDestination dest;
if (!ExtractDestination(script, dest)) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Descriptor does not have a "
"corresponding address"));
}
addresses.push_back(EncodeDestination(dest, config));
}
}
// This should not be possible, but an assert seems overkill:
if (addresses.empty()) {
throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result");
}
return addresses;
},
};
}
static RPCHelpMan verifymessage() {
return RPCHelpMan{
"verifymessage",
"Verify a signed message\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The bitcoin address to use for the signature."},
{"signature", RPCArg::Type::STR, RPCArg::Optional::NO,
"The signature provided by the signer in base 64 encoding (see "
"signmessage)."},
{"message", RPCArg::Type::STR, RPCArg::Optional::NO,
"The message that was signed."},
},
RPCResult{RPCResult::Type::BOOL, "",
"If the signature is verified or not."},
RPCExamples{
"\nUnlock the wallet for 30 seconds\n" +
HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
"\nCreate the signature\n" +
HelpExampleCli(
"signmessage",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
"\nVerify the signature\n" +
HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4"
"XX\" \"signature\" \"my "
"message\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4"
"XX\", \"signature\", \"my "
"message\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
LOCK(cs_main);
std::string strAddress = request.params[0].get_str();
std::string strSign = request.params[1].get_str();
std::string strMessage = request.params[2].get_str();
switch (MessageVerify(config.GetChainParams(), strAddress, strSign,
strMessage)) {
case MessageVerificationResult::ERR_INVALID_ADDRESS:
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
throw JSONRPCError(RPC_TYPE_ERROR,
"Address does not refer to key");
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Malformed base64 encoding");
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
case MessageVerificationResult::ERR_NOT_SIGNED:
return false;
case MessageVerificationResult::OK:
return true;
}
return false;
},
};
}
static RPCHelpMan signmessagewithprivkey() {
return RPCHelpMan{
"signmessagewithprivkey",
"Sign a message with the private key of an address\n",
{
{"privkey", RPCArg::Type::STR, RPCArg::Optional::NO,
"The private key to sign the message with."},
{"message", RPCArg::Type::STR, RPCArg::Optional::NO,
"The message to create a signature of."},
},
RPCResult{RPCResult::Type::STR, "signature",
"The signature of the message encoded in base 64"},
RPCExamples{"\nCreate the signature\n" +
HelpExampleCli("signmessagewithprivkey",
"\"privkey\" \"my message\"") +
"\nVerify the signature\n" +
HelpExampleCli("verifymessage",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" "
"\"signature\" \"my message\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("signmessagewithprivkey",
"\"privkey\", \"my message\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::string strPrivkey = request.params[0].get_str();
std::string strMessage = request.params[1].get_str();
CKey key = DecodeSecret(strPrivkey);
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid private key");
}
std::string signature;
if (!MessageSign(key, strMessage, signature)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
}
return signature;
},
};
}
static RPCHelpMan setmocktime() {
return RPCHelpMan{
"setmocktime",
"Set the local time to given timestamp (-regtest only)\n",
{
{"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO,
UNIX_EPOCH_TIME + "\n"
"Pass 0 to go back to using the system time."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!config.GetChainParams().IsMockableChain()) {
throw std::runtime_error(
"setmocktime for regression testing (-regtest mode) only");
}
// For now, don't change mocktime if we're in the middle of
// validation, as this could have an effect on mempool time-based
// eviction, as well as IsInitialBlockDownload().
// TODO: figure out the right way to synchronize around mocktime,
// and ensure all call sites of GetTime() are accessing this safely.
LOCK(cs_main);
RPCTypeCheck(request.params, {UniValue::VNUM});
const int64_t time{request.params[0].get_int64()};
if (time < 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Mocktime can not be negative: %s.", time));
}
SetMockTime(time);
auto node_context = util::AnyPtr<NodeContext>(request.context);
if (node_context) {
for (const auto &chain_client : node_context->chain_clients) {
chain_client->setMockTime(time);
}
}
return NullUniValue;
},
};
}
static RPCHelpMan mockscheduler() {
return RPCHelpMan{
"mockscheduler",
"Bump the scheduler into the future (-regtest only)\n",
{
{"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO,
"Number of seconds to forward the scheduler into the future."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (!Params().IsMockableChain()) {
throw std::runtime_error("mockscheduler is for regression "
"testing (-regtest mode) only");
}
// check params are valid values
RPCTypeCheck(request.params, {UniValue::VNUM});
int64_t delta_seconds = request.params[0].get_int64();
if ((delta_seconds <= 0) || (delta_seconds > 3600)) {
throw std::runtime_error(
"delta_time must be between 1 and 3600 seconds (1 hr)");
}
auto node_context = util::AnyPtr<NodeContext>(request.context);
// protect against null pointer dereference
CHECK_NONFATAL(node_context);
CHECK_NONFATAL(node_context->scheduler);
node_context->scheduler->MockForward(
std::chrono::seconds(delta_seconds));
return NullUniValue;
},
};
}
static UniValue RPCLockedMemoryInfo() {
LockedPool::Stats stats = LockedPoolManager::Instance().stats();
UniValue obj(UniValue::VOBJ);
obj.pushKV("used", uint64_t(stats.used));
obj.pushKV("free", uint64_t(stats.free));
obj.pushKV("total", uint64_t(stats.total));
obj.pushKV("locked", uint64_t(stats.locked));
obj.pushKV("chunks_used", uint64_t(stats.chunks_used));
obj.pushKV("chunks_free", uint64_t(stats.chunks_free));
return obj;
}
#ifdef HAVE_MALLOC_INFO
static std::string RPCMallocInfo() {
char *ptr = nullptr;
size_t size = 0;
FILE *f = open_memstream(&ptr, &size);
if (f) {
malloc_info(0, f);
fclose(f);
if (ptr) {
std::string rv(ptr, size);
free(ptr);
return rv;
}
}
return "";
}
#endif
static RPCHelpMan getmemoryinfo() {
/* Please, avoid using the word "pool" here in the RPC interface or help,
* as users will undoubtedly confuse it with the other "memory pool"
*/
return RPCHelpMan{
"getmemoryinfo",
"Returns an object containing information about memory usage.\n",
{
{"mode", RPCArg::Type::STR, /* default */ "\"stats\"",
"determines what kind of information is returned.\n"
" - \"stats\" returns general statistics about memory usage in "
"the daemon.\n"
" - \"mallocinfo\" returns an XML string describing low-level "
"heap state (only available if compiled with glibc 2.10+)."},
},
{
RPCResult{
"mode \"stats\"",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"locked",
"Information about locked memory manager",
{
{RPCResult::Type::NUM, "used", "Number of bytes used"},
{RPCResult::Type::NUM, "free",
"Number of bytes available in current arenas"},
{RPCResult::Type::NUM, "total",
"Total number of bytes managed"},
{RPCResult::Type::NUM, "locked",
"Amount of bytes that succeeded locking. If this "
"number is smaller than total, locking pages failed "
"at some point and key data could be swapped to "
"disk."},
{RPCResult::Type::NUM, "chunks_used",
"Number allocated chunks"},
{RPCResult::Type::NUM, "chunks_free",
"Number unused chunks"},
}},
}},
RPCResult{"mode \"mallocinfo\"", RPCResult::Type::STR, "",
"\"<malloc version=\"1\">...\""},
},
RPCExamples{HelpExampleCli("getmemoryinfo", "") +
HelpExampleRpc("getmemoryinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::string mode = request.params[0].isNull()
? "stats"
: request.params[0].get_str();
if (mode == "stats") {
UniValue obj(UniValue::VOBJ);
obj.pushKV("locked", RPCLockedMemoryInfo());
return obj;
} else if (mode == "mallocinfo") {
#ifdef HAVE_MALLOC_INFO
return RPCMallocInfo();
#else
throw JSONRPCError(RPC_INVALID_PARAMETER,
"mallocinfo is only available when compiled "
"with glibc 2.10+");
#endif
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"unknown mode " + mode);
}
},
};
}
static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
cats = cats.get_array();
for (size_t i = 0; i < cats.size(); ++i) {
std::string cat = cats[i].get_str();
bool success;
if (enable) {
success = LogInstance().EnableCategory(cat);
} else {
success = LogInstance().DisableCategory(cat);
}
if (!success) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"unknown logging category " + cat);
}
}
}
static RPCHelpMan logging() {
return RPCHelpMan{
"logging",
"Gets and sets the logging configuration.\n"
"When called without an argument, returns the list of categories with "
"status that are currently being debug logged or not.\n"
"When called with arguments, adds or removes categories from debug "
"logging and return the lists above.\n"
"The arguments are evaluated in order \"include\", \"exclude\".\n"
"If an item is both included and excluded, it will thus end up being "
"excluded.\n"
"The valid logging categories are: " +
LogInstance().LogCategoriesString() +
"\n"
"In addition, the following are available as category names with "
"special meanings:\n"
" - \"all\", \"1\" : represent all logging categories.\n"
" - \"none\", \"0\" : even if other logging categories are "
"specified, ignore all of them.\n",
{
{"include",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The categories to add to debug logging",
{
{"include_category", RPCArg::Type::STR,
RPCArg::Optional::OMITTED, "the valid logging category"},
}},
{"exclude",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The categories to remove from debug logging",
{
{"exclude_category", RPCArg::Type::STR,
RPCArg::Optional::OMITTED, "the valid logging category"},
}},
},
RPCResult{
RPCResult::Type::OBJ_DYN,
"",
"keys are the logging categories, and values indicates its status",
{
{RPCResult::Type::BOOL, "category",
"if being debug logged or not. false:inactive, true:active"},
}},
RPCExamples{
HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") +
HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
uint32_t original_log_categories = LogInstance().GetCategoryMask();
if (request.params[0].isArray()) {
EnableOrDisableLogCategories(request.params[0], true);
}
if (request.params[1].isArray()) {
EnableOrDisableLogCategories(request.params[1], false);
}
uint32_t updated_log_categories = LogInstance().GetCategoryMask();
uint32_t changed_log_categories =
original_log_categories ^ updated_log_categories;
/**
* Update libevent logging if BCLog::LIBEVENT has changed.
* If the library version doesn't allow it,
* UpdateHTTPServerLogging() returns false, in which case we should
* clear the BCLog::LIBEVENT flag. Throw an error if the user has
* explicitly asked to change only the libevent flag and it failed.
*/
if (changed_log_categories & BCLog::LIBEVENT) {
if (!UpdateHTTPServerLogging(
LogInstance().WillLogCategory(BCLog::LIBEVENT))) {
LogInstance().DisableCategory(BCLog::LIBEVENT);
if (changed_log_categories == BCLog::LIBEVENT) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"libevent logging cannot be updated when "
"using libevent before v2.1.1.");
}
}
}
UniValue result(UniValue::VOBJ);
for (const auto &logCatActive : LogInstance().LogCategoriesList()) {
result.pushKV(logCatActive.category, logCatActive.active);
}
return result;
},
};
}
static RPCHelpMan echo(const std::string &name) {
return RPCHelpMan{
name,
"Simply echo back the input arguments. This command is for "
"testing.\n"
"\nIt will return an internal bug report when "
"arg9='trigger_internal_bug' is passed.\n"
"\nThe difference between echo and echojson is that echojson has "
"argument conversion enabled in the client-side table in "
"bitcoin-cli and the GUI. There is no server-side difference.",
{
{"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
{"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG,
""},
},
RPCResult{RPCResult::Type::NONE, "", "Returns whatever was passed in"},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (request.params[9].isStr()) {
CHECK_NONFATAL(request.params[9].get_str() !=
"trigger_internal_bug");
}
return request.params;
},
};
}
static RPCHelpMan echo() {
return echo("echo");
}
static RPCHelpMan echojson() {
return echo("echojson");
}
static RPCHelpMan getcurrencyinfo() {
return RPCHelpMan{
"getcurrencyinfo",
"Returns an object containing information about the currency.\n",
{},
{
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "ticker", "Ticker symbol"},
{RPCResult::Type::NUM, "satoshisperunit",
"Number of satoshis per base unit"},
{RPCResult::Type::NUM, "decimals",
"Number of digits to the right of the decimal point."},
}},
},
RPCExamples{HelpExampleCli("getcurrencyinfo", "") +
HelpExampleRpc("getcurrencyinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const Currency ¤cy = Currency::get();
UniValue res(UniValue::VOBJ);
res.pushKV("ticker", currency.ticker);
res.pushKV("satoshisperunit", currency.baseunit / SATOSHI);
res.pushKV("decimals", currency.decimals);
return res;
},
};
}
static UniValue SummaryToJSON(const IndexSummary &&summary,
std::string index_name) {
UniValue ret_summary(UniValue::VOBJ);
if (!index_name.empty() && index_name != summary.name) {
return ret_summary;
}
UniValue entry(UniValue::VOBJ);
entry.pushKV("synced", summary.synced);
entry.pushKV("best_block_height", summary.best_block_height);
ret_summary.pushKV(summary.name, entry);
return ret_summary;
}
static RPCHelpMan getindexinfo() {
return RPCHelpMan{
"getindexinfo",
"Returns the status of one or all available indices currently "
"running in the node.\n",
{
{"index_name", RPCArg::Type::STR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"Filter results for an index with a specific name."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"name",
"The name of the index",
{
{RPCResult::Type::BOOL, "synced",
"Whether the index is synced or not"},
{RPCResult::Type::NUM, "best_block_height",
"The block height to which the index is synced"},
}},
},
},
RPCExamples{HelpExampleCli("getindexinfo", "") +
HelpExampleRpc("getindexinfo", "") +
HelpExampleCli("getindexinfo", "txindex") +
HelpExampleRpc("getindexinfo", "txindex")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
UniValue result(UniValue::VOBJ);
const std::string index_name =
request.params[0].isNull() ? "" : request.params[0].get_str();
if (g_txindex) {
result.pushKVs(
SummaryToJSON(g_txindex->GetSummary(), index_name));
}
if (g_coin_stats_index) {
result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(),
index_name));
}
ForEachBlockFilterIndex([&result, &index_name](
const BlockFilterIndex &index) {
result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
});
return result;
},
};
}
void RegisterMiscRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ------------------ ----------------------
{ "control", getmemoryinfo, },
{ "control", logging, },
{ "util", validateaddress, },
{ "util", createmultisig, },
{ "util", deriveaddresses, },
{ "util", getdescriptorinfo, },
{ "util", verifymessage, },
{ "util", signmessagewithprivkey, },
{ "util", getcurrencyinfo, },
{ "util", getindexinfo, },
/* Not shown in help */
{ "hidden", setmocktime, },
{ "hidden", mockscheduler, },
{ "hidden", echo, },
{ "hidden", echojson, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index f497a3060..9d4b35767 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,1212 +1,1193 @@
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <rpc/server.h>
#include <addrman.h>
#include <avalanche/avalanche.h>
#include <banman.h>
#include <chainparams.h>
#include <clientversion.h>
#include <config.h>
-#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <net_types.h> // For banmap_t
#include <netbase.h>
#include <node/context.h>
#include <policy/settings.h>
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <sync.h>
#include <timedata.h>
#include <util/strencodings.h>
#include <util/string.h>
-#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <version.h>
#include <warnings.h>
#include <optional>
#include <univalue.h>
-CConnman &EnsureConnman(const NodeContext &node) {
- if (!node.connman) {
- throw JSONRPCError(
- RPC_CLIENT_P2P_DISABLED,
- "Error: Peer-to-peer functionality missing or disabled");
- }
- return *node.connman;
-}
-
-PeerManager &EnsurePeerman(const NodeContext &node) {
- if (!node.peerman) {
- throw JSONRPCError(
- RPC_CLIENT_P2P_DISABLED,
- "Error: Peer-to-peer functionality missing or disabled");
- }
- return *node.peerman;
-}
-
static RPCHelpMan getconnectioncount() {
return RPCHelpMan{
"getconnectioncount",
"Returns the number of connections to other nodes.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The connection count"},
RPCExamples{HelpExampleCli("getconnectioncount", "") +
HelpExampleRpc("getconnectioncount", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
const CConnman &connman = EnsureConnman(node);
return int(connman.GetNodeCount(CConnman::CONNECTIONS_ALL));
},
};
}
static RPCHelpMan ping() {
return RPCHelpMan{
"ping",
"Requests that a ping be sent to all other nodes, to measure ping "
"time.\n"
"Results provided in getpeerinfo, pingtime and pingwait fields are "
"decimal seconds.\n"
"Ping command is handled in queue with all other commands, so it "
"measures processing backlog, not just network ping.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("ping", "") + HelpExampleRpc("ping", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
PeerManager &peerman = EnsurePeerman(node);
// Request that each node send a ping during next message processing
// pass
peerman.SendPings();
return NullUniValue;
},
};
}
static RPCHelpMan getpeerinfo() {
return RPCHelpMan{
"getpeerinfo",
"Returns data about each connected network node as a json array of "
"objects.\n",
{},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{
RPCResult::Type::OBJ,
"",
"",
{{
{RPCResult::Type::NUM, "id", "Peer index"},
{RPCResult::Type::STR, "addr",
"(host:port) The IP address and port of the peer"},
{RPCResult::Type::STR, "addrbind",
"(ip:port) Bind address of the connection to the peer"},
{RPCResult::Type::STR, "addrlocal",
"(ip:port) Local address as reported by the peer"},
{RPCResult::Type::BOOL, "addr_relay_enabled",
"Whether we participate in address relay with this peer"},
{RPCResult::Type::NUM, "addr_processed",
"The total number of addresses processed, excluding those "
"dropped due to rate limiting"},
{RPCResult::Type::NUM, "addr_rate_limited",
"The total number of addresses dropped due to rate "
"limiting"},
{RPCResult::Type::STR, "network",
"Network (" +
Join(GetNetworkNames(/* append_unroutable */ true),
", ") +
")"},
{RPCResult::Type::NUM, "mapped_as",
"The AS in the BGP route to the peer used for "
"diversifying\n"
"peer selection (only available if the asmap config flag "
"is set)\n"},
{RPCResult::Type::STR_HEX, "services",
"The services offered"},
{RPCResult::Type::ARR,
"servicesnames",
"the services offered, in human-readable form",
{{RPCResult::Type::STR, "SERVICE_NAME",
"the service name if it is recognised"}}},
{RPCResult::Type::BOOL, "relaytxes",
"Whether peer has asked us to relay transactions to it"},
{RPCResult::Type::NUM_TIME, "lastsend",
"The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv",
"The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction",
"The " + UNIX_EPOCH_TIME +
" of the last valid transaction received from this "
"peer"},
{RPCResult::Type::NUM_TIME, "last_block",
"The " + UNIX_EPOCH_TIME +
" of the last block received from this peer"},
{RPCResult::Type::NUM, "bytessent", "The total bytes sent"},
{RPCResult::Type::NUM, "bytesrecv",
"The total bytes received"},
{RPCResult::Type::NUM_TIME, "conntime",
"The " + UNIX_EPOCH_TIME + " of the connection"},
{RPCResult::Type::NUM, "timeoffset",
"The time offset in seconds"},
{RPCResult::Type::NUM, "pingtime",
"ping time (if available)"},
{RPCResult::Type::NUM, "minping",
"minimum observed ping time (if any at all)"},
{RPCResult::Type::NUM, "pingwait",
"ping wait (if non-zero)"},
{RPCResult::Type::NUM, "version",
"The peer version, such as 70001"},
{RPCResult::Type::STR, "subver", "The string version"},
{RPCResult::Type::BOOL, "inbound",
"Inbound (true) or Outbound (false)"},
{RPCResult::Type::BOOL, "bip152_hb_to",
"Whether we selected peer as (compact blocks) "
"high-bandwidth peer"},
{RPCResult::Type::BOOL, "bip152_hb_from",
"Whether peer selected us as (compact blocks) "
"high-bandwidth peer"},
{RPCResult::Type::STR, "connection_type",
"Type of connection: \n" +
Join(CONNECTION_TYPE_DOC, ",\n") + "."},
{RPCResult::Type::NUM, "startingheight",
"The starting height (block) of the peer"},
{RPCResult::Type::NUM, "synced_headers",
"The last header we have in common with this peer"},
{RPCResult::Type::NUM, "synced_blocks",
"The last block we have in common with this peer"},
{RPCResult::Type::ARR,
"inflight",
"",
{
{RPCResult::Type::NUM, "n",
"The heights of blocks we're currently asking from "
"this peer"},
}},
{RPCResult::Type::NUM, "minfeefilter",
"The minimum fee rate for transactions this peer accepts"},
{RPCResult::Type::OBJ_DYN,
"bytessent_per_msg",
"",
{{RPCResult::Type::NUM, "msg",
"The total bytes sent aggregated by message type\n"
"When a message type is not listed in this json object, "
"the bytes sent are 0.\n"
"Only known message types can appear as keys in the "
"object."}}},
{RPCResult::Type::OBJ,
"bytesrecv_per_msg",
"",
{{RPCResult::Type::NUM, "msg",
"The total bytes received aggregated by message type\n"
"When a message type is not listed in this json object, "
"the bytes received are 0.\n"
"Only known message types can appear as keys in the "
"object and all bytes received\n"
"of unknown message types are listed under '" +
NET_MESSAGE_COMMAND_OTHER + "'."}}},
{RPCResult::Type::NUM, "activity_score",
"Avalanche activity score of this node (if any)"},
}},
}},
},
RPCExamples{HelpExampleCli("getpeerinfo", "") +
HelpExampleRpc("getpeerinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
const CConnman &connman = EnsureConnman(node);
const PeerManager &peerman = EnsurePeerman(node);
std::vector<CNodeStats> vstats;
connman.GetNodeStats(vstats);
UniValue ret(UniValue::VARR);
for (const CNodeStats &stats : vstats) {
UniValue obj(UniValue::VOBJ);
CNodeStateStats statestats;
bool fStateStats =
peerman.GetNodeStateStats(stats.nodeid, statestats);
obj.pushKV("id", stats.nodeid);
obj.pushKV("addr", stats.m_addr_name);
if (stats.addrBind.IsValid()) {
obj.pushKV("addrbind", stats.addrBind.ToString());
}
if (!(stats.addrLocal.empty())) {
obj.pushKV("addrlocal", stats.addrLocal);
}
obj.pushKV("addr_relay_enabled",
statestats.m_addr_relay_enabled);
obj.pushKV("network", GetNetworkName(stats.m_network));
if (stats.m_mapped_as != 0) {
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", count_seconds(stats.m_last_send));
obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
obj.pushKV("last_transaction",
count_seconds(stats.m_last_tx_time));
if (g_avalanche) {
obj.pushKV("last_proof",
count_seconds(stats.m_last_proof_time));
}
obj.pushKV("last_block",
count_seconds(stats.m_last_block_time));
obj.pushKV("bytessent", stats.nSendBytes);
obj.pushKV("bytesrecv", stats.nRecvBytes);
obj.pushKV("conntime", count_seconds(stats.m_connected));
obj.pushKV("timeoffset", stats.nTimeOffset);
if (stats.m_last_ping_time > 0us) {
obj.pushKV("pingtime",
CountSecondsDouble(stats.m_last_ping_time));
}
if (stats.m_min_ping_time < std::chrono::microseconds::max()) {
obj.pushKV("minping",
CountSecondsDouble(stats.m_min_ping_time));
}
if (fStateStats && statestats.m_ping_wait > 0s) {
obj.pushKV("pingwait",
CountSecondsDouble(statestats.m_ping_wait));
}
obj.pushKV("version", stats.nVersion);
// Use the sanitized form of subver here, to avoid tricksy
// remote peers from corrupting or modifying the JSON output by
// putting special characters in their ver message.
obj.pushKV("subver", stats.cleanSubVer);
obj.pushKV("inbound", stats.fInbound);
obj.pushKV("bip152_hb_to", stats.m_bip152_highbandwidth_to);
obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from);
if (fStateStats) {
obj.pushKV("startingheight", statestats.m_starting_height);
obj.pushKV("synced_headers", statestats.nSyncHeight);
obj.pushKV("synced_blocks", statestats.nCommonHeight);
UniValue heights(UniValue::VARR);
for (const int height : statestats.vHeightInFlight) {
heights.push_back(height);
}
obj.pushKV("inflight", heights);
obj.pushKV("addr_processed", statestats.m_addr_processed);
obj.pushKV("addr_rate_limited",
statestats.m_addr_rate_limited);
}
UniValue permissions(UniValue::VARR);
for (const auto &permission :
NetPermissions::ToStrings(stats.m_permissionFlags)) {
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
obj.pushKV("minfeefilter", stats.minFeeFilter);
UniValue sendPerMsgCmd(UniValue::VOBJ);
for (const auto &i : stats.mapSendBytesPerMsgCmd) {
if (i.second > 0) {
sendPerMsgCmd.pushKV(i.first, i.second);
}
}
obj.pushKV("bytessent_per_msg", sendPerMsgCmd);
UniValue recvPerMsgCmd(UniValue::VOBJ);
for (const auto &i : stats.mapRecvBytesPerMsgCmd) {
if (i.second > 0) {
recvPerMsgCmd.pushKV(i.first, i.second);
}
}
obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd);
obj.pushKV("connection_type", stats.m_conn_type_string);
if (stats.m_availabilityScore) {
obj.pushKV("availability_score",
*stats.m_availabilityScore);
}
ret.push_back(obj);
}
return ret;
},
};
}
static RPCHelpMan addnode() {
return RPCHelpMan{
"addnode",
"Attempts to add or remove a node from the addnode list.\n"
"Or try a connection to a node once.\n"
"Nodes added using addnode (or -connect) are protected from "
"DoS disconnection and are not required to be\n"
"full nodes as other outbound peers are (though such peers "
"will not be synced from).\n",
{
{"node", RPCArg::Type::STR, RPCArg::Optional::NO,
"The node (see getpeerinfo for nodes)"},
{"command", RPCArg::Type::STR, RPCArg::Optional::NO,
"'add' to add a node to the list, 'remove' to remove a "
"node from the list, 'onetry' to try a connection to the "
"node once"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") +
HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (strCommand != "onetry" && strCommand != "add" &&
strCommand != "remove") {
throw std::runtime_error(self.ToString());
}
NodeContext &node = EnsureAnyNodeContext(request.context);
CConnman &connman = EnsureConnman(node);
std::string strNode = request.params[0].get_str();
if (strCommand == "onetry") {
CAddress addr;
connman.OpenNetworkConnection(addr, false, nullptr,
strNode.c_str(),
ConnectionType::MANUAL);
return NullUniValue;
}
if ((strCommand == "add") && (!connman.AddNode(strNode))) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: Node already added");
} else if ((strCommand == "remove") &&
(!connman.RemoveAddedNode(strNode))) {
throw JSONRPCError(
RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node could not be removed. It has not been "
"added previously.");
}
return NullUniValue;
},
};
}
static RPCHelpMan addconnection() {
return RPCHelpMan{
"addconnection",
"\nOpen an outbound connection to a specified node. This RPC is for "
"testing only.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The IP address and port to attempt connecting to."},
{"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO,
"Type of connection to open (\"outbound-full-relay\", "
"\"block-relay-only\", \"addr-fetch\", \"feeler\" or "
"\"avalanche\")."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"Address of newly added connection."},
{RPCResult::Type::STR, "connection_type",
"Type of connection opened."},
}},
RPCExamples{
HelpExampleCli("addconnection",
"\"192.168.0.6:8333\" \"outbound-full-relay\"") +
HelpExampleRpc("addconnection",
"\"192.168.0.6:8333\" \"outbound-full-relay\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (config.GetChainParams().NetworkIDString() !=
CBaseChainParams::REGTEST) {
throw std::runtime_error("addconnection is for regression "
"testing (-regtest mode) only.");
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR});
const std::string address = request.params[0].get_str();
const std::string conn_type_in{
TrimString(request.params[1].get_str())};
ConnectionType conn_type{};
if (conn_type_in == "outbound-full-relay") {
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
} else if (conn_type_in == "block-relay-only") {
conn_type = ConnectionType::BLOCK_RELAY;
} else if (conn_type_in == "addr-fetch") {
conn_type = ConnectionType::ADDR_FETCH;
} else if (conn_type_in == "feeler") {
conn_type = ConnectionType::FEELER;
} else if (conn_type_in == "avalanche") {
if (!g_avalanche || !isAvalancheEnabled(gArgs)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Error: avalanche outbound requested "
"but avalanche is not enabled.");
}
conn_type = ConnectionType::AVALANCHE_OUTBOUND;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString());
}
NodeContext &node = EnsureAnyNodeContext(request.context);
CConnman &connman = EnsureConnman(node);
const bool success = connman.AddConnection(address, conn_type);
if (!success) {
throw JSONRPCError(RPC_CLIENT_NODE_CAPACITY_REACHED,
"Error: Already at capacity for specified "
"connection type.");
}
UniValue info(UniValue::VOBJ);
info.pushKV("address", address);
info.pushKV("connection_type", conn_type_in);
return info;
},
};
}
static RPCHelpMan disconnectnode() {
return RPCHelpMan{
"disconnectnode",
"Immediately disconnects from the specified peer node.\n"
"\nStrictly one out of 'address' and 'nodeid' can be provided to "
"identify the node.\n"
"\nTo disconnect by nodeid, either set 'address' to the empty string, "
"or call using the named 'nodeid' argument only.\n",
{
{"address", RPCArg::Type::STR,
/* default */ "fallback to nodeid",
"The IP address/port of the node"},
{"nodeid", RPCArg::Type::NUM,
/* default */ "fallback to address",
"The node ID (see getpeerinfo for node IDs)"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleCli("disconnectnode", "\"\" 1") +
HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleRpc("disconnectnode", "\"\", 1")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
CConnman &connman = EnsureConnman(node);
bool success;
const UniValue &address_arg = request.params[0];
const UniValue &id_arg = request.params[1];
if (!address_arg.isNull() && id_arg.isNull()) {
/* handle disconnect-by-address */
success = connman.DisconnectNode(address_arg.get_str());
} else if (!id_arg.isNull() && (address_arg.isNull() ||
(address_arg.isStr() &&
address_arg.get_str().empty()))) {
/* handle disconnect-by-id */
NodeId nodeid = (NodeId)id_arg.get_int64();
success = connman.DisconnectNode(nodeid);
} else {
throw JSONRPCError(
RPC_INVALID_PARAMS,
"Only one of address and nodeid should be provided.");
}
if (!success) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED,
"Node not found in connected nodes");
}
return NullUniValue;
},
};
}
static RPCHelpMan getaddednodeinfo() {
return RPCHelpMan{
"getaddednodeinfo",
"Returns information about the given added node, or all added nodes\n"
"(note that onetry addnodes are not listed here)\n",
{
{"node", RPCArg::Type::STR, /* default */ "all nodes",
"If provided, return information about this specific node, "
"otherwise all nodes are returned."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "addednode",
"The node IP address or name (as provided to addnode)"},
{RPCResult::Type::BOOL, "connected", "If connected"},
{RPCResult::Type::ARR,
"addresses",
"Only when connected = true",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"The bitcoin server IP and port we're "
"connected to"},
{RPCResult::Type::STR, "connected",
"connection, inbound or outbound"},
}},
}},
}},
}},
RPCExamples{HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") +
HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
const CConnman &connman = EnsureConnman(node);
std::vector<AddedNodeInfo> vInfo = connman.GetAddedNodeInfo();
if (!request.params[0].isNull()) {
bool found = false;
for (const AddedNodeInfo &info : vInfo) {
if (info.strAddedNode == request.params[0].get_str()) {
vInfo.assign(1, info);
found = true;
break;
}
}
if (!found) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node has not been added.");
}
}
UniValue ret(UniValue::VARR);
for (const AddedNodeInfo &info : vInfo) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("addednode", info.strAddedNode);
obj.pushKV("connected", info.fConnected);
UniValue addresses(UniValue::VARR);
if (info.fConnected) {
UniValue address(UniValue::VOBJ);
address.pushKV("address", info.resolvedAddress.ToString());
address.pushKV("connected",
info.fInbound ? "inbound" : "outbound");
addresses.push_back(address);
}
obj.pushKV("addresses", addresses);
ret.push_back(obj);
}
return ret;
},
};
}
static RPCHelpMan getnettotals() {
return RPCHelpMan{
"getnettotals",
"Returns information about network traffic, including bytes in, "
"bytes out,\n"
"and current time.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "totalbytesrecv",
"Total bytes received"},
{RPCResult::Type::NUM, "totalbytessent", "Total bytes sent"},
{RPCResult::Type::NUM_TIME, "timemillis",
"Current " + UNIX_EPOCH_TIME + " in milliseconds"},
{RPCResult::Type::OBJ,
"uploadtarget",
"",
{
{RPCResult::Type::NUM, "timeframe",
"Length of the measuring timeframe in seconds"},
{RPCResult::Type::NUM, "target", "Target in bytes"},
{RPCResult::Type::BOOL, "target_reached",
"True if target is reached"},
{RPCResult::Type::BOOL, "serve_historical_blocks",
"True if serving historical blocks"},
{RPCResult::Type::NUM, "bytes_left_in_cycle",
"Bytes left in current time cycle"},
{RPCResult::Type::NUM, "time_left_in_cycle",
"Seconds left in current time cycle"},
}},
}},
RPCExamples{HelpExampleCli("getnettotals", "") +
HelpExampleRpc("getnettotals", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
const CConnman &connman = EnsureConnman(node);
UniValue obj(UniValue::VOBJ);
obj.pushKV("totalbytesrecv", connman.GetTotalBytesRecv());
obj.pushKV("totalbytessent", connman.GetTotalBytesSent());
obj.pushKV("timemillis", GetTimeMillis());
UniValue outboundLimit(UniValue::VOBJ);
outboundLimit.pushKV(
"timeframe", count_seconds(connman.GetMaxOutboundTimeframe()));
outboundLimit.pushKV("target", connman.GetMaxOutboundTarget());
outboundLimit.pushKV("target_reached",
connman.OutboundTargetReached(false));
outboundLimit.pushKV("serve_historical_blocks",
!connman.OutboundTargetReached(true));
outboundLimit.pushKV("bytes_left_in_cycle",
connman.GetOutboundTargetBytesLeft());
outboundLimit.pushKV(
"time_left_in_cycle",
count_seconds(connman.GetMaxOutboundTimeLeftInCycle()));
obj.pushKV("uploadtarget", outboundLimit);
return obj;
},
};
}
static UniValue GetNetworksInfo() {
UniValue networks(UniValue::VARR);
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n);
if (network == NET_UNROUTABLE || network == NET_CJDNS ||
network == NET_INTERNAL) {
continue;
}
proxyType proxy;
UniValue obj(UniValue::VOBJ);
GetProxy(network, proxy);
obj.pushKV("name", GetNetworkName(network));
obj.pushKV("limited", !IsReachable(network));
obj.pushKV("reachable", IsReachable(network));
obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort()
: std::string());
obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials);
networks.push_back(obj);
}
return networks;
}
static RPCHelpMan getnetworkinfo() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"getnetworkinfo",
"Returns an object containing various state info regarding P2P "
"networking.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "version", "the server version"},
{RPCResult::Type::STR, "subversion",
"the server subversion string"},
{RPCResult::Type::NUM, "protocolversion",
"the protocol version"},
{RPCResult::Type::STR_HEX, "localservices",
"the services we offer to the network"},
{RPCResult::Type::ARR,
"localservicesnames",
"the services we offer to the network, in human-readable form",
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name"},
}},
{RPCResult::Type::BOOL, "localrelay",
"true if transaction relay is requested from peers"},
{RPCResult::Type::NUM, "timeoffset", "the time offset"},
{RPCResult::Type::NUM, "connections",
"the total number of connections"},
{RPCResult::Type::NUM, "connections_in",
"the number of inbound connections"},
{RPCResult::Type::NUM, "connections_out",
"the number of outbound connections"},
{RPCResult::Type::BOOL, "networkactive",
"whether p2p networking is enabled"},
{RPCResult::Type::ARR,
"networks",
"information per network",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "name",
"network (" + Join(GetNetworkNames(), ", ") + ")"},
{RPCResult::Type::BOOL, "limited",
"is the network limited using -onlynet?"},
{RPCResult::Type::BOOL, "reachable",
"is the network reachable?"},
{RPCResult::Type::STR, "proxy",
"(\"host:port\") the proxy that is used for this "
"network, or empty if none"},
{RPCResult::Type::BOOL, "proxy_randomize_credentials",
"Whether randomized credentials are used"},
}},
}},
{RPCResult::Type::NUM, "relayfee",
"minimum relay fee for transactions in " + ticker + "/kB"},
{RPCResult::Type::NUM, "excessutxocharge",
"minimum charge for excess utxos in " + ticker},
{RPCResult::Type::ARR,
"localaddresses",
"list of local addresses",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address", "network address"},
{RPCResult::Type::NUM, "port", "network port"},
{RPCResult::Type::NUM, "score", "relative score"},
}},
}},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getnetworkinfo", "") +
HelpExampleRpc("getnetworkinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
obj.pushKV("version", CLIENT_VERSION);
obj.pushKV("subversion", userAgent(config));
obj.pushKV("protocolversion", PROTOCOL_VERSION);
NodeContext &node = EnsureAnyNodeContext(request.context);
if (node.connman) {
ServiceFlags services = node.connman->GetLocalServices();
obj.pushKV("localservices", strprintf("%016x", services));
obj.pushKV("localservicesnames", GetServicesNames(services));
}
if (node.peerman) {
obj.pushKV("localrelay", !node.peerman->IgnoresIncomingTxs());
}
obj.pushKV("timeoffset", GetTimeOffset());
if (node.connman) {
obj.pushKV("networkactive", node.connman->GetNetworkActive());
obj.pushKV("connections", int(node.connman->GetNodeCount(
CConnman::CONNECTIONS_ALL)));
obj.pushKV("connections_in", int(node.connman->GetNodeCount(
CConnman::CONNECTIONS_IN)));
obj.pushKV("connections_out", int(node.connman->GetNodeCount(
CConnman::CONNECTIONS_OUT)));
}
obj.pushKV("networks", GetNetworksInfo());
obj.pushKV("relayfee", ::minRelayTxFee.GetFeePerK());
obj.pushKV("excessutxocharge", config.GetExcessUTXOCharge());
UniValue localAddresses(UniValue::VARR);
{
LOCK(cs_mapLocalHost);
for (const std::pair<const CNetAddr, LocalServiceInfo> &item :
mapLocalHost) {
UniValue rec(UniValue::VOBJ);
rec.pushKV("address", item.first.ToString());
rec.pushKV("port", item.second.nPort);
rec.pushKV("score", item.second.nScore);
localAddresses.push_back(rec);
}
}
obj.pushKV("localaddresses", localAddresses);
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
};
}
static RPCHelpMan setban() {
return RPCHelpMan{
"setban",
"Attempts to add or remove an IP/Subnet from the banned list.\n",
{
{"subnet", RPCArg::Type::STR, RPCArg::Optional::NO,
"The IP/Subnet (see getpeerinfo for nodes IP) with an optional "
"netmask (default is /32 = single IP)"},
{"command", RPCArg::Type::STR, RPCArg::Optional::NO,
"'add' to add an IP/Subnet to the list, 'remove' to remove an "
"IP/Subnet from the list"},
{"bantime", RPCArg::Type::NUM, /* default */ "0",
"time in seconds how long (or until when if [absolute] is set) "
"the IP is banned (0 or empty means using the default time of 24h "
"which can also be overwritten by the -bantime startup argument)"},
{"absolute", RPCArg::Type::BOOL, /* default */ "false",
"If set, the bantime must be an absolute timestamp expressed in " +
UNIX_EPOCH_TIME},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") +
HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") +
HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400")},
[&](const RPCHelpMan &help, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (strCommand != "add" && strCommand != "remove") {
throw std::runtime_error(help.ToString());
}
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
CSubNet subNet;
CNetAddr netAddr;
bool isSubnet = false;
if (request.params[0].get_str().find('/') != std::string::npos) {
isSubnet = true;
}
if (!isSubnet) {
CNetAddr resolved;
LookupHost(request.params[0].get_str(), resolved, false);
netAddr = resolved;
} else {
LookupSubNet(request.params[0].get_str(), subNet);
}
if (!(isSubnet ? subNet.IsValid() : netAddr.IsValid())) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Invalid IP/Subnet");
}
if (strCommand == "add") {
if (isSubnet ? node.banman->IsBanned(subNet)
: node.banman->IsBanned(netAddr)) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: IP/Subnet already banned");
}
// Use standard bantime if not specified.
int64_t banTime = 0;
if (!request.params[2].isNull()) {
banTime = request.params[2].get_int64();
}
bool absolute = false;
if (request.params[3].isTrue()) {
absolute = true;
}
if (isSubnet) {
node.banman->Ban(subNet, banTime, absolute);
if (node.connman) {
node.connman->DisconnectNode(subNet);
}
} else {
node.banman->Ban(netAddr, banTime, absolute);
if (node.connman) {
node.connman->DisconnectNode(netAddr);
}
}
} else if (strCommand == "remove") {
if (!(isSubnet ? node.banman->Unban(subNet)
: node.banman->Unban(netAddr))) {
throw JSONRPCError(
RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Unban failed. Requested address/subnet "
"was not previously manually banned.");
}
}
return NullUniValue;
},
};
}
static RPCHelpMan listbanned() {
return RPCHelpMan{
"listbanned",
"List all manually banned IPs/Subnets.\n",
{},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address", ""},
{RPCResult::Type::NUM_TIME, "banned_until", ""},
{RPCResult::Type::NUM_TIME, "ban_created", ""},
{RPCResult::Type::STR, "ban_reason", ""},
}},
}},
RPCExamples{HelpExampleCli("listbanned", "") +
HelpExampleRpc("listbanned", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
banmap_t banMap;
node.banman->GetBanned(banMap);
UniValue bannedAddresses(UniValue::VARR);
for (const auto &entry : banMap) {
const CBanEntry &banEntry = entry.second;
UniValue rec(UniValue::VOBJ);
rec.pushKV("address", entry.first.ToString());
rec.pushKV("banned_until", banEntry.nBanUntil);
rec.pushKV("ban_created", banEntry.nCreateTime);
bannedAddresses.push_back(rec);
}
return bannedAddresses;
},
};
}
static RPCHelpMan clearbanned() {
return RPCHelpMan{
"clearbanned",
"Clear all banned IPs.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("clearbanned", "") +
HelpExampleRpc("clearbanned", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
node.banman->ClearBanned();
return NullUniValue;
},
};
}
static RPCHelpMan setnetworkactive() {
return RPCHelpMan{
"setnetworkactive",
"Disable/enable all p2p network activity.\n",
{
{"state", RPCArg::Type::BOOL, RPCArg::Optional::NO,
"true to enable networking, false to disable"},
},
RPCResult{RPCResult::Type::BOOL, "", "The value that was passed in"},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
CConnman &connman = EnsureConnman(node);
connman.SetNetworkActive(request.params[0].get_bool());
return connman.GetNetworkActive();
},
};
}
static RPCHelpMan getnodeaddresses() {
return RPCHelpMan{
"getnodeaddresses",
"Return known addresses, which can potentially be used to find new "
"nodes in the network.\n",
{
{"count", RPCArg::Type::NUM, /* default */ "1",
"The maximum number of addresses to return. Specify 0 to return "
"all known addresses."},
{"network", RPCArg::Type::STR, "all networks",
"Return only addresses of the specified network. Can be one of: " +
Join(GetNetworkNames(), ", ") + "."},
},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The " + UNIX_EPOCH_TIME +
" when the node was last seen"},
{RPCResult::Type::NUM, "services",
"The services offered by the node"},
{RPCResult::Type::STR, "address",
"The address of the node"},
{RPCResult::Type::NUM, "port",
"The port number of the node"},
{RPCResult::Type::STR, "network",
"The network (" + Join(GetNetworkNames(), ", ") +
") the node connected through"},
}},
}},
RPCExamples{HelpExampleCli("getnodeaddresses", "8") +
HelpExampleCli("getnodeaddresses", "4 \"i2p\"") +
HelpExampleCli("-named getnodeaddresses",
"network=onion count=12") +
HelpExampleRpc("getnodeaddresses", "8") +
HelpExampleRpc("getnodeaddresses", "4, \"i2p\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
const CConnman &connman = EnsureConnman(node);
const int count{
request.params[0].isNull() ? 1 : request.params[0].get_int()};
if (count < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Address count out of range");
}
const std::optional<Network> network{
request.params[1].isNull()
? std::nullopt
: std::optional<Network>{
ParseNetwork(request.params[1].get_str())}};
if (network == NET_UNROUTABLE) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Network not recognized: %s",
request.params[1].get_str()));
}
// returns a shuffled list of CAddress
const std::vector<CAddress> vAddr{
connman.GetAddresses(count, /* max_pct */ 0, network)};
UniValue ret(UniValue::VARR);
for (const CAddress &addr : vAddr) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("time", int(addr.nTime));
obj.pushKV("services", uint64_t(addr.nServices));
obj.pushKV("address", addr.ToStringIP());
obj.pushKV("port", addr.GetPort());
obj.pushKV("network", GetNetworkName(addr.GetNetClass()));
ret.push_back(obj);
}
return ret;
},
};
}
static RPCHelpMan addpeeraddress() {
return RPCHelpMan{
"addpeeraddress",
"Add the address of a potential peer to the address manager. This "
"RPC is for testing only.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The IP address of the peer"},
{"port", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The port of the peer"},
{"tried", RPCArg::Type::BOOL, "false",
"If true, attempt to add the peer to the tried addresses table"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "success",
"whether the peer address was successfully added to the "
"address manager"},
},
},
RPCExamples{
HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333 true") +
HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333, true")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.addrman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Address manager functionality missing or disabled");
}
const std::string &addr_string{request.params[0].get_str()};
const uint16_t port{
static_cast<uint16_t>(request.params[1].get_int())};
const bool tried{request.params[2].isTrue()};
UniValue obj(UniValue::VOBJ);
CNetAddr net_addr;
bool success{false};
if (LookupHost(addr_string, net_addr, false)) {
CAddress address{{net_addr, port}, ServiceFlags(NODE_NETWORK)};
address.nTime = GetAdjustedTime();
// The source address is set equal to the address. This is
// equivalent to the peer announcing itself.
if (node.addrman->Add({address}, address)) {
success = true;
if (tried) {
// Attempt to move the address to the tried addresses
// table.
node.addrman->Good(address);
}
}
}
obj.pushKV("success", success);
return obj;
},
};
}
void RegisterNetRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ------------------ ----------------------
{ "network", getconnectioncount, },
{ "network", ping, },
{ "network", getpeerinfo, },
{ "network", addnode, },
{ "network", disconnectnode, },
{ "network", getaddednodeinfo, },
{ "network", getnettotals, },
{ "network", getnetworkinfo, },
{ "network", setban, },
{ "network", listbanned, },
{ "network", clearbanned, },
{ "network", setnetworkactive, },
{ "network", getnodeaddresses, },
{ "hidden", addconnection, },
{ "hidden", addpeeraddress, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/rpc/net.h b/src/rpc/net.h
deleted file mode 100644
index fd0a877f1..000000000
--- a/src/rpc/net.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2021 The Bitcoin Core developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#ifndef BITCOIN_RPC_NET_H
-#define BITCOIN_RPC_NET_H
-
-class CConnman;
-class PeerManager;
-struct NodeContext;
-
-CConnman &EnsureConnman(const NodeContext &node);
-PeerManager &EnsurePeerman(const NodeContext &node);
-
-#endif // BITCOIN_RPC_NET_H
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 0ab4ac092..7dad4bea6 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1,2297 +1,2298 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
#include <config.h>
#include <consensus/amount.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
#include <merkleblock.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/psbt.h>
#include <node/transaction.h>
#include <policy/packages.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <psbt.h>
#include <random.h>
#include <rpc/blockchain.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/bip32.h>
#include <util/error.h>
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <validation.h>
#include <validationinterface.h>
#include <cstdint>
#include <numeric>
#include <univalue.h>
static void TxToJSON(const CTransaction &tx, const BlockHash &hashBlock,
UniValue &entry, CChainState &active_chainstate) {
// 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, BlockHash(), entry, true, RPCSerializationFlags());
if (!hashBlock.IsNull()) {
LOCK(cs_main);
entry.pushKV("blockhash", hashBlock.GetHex());
CBlockIndex *pindex =
active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
if (pindex) {
if (active_chainstate.m_chain.Contains(pindex)) {
entry.pushKV("confirmations",
1 + active_chainstate.m_chain.Height() -
pindex->nHeight);
entry.pushKV("time", pindex->GetBlockTime());
entry.pushKV("blocktime", pindex->GetBlockTime());
} else {
entry.pushKV("confirmations", 0);
}
}
}
}
static RPCHelpMan getrawtransaction() {
return RPCHelpMan{
"getrawtransaction",
"\nReturn the raw transaction data.\n"
"\nBy 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"
"\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'.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"If false, return a string, otherwise return a json object"},
{"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 verbose is set to true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "in_active_chain",
"Whether specified block is in the active chain or not "
"(only present with explicit \"blockhash\" argument)"},
{RPCResult::Type::STR_HEX, "hex",
"The serialized, hex-encoded data for 'txid'"},
{RPCResult::Type::STR_HEX, "txid",
"The transaction id (same as provided)"},
{RPCResult::Type::STR_HEX, "hash", "The transaction hash"},
{RPCResult::Type::NUM, "size",
"The serialized transaction size"},
{RPCResult::Type::NUM, "version", "The version"},
{RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
{RPCResult::Type::ARR,
"vin",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::STR, "vout", ""},
{RPCResult::Type::OBJ,
"scriptSig",
"The script",
{
{RPCResult::Type::STR, "asm", "asm"},
{RPCResult::Type::STR_HEX, "hex", "hex"},
}},
{RPCResult::Type::NUM, "sequence",
"The script sequence number"},
}},
}},
{RPCResult::Type::ARR,
"vout",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "value",
"The value in " + Currency::get().ticker},
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR, "asm", "the asm"},
{RPCResult::Type::STR, "hex", "the hex"},
{RPCResult::Type::NUM, "reqSigs",
"The required sigs"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
{RPCResult::Type::ARR,
"addresses",
"",
{
{RPCResult::Type::STR, "address",
"bitcoin address"},
}},
}},
}},
}},
{RPCResult::Type::STR_HEX, "blockhash", "the block hash"},
{RPCResult::Type::NUM, "confirmations",
"The confirmations"},
{RPCResult::Type::NUM_TIME, "blocktime",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "time", "Same as \"blocktime\""},
}},
},
RPCExamples{HelpExampleCli("getrawtransaction", "\"mytxid\"") +
HelpExampleCli("getrawtransaction", "\"mytxid\" true") +
HelpExampleRpc("getrawtransaction", "\"mytxid\", true") +
HelpExampleCli("getrawtransaction",
"\"mytxid\" false \"myblockhash\"") +
HelpExampleCli("getrawtransaction",
"\"mytxid\" true \"myblockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
bool in_active_chain = true;
TxId txid = TxId(ParseHashV(request.params[0], "parameter 1"));
CBlockIndex *blockindex = nullptr;
const CChainParams ¶ms = config.GetChainParams();
if (txid == params.GenesisBlock().hashMerkleRoot) {
// Special exception for the genesis block coinbase transaction
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;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].isNum()
? (request.params[1].get_int() != 0)
: request.params[1].get_bool();
}
if (!request.params[2].isNull()) {
LOCK(cs_main);
BlockHash blockhash(
ParseHashV(request.params[2], "parameter 3"));
blockindex = chainman.m_blockman.LookupBlockIndex(blockhash);
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;
if (g_txindex && !blockindex) {
f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain();
}
BlockHash hash_block;
const CTransactionRef tx =
GetTransaction(blockindex, node.mempool.get(), txid,
params.GetConsensus(), hash_block);
if (!tx) {
std::string errmsg;
if (blockindex) {
if (!blockindex->nStatus.hasData()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Block not available");
}
errmsg = "No such transaction found in the provided block";
} else if (!g_txindex) {
errmsg =
"No such mempool transaction. Use -txindex or provide "
"a block hash to enable blockchain transaction queries";
} else if (!f_txindex_ready) {
errmsg = "No such mempool transaction. Blockchain "
"transactions are still in the process of being "
"indexed";
} else {
errmsg = "No such mempool or blockchain transaction";
}
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
errmsg + ". Use gettransaction for wallet transactions.");
}
if (!fVerbose) {
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());
return result;
},
};
}
static RPCHelpMan gettxoutproof() {
return RPCHelpMan{
"gettxoutproof",
"Returns a hex-encoded proof that \"txid\" was included in a block.\n"
"\nNOTE: By default this function only works sometimes. "
"This is when there is an\n"
"unspent output in the utxo for this transaction. To make it always "
"work,\n"
"you need to maintain a transaction index, using the -txindex command "
"line option or\n"
"specify the block in which the transaction is included manually (by "
"blockhash).\n",
{
{
"txids",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The txids to filter",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"A transaction hash"},
},
},
{"blockhash", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED_NAMED_ARG,
"If specified, looks for txid in the block with this hash"},
},
RPCResult{
RPCResult::Type::STR, "data",
"A string that is a serialized, hex-encoded data for the proof."},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::set<TxId> setTxIds;
TxId oneTxId;
UniValue txids = request.params[0].get_array();
for (unsigned int idx = 0; idx < txids.size(); idx++) {
const UniValue &utxid = txids[idx];
TxId txid(ParseHashV(utxid, "txid"));
if (setTxIds.count(txid)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
std::string("Invalid parameter, duplicated txid: ") +
utxid.get_str());
}
setTxIds.insert(txid);
oneTxId = txid;
}
CBlockIndex *pblockindex = nullptr;
BlockHash hashBlock;
ChainstateManager &chainman = EnsureAnyChainman(request.context);
if (!request.params[1].isNull()) {
LOCK(cs_main);
hashBlock =
BlockHash(ParseHashV(request.params[1], "blockhash"));
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
} else {
LOCK(cs_main);
CChainState &active_chainstate = chainman.ActiveChainstate();
// Loop through txids and try to find which block they're in.
// Exit loop once a block is found.
for (const auto &txid : setTxIds) {
const Coin &coin =
AccessByTxid(active_chainstate.CoinsTip(), txid);
if (!coin.IsSpent()) {
pblockindex =
active_chainstate.m_chain[coin.GetHeight()];
break;
}
}
}
// Allow txindex to catch up if we need to query it and before we
// acquire cs_main.
if (g_txindex && !pblockindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
const Consensus::Params ¶ms =
config.GetChainParams().GetConsensus();
LOCK(cs_main);
if (pblockindex == nullptr) {
const CTransactionRef tx = GetTransaction(
/* block_index */ nullptr,
/* mempool */ nullptr, oneTxId, Params().GetConsensus(),
hashBlock);
if (!tx || hashBlock.IsNull()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not yet in block");
}
pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Transaction index corrupt");
}
}
CBlock block;
if (!ReadBlockFromDisk(block, pblockindex, params)) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Can't read block from disk");
}
unsigned int ntxFound = 0;
for (const auto &tx : block.vtx) {
if (setTxIds.count(tx->GetId())) {
ntxFound++;
}
}
if (ntxFound != setTxIds.size()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Not all transactions found in specified or "
"retrieved block");
}
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION);
CMerkleBlock mb(block, setTxIds);
ssMB << mb;
std::string strHex = HexStr(ssMB);
return strHex;
},
};
}
static RPCHelpMan verifytxoutproof() {
return RPCHelpMan{
"verifytxoutproof",
"Verifies that a proof points to a transaction in a block, returning "
"the transaction it commits to\n"
"and throwing an RPC error if the block is not in our best chain\n",
{
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex-encoded proof generated by gettxoutproof"},
},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The txid(s) which the proof commits to, or empty array "
"if the proof can not be validated."},
}},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK,
PROTOCOL_VERSION);
CMerkleBlock merkleBlock;
ssMB >> merkleBlock;
UniValue res(UniValue::VARR);
std::vector<uint256> vMatch;
std::vector<size_t> vIndex;
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) !=
merkleBlock.header.hashMerkleRoot) {
return res;
}
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
const CBlockIndex *pindex = chainman.m_blockman.LookupBlockIndex(
merkleBlock.header.GetHash());
if (!pindex || !chainman.ActiveChain().Contains(pindex) ||
pindex->nTx == 0) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found in chain");
}
// Check if proof is valid, only add results if so
if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
for (const uint256 &hash : vMatch) {
res.push_back(hash.GetHex());
}
}
return res;
},
};
}
static RPCHelpMan createrawtransaction() {
return RPCHelpMan{
"createrawtransaction",
"Create a transaction spending the given inputs and creating new "
"outputs.\n"
"Outputs can be addresses or data.\n"
"Returns hex-encoded raw transaction.\n"
"Note that the transaction's inputs are not signed, and\n"
"it is not stored in the wallet or transmitted to the network.\n",
{
{
"inputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The inputs",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"sequence", RPCArg::Type::NUM, /* default */
"depends on the value of the 'locktime' argument",
"The sequence number"},
},
},
},
},
{
"outputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The outputs (key-value pairs), where none of "
"the keys are duplicated.\n"
"That is, each address can only appear once and there can only "
"be one 'data' object.\n"
"For compatibility reasons, a dictionary, which holds the "
"key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"address", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO,
"A key-value pair. The key (string) is the "
"bitcoin address, the value (float or string) is "
"the amount in " +
Currency::get().ticker},
},
},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"data", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO,
"A key-value pair. The key must be \"data\", the "
"value is hex-encoded data"},
},
},
},
},
{"locktime", RPCArg::Type::NUM, /* default */ "0",
"Raw locktime. Non-0 value also locktime-activates inputs"},
},
RPCResult{RPCResult::Type::STR_HEX, "transaction",
"hex string of the transaction"},
RPCExamples{
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"address\\\":10000.00}]\"") +
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
HelpExampleRpc("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\", \"[{\\\"address\\\":10000.00}]\"") +
HelpExampleRpc("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params,
{UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM},
true);
CMutableTransaction rawTx =
ConstructTransaction(config.GetChainParams(), request.params[0],
request.params[1], request.params[2]);
return EncodeHexTx(CTransaction(rawTx));
},
};
}
static RPCHelpMan decoderawtransaction() {
return RPCHelpMan{
"decoderawtransaction",
"Return a JSON object representing the serialized, hex-encoded "
"transaction.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction hex string"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
{RPCResult::Type::STR_HEX, "hash", "The transaction hash"},
{RPCResult::Type::NUM, "size", "The transaction size"},
{RPCResult::Type::NUM, "version", "The version"},
{RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
{RPCResult::Type::ARR,
"vin",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::NUM, "vout", "The output number"},
{RPCResult::Type::OBJ,
"scriptSig",
"The script",
{
{RPCResult::Type::STR, "asm", "asm"},
{RPCResult::Type::STR_HEX, "hex", "hex"},
}},
{RPCResult::Type::NUM, "sequence",
"The script sequence number"},
}},
}},
{RPCResult::Type::ARR,
"vout",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "value",
"The value in " + Currency::get().ticker},
{RPCResult::Type::NUM, "n", "index"},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR, "asm", "the asm"},
{RPCResult::Type::STR_HEX, "hex", "the hex"},
{RPCResult::Type::NUM, "reqSigs",
"The required sigs"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
{RPCResult::Type::ARR,
"addresses",
"",
{
{RPCResult::Type::STR, "address",
"bitcoin address"},
}},
}},
}},
}},
}},
RPCExamples{HelpExampleCli("decoderawtransaction", "\"hexstring\"") +
HelpExampleRpc("decoderawtransaction", "\"hexstring\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"TX decode failed");
}
UniValue result(UniValue::VOBJ);
TxToUniv(CTransaction(std::move(mtx)), BlockHash(), result, false);
return result;
},
};
}
static RPCHelpMan decodescript() {
return RPCHelpMan{
"decodescript",
"Decode a hex-encoded script.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hex-encoded script"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "asm", "Script public key"},
{RPCResult::Type::STR, "type",
"The output type (e.g. " + GetAllOutputTypes() + ")"},
{RPCResult::Type::NUM, "reqSigs", "The required signatures"},
{RPCResult::Type::ARR,
"addresses",
"",
{
{RPCResult::Type::STR, "address", "bitcoin address"},
}},
{RPCResult::Type::STR, "p2sh",
"address of P2SH script wrapping this redeem script (not "
"returned if the script is already a P2SH)"},
}},
RPCExamples{HelpExampleCli("decodescript", "\"hexstring\"") +
HelpExampleRpc("decodescript", "\"hexstring\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
UniValue r(UniValue::VOBJ);
CScript script;
if (request.params[0].get_str().size() > 0) {
std::vector<uint8_t> scriptData(
ParseHexV(request.params[0], "argument"));
script = CScript(scriptData.begin(), scriptData.end());
} else {
// Empty scripts are valid.
}
ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false);
UniValue type;
type = find_value(r, "type");
if (type.isStr() && type.get_str() != "scripthash") {
// P2SH cannot be wrapped in a P2SH. If this script is already a
// P2SH, don't return the address for a P2SH of the P2SH.
r.pushKV("p2sh", EncodeDestination(ScriptHash(script), config));
}
return r;
},
};
}
static RPCHelpMan combinerawtransaction() {
return RPCHelpMan{
"combinerawtransaction",
"Combine multiple partially signed transactions into one "
"transaction.\n"
"The combined transaction may be another partially signed transaction "
"or a \n"
"fully signed transaction.",
{
{
"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The hex strings of partially signed "
"transactions",
{
{"hexstring", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED,
"A hex-encoded raw transaction"},
},
},
},
RPCResult{RPCResult::Type::STR, "",
"The hex-encoded raw transaction with signature(s)"},
RPCExamples{HelpExampleCli("combinerawtransaction",
R"('["myhex1", "myhex2", "myhex3"]')")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
UniValue txs = request.params[0].get_array();
std::vector<CMutableTransaction> txVariants(txs.size());
for (unsigned int idx = 0; idx < txs.size(); idx++) {
if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) {
throw JSONRPCError(
RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed for tx %d", idx));
}
}
if (txVariants.empty()) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Missing transactions");
}
// mergedTx will end up with all the signatures; it
// starts as a clone of the rawtx:
CMutableTransaction mergedTx(txVariants[0]);
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
NodeContext &node = EnsureAnyNodeContext(request.context);
const CTxMemPool &mempool = EnsureMemPool(node);
ChainstateManager &chainman = EnsureChainman(node);
LOCK2(cs_main, mempool.cs);
CCoinsViewCache &viewChain =
chainman.ActiveChainstate().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
// temporarily switch cache backend to db+mempool view
view.SetBackend(viewMempool);
for (const CTxIn &txin : mergedTx.vin) {
// Load entries from viewChain into view; can fail.
view.AccessCoin(txin.prevout);
}
// switch back to avoid locking mempool for too long
view.SetBackend(viewDummy);
}
// Use CTransaction for the constant parts of the
// transaction to avoid rehashing.
const CTransaction txConst(mergedTx);
// Sign what we can:
for (size_t i = 0; i < mergedTx.vin.size(); i++) {
CTxIn &txin = mergedTx.vin[i];
const Coin &coin = view.AccessCoin(txin.prevout);
if (coin.IsSpent()) {
throw JSONRPCError(RPC_VERIFY_ERROR,
"Input not found or already spent");
}
SignatureData sigdata;
const CTxOut &txout = coin.GetTxOut();
// ... and merge in other signatures:
for (const CMutableTransaction &txv : txVariants) {
if (txv.vin.size() > i) {
sigdata.MergeSignatureData(
DataFromTransaction(txv, i, txout));
}
}
ProduceSignature(DUMMY_SIGNING_PROVIDER,
MutableTransactionSignatureCreator(
&mergedTx, i, txout.nValue),
txout.scriptPubKey, sigdata);
UpdateInput(txin, sigdata);
}
return EncodeHexTx(CTransaction(mergedTx));
},
};
}
static RPCHelpMan signrawtransactionwithkey() {
return RPCHelpMan{
"signrawtransactionwithkey",
"Sign inputs for raw transaction (serialized, hex-encoded).\n"
"The second argument is an array of base58-encoded private\n"
"keys that will be the only keys used to sign the transaction.\n"
"The third optional argument (may be null) is an array of previous "
"transaction outputs that\n"
"this transaction depends on but may not yet be in the block chain.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction hex string"},
{
"privkeys",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The base58-encoded private keys for signing",
{
{"privatekey", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"private key in base58-encoding"},
},
},
{
"prevtxs",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The previous dependent transaction outputs",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"scriptPubKey", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "script key"},
{"redeemScript", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED,
"(required for P2SH) redeem script"},
{"amount", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO, "The amount spent"},
},
},
},
},
{"sighashtype", RPCArg::Type::STR, /* default */ "ALL|FORKID",
"The signature hash type. Must be one of:\n"
" \"ALL|FORKID\"\n"
" \"NONE|FORKID\"\n"
" \"SINGLE|FORKID\"\n"
" \"ALL|FORKID|ANYONECANPAY\"\n"
" \"NONE|FORKID|ANYONECANPAY\"\n"
" \"SINGLE|FORKID|ANYONECANPAY\""},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hex",
"The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete",
"If the transaction has a complete set of signatures"},
{RPCResult::Type::ARR,
"errors",
/* optional */ true,
"Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The hash of the referenced, previous transaction"},
{RPCResult::Type::NUM, "vout",
"The index of the output to spent and used as "
"input"},
{RPCResult::Type::STR_HEX, "scriptSig",
"The hex-encoded signature script"},
{RPCResult::Type::NUM, "sequence",
"Script sequence number"},
{RPCResult::Type::STR, "error",
"Verification or signing error related to the "
"input"},
}},
}},
}},
RPCExamples{
HelpExampleCli("signrawtransactionwithkey",
"\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") +
HelpExampleRpc("signrawtransactionwithkey",
"\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params,
{UniValue::VSTR, UniValue::VARR, UniValue::VARR,
UniValue::VSTR},
true);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"TX decode failed");
}
FillableSigningProvider keystore;
const UniValue &keys = request.params[1].get_array();
for (size_t idx = 0; idx < keys.size(); ++idx) {
UniValue k = keys[idx];
CKey key = DecodeSecret(k.get_str());
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid private key");
}
keystore.AddKey(key);
}
// Fetch previous transactions (inputs):
std::map<COutPoint, Coin> coins;
for (const CTxIn &txin : mtx.vin) {
// Create empty map entry keyed by prevout.
coins[txin.prevout];
}
NodeContext &node = EnsureAnyNodeContext(request.context);
FindCoins(node, coins);
// Parse the prevtxs array
ParsePrevouts(request.params[2], &keystore, coins);
UniValue result(UniValue::VOBJ);
SignTransaction(mtx, &keystore, coins, request.params[3], result);
return result;
},
};
}
static RPCHelpMan sendrawtransaction() {
return RPCHelpMan{
"sendrawtransaction",
"Submits raw transaction (serialized, hex-encoded) to local node and "
"network.\n"
"\nAlso see createrawtransaction and "
"signrawtransactionwithkey calls.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex string of the raw transaction"},
{"maxfeerate", RPCArg::Type::AMOUNT,
/* default */
FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
"Reject transactions whose fee rate is higher than the specified "
"value, expressed in " +
Currency::get().ticker +
"/kB\nSet to 0 to accept any fee rate.\n"},
},
RPCResult{RPCResult::Type::STR_HEX, "", "The transaction hash in hex"},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli(
"createrawtransaction",
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" "
"\"{\\\"myaddress\\\":10000}\"") +
"Sign the transaction, and get back the hex\n" +
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
"\nSend the transaction (signed hex)\n" +
HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("sendrawtransaction", "\"signedhex\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params,
{
UniValue::VSTR,
// VNUM or VSTR, checked inside AmountFromValue()
UniValueType(),
});
// parse hex string from parameter
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"TX decode failed");
}
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const CFeeRate max_raw_tx_fee_rate =
request.params[1].isNull()
? DEFAULT_MAX_RAW_TX_FEE_RATE
: CFeeRate(AmountFromValue(request.params[1]));
int64_t virtual_size = GetVirtualTransactionSize(*tx);
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
std::string err_string;
AssertLockNotHeld(cs_main);
NodeContext &node = EnsureAnyNodeContext(request.context);
const TransactionError err = BroadcastTransaction(
node, config, tx, err_string, max_raw_tx_fee, /*relay*/ true,
/*wait_callback*/ true);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err, err_string);
}
return tx->GetHash().GetHex();
},
};
}
static RPCHelpMan testmempoolaccept() {
return RPCHelpMan{
"testmempoolaccept",
"\nReturns result of mempool acceptance tests indicating if raw "
"transaction(s) (serialized, hex-encoded) would be accepted by "
"mempool.\n"
"\nIf multiple transactions are passed in, parents must come before "
"children and package policies apply: the transactions cannot conflict "
"with any mempool transactions or each other.\n"
"\nIf one transaction fails, other transactions may not be fully "
"validated (the 'allowed' key will be blank).\n"
"\nThe maximum number of transactions allowed is " +
ToString(MAX_PACKAGE_COUNT) +
".\n"
"\nThis checks if transactions violate the consensus or policy "
"rules.\n"
"\nSee sendrawtransaction call.\n",
{
{
"rawtxs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"An array of hex strings of raw transactions.",
{
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
""},
},
},
{"maxfeerate", RPCArg::Type::AMOUNT,
/* default */
FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
"Reject transactions whose fee rate is higher than the specified "
"value, expressed in " +
Currency::get().ticker + "/kB\n"},
},
RPCResult{
RPCResult::Type::ARR,
"",
"The result of the mempool acceptance test for each raw "
"transaction in the input array.\n"
"Returns results for each transaction in the same order they were "
"passed in.\n"
"Transactions that cannot be fully validated due to failures in "
"other transactions will not contain an 'allowed' result.\n",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction hash in hex"},
{RPCResult::Type::STR, "package-error",
"Package validation error, if any (only possible if "
"rawtxs had more than 1 transaction)."},
{RPCResult::Type::BOOL, "allowed",
"Whether this tx would be accepted to the mempool and "
"pass client-specified maxfeerate. "
"If not present, the tx was not fully validated due to a "
"failure in another tx in the list."},
{RPCResult::Type::NUM, "size", "The transaction size"},
{RPCResult::Type::OBJ,
"fees",
"Transaction fees (only present if 'allowed' is true)",
{
{RPCResult::Type::STR_AMOUNT, "base",
"transaction fee in " + Currency::get().ticker},
}},
{RPCResult::Type::STR, "reject-reason",
"Rejection string (only present when 'allowed' is "
"false)"},
}},
}},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli(
"createrawtransaction",
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" "
"\"{\\\"myaddress\\\":10000}\"") +
"Sign the transaction, and get back the hex\n" +
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
"\nTest acceptance of the transaction (signed hex)\n" +
HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params,
{
UniValue::VARR,
// VNUM or VSTR, checked inside AmountFromValue()
UniValueType(),
});
const UniValue raw_transactions = request.params[0].get_array();
if (raw_transactions.size() < 1 ||
raw_transactions.size() > MAX_PACKAGE_COUNT) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Array must contain between 1 and " +
ToString(MAX_PACKAGE_COUNT) +
" transactions.");
}
const CFeeRate max_raw_tx_fee_rate =
request.params[1].isNull()
? DEFAULT_MAX_RAW_TX_FEE_RATE
: CFeeRate(AmountFromValue(request.params[1]));
std::vector<CTransactionRef> txns;
txns.reserve(raw_transactions.size());
for (const auto &rawtx : raw_transactions.getValues()) {
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, rawtx.get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"TX decode failed: " + rawtx.get_str());
}
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
}
NodeContext &node = EnsureAnyNodeContext(request.context);
CTxMemPool &mempool = EnsureMemPool(node);
ChainstateManager &chainman = EnsureChainman(node);
CChainState &chainstate = chainman.ActiveChainstate();
const PackageMempoolAcceptResult package_result = [&] {
LOCK(::cs_main);
if (txns.size() > 1) {
return ProcessNewPackage(config, chainstate, mempool, txns,
/* test_accept */ true);
}
return PackageMempoolAcceptResult(
txns[0]->GetId(),
chainman.ProcessTransaction(txns[0],
/* test_accept*/ true));
}();
UniValue rpc_result(UniValue::VARR);
// We will check transaction fees while we iterate through txns in
// order. If any transaction fee exceeds maxfeerate, we will leave
// the rest of the validation results blank, because it doesn't make
// sense to return a validation result for a transaction if its
// ancestor(s) would not be submitted.
bool exit_early{false};
for (const auto &tx : txns) {
UniValue result_inner(UniValue::VOBJ);
result_inner.pushKV("txid", tx->GetId().GetHex());
if (package_result.m_state.GetResult() ==
PackageValidationResult::PCKG_POLICY) {
result_inner.pushKV(
"package-error",
package_result.m_state.GetRejectReason());
}
auto it = package_result.m_tx_results.find(tx->GetId());
if (exit_early || it == package_result.m_tx_results.end()) {
// Validation unfinished. Just return the txid.
rpc_result.push_back(result_inner);
continue;
}
const auto &tx_result = it->second;
// Package testmempoolaccept doesn't allow transactions to
// already be in the mempool.
CHECK_NONFATAL(tx_result.m_result_type !=
MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
if (tx_result.m_result_type ==
MempoolAcceptResult::ResultType::VALID) {
const Amount fee = tx_result.m_base_fees.value();
// Check that fee does not exceed maximum fee
const int64_t virtual_size = tx_result.m_vsize.value();
const Amount max_raw_tx_fee =
max_raw_tx_fee_rate.GetFee(virtual_size);
if (max_raw_tx_fee != Amount::zero() &&
fee > max_raw_tx_fee) {
result_inner.pushKV("allowed", false);
result_inner.pushKV("reject-reason",
"max-fee-exceeded");
exit_early = true;
} else {
// Only return the fee and size if the transaction
// would pass ATMP.
// These can be used to calculate the feerate.
result_inner.pushKV("allowed", true);
result_inner.pushKV("size", virtual_size);
UniValue fees(UniValue::VOBJ);
fees.pushKV("base", fee);
result_inner.pushKV("fees", fees);
}
} else {
result_inner.pushKV("allowed", false);
const TxValidationState state = tx_result.m_state;
if (state.GetResult() ==
TxValidationResult::TX_MISSING_INPUTS) {
result_inner.pushKV("reject-reason", "missing-inputs");
} else {
result_inner.pushKV("reject-reason",
state.GetRejectReason());
}
}
rpc_result.push_back(result_inner);
}
return rpc_result;
},
};
}
static RPCHelpMan decodepsbt() {
return RPCHelpMan{
"decodepsbt",
"Return a JSON object representing the serialized, base64-encoded "
"partially signed Bitcoin transaction.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"The PSBT base64 string"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"tx",
"The decoded network-serialized unsigned transaction.",
{
{RPCResult::Type::ELISION, "",
"The layout is the same as the output of "
"decoderawtransaction."},
}},
{RPCResult::Type::OBJ_DYN,
"unknown",
"The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key",
"(key-value pair) An unknown key-value pair"},
}},
{RPCResult::Type::ARR,
"inputs",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"utxo",
/* optional */ true,
"Transaction output for UTXOs",
{
{RPCResult::Type::NUM, "amount",
"The value in " + Currency::get().ticker},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex",
"The hex"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address",
" Bitcoin address if there is one"},
}},
}},
{RPCResult::Type::OBJ_DYN,
"partial_signatures",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "pubkey",
"The public key and signature that corresponds "
"to it."},
}},
{RPCResult::Type::STR, "sighash", /* optional */ true,
"The sighash type to be used"},
{RPCResult::Type::OBJ,
"redeem_script",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR,
"bip32_derivs",
/* optional */ true,
"",
{
{RPCResult::Type::OBJ,
"pubkey",
/* optional */ true,
"The public key with the derivation path as "
"the value.",
{
{RPCResult::Type::STR, "master_fingerprint",
"The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
{RPCResult::Type::OBJ,
"final_scriptsig",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR, "hex", "The hex"},
}},
{RPCResult::Type::OBJ_DYN,
"unknown",
"The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key",
"(key-value pair) An unknown key-value pair"},
}},
}},
}},
{RPCResult::Type::ARR,
"outputs",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::OBJ,
"redeem_script",
/* optional */ true,
"",
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type",
"The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR,
"bip32_derivs",
/* optional */ true,
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "pubkey",
"The public key this path corresponds to"},
{RPCResult::Type::STR, "master_fingerprint",
"The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
{RPCResult::Type::OBJ_DYN,
"unknown",
"The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key",
"(key-value pair) An unknown key-value pair"},
}},
}},
}},
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true,
"The transaction fee paid if all UTXOs slots in the PSBT have "
"been filled."},
}},
RPCExamples{HelpExampleCli("decodepsbt", "\"psbt\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
UniValue result(UniValue::VOBJ);
// Add the decoded tx
UniValue tx_univ(UniValue::VOBJ);
TxToUniv(CTransaction(*psbtx.tx), BlockHash(), tx_univ, false);
result.pushKV("tx", tx_univ);
// Unknown data
if (psbtx.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : psbtx.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
result.pushKV("unknown", unknowns);
}
// inputs
Amount total_in = Amount::zero();
bool have_all_utxos = true;
UniValue inputs(UniValue::VARR);
for (size_t i = 0; i < psbtx.inputs.size(); ++i) {
const PSBTInput &input = psbtx.inputs[i];
UniValue in(UniValue::VOBJ);
// UTXOs
if (!input.utxo.IsNull()) {
const CTxOut &txout = input.utxo;
UniValue out(UniValue::VOBJ);
out.pushKV("amount", txout.nValue);
if (MoneyRange(txout.nValue) &&
MoneyRange(total_in + txout.nValue)) {
total_in += txout.nValue;
} else {
// Hack to just not show fee later
have_all_utxos = false;
}
UniValue o(UniValue::VOBJ);
ScriptToUniv(txout.scriptPubKey, o, true);
out.pushKV("scriptPubKey", o);
in.pushKV("utxo", out);
} else {
have_all_utxos = false;
}
// Partial sigs
if (!input.partial_sigs.empty()) {
UniValue partial_sigs(UniValue::VOBJ);
for (const auto &sig : input.partial_sigs) {
partial_sigs.pushKV(HexStr(sig.second.first),
HexStr(sig.second.second));
}
in.pushKV("partial_signatures", partial_sigs);
}
// Sighash
uint8_t sighashbyte =
input.sighash_type.getRawSigHashType() & 0xff;
if (sighashbyte > 0) {
in.pushKV("sighash", SighashToStr(sighashbyte));
}
// Redeem script
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(input.redeem_script, r, false);
in.pushKV("redeem_script", r);
}
// keypaths
if (!input.hd_keypaths.empty()) {
UniValue keypaths(UniValue::VARR);
for (auto entry : input.hd_keypaths) {
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));
keypath.pushKV(
"master_fingerprint",
strprintf("%08x",
ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path",
WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
in.pushKV("bip32_derivs", keypaths);
}
// Final scriptSig
if (!input.final_script_sig.empty()) {
UniValue scriptsig(UniValue::VOBJ);
scriptsig.pushKV(
"asm", ScriptToAsmStr(input.final_script_sig, true));
scriptsig.pushKV("hex", HexStr(input.final_script_sig));
in.pushKV("final_scriptSig", scriptsig);
}
// Unknown data
if (input.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : input.unknown) {
unknowns.pushKV(HexStr(entry.first),
HexStr(entry.second));
}
in.pushKV("unknown", unknowns);
}
inputs.push_back(in);
}
result.pushKV("inputs", inputs);
// outputs
Amount output_value = Amount::zero();
UniValue outputs(UniValue::VARR);
for (size_t i = 0; i < psbtx.outputs.size(); ++i) {
const PSBTOutput &output = psbtx.outputs[i];
UniValue out(UniValue::VOBJ);
// Redeem script
if (!output.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(output.redeem_script, r, false);
out.pushKV("redeem_script", r);
}
// keypaths
if (!output.hd_keypaths.empty()) {
UniValue keypaths(UniValue::VARR);
for (auto entry : output.hd_keypaths) {
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));
keypath.pushKV(
"master_fingerprint",
strprintf("%08x",
ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path",
WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
out.pushKV("bip32_derivs", keypaths);
}
// Unknown data
if (output.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : output.unknown) {
unknowns.pushKV(HexStr(entry.first),
HexStr(entry.second));
}
out.pushKV("unknown", unknowns);
}
outputs.push_back(out);
// Fee calculation
if (MoneyRange(psbtx.tx->vout[i].nValue) &&
MoneyRange(output_value + psbtx.tx->vout[i].nValue)) {
output_value += psbtx.tx->vout[i].nValue;
} else {
// Hack to just not show fee later
have_all_utxos = false;
}
}
result.pushKV("outputs", outputs);
if (have_all_utxos) {
result.pushKV("fee", total_in - output_value);
}
return result;
},
};
}
static RPCHelpMan combinepsbt() {
return RPCHelpMan{
"combinepsbt",
"Combine multiple partially signed Bitcoin transactions into one "
"transaction.\n"
"Implements the Combiner role.\n",
{
{
"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The base64 strings of partially signed transactions",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"A base64 string of a PSBT"},
},
},
},
RPCResult{RPCResult::Type::STR, "",
"The base64-encoded partially signed transaction"},
RPCExamples{HelpExampleCli(
"combinepsbt", R"('["mybase64_1", "mybase64_2", "mybase64_3"]')")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Parameter 'txs' cannot be empty");
}
for (size_t i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
}
PartiallySignedTransaction merged_psbt;
const TransactionError error = CombinePSBTs(merged_psbt, psbtxs);
if (error != TransactionError::OK) {
throw JSONRPCTransactionError(error);
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << merged_psbt;
return EncodeBase64(MakeUCharSpan(ssTx));
},
};
}
static RPCHelpMan finalizepsbt() {
return RPCHelpMan{
"finalizepsbt",
"Finalize the inputs of a PSBT. If the transaction is fully signed, it "
"will produce a\n"
"network serialized transaction which can be broadcast with "
"sendrawtransaction. Otherwise a PSBT will be\n"
"created which has the final_scriptSigfields filled for inputs that "
"are complete.\n"
"Implements the Finalizer and Extractor roles.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"},
{"extract", RPCArg::Type::BOOL, /* default */ "true",
"If true and the transaction is complete,\n"
" extract and return the complete "
"transaction in normal network serialization instead of the "
"PSBT."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "psbt",
"The base64-encoded partially signed transaction if not "
"extracted"},
{RPCResult::Type::STR_HEX, "hex",
"The hex-encoded network transaction if extracted"},
{RPCResult::Type::BOOL, "complete",
"If the transaction has a complete set of signatures"},
}},
RPCExamples{HelpExampleCli("finalizepsbt", "\"psbt\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL},
true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
bool extract =
request.params[1].isNull() ||
(!request.params[1].isNull() && request.params[1].get_bool());
CMutableTransaction mtx;
bool complete = FinalizeAndExtractPSBT(psbtx, mtx);
UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
std::string result_str;
if (complete && extract) {
ssTx << mtx;
result_str = HexStr(ssTx);
result.pushKV("hex", result_str);
} else {
ssTx << psbtx;
result_str = EncodeBase64(ssTx.str());
result.pushKV("psbt", result_str);
}
result.pushKV("complete", complete);
return result;
},
};
}
static RPCHelpMan createpsbt() {
return RPCHelpMan{
"createpsbt",
"Creates a transaction in the Partially Signed Transaction format.\n"
"Implements the Creator role.\n",
{
{
"inputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The json objects",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"sequence", RPCArg::Type::NUM, /* default */
"depends on the value of the 'locktime' argument",
"The sequence number"},
},
},
},
},
{
"outputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The outputs (key-value pairs), where none of "
"the keys are duplicated.\n"
"That is, each address can only appear once and there can only "
"be one 'data' object.\n"
"For compatibility reasons, a dictionary, which holds the "
"key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"address", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO,
"A key-value pair. The key (string) is the "
"bitcoin address, the value (float or string) is "
"the amount in " +
Currency::get().ticker},
},
},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"data", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO,
"A key-value pair. The key must be \"data\", the "
"value is hex-encoded data"},
},
},
},
},
{"locktime", RPCArg::Type::NUM, /* default */ "0",
"Raw locktime. Non-0 value also locktime-activates inputs"},
},
RPCResult{RPCResult::Type::STR, "",
"The resulting raw transaction (base64-encoded string)"},
RPCExamples{HelpExampleCli(
"createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params,
{
UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM,
},
true);
CMutableTransaction rawTx =
ConstructTransaction(config.GetChainParams(), request.params[0],
request.params[1], request.params[2]);
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = rawTx;
for (size_t i = 0; i < rawTx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (size_t i = 0; i < rawTx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64(MakeUCharSpan(ssTx));
},
};
}
static RPCHelpMan converttopsbt() {
return RPCHelpMan{
"converttopsbt",
"Converts a network serialized transaction to a PSBT. "
"This should be used only with createrawtransaction and "
"fundrawtransaction\n"
"createpsbt and walletcreatefundedpsbt should be used for new "
"applications.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex string of a raw transaction"},
{"permitsigdata", RPCArg::Type::BOOL, /* default */ "false",
"If true, any signatures in the input will be discarded and "
"conversion.\n"
" will continue. If false, RPC will "
"fail if any signatures are present."},
},
RPCResult{RPCResult::Type::STR, "",
"The resulting raw transaction (base64-encoded string)"},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
"\nConvert the transaction to a PSBT\n" +
HelpExampleCli("converttopsbt", "\"rawtransaction\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL},
true);
// parse hex string from parameter
CMutableTransaction tx;
bool permitsigdata = request.params[1].isNull()
? false
: request.params[1].get_bool();
if (!DecodeHexTx(tx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"TX decode failed");
}
// Remove all scriptSigs from inputs
for (CTxIn &input : tx.vin) {
if (!input.scriptSig.empty() && !permitsigdata) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Inputs must not have scriptSigs");
}
input.scriptSig.clear();
}
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = tx;
for (size_t i = 0; i < tx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (size_t i = 0; i < tx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64(MakeUCharSpan(ssTx));
},
};
}
RPCHelpMan utxoupdatepsbt() {
return RPCHelpMan{
"utxoupdatepsbt",
"Updates all inputs and outputs in a PSBT with data from output "
"descriptors, the UTXO set or the mempool.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"},
{"descriptors",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"An array of either strings or objects",
{
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"An output descriptor"},
{"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"An object with an output descriptor and extra information",
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
"An output descriptor"},
{"range", RPCArg::Type::RANGE, "1000",
"Up to what index HD chains should be explored (either "
"end or [begin,end])"},
}},
}},
},
RPCResult{RPCResult::Type::STR, "",
"The base64-encoded partially signed transaction with inputs "
"updated"},
RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR},
true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
// Parse descriptors, if any.
FlatSigningProvider provider;
if (!request.params[1].isNull()) {
auto descs = request.params[1].get_array();
for (size_t i = 0; i < descs.size(); ++i) {
EvalDescriptorStringOrObject(descs[i], provider);
}
}
// We don't actually need private keys further on; hide them as a
// precaution.
HidingSigningProvider public_provider(&provider, /* nosign */ true,
/* nobip32derivs */ false);
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
NodeContext &node = EnsureAnyNodeContext(request.context);
const CTxMemPool &mempool = EnsureMemPool(node);
ChainstateManager &chainman = EnsureChainman(node);
LOCK2(cs_main, mempool.cs);
CCoinsViewCache &viewChain =
chainman.ActiveChainstate().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
// temporarily switch cache backend to db+mempool view
view.SetBackend(viewMempool);
for (const CTxIn &txin : psbtx.tx->vin) {
// Load entries from viewChain into view; can fail.
view.AccessCoin(txin.prevout);
}
// switch back to avoid locking mempool for too long
view.SetBackend(viewDummy);
}
// Fill the inputs
for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput &input = psbtx.inputs.at(i);
if (!input.utxo.IsNull()) {
continue;
}
// Update script/keypath information using descriptor data.
// Note that SignPSBTInput does a lot more than just
// constructing ECDSA signatures we don't actually care about
// those here, in fact.
SignPSBTInput(public_provider, psbtx, i,
/* sighash_type */ SigHashType().withForkId());
}
// Update script/keypath information using descriptor data.
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
UpdatePSBTOutput(public_provider, psbtx, i);
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64(MakeUCharSpan(ssTx));
},
};
}
RPCHelpMan joinpsbts() {
return RPCHelpMan{
"joinpsbts",
"Joins multiple distinct PSBTs with different inputs and outputs "
"into one PSBT with inputs and outputs from all of the PSBTs\n"
"No input in any of the PSBTs can be in more than one of the PSBTs.\n",
{{"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"The base64 strings of partially signed transactions",
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"}}}},
RPCResult{RPCResult::Type::STR, "",
"The base64-encoded partially signed transaction"},
RPCExamples{HelpExampleCli("joinpsbts", "\"psbt\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.size() <= 1) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"At least two PSBTs are required to join PSBTs.");
}
uint32_t best_version = 1;
uint32_t best_locktime = 0xffffffff;
for (size_t i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
// Choose the highest version number
if (static_cast<uint32_t>(psbtx.tx->nVersion) > best_version) {
best_version = static_cast<uint32_t>(psbtx.tx->nVersion);
}
// Choose the lowest lock time
if (psbtx.tx->nLockTime < best_locktime) {
best_locktime = psbtx.tx->nLockTime;
}
}
// Create a blank psbt where everything will be added
PartiallySignedTransaction merged_psbt;
merged_psbt.tx = CMutableTransaction();
merged_psbt.tx->nVersion = static_cast<int32_t>(best_version);
merged_psbt.tx->nLockTime = best_locktime;
// Merge
for (auto &psbt : psbtxs) {
for (size_t i = 0; i < psbt.tx->vin.size(); ++i) {
if (!merged_psbt.AddInput(psbt.tx->vin[i],
psbt.inputs[i])) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Input %s:%d exists in multiple PSBTs",
psbt.tx->vin[i]
.prevout.GetTxId()
.ToString()
.c_str(),
psbt.tx->vin[i].prevout.GetN()));
}
}
for (size_t i = 0; i < psbt.tx->vout.size(); ++i) {
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]);
}
merged_psbt.unknown.insert(psbt.unknown.begin(),
psbt.unknown.end());
}
// Generate list of shuffled indices for shuffling inputs and
// outputs of the merged PSBT
std::vector<int> input_indices(merged_psbt.inputs.size());
std::iota(input_indices.begin(), input_indices.end(), 0);
std::vector<int> output_indices(merged_psbt.outputs.size());
std::iota(output_indices.begin(), output_indices.end(), 0);
// Shuffle input and output indices lists
Shuffle(input_indices.begin(), input_indices.end(),
FastRandomContext());
Shuffle(output_indices.begin(), output_indices.end(),
FastRandomContext());
PartiallySignedTransaction shuffled_psbt;
shuffled_psbt.tx = CMutableTransaction();
shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion;
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
for (int i : input_indices) {
shuffled_psbt.AddInput(merged_psbt.tx->vin[i],
merged_psbt.inputs[i]);
}
for (int i : output_indices) {
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i],
merged_psbt.outputs[i]);
}
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(),
merged_psbt.unknown.end());
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << shuffled_psbt;
return EncodeBase64(MakeUCharSpan(ssTx));
},
};
}
RPCHelpMan analyzepsbt() {
return RPCHelpMan{
"analyzepsbt",
"Analyzes and provides information about the current status of a "
"PSBT and its inputs\n",
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"}},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ARR,
"inputs",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "has_utxo",
"Whether a UTXO is provided"},
{RPCResult::Type::BOOL, "is_final",
"Whether the input is finalized"},
{RPCResult::Type::OBJ,
"missing",
/* optional */ true,
"Things that are missing that are required to "
"complete this input",
{
{RPCResult::Type::ARR,
"pubkeys",
/* optional */ true,
"",
{
{RPCResult::Type::STR_HEX, "keyid",
"Public key ID, hash160 of the public "
"key, of a public key whose BIP 32 "
"derivation path is missing"},
}},
{RPCResult::Type::ARR,
"signatures",
/* optional */ true,
"",
{
{RPCResult::Type::STR_HEX, "keyid",
"Public key ID, hash160 of the public "
"key, of a public key whose signature is "
"missing"},
}},
{RPCResult::Type::STR_HEX, "redeemscript",
/* optional */ true,
"Hash160 of the redeemScript that is missing"},
}},
{RPCResult::Type::STR, "next", /* optional */ true,
"Role of the next person that this input needs to "
"go to"},
}},
}},
{RPCResult::Type::NUM, "estimated_vsize", /* optional */ true,
"Estimated vsize of the final signed transaction"},
{RPCResult::Type::STR_AMOUNT, "estimated_feerate",
/* optional */ true,
"Estimated feerate of the final signed transaction in " +
Currency::get().ticker +
"/kB. Shown only if all UTXO slots in the PSBT have been "
"filled"},
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true,
"The transaction fee paid. Shown only if all UTXO slots in "
"the PSBT have been filled"},
{RPCResult::Type::STR, "next",
"Role of the next person that this psbt needs to go to"},
{RPCResult::Type::STR, "error", /* optional */ true,
"Error message (if there is one)"},
}},
RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transaction
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
PSBTAnalysis psbta = AnalyzePSBT(psbtx);
UniValue result(UniValue::VOBJ);
UniValue inputs_result(UniValue::VARR);
for (const auto &input : psbta.inputs) {
UniValue input_univ(UniValue::VOBJ);
UniValue missing(UniValue::VOBJ);
input_univ.pushKV("has_utxo", input.has_utxo);
input_univ.pushKV("is_final", input.is_final);
input_univ.pushKV("next", PSBTRoleName(input.next));
if (!input.missing_pubkeys.empty()) {
UniValue missing_pubkeys_univ(UniValue::VARR);
for (const CKeyID &pubkey : input.missing_pubkeys) {
missing_pubkeys_univ.push_back(HexStr(pubkey));
}
missing.pushKV("pubkeys", missing_pubkeys_univ);
}
if (!input.missing_redeem_script.IsNull()) {
missing.pushKV("redeemscript",
HexStr(input.missing_redeem_script));
}
if (!input.missing_sigs.empty()) {
UniValue missing_sigs_univ(UniValue::VARR);
for (const CKeyID &pubkey : input.missing_sigs) {
missing_sigs_univ.push_back(HexStr(pubkey));
}
missing.pushKV("signatures", missing_sigs_univ);
}
if (!missing.getKeys().empty()) {
input_univ.pushKV("missing", missing);
}
inputs_result.push_back(input_univ);
}
if (!inputs_result.empty()) {
result.pushKV("inputs", inputs_result);
}
if (psbta.estimated_vsize != std::nullopt) {
result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize);
}
if (psbta.estimated_feerate != std::nullopt) {
result.pushKV("estimated_feerate",
psbta.estimated_feerate->GetFeePerK());
}
if (psbta.fee != std::nullopt) {
result.pushKV("fee", *psbta.fee);
}
result.pushKV("next", PSBTRoleName(psbta.next));
if (!psbta.error.empty()) {
result.pushKV("error", psbta.error);
}
return result;
},
};
}
void RegisterRawTransactionRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ------------------ ----------------------
{ "rawtransactions", getrawtransaction, },
{ "rawtransactions", createrawtransaction, },
{ "rawtransactions", decoderawtransaction, },
{ "rawtransactions", decodescript, },
{ "rawtransactions", sendrawtransaction, },
{ "rawtransactions", combinerawtransaction, },
{ "rawtransactions", signrawtransactionwithkey, },
{ "rawtransactions", testmempoolaccept, },
{ "rawtransactions", decodepsbt, },
{ "rawtransactions", combinepsbt, },
{ "rawtransactions", finalizepsbt, },
{ "rawtransactions", createpsbt, },
{ "rawtransactions", converttopsbt, },
{ "rawtransactions", utxoupdatepsbt, },
{ "rawtransactions", joinpsbts, },
{ "rawtransactions", analyzepsbt, },
{ "blockchain", gettxoutproof, },
{ "blockchain", verifytxoutproof, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/rpc/server_util.cpp b/src/rpc/server_util.cpp
new file mode 100644
index 000000000..3c0066ad8
--- /dev/null
+++ b/src/rpc/server_util.cpp
@@ -0,0 +1,64 @@
+// Copyright (c) 2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <rpc/server_util.h>
+
+#include <net_processing.h>
+#include <node/context.h>
+#include <rpc/protocol.h>
+#include <rpc/request.h>
+#include <txmempool.h>
+#include <util/system.h>
+#include <validation.h>
+
+#include <any>
+
+NodeContext &EnsureAnyNodeContext(const std::any &context) {
+ auto node_context = util::AnyPtr<NodeContext>(context);
+ if (!node_context) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found");
+ }
+ return *node_context;
+}
+
+CTxMemPool &EnsureMemPool(const NodeContext &node) {
+ if (!node.mempool) {
+ throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED,
+ "Mempool disabled or instance not found");
+ }
+ return *node.mempool;
+}
+
+CTxMemPool &EnsureAnyMemPool(const std::any &context) {
+ return EnsureMemPool(EnsureAnyNodeContext(context));
+}
+
+ChainstateManager &EnsureChainman(const NodeContext &node) {
+ if (!node.chainman) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found");
+ }
+ return *node.chainman;
+}
+
+ChainstateManager &EnsureAnyChainman(const std::any &context) {
+ return EnsureChainman(EnsureAnyNodeContext(context));
+}
+
+CConnman &EnsureConnman(const NodeContext &node) {
+ if (!node.connman) {
+ throw JSONRPCError(
+ RPC_CLIENT_P2P_DISABLED,
+ "Error: Peer-to-peer functionality missing or disabled");
+ }
+ return *node.connman;
+}
+
+PeerManager &EnsurePeerman(const NodeContext &node) {
+ if (!node.peerman) {
+ throw JSONRPCError(
+ RPC_CLIENT_P2P_DISABLED,
+ "Error: Peer-to-peer functionality missing or disabled");
+ }
+ return *node.peerman;
+}
diff --git a/src/rpc/server_util.h b/src/rpc/server_util.h
new file mode 100644
index 000000000..35e437010
--- /dev/null
+++ b/src/rpc/server_util.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_RPC_SERVER_UTIL_H
+#define BITCOIN_RPC_SERVER_UTIL_H
+
+#include <any>
+
+class CConnman;
+class ChainstateManager;
+class CTxMemPool;
+struct NodeContext;
+class PeerManager;
+
+NodeContext &EnsureAnyNodeContext(const std::any &context);
+CTxMemPool &EnsureMemPool(const NodeContext &node);
+CTxMemPool &EnsureAnyMemPool(const std::any &context);
+ChainstateManager &EnsureChainman(const NodeContext &node);
+ChainstateManager &EnsureAnyChainman(const std::any &context);
+CConnman &EnsureConnman(const NodeContext &node);
+PeerManager &EnsurePeerman(const NodeContext &node);
+
+#endif // BITCOIN_RPC_SERVER_UTIL_H
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Apr 27, 10:27 (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5573253
Default Alt Text
(495 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment