Page MenuHomePhabricator

No OneTemporary

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 &param,
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 &param) {
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 &param,
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 &params,
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 &params = 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 &currency = 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 &params = 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 &params =
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

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)

Event Timeline