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 @@
   <https://download.bitcoinabc.org/0.22.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 <serialize.h>
 #include <uint256.h>
 #include <util/time.h>
+#include <validation.h>
 
 #include <algorithm>
 
@@ -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<CBlockHeader> 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<CAddress> *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 <chainparams.h>
+#include <hash.h>
 #include <protocol.h>
 #include <seeder/bitcoin.h>
 #include <seeder/db.h>
@@ -10,6 +11,7 @@
 #include <serialize.h>
 #include <streams.h>
 #include <util/system.h>
+#include <validation.h>
 #include <version.h>
 
 #include <boost/test/unit_test.hpp>
@@ -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<CAddress> 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<CBlock> headers(numHeaders, CBlock(header));
+    payload << headers;
+    return payload;
+}
+
+BOOST_AUTO_TEST_CASE(addrs_before_checkpoint) {
+    std::vector<CAddress> 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<CAddress> 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<CAddress> 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<CAddress> 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<CAddress> 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<CAddress> 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<CAddress> 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()