Skip to content

Commit

Permalink
Add target arg to checkblock RPC and IPC
Browse files Browse the repository at this point in the history
  • Loading branch information
Sjors committed Jan 29, 2025
1 parent 18fda6c commit 6bffc64
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 13 deletions.
6 changes: 4 additions & 2 deletions src/interfaces/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/ipc/capnp/mining.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
2 changes: 1 addition & 1 deletion src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
5 changes: 5 additions & 0 deletions src/node/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 7 additions & 1 deletion src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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."},
},
}
},
Expand All @@ -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};
},
};
Expand Down
24 changes: 21 additions & 3 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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);
}

/**
Expand Down
7 changes: 5 additions & 2 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions test/functional/rpc_checkblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 6bffc64

Please sign in to comment.