Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpc: checkblock #75

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/pow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,19 @@ bool CheckProofOfWorkImpl(uint256 hash, unsigned int nBits, const Consensus::Par

return true;
}

bool CheckWeakProofOfWork(uint256 hash, unsigned int nBits, unsigned int multiplier, const Consensus::Params& params)
{
bool fNegative;
bool fOverflow;
arith_uint256 bnTarget;
bnTarget.SetCompact(nBits, &fNegative, &fOverflow);
bnTarget = bnTarget * multiplier;
// Check range
if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit) * multiplier)
return false;
// Check proof of work matches claimed amount
if (UintToArith256(hash) > bnTarget)
return false;
return true;
}
2 changes: 2 additions & 0 deletions src/pow.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&);
bool CheckProofOfWorkImpl(uint256 hash, unsigned int nBits, const Consensus::Params&);
/** Check whether a block hash satisfies the (weaker) proof-of-work requirement specified by nBits and multiplier */
bool CheckWeakProofOfWork(uint256 hash, unsigned int nBits, unsigned int multiplier, const Consensus::Params& params);

/**
* Return false if the proof-of-work requirement specified by new_nbits at a
Expand Down
3 changes: 3 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "submitpackage", 0, "package" },
{ "submitpackage", 1, "maxfeerate" },
{ "submitpackage", 2, "maxburnamount" },
{ "checkblock", 1, "options" },
{ "checkblock", 1, "check_pow"},
{ "checkblock", 1, "multiplier"},
{ "combinerawtransaction", 0, "txs" },
{ "fundrawtransaction", 1, "options" },
{ "fundrawtransaction", 1, "add_inputs"},
Expand Down
67 changes: 67 additions & 0 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,72 @@ static RPCHelpMan submitheader()
};
}

static RPCHelpMan checkblock()
{
return RPCHelpMan{"checkblock",
"\nChecks a new block without submitting to the network.\n",
{
{"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded block data to submit"},
{"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 always checked."},
{"multiplier", RPCArg::Type::NUM, RPCArg::Default{1}, "Check against a higher target. The nBits value is not used, but still needs to match the consensus value."},
},
}
},
{
RPCResult{"If the block passed all checks", RPCResult::Type::NONE, "", ""},
RPCResult{"Otherwise", RPCResult::Type::STR, "", "According to BIP22"},
},
RPCExamples{
HelpExampleCli("checkblock", "\"mydata\"")
+ HelpExampleRpc("checkblock", "\"mydata\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>();
CBlock& block = *blockptr;
if (!DecodeHexBlk(block, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
}

ChainstateManager& chainman = EnsureAnyChainman(request.context);

unsigned int multiplier{1};
bool check_pow{true};

if (!request.params[1].isNull()) {
UniValue options = request.params[1];
// RPCTypeCheckObj(options,
// {
// {"multiplier", UniValueType(UniValue::VNUM)},
// }
// );

if (options.exists("multiplier")) {
multiplier = options["multiplier"].getInt<uint32_t>();
}
if (options.exists("check_pow")) {
check_pow = options["check_pow"].get_bool();
}
}

// TODO:
// - check that our tip is below BIP34_IMPLIES_BIP30_LIMIT - 1
// - check we're above BIP34 / BIP68 height

auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
CHECK_NONFATAL(chainman.m_options.signals)->RegisterSharedValidationInterface(sc);
// TODO: add checkNewBlock() method to chainman interface
chainman.CheckNewBlock(blockptr, check_pow, /*multiplier=*/multiplier);
CHECK_NONFATAL(chainman.m_options.signals)->UnregisterSharedValidationInterface(sc);
// TODO uncomment when that works
// CHECK_NONFATAL(!sc->found);
return BIP22ValidationResult(sc->state);
},
};
}

void RegisterMiningRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
Expand All @@ -1099,6 +1165,7 @@ void RegisterMiningRPCCommands(CRPCTable& t)
{"mining", &getblocktemplate},
{"mining", &submitblock},
{"mining", &submitheader},
{"mining", &checkblock},

{"hidden", &generatetoaddress},
{"hidden", &generatetodescriptor},
Expand Down
199 changes: 199 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4582,6 +4582,205 @@ bool ChainstateManager::AcceptBlock(const std::shared_ptr<const CBlock>& pblock,
return true;
}

void ChainstateManager::CheckNewBlock(const std::shared_ptr<const CBlock>& block, const bool check_pow, const unsigned int multiplier)
{
AssertLockNotHeld(cs_main);

{
// TODO: cleanup the ret mess
bool ret{true};
BlockValidationState state;

// CheckBlock() does not support multi-threaded block validation because CBlock::fChecked can cause data race.
// Therefore, the following critical section must include the CheckBlock() call as well.
LOCK(cs_main);

CBlockIndex* tip = ActiveTip();

if (block->hashPrevBlock != *tip->phashBlock) {
state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "inconclusive-not-best-prevblk", "block not on tip");
ret = false;
}

// Check the actual proof-of-work. The nBits value is checked by
// ContextualCheckBlock below later as part of AcceptBlock.
const CChainParams& params{GetParams()};
if (ret && check_pow) {
ret = CheckWeakProofOfWork(block->GetHash(), block->nBits, multiplier, params.GetConsensus());
if (!ret) {
if (multiplier == 1) {
state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "high-hash", "proof of work failed");
} else {
state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "high-weak-hash", "weak proof of work failed");
}
}
}
if (ret) {
ret = CheckBlock(*block, state, GetConsensus(), /*fCheckPow=*/false);
}
if (ret) {
/**
* At this point ProcessNewBlock would call AcceptBlock(), but we
* don't want to store the block or its header. Run individual checks
* instead.
*
* - don't run AcceptBlockHeader(): it doesn't check anything we care about
* - we already ran CheckBlock()
* - do run ContextualCheckBlockHeader()
* - do run ContextualCheckBlock()
*/

if (!ContextualCheckBlockHeader(*block, state, ActiveChainstate().m_blockman, ActiveChainstate().m_chainman, tip)) {
ret = false;
}

if (ret && !ContextualCheckBlock(*block, state, *this, tip)) {
ret = false;
}

if (ret) {
// Run a subset of ConnectBlock() to check the transactions, without updating
// the UTXO set. Does update validation caches.

// Skip BIP34-implies-BIP30 check
// TODO: only on mainnet (disallow testnet3 altogether??)
Assert(tip->nHeight < 1983702 - 1); // BIP34_IMPLIES_BIP30_LIMIT
// TODO: assert height is above BIP34 and BIP68 activation

CBlockIndex index{*block};
uint256 block_hash(block->GetHash());
index.pprev = tip;
index.nHeight = tip->nHeight + 1;
index.phashBlock = &block_hash;

const bool parallel_script_checks{ActiveChainstate().m_chainman.GetCheckQueue().HasThreads()};

int nLockTimeFlags = LOCKTIME_VERIFY_SEQUENCE;

// Get the script flags for this block
unsigned int flags{GetBlockScriptFlags(index, ActiveChainstate().m_chainman)};

// We don't want to update the actual chainstate, so create
// a cache on top of it.
CCoinsViewCache tipView(&ActiveChainstate().CoinsTip());
CCoinsView blockCoins;
CCoinsViewCache view(&blockCoins);
view.SetBackend(tipView);

// verify that the view's current state corresponds to the previous block
Assume(index.pprev->GetBlockHash() == view.GetBestBlock());

// Precomputed transaction data pointers must not be invalidated
// until after `control` has run the script checks (potentially
// in multiple threads). Preallocate the vector size so a new allocation
// doesn't invalidate pointers into the vector, and keep txsdata in scope
// for as long as `control`.
CCheckQueueControl<CScriptCheck> control(parallel_script_checks ? &ActiveChainstate().m_chainman.GetCheckQueue() : nullptr);
std::vector<PrecomputedTransactionData> txsdata(block->vtx.size());

std::vector<int> prevheights;
CAmount nFees = 0;
int64_t nSigOpsCost = 0;
for (unsigned int i = 0; i < block->vtx.size(); i++)
{
if (!state.IsValid()) break;
const CTransaction &tx = *(block->vtx[i]);

if (!tx.IsCoinBase())
{
CAmount txfee = 0;
TxValidationState tx_state;
if (!Consensus::CheckTxInputs(tx, tx_state, view, index.nHeight, txfee)) {
// Any transaction validation failure is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(),
tx_state.GetDebugMessage() + " in transaction " + tx.GetHash().ToString());
break;
}
nFees += txfee;
if (!MoneyRange(nFees)) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-accumulated-fee-outofrange",
"accumulated fee in the block out of range");
break;
}

// Check that transaction is BIP68 final
// BIP68 lock checks (as opposed to nLockTime checks) must
// be in ConnectBlock because they require the UTXO set
prevheights.resize(tx.vin.size());
for (size_t j = 0; j < tx.vin.size(); j++) {
prevheights[j] = view.AccessCoin(tx.vin[j].prevout).nHeight;
}

if (!SequenceLocks(tx, nLockTimeFlags, prevheights, index)) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-nonfinal",
"contains a non-BIP68-final transaction " + tx.GetHash().ToString());
break;
}
}

// GetTransactionSigOpCost counts 3 types of sigops:
// * legacy (always)
// * p2sh (when P2SH enabled in flags and excludes coinbase)
// * witness (when witness enabled in flags and excludes coinbase)
nSigOpsCost += GetTransactionSigOpCost(tx, view, flags);
if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "too many sigops");
break;
}

if (!tx.IsCoinBase())
{
std::vector<CScriptCheck> vChecks;
TxValidationState tx_state;
// cacheSigStore and cacheFullScriptStore are set to true
// because the cache needs to be retained for when this
// block is eventually submitted.
if (!CheckInputScripts(tx, tx_state, view, flags, /*cacheSigStore=*/true, /*cacheFullScriptStore=*/true, txsdata[i], ActiveChainstate().m_chainman.m_validation_cache, parallel_script_checks ? &vChecks : nullptr)) {
// Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(), tx_state.GetDebugMessage());
break;
}
control.Add(std::move(vChecks));
}

CTxUndo undoDummy;
UpdateCoins(tx, view, undoDummy, index.nHeight);

}

CAmount blockReward = nFees + GetBlockSubsidy(index.nHeight, params.GetConsensus());
if (block->vtx[0]->GetValueOut() > blockReward && state.IsValid()) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount",
strprintf("coinbase pays too much (actual=%d vs limit=%d)", block->vtx[0]->GetValueOut(), blockReward));
}

auto parallel_result = control.Complete();
if (parallel_result.has_value() && state.IsValid()) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(parallel_result->first)), parallel_result->second);
}

if (!state.IsValid()) {
ret = false;
}

// TODO: (optionally) loop through the block again and add useful transactions to the mempool?

}

}
if (!ret) {
if (m_options.signals) {
m_options.signals->BlockChecked(*block, state);
}
return;
}
}

return;
}

bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block)
{
AssertLockNotHeld(cs_main);
Expand Down
29 changes: 29 additions & 0 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,35 @@ class ChainstateManager
FlatFilePos* dbp = nullptr,
std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent = nullptr);

/**
* Verify a block. Optionally skips proof-of-work check or use nBits multiplier.
*
* TODO: avoid this convoluted mess?
*
* If you want to *possibly* get feedback on whether block is valid, you must
* install a CValidationInterface (see validationinterface.h) - this will have
* its BlockChecked method called whenever *any* block completes validation.
*
* Note that we guarantee that either the proof-of-work is valid on block, or
* (and possibly also) BlockChecked will have been called.
*
* May not be called in a validationinterface callback.
*
* TODO:
* - option to add good transactions to the mempool
* - option to jail non-standard transactions:
* https://delvingbitcoin.org/t/second-look-at-weak-blocks/805
* - optionally skip PoW check
* - optionally provide higher target
*
* @param[in] block The block we want to process.
* @param[in] check_pow Perform proof-of-work check, nbits in the header
* is always checked.
* @param[in] multiplier nBits multiplier, does not apply to the nbits
* header value check.
*/
void CheckNewBlock(const std::shared_ptr<const CBlock>& block, const bool check_pow = true, const unsigned int multiplier = 1) LOCKS_EXCLUDED(cs_main);

/**
* Process an incoming block. This only returns after the best known valid
* block is made active. Note that it does not, however, guarantee that the
Expand Down
Loading
Loading