diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -14,3 +14,10 @@ - See the release notes from v0.20.6 for a full description of the changes from the 'account' API to the 'label' API. + +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.h b/src/seeder/bitcoin.h --- a/src/seeder/bitcoin.h +++ b/src/seeder/bitcoin.h @@ -16,6 +16,8 @@ return Params().GetDefaultPort(); } +static const unsigned int MAX_HEADERS_PER_MESSAGE = 2000; + // The network magic to use. extern CMessageHeader::MessageMagic netMagic; diff --git a/src/seeder/bitcoin.cpp b/src/seeder/bitcoin.cpp --- a/src/seeder/bitcoin.cpp +++ b/src/seeder/bitcoin.cpp @@ -114,18 +114,62 @@ 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 + BlockHash 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 PeerMessagingState::AwaitingMessages; } + if (strCommand == NetMsgType::HEADERS) { + uint64_t nHeaders = ReadCompactSize(recv); + // Peer node did not find the checkpoint requested by the seeder + if (nHeaders == 0) { + ban = 100000; + return PeerMessagingState::Finished; + } + // Peer node sent a malformed headers message + if (nHeaders > MAX_HEADERS_PER_MESSAGE) { + ban = 100000; + return PeerMessagingState::Finished; + } + CBlockHeader header; + recv >> header; + /** + * If the node has this checkpoint then it will return the first + * MAX_HEADERS_PER_MESSAGE 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) { + if (vAddr) { + BeginMessage("getaddr"); + EndMessage(); + doneAfter = time(nullptr) + GetTimeout(); + } else { + doneAfter = time(nullptr) + 1; + } + return PeerMessagingState::AwaitingMessages; + } + + /** + * Conversely, if hashPrevBlock is not the checkpoint, then the node is + * not following the correct chain and should be banned. + */ + ban = 100000; + return PeerMessagingState::Finished; + } + 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 @@ -139,4 +140,83 @@ } } +enum HeaderMessageType { + WRONG_CHAIN_HEADER, + GOOD_HEADER, + NO_HEADERS, + TOO_MANY_HEADERS, +}; + +static CDataStream CreateHeadersMessage(HeaderMessageType headersMessageContent, + int32_t nVersion = INIT_PROTO_VERSION, + uint32_t timeStamp = 0, + uint32_t bits = 0, uint32_t nonce = 0) { + CDataStream payload(SER_NETWORK, nVersion); + if (headersMessageContent == HeaderMessageType::NO_HEADERS) { + payload << 0; + return payload; + } + uint16_t numberOfHeaders = 1; + if (headersMessageContent == HeaderMessageType::TOO_MANY_HEADERS) { + numberOfHeaders = MAX_HEADERS_PER_MESSAGE + 1; + } + // 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 garbageHash = Hash(payload.begin(), payload.end()); + uint256 merkleRoot = garbageHash; + BlockHash previousBlockHash = + headersMessageContent == HeaderMessageType::WRONG_CHAIN_HEADER + ? BlockHash(garbageHash) + : Params().Checkpoints().mapCheckpoints.rbegin()->second; + + CBlockHeader header; + header.nVersion = nVersion; + header.hashPrevBlock = previousBlockHash; + header.hashMerkleRoot = merkleRoot; + header.nTime = timeStamp; + header.nBits = bits; + header.nNonce = nonce; + // we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx + // count at the end + std::vector headers(numberOfHeaders, CBlock(header)); + payload << headers; + return payload; +} + +BOOST_AUTO_TEST_CASE(seeder_node_headers_test_happy_path) { + // Happy path + CDataStream headersMessage = + CreateHeadersMessage(HeaderMessageType::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(HeaderMessageType::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(HeaderMessageType::NO_HEADERS); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + +BOOST_AUTO_TEST_CASE(seeder_node_too_many_headers_test) { + // Seeder receives a HEADERS message with too many headers + CDataStream headersMessage = + CreateHeadersMessage(HeaderMessageType::TOO_MANY_HEADERS); + testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage, + PeerMessagingState::Finished); + BOOST_CHECK_EQUAL(testNode->GetBan(), 100000); +} + BOOST_AUTO_TEST_SUITE_END()