diff --git a/src/seeder/bitcoin.cpp b/src/seeder/bitcoin.cpp index ec3c7e9fc..afceb7253 100644 --- a/src/seeder/bitcoin.cpp +++ b/src/seeder/bitcoin.cpp @@ -1,278 +1,284 @@ // Copyright (c) 2017-2020 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 #include #include #include #include #define BITCOIN_SEED_NONCE 0x0539a019ca550825ULL 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; } } PeerMessagingState CSeederNode::ProcessMessage(std::string strCommand, CDataStream &recv) { // tfm::format(std::cout, "%s: RECV %s\n", ToString(you), // strCommand); if (strCommand == NetMsgType::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; vSend.SetVersion(std::min(nVersion, PROTOCOL_VERSION)); MessageWriter::WriteMessage(vSend, NetMsgType::VERACK); return PeerMessagingState::AwaitingMessages; } if (strCommand == NetMsgType::VERACK) { vRecv.SetVersion(std::min(nVersion, PROTOCOL_VERSION)); // tfm::format(std::cout, "\n%s: version %i\n", ToString(you), // nVersion); if (vAddr) { MessageWriter::WriteMessage(vSend, NetMsgType::GETADDR); + std::vector locatorHash( + 1, Params().Checkpoints().mapCheckpoints.rbegin()->second); + MessageWriter::WriteMessage(vSend, NetMsgType::GETHEADERS, + CBlockLocator(locatorHash), uint256()); doneAfter = GetTime() + GetTimeout(); } else { doneAfter = GetTime() + 1; } return PeerMessagingState::AwaitingMessages; } if (strCommand == NetMsgType::ADDR && vAddr) { std::vector vAddrNew; recv >> vAddrNew; // tfm::format(std::cout, "%s: got %i addresses\n", // ToString(you), // (int)vAddrNew.size()); int64_t now = GetTime(); 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; // tfm::format(std::cout, "%s: got address %s\n", // ToString(you), // addr.ToString(), (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); } // tfm::format(std::cout, "%s: added address %s (#%i)\n", // ToString(you), // addr.ToString(), (int)(vAddr->size())); if (vAddr->size() > ADDR_SOFT_CAP) { doneAfter = 1; return PeerMessagingState::Finished; } } return PeerMessagingState::AwaitingMessages; } return PeerMessagingState::AwaitingMessages; } bool CSeederNode::ProcessMessages() { if (vRecv.empty()) { return false; } const CMessageHeader::MessageMagic netMagic = Params().NetMagic(); 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)) { // tfm::format(std::cout, "%s: BAD (invalid header)\n", // ToString(you)); ban = 100000; return true; } std::string strCommand = hdr.GetCommand(); unsigned int nMessageSize = hdr.nMessageSize; if (nMessageSize > MAX_SIZE) { // tfm::format(std::cout, "%s: BAD (message too large)\n", // ToString(you)); 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) == PeerMessagingState::Finished) { return true; } // tfm::format(std::cout, "%s: done processing %s\n", // ToString(you), // strCommand); } 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)) { if (GetTime() > 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, false); } } if (!connected) { // tfm::format(std::cout, "Cannot connect to %s\n", // ToString(you)); CloseSocket(sock); return false; } // Write version message uint64_t nLocalServices = 0; uint64_t nLocalNonce = BITCOIN_SEED_NONCE; CService myService; CAddress me(myService, ServiceFlags(NODE_NETWORK)); std::string ver = "/bitcoin-cash-seeder:0.15/"; MessageWriter::WriteMessage(vSend, NetMsgType::VERSION, PROTOCOL_VERSION, nLocalServices, GetTime(), you, me, nLocalNonce, ver, GetRequireHeight()); Send(); bool res = true; int64_t now; while (now = GetTime(), 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) { // tfm::format(std::cout, "%s: BAD (connection closed // prematurely)\n", // ToString(you)); res = false; break; } else { // tfm::format(std::cout, "%s: BAD (connection error)\n", // ToString(you)); res = false; break; } ProcessMessages(); Send(); } if (sock == INVALID_SOCKET) { res = false; } close(sock); sock = INVALID_SOCKET; return (ban == 0) && res; } diff --git a/src/seeder/test/p2p_messaging_tests.cpp b/src/seeder/test/p2p_messaging_tests.cpp index cecc9f754..6a6dc05ab 100644 --- a/src/seeder/test/p2p_messaging_tests.cpp +++ b/src/seeder/test/p2p_messaging_tests.cpp @@ -1,148 +1,162 @@ // 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. #include #include #include #include #include #include #include #include #include #include #include #include #include #include std::ostream &operator<<(std::ostream &os, const PeerMessagingState &state) { os << to_integral(state); return os; } namespace { class CSeederNodeTest : public CSeederNode { public: CSeederNodeTest(const CService &service, std::vector *vAddrIn) : CSeederNode(service, vAddrIn) {} void TestProcessMessage(const std::string &strCommand, CDataStream &message, PeerMessagingState expectedState) { PeerMessagingState ret = ProcessMessage(strCommand, message); BOOST_CHECK_EQUAL(ret, expectedState); } CDataStream getSendBuffer() { return vSend; } }; } // namespace static const unsigned short SERVICE_PORT = 18444; struct SeederTestingSetup { SeederTestingSetup() { SelectParams(CBaseChainParams::REGTEST); 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(process_version_msg) { CService serviceFrom; CAddress addrFrom(serviceFrom, ServiceFlags(NODE_NETWORK)); CDataStream versionMessage = CreateVersionMessage( GetTime(), 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()); } BOOST_AUTO_TEST_CASE(process_verack_msg) { CDataStream verackMessage(SER_NETWORK, 0); verackMessage.SetVersion(INIT_PROTO_VERSION); testNode->TestProcessMessage(NetMsgType::VERACK, verackMessage, PeerMessagingState::AwaitingMessages); // Seeder should respond with an ADDR message const CMessageHeader::MessageMagic netMagic = Params().NetMagic(); CMessageHeader header(netMagic); - testNode->getSendBuffer() >> header; + CDataStream sendBuffer = testNode->getSendBuffer(); + sendBuffer >> header; BOOST_CHECK(header.IsValidWithoutConfig(netMagic)); BOOST_CHECK_EQUAL(header.GetCommand(), NetMsgType::GETADDR); + + // Next message should be GETHEADERS + sendBuffer >> header; + BOOST_CHECK(header.IsValidWithoutConfig(netMagic)); + BOOST_CHECK_EQUAL(header.GetCommand(), NetMsgType::GETHEADERS); + + CBlockLocator locator; + uint256 hashStop; + sendBuffer >> locator >> hashStop; + std::vector expectedLocator = { + Params().Checkpoints().mapCheckpoints.rbegin()->second}; + BOOST_CHECK(locator.vHave == expectedLocator); + BOOST_CHECK(hashStop == uint256()); } static CDataStream CreateAddrMessage(std::vector sendAddrs, uint32_t nVersion = INIT_PROTO_VERSION) { CDataStream payload(SER_NETWORK, 0); payload.SetVersion(nVersion); payload << sendAddrs; return payload; } BOOST_AUTO_TEST_CASE(process_addr_msg) { // 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()