diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -5,3 +5,10 @@ This release includes the following features and fixes: + +# Seeder + +- Seeder now requires nodes to have the block after the most recent checkpoint + in its chain in order to be considered a good seed. +- Nodes that are undergoing IBD are not considered good seeds until IBD is + completed. diff --git a/src/seeder/bitcoin.h b/src/seeder/bitcoin.h --- a/src/seeder/bitcoin.h +++ b/src/seeder/bitcoin.h @@ -25,6 +25,13 @@ Finished, }; +enum class CheckpointState { + HasLatest, + IncompleteChain, + NotChecked, + NotFound, +}; + namespace { class CSeederNodeTest; } @@ -45,6 +52,7 @@ int ban; int64_t doneAfter; CAddress you; + CheckpointState checkpointState; int GetTimeout() { return you.IsTor() ? 120 : 30; } diff --git a/src/seeder/bitcoin.cpp b/src/seeder/bitcoin.cpp --- a/src/seeder/bitcoin.cpp +++ b/src/seeder/bitcoin.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -101,13 +102,69 @@ // ToString(you), // addr.ToString(), (int)(vAddr->size())); if (vAddr->size() > ADDR_SOFT_CAP) { - doneAfter = 1; - return PeerMessagingState::Finished; + // Seeder is finished with a node after it has received + // ADDR_SOFT_CAP addrs from a node AND after the seeder has + // checked the node's chain + if (checkpointState != CheckpointState::NotChecked) { + // Note that we do not ban until after addrs are received + // so that we can still crawl the network effectively. + if (checkpointState == CheckpointState::NotFound) { + ban = 100000; + } + doneAfter = checkpointState == CheckpointState::HasLatest; + return PeerMessagingState::Finished; + } + break; } } return PeerMessagingState::AwaitingMessages; } + // A node in the process of IBD-ing will not send this message and + // therefore will timeout waiting for the checkpoint. + if (strCommand == NetMsgType::HEADERS && vAddr) { + std::vector headers; + recv >> headers; + const CChainParams &chainParams = Params(); + doneAfter = 0; + checkpointState = CheckpointState::NotFound; + + if (headers.size() > MAX_HEADERS_RESULTS) { + tfm::format(std::cout, + "%s: Misbehaving: oversized HEADERS message\n", + ToString(you)); + ban = 100000; + return PeerMessagingState::Finished; + } + + if (headers.size() == 0) { + // This is an ambiguous situation where either the peer only has + // the genesis block or has exactly up to the checkpoint. Either + // way, the peer should be marked bad but not banned. + checkpointState = CheckpointState::IncompleteChain; + } else if (chainParams.Checkpoints().mapCheckpoints.rbegin()->second == + headers[0].hashPrevBlock) { + // The hashPrevBlock of the first header received should be the hash + // of the checkpoint if the node is following the correct chain. + doneAfter = GetTime() + GetTimeout(); + checkpointState = CheckpointState::HasLatest; + } + + // Seeder is finished with a node after it has received ADDR_SOFT_CAP + // addrs from a node AND after the seeder has checked the node's chain + if (vAddr->size() > ADDR_SOFT_CAP) { + // Note that we do not ban until after addrs are received + // so that we can still crawl the network effectively. + if (checkpointState == CheckpointState::NotFound) { + ban = 100000; + } + doneAfter = checkpointState == CheckpointState::HasLatest; + return PeerMessagingState::Finished; + } + + return PeerMessagingState::AwaitingMessages; + } + return PeerMessagingState::AwaitingMessages; } @@ -175,7 +232,8 @@ 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)) { + doneAfter(0), you(ip, ServiceFlags(NODE_NETWORK)), + checkpointState(CheckpointState::NotChecked) { if (GetTime() > 1329696000) { vSend.SetVersion(209); vRecv.SetVersion(209); @@ -280,5 +338,5 @@ } close(sock); sock = INVALID_SOCKET; - return (ban == 0) && res; + return (ban == 0) && res && (checkpointState == CheckpointState::HasLatest); } diff --git a/src/seeder/test/p2p_messaging_tests.cpp b/src/seeder/test/p2p_messaging_tests.cpp --- a/src/seeder/test/p2p_messaging_tests.cpp +++ b/src/seeder/test/p2p_messaging_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #include @@ -63,7 +65,7 @@ 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); + CDataStream payload(SER_NETWORK, PROTOCOL_VERSION); payload.SetVersion(nVersion); ServiceFlags serviceflags = ServiceFlags(NODE_NETWORK); payload << nVersion << uint64_t(serviceflags) << now << addrTo << addrFrom @@ -91,7 +93,7 @@ } BOOST_AUTO_TEST_CASE(process_verack_msg) { - CDataStream verackMessage(SER_NETWORK, 0); + CDataStream verackMessage(SER_NETWORK, PROTOCOL_VERSION); verackMessage.SetVersion(INIT_PROTO_VERSION); testNode->TestProcessMessage(NetMsgType::VERACK, verackMessage, PeerMessagingState::AwaitingMessages); @@ -120,7 +122,7 @@ static CDataStream CreateAddrMessage(std::vector sendAddrs, uint32_t nVersion = INIT_PROTO_VERSION) { - CDataStream payload(SER_NETWORK, 0); + CDataStream payload(SER_NETWORK, PROTOCOL_VERSION); payload.SetVersion(nVersion); payload << sendAddrs; return payload; @@ -143,7 +145,7 @@ sendAddrs.resize(1); addrMessage = CreateAddrMessage(sendAddrs); testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, - PeerMessagingState::Finished); + PeerMessagingState::AwaitingMessages); BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); // Test the seeder's behavior after ADDR_SOFT_CAP addrs @@ -153,10 +155,177 @@ sendAddrs.resize(i, sendAddrs[0]); addrMessage = CreateAddrMessage(sendAddrs); testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, - PeerMessagingState::Finished); + PeerMessagingState::AwaitingMessages); BOOST_CHECK_EQUAL(expectedSize, vAddr.size()); ++expectedSize; } } +static CDataStream CreateHeadersMessage(int numHeaders = 1) { + CDataStream payload(SER_NETWORK, PROTOCOL_VERSION); + + BlockHash previousBlockHash = + Params().Checkpoints().mapCheckpoints.rbegin()->second; + + // Treat negative numHeaders as the wrong chain + if (numHeaders < 0) { + numHeaders *= -1; + previousBlockHash = BlockHash(); + } + + CBlockHeader header; + header.hashPrevBlock = previousBlockHash; + std::vector headers(numHeaders, CBlock(header)); + payload << headers; + return payload; +} + +BOOST_AUTO_TEST_CASE(addrs_before_checkpoint) { + std::vector sendAddrs(ADDR_SOFT_CAP, vAddr[0]); + CDataStream addrMessage = CreateAddrMessage(sendAddrs); + BOOST_CHECK_EQUAL(1, vAddr.size()); + testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, + PeerMessagingState::AwaitingMessages); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); + + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + CDataStream headersMessage = CreateHeadersMessage(); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); +} + +BOOST_AUTO_TEST_CASE(wrong_chain_before_addrs) { + // Happy path except the addrs come after the headers from the wrong chain + CDataStream headersMessage = CreateHeadersMessage(-1); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::AwaitingMessages); + + // Banning should not occur until after addrs are finished being received. + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + + std::vector sendAddrs(ADDR_SOFT_CAP, vAddr[0]); + CDataStream addrMessage = CreateAddrMessage(sendAddrs); + BOOST_CHECK_EQUAL(1, vAddr.size()); + testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + +BOOST_AUTO_TEST_CASE(headers_wrong_chain_after_addrs) { + // Seeder receives a HEADERS message obviously following the wrong chain + // This type of node should be banned after filling vAddr. + std::vector sendAddrs(ADDR_SOFT_CAP, vAddr[0]); + CDataStream addrMessage = CreateAddrMessage(sendAddrs); + BOOST_CHECK_EQUAL(1, vAddr.size()); + testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, + PeerMessagingState::AwaitingMessages); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); + + // Banning should not occur until after addrs are finished being received. + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + CDataStream headersMessage = CreateHeadersMessage(-1); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + +BOOST_AUTO_TEST_CASE(too_many_headers) { + // Seeder receives a HEADERS message with too many headers + // This is a misbehaving node and should be banned immediately. + CDataStream headersMessage = CreateHeadersMessage(MAX_HEADERS_RESULTS + 1); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + +BOOST_AUTO_TEST_CASE(empty_headers_test) { + /** + * Seeder receives an empty HEADERS message after filling vAddr + * Peer node did not have the checkpoint block requested by the seeder. + * Peer only has up to the checkpoint or only has the genesis block. + * Since there is no way to tell the difference do not ban. + */ + std::vector sendAddrs(ADDR_SOFT_CAP, vAddr[0]); + CDataStream addrMessage = CreateAddrMessage(sendAddrs); + BOOST_CHECK_EQUAL(1, vAddr.size()); + testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, + PeerMessagingState::AwaitingMessages); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); + + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + CDataStream headersMessage = CreateHeadersMessage(0); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); +} + +BOOST_AUTO_TEST_CASE(checkpoint_before_addrs) { + CDataStream headersMessage = CreateHeadersMessage(); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::AwaitingMessages); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + + std::vector sendAddrs(ADDR_SOFT_CAP, vAddr[0]); + CDataStream addrMessage = CreateAddrMessage(sendAddrs); + BOOST_CHECK_EQUAL(1, vAddr.size()); + testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); +} + +BOOST_AUTO_TEST_CASE(addrs_before_and_after_checkpoint) { + // Addrs come before and after the headers message. The first addr does not + // fill vAddr, so the seeder should wait for more addrs before it is + // finished with the node + std::vector sendAddrs(ADDR_SOFT_CAP - 1, vAddr[0]); + 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()); + + CDataStream headersMessage = CreateHeadersMessage(); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::AwaitingMessages); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + + sendAddrs.resize(1); + addrMessage = CreateAddrMessage(sendAddrs); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP, vAddr.size()); + testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); +} + +BOOST_AUTO_TEST_CASE(addrs_before_and_after_wrong_chain) { + // Addrs come before and after the headers message. The first addr does not + // fill vAddr, so the seeder should wait for more addrs before it is + // finished with the node. The node is on the wrong chain + std::vector sendAddrs(ADDR_SOFT_CAP - 1, vAddr[0]); + 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()); + + // Banning should not occur until after addrs are finished being received. + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + CDataStream headersMessage = CreateHeadersMessage(-1); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::AwaitingMessages); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); + + sendAddrs.resize(1); + addrMessage = CreateAddrMessage(sendAddrs); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP, vAddr.size()); + testNode->TestProcessMessage(NetMsgType::ADDR, addrMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(ADDR_SOFT_CAP + 1, vAddr.size()); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + BOOST_AUTO_TEST_SUITE_END()