From c4cc9e3e9df2733260942e0513dd8478d2a104da 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 bbcf39b5932..686b177fe32 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 2b28ade273c..ceba55d36a2 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 ba7b9f3d7bf5a1ad395262b080e832f5c9958e4d 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 889c00c7832..89fdd855a45 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 7ddbed4f9fc0c90bfed244a71194740a4a1fa1be 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 823d2303c81..3caf4bf4ebe 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 7e5936fddfc..c8bd8d3fd2f 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 d367ec122dd..d30d8533a5c 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 f02e6914ef5..ae95beae2c1 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 705b8e8fe5e..c92b1398c39 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 d20d96fa41ce706ccc480b4f3143438ce0720348 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 384ca311c74..2dfa568c5b6 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 341f93251677fee66c822f414b75499e8b3b31f6 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Wed, 8 Jan 2025 13:03:08 +0100 Subject: [PATCH 05/14] rpc: add GetTarget helper --- src/rpc/util.cpp | 8 ++++++++ src/rpc/util.h | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index b1fbc256414..2941dda8c0a 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