diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 2b2dd4bb6865..8d9f5b4ea805 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -17,6 +17,22 @@ static constexpr int NO_WITNESS_COMMITMENT{-1}; /** Minimum size of a witness commitment structure. Defined in BIP 141. **/ static constexpr size_t MINIMUM_WITNESS_COMMITMENT{38}; +struct ValidationIssue { + enum class Level { ERROR, WARNING }; + std::string check; // e.g. "pow", "merkle", "witness_commitment", "tx[12].inputs" + std::string message; // human detail + Level level{Level::ERROR}; +}; + +struct BlockDiagnostics { + uint256 block_hash{}; + std::vector issues; + bool ok() const { return issues.empty(); } + void add(const std::string& check, const std::string& msg, ValidationIssue::Level lvl = ValidationIssue::Level::ERROR) { + issues.push_back(ValidationIssue{check, msg, lvl}); + } +}; + /** A "reason" why a transaction was invalid, suitable for determining whether the * provider of the transaction should be banned/ignored/disconnected/etc. */ diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 9287a67a04ea..5a157b2b65f0 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -312,7 +312,7 @@ struct Peer { std::chrono::microseconds m_next_inv_send_time GUARDED_BY(m_tx_inventory_mutex){0}; /** The mempool sequence num at which we sent the last `inv` message to this peer. * Can relay txs with lower sequence numbers than this (see CTxMempool::info_for_relay). */ - uint64_t m_last_inv_sequence GUARDED_BY(NetEventsInterface::g_msgproc_mutex){1}; + uint64_t m_last_inv_sequence GUARDED_BY(m_tx_inventory_mutex){1}; /** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */ std::atomic m_fee_filter_received{0}; @@ -942,7 +942,7 @@ class PeerManagerImpl final : public PeerManager /** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */ CTransactionRef FindTxForGetData(const Peer::TxRelay& tx_relay, const GenTxid& gtxid) - EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, NetEventsInterface::g_msgproc_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, !tx_relay.m_tx_inventory_mutex); void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex, NetEventsInterface::g_msgproc_mutex) @@ -1728,9 +1728,13 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { stats.m_relay_txs = WITH_LOCK(tx_relay->m_bloom_filter_mutex, return tx_relay->m_relay_txs); stats.m_fee_filter_received = tx_relay->m_fee_filter_received.load(); + LOCK(tx_relay->m_tx_inventory_mutex); + stats.m_last_inv_seq = tx_relay->m_last_inv_sequence; + stats.m_inv_to_send = tx_relay->m_tx_inventory_to_send.size(); } else { stats.m_relay_txs = false; stats.m_fee_filter_received = 0; + stats.m_inv_to_send = 0; } stats.m_ping_wait = ping_wait; @@ -2362,8 +2366,8 @@ CTransactionRef PeerManagerImpl::FindTxForGetData(const Peer::TxRelay& tx_relay, { // If a tx was in the mempool prior to the last INV for this peer, permit the request. auto txinfo{std::visit( - [&](const auto& id) EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex) { - return m_mempool.info_for_relay(id, tx_relay.m_last_inv_sequence); + [&](const auto& id) { + return m_mempool.info_for_relay(id, WITH_LOCK(tx_relay.m_tx_inventory_mutex, return tx_relay.m_last_inv_sequence)); }, gtxid)}; if (txinfo.tx) { diff --git a/src/net_processing.h b/src/net_processing.h index 8c140d98ad66..6eb4a5e16a2c 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -54,6 +54,8 @@ struct CNodeStateStats { std::chrono::microseconds m_ping_wait; std::vector vHeightInFlight; bool m_relay_txs; + int m_inv_to_send = 0; + uint64_t m_last_inv_seq{0}; CAmount m_fee_filter_received; uint64_t m_addr_processed = 0; uint64_t m_addr_rate_limited = 0; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 850cdb70c4e3..e8bb9e0f4f20 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1094,6 +1094,83 @@ static RPCHelpMan submitblock() }; } +static RPCHelpMan diagnoseblock() +{ + return RPCHelpMan{ + "diagnoseblock", + "Decode a raw block and run comprehensive diagnostics without early exit.\n" + "Returns all detected issues (PoW/merkle/commitment/tx inputs/scripts/context).\n", + { + {"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex-encoded block"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "ok", "true if no issues found"}, + {RPCResult::Type::STR_HEX, "blockhash", "hash of the provided block"}, + {RPCResult::Type::ARR, "issues", "list of problems found", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "level", "\"error\" or \"warning\""}, + {RPCResult::Type::STR, "check", "which check failed (e.g. pow, merkle, tx[i].inputs, ... )"}, + {RPCResult::Type::STR, "message", "human-readable detail"}, + } + }, + } + }, + } + }, + RPCExamples{ + HelpExampleCli("diagnoseblock", "\"\"") + + HelpExampleRpc("diagnoseblock", "\"\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + // Parse block + CBlock block; + if (!DecodeHexBlk(block, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); + } + + ChainstateManager& chainman = EnsureAnyChainman(request.context); + + // If we know the parent, fill in uncommitted structures so witness commitment checks reflect reality. + { + LOCK(cs_main); + if (const CBlockIndex* pindexPrev = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)) { + chainman.UpdateUncommittedBlockStructures(block, pindexPrev); + } + } + + // Run diagnostics + BlockDiagnostics diag; + { + LOCK(cs_main); + Chainstate& active = chainman.ActiveChainstate(); + DiagnoseBlock(block, active, diag); + } + + // Build result + UniValue out(UniValue::VOBJ); + out.pushKV("ok", diag.ok()); + out.pushKV("blockhash", diag.block_hash.GetHex()); + + UniValue issues(UniValue::VARR); + for (const auto& iss : diag.issues) { + UniValue it(UniValue::VOBJ); + it.pushKV("level", iss.level == ValidationIssue::Level::ERROR ? "error" : "warning"); + it.pushKV("check", iss.check); + it.pushKV("message", iss.message); + issues.push_back(std::move(it)); + } + out.pushKV("issues", std::move(issues)); + return out; + } + }; +} + + static RPCHelpMan submitheader() { return RPCHelpMan{ @@ -1149,6 +1226,7 @@ void RegisterMiningRPCCommands(CRPCTable& t) {"hidden", &generatetodescriptor}, {"hidden", &generateblock}, {"hidden", &generate}, + {"mining", &diagnoseblock}, }; for (const auto& c : commands) { t.appendCommand(c.name, &c); diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index fbb70d72161d..ba74283d237c 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -142,6 +142,8 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, {RPCResult::Type::BOOL, "relaytxes", "Whether we relay transactions to this peer"}, + {RPCResult::Type::NUM, "last_inv_sequence", "Mempool sequence number of this peer's last INV"}, + {RPCResult::Type::NUM, "inv_to_send", "How many txs we have queued to announce to this peer"}, {RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"}, {RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"}, {RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"}, @@ -238,6 +240,8 @@ static RPCHelpMan getpeerinfo() obj.pushKV("services", strprintf("%016x", services)); obj.pushKV("servicesnames", GetServicesNames(services)); obj.pushKV("relaytxes", statestats.m_relay_txs); + obj.pushKV("last_inv_sequence", statestats.m_last_inv_seq); + obj.pushKV("inv_to_send", statestats.m_inv_to_send); obj.pushKV("lastsend", count_seconds(stats.m_last_send)); obj.pushKV("lastrecv", count_seconds(stats.m_last_recv)); obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time)); diff --git a/src/validation.cpp b/src/validation.cpp index 8fcc719a6848..f48227a90e8e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -6,6 +6,11 @@ #include // IWYU pragma: keep #include +#include + +#include +#include