diff --git a/src/seeder/bitcoin.cpp b/src/seeder/bitcoin.cpp index d2da9d8d9a..1f58da56a8 100644 --- a/src/seeder/bitcoin.cpp +++ b/src/seeder/bitcoin.cpp @@ -1,347 +1,348 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include // The network magic to use. CMessageHeader::MessageMagic netMagic = {{0xe3, 0xe1, 0xf3, 0xe8}}; #define BITCOIN_SEED_NONCE 0x0539a019ca550825ULL static const uint32_t allones(-1); void CSeederNode::BeginMessage(const char *pszCommand) { if (nHeaderStart != allones) { AbortMessage(); } nHeaderStart = vSend.size(); vSend << CMessageHeader(netMagic, pszCommand, 0); nMessageStart = vSend.size(); // fprintf(stdout, "%s: SEND %s\n", ToString(you).c_str(), pszCommand); } void CSeederNode::AbortMessage() { if (nHeaderStart == allones) { return; } vSend.resize(nHeaderStart); nHeaderStart = allones; nMessageStart = allones; } void CSeederNode::EndMessage() { if (nHeaderStart == allones) { return; } uint32_t nSize = vSend.size() - nMessageStart; memcpy((char *)&vSend[nHeaderStart] + offsetof(CMessageHeader, nMessageSize), &nSize, sizeof(nSize)); if (vSend.GetVersion() >= 209) { uint256 hash = Hash(vSend.begin() + nMessageStart, vSend.end()); unsigned int nChecksum = 0; memcpy(&nChecksum, &hash, sizeof(nChecksum)); assert(nMessageStart - nHeaderStart >= offsetof(CMessageHeader, pchChecksum) + sizeof(nChecksum)); memcpy((char *)&vSend[nHeaderStart] + offsetof(CMessageHeader, pchChecksum), &nChecksum, sizeof(nChecksum)); } nHeaderStart = allones; nMessageStart = allones; } void CSeederNode::Send() { if (sock == INVALID_SOCKET) { return; } if (vSend.empty()) { return; } int nBytes = send(sock, &vSend[0], vSend.size(), 0); if (nBytes > 0) { vSend.erase(vSend.begin(), vSend.begin() + nBytes); } else { close(sock); sock = INVALID_SOCKET; } } void CSeederNode::PushVersion() { int64_t nTime = time(nullptr); uint64_t nLocalNonce = BITCOIN_SEED_NONCE; int64_t nLocalServices = 0; CService myService; CAddress me(myService, ServiceFlags(NODE_NETWORK | NODE_BITCOIN_CASH)); BeginMessage("version"); int nBestHeight = GetRequireHeight(); std::string ver = "/bitcoin-cash-seeder:0.15/"; vSend << PROTOCOL_VERSION << nLocalServices << nTime << you << me << nLocalNonce << ver << nBestHeight; EndMessage(); } -bool CSeederNode::ProcessMessage(std::string strCommand, CDataStream &recv) { +PeerMessagingState CSeederNode::ProcessMessage(std::string strCommand, + CDataStream &recv) { // fprintf(stdout, "%s: RECV %s\n", ToString(you).c_str(), // strCommand.c_str()); if (strCommand == "version") { int64_t nTime; CAddress addrMe; CAddress addrFrom; uint64_t nNonce = 1; uint64_t nServiceInt; recv >> nVersion >> nServiceInt >> nTime >> addrMe; you.nServices = ServiceFlags(nServiceInt); recv >> addrFrom >> nNonce; recv >> strSubVer; recv >> nStartingHeight; BeginMessage("verack"); EndMessage(); vSend.SetVersion(std::min(nVersion, PROTOCOL_VERSION)); - return false; + return PeerMessagingState::AwaitingMessages; } if (strCommand == "verack") { vRecv.SetVersion(std::min(nVersion, PROTOCOL_VERSION)); // fprintf(stdout, "\n%s: version %i\n", ToString(you).c_str(), // nVersion); if (vAddr) { BeginMessage("getaddr"); EndMessage(); doneAfter = time(nullptr) + GetTimeout(); } else { doneAfter = time(nullptr) + 1; } - return false; + return PeerMessagingState::AwaitingMessages; } if (strCommand == "addr" && vAddr) { std::vector vAddrNew; recv >> vAddrNew; // fprintf(stdout, "%s: got %i addresses\n", ToString(you).c_str(), // (int)vAddrNew.size()); int64_t now = time(nullptr); std::vector::iterator it = vAddrNew.begin(); if (vAddrNew.size() > 1) { if (doneAfter == 0 || doneAfter > now + 1) { doneAfter = now + 1; } } while (it != vAddrNew.end()) { CAddress &addr = *it; // fprintf(stdout, "%s: got address %s\n", // ToString(you).c_str(), // addr.ToString().c_str(), (int)(vAddr->size())); it++; if (addr.nTime <= 100000000 || addr.nTime > now + 600) { addr.nTime = now - 5 * 86400; } if (addr.nTime > now - 604800) { vAddr->push_back(addr); } // fprintf(stdout, "%s: added address %s (#%i)\n", // ToString(you).c_str(), // addr.ToString().c_str(), (int)(vAddr->size())); if (vAddr->size() > 1000) { doneAfter = 1; - return true; + return PeerMessagingState::Finished; } } - return false; + return PeerMessagingState::AwaitingMessages; } - return false; + return PeerMessagingState::AwaitingMessages; } bool CSeederNode::ProcessMessages() { if (vRecv.empty()) { return false; } do { CDataStream::iterator pstart = std::search( vRecv.begin(), vRecv.end(), BEGIN(netMagic), END(netMagic)); uint32_t nHeaderSize = GetSerializeSize(CMessageHeader(netMagic), vRecv.GetVersion()); if (vRecv.end() - pstart < nHeaderSize) { if (vRecv.size() > nHeaderSize) { vRecv.erase(vRecv.begin(), vRecv.end() - nHeaderSize); } break; } vRecv.erase(vRecv.begin(), pstart); std::vector vHeaderSave(vRecv.begin(), vRecv.begin() + nHeaderSize); CMessageHeader hdr(netMagic); vRecv >> hdr; if (!hdr.IsValidWithoutConfig(netMagic)) { // fprintf(stdout, "%s: BAD (invalid header)\n", // ToString(you).c_str()); ban = 100000; return true; } std::string strCommand = hdr.GetCommand(); unsigned int nMessageSize = hdr.nMessageSize; if (nMessageSize > MAX_SIZE) { // fprintf(stdout, "%s: BAD (message too large)\n", // ToString(you).c_str()); ban = 100000; return true; } if (nMessageSize > vRecv.size()) { vRecv.insert(vRecv.begin(), vHeaderSave.begin(), vHeaderSave.end()); break; } if (vRecv.GetVersion() >= 209) { uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize); if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { continue; } } CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.GetType(), vRecv.GetVersion()); vRecv.ignore(nMessageSize); - if (ProcessMessage(strCommand, vMsg)) { + if (ProcessMessage(strCommand, vMsg) == PeerMessagingState::Finished) { return true; } // fprintf(stdout, "%s: done processing %s\n", // ToString(you).c_str(), // strCommand.c_str()); } while (1); return false; } CSeederNode::CSeederNode(const CService &ip, std::vector *vAddrIn) : sock(INVALID_SOCKET), vSend(SER_NETWORK, 0), vRecv(SER_NETWORK, 0), nHeaderStart(-1), nMessageStart(-1), nVersion(0), vAddr(vAddrIn), ban(0), doneAfter(0), you(ip, ServiceFlags(NODE_NETWORK | NODE_BITCOIN_CASH)) { if (time(nullptr) > 1329696000) { vSend.SetVersion(209); vRecv.SetVersion(209); } } bool CSeederNode::Run() { // FIXME: This logic is duplicated with CConnman::ConnectNode for no // good reason. bool connected = false; proxyType proxy; if (you.IsValid()) { bool proxyConnectionFailed = false; if (GetProxy(you.GetNetwork(), proxy)) { sock = CreateSocket(proxy.proxy); if (sock == INVALID_SOCKET) { return false; } connected = ConnectThroughProxy( proxy, you.ToStringIP(), you.GetPort(), sock, nConnectTimeout, &proxyConnectionFailed); } else { // no proxy needed (none set for target network) sock = CreateSocket(you); if (sock == INVALID_SOCKET) { return false; } // no proxy needed (none set for target network) connected = ConnectSocketDirectly(you, sock, nConnectTimeout, true); } } if (!connected) { // fprintf(stdout, "Cannot connect to %s\n", ToString(you).c_str()); CloseSocket(sock); return false; } PushVersion(); Send(); bool res = true; int64_t now; while (now = time(nullptr), ban == 0 && (doneAfter == 0 || doneAfter > now) && sock != INVALID_SOCKET) { char pchBuf[0x10000]; fd_set fdsetRecv; fd_set fdsetError; FD_ZERO(&fdsetRecv); FD_ZERO(&fdsetError); FD_SET(sock, &fdsetRecv); FD_SET(sock, &fdsetError); struct timeval wa; if (doneAfter) { wa.tv_sec = doneAfter - now; wa.tv_usec = 0; } else { wa.tv_sec = GetTimeout(); wa.tv_usec = 0; } int ret = select(sock + 1, &fdsetRecv, nullptr, &fdsetError, &wa); if (ret != 1) { if (!doneAfter) { res = false; } break; } int nBytes = recv(sock, pchBuf, sizeof(pchBuf), 0); int nPos = vRecv.size(); if (nBytes > 0) { vRecv.resize(nPos + nBytes); memcpy(&vRecv[nPos], pchBuf, nBytes); } else if (nBytes == 0) { // fprintf(stdout, "%s: BAD (connection closed prematurely)\n", // ToString(you).c_str()); res = false; break; } else { // fprintf(stdout, "%s: BAD (connection error)\n", // ToString(you).c_str()); res = false; break; } ProcessMessages(); Send(); } if (sock == INVALID_SOCKET) { res = false; } close(sock); sock = INVALID_SOCKET; return (ban == 0) && res; } bool TestNode(const CService &cip, int &ban, int &clientV, std::string &clientSV, int &blocks, std::vector *vAddr) { try { CSeederNode node(cip, vAddr); bool ret = node.Run(); if (!ret) { ban = node.GetBan(); } else { ban = 0; } clientV = node.GetClientVersion(); clientSV = node.GetClientSubVersion(); blocks = node.GetStartingHeight(); // fprintf(stdout, "%s: %s!!!\n", cip.ToString().c_str(), ret ? "GOOD" : // "BAD"); return ret; } catch (std::ios_base::failure &e) { ban = 0; return false; } } diff --git a/src/seeder/bitcoin.h b/src/seeder/bitcoin.h index e35b2cd3ee..2536783b08 100644 --- a/src/seeder/bitcoin.h +++ b/src/seeder/bitcoin.h @@ -1,71 +1,77 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SEEDER_BITCOIN_H #define BITCOIN_SEEDER_BITCOIN_H #include #include #include #include #include static inline unsigned short GetDefaultPort() { return Params().GetDefaultPort(); } // The network magic to use. extern CMessageHeader::MessageMagic netMagic; +enum PeerMessagingState : bool { + AwaitingMessages = false, + Finished = true, +}; + class CSeederNode { private: SOCKET sock; CDataStream vSend; CDataStream vRecv; uint32_t nHeaderStart; uint32_t nMessageStart; int nVersion; std::string strSubVer; int nStartingHeight; std::vector *vAddr; int ban; int64_t doneAfter; CAddress you; int GetTimeout() { return you.IsTor() ? 120 : 30; } void BeginMessage(const char *pszCommand); void AbortMessage(); void EndMessage(); void Send(); void PushVersion(); bool ProcessMessages(); protected: - bool ProcessMessage(std::string strCommand, CDataStream &recv); + PeerMessagingState ProcessMessage(std::string strCommand, + CDataStream &recv); public: CSeederNode(const CService &ip, std::vector *vAddrIn); bool Run(); int GetBan() { return ban; } int GetClientVersion() { return nVersion; } std::string GetClientSubVersion() { return strSubVer; } int GetStartingHeight() { return nStartingHeight; } }; bool TestNode(const CService &cip, int &ban, int &client, std::string &clientSV, int &blocks, std::vector *vAddr); #endif // BITCOIN_SEEDER_BITCOIN_H diff --git a/src/seeder/test/p2p_messaging_tests.cpp b/src/seeder/test/p2p_messaging_tests.cpp index ce3d68c8f0..a8c75d617d 100644 --- a/src/seeder/test/p2p_messaging_tests.cpp +++ b/src/seeder/test/p2p_messaging_tests.cpp @@ -1,139 +1,135 @@ // Copyright (c) 2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #define BOOST_TEST_MODULE Bitcoin Seeder Test Suite #include #include #include #include #include #include #include #include #include #include #include const std::function G_TRANSLATION_FUN = nullptr; #include -enum PeerMessagingState : bool { - Finished = true, - AwaitingMessages = false, -}; - class TestCSeederNode : public CSeederNode { public: TestCSeederNode(const CService &service, std::vector *vAddrIn) : CSeederNode(service, vAddrIn) { SelectParams(CBaseChainParams::REGTEST); } void TestProcessMessage(const std::string &strCommand, CDataStream &message, - PeerMessagingState state) { - bool ret = CSeederNode::ProcessMessage(strCommand, message); - BOOST_CHECK_EQUAL(ret, bool(state)); + PeerMessagingState expectedState) { + PeerMessagingState ret = + CSeederNode::ProcessMessage(strCommand, message); + BOOST_CHECK_EQUAL(ret, expectedState); } }; static const unsigned short SERVICE_PORT = 18444; struct SeederTestingSetup { SeederTestingSetup() { CNetAddr ip; ip.SetInternal("bitcoin.test"); CService service = {ip, SERVICE_PORT}; vAddr.emplace_back(service, ServiceFlags()); testNode = std::make_unique(service, &vAddr); } std::vector vAddr; std::unique_ptr testNode; }; BOOST_FIXTURE_TEST_SUITE(p2p_messaging_tests, SeederTestingSetup) static CDataStream CreateVersionMessage(int64_t now, CAddress addrTo, CAddress addrFrom, int32_t start_height, uint32_t nVersion, uint64_t nonce = 0, std::string user_agent = "/bitcoin-cash-seeder:0.15/") { CDataStream payload(SER_NETWORK, 0); payload.SetVersion(nVersion); ServiceFlags serviceflags = ServiceFlags(NODE_NETWORK); payload << nVersion << uint64_t(serviceflags) << now << addrTo << addrFrom << nonce << user_agent << start_height; return payload; } static const int SEEDER_INIT_VERSION = 0; BOOST_AUTO_TEST_CASE(seeder_node_version_test) { CService serviceFrom; CAddress addrFrom(serviceFrom, ServiceFlags(NODE_NETWORK | NODE_BITCOIN_CASH)); CDataStream versionMessage = CreateVersionMessage(time(nullptr), vAddr[0], addrFrom, GetRequireHeight(), INIT_PROTO_VERSION); // Verify the version is set as the initial value BOOST_CHECK_EQUAL(testNode->CSeederNode::GetClientVersion(), SEEDER_INIT_VERSION); testNode->TestProcessMessage(NetMsgType::VERSION, versionMessage, PeerMessagingState::AwaitingMessages); // Verify the version has been updated BOOST_CHECK_EQUAL(testNode->CSeederNode::GetClientVersion(), versionMessage.GetVersion()); } static CDataStream CreateAddrMessage(std::vector sendAddrs, uint32_t nVersion = INIT_PROTO_VERSION) { CDataStream payload(SER_NETWORK, 0); payload.SetVersion(nVersion); payload << sendAddrs; return payload; } // After the 1000th addr, the seeder will only add one more address per addr // message. static const int ADDR_SOFT_CAP = 1000; BOOST_AUTO_TEST_CASE(seeder_node_addr_test) { // vAddrs starts with 1 entry. std::vector sendAddrs(ADDR_SOFT_CAP - 1, vAddr[0]); // Happy path // addrs are added normally to vAddr until ADDR_SOFT_CAP is reached. // Add addrs up to the soft cap. CDataStream addrMessage = CreateAddrMessage(sendAddrs); BOOST_CHECK_EQUAL(1, vAddr.size()); testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, PeerMessagingState::AwaitingMessages); BOOST_CHECK_EQUAL(ADDR_SOFT_CAP, vAddr.size()); // ADDR_SOFT_CAP is exceeded sendAddrs.resize(1); addrMessage = CreateAddrMessage(sendAddrs); testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, PeerMessagingState::Finished); BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); // Test the seeder's behavior after ADDR_SOFT_CAP addrs // Only one addr per ADDR message will be added, the rest are ignored size_t expectedSize = vAddr.size() + 1; for (size_t i = 1; i < 10; i++) { sendAddrs.resize(i, sendAddrs[0]); addrMessage = CreateAddrMessage(sendAddrs); testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, PeerMessagingState::Finished); BOOST_CHECK_EQUAL(expectedSize, vAddr.size()); ++expectedSize; } } BOOST_AUTO_TEST_SUITE_END()