Commit 25ffc84f authored by Tom Zander's avatar Tom Zander
Browse files

Introduce new lib p2p

We reuse the NetworkManager lower level code in order to connect
to the Bitcoin P2P network.
This implements the basics for anyone wanting to be a player on
this network.
parent 37d8209d
Pipeline #138016872 passed with stages
in 24 minutes and 27 seconds
......@@ -284,6 +284,7 @@ set(LIBHTTPENGINE_INCLUDES ${CMAKE_SOURCE_DIR}/libs/httpengine ${CMAKE_BINARY_DI
# these two should only ever be used by the hub hub-qt and the unit tests.
set(LIBSERVER_INCLUDES ${LIBNETWORKMANAGER_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/server ${CMAKE_SOURCE_DIR}/libs/server/wallet)
set(LIBAPI_INCLUDES ${LIBSERVER_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/api)
set(LIBP2P_INCLUDES ${LIBNETWORKMANAGER_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/p2p)
add_subdirectory(libs)
......
......@@ -20,7 +20,7 @@ add_subdirectory(networkmanager)
add_subdirectory(utils)
add_subdirectory(server)
add_subdirectory(crypto)
#add_subdirectory(console)
add_subdirectory(p2p)
add_subdirectory(api)
add_subdirectory(utxo)
......
/*
* This file is part of the Flowee project
* Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
*
* 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 "Action.h"
#include "DownloadManager.h"
#include <functional>
Action::Action(DownloadManager *parent)
: m_dlm(parent),
m_timer(parent->service())
{
}
void Action::again()
{
m_timer.expires_from_now(boost::posix_time::milliseconds(1500));
m_timer.async_wait(m_dlm->strand().wrap(std::bind(&Action::execute, this, std::placeholders::_1)));
}
void Action::start()
{
assert(m_dlm);
m_dlm->strand().post(std::bind(&Action::execute, this, boost::system::error_code()));
}
void Action::cancel()
{
m_timer.cancel();
}
Action::~Action()
{
}
/*
* This file is part of the Flowee project
* Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
*
* 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/>.
*/
#ifndef ACTION_H
#define ACTION_H
class DownloadManager;
#include <boost/asio/io_service.hpp>
#include <boost/asio/deadline_timer.hpp>
class Action
{
public:
virtual ~Action();
void start();
virtual void cancel();
protected:
explicit Action(DownloadManager *parent);
virtual void execute(const boost::system::error_code &error) = 0;
void again();
DownloadManager *m_dlm;
private:
boost::asio::deadline_timer m_timer;
};
#endif
/*
* This file is part of the Flowee project
* Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
*
* 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 "BlockHeader.h"
#include <utils/streaming/P2PParser.h>
#include <utils/hash.h>
BlockHeader BlockHeader::fromMessage(Streaming::P2PParser &parser)
{
BlockHeader answer;
answer.nVersion = parser.readInt();
answer.hashPrevBlock = parser.readUint256();
answer.hashMerkleRoot = parser.readUint256();
answer.nTime = parser.readInt();
answer.nBits = parser.readInt();
answer.nNonce = parser.readInt();
return answer;
}
uint256 BlockHeader::createHash() const
{
static_assert (sizeof(*this) == 80, "Header size");
assert(!hashMerkleRoot.IsNull());
CHash256 hasher;
hasher.Write(reinterpret_cast<const uint8_t*>(this), 80);
uint256 hash;
hasher.Finalize((unsigned char*)&hash);
return hash;
}
arith_uint256 BlockHeader::blockProof() const
{
arith_uint256 bnTarget;
bool fNegative;
bool fOverflow;
bnTarget.SetCompact(nBits, &fNegative, &fOverflow);
if (fNegative || fOverflow || bnTarget == 0)
return 0;
// We need to compute 2**256 / (bnTarget+1), but we can't represent 2**256
// as it's too large for a arith_uint256. However, as 2**256 is at least as large
// as bnTarget+1, it is equal to ((2**256 - bnTarget - 1) / (bnTarget+1)) + 1,
// or ~bnTarget / (nTarget+1) + 1.
return (~bnTarget / (bnTarget + 1)) + 1;
}
/*
* This file is part of the Flowee project
* Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
*
* 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/>.
*/
#ifndef BLOCKHEADER_H
#define BLOCKHEADER_H
#include <uint256.h>
#include <cstdint>
#include <arith_uint256.h>
namespace Streaming {
class P2PParser;
}
struct BlockHeader
{
static BlockHeader fromMessage(Streaming::P2PParser &parser);
uint256 createHash() const;
arith_uint256 blockProof() const;
int32_t nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
uint32_t nTime;
uint32_t nBits;
uint32_t nNonce;
};
#endif
/*
* This file is part of the Flowee project
* Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
*
* 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 "Blockchain.h"
#include "DownloadManager.h"
#include <utils/utiltime.h>
#include <streaming/P2PParser.h>
Blockchain::Blockchain(DownloadManager *downloadManager)
: m_dlmanager(downloadManager)
{
assert(m_dlmanager);
m_longestChain.reserve(650000); // pre-allocate
BlockHeader genesis;
genesis.nBits = 0x1d00ffff;
genesis.nTime = 1231006505;
genesis.nNonce = 2083236893;
genesis.nVersion = 1;
genesis.hashMerkleRoot = uint256S("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b");
m_longestChain.push_back(genesis);
uint256 genesisHash = genesis.createHash();
m_blockHeight.insert(std::make_pair(genesisHash, 0));
m_tip.tip = genesisHash;
m_tip.height = 0;
m_tip.chainWork += genesis.blockProof();
checkpoints.insert(std::make_pair( 11111, uint256S("0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")));
checkpoints.insert(std::make_pair( 33333, uint256S("000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")));
checkpoints.insert(std::make_pair( 74000, uint256S("0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")));
checkpoints.insert(std::make_pair(105000, uint256S("00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97")));
checkpoints.insert(std::make_pair(134444, uint256S("00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")));
checkpoints.insert(std::make_pair(168000, uint256S("000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763")));
checkpoints.insert(std::make_pair(193000, uint256S("000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317")));
checkpoints.insert(std::make_pair(210000, uint256S("000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e")));
checkpoints.insert(std::make_pair(216116, uint256S("00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e")));
checkpoints.insert(std::make_pair(225430, uint256S("00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932")));
checkpoints.insert(std::make_pair(250000, uint256S("000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214")));
checkpoints.insert(std::make_pair(279000, uint256S("0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40")));
checkpoints.insert(std::make_pair(295000, uint256S("00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983")));
checkpoints.insert(std::make_pair(478559, uint256S("000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec")));
checkpoints.insert(std::make_pair(556767, uint256S("0000000000000000004626ff6e3b936941d341c5932ece4357eeccac44e6d56c")));
checkpoints.insert(std::make_pair(582680, uint256S("000000000000000001b4b8e36aec7d4f9671a47872cb9a74dc16ca398c7dcc18")));
checkpoints.insert(std::make_pair(609136, uint256S("000000000000000000b48bb207faac5ac655c313e41ac909322eaa694f5bc5b1")));
}
Message Blockchain::createGetHeadersRequest(Streaming::P2PBuilder &builder)
{
std::unique_lock<std::mutex> lock(m_lock);
uint256 tip;
if (m_tip.height < 1000) {
builder.writeCompactSize(1);
builder.writeByteArray(tip.begin(), 32, Streaming::RawBytes);
} else {
builder.writeCompactSize(10);
std::array<int, 10> offsets = {
m_tip.height,
m_tip.height - 3,
m_tip.height - 20,
m_tip.height - 60,
m_tip.height - 100,
m_tip.height - 200,
m_tip.height - 400,
m_tip.height - 600,
m_tip.height - 800,
m_tip.height - 1000
};
for (auto i : offsets) {
uint256 hash = m_longestChain.at(i - 1).createHash();
builder.writeByteArray(hash.begin(), 32, Streaming::RawBytes);
}
}
builder.writeByteArray(tip.begin(), 32, Streaming::RawBytes);
return builder.message(Api::P2P::GetHeaders);
}
void Blockchain::processBlockHeaders(Message message, int peerId)
{
int newTip;
{
std::unique_lock<std::mutex> lock(m_lock);
Streaming::P2PParser parser(message);
auto count = parser.readCompactInt();
if (count > 2000) {
logInfo() << "Peer" << peerId << "Sent too many headers" << count << "p2p protocol violation";
m_dlmanager->reportDataFailure(peerId);
return;
}
const uint32_t maxFuture = time(nullptr) + 7200; // headers can not be more than 2 hours in the future.
uint256 prevHash;
int startHeight = -1;
int height = 0;
int64_t previousTime = 0;
arith_uint256 chainWork;
for (size_t i = 0; i < count; ++i) {
BlockHeader header = BlockHeader::fromMessage(parser);
/*int txCount =*/ parser.readCompactInt(); // always zero
// timestamp not more than 2h in the future.
if (header.nTime > maxFuture) {
logInfo() << "Peer" << peerId << "sent bogus headers. Too far in future";
m_dlmanager->reportDataFailure(peerId);
return;
}
if (startHeight == -1) { // first header in the sequence.
auto iter = m_blockHeight.find(header.hashPrevBlock);
if (iter == m_blockHeight.end()) {
logInfo() << "Peer" << peerId << "is on a different chain, headers don't extend ours";
m_dlmanager->reportDataFailure(peerId);
return;
}
height = startHeight = iter->second + 1;
if (m_tip.height + 1 == startHeight) {
chainWork = m_tip.chainWork;
previousTime = m_longestChain.end()->nTime;
} else if (m_tip.height - startHeight > (int) count) {
logInfo() << "Peer" << peerId << "is on a different chain, headers don't extend ours";
m_dlmanager->reportDataFailure(peerId);
return;
} else {
// rollback the chainWork to branch-point
assert(m_tip.height == (int) m_longestChain.size() - 1);
chainWork = m_tip.chainWork;
for (int height = m_tip.height; height >= startHeight; --height) {
chainWork -= m_longestChain.at(height).blockProof();
}
previousTime = m_longestChain.at(startHeight - 1).nTime;
}
}
else if (prevHash != header.hashPrevBlock) { // check if we are really a sequence.
logInfo() << "Peer" << peerId << "Sent bogus headers. Not in sequence";
m_dlmanager->reportDataFailure(peerId);
return;
}
uint256 hash = header.createHash();
// check POW
{
bool fNegative;
bool fOverflow;
arith_uint256 bnTarget;
bnTarget.SetCompact(header.nBits, &fNegative, &fOverflow);
if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(powLimit)
|| UintToArith256(hash) > bnTarget) {// Check proof of work matches claimed amount
logInfo() << "Peer" << peerId << "sent bogus headers. POW failed" << height;
m_dlmanager->reportDataFailure(peerId);
return;
}
}
chainWork += header.blockProof();
auto cpIter = checkpoints.find(height);
if (cpIter != checkpoints.end()) {
if (cpIter->second != hash) {
logInfo() << "Peer" << peerId << "is on a different chain, checkpoint failure:" << height;
m_dlmanager->reportDataFailure(peerId);
return;
}
}
prevHash = std::move(hash);
++height;
}
if (chainWork <= m_tip.chainWork)
return;
// The new chain has more PoW, apply it.
parser = Streaming::P2PParser(message);
count = parser.readCompactInt();
height = startHeight;
m_longestChain.resize(startHeight + count);
for (size_t i = 0; i < count; ++i) {
BlockHeader header = BlockHeader::fromMessage(parser);
/*int txCount =*/ parser.readCompactInt(); // always zero
m_blockHeight.insert(std::make_pair(header.createHash(), height));
m_longestChain[height++] = header;
}
m_tip.height = height - 1;
m_tip.tip = prevHash;
m_tip.chainWork= chainWork;
newTip = m_tip.height;
logInfo() << "Headers now at" << newTip << m_tip.tip <<
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", m_longestChain.back().nTime).c_str();
} // lock-scope
m_dlmanager->headersDownloadFinished(newTip, peerId);
}
int Blockchain::expectedBlockHeight() const
{
std::unique_lock<std::mutex> lock(m_lock);
const BlockHeader &tip = m_longestChain.back();
int secsSinceLastBlock = time(nullptr) - tip.nTime;
return m_tip.height + (secsSinceLastBlock + 300) / 600; // add how many 10 minutes chunks fit in the time-span.
}
bool Blockchain::isKnown(const uint256 &blockId) const
{
std::unique_lock<std::mutex> lock(m_lock);
return m_blockHeight.find(blockId) != m_blockHeight.end();
}
int Blockchain::blockHeightFor(const uint256 &blockId) const
{
std::unique_lock<std::mutex> lock(m_lock);
auto iter = m_blockHeight.find(blockId);
if (iter == m_blockHeight.end())
return -1;
if (int(m_longestChain.size()) <= iter->second)
return -1;
if (m_longestChain.at(iter->second).createHash() != blockId)
return -1;
return iter->second;
}
BlockHeader Blockchain::block(int height) const
{
assert(height > 0);
std::unique_lock<std::mutex> lock(m_lock);
if (int(m_longestChain.size()) <= height)
return BlockHeader();
return m_longestChain.at(height);
}
/*
* This file is part of the Flowee project
* Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
*
* 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/>.
*/
#ifndef BLOCKCHAIN_H
#define BLOCKCHAIN_H
#include "BlockHeader.h"
#include <streaming/P2PBuilder.h>
#include <Message.h>
#include <arith_uint256.h>
#include <uint256.h>
#include <boost/unordered_map.hpp>
#include <mutex>
class DownloadManager;
class Blockchain
{
public:
Blockchain(DownloadManager *downloadManager);
Message createGetHeadersRequest(Streaming::P2PBuilder &builder);
void processBlockHeaders(Message message, int peerId);
int expectedBlockHeight() const;
bool isKnown(const uint256 &blockId) const;
int blockHeightFor(const uint256 &blockId) const;
BlockHeader block(int height) const;
private:
mutable std::mutex m_lock;
std::vector<BlockHeader> m_longestChain;
struct ChainTip {
uint256 tip;
int height;
arith_uint256 chainWork;
};
ChainTip m_tip;
typedef boost::unordered_map<uint256, int, HashShortener> BlockHeightMap;
BlockHeightMap m_blockHeight;
DownloadManager *m_dlmanager;
// consensus
const uint256 powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
std::map<int, uint256> checkpoints;
};
#endif
# This file is part of the Flowee project
# Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
#
# 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_directories(${LIBP2P_INCLUDES} ${CMAKE_BINARY_DIR}/include)
add_library(flowee_p2p STATIC
Action.cpp
BlockHeader.cpp
Blockchain.cpp
ConnectionManager.cpp
DataListenerInterface.cpp
DownloadManager.cpp
FillAddressDBAction.cpp
InventoryItem.cpp
P2PNetInterface.cpp
Peer.cpp
PeerAddressDB.cpp
PrivacySegment.cpp
SyncChainAction.cpp
SyncSPVAction.cpp
)
target_link_libraries(flowee_p2p flowee_networkmanager)
add_definitions(-DLOG_DEFAULT_SECTION=2009) # aka Logger::P2PNet
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libflowee_p2p.a DESTINATION lib)
install(FILES
Action.h
BlockHeader.h
Blockchain.h
ConnectionManager.h
DataListenerInterface.h
DownloadManager.h
FillAddressDBAction.h
InventoryItem.h
P2PNetInterface.h
Peer.h
PeerAddressDB.h
PrivacySegment.h
SyncChainAction.h
SyncSPVAction.h
DESTINATION include/flowee/p2p)
/*
* This file is part of the Flowee project
* Copyright (C) 2020 Tom Zander <tomz@freedommail.ch>
*
* 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 "ConnectionManager.h"
#include "DownloadManager.h"
#include "P2PNetInterface.h"
#include "Peer.h"
#include "PrivacySegment.h"
#include <streaming/BufferPool.h>