Files
thehub/libs/p2p/SyncChainAction.cpp
T
2022-11-04 11:50:49 +01:00

188 lines
7.0 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2020 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_startHeight = parent->blockHeight();
m_states.resize(RINGBUF_SIZE);
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.
* -----------------------------------------------------------
*
* 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)
*
* We require each of those peers to have done a 'getheaders' call as the
* system will disconnect any peer that doesn't agree with the longest chain
* and our checkpoints.
*
* Last, we use the date to guestimate the chain-height so we know we are
* stuck on a dead couple of nodes if stay get behind too much.
*/
const uint32_t now = time(nullptr);
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, it 10 measurements took
// 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();
auto p = cm.peer(m_dlm->peerDownloadingHeaders());
if (p)
cm.disconnect(p);
} else if (canAddNewPeer()){
logInfo() << "SyncChain would like a faster peer. Connecting to new one";
connectToNextPeer();
}
}
}
m_states[m_stateIndex].height = m_dlm->blockHeight();
m_states[m_stateIndex++].timestamp = now;
if (m_stateIndex >= RINGBUF_SIZE)
m_stateIndex = 0;
again();
return;
}
int goodPeers = 0;
bool receivedHeaders = false;
for (auto peer: m_dlm->connectionManager().connectedPeers()) {
receivedHeaders |= peer->receivedHeaders();
if (peer->receivedHeaders() && peer->peerAddress().punishment() <= 300) {
auto i = m_doubtfulPeers.find(peer->connectionId());
if (i != m_doubtfulPeers.end())
m_doubtfulPeers.erase(i);
goodPeers++;
if (goodPeers >= MaxGoodPeers)
break;
}
else if (peer->startHeight() >= m_dlm->blockHeight()) {
auto i = m_doubtfulPeers.find(peer->connectionId());
if (i == m_doubtfulPeers.end()) {
// new peer. it looks promising
// But lets wait to see if the headers message will be sent by them.
m_doubtfulPeers.insert(std::make_pair(peer->connectionId(), now));
if (!peer->requestedHeader())
m_dlm->connectionManager().requestHeaders(peer);
}
}
}
if (goodPeers > 0 && !receivedHeaders && m_doubtfulPeers.empty()) {
// make sure that we actually try to download headers if we weren't already
m_dlm->getMoreHeaders();
}
if (goodPeers < MinGoodPeers) {
logInfo() << "SyncChain has" << goodPeers << "good peers, which is less than I need. Connecting a new peer";
connectToNextPeer();
}
// so we have enough peers, that seem to agree with our chain. Lets see if our chain
// is up-to-date.
else if (m_dlm->blockchain().expectedBlockHeight() - m_dlm->blockHeight() < 3) {
// close enough. We are not catching-up, anyway.
logDebug() << "SyncChain done";
m_dlm->done(this);
return;
}
else {
// 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 a couple more peers.
if (goodPeers < MaxGoodPeers) {
logDebug() << "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;
}