Skip to content
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/consensus/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValidationIssue> 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.
*/
Expand Down
12 changes: 8 additions & 4 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CAmount> m_fee_filter_received{0};
Expand Down Expand Up @@ -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<bool>& interruptMsgProc)
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex, NetEventsInterface::g_msgproc_mutex)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/net_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ struct CNodeStateStats {
std::chrono::microseconds m_ping_wait;
std::vector<int> 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;
Expand Down
78 changes: 78 additions & 0 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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", "\"<blockhex>\"") +
HelpExampleRpc("diagnoseblock", "\"<blockhex>\"")
},
[&](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{
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/rpc/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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));
Expand Down
Loading
Loading