Skip to content

Commit 3555473

Browse files
sipasdaftuar
authored andcommitted
Track headers presync progress and log it
1 parent 03712dd commit 3555473

File tree

4 files changed

+127
-7
lines changed

4 files changed

+127
-7
lines changed

src/headerssync.h

+6
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ class HeadersSyncState {
121121
/** Return the height reached during the PRESYNC phase */
122122
int64_t GetPresyncHeight() const { return m_current_height; }
123123

124+
/** Return the block timestamp of the last header received during the PRESYNC phase. */
125+
uint32_t GetPresyncTime() const { return m_last_header_received.nTime; }
126+
127+
/** Return the amount of work in the chain received during the PRESYNC phase. */
128+
arith_uint256 GetPresyncWork() const { return m_current_chain_work; }
129+
124130
/** Construct a HeadersSyncState object representing a headers sync via this
125131
* download-twice mechanism).
126132
*

src/net_processing.cpp

+89-7
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,9 @@ class PeerManagerImpl final : public PeerManager
513513

514514
/** Implement NetEventsInterface */
515515
void InitializeNode(CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
516-
void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
516+
void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex);
517517
bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override
518-
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex);
518+
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex);
519519
bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing)
520520
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex);
521521

@@ -532,7 +532,7 @@ class PeerManagerImpl final : public PeerManager
532532
void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); };
533533
void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv,
534534
const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override
535-
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex);
535+
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex);
536536
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override;
537537

538538
private:
@@ -601,7 +601,7 @@ class PeerManagerImpl final : public PeerManager
601601
void ProcessHeadersMessage(CNode& pfrom, Peer& peer,
602602
std::vector<CBlockHeader>&& headers,
603603
bool via_compact_block)
604-
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
604+
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex);
605605
/** Various helpers for headers processing, invoked by ProcessHeadersMessage() */
606606
/** Return true if headers are continuous and have valid proof-of-work (DoS points assigned on failure) */
607607
bool CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer);
@@ -633,7 +633,7 @@ class PeerManagerImpl final : public PeerManager
633633
*/
634634
bool IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfrom,
635635
std::vector<CBlockHeader>& headers)
636-
EXCLUSIVE_LOCKS_REQUIRED(peer.m_headers_sync_mutex);
636+
EXCLUSIVE_LOCKS_REQUIRED(peer.m_headers_sync_mutex, !m_headers_presync_mutex);
637637
/** Check work on a headers chain to be processed, and if insufficient,
638638
* initiate our anti-DoS headers sync mechanism.
639639
*
@@ -649,7 +649,7 @@ class PeerManagerImpl final : public PeerManager
649649
bool TryLowWorkHeadersSync(Peer& peer, CNode& pfrom,
650650
const CBlockIndex* chain_start_header,
651651
std::vector<CBlockHeader>& headers)
652-
EXCLUSIVE_LOCKS_REQUIRED(!peer.m_headers_sync_mutex, !m_peer_mutex);
652+
EXCLUSIVE_LOCKS_REQUIRED(!peer.m_headers_sync_mutex, !m_peer_mutex, !m_headers_presync_mutex);
653653

654654
/** Return true if the given header is an ancestor of
655655
* m_chainman.m_best_header or our current tip */
@@ -844,6 +844,24 @@ class PeerManagerImpl final : public PeerManager
844844
std::shared_ptr<const CBlockHeaderAndShortTxIDs> m_most_recent_compact_block GUARDED_BY(m_most_recent_block_mutex);
845845
uint256 m_most_recent_block_hash GUARDED_BY(m_most_recent_block_mutex);
846846

847+
// Data about the low-work headers synchronization, aggregated from all peers' HeadersSyncStates.
848+
/** Mutex guarding the other m_headers_presync_* variables. */
849+
Mutex m_headers_presync_mutex;
850+
/** A type to represent statistics about a peer's low-work headers sync.
851+
*
852+
* - The first field is the total verified amount of work in that synchronization.
853+
* - The second is:
854+
* - nullopt: the sync is in REDOWNLOAD phase (phase 2).
855+
* - {height, timestamp}: the sync has the specified tip height and block timestamp (phase 1).
856+
*/
857+
using HeadersPresyncStats = std::pair<arith_uint256, std::optional<std::pair<int64_t, uint32_t>>>;
858+
/** Statistics for all peers in low-work headers sync. */
859+
std::map<NodeId, HeadersPresyncStats> m_headers_presync_stats GUARDED_BY(m_headers_presync_mutex) {};
860+
/** The peer with the most-work entry in m_headers_presync_stats. */
861+
NodeId m_headers_presync_bestpeer GUARDED_BY(m_headers_presync_mutex) {-1};
862+
/** The m_headers_presync_stats improved, and needs signalling. */
863+
std::atomic_bool m_headers_presync_should_signal{false};
864+
847865
/** Height of the highest block announced using BIP 152 high-bandwidth mode. */
848866
int m_highest_fast_announce{0};
849867

@@ -1502,6 +1520,10 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
15021520
// fSuccessfullyConnected set.
15031521
m_addrman.Connected(node.addr);
15041522
}
1523+
{
1524+
LOCK(m_headers_presync_mutex);
1525+
m_headers_presync_stats.erase(nodeid);
1526+
}
15051527
LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid);
15061528
}
15071529

@@ -2448,6 +2470,48 @@ bool PeerManagerImpl::IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfro
24482470

24492471
if (peer.m_headers_sync->GetState() == HeadersSyncState::State::FINAL) {
24502472
peer.m_headers_sync.reset(nullptr);
2473+
2474+
// Delete this peer's entry in m_headers_presync_stats.
2475+
// If this is m_headers_presync_bestpeer, it will be replaced later
2476+
// by the next peer that triggers the else{} branch below.
2477+
LOCK(m_headers_presync_mutex);
2478+
m_headers_presync_stats.erase(pfrom.GetId());
2479+
} else {
2480+
// Build statistics for this peer's sync.
2481+
HeadersPresyncStats stats;
2482+
stats.first = peer.m_headers_sync->GetPresyncWork();
2483+
if (peer.m_headers_sync->GetState() == HeadersSyncState::State::PRESYNC) {
2484+
stats.second = {peer.m_headers_sync->GetPresyncHeight(),
2485+
peer.m_headers_sync->GetPresyncTime()};
2486+
}
2487+
2488+
// Update statistics in stats.
2489+
LOCK(m_headers_presync_mutex);
2490+
m_headers_presync_stats[pfrom.GetId()] = stats;
2491+
auto best_it = m_headers_presync_stats.find(m_headers_presync_bestpeer);
2492+
bool best_updated = false;
2493+
if (best_it == m_headers_presync_stats.end()) {
2494+
// If the cached best peer is outdated, iterate over all remaining ones (including
2495+
// newly updated one) to find the best one.
2496+
NodeId peer_best{-1};
2497+
const HeadersPresyncStats* stat_best{nullptr};
2498+
for (const auto& [peer, stat] : m_headers_presync_stats) {
2499+
if (!stat_best || stat > *stat_best) {
2500+
peer_best = peer;
2501+
stat_best = &stat;
2502+
}
2503+
}
2504+
m_headers_presync_bestpeer = peer_best;
2505+
best_updated = (peer_best == pfrom.GetId());
2506+
} else if (best_it->first == pfrom.GetId() || stats > best_it->second) {
2507+
// pfrom was and remains the best peer, or pfrom just became best.
2508+
m_headers_presync_bestpeer = pfrom.GetId();
2509+
best_updated = true;
2510+
}
2511+
if (best_updated && stats.second.has_value()) {
2512+
// If the best peer updated, and it is in its first phase, signal.
2513+
m_headers_presync_should_signal = true;
2514+
}
24512515
}
24522516

24532517
if (result.success) {
@@ -2676,6 +2740,8 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
26762740
LOCK(peer.m_headers_sync_mutex);
26772741
if (peer.m_headers_sync) {
26782742
peer.m_headers_sync.reset(nullptr);
2743+
LOCK(m_headers_presync_mutex);
2744+
m_headers_presync_stats.erase(pfrom.GetId());
26792745
}
26802746
return;
26812747
}
@@ -4318,7 +4384,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43184384
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
43194385
}
43204386

4321-
return ProcessHeadersMessage(pfrom, *peer, std::move(headers), /*via_compact_block=*/false);
4387+
ProcessHeadersMessage(pfrom, *peer, std::move(headers), /*via_compact_block=*/false);
4388+
4389+
// Check if the headers presync progress needs to be reported to validation.
4390+
// This needs to be done without holding the m_headers_presync_mutex lock.
4391+
if (m_headers_presync_should_signal.exchange(false)) {
4392+
HeadersPresyncStats stats;
4393+
{
4394+
LOCK(m_headers_presync_mutex);
4395+
auto it = m_headers_presync_stats.find(m_headers_presync_bestpeer);
4396+
if (it != m_headers_presync_stats.end()) stats = it->second;
4397+
}
4398+
if (stats.second) {
4399+
m_chainman.ReportHeadersPresync(stats.first, stats.second->first, stats.second->second);
4400+
}
4401+
}
4402+
4403+
return;
43224404
}
43234405

43244406
if (msg_type == NetMsgType::BLOCK)

src/validation.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -3711,6 +3711,29 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>&
37113711
return true;
37123712
}
37133713

3714+
void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp)
3715+
{
3716+
AssertLockNotHeld(cs_main);
3717+
const auto& chainstate = ActiveChainstate();
3718+
{
3719+
LOCK(cs_main);
3720+
// Don't report headers presync progress if we already have a post-minchainwork header chain.
3721+
// This means we lose reporting for potentially legimate, but unlikely, deep reorgs, but
3722+
// prevent attackers that spam low-work headers from filling our logs.
3723+
if (m_best_header->nChainWork >= UintToArith256(GetConsensus().nMinimumChainWork)) return;
3724+
// Rate limit headers presync updates to 4 per second, as these are not subject to DoS
3725+
// protection.
3726+
auto now = std::chrono::steady_clock::now();
3727+
if (now < m_last_presync_update + std::chrono::milliseconds{250}) return;
3728+
m_last_presync_update = now;
3729+
}
3730+
if (chainstate.IsInitialBlockDownload()) {
3731+
const int64_t blocks_left{(GetTime() - timestamp) / GetConsensus().nPowTargetSpacing};
3732+
const double progress{100.0 * height / (height + blocks_left)};
3733+
LogPrintf("Pre-synchronizing blockheaders, height: %d (~%.2f%%)\n", height, progress);
3734+
}
3735+
}
3736+
37143737
/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
37153738
bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked)
37163739
{

src/validation.h

+9
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,9 @@ class ChainstateManager
868868
bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
869869
friend CChainState;
870870

871+
/** Most recent headers presync progress update, for rate-limiting. */
872+
std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {};
873+
871874
public:
872875
using Options = kernel::ChainstateManagerOpts;
873876

@@ -1046,6 +1049,12 @@ class ChainstateManager
10461049
/** Produce the necessary coinbase commitment for a block (modifies the hash, don't call for mined blocks). */
10471050
std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const;
10481051

1052+
/** This is used by net_processing to report pre-synchronization progress of headers, as
1053+
* headers are not yet fed to validation during that time, but validation is (for now)
1054+
* responsible for logging and signalling through NotifyHeaderTip, so it needs this
1055+
* information. */
1056+
void ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp);
1057+
10491058
~ChainstateManager();
10501059
};
10511060

0 commit comments

Comments
 (0)