mirror of
https://github.com/cculianu/Fulcrum.git
synced 2026-08-13 12:33:27 +02:00
Work in progress for peering
This commit is contained in:
parent
b0c42329b6
commit
d5b772dfd1
14 changed files with 578 additions and 24 deletions
|
|
@ -152,8 +152,10 @@ SOURCES += \
|
|||
Mixins.cpp \
|
||||
Mgr.cpp \
|
||||
Options.cpp \
|
||||
PeerMgr.cpp \
|
||||
RecordFile.cpp \
|
||||
RPC.cpp \
|
||||
ServerMisc.cpp \
|
||||
Servers.cpp \
|
||||
SrvMgr.cpp \
|
||||
Storage.cpp \
|
||||
|
|
@ -178,8 +180,10 @@ HEADERS += \
|
|||
Mgr.h \
|
||||
Mixins.h \
|
||||
Options.h \
|
||||
PeerMgr.h \
|
||||
RecordFile.h \
|
||||
RPC.h \
|
||||
ServerMisc.h \
|
||||
Servers.h \
|
||||
SrvMgr.h \
|
||||
Storage.h \
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public:
|
|||
/// or if the file cannot be opened, the default banner text will be emitted to the client as a fallback.
|
||||
QString bannerFile = "";
|
||||
|
||||
bool peerDiscovery = false, peerAnnounceSelf = false; ///< comes from config setting: 'peering' and 'announce' TODO
|
||||
bool peerDiscovery = true, peerAnnounceSelf = true; ///< comes from config setting: 'peering' and 'announce' TODO -- for now hard-coded on for testing
|
||||
|
||||
std::optional<QString> hostName; ///< corresponds to hostname in server config
|
||||
std::optional<quint16> publicTcp; ///< corresponds to public_tcp_port in server config -- if unspecified will default to the first TCP interface, if !has_value, it will not be announced
|
||||
|
|
|
|||
189
PeerMgr.cpp
Normal file
189
PeerMgr.cpp
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#include "PeerMgr.h"
|
||||
|
||||
#include "Options.h"
|
||||
#include "Servers.h"
|
||||
#include "Storage.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <QHostInfo>
|
||||
#include <QSet>
|
||||
|
||||
PeerMgr::PeerMgr(const std::shared_ptr<Storage> &storage_ , const std::shared_ptr<const Options> &options_)
|
||||
: IdMixin(newId()), storage(storage_), options(options_)
|
||||
{
|
||||
setObjectName("PeerMgr");
|
||||
_thread.setObjectName(objectName());
|
||||
}
|
||||
|
||||
PeerMgr::~PeerMgr() { stop(); /* noop if already stopped */ Debug() << __func__; }
|
||||
|
||||
void PeerMgr::startup()
|
||||
{
|
||||
if (storage->genesisHash().length() != HashLen)
|
||||
throw InternalError("PeerMgr cannot be started until we have a valid genesis hash! FIXME!");
|
||||
if (const auto chain = storage->getChain(); !QSet<QString>{"test", "main"}.contains(chain))
|
||||
// can only do peering with testnet or mainnet after they have been defined (no regtest)
|
||||
throw InternalError(QString("PeerMgr cannot be started for the given chain \"%1\"").arg(chain));
|
||||
else if (chain == "test")
|
||||
parseServersDotJson(":resources/servers_testnet.json");
|
||||
else
|
||||
parseServersDotJson(":resources/servers_testnet.json");
|
||||
start();
|
||||
}
|
||||
|
||||
void PeerMgr::parseServersDotJson(const QString &fnIn)
|
||||
{
|
||||
QVariantMap m = Util::Json::parseFile(fnIn).toMap();
|
||||
const QString fn = Util::basename(fnIn); // use basename for error messages below, etc
|
||||
if (m.isEmpty()) throw InternalError(QString("PeerMgr: %1 file parsed to an empty dict! FIXME!").arg(fn));
|
||||
for (auto it = m.begin(); it != m.end(); ++it) {
|
||||
PeerInfo info;
|
||||
QVariantMap d = it.value().toMap();
|
||||
info.hostName = it.key().trimmed().toLower();
|
||||
// skip empties/malformed entries, or pruning entries -- thisdefensive programming.. ideally we include only good entries in servers.json
|
||||
if (info.hostName.isEmpty() || d.isEmpty() || (!d.value("pruning").isNull() && d.value("pruning").toString() != "-")) {
|
||||
Debug() << "Server \"" << info.hostName << "\" in " << fn << " has either no data or uses pruning, skipping";
|
||||
continue;
|
||||
}
|
||||
bool ok;
|
||||
unsigned val = d.value("s", 0).toUInt(&ok);
|
||||
if (ok && val && val <= USHRT_MAX)
|
||||
info.ssl = quint16(val);
|
||||
val = d.value("t", 0).toUInt(&ok);
|
||||
if (ok && val && val <= USHRT_MAX)
|
||||
info.tcp = quint16(val);
|
||||
info.protocolVersion = d.value("version", ServerMisc::MinProtocolVersion.toString()).toString();
|
||||
if (!info.isMinimallyValid()) {
|
||||
Debug() << "Bad server in " << fn << ": " << info.hostName;
|
||||
continue;
|
||||
} else if (info.protocolVersion < ServerMisc::MinProtocolVersion || info.protocolVersion > ServerMisc::MaxProtocolVersion) {
|
||||
Debug() << "Server in " << fn << " has incompatible protocol version (" << info.protocolVersion.toString() << "), skipping";
|
||||
continue;
|
||||
}
|
||||
seedPeers[info.hostName] = info;
|
||||
}
|
||||
if (seedPeers.isEmpty())
|
||||
throw InternalError(QString("PeerMgr: No valid peers parsed from %1").arg(fn));
|
||||
seedPeers.squeeze();
|
||||
Debug() << objectName() << ": using " << seedPeers.size() << " peers from " << fn;
|
||||
}
|
||||
|
||||
void PeerMgr::on_started()
|
||||
{
|
||||
Debug() << objectName() << ": started ok";
|
||||
}
|
||||
|
||||
void PeerMgr::cleanup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void PeerMgr::on_rpcAddPeer(const PeerInfoList &infos, const QHostAddress &source)
|
||||
{
|
||||
Debug() << __func__ << " source: " << source.toString();
|
||||
|
||||
// TODO: perhaps put all these in a queue and collapse dupes down -- as it stands clients can spam the same request
|
||||
// over and over and cause us to waste time doing network lookups.
|
||||
|
||||
for (const auto & pi : infos) {
|
||||
// For each peer in the list, do a DNS lookup and verify that the source address matches at least one
|
||||
// of the resolved addresses. If that is the case, we can proceed with the peer add (addPeerVerifiedSource).
|
||||
// Otherwise, we reject add_peer requests from random sources.
|
||||
std::shared_ptr<std::optional<int>> lookupId = std::make_shared<decltype(lookupId)::element_type>();
|
||||
*lookupId = QHostInfo::lookupHost(pi.hostName, this, [this, pi, source, lookupId](const QHostInfo &result){
|
||||
lookupId->reset(); // signify we no longer need a cancellation .. calls reset on the std::optional (not on the shared_ptr)
|
||||
if (result.error() != QHostInfo::NoError) {
|
||||
Debug() << "add_peer: Host lookup error for " << pi.hostName << ": " << result.errorString();
|
||||
return;
|
||||
}
|
||||
for (const auto & addr : result.addresses()) {
|
||||
if (addr == source) {
|
||||
Debug() << "add_peer: " << pi.hostName << " address (" << addr.toString() << ") matches source (" << source.toString() << "), processing further ...";
|
||||
addPeerVerifiedSource(pi, addr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Debug() << "add_peer: Rejected because source (" << source.toString() << ") does not match resolved address ("
|
||||
<< (result.addresses().isEmpty() ? QString() : result.addresses().front().toString()) << ")";
|
||||
});
|
||||
QTimer::singleShot(DNSTimeoutMS, this, [lookupId, hostName = pi.hostName]{
|
||||
if (lookupId->has_value()) {
|
||||
QHostInfo::abortHostLookup(lookupId->value());
|
||||
Debug() << "add_peer: hostname lookup for " << hostName << " timed out after " << QString::number(DNSTimeoutMS/1e3, 'f', 1) << " secs";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void PeerMgr::addPeerVerifiedSource(const PeerInfo &piIn, const QHostAddress & addr)
|
||||
{
|
||||
PeerInfo pi(piIn);
|
||||
pi.addr = addr;
|
||||
Debug() << __func__ << " peer " << pi.hostName << " ipaddr: " << pi.addr.toString();
|
||||
// TODO ...
|
||||
}
|
||||
|
||||
void PeerMgr::allServersStarted()
|
||||
{
|
||||
// TODO ...
|
||||
Debug() << __func__;
|
||||
}
|
||||
|
||||
|
||||
/* static */ QList<PeerInfo> PeerInfo::fromFeaturesMap(const QVariantMap &m)
|
||||
{
|
||||
QList<PeerInfo> ret;
|
||||
|
||||
if (!m.value("pruning").isNull())
|
||||
throw BadFeaturesMap("Pruning not supported");
|
||||
|
||||
PeerInfo base;
|
||||
|
||||
base.subversion = m.value("server_version", "Unknown").toString().trimmed().left(80);
|
||||
base.protocolMin = m.value("protocol_min").toString().trimmed().left(80);
|
||||
base.protocolMax = m.value("protocol_max").toString().trimmed().left(80);
|
||||
base.genesisHash = QByteArray::fromHex(m.value("genesis_hash").toString().trimmed().toUtf8()).left(HashLen+1);
|
||||
if (base.genesisHash.length() != HashLen)
|
||||
throw BadFeaturesMap("Bad genesis hash");
|
||||
base.hashFunction = m.value("hash_function").toString().trimmed().toLower();
|
||||
if (base.hashFunction != ServerMisc::HashFunction)
|
||||
throw BadFeaturesMap("Bad/incompatible hash function");
|
||||
|
||||
if (!base.protocolMin.isValid() || !base.protocolMax.isValid() || base.protocolMin > base.protocolMax)
|
||||
throw BadFeaturesMap("Bad protocol min/max");
|
||||
if (base.protocolMin > ServerMisc::MaxProtocolVersion || base.protocolMax < ServerMisc::MinProtocolVersion)
|
||||
throw BadFeaturesMap("Incompatible server protocol");
|
||||
|
||||
const auto hosts = m.value("hosts").toMap();
|
||||
|
||||
if (hosts.size() > 4)
|
||||
// Disallow huge maps
|
||||
throw BadFeaturesMap("Hosts map cannot have more than 4 hosts in it!");
|
||||
|
||||
// now, parse each host
|
||||
for (auto it = hosts.begin(); it != hosts.end(); ++it) {
|
||||
PeerInfo pi(base); // copy c'tor of base, but fill in host, tcp, and ssl
|
||||
pi.hostName = it.key().trimmed().toLower().left(120); // we don't support super long hostnames as a paranoia defense
|
||||
const auto m = it.value().toMap(); // <--- note to self: shadows outer scope 'm'
|
||||
if (!m.value("tcp_port").isNull()) {
|
||||
bool ok;
|
||||
unsigned val = m.value("tcp_port", 0).toUInt(&ok);
|
||||
if (!ok || !val || val > USHRT_MAX) throw BadFeaturesMap("Bad tcp_port");
|
||||
pi.tcp = quint16(val);
|
||||
}
|
||||
if (!m.value("ssl_port").isNull()) {
|
||||
bool ok;
|
||||
unsigned val = m.value("ssl_port", 0).toUInt(&ok);
|
||||
if (!ok || !val || val > USHRT_MAX) throw BadFeaturesMap("Bad ssl_port");
|
||||
pi.ssl = quint16(val);
|
||||
}
|
||||
if (!pi.isMinimallyValid())
|
||||
throw BadFeaturesMap(QString("Bad host: ") + pi.hostName);
|
||||
ret.push_back(pi);
|
||||
}
|
||||
|
||||
if (ret.isEmpty())
|
||||
throw BadFeaturesMap("No hosts!");
|
||||
|
||||
return ret;
|
||||
}
|
||||
97
PeerMgr.h
Normal file
97
PeerMgr.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
|
||||
// Copyright (C) 2019-2020 Calin A. Culianu <calin.culianu@gmail.com>
|
||||
//
|
||||
// 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 (see LICENSE.txt). If not, see
|
||||
// <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "Mixins.h"
|
||||
#include "Mgr.h"
|
||||
#include "ServerMisc.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
|
||||
struct Options;
|
||||
class Storage;
|
||||
struct PeerInfo;
|
||||
|
||||
using PeerInfoList = QList<PeerInfo>;
|
||||
|
||||
class PeerMgr : public Mgr, public IdMixin, public ThreadObjectMixin, public TimersByNameMixin
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PeerMgr(const std::shared_ptr<Storage> & , const std::shared_ptr<const Options> &);
|
||||
~PeerMgr() override;
|
||||
|
||||
void startup() noexcept(false) override; ///< may throw
|
||||
void cleanup() override;
|
||||
|
||||
/// The hostname lookup timeout for add_peer hostname verification
|
||||
static constexpr int DNSTimeoutMS = 3000;
|
||||
|
||||
public slots:
|
||||
/// The various Server instances are connected to this slot (via their gotRpcAddPeer signals), connections made by SrvMgr.
|
||||
void on_rpcAddPeer(const PeerInfoList &, const QHostAddress & source);
|
||||
void allServersStarted(); ///< tells this instance all our services are up, so it may begin searching for peers and publishing our information
|
||||
|
||||
protected:
|
||||
void on_started() override;
|
||||
private:
|
||||
const std::shared_ptr<const Storage> storage; ///< from SrvMgr, read-only, for getChain() and genesisHash()
|
||||
const std::shared_ptr<const Options> options; ///< from SrvMgr that creates us.
|
||||
|
||||
QHash<QString, PeerInfo> seedPeers; ///< parsed PeerInfos from server.json or servers_testnet.json
|
||||
void parseServersDotJson(const QString &) noexcept(false); ///< may throw
|
||||
|
||||
void addPeerVerifiedSource(const PeerInfo &, const QHostAddress &resolvedAddress);
|
||||
};
|
||||
|
||||
/// Thrown by PeerInfo::fromFeaturesMap
|
||||
struct BadFeaturesMap : public Exception { using Exception::Exception; };
|
||||
|
||||
struct PeerInfo
|
||||
{
|
||||
QString hostName;
|
||||
QHostAddress addr; ///< may originally come from json or be empty -- is populated/reverified later after hostname lookup.
|
||||
quint16 ssl = 0; ///< ssl port - 0 means undefined (no port)
|
||||
quint16 tcp = 0; ///< tcp port - 0 means undefined (no port)
|
||||
Version protocolVersion; ///< may originally come from json or be empty -- is populated/reverified later after connection to peer
|
||||
|
||||
QString subversion; ///< if we actually managed to connect to the server, its subversion string e.g. "Fulcrum 1.0", or may come initially from features dict
|
||||
/// These get populated if we managed to connect to the server, or if the server's info came from a features dictionary
|
||||
QByteArray genesisHash; ///< if known, may be empty, may come from features map
|
||||
Version protocolMin,
|
||||
protocolMax;
|
||||
QString hashFunction = ServerMisc::HashFunction; /// may also come from features map
|
||||
|
||||
void clear() { *this = PeerInfo(); }
|
||||
bool isTor() const { return hostName.toLower().endsWith(".onion"); }
|
||||
/// minimal checking that hostname is not empty and that at least an ssl or a tcp port are defined.
|
||||
bool isMinimallyValid() const { return !hostName.isEmpty() && (ssl || tcp) && hashFunction == ServerMisc::HashFunction; }
|
||||
|
||||
/// Pass it the "features" map as returned by server.features. Normally only one PeerInfo will be returned, but
|
||||
/// there may be multiple in the case of .onion in the 'hosts' sub-map. All of the hosts returned have identical
|
||||
/// members with the exception of .hostName, .ssl, and .tcp which may differ. Will never return an empty list,
|
||||
/// instead, it will throw if no servers are contained in the map. Will also throw if other minimal checks fail.
|
||||
static PeerInfoList fromFeaturesMap(const QVariantMap &m) noexcept(false); ///< NOTE: May throw BadFeaturesMap
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(PeerInfo);
|
||||
Q_DECLARE_METATYPE(PeerInfoList);
|
||||
10
ServerMisc.cpp
Normal file
10
ServerMisc.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#include "Common.h"
|
||||
#include "ServerMisc.h"
|
||||
|
||||
namespace ServerMisc
|
||||
{
|
||||
const Version MinProtocolVersion(1,4,0);
|
||||
const Version MaxProtocolVersion(1,4,2);
|
||||
const QString AppVersion(VERSION);
|
||||
const QString AppSubVersion = QString("%1 %2").arg(APPNAME).arg(VERSION);
|
||||
}
|
||||
35
ServerMisc.h
Normal file
35
ServerMisc.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
|
||||
// Copyright (C) 2019-2020 Calin A. Culianu <calin.culianu@gmail.com>
|
||||
//
|
||||
// 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 (see LICENSE.txt). If not, see
|
||||
// <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace ServerMisc
|
||||
{
|
||||
constexpr const char * const HashFunction = "sha256";
|
||||
|
||||
/// Used in various places to rejects old clients or incompatible peers. Currently 1.4 and 1.4.2 respectively.
|
||||
extern const Version MinProtocolVersion, MaxProtocolVersion;
|
||||
|
||||
extern const QString AppVersion, ///< in string form suitable for sending in protocol or banner e.g. "1.0"
|
||||
AppSubVersion; ///< e.g. "Fulcrum 1.0"
|
||||
}
|
||||
|
||||
43
Servers.cpp
43
Servers.cpp
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
#include "BitcoinD.h"
|
||||
#include "Merkle.h"
|
||||
#include "PeerMgr.h"
|
||||
#include "ServerMisc.h"
|
||||
#include "Storage.h"
|
||||
#include "SubsMgr.h"
|
||||
|
||||
|
|
@ -322,11 +324,6 @@ namespace {
|
|||
}
|
||||
|
||||
|
||||
/* static */ const Version Server::MinProtocolVersion(1,4,0);
|
||||
const Version Server::MaxProtocolVersion(1,4,2);
|
||||
/* static */ const QString Server::AppVersion(VERSION);
|
||||
const QString Server::AppSubVersion = QString("%1 %2").arg(APPNAME).arg(VERSION);
|
||||
|
||||
Server::Server(const QHostAddress &a, quint16 p, const std::shared_ptr<const Options> & opts,
|
||||
const std::shared_ptr<Storage> &s, const std::shared_ptr<BitcoinDMgr> &bdm)
|
||||
: AbstractTcpServer(a, p), options(opts), storage(s), bitcoindmgr(bdm)
|
||||
|
|
@ -622,16 +619,30 @@ void Server::generic_async_to_bitcoind(Client *c, const RPC::Message::Id & reqId
|
|||
|
||||
void Server::rpc_server_add_peer(Client *c, const RPC::Message &m)
|
||||
{
|
||||
// TODO: Implement this. This is a stub implementation always returning true.
|
||||
const auto map = m.paramsList().front().toMap();
|
||||
if (map.isEmpty())
|
||||
throw RPCError(QString("%1 expected a non-empty dictionary argument").arg(m.method));
|
||||
emit c->sendResult(m.id, true);
|
||||
bool retval = true;
|
||||
try {
|
||||
const auto peerList = PeerInfo::fromFeaturesMap(map); // this may throw BadFeaturesMap
|
||||
if (peerList.isEmpty() || peerList.front().genesisHash != storage->genesisHash())
|
||||
throw BadFeaturesMap("Incompatible genesis hash");
|
||||
const auto peerAddress = c->peerAddress();
|
||||
Debug() << "add_peer tentatively accepted for host " << peerList.front().hostName << " (" << peerList.size() << ")" << " from " << peerAddress.toString();
|
||||
emit gotRpcAddPeer(peerList, peerAddress);
|
||||
} catch (const BadFeaturesMap & e) {
|
||||
const auto hm = map.value("hosts").toMap();
|
||||
const QString hostNamePart = !hm.isEmpty() ? QString(" (%1)").arg(hm.firstKey()) : QString();
|
||||
Debug() << "Refusing add_peer" << hostNamePart << " for reason: " << e.what();
|
||||
retval = false;
|
||||
}
|
||||
emit c->sendResult(m.id, retval);
|
||||
|
||||
}
|
||||
void Server::rpc_server_banner(Client *c, const RPC::Message &m)
|
||||
{
|
||||
constexpr int MAX_BANNER_DATA = 16384;
|
||||
static const QString bannerFallback = QString("Connected to a %1 server").arg(AppSubVersion);
|
||||
static const QString bannerFallback = QString("Connected to a %1 server").arg(ServerMisc::AppSubVersion);
|
||||
const QString bannerFile(options->bannerFile);
|
||||
if (bannerFile.isEmpty() || !QFile::exists(bannerFile) || !QFileInfo(bannerFile).isReadable()) {
|
||||
// fallback -- banner file invalid/not readable/not specified
|
||||
|
|
@ -654,8 +665,8 @@ void Server::rpc_server_banner(Client *c, const RPC::Message &m)
|
|||
} else {
|
||||
// read banner file ok, perform variable substitutions
|
||||
ret = QString::fromUtf8(bannerFileData)
|
||||
.replace("$SERVER_VERSION", AppVersion)
|
||||
.replace("$SERVER_SUBVERSION", AppSubVersion)
|
||||
.replace("$SERVER_VERSION", ServerMisc::AppVersion)
|
||||
.replace("$SERVER_SUBVERSION", ServerMisc::AppSubVersion)
|
||||
.replace("$DONATION_ADDRESS", donationAddress)
|
||||
.replace("$DAEMON_VERSION", daemonVersion.toString(true))
|
||||
.replace("$DAEMON_SUBVERSION", subversion)
|
||||
|
|
@ -675,10 +686,10 @@ void Server::rpc_server_features(Client *c, const RPC::Message &m)
|
|||
QVariantMap r;
|
||||
r["pruning"] = QVariant(); // null
|
||||
r["genesis_hash"] = QString(Util::ToHexFast(storage->genesisHash()));
|
||||
r["server_version"] = AppSubVersion;
|
||||
r["protocol_min"] = MinProtocolVersion.toString();
|
||||
r["protocol_max"] = MaxProtocolVersion.toString();
|
||||
r["hash_function"] = "sha256";
|
||||
r["server_version"] = ServerMisc::AppSubVersion;
|
||||
r["protocol_min"] = ServerMisc::MinProtocolVersion.toString();
|
||||
r["protocol_max"] = ServerMisc::MaxProtocolVersion.toString();
|
||||
r["hash_function"] = ServerMisc::HashFunction;
|
||||
|
||||
QVariantMap hmap;
|
||||
if (options->publicTcp.has_value())
|
||||
|
|
@ -718,13 +729,13 @@ void Server::rpc_server_version(Client *c, const RPC::Message &m)
|
|||
throw RPCError(QString("%1 already sent").arg(m.method));
|
||||
|
||||
Version ver = l[1].toString().left(kMaxServerVersionLen); // try and parse version, see Version.cpp, QString constructor.
|
||||
if (!ver.isValid() || ver < MinProtocolVersion || ver > MaxProtocolVersion)
|
||||
if (!ver.isValid() || ver < ServerMisc::MinProtocolVersion || ver > ServerMisc::MaxProtocolVersion)
|
||||
throw RPCErrorWithDisconnect("Unsupported protocol version");
|
||||
|
||||
c->info.userAgent = l[0].toString().left(kMaxServerVersionLen);
|
||||
c->info.protocolVersion = ver;
|
||||
c->info.alreadySentVersion = true;
|
||||
emit c->sendResult(m.id, QStringList({AppSubVersion, ver.toString()}));
|
||||
emit c->sendResult(m.id, QStringList({ServerMisc::AppSubVersion, ver.toString()}));
|
||||
}
|
||||
|
||||
/// returns the 'branch' and 'root' keys ready to be put in the results dictionary
|
||||
|
|
|
|||
11
Servers.h
11
Servers.h
|
|
@ -21,6 +21,7 @@
|
|||
#include "Common.h"
|
||||
#include "Mixins.h"
|
||||
#include "Options.h"
|
||||
#include "PeerMgr.h"
|
||||
#include "RPC.h"
|
||||
#include "Util.h"
|
||||
#include "Version.h"
|
||||
|
|
@ -138,12 +139,6 @@ public:
|
|||
// this must be called in the thread context of this thread
|
||||
QVariantMap stats() const;
|
||||
|
||||
/// Used in various places to rejects old clients or incompatible peers. Currently 1.4 and 1.4.2 respectively.
|
||||
static const Version MinProtocolVersion, MaxProtocolVersion;
|
||||
|
||||
static const QString AppVersion, ///< in string form suitable for sending in protocol or banner e.g. "1.0"
|
||||
AppSubVersion; ///< e.g. "Fulcrum 1.0"
|
||||
|
||||
signals:
|
||||
/// connected to SrvMgr clientConnected slot by SrvMgr class
|
||||
void clientConnected(IdMixin::Id clientId, const QHostAddress & remoteAddress);
|
||||
|
|
@ -153,6 +148,10 @@ signals:
|
|||
/// Connected to SrvMgr parent's "newHeader" signal (which itself is connected to Controller's newHeader).
|
||||
/// Used to notify clients that are subscribed to headers that a new header has arrived.
|
||||
void newHeader(unsigned height, const QByteArray &header);
|
||||
|
||||
/// Inform PeerMgr of new add_peer request coming in
|
||||
void gotRpcAddPeer(const PeerInfoList &, const QHostAddress &source);
|
||||
|
||||
public slots:
|
||||
void onMessage(IdMixin::Id clientId, const RPC::Message &m);
|
||||
void onErrorMessage(IdMixin::Id clientId, const RPC::Message &m);
|
||||
|
|
|
|||
15
SrvMgr.cpp
15
SrvMgr.cpp
|
|
@ -19,6 +19,7 @@
|
|||
#include "SrvMgr.h"
|
||||
|
||||
#include "BitcoinD.h"
|
||||
#include "PeerMgr.h"
|
||||
#include "Servers.h"
|
||||
#include "Storage.h"
|
||||
#include "Util.h"
|
||||
|
|
@ -51,12 +52,19 @@ void SrvMgr::startup()
|
|||
|
||||
void SrvMgr::cleanup()
|
||||
{
|
||||
peermgr.reset(); // unique_ptr, kill peermgr (if any)
|
||||
servers.clear(); // unique_ptrs auto-delete all servers
|
||||
}
|
||||
|
||||
// throw Exception on error
|
||||
void SrvMgr::startServers()
|
||||
{
|
||||
if (options->peerDiscovery) {
|
||||
Log() << "SrvMgr: starting PeerMgr ...";
|
||||
peermgr = std::make_unique<PeerMgr>(storage, options);
|
||||
peermgr->startup();
|
||||
} else peermgr.reset();
|
||||
|
||||
const auto num = options->interfaces.length() + options->sslInterfaces.length();
|
||||
Log() << "SrvMgr: starting " << num << " " << Util::Pluralize("service", num) << " ...";
|
||||
const auto firstSsl = options->interfaces.size();
|
||||
|
|
@ -78,9 +86,16 @@ void SrvMgr::startServers()
|
|||
// if srv receives this message, it will delete the client then we will get a signal back that it is now gone
|
||||
connect(this, &SrvMgr::clientExceedsConnectionLimit, srv, qOverload<IdMixin::Id>(&Server::killClient));
|
||||
|
||||
if (peermgr) {
|
||||
connect(srv, &Server::gotRpcAddPeer, peermgr.get(), &PeerMgr::on_rpcAddPeer);
|
||||
}
|
||||
|
||||
srv->tryStart();
|
||||
++i;
|
||||
}
|
||||
|
||||
if (peermgr)
|
||||
peermgr->allServersStarted();
|
||||
}
|
||||
|
||||
void SrvMgr::clientConnected(IdMixin::Id cid, const QHostAddress &addr)
|
||||
|
|
|
|||
2
SrvMgr.h
2
SrvMgr.h
|
|
@ -27,6 +27,7 @@
|
|||
#include <memory>
|
||||
|
||||
class BitcoinDMgr;
|
||||
class PeerMgr;
|
||||
class Server;
|
||||
class Storage;
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ private:
|
|||
std::shared_ptr<Storage> storage;
|
||||
std::shared_ptr<BitcoinDMgr> bitcoindmgr;
|
||||
std::list<std::unique_ptr<Server>> servers;
|
||||
std::unique_ptr<PeerMgr> peermgr; ///< will be nullptr if options->peerDiscovery is false
|
||||
|
||||
QMultiHash<QHostAddress, IdMixin::Id> addrIdMap;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "BTC.h"
|
||||
#include "Controller.h"
|
||||
#include "Mixins.h"
|
||||
#include "PeerMgr.h"
|
||||
#include "RPC.h"
|
||||
#include "SrvMgr.h"
|
||||
|
||||
|
|
@ -44,6 +45,9 @@ void App::register_MetaTypes()
|
|||
|
||||
qRegisterMetaType<QHostAddress>("QHostAddress");
|
||||
|
||||
qRegisterMetaType<PeerInfo>("PeerInfo");
|
||||
qRegisterMetaType<PeerInfoList>("PeerInfoList");
|
||||
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
<RCC>
|
||||
<qresource prefix="/file"/>
|
||||
<qresource prefix="/">
|
||||
<file>resources/servers.json</file>
|
||||
<file>resources/servers_testnet.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
149
resources/servers.json
Normal file
149
resources/servers.json
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
{
|
||||
"bch.imaginary.cash": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"bch0.kister.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"bch.loping.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"j2tjfxntnsqpojaamnndgmfrc6lh3thattnlpc2xx53h2ojoi7agccid.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1",
|
||||
"display": "bch.loping.net"
|
||||
},
|
||||
"bch.soul-dev.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"bchx.disdev.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"bitcoincash.quangld.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"blackie.c3-soft.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"crypto.mldlabs.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"electron-cash.dragon.zone": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"electron.jochen-hoenicke.de": {
|
||||
"pruning": "-",
|
||||
"s": "51002",
|
||||
"t": "51001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"electroncash.dk": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"electrum.imaginary.cash": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"electrumx.hillsideinternet.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"electrum-abc.criptolayer.net": {
|
||||
"pruning": "-",
|
||||
"s": "50012",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"electrumx-cash.1209k.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"greedyhog.ddns.net": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"wallet.satoshiscoffeehouse.com": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"7nshufncf3nmp7pa42oqhnj6whsjgo2eok4jveex62tczuhvqur5ciad.onion": {
|
||||
"pruning": "-",
|
||||
"t": "50001",
|
||||
"version": "1.4.1",
|
||||
"display": "electrum.imaginary.cash"
|
||||
},
|
||||
"kisternetg2pq7wx.onion": {
|
||||
"pruning": "-",
|
||||
"t": "50001",
|
||||
"version": "1.4.1"
|
||||
},
|
||||
"electron.coinucopia.io": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"bxdp2p6abpqt5etc.onion": {
|
||||
"pruning": "-",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"jh3jgcrwweh6yvmprtjnp72u2hqn34nlftlg3msrr4vmlapft4yvt2id.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"electroncash.de": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4"
|
||||
},
|
||||
"jktsologn7uprtwn7gsgmwuddj6rxsqmwc2vaug7jwcwzm2bxqnfpwad.onion": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.4",
|
||||
"display": "electroncash.de"
|
||||
}
|
||||
}
|
||||
36
resources/servers_testnet.json
Normal file
36
resources/servers_testnet.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"bch0.kister.net": {
|
||||
"s": "51002"
|
||||
},
|
||||
"blackie.c3-soft.com": {
|
||||
"s": "60002"
|
||||
},
|
||||
"testnet.imaginary.cash": {
|
||||
"s": "50002"
|
||||
},
|
||||
"testnet.bitcoincash.network": {
|
||||
"s": "60002"
|
||||
},
|
||||
"ebf56u6xk2e2fjlqyuz4zjj2gsrrnavfronrerh7qcvzllknytdgmmyd.onion": {
|
||||
"s": "50004",
|
||||
"t": "50003"
|
||||
},
|
||||
"jktsologn7uprtwn7gsgmwuddj6rxsqmwc2vaug7jwcwzm2bxqnfpwad.onion": {
|
||||
"s": "50004",
|
||||
"t": "50003",
|
||||
"display": "electroncash.de"
|
||||
},
|
||||
"electroncash.de": {
|
||||
"s": "50004",
|
||||
"t": "50003"
|
||||
},
|
||||
"tbch.loping.net": {
|
||||
"s": "60002",
|
||||
"t": "60001"
|
||||
},
|
||||
"scgjgc67226l65u52wyvulioxixv34p5dth73oj35ej7ham2zavdtxid.onion": {
|
||||
"s": "60002",
|
||||
"t": "60001",
|
||||
"display": "tbch.loping.net"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue