From 6accee18442f79fd2f2796027371a70e3757d825 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 3 Jan 2025 10:07:38 +0100 Subject: [PATCH 01/14] consensus: add DeriveTarget() to pow.h Split CheckProofOfWorkImpl() to introduce a helper function DeriveTarget() which converts the nBits value to the target. The function takes pow_limit as an argument so later commits can avoid having to pass ChainstateManager through the call stack. Co-authored-by: Ryan Ofsky --- src/pow.cpp | 14 +++++++++++--- src/pow.h | 12 ++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/pow.cpp b/src/pow.cpp index bbcf39b593..686b177fe3 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 2b28ade273..ceba55d36a 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&); From 3e976d4171b91662705458c88d6a33d42671ae5a Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Mon, 30 Dec 2024 17:56:54 +0100 Subject: [PATCH 02/14] build: move pow and chain to bitcoin_common The next commit needs pow.cpp in rpc/util.cpp. --- src/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 889c00c783..89fdd855a4 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 From 2401931542504afab7ccc2450d29122c7dec4bdb Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Wed, 8 Jan 2025 13:13:08 +0100 Subject: [PATCH 03/14] rpc: add nBits to getmininginfo Also expands nBits test coverage. --- src/rpc/blockchain.cpp | 4 ++-- src/rpc/mining.cpp | 5 ++++- test/functional/mining_basic.py | 3 +++ test/functional/rpc_blockchain.py | 6 ++++-- test/functional/test_framework/blocktools.py | 6 +++++- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 823d2303c8..3caf4bf4eb 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -553,7 +553,7 @@ 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::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"}, @@ -727,7 +727,7 @@ 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::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"}, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index e230281240..99eb87af27 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -421,6 +421,7 @@ 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::NUM, "networkhashps", "The network hashes per second"}, {RPCResult::Type::NUM, "pooledtx", "The size of the mempool"}, @@ -446,12 +447,14 @@ 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("networkhashps", getnetworkhashps().HandleRequest(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); obj.pushKV("chain", chainman.GetParams().GetChainTypeString()); diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index d367ec122d..d30d8533a5 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -16,6 +16,8 @@ from test_framework.blocktools import ( get_witness_script, NORMAL_GBT_REQUEST_PARAMS, TIME_GENESIS_BLOCK, + REGTEST_N_BITS, + nbits_str, ) from test_framework.messages import ( BLOCK_HEADER_SIZE, @@ -206,6 +208,7 @@ class MiningTest(BitcoinTestFramework): assert_equal(mining_info['chain'], self.chain) assert 'currentblocktx' not in mining_info assert 'currentblockweight' not in mining_info + assert_equal(mining_info['bits'], nbits_str(REGTEST_N_BITS)) assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index f02e6914ef..ae95beae2c 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2014-2022 The Bitcoin Core developers +# Copyright (c) 2014-present The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test RPCs related to blockchainstate. @@ -30,9 +30,11 @@ import textwrap from test_framework.blocktools import ( MAX_FUTURE_BLOCK_TIME, TIME_GENESIS_BLOCK, + REGTEST_N_BITS, create_block, create_coinbase, create_tx_with_script, + nbits_str, ) from test_framework.messages import ( CBlockHeader, @@ -412,7 +414,7 @@ class BlockchainTest(BitcoinTestFramework): assert_is_hash_string(header['hash']) assert_is_hash_string(header['previousblockhash']) assert_is_hash_string(header['merkleroot']) - assert_is_hash_string(header['bits'], length=None) + assert_equal(header['bits'], nbits_str(REGTEST_N_BITS)) assert isinstance(header['time'], int) assert_equal(header['mediantime'], TIME_RANGE_MTP) assert isinstance(header['nonce'], int) diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 705b8e8fe5..c92b1398c3 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -65,6 +65,10 @@ NORMAL_GBT_REQUEST_PARAMS = {"rules": ["segwit"]} VERSIONBITS_LAST_OLD_BLOCK_VERSION = 4 MIN_BLOCKS_TO_KEEP = 288 +REGTEST_N_BITS = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams" + +def nbits_str(nbits): + return f"{nbits:08x}" def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl=None, txlist=None): """Create a block (with regtest difficulty).""" @@ -77,7 +81,7 @@ def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl if tmpl and tmpl.get('bits') is not None: block.nBits = struct.unpack('>I', bytes.fromhex(tmpl['bits']))[0] else: - block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams + block.nBits = REGTEST_N_BITS if coinbase is None: coinbase = create_coinbase(height=tmpl['height']) block.vtx.append(coinbase) From ddd2ede17a0ba78275da093c977d6a15c96c5d32 Mon Sep 17 00:00:00 2001 From: tdb3 <106488469+tdb3@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:50:03 -0500 Subject: [PATCH 04/14] test: use REGTEST_N_BITS in feature_block --- test/functional/feature_block.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 384ca311c7..2dfa568c5b 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -12,6 +12,7 @@ from test_framework.blocktools import ( create_tx_with_script, get_legacy_sigopcount_block, MAX_BLOCK_SIGOPS, + REGTEST_N_BITS, ) from test_framework.messages import ( CBlock, @@ -590,7 +591,7 @@ class FullBlockTest(BitcoinTestFramework): b44 = CBlock() b44.nTime = self.tip.nTime + 1 b44.hashPrevBlock = self.tip.sha256 - b44.nBits = 0x207fffff + b44.nBits = REGTEST_N_BITS b44.vtx.append(coinbase) tx = self.create_and_sign_transaction(out[14], 1) b44.vtx.append(tx) @@ -606,7 +607,7 @@ class FullBlockTest(BitcoinTestFramework): b45 = CBlock() b45.nTime = self.tip.nTime + 1 b45.hashPrevBlock = self.tip.sha256 - b45.nBits = 0x207fffff + b45.nBits = REGTEST_N_BITS b45.vtx.append(non_coinbase) b45.hashMerkleRoot = b45.calc_merkle_root() b45.solve() @@ -620,7 +621,7 @@ class FullBlockTest(BitcoinTestFramework): b46 = CBlock() b46.nTime = b44.nTime + 1 b46.hashPrevBlock = b44.sha256 - b46.nBits = 0x207fffff + b46.nBits = REGTEST_N_BITS b46.vtx = [] b46.hashMerkleRoot = 0 b46.solve() From ef9e86039aee0a43ea4a64eaaaf759b370f6a51e Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Wed, 8 Jan 2025 13:03:08 +0100 Subject: [PATCH 05/14] rpc: gettarget --- src/rpc/blockchain.cpp | 21 ++++++++++++++++++++ src/rpc/util.cpp | 8 ++++++++ src/rpc/util.h | 10 ++++++++++ src/test/fuzz/rpc.cpp | 1 + test/functional/rpc_blockchain.py | 9 +++++++++ test/functional/test_framework/blocktools.py | 6 ++++++ 6 files changed, 55 insertions(+) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 3caf4bf4eb..6756900cb2 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -447,6 +447,26 @@ static RPCHelpMan getdifficulty() }; } +static RPCHelpMan gettarget() +{ + return RPCHelpMan{"gettarget", + "\nReturns the proof-of-work target.\n", + {}, + 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()))}; + return GetTarget(tip, chainman.GetParams().GetConsensus().powLimit).GetHex(); +}, + }; +} + static RPCHelpMan getblockfrompeer() { return RPCHelpMan{ @@ -3382,6 +3402,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t) {"blockchain", &getblockheader}, {"blockchain", &getchaintips}, {"blockchain", &getdifficulty}, + {"blockchain", &gettarget}, {"blockchain", &getdeploymentinfo}, {"blockchain", &gettxout}, {"blockchain", &gettxoutsetinfo}, diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index b1fbc25641..2941dda8c0 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