/* * This file is part of the Flowee project * Copyright (C) 2020-2026 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "SyncChainAction.h" #include "DownloadManager.h" #include "Peer.h" #include #include static int MinGoodPeers = 4; static int MaxGoodPeers = 8; constexpr int RINGBUF_SIZE = 10; SyncChainAction::SyncChainAction(DownloadManager *parent) : Action(parent), m_states(RINGBUF_SIZE) { m_startHeight = parent->blockHeight(); if (parent->chain() != P2PNet::MainChain) { MinGoodPeers = 3; MaxGoodPeers = 3; } } void SyncChainAction::execute(const boost::system::error_code &error) { if (error) return; /* * This class observes the system, typically shortly after startup. * The point is to make sure we have a fully up-to-date chain. * * We also monitor the 'getheaders' updating of a big amount (again, * only at startup) and kick a node that is slow in giving us headers. * ----------------------------------------------------------- * * The goal is to get various peers to agree on what is the 'tip'. * These peers should also come from different sections of the Internet * since most of the sybils would end up with very similar IPs. (TODO) * * The downloadmanager/connection manager duo make sure that all peers * get a 'getheaders' call regularly, and kick out peers that are not on * our chain. * Additionally they make sure that INV announcements of blocks are validated * similarly and the Peer objects 'peerHeight()' is updated on a new block. */ const int localHeight = m_dlm->blockHeight(); const int goal = m_dlm->blockchain().expectedBlockHeight(); const int theoreticalDiff = std::abs(localHeight - goal); const auto connected = m_dlm->connectionManager().connectedPeers(); if (static_cast(m_dlm->connectionManager().connectedPeers().size()) < MinGoodPeers) { logInfo(2021) << "SyncChain has" << connected.size() << "peers, which is less than I need. Connecting a new peer"; int newPeers = 1; if (m_peerRequests++ > 13) // speed up after some time newPeers = 10; while (newPeers-- > 0) connectToNextPeer(); m_dlm->connectionManager().setBlockHeightCertainty(P2PNet::NotCertain); } int peersAgree = 0; int goodPeers = 0; int peersGotRequestedToSendHeaders = 0; const uint32_t now = time(nullptr); const uint32_t headersOkTime = 3600 * 24 * 7; // a full week for (const auto &peer : connected) { if (!peer->receivedHeaders() && now - peer->peerAddress().lastReceivedGoodHeaders() > headersOkTime) { if (peer->requestedHeader()) ++peersGotRequestedToSendHeaders; continue; } if (peer->peerAddress().punishment() > 300) continue; ++goodPeers; if (peer->peerHeight() == localHeight) ++peersAgree; } if ((peersAgree >= MinGoodPeers && theoreticalDiff < 6) || peersAgree >= MaxGoodPeers) { // DONE! logInfo(2021) << "==== SyncChainAction ==== DONE"; if (localHeight - m_startHeight > 800) { // 800 should match ConnectionManager::connectionEstablished // in ConnectionManager we check a peer is on the right chain by doing a 'headers' call. // Unless we are behind too far as that would not be useful. // So, now we are caught up, make those peers show some headers. for (const auto &peer : connected) { if (!peer->receivedHeaders()) { logInfo(2021) << "Post catching up, request headers of peer" << peer->connectionId(); m_dlm->connectionManager().requestHeaders(peer, 4); } } } m_dlm->connectionManager().setBlockHeightCertainty(P2PNet::Certain); m_dlm->done(this); // deletes me return; // action is done } else if (peersAgree >= 3) { m_dlm->connectionManager().setBlockHeightCertainty(P2PNet::ReasonablyCertain); } if (m_dlm->peerDownloadingHeaders() != -1) { // a download is in progress // no need to do anything, just wait. // We will check if we are actually progressing up in block-height since // our peer might stop sending us headers for some reason. int score = 0; int prevHeight = 0; uint32_t oldestTime = 0; for (int i = m_stateIndex + 1; i != m_stateIndex; ++i) { if (prevHeight == 0) { if (i < RINGBUF_SIZE) { prevHeight = m_states[i].height; if (prevHeight == 0) // when no measurements present, allow starting score += 100; else oldestTime = m_states[i].timestamp; } } else { int diff = m_states[i].height - prevHeight; if (diff > 900) score += 100; } if (i >= RINGBUF_SIZE - 1) i = -1; // behave like a ring-buffer. } if (score <= 400) { // getting maybe 3000 block-headers in 15 secs is too slow :( assert(oldestTime > 0); if (now - oldestTime > 60) { // this action should run every 1.5 seconds, if 10 measurements took // more than a minute then we just slept or something. logDebug(2021) << "Slowness detected in header download, probably due to app-sleep. Waiting longer"; } else { // take action, find a different peer to download from. auto &cm = m_dlm->connectionManager(); if (cm.connectedPeers().size() > 1) { // only if we actually have another logInfo(2021) << "SyncChain disconnects peer that is holding up downloads" << m_dlm->peerDownloadingHeaders(); auto p = cm.peer(m_dlm->peerDownloadingHeaders()); if (p) cm.disconnect(p); for (size_t i = 0; i < RINGBUF_SIZE; ++i) { m_states[i].height = 0; // reset history and score. } } else if (canAddNewPeer()){ logInfo(2021) << "SyncChain would like a faster peer. Connecting to new one"; connectToNextPeer(); } } } m_states[m_stateIndex].height = std::max(1, m_dlm->blockHeight()); m_states[m_stateIndex++].timestamp = now; if (m_stateIndex >= RINGBUF_SIZE) m_stateIndex = 0; } else { if (!connected.empty() && peersGotRequestedToSendHeaders == 0 && peersAgree == 0) { // make sure that we actually try to download headers if we weren't already m_dlm->getMoreHeaders(); } if (static_cast(connected.size()) >= MinGoodPeers && goodPeers > MinGoodPeers && peersAgree < MinGoodPeers) { // ok, this is annoying. // we have enough peers, they have all had the headers sent, we are not downloading any headers. // but we seem to be behind... // Lets add more peers. logInfo(2021) << "SyncChain has" << goodPeers << "good peers, but we are still behind. Connecting a new peer"; connectToNextPeer(); } } again(); } void SyncChainAction::connectToNextPeer() { auto address = m_dlm->connectionManager().peerAddressDb().findBest(5); // 5 is service Bloom and Network if (address.isValid()) { m_dlm->connectionManager().connect(address); m_lastPeerAddedTime = time(nullptr); } } bool SyncChainAction::canAddNewPeer() { return time(nullptr) - m_lastPeerAddedTime > 30; }