Files
thehub/libs/p2p/SyncChainAction.cpp
T

199 lines
7.8 KiB
C++
Raw Permalink Normal View History

2020-04-17 19:33:06 +02:00
/*
* This file is part of the Flowee project
* Copyright (C) 2020-2024 Tom Zander <tom@flowee.org>
2020-04-17 19:33:06 +02:00
*
* 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>
2020-11-13 20:40:23 +01:00
static int MinGoodPeers = 4;
2020-11-05 17:42:51 +01:00
static int MaxGoodPeers = 8;
2020-04-17 19:33:06 +02:00
constexpr int RINGBUF_SIZE = 10;
SyncChainAction::SyncChainAction(DownloadManager *parent)
2023-11-03 22:12:02 +01:00
: Action(parent),
m_states(RINGBUF_SIZE)
2020-04-17 19:33:06 +02:00
{
m_startHeight = parent->blockHeight();
2020-11-13 20:40:23 +01:00
if (parent->chain() != P2PNet::MainChain) {
MinGoodPeers = 3;
MaxGoodPeers = 3;
}
2020-04-17 19:33:06 +02:00
}
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.
2020-04-17 19:33:06 +02:00
* -----------------------------------------------------------
*
* 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.
2020-04-17 19:33:06 +02:00
*/
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();
2024-02-09 21:12:45 +01:00
if (static_cast<int>(m_dlm->connectionManager().connectedPeers().size()) < MinGoodPeers) {
logInfo() << "SyncChain has" << connected.size() << "peers, which is less than I need. Connecting a new peer";
connectToNextPeer();
}
int peersAgree = 0;
int goodPeers = 0;
int peersGotRequestedToSendHeaders = 0;
for (const auto &peer : connected) {
if (!peer->receivedHeaders()) {
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() << "==== 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()) {
2023-04-17 19:25:07 +02:00
logInfo() << "Post catching up, request headers of peer" << peer->connectionId();
m_dlm->connectionManager().requestHeaders(peer, 4);
}
}
}
m_dlm->done(this); // deletes me
return; // action is done
}
2020-04-17 19:33:06 +02:00
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.
const uint32_t now = time(nullptr);
2020-04-17 19:33:06 +02:00
int score = 0;
int prevHeight = 0;
uint32_t oldestTime = 0;
for (int i = m_stateIndex + 1; i != m_stateIndex; ++i) {
if (prevHeight == 0) {
2020-05-09 19:58:44 +02:00
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;
}
2020-04-17 19:33:06 +02:00
} 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.
}
2023-11-03 22:12:02 +01:00
if (score <= 400) {
2020-04-17 19:33:06 +02:00
// getting maybe 3000 block-headers in 15 secs is too slow :(
assert(oldestTime > 0);
if (now - oldestTime > 60) {
2023-03-25 17:26:24 +01:00
// this action should run every 1.5 seconds, if 10 measurements took
2020-04-17 19:33:06 +02:00
// more than a minute then we just slept or something.
logDebug() << "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() << "SyncChain disconnects peer that is holding up downloads" << m_dlm->peerDownloadingHeaders();
2020-05-10 01:04:24 +02:00
auto p = cm.peer(m_dlm->peerDownloadingHeaders());
if (p)
cm.disconnect(p);
2023-11-03 22:12:02 +01:00
for (size_t i = 0; i < RINGBUF_SIZE; ++i) {
m_states[i].height = 0; // reset history and score.
}
2020-04-17 19:33:06 +02:00
} else if (canAddNewPeer()){
logInfo() << "SyncChain would like a faster peer. Connecting to new one";
connectToNextPeer();
}
}
}
2023-11-03 22:12:02 +01:00
m_states[m_stateIndex].height = std::max(1, m_dlm->blockHeight());
2020-04-17 19:33:06 +02:00
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...
2020-04-17 19:33:06 +02:00
// Lets add more peers.
logInfo() << "SyncChain has" << goodPeers << "good peers, but we are still behind. Connecting a new peer";
2020-04-17 19:33:06 +02:00
connectToNextPeer();
}
}
2020-04-17 19:33:06 +02:00
again();
}
void SyncChainAction::connectToNextPeer()
{
2022-11-04 11:50:49 +01:00
auto address = m_dlm->connectionManager().peerAddressDb().findBest(5); // 5 is service Bloom and Network
2020-04-17 19:33:06 +02:00
if (address.isValid())
m_dlm->connectionManager().connect(address);
m_lastPeerAddedTime = time(nullptr);
}
bool SyncChainAction::canAddNewPeer()
{
return time(nullptr) - m_lastPeerAddedTime > 30;
}