/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see .
*/
/** @file EthereumPeer.cpp
* @author Gav Wood
* @date 2014
*/
#include "EthereumPeer.h"
#include
#include
#include
#include
#include "BlockChain.h"
#include "EthereumHost.h"
using namespace std;
using namespace dev;
using namespace dev::eth;
using namespace p2p;
#define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] "
EthereumPeer::EthereumPeer(Session* _s, HostCapabilityFace* _h):
Capability(_s, _h)
{
sendStatus();
}
EthereumPeer::~EthereumPeer()
{
giveUpOnFetch();
}
EthereumHost* EthereumPeer::host() const
{
return static_cast(Capability::hostCapability());
}
void EthereumPeer::sendStatus()
{
RLPStream s;
prep(s);
s.appendList(6) << StatusPacket
<< host()->protocolVersion()
<< host()->networkId()
<< host()->m_chain->details().totalDifficulty
<< host()->m_chain->currentHash()
<< host()->m_chain->genesisHash();
sealAndSend(s);
}
void EthereumPeer::startInitialSync()
{
// Grab trsansactions off them.
{
RLPStream s;
prep(s).appendList(1);
s << GetTransactionsPacket;
sealAndSend(s);
}
h256 c = host()->m_chain->currentHash();
unsigned n = host()->m_chain->number();
u256 td = max(host()->m_chain->details().totalDifficulty, host()->m_totalDifficultyOfNeeded);
clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain->details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty;
if (td > m_totalDifficulty)
return; // All good - we have the better chain.
// Our chain isn't better - grab theirs.
{
RLPStream s;
prep(s).appendList(3);
s << GetBlockHashesPacket << m_latestHash << c_maxHashesAsk;
m_neededBlocks = h256s(1, m_latestHash);
sealAndSend(s);
}
}
inline string toString(h256s const& _bs)
{
ostringstream out;
out << "[ ";
for (auto i: _bs)
out << i.abridged() << ", ";
out << "]";
return out.str();
}
void EthereumPeer::giveUpOnFetch()
{
clogS(NetNote) << "GIVE UP FETCH; can't get" << toString(m_askedBlocks);
if (m_askedBlocks.size())
{
Guard l (host()->x_blocksNeeded);
host()->m_blocksNeeded.reserve(host()->m_blocksNeeded.size() + m_askedBlocks.size());
for (auto i: m_askedBlocks)
{
m_failedBlocks.insert(i);
host()->m_blocksOnWay.erase(i);
host()->m_blocksNeeded.push_back(i);
}
m_askedBlocks.clear();
}
}
bool EthereumPeer::interpret(RLP const& _r)
{
switch (_r[0].toInt())
{
case StatusPacket:
{
m_protocolVersion = _r[1].toInt();
m_networkId = _r[2].toInt();
m_totalDifficulty = _r[3].toInt();
m_latestHash = _r[4].toHash();
auto genesisHash = _r[5].toHash();
clogS(NetMessageSummary) << "Status:" << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged();
if (genesisHash != host()->m_chain->genesisHash())
disable("Invalid genesis hash");
if (m_protocolVersion != host()->protocolVersion())
disable("Invalid protocol version.");
if (m_networkId != host()->networkId())
disable("Invalid network identifier.");
startInitialSync();
break;
}
case GetTransactionsPacket:
{
m_requireTransactions = true;
break;
}
case TransactionsPacket:
clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << "entries)";
addRating(_r.itemCount() - 1);
for (unsigned i = 1; i < _r.itemCount(); ++i)
{
host()->m_incomingTransactions.push_back(_r[i].data().toBytes());
m_knownTransactions.insert(sha3(_r[i].data()));
}
break;
case GetBlockHashesPacket:
{
h256 later = _r[1].toHash();
unsigned limit = _r[2].toInt();
clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries," << later.abridged() << ")";
unsigned c = min(host()->m_chain->number(later), limit);
RLPStream s;
prep(s).appendList(1 + c).append(BlockHashesPacket);
h256 p = host()->m_chain->details(later).parent;
for (unsigned i = 0; i < c; ++i, p = host()->m_chain->details(p).parent)
s << p;
sealAndSend(s);
break;
}
case BlockHashesPacket:
{
clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << "entries)";
if (_r.itemCount() == 1)
{
host()->noteHaveChain(this);
return true;
}
for (unsigned i = 1; i < _r.itemCount(); ++i)
{
auto h = _r[i].toHash();
if (host()->m_chain->details(h))
{
host()->noteHaveChain(this);
return true;
}
else
m_neededBlocks.push_back(h);
}
// run through - ask for more.
RLPStream s;
prep(s).appendList(3);
s << GetBlockHashesPacket << m_neededBlocks.back() << c_maxHashesAsk;
sealAndSend(s);
break;
}
case GetBlocksPacket:
{
clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << "entries)";
// TODO: return the requested blocks.
bytes rlp;
unsigned n = 0;
for (unsigned i = 1; i < _r.itemCount() && i <= c_maxBlocks; ++i)
{
auto b = host()->m_chain->block(_r[i].toHash());
if (b.size())
{
rlp += b;
++n;
}
}
RLPStream s;
sealAndSend(prep(s).appendList(n + 1).append(BlocksPacket).appendRaw(rlp, n));
break;
}
case BlocksPacket:
{
clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << "entries)";
if (_r.itemCount() == 1 && !m_askedBlocksChanged)
{
// Couldn't get any from last batch - probably got to this peer's latest block - just give up.
giveUpOnFetch();
}
m_askedBlocksChanged = false;
unsigned used = 0;
for (unsigned i = 1; i < _r.itemCount(); ++i)
{
auto h = BlockInfo::headerHash(_r[i].data());
if (host()->noteBlock(h, _r[i].data()))
used++;
m_askedBlocks.erase(h);
m_knownBlocks.insert(h);
}
addRating(used);
unsigned knownParents = 0;
unsigned unknownParents = 0;
if (g_logVerbosity >= NetMessageSummary::verbosity)
{
for (unsigned i = 1; i < _r.itemCount(); ++i)
{
auto h = BlockInfo::headerHash(_r[i].data());
BlockInfo bi(_r[i].data());
if (!host()->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash))
{
unknownParents++;
clogS(NetAllDetail) << "Unknown parent" << bi.parentHash << "of block" << h;
}
else
{
knownParents++;
clogS(NetAllDetail) << "Known parent" << bi.parentHash << "of block" << h;
}
}
}
clogS(NetMessageSummary) << dec << knownParents << "known parents," << unknownParents << "unknown," << used << "used.";
continueGettingChain();
break;
}
default:
return false;
}
return true;
}
void EthereumPeer::restartGettingChain()
{
if (m_askedBlocks.size())
{
m_askedBlocksChanged = true; // So that we continue even if the Ask's reply is empty.
m_askedBlocks.clear(); // So that we restart once we get the Ask's reply.
m_failedBlocks.clear();
}
else
ensureGettingChain();
}
void EthereumPeer::ensureGettingChain()
{
if (m_askedBlocks.size())
return; // Already asked & waiting for some.
continueGettingChain();
}
void EthereumPeer::continueGettingChain()
{
if (!m_askedBlocks.size())
m_askedBlocks = host()->neededBlocks(m_failedBlocks);
if (m_askedBlocks.size())
{
RLPStream s;
prep(s);
s.appendList(m_askedBlocks.size() + 1) << GetBlocksPacket;
for (auto i: m_askedBlocks)
s << i;
sealAndSend(s);
}
else
{
clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have" << m_failedBlocks.size() << "of our needed blocks.";
host()->noteDoneBlocks();
}
}