diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -3,3 +3,9 @@ This release includes the following features and fixes: +Seeder +------ + - Seeder now requires nodes to have the most recent checkpoint in its chain + in order to be considered a good seed. + - Nodes that do not have the most recent checkpoint in its chain are + temporally banned. diff --git a/src/seeder/bitcoin.cpp b/src/seeder/bitcoin.cpp --- a/src/seeder/bitcoin.cpp +++ b/src/seeder/bitcoin.cpp @@ -113,18 +113,67 @@ 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; - } + // Create a getheaders message + BeginMessage("getheaders"); + // Locator hash; Seeder requests the headers after the latest checkpoint + uint256 seederCheckpoint = + Params().Checkpoints().mapCheckpoints.rbegin()->second; + std::vector hashStop(32, 0); + uint8_t numberOfLocatorHashes = 1; + vSend << PROTOCOL_VERSION << numberOfLocatorHashes << seederCheckpoint + << uint256(hashStop); + EndMessage(); + // Update socket timeout + doneAfter = time(nullptr) + GetTimeout(); return false; } + if (strCommand == NetMsgType::HEADERS) { + uint64_t nHeaders = ReadCompactSize(recv); + if (nHeaders == 0) { + // fprintf(stderr, + //"Peer (%s) did not find seeder checkpoint, banned.\n", + // ToString(you).c_str()); + ban = 100000; + return true; + } + CBlockHeader header; + recv >> header; + /** + * If the node has this checkpoint then it will return the first 2000 + * block headers it knows that come after the checkpoint. This means + * the hashPrevBlock of the first header received should be the hash of + * the checkpoint if the node is following the correct chain. + */ + if (Params().Checkpoints().mapCheckpoints.rbegin()->second == + header.hashPrevBlock) { + // fprintf(stderr, "Peer (%s) found seeder checkpoint.\n", + // ToString(you).c_str()); + // 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; + } + + /** + * Conversely, if hasPrevBlock is not the checkpoint, then the node is + * not following the correct chain and should be banned. + * This should probably be a longer lasting ban in the future. + */ + // fprintf(stderr, + //"Peer (%s) returned a header the does not come after the " + //"checkpoint and is therefore on the wrong chain, banned.\n", + // ToString(you).c_str()); + ban = 100000; + return true; + } + if (strCommand == "addr" && vAddr) { std::vector vAddrNew; recv >> vAddrNew; 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 @@ -5,6 +5,7 @@ #define BOOST_TEST_MODULE Bitcoin Seeder Test Suite #include +#include #include #include #include @@ -134,4 +135,70 @@ } } +static const uint8_t SEND_WRONG_CHAIN_HEADER = 0; +static const uint8_t SEND_GOOD_HEADER = 1; +static const uint8_t SEND_NO_HEADERS = 2; +static const uint8_t HEADERS_TXN_COUNT = 0; + +static CDataStream CreateHeadersMessage(uint8_t headersMessageContent, + int32_t nVersion = INIT_PROTO_VERSION, + uint32_t timeStamp = 0, + uint32_t bits = 0, uint32_t nonce = 0) { + CDataStream payload(SER_NETWORK, 0); + payload.SetVersion(nVersion); + uint8_t numberOfHeaders = headersMessageContent == SEND_NO_HEADERS ? 0 : 1; + payload << numberOfHeaders; + if (headersMessageContent == SEND_NO_HEADERS) { + return payload; + } + // The actual header can be garbage data, Seeder only cares that it got + // a specific type of message, the size, and the hash of the previous block. + uint256 merkleRoot = Hash(payload.begin(), payload.end()); + uint256 previousBlockHash; + if (headersMessageContent == SEND_WRONG_CHAIN_HEADER) { + previousBlockHash = Hash(payload.begin(), payload.end()); + } else { + previousBlockHash = + Params().Checkpoints().mapCheckpoints.rbegin()->second; + } + payload << nVersion; + // prev_block + payload << previousBlockHash; + // merkle_root + payload << merkleRoot; + // timestamp + payload << timeStamp; + // bits + payload << bits; + // nonce + payload << nonce; + // txn_count + payload << HEADERS_TXN_COUNT; + return payload; +} + +BOOST_AUTO_TEST_CASE(seeder_node_headers_test_happy_path) { + // Happy path + CDataStream headersMessage = CreateHeadersMessage(SEND_GOOD_HEADER); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::AwaitingMessages); + BOOST_CHECK_EQUAL(testNode->GetBan(), 0); +} + +BOOST_AUTO_TEST_CASE(seeder_node_headers_wrong_chain_test) { + // Seeder receives a HEADERS message following the wrong chain + CDataStream headersMessage = CreateHeadersMessage(SEND_WRONG_CHAIN_HEADER); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + +BOOST_AUTO_TEST_CASE(seeder_node_empty_headers_test) { + // Seeder receives an empty HEADERS message + CDataStream headersMessage = CreateHeadersMessage(SEND_NO_HEADERS); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + BOOST_AUTO_TEST_SUITE_END()