diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index e3d651bdedf65..c4998fdfeaf95 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -99,11 +99,13 @@ class Mining * @param[in] block the block to check * @param[in] options verification options: the proof-of-work check can be * skipped in order to verify a template generated by - * external software. + * external software. A custom higher target can be + * provided to verify weak blocks. * @param[out] reason failure reason (BIP22) * @returns whether the block is valid * - * For signets the challenge verification is skipped when check_pow is false. + * For signets the challenge verification is skipped when check_pow is false or + * a custom target is provided (via options). */ virtual bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason) = 0; diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp index 59eeb2b6de631..d7807752f0ff3 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -43,4 +43,5 @@ struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") { struct BlockCheckOptions $Proxy.wrap("node::BlockCheckOptions") { checkMerkleRoot @0 :Bool $Proxy.name("check_merkle_root"); checkPow @1 :Bool $Proxy.name("check_pow"); + target @2 :Data $Proxy.name("target"); } diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 0613568fe1731..9d0f37d9d0c6c 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -990,7 +990,7 @@ class MinerImpl : public Mining bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason) override { - return chainman().TestBlockValidity(block, reason, /*check_pow=*/options.check_pow, /*=check_merkle_root=*/options.check_merkle_root); + return chainman().TestBlockValidity(block, reason, /*check_pow=*/options.check_pow, /*=check_merkle_root=*/options.check_merkle_root, /*target=*/options.target); } NodeContext* context() override { return &m_node; } diff --git a/src/node/types.h b/src/node/types.h index 64238630e5cf7..f3363cc840856 100644 --- a/src/node/types.h +++ b/src/node/types.h @@ -73,6 +73,11 @@ struct BlockCheckOptions { * Set false to omit the proof-of-work check */ bool check_pow{true}; + + /** + * Optionally higher target for weak blocks. + */ + uint256 target{uint256::ZERO}; }; } // namespace node diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 01fbdf12202ae..560a50872b193 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1123,6 +1123,7 @@ static RPCHelpMan checkblock() {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", { {"check_pow", RPCArg::Type::BOOL, RPCArg::Default{true}, "verify the proof-of-work. The nBits value is still checked."}, + {"target", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"consensus target"}, "Check against a higher target. The nBits value is checked against the original target."}, }, } }, @@ -1145,21 +1146,26 @@ static RPCHelpMan checkblock() Mining& miner = EnsureMining(node); bool check_pow{true}; + uint256 target{uint256::ZERO}; if (!request.params[1].isNull()) { UniValue options = request.params[1]; RPCTypeCheckObj(options, { {"check_pow", UniValueType(UniValue::VBOOL)}, + {"target", UniValueType(UniValue::VSTR)}, }, /*fAllowNull=*/true, /*fStrict=*/true ); if (options.exists("check_pow")) { check_pow = options["check_pow"].get_bool(); } + if (options.exists("target")) { + target = ParseHashV(options["target"], "target"); + } } std::string reason; - bool res = miner.checkBlock(block, {.check_pow = check_pow}, reason); + bool res = miner.checkBlock(block, {.check_pow = check_pow, .target = target}, reason); return res ? UniValue::VNULL : UniValue{reason}; }, }; diff --git a/src/validation.cpp b/src/validation.cpp index 8a7cc89aa7b08..ed01a29acf7a9 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4653,7 +4653,7 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& return result; } -bool ChainstateManager::TestBlockValidity(const CBlock& block, std::string& reason, const bool check_pow, const bool check_merkle_root) +bool ChainstateManager::TestBlockValidity(const CBlock& block, std::string& reason, const bool check_pow, const bool check_merkle_root, const uint256 target) { ChainstateManager& chainman{ActiveChainstate().m_chainman}; @@ -4670,16 +4670,34 @@ bool ChainstateManager::TestBlockValidity(const CBlock& block, std::string& reas return false; } + bool custom_target{false}; + + if (check_pow) { + // The default target value of 0, as well as any target lower than consensus, + // is rounded up. + const arith_uint256 consensus_target{*Assert(DeriveTarget(block.nBits, GetParams().GetConsensus().powLimit))}; + const arith_uint256 max_target = std::max(UintToArith256(target), consensus_target); + + // Check the actual proof-of-work. The nBits value is checked by + // ContextualCheckBlock below. When target equals consensus_target, + // have CheckBlock() below do this instead. + custom_target = max_target != consensus_target; + if (custom_target && UintToArith256(block.GetHash()) > max_target) { + reason = "high-hash"; + return false; + } + } + // Sanity check Assert(!block.fChecked); // For signets CheckBlock() verifies the challenge iif fCheckPow is set. - if (!CheckBlock(block, state, GetConsensus(), /*fCheckPow=*/check_pow, /*fCheckMerkleRoot=*/check_merkle_root)) { + if (!CheckBlock(block, state, GetConsensus(), /*fCheckPow=*/check_pow && !custom_target, /*fCheckMerkleRoot=*/check_merkle_root)) { reason = state.GetRejectReason(); Assume(!reason.empty()); return false; } else { // Sanity check - Assume(check_pow || !block.fChecked); + Assume((check_pow && !custom_target) || !block.fChecked); } /** diff --git a/src/validation.h b/src/validation.h index a146a12f1d124..38f256a78872f 100644 --- a/src/validation.h +++ b/src/validation.h @@ -1182,10 +1182,13 @@ class ChainstateManager * @param[in] check_pow perform proof-of-work check, nBits in the header * is always checked * @param[in] check_merkle_root check the merkle root + * @param[in] target apply a higher work target (default 0, rounded up + * to nBits target) * - * For signets the challenge verification is skipped when check_pow is false. + * For signets the challenge verification is skipped when check_pow is false + * or a higher target is provided. */ - bool TestBlockValidity(const CBlock& block, std::string& reason, const bool check_pow = true, const bool check_merkle_root = true); + bool TestBlockValidity(const CBlock& block, std::string& reason, const bool check_pow = true, const bool check_merkle_root = true, const uint256 target = uint256::ZERO); /** * Process an incoming block. This only returns after the best known valid diff --git a/test/functional/rpc_checkblock.py b/test/functional/rpc_checkblock.py index 0c1767acb856d..a7b780580029f 100755 --- a/test/functional/rpc_checkblock.py +++ b/test/functional/rpc_checkblock.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test checkblock RPC -Generate several blocks and test them against the checkblock RPC. +Generate several (weak) blocks and test them against the checkblock RPC. """ from concurrent.futures import ThreadPoolExecutor @@ -60,14 +60,15 @@ def run_test(self): assert_equal(node.checkblock(bad_block_2.serialize().hex()), "bad-diffbits") - self.log.info("Generate a block") + self.log.info("Generate a weak block") target = uint256_from_compact(block_2.nBits) # Ensure that it doesn't meet the target by coincidence - while block_2.sha256 <= target: + # Leave some room to test a weak block target + while block_2.sha256 <= target + 10: block_2.nNonce += 1 block_2.rehash() - self.log.info("A block without PoW won't pass the check by default") + self.log.info("A weak block won't pass the check by default") assert_equal(node.checkblock(block_2.serialize().hex()), "high-hash") self.log.info("Skip the PoW check")