diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index 021d5e1597624..94149106ee208 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -64,7 +64,7 @@ export BASE_OUTDIR=${BASE_OUTDIR:-$BASE_SCRATCH_DIR/out} # The folder for previous release binaries. # This folder exists only on the ci guest, and on the ci host as a volume. export PREVIOUS_RELEASES_DIR=${PREVIOUS_RELEASES_DIR:-$BASE_ROOT_DIR/prev_releases} -export CI_BASE_PACKAGES=${CI_BASE_PACKAGES:-build-essential pkg-config curl ca-certificates ccache python3 rsync git procps bison e2fsprogs cmake} +export CI_BASE_PACKAGES=${CI_BASE_PACKAGES:-build-essential pkg-config curl ca-certificates ccache python3 rsync git procps bison e2fsprogs cmake net-tools tcpdump} export GOAL=${GOAL:-install} export DIR_QA_ASSETS=${DIR_QA_ASSETS:-${BASE_SCRATCH_DIR}/qa-assets} export CI_RETRY_EXE=${CI_RETRY_EXE:-"retry --"} diff --git a/ci/test/00_setup_env_i686_centos.sh b/ci/test/00_setup_env_i686_centos.sh index 22657742d7c0e..d6bd93187041a 100755 --- a/ci/test/00_setup_env_i686_centos.sh +++ b/ci/test/00_setup_env_i686_centos.sh @@ -10,7 +10,7 @@ export HOST=i686-pc-linux-gnu export CONTAINER_NAME=ci_i686_centos export CI_IMAGE_NAME_TAG="quay.io/centos/amd64:stream9" export STREAM_GCC_V="12" -export CI_BASE_PACKAGES="gcc-toolset-${STREAM_GCC_V}-gcc-c++ glibc-devel.x86_64 gcc-toolset-${STREAM_GCC_V}-libstdc++-devel.x86_64 glibc-devel.i686 gcc-toolset-${STREAM_GCC_V}-libstdc++-devel.i686 ccache make git python3 python3-pip which patch lbzip2 xz procps-ng dash rsync coreutils bison e2fsprogs cmake" +export CI_BASE_PACKAGES="gcc-toolset-${STREAM_GCC_V}-gcc-c++ glibc-devel.x86_64 gcc-toolset-${STREAM_GCC_V}-libstdc++-devel.x86_64 glibc-devel.i686 gcc-toolset-${STREAM_GCC_V}-libstdc++-devel.i686 ccache make git python3 python3-pip which patch lbzip2 xz procps-ng dash rsync coreutils bison e2fsprogs cmake net-tools tcpdump" export PIP_PACKAGES="pyzmq" export GOAL="install" export BITCOIN_CONFIG="-DWITH_ZMQ=ON -DBUILD_GUI=ON -DREDUCE_EXPORTS=ON" diff --git a/ci/test/00_setup_env_mac_native.sh b/ci/test/00_setup_env_mac_native.sh index e01a56895bfb6..317694e6f47a2 100755 --- a/ci/test/00_setup_env_mac_native.sh +++ b/ci/test/00_setup_env_mac_native.sh @@ -15,3 +15,5 @@ export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DREDUCE_EXPORTS=ON" export CI_OS_NAME="macos" export NO_DEPENDS=1 export OSX_SDK="" +# Can't run tcpdump: tcpdump: en0: You don't have permission to capture on that device +export CI_TCPDUMP_OK_TO_FAIL=1 diff --git a/ci/test/00_setup_env_mac_native_fuzz.sh b/ci/test/00_setup_env_mac_native_fuzz.sh index 1a453a4353f8c..977358d9cf4d3 100755 --- a/ci/test/00_setup_env_mac_native_fuzz.sh +++ b/ci/test/00_setup_env_mac_native_fuzz.sh @@ -14,3 +14,5 @@ export OSX_SDK="" export RUN_UNIT_TESTS=false export RUN_FUNCTIONAL_TESTS=false export RUN_FUZZ_TESTS=true +# Can't run tcpdump: tcpdump: en0: You don't have permission to capture on that device +export CI_TCPDUMP_OK_TO_FAIL=1 diff --git a/ci/test/02_run_container.sh b/ci/test/02_run_container.sh index ce01db325cebe..e1539e8bef3a6 100755 --- a/ci/test/02_run_container.sh +++ b/ci/test/02_run_container.sh @@ -91,7 +91,7 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then # Append $USER to /tmp/env to support multi-user systems and $CONTAINER_NAME # to allow support starting multiple runs simultaneously by the same user. # shellcheck disable=SC2086 - CI_CONTAINER_ID=$(docker run --cap-add LINUX_IMMUTABLE $CI_CONTAINER_CAP --rm --interactive --detach --tty \ + CI_CONTAINER_ID=$(docker run --cap-add LINUX_IMMUTABLE --cap-add NET_RAW $CI_CONTAINER_CAP --rm --interactive --detach --tty \ --mount "type=bind,src=$BASE_READ_ONLY_DIR,dst=$BASE_READ_ONLY_DIR,readonly" \ --mount "${CI_CCACHE_MOUNT}" \ --mount "${CI_DEPENDS_MOUNT}" \ diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh index 6e77a8927c222..7b10f3f27d5d5 100755 --- a/ci/test/03_test_script.sh +++ b/ci/test/03_test_script.sh @@ -146,21 +146,74 @@ if [ "$RUN_CHECK_DEPS" = "true" ]; then "${BASE_ROOT_DIR}/contrib/devtools/check-deps.sh" . fi +function get_interfaces() +{ + set -o pipefail + ifconfig | awk -F ':| ' '/^[^[:space:]]/ { if (!match($1, /^lo/)) { print $1 } }' + set +o pipefail +} + +function tcpdump_file() +{ + echo "/tmp/tcpdump_$1_$2" +} + +function traffic_monitor_begin() +{ + test_name="$1" + for ifname in $(get_interfaces) ; do + tcpdump -nU -i "$ifname" -w "$(tcpdump_file "$test_name" "$ifname")" & + done +} + +function traffic_monitor_end() +{ + test_name="$1" + + for ifname in $(get_interfaces) ; do + f=$(tcpdump_file "$test_name" "$ifname") + if [ ! -e "$f" ] && [ "$CI_TCPDUMP_OK_TO_FAIL" = "1" ] ; then + # In some CI environments this script is not running as root and so the + # tcpdump errors and does not create $f. Skip silently those, but we + # need at least one where tcpdump can run and this is the ASAN one. So + # treat the absence of $f as an error only on the ASAN task. + continue + fi + # We are running as root and those files are created with owner:group = + # tcpdump:tcpdump and then `tcpdump -r` refuses to read them with an error + # "permission denied" if they are not owned by root:root. + chown root:root "$f" + out="$(tcpdump -n -r "$f" --direction=out tcp or udp)" + if [ -n "$out" ] ; then + echo "Error: outbound TCP or UDP packets on the non loopback interface generated during $test_name tests:" >&2 + tcpdump -n -r "$f" tcp or udp + exit 1 + fi + done +} + if [ "$RUN_UNIT_TESTS" = "true" ]; then + traffic_monitor_begin "unitparallel" DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" CTEST_OUTPUT_ON_FAILURE=ON ctest --stop-on-failure "${MAKEJOBS}" --timeout $(( TEST_RUNNER_TIMEOUT_FACTOR * 60 )) + traffic_monitor_end "unitparallel" fi if [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then + traffic_monitor_begin "unitsequential" DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" "${BASE_OUTDIR}"/bin/test_bitcoin --catch_system_errors=no -l test_suite + traffic_monitor_end "unitsequential" fi if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then + traffic_monitor_begin "functional" # parses TEST_RUNNER_EXTRA as an array which allows for multiple arguments such as TEST_RUNNER_EXTRA='--exclude "rpc_bind.py --ipv6"' eval "TEST_RUNNER_EXTRA=($TEST_RUNNER_EXTRA)" LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" test/functional/test_runner.py --ci "${MAKEJOBS}" --tmpdirprefix "${BASE_SCRATCH_DIR}"/test_runner/ --ansi --combinedlogslen=99999999 --timeout-factor="${TEST_RUNNER_TIMEOUT_FACTOR}" "${TEST_RUNNER_EXTRA[@]}" --quiet --failfast + traffic_monitor_end "functional" fi if [ "${RUN_TIDY}" = "true" ]; then + traffic_monitor_begin "tidy" cmake -B /tidy-build -DLLVM_DIR=/usr/lib/llvm-"${TIDY_LLVM_V}"/cmake -DCMAKE_BUILD_TYPE=Release -S "${BASE_ROOT_DIR}"/contrib/devtools/bitcoin-tidy cmake --build /tidy-build "$MAKEJOBS" cmake --build /tidy-build --target bitcoin-tidy-tests "$MAKEJOBS" @@ -185,9 +238,12 @@ if [ "${RUN_TIDY}" = "true" ]; then cd "${BASE_ROOT_DIR}/src" python3 "/include-what-you-use/fix_includes.py" --nosafe_headers < /tmp/iwyu_ci.out git --no-pager diff + traffic_monitor_end "tidy" fi if [ "$RUN_FUZZ_TESTS" = "true" ]; then + traffic_monitor_begin "fuzz" # shellcheck disable=SC2086 LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" test/fuzz/test_runner.py ${FUZZ_TESTS_CONFIG} "${MAKEJOBS}" -l DEBUG "${DIR_FUZZ_IN}" --empty_min_time=60 + traffic_monitor_end "fuzz" fi diff --git a/doc/release-notes-31583.md b/doc/release-notes-31583.md new file mode 100644 index 0000000000000..e641fc766b5c0 --- /dev/null +++ b/doc/release-notes-31583.md @@ -0,0 +1,14 @@ +Updated RPCs +--- +- `getmininginfo` now returns `nBits` and the current target in the `target` field. It also returns a `next` object which specifies the `height`, `nBits`, `difficulty`, and `target` for the next block. +- `getdifficulty` can now return the difficulty for the next block (rather than the current tip) when calling with the boolean `next` argument set to true. +- `getblock` and `getblockheader` now return the current target in the `target` field +- `getblockchaininfo` and `getchainstates` now return `nBits` and the current target in the `target` field + +New RPCs +--- +- `gettarget` can be used to return the current target (for tip) or target for the next block (with the `next` argument) + +REST interface +--- +- `GET /rest/block/.json` and `GET /rest/headers/.json` now return the current target in the `target` field diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 889c00c78327f..89fdd855a4598 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -109,6 +109,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL addresstype.cpp base58.cpp bech32.cpp + chain.cpp chainparams.cpp chainparamsbase.cpp coins.cpp @@ -142,6 +143,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL outputtype.cpp policy/feerate.cpp policy/policy.cpp + pow.cpp protocol.cpp psbt.cpp rpc/external_signer.cpp @@ -200,7 +202,6 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL bip324.cpp blockencodings.cpp blockfilter.cpp - chain.cpp consensus/tx_verify.cpp dbwrapper.cpp deploymentstatus.cpp @@ -262,7 +263,6 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL policy/rbf.cpp policy/settings.cpp policy/truc_policy.cpp - pow.cpp rest.cpp rpc/blockchain.cpp rpc/fees.cpp diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index 7e3e2d8e48ab0..df951a14e4955 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -48,8 +48,9 @@ struct TestBlockAndIndex { static void BlockToJsonVerbose(benchmark::Bench& bench) { TestBlockAndIndex data; + const uint256 pow_limit{data.testing_setup->m_node.chainman->GetParams().GetConsensus().powLimit}; bench.run([&] { - auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, data.blockindex, data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); + auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, data.blockindex, data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT, pow_limit); ankerl::nanobench::doNotOptimizeAway(univalue); }); } @@ -59,7 +60,8 @@ BENCHMARK(BlockToJsonVerbose, benchmark::PriorityLevel::HIGH); static void BlockToJsonVerboseWrite(benchmark::Bench& bench) { TestBlockAndIndex data; - auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, data.blockindex, data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); + const uint256 pow_limit{data.testing_setup->m_node.chainman->GetParams().GetConsensus().powLimit}; + auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, data.blockindex, data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT, pow_limit); bench.run([&] { auto str = univalue.write(); ankerl::nanobench::doNotOptimizeAway(str); diff --git a/src/pow.cpp b/src/pow.cpp index bbcf39b5932ed..686b177fe3259 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -143,7 +143,7 @@ bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& return CheckProofOfWorkImpl(hash, nBits, params); } -bool CheckProofOfWorkImpl(uint256 hash, unsigned int nBits, const Consensus::Params& params) +std::optional DeriveTarget(unsigned int nBits, const uint256 pow_limit) { bool fNegative; bool fOverflow; @@ -152,8 +152,16 @@ bool CheckProofOfWorkImpl(uint256 hash, unsigned int nBits, const Consensus::Par bnTarget.SetCompact(nBits, &fNegative, &fOverflow); // Check range - if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit)) - return false; + if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(pow_limit)) + return {}; + + return bnTarget; +} + +bool CheckProofOfWorkImpl(uint256 hash, unsigned int nBits, const Consensus::Params& params) +{ + auto bnTarget{DeriveTarget(nBits, params.powLimit)}; + if (!bnTarget) return false; // Check proof of work matches claimed amount if (UintToArith256(hash) > bnTarget) diff --git a/src/pow.h b/src/pow.h index 2b28ade273c5c..ceba55d36a2e7 100644 --- a/src/pow.h +++ b/src/pow.h @@ -13,6 +13,18 @@ class CBlockHeader; class CBlockIndex; class uint256; +class arith_uint256; + +/** + * Convert nBits value to target. + * + * @param[in] nBits compact representation of the target + * @param[in] pow_limit PoW limit (consensus parameter) + * + * @return the proof-of-work target or nullopt if the nBits value + * is invalid (due to overflow or exceeding pow_limit) + */ +std::optional DeriveTarget(unsigned int nBits, const uint256 pow_limit); unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params&); unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params&); diff --git a/src/rest.cpp b/src/rest.cpp index 3e4b8b37c6c4b..bb064782f5d26 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -225,10 +225,10 @@ static bool rest_headers(const std::any& context, const CBlockIndex* tip = nullptr; std::vector headers; headers.reserve(*parsed_count); + ChainstateManager* maybe_chainman = GetChainman(context, req); + if (!maybe_chainman) return false; + ChainstateManager& chainman = *maybe_chainman; { - 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(); @@ -268,7 +268,7 @@ static bool rest_headers(const std::any& context, case RESTResponseFormat::JSON: { UniValue jsonHeaders(UniValue::VARR); for (const CBlockIndex *pindex : headers) { - jsonHeaders.push_back(blockheaderToJSON(*tip, *pindex)); + jsonHeaders.push_back(blockheaderToJSON(*tip, *pindex, chainman.GetConsensus().powLimit)); } std::string strJSON = jsonHeaders.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); @@ -341,7 +341,7 @@ static bool rest_block(const std::any& context, CBlock block{}; DataStream block_stream{block_data}; block_stream >> TX_WITH_WITNESS(block); - UniValue objBlock = blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity); + UniValue objBlock = blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity, chainman.GetConsensus().powLimit); std::string strJSON = objBlock.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); req->WriteReply(HTTP_OK, strJSON); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 823d2303c81fd..1d13f855f4fde 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -146,7 +146,7 @@ static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateMan } } -UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex) +UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex, const uint256 pow_limit) { // Serialize passed information without accessing chain state of the active chain! AssertLockNotHeld(cs_main); // For performance reasons @@ -164,6 +164,7 @@ UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex result.pushKV("mediantime", blockindex.GetMedianTimePast()); result.pushKV("nonce", blockindex.nNonce); result.pushKV("bits", strprintf("%08x", blockindex.nBits)); + result.pushKV("target", GetTarget(tip, pow_limit).GetHex()); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex.nChainWork.GetHex()); result.pushKV("nTx", blockindex.nTx); @@ -175,9 +176,9 @@ UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex return result; } -UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity) +UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity, const uint256 pow_limit) { - UniValue result = blockheaderToJSON(tip, blockindex); + UniValue result = blockheaderToJSON(tip, blockindex, pow_limit); result.pushKV("strippedsize", (int)::GetSerializeSize(TX_NO_WITNESS(block))); result.pushKV("size", (int)::GetSerializeSize(TX_WITH_WITNESS(block))); @@ -431,7 +432,9 @@ static RPCHelpMan getdifficulty() { return RPCHelpMan{"getdifficulty", "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n", - {}, + { + {"next", RPCArg::Type::BOOL, RPCArg::Default{false}, "difficulty for the next block"}, + }, RPCResult{ RPCResult::Type::NUM, "", "the proof-of-work difficulty as a multiple of the minimum difficulty."}, RPCExamples{ @@ -441,8 +444,55 @@ static RPCHelpMan getdifficulty() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { ChainstateManager& chainman = EnsureAnyChainman(request.context); - LOCK(cs_main); - return GetDifficulty(*CHECK_NONFATAL(chainman.ActiveChain().Tip())); + CBlockIndex& tip{*CHECK_NONFATAL(WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip()))}; + + bool next{false}; + if (!request.params[0].isNull()) { + next = request.params[0].get_bool(); + } + + if (next) { + CBlockIndex next_index; + NextEmptyBlockIndex(tip, chainman.GetConsensus(), next_index); + return GetDifficulty(next_index); + } else { + return GetDifficulty(tip); + } + +}, + }; +} + +static RPCHelpMan gettarget() +{ + return RPCHelpMan{"gettarget", + "\nReturns the proof-of-work target.\n", + { + {"next", RPCArg::Type::BOOL, RPCArg::Default{false}, "target for the next block"}, + }, + RPCResult{ + RPCResult::Type::STR_HEX, "", "the proof-of-work target."}, + RPCExamples{ + HelpExampleCli("gettarget", "") + + HelpExampleRpc("gettarget", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + ChainstateManager& chainman = EnsureAnyChainman(request.context); + CBlockIndex& tip{*CHECK_NONFATAL(WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip()))}; + + bool next{false}; + if (!request.params[0].isNull()) { + next = request.params[0].get_bool(); + } + + if (next) { + CBlockIndex next_index; + NextEmptyBlockIndex(tip, chainman.GetConsensus(), next_index); + return GetTarget(next_index, chainman.GetConsensus().powLimit).GetHex(); + } else { + return GetTarget(tip, chainman.GetConsensus().powLimit).GetHex(); + } }, }; } @@ -553,7 +603,8 @@ static RPCHelpMan getblockheader() {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::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"}, + {RPCResult::Type::STR_HEX, "target", "The difficulty target"}, {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"}, @@ -577,8 +628,8 @@ static RPCHelpMan getblockheader() const CBlockIndex* pblockindex; const CBlockIndex* tip; + ChainstateManager& chainman = EnsureAnyChainman(request.context); { - ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); pblockindex = chainman.m_blockman.LookupBlockIndex(hash); tip = chainman.ActiveChain().Tip(); @@ -596,7 +647,7 @@ static RPCHelpMan getblockheader() return strHex; } - return blockheaderToJSON(*tip, *pblockindex); + return blockheaderToJSON(*tip, *pblockindex, chainman.GetConsensus().powLimit); }, }; } @@ -727,7 +778,8 @@ static RPCHelpMan getblock() {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::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"}, + {RPCResult::Type::STR_HEX, "target", "The difficulty target"}, {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"}, @@ -802,7 +854,7 @@ static RPCHelpMan getblock() tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT; } - return blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity); + return blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity, chainman.GetConsensus().powLimit); }, }; } @@ -1296,6 +1348,8 @@ RPCHelpMan getblockchaininfo() {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::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"}, + {RPCResult::Type::STR_HEX, "target", "The difficulty target"}, {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}, @@ -1334,6 +1388,8 @@ RPCHelpMan getblockchaininfo() obj.pushKV("blocks", height); obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1); obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex()); + obj.pushKV("bits", strprintf("%08x", tip.nBits)); + obj.pushKV("target", GetTarget(tip, chainman.GetConsensus().powLimit).GetHex()); obj.pushKV("difficulty", GetDifficulty(tip)); obj.pushKV("time", tip.GetBlockTime()); obj.pushKV("mediantime", tip.GetMedianTimePast()); @@ -3301,6 +3357,8 @@ static RPCHelpMan loadtxoutset() const std::vector RPCHelpForChainstate{ {RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"}, {RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"}, + {RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"}, + {RPCResult::Type::STR_HEX, "target", "The difficulty target"}, {RPCResult::Type::NUM, "difficulty", "difficulty of the tip"}, {RPCResult::Type::NUM, "verificationprogress", "progress towards the network tip"}, {RPCResult::Type::STR_HEX, "snapshot_blockhash", /*optional=*/true, "the base block of the snapshot this chainstate is based on, if any"}, @@ -3343,6 +3401,8 @@ return RPCHelpMan{ data.pushKV("blocks", (int)chain.Height()); data.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); + data.pushKV("bits", strprintf("%08x", tip->nBits)); + data.pushKV("target", GetTarget(*tip, chainman.GetConsensus().powLimit).GetHex()); data.pushKV("difficulty", GetDifficulty(*tip)); data.pushKV("verificationprogress", chainman.GuessVerificationProgress(tip)); data.pushKV("coins_db_cache_bytes", cs.m_coinsdb_cache_size_bytes); @@ -3382,6 +3442,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t) {"blockchain", &getblockheader}, {"blockchain", &getchaintips}, {"blockchain", &getdifficulty}, + {"blockchain", &gettarget}, {"blockchain", &getdeploymentinfo}, {"blockchain", &gettxout}, {"blockchain", &gettxoutsetinfo}, diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 89b9921d5596b..954ede6519b4c 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -36,10 +36,10 @@ static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; double GetDifficulty(const CBlockIndex& blockindex); /** Block description to JSON */ -UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main); +UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity, const uint256 pow_limit) LOCKS_EXCLUDED(cs_main); /** Block header to JSON */ -UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex) LOCKS_EXCLUDED(cs_main); +UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex, const uint256 pow_limit) LOCKS_EXCLUDED(cs_main); /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector>& scores, int64_t total_weight); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 1b711e3c5b150..1805aa3c26066 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -114,6 +114,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getblock", 1, "verbose" }, { "getblockheader", 1, "verbose" }, { "getchaintxstats", 0, "nblocks" }, + { "getdifficulty", 0, "next" }, + { "gettarget", 0, "next" }, { "gettransaction", 1, "include_watchonly" }, { "gettransaction", 2, "verbose" }, { "getrawtransaction", 1, "verbosity" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 7e5936fddfc7b..5e3d1120e3201 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -421,11 +421,20 @@ static RPCHelpMan getmininginfo() {RPCResult::Type::NUM, "blocks", "The current block"}, {RPCResult::Type::NUM, "currentblockweight", /*optional=*/true, "The block weight 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::STR_HEX, "bits", "The current nBits, compact representation of the block difficulty target"}, {RPCResult::Type::NUM, "difficulty", "The current difficulty"}, + {RPCResult::Type::STR_HEX, "target", "The current target"}, {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 (" LIST_CHAIN_NAMES ")"}, {RPCResult::Type::STR_HEX, "signet_challenge", /*optional=*/true, "The block challenge (aka. block script), in hexadecimal (only present if the current network is a signet)"}, + {RPCResult::Type::OBJ, "next", "The next block", + { + {RPCResult::Type::NUM, "height", "The next height"}, + {RPCResult::Type::STR_HEX, "bits", "The next target nBits"}, + {RPCResult::Type::NUM, "difficulty", "The next difficulty"}, + {RPCResult::Type::STR_HEX, "target", "The next target"} + }}, (IsDeprecatedRPCEnabled("warnings") ? RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} : RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)", @@ -446,18 +455,32 @@ static RPCHelpMan getmininginfo() ChainstateManager& chainman = EnsureChainman(node); LOCK(cs_main); const CChain& active_chain = chainman.ActiveChain(); + CBlockIndex& tip{*CHECK_NONFATAL(active_chain.Tip())}; UniValue obj(UniValue::VOBJ); obj.pushKV("blocks", active_chain.Height()); if (BlockAssembler::m_last_block_weight) obj.pushKV("currentblockweight", *BlockAssembler::m_last_block_weight); if (BlockAssembler::m_last_block_num_txs) obj.pushKV("currentblocktx", *BlockAssembler::m_last_block_num_txs); - obj.pushKV("difficulty", GetDifficulty(*CHECK_NONFATAL(active_chain.Tip()))); + obj.pushKV("bits", strprintf("%08x", tip.nBits)); + obj.pushKV("difficulty", GetDifficulty(tip)); + obj.pushKV("target", GetTarget(tip, chainman.GetConsensus().powLimit).GetHex()); obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); obj.pushKV("chain", chainman.GetParams().GetChainTypeString()); + + UniValue next(UniValue::VOBJ); + CBlockIndex next_index; + NextEmptyBlockIndex(tip, chainman.GetConsensus(), next_index); + + next.pushKV("height", next_index.nHeight); + next.pushKV("bits", strprintf("%08x", next_index.nBits)); + next.pushKV("difficulty", GetDifficulty(next_index)); + next.pushKV("target", GetTarget(next_index, chainman.GetConsensus().powLimit).GetHex()); + obj.pushKV("next", next); + if (chainman.GetParams().GetChainType() == ChainType::SIGNET) { const std::vector& signet_challenge = - chainman.GetParams().GetConsensus().signet_challenge; + chainman.GetConsensus().signet_challenge; obj.pushKV("signet_challenge", HexStr(signet_challenge)); } obj.pushKV("warnings", node::GetWarningsForRpc(*CHECK_NONFATAL(node.warnings), IsDeprecatedRPCEnabled("warnings"))); diff --git a/src/rpc/server_util.cpp b/src/rpc/server_util.cpp index 0387cbb8e25ac..0edd7527a951c 100644 --- a/src/rpc/server_util.cpp +++ b/src/rpc/server_util.cpp @@ -4,10 +4,13 @@ #include +#include #include #include #include +#include #include +#include #include #include #include @@ -17,6 +20,7 @@ #include using node::NodeContext; +using node::UpdateTime; NodeContext& EnsureAnyNodeContext(const std::any& context) { @@ -129,3 +133,18 @@ AddrMan& EnsureAnyAddrman(const std::any& context) { return EnsureAddrman(EnsureAnyNodeContext(context)); } + +void NextEmptyBlockIndex(CBlockIndex& tip, const Consensus::Params& consensusParams, CBlockIndex& next_index) +{ + CBlockHeader next_header{}; + next_header.hashPrevBlock = tip.GetBlockHash(); + UpdateTime(&next_header, consensusParams, &tip); + next_header.nBits = GetNextWorkRequired(&tip, &next_header, consensusParams); + next_header.nNonce = 0; + + next_index.pprev = &tip; + next_index.nTime = next_header.nTime; + next_index.nBits = next_header.nBits; + next_index.nNonce = next_header.nNonce; + next_index.nHeight = tip.nHeight + 1; +} diff --git a/src/rpc/server_util.h b/src/rpc/server_util.h index 1e6fb7e6a666e..6fb5e1c9b35bc 100644 --- a/src/rpc/server_util.h +++ b/src/rpc/server_util.h @@ -7,8 +7,11 @@ #include +#include + class AddrMan; class ArgsManager; +class CBlockIndex; class CBlockPolicyEstimator; class CConnman; class CTxMemPool; @@ -39,4 +42,7 @@ PeerManager& EnsurePeerman(const node::NodeContext& node); AddrMan& EnsureAddrman(const node::NodeContext& node); AddrMan& EnsureAnyAddrman(const std::any& context); +/** Return an empty block index on top of the tip, with height, time and nBits set */ +void NextEmptyBlockIndex(CBlockIndex& tip, const Consensus::Params& consensusParams, CBlockIndex& next_index); + #endif // BITCOIN_RPC_SERVER_UTIL_H diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index b1fbc25641442..2941dda8c0ab4 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -4,6 +4,7 @@ #include // IWYU pragma: keep +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include