Prevent inaccurate missing spans when adding a block

This commit is contained in:
j-berman
2026-06-30 10:14:43 -07:00
parent 286a7c9c77
commit 700ec36e75
2 changed files with 25 additions and 2 deletions
@@ -193,6 +193,7 @@ namespace cryptonote
std::atomic<bool> m_ask_for_txpool_complement;
boost::mutex m_sync_lock;
block_queue m_block_queue;
boost::mutex m_check_span_queue_mutex;
epee::math_helper::once_a_time_seconds<8> m_idle_peer_kicker;
epee::math_helper::once_a_time_milliseconds<100> m_standby_checker;
epee::math_helper::once_a_time_seconds<101> m_sync_search_checker;
@@ -1489,8 +1489,16 @@ namespace cryptonote
return 1;
}
boost::unique_lock<boost::mutex> check_span_lock{m_check_span_queue_mutex, boost::defer_lock};
bool stopped = false;
epee::unique_scope_guard cleanup_on_exit = [this, &stopped, &context, span_connection_id, start_height]() {
epee::unique_scope_guard cleanup_on_exit = [this, &check_span_lock, &stopped, &context, span_connection_id, start_height]() {
// Grab the span queue check lock so that we make sure we finish writing the block to the db before checking
// the span queue again. Otherwise it's possible for the span queue check to read the db height at n-1 in
// thread1, then wait for block n to be added and its span removed from the queue in this thread2, then check
// the span queue in thread1 and incorrectly think the span starting at block n is missing.
// TODO: a better sync protocol.
check_span_lock.lock();
if (!m_core.cleanup_handle_incoming_blocks())
{
LOG_PRINT_CCONTEXT_L0("Failure in cleanup_handle_incoming_blocks");
@@ -1619,7 +1627,10 @@ namespace cryptonote
MGINFO_YELLOW("Synced " << current_blockchain_height << "/" << target_blockchain_height
<< progress_message << timing_message);
if (previous_stripe != current_stripe)
{
check_span_lock.unlock();
notify_new_stripe(context, current_stripe);
}
}
}
}
@@ -1964,7 +1975,7 @@ skip:
{
// take out blocks we already have
size_t skip = 0;
while (skip < context.m_needed_objects.size() && (m_core.have_block(context.m_needed_objects[skip].first) || (check_block_queue && m_block_queue.have(context.m_needed_objects[skip].first))))
while (skip < context.m_needed_objects.size() && (m_core.have_block_unlocked(context.m_needed_objects[skip].first) || (check_block_queue && m_block_queue.have(context.m_needed_objects[skip].first))))
{
// if we're popping the last hash, record it so we can ask again from that hash,
// this prevents never being able to progress on peers we get old hash lists from
@@ -2020,6 +2031,15 @@ skip:
{
do
{
// Enforce synchronization when checking the span queue. This is a simple solution to prevent unexpected races
// when checking the span queue. It's not ideal and doesn't fully solve all possible races.
// This section largely needs to be reworked.
// Warning: make sure to unlock this to avoid deadlocks if necessary
// If any of the functions below acquire the txpool lock (m_transactions_lock) or m_incoming_tx_lock, we can
// deadlock, since m_core.prepare_handle_incoming_blocks acquires both and does not release until
// m_core.cleanup_handle_incoming_blocks.
boost::unique_lock<boost::mutex> check_span_lock{m_check_span_queue_mutex};
const size_t nspans = m_block_queue.get_num_filled_spans();
const size_t size = m_block_queue.get_data_size();
const uint64_t bc_height = m_core.get_current_blockchain_height();
@@ -2039,6 +2059,7 @@ skip:
}
MDEBUG(context << "Nothing to get from this peer, and it's not ahead of us, all done");
context.set_state_normal();
check_span_lock.unlock();
if (m_core.get_current_blockchain_height() >= m_core.get_target_blockchain_height())
on_connection_synchronized();
return true;
@@ -2110,6 +2131,7 @@ skip:
MLOG_PEER_STATE("resuming");
context.m_state = cryptonote_connection_context::state_standby;
++context.m_callback_request_count;
check_span_lock.unlock();
m_p2p->request_callback(context);
return true;
}