Files
thehub/libs/p2p/SyncChainAction.cpp
tomFlowee 25eb649ac8 Start finding peers more agressively after time
Try to be closer to what the SPV action does, after calmly starting we
agressively get more new peers every cycle to compensate for the problem
that our addresses database may have lots of outdated data.
2026-02-05 13:35:42 +01:00

212 lines
8.4 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2020-2026 Tom Zander <tom@flowee.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "SyncChainAction.h"
#include "DownloadManager.h"
#include "Peer.h"
#include <Logger.h>
#include <time.h>
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<int>(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<int>(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;
}