diff --git a/Fulcrum.pro b/Fulcrum.pro index e1cb2a1..0abe22a 100644 --- a/Fulcrum.pro +++ b/Fulcrum.pro @@ -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 \ diff --git a/Options.h b/Options.h index f882a32..fbd77a5 100644 --- a/Options.h +++ b/Options.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 hostName; ///< corresponds to hostname in server config std::optional 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 diff --git a/PeerMgr.cpp b/PeerMgr.cpp new file mode 100644 index 0000000..691b1b6 --- /dev/null +++ b/PeerMgr.cpp @@ -0,0 +1,189 @@ +#include "PeerMgr.h" + +#include "Options.h" +#include "Servers.h" +#include "Storage.h" +#include "Util.h" + +#include +#include + +PeerMgr::PeerMgr(const std::shared_ptr &storage_ , const std::shared_ptr &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{"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> lookupId = std::make_shared(); + *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::fromFeaturesMap(const QVariantMap &m) +{ + QList 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; +} diff --git a/PeerMgr.h b/PeerMgr.h new file mode 100644 index 0000000..d30b8bc --- /dev/null +++ b/PeerMgr.h @@ -0,0 +1,97 @@ +// +// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash +// Copyright (C) 2019-2020 Calin A. Culianu +// +// 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 +// . +// +#pragma once + +#include "Mixins.h" +#include "Mgr.h" +#include "ServerMisc.h" +#include "Version.h" + +#include +#include +#include + +struct Options; +class Storage; +struct PeerInfo; + +using PeerInfoList = QList; + +class PeerMgr : public Mgr, public IdMixin, public ThreadObjectMixin, public TimersByNameMixin +{ + Q_OBJECT +public: + PeerMgr(const std::shared_ptr & , const std::shared_ptr &); + ~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 storage; ///< from SrvMgr, read-only, for getChain() and genesisHash() + const std::shared_ptr options; ///< from SrvMgr that creates us. + + QHash 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); diff --git a/ServerMisc.cpp b/ServerMisc.cpp new file mode 100644 index 0000000..b91047f --- /dev/null +++ b/ServerMisc.cpp @@ -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); +} diff --git a/ServerMisc.h b/ServerMisc.h new file mode 100644 index 0000000..b84d58f --- /dev/null +++ b/ServerMisc.h @@ -0,0 +1,35 @@ +// +// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash +// Copyright (C) 2019-2020 Calin A. Culianu +// +// 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 +// . +// +#pragma once + +#include "Version.h" + +#include + +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" +} + diff --git a/Servers.cpp b/Servers.cpp index 9655567..ed26529 100644 --- a/Servers.cpp +++ b/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 & opts, const std::shared_ptr &s, const std::shared_ptr &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 diff --git a/Servers.h b/Servers.h index 50846ac..4509d50 100644 --- a/Servers.h +++ b/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); diff --git a/SrvMgr.cpp b/SrvMgr.cpp index 2da184a..b3f26ec 100644 --- a/SrvMgr.cpp +++ b/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(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(&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) diff --git a/SrvMgr.h b/SrvMgr.h index 1e108f2..5a4f832 100644 --- a/SrvMgr.h +++ b/SrvMgr.h @@ -27,6 +27,7 @@ #include class BitcoinDMgr; +class PeerMgr; class Server; class Storage; @@ -70,6 +71,7 @@ private: std::shared_ptr storage; std::shared_ptr bitcoindmgr; std::list> servers; + std::unique_ptr peermgr; ///< will be nullptr if options->peerDiscovery is false QMultiHash addrIdMap; }; diff --git a/register_MetaTypes.cpp b/register_MetaTypes.cpp index ca1aa1d..295ec70 100644 --- a/register_MetaTypes.cpp +++ b/register_MetaTypes.cpp @@ -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"); + qRegisterMetaType("PeerInfo"); + qRegisterMetaType("PeerInfoList"); + registered = true; } } diff --git a/resources.qrc b/resources.qrc index b447d5f..85e31d7 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,3 +1,6 @@ - + + resources/servers.json + resources/servers_testnet.json + diff --git a/resources/servers.json b/resources/servers.json new file mode 100644 index 0000000..709e470 --- /dev/null +++ b/resources/servers.json @@ -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" + } +} diff --git a/resources/servers_testnet.json b/resources/servers_testnet.json new file mode 100644 index 0000000..8c394a3 --- /dev/null +++ b/resources/servers_testnet.json @@ -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" + } +}