Skip to content

Commit f6bc91d

Browse files
committed
Implement block acceptance verification system
Parent blocks are now verified after submission via async checks at +10s, +30s, and +120s. The verification callback checks if the block hash appears in the header chain (embedded mode) or queries the daemon via RPC. Status tracking: - FoundBlock now has status (pending/confirmed/orphaned/stale) and check_count fields - /recent_blocks REST endpoint includes "status" and "checks" fields - Verification results are logged: "Block CONFIRMED" or "Block ORPHANED" Architecture: - set_block_verify_fn() accepts a callback: returns >0=confirmed, <0=orphaned, 0=pending - schedule_block_verification() fires timers at +10s, +30s, +120s - Works with both embedded (header chain lookup) and RPC (getblock) backends — ready for future DOGE embedded P2P node For merged blocks: submitauxblock RPC already returns success/failure. Full async verification (delayed getblock) will be added when the embedded DOGE P2P node is implemented.
1 parent 7f70bd3 commit f6bc91d

3 files changed

Lines changed: 117 additions & 4 deletions

File tree

src/c2pool/c2pool_refactored.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,19 @@ int main(int argc, char* argv[]) {
10991099
// Wire embedded node + header-sync callback (now that web_server is alive)
11001100
web_server.set_embedded_node(embedded_node.get());
11011101

1102+
// Wire block verification: check header chain for found blocks
1103+
auto* mi = web_server.get_mining_interface();
1104+
mi->set_io_context(&ioc);
1105+
mi->set_block_verify_fn(
1106+
[chain = embedded_chain.get()](const std::string& hash_hex) -> int {
1107+
uint256 h;
1108+
h.SetHex(hash_hex);
1109+
auto entry = chain->get_header(h);
1110+
if (entry.has_value())
1111+
return 1; // confirmed — in our chain
1112+
return 0; // unknown — still pending
1113+
});
1114+
11021115
// Header validation thread pool (1 thread) — keeps scrypt off io_context.
11031116
// Shared_ptr so it stays alive for the lifetime of the callbacks.
11041117
auto hdr_pool = std::make_shared<boost::asio::thread_pool>(1);
@@ -1338,6 +1351,9 @@ int main(int argc, char* argv[]) {
13381351
}
13391352
}
13401353
web_server.get_mining_interface()->record_found_block(height, block_hash);
1354+
// Schedule async verification at +10s, +30s, +120s
1355+
if (stale_info == 0)
1356+
web_server.get_mining_interface()->schedule_block_verification(block_hash.GetHex());
13411357

13421358
const char* stale_str = (stale_info == 253) ? " [ORPHAN — stale prev]"
13431359
: (stale_info == 254) ? " [DOA — daemon rejected]"

src/core/web_server.cpp

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,10 +2093,18 @@ nlohmann::json MiningInterface::rest_fee()
20932093

20942094
nlohmann::json MiningInterface::rest_recent_blocks()
20952095
{
2096+
static const char* status_str[] = {"pending", "confirmed", "orphaned", "stale"};
20962097
nlohmann::json arr = nlohmann::json::array();
20972098
std::lock_guard<std::mutex> lock(m_blocks_mutex);
2098-
for (const auto& b : m_found_blocks)
2099-
arr.push_back({{"height", b.height}, {"hash", b.hash}, {"ts", b.ts}});
2099+
for (const auto& b : m_found_blocks) {
2100+
arr.push_back({
2101+
{"height", b.height},
2102+
{"hash", b.hash},
2103+
{"ts", b.ts},
2104+
{"status", status_str[static_cast<int>(b.status)]},
2105+
{"checks", b.check_count}
2106+
});
2107+
}
21002108
return arr;
21012109
}
21022110

@@ -2362,11 +2370,76 @@ void MiningInterface::record_found_block(uint64_t height, const uint256& hash, u
23622370
{
23632371
if (ts == 0) ts = static_cast<uint64_t>(std::time(nullptr));
23642372
std::lock_guard<std::mutex> lock(m_blocks_mutex);
2365-
m_found_blocks.insert(m_found_blocks.begin(), FoundBlock{height, hash.GetHex(), ts});
2373+
m_found_blocks.insert(m_found_blocks.begin(),
2374+
FoundBlock{height, hash.GetHex(), ts, BlockStatus::pending, 0});
23662375
if (m_found_blocks.size() > 100)
23672376
m_found_blocks.resize(100);
23682377
}
23692378

2379+
void MiningInterface::set_block_verify_fn(block_verify_fn_t fn) { m_block_verify_fn = std::move(fn); }
2380+
2381+
void MiningInterface::verify_found_block(size_t index)
2382+
{
2383+
std::string hash;
2384+
{
2385+
std::lock_guard<std::mutex> lock(m_blocks_mutex);
2386+
if (index >= m_found_blocks.size()) return;
2387+
auto& blk = m_found_blocks[index];
2388+
if (blk.status != BlockStatus::pending) return;
2389+
blk.check_count++;
2390+
hash = blk.hash;
2391+
}
2392+
2393+
if (!m_block_verify_fn) return;
2394+
2395+
// Callback returns: >0 confirmed, <0 orphaned, 0 unknown/pending
2396+
int result = 0;
2397+
try { result = m_block_verify_fn(hash); } catch (...) {}
2398+
2399+
std::lock_guard<std::mutex> lock(m_blocks_mutex);
2400+
if (index >= m_found_blocks.size()) return;
2401+
auto& blk = m_found_blocks[index];
2402+
if (blk.status != BlockStatus::pending) return;
2403+
2404+
if (result > 0) {
2405+
blk.status = BlockStatus::confirmed;
2406+
LOG_INFO << "Block CONFIRMED: height=" << blk.height
2407+
<< " hash=" << blk.hash.substr(0, 16) << "..."
2408+
<< " (check #" << (int)blk.check_count << ")";
2409+
} else if (result < 0 || blk.check_count >= 3) {
2410+
blk.status = BlockStatus::orphaned;
2411+
LOG_WARNING << "Block ORPHANED: height=" << blk.height
2412+
<< " hash=" << blk.hash.substr(0, 16) << "..."
2413+
<< " (check #" << (int)blk.check_count << ")";
2414+
}
2415+
}
2416+
2417+
void MiningInterface::schedule_block_verification(const std::string& block_hash)
2418+
{
2419+
// Find the block index by hash
2420+
size_t idx = SIZE_MAX;
2421+
{
2422+
std::lock_guard<std::mutex> lock(m_blocks_mutex);
2423+
for (size_t i = 0; i < m_found_blocks.size(); ++i) {
2424+
if (m_found_blocks[i].hash == block_hash) {
2425+
idx = i;
2426+
break;
2427+
}
2428+
}
2429+
}
2430+
if (idx == SIZE_MAX) return;
2431+
2432+
// Schedule checks at +10s, +30s, +120s after block submission
2433+
auto& ioc = *m_context;
2434+
for (int delay : {10, 30, 120}) {
2435+
auto timer = std::make_shared<boost::asio::steady_timer>(
2436+
ioc, std::chrono::seconds(delay));
2437+
timer->async_wait([this, idx, timer](boost::system::error_code ec) {
2438+
if (!ec) verify_found_block(idx);
2439+
});
2440+
}
2441+
}
2442+
23702443
// ──────────── p2pool legacy compatibility REST endpoints ─────────────────
23712444
// These endpoints reproduce the exact JSON shape that the original p2pool
23722445
// dashboard (Forrest Voight) and jtoomim fork expect, enabling third-party

src/core/web_server.hpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,13 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server
428428

429429
WorkSnapshot get_work_snapshot() const;
430430

431+
// Block acceptance verification: schedule async checks at +10s, +30s, +120s.
432+
// The verify callback returns: >0=confirmed, <0=orphaned, 0=pending.
433+
using block_verify_fn_t = std::function<int(const std::string& hash)>;
434+
void set_block_verify_fn(block_verify_fn_t fn);
435+
void set_io_context(boost::asio::io_context* ctx) { m_context = ctx; }
436+
void schedule_block_verification(const std::string& block_hash);
437+
431438
// Callback fired whenever a block submission is attempted.
432439
// Arguments: header hex (first 80 bytes), stale_info (none=accepted, orphan=stale prev, doa=daemon rejected).
433440
void set_on_block_submitted(std::function<void(const std::string& header_hex, int stale_info)> fn);
@@ -499,6 +506,8 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server
499506
ltc::interfaces::Node* m_coin_node = nullptr;
500507
// Phase 4: embedded coin node (preferred over RPC when set)
501508
ltc::coin::CoinNodeInterface* m_embedded_node = nullptr;
509+
// io_context for scheduling async verification timers
510+
boost::asio::io_context* m_context = nullptr;
502511
std::atomic<bool> m_work_valid{false};
503512
nlohmann::json m_cached_template;
504513
std::vector<std::string> m_cached_merkle_branches; // Stratum merkle branches
@@ -567,10 +576,25 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server
567576
std::atomic<double> m_network_difficulty{0.0};
568577

569578
// Recently found blocks for /recent_blocks
570-
struct FoundBlock { uint64_t height; std::string hash; uint64_t ts; };
579+
enum class BlockStatus : uint8_t {
580+
pending = 0, // submitted, awaiting confirmation
581+
confirmed = 1, // in best chain (confirmations > 0)
582+
orphaned = 2, // replaced by another block at same height
583+
stale = 3, // submitted with stale prev_block
584+
};
585+
struct FoundBlock {
586+
uint64_t height;
587+
std::string hash;
588+
uint64_t ts;
589+
BlockStatus status{BlockStatus::pending};
590+
uint8_t check_count{0}; // how many verification attempts
591+
};
571592
std::vector<FoundBlock> m_found_blocks; // newest first, capped at 100
572593
mutable std::mutex m_blocks_mutex;
573594

595+
block_verify_fn_t m_block_verify_fn;
596+
void verify_found_block(size_t index);
597+
574598
// Pool start time for /uptime
575599
std::chrono::steady_clock::time_point m_start_time{std::chrono::steady_clock::now()};
576600

0 commit comments

Comments
 (0)