diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -505,6 +505,7 @@
 	banman.cpp
 	blockencodings.cpp
 	blockfilter.cpp
+	blockindex.cpp
 	chain.cpp
 	checkpoints.cpp
 	config.cpp
diff --git a/src/Makefile.am b/src/Makefile.am
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -127,6 +127,7 @@
   blockencodings.h \
   blockfileinfo.h \
   blockfilter.h \
+  blockindex.h \
   blockindexworkcomparator.h \
   blockstatus.h \
   blockvalidity.h \
@@ -309,6 +310,7 @@
   banman.cpp \
   blockencodings.cpp \
   blockfilter.cpp \
+  blockindex.cpp \
   chain.cpp \
   checkpoints.cpp \
   config.cpp \
diff --git a/src/avalanche/test/processor_tests.cpp b/src/avalanche/test/processor_tests.cpp
--- a/src/avalanche/test/processor_tests.cpp
+++ b/src/avalanche/test/processor_tests.cpp
@@ -6,6 +6,7 @@
 
 #include <avalanche/peermanager.h>
 #include <avalanche/test/util.h>
+#include <chain.h>
 #include <config.h>
 #include <net_processing.h> // For PeerLogicValidation
 #include <util/time.h>
diff --git a/src/blockindex.h b/src/blockindex.h
new file mode 100644
--- /dev/null
+++ b/src/blockindex.h
@@ -0,0 +1,212 @@
+// Copyright (c) 2009-2020 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_BLOCKINDEX_H
+#define BITCOIN_BLOCKINDEX_H
+
+#include <arith_uint256.h>
+#include <blockstatus.h>
+#include <flatfile.h>
+#include <primitives/block.h>
+#include <tinyformat.h>
+#include <uint256.h>
+
+struct BlockHash;
+
+/**
+ * The block chain is a tree shaped structure starting with the genesis block at
+ * the root, with each block potentially having multiple candidates to be the
+ * next block. A blockindex may have multiple pprev pointing to it, but at most
+ * one of them can be part of the currently active branch.
+ */
+class CBlockIndex {
+public:
+    //! pointer to the hash of the block, if any. Memory is owned by this
+    //! CBlockIndex
+    const BlockHash *phashBlock = nullptr;
+
+    //! pointer to the index of the predecessor of this block
+    CBlockIndex *pprev = nullptr;
+
+    //! pointer to the index of some further predecessor of this block
+    CBlockIndex *pskip = nullptr;
+
+    //! height of the entry in the chain. The genesis block has height 0
+    int nHeight = 0;
+
+    //! Which # file this block is stored in (blk?????.dat)
+    int nFile = 0;
+
+    //! Byte offset within blk?????.dat where this block's data is stored
+    unsigned int nDataPos = 0;
+
+    //! Byte offset within rev?????.dat where this block's undo data is stored
+    unsigned int nUndoPos = 0;
+
+    //! (memory only) Total amount of work (expected number of hashes) in the
+    //! chain up to and including this block
+    arith_uint256 nChainWork = arith_uint256();
+
+    //! Number of transactions in this block.
+    //! Note: in a potential headers-first mode, this number cannot be relied
+    //! upon
+    unsigned int nTx = 0;
+
+private:
+    //! (memory only) Number of transactions in the chain up to and including
+    //! this block.
+    //! This value will be non-zero only if and only if transactions for this
+    //! block and all its parents are available. Change to 64-bit type when
+    //! necessary; won't happen before 2030
+    unsigned int nChainTx = 0;
+
+public:
+    //! Verification status of this block. See enum BlockStatus
+    BlockStatus nStatus = BlockStatus();
+
+    //! block header
+    int32_t nVersion = 0;
+    uint256 hashMerkleRoot = uint256();
+    uint32_t nTime = 0;
+    uint32_t nBits = 0;
+    uint32_t nNonce = 0;
+
+    //! (memory only) Sequential id assigned to distinguish order in which
+    //! blocks are received.
+    int32_t nSequenceId = 0;
+
+    //! (memory only) block header metadata
+    uint64_t nTimeReceived = 0;
+
+    //! (memory only) Maximum nTime in the chain up to and including this block.
+    unsigned int nTimeMax = 0;
+
+    explicit CBlockIndex() = default;
+
+    explicit CBlockIndex(const CBlockHeader &block) : CBlockIndex() {
+        nVersion = block.nVersion;
+        hashMerkleRoot = block.hashMerkleRoot;
+        nTime = block.nTime;
+        nTimeReceived = 0;
+        nBits = block.nBits;
+        nNonce = block.nNonce;
+    }
+
+    FlatFilePos GetBlockPos() const {
+        FlatFilePos ret;
+        if (nStatus.hasData()) {
+            ret.nFile = nFile;
+            ret.nPos = nDataPos;
+        }
+        return ret;
+    }
+
+    FlatFilePos GetUndoPos() const {
+        FlatFilePos ret;
+        if (nStatus.hasUndo()) {
+            ret.nFile = nFile;
+            ret.nPos = nUndoPos;
+        }
+        return ret;
+    }
+
+    CBlockHeader GetBlockHeader() const {
+        CBlockHeader block;
+        block.nVersion = nVersion;
+        if (pprev) {
+            block.hashPrevBlock = pprev->GetBlockHash();
+        }
+        block.hashMerkleRoot = hashMerkleRoot;
+        block.nTime = nTime;
+        block.nBits = nBits;
+        block.nNonce = nNonce;
+        return block;
+    }
+
+    BlockHash GetBlockHash() const { return *phashBlock; }
+
+    /**
+     * Get the number of transaction in the chain so far.
+     */
+    int64_t GetChainTxCount() const { return nChainTx; }
+
+    /**
+     * Update chain tx stats.
+     */
+    bool UpdateChainStats();
+
+    /**
+     * Check whether this block's and all previous blocks' transactions have
+     * been downloaded (and stored to disk) at some point.
+     *
+     * Does not imply the transactions are consensus-valid (ConnectTip might
+     * fail) Does not imply the transactions are still stored on disk.
+     * (IsBlockPruned might return true)
+     */
+    bool HaveTxsDownloaded() const { return GetChainTxCount() != 0; }
+
+    int64_t GetBlockTime() const { return int64_t(nTime); }
+
+    int64_t GetBlockTimeMax() const { return int64_t(nTimeMax); }
+
+    int64_t GetHeaderReceivedTime() const { return nTimeReceived; }
+
+    int64_t GetReceivedTimeDiff() const {
+        return GetHeaderReceivedTime() - GetBlockTime();
+    }
+
+    static constexpr int nMedianTimeSpan = 11;
+
+    int64_t GetMedianTimePast() const {
+        int64_t pmedian[nMedianTimeSpan];
+        int64_t *pbegin = &pmedian[nMedianTimeSpan];
+        int64_t *pend = &pmedian[nMedianTimeSpan];
+
+        const CBlockIndex *pindex = this;
+        for (int i = 0; i < nMedianTimeSpan && pindex;
+             i++, pindex = pindex->pprev) {
+            *(--pbegin) = pindex->GetBlockTime();
+        }
+
+        std::sort(pbegin, pend);
+        return pbegin[(pend - pbegin) / 2];
+    }
+
+    std::string ToString() const {
+        return strprintf(
+            "CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)", pprev,
+            nHeight, hashMerkleRoot.ToString(), GetBlockHash().ToString());
+    }
+
+    //! Check whether this block index entry is valid up to the passed validity
+    //! level.
+    bool IsValid(enum BlockValidity nUpTo = BlockValidity::TRANSACTIONS) const {
+        return nStatus.isValid(nUpTo);
+    }
+
+    //! Raise the validity level of this block index entry.
+    //! Returns true if the validity was changed.
+    bool RaiseValidity(enum BlockValidity nUpTo) {
+        // Only validity flags allowed.
+        if (nStatus.isInvalid()) {
+            return false;
+        }
+
+        if (nStatus.getValidity() >= nUpTo) {
+            return false;
+        }
+
+        nStatus = nStatus.withValidity(nUpTo);
+        return true;
+    }
+
+    //! Build the skiplist pointer for this entry.
+    void BuildSkip();
+
+    //! Efficiently find an ancestor of this block.
+    CBlockIndex *GetAncestor(int height);
+    const CBlockIndex *GetAncestor(int height) const;
+};
+
+#endif // BITCOIN_BLOCKINDEX_H
diff --git a/src/blockindex.cpp b/src/blockindex.cpp
new file mode 100644
--- /dev/null
+++ b/src/blockindex.cpp
@@ -0,0 +1,77 @@
+// Copyright (c) 2009-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 <blockindex.h>
+
+/**
+ * Turn the lowest '1' bit in the binary representation of a number into a '0'.
+ */
+static inline int InvertLowestOne(int n) {
+    return n & (n - 1);
+}
+
+/** Compute what height to jump back to with the CBlockIndex::pskip pointer. */
+static inline int GetSkipHeight(int height) {
+    if (height < 2) {
+        return 0;
+    }
+
+    // Determine which height to jump back to. Any number strictly lower than
+    // height is acceptable, but the following expression seems to perform well
+    // in simulations (max 110 steps to go back up to 2**18 blocks).
+    return (height & 1) ? InvertLowestOne(InvertLowestOne(height - 1)) + 1
+                        : InvertLowestOne(height);
+}
+
+bool CBlockIndex::UpdateChainStats() {
+    if (pprev == nullptr) {
+        nChainTx = nTx;
+        return true;
+    }
+
+    if (pprev->HaveTxsDownloaded()) {
+        nChainTx = pprev->nChainTx + nTx;
+        return true;
+    }
+
+    nChainTx = 0;
+    return false;
+}
+
+const CBlockIndex *CBlockIndex::GetAncestor(int height) const {
+    if (height > nHeight || height < 0) {
+        return nullptr;
+    }
+
+    const CBlockIndex *pindexWalk = this;
+    int heightWalk = nHeight;
+    while (heightWalk > height) {
+        int heightSkip = GetSkipHeight(heightWalk);
+        int heightSkipPrev = GetSkipHeight(heightWalk - 1);
+        if (pindexWalk->pskip != nullptr &&
+            (heightSkip == height ||
+             (heightSkip > height && !(heightSkipPrev < heightSkip - 2 &&
+                                       heightSkipPrev >= height)))) {
+            // Only follow pskip if pprev->pskip isn't better than pskip->pprev.
+            pindexWalk = pindexWalk->pskip;
+            heightWalk = heightSkip;
+        } else {
+            assert(pindexWalk->pprev);
+            pindexWalk = pindexWalk->pprev;
+            heightWalk--;
+        }
+    }
+    return pindexWalk;
+}
+
+CBlockIndex *CBlockIndex::GetAncestor(int height) {
+    return const_cast<CBlockIndex *>(
+        const_cast<const CBlockIndex *>(this)->GetAncestor(height));
+}
+
+void CBlockIndex::BuildSkip() {
+    if (pprev) {
+        pskip = pprev->GetAncestor(GetSkipHeight(nHeight));
+    }
+}
diff --git a/src/blockindexworkcomparator.h b/src/blockindexworkcomparator.h
--- a/src/blockindexworkcomparator.h
+++ b/src/blockindexworkcomparator.h
@@ -5,8 +5,7 @@
 #ifndef BITCOIN_BLOCKINDEXWORKCOMPARATOR_H
 #define BITCOIN_BLOCKINDEXWORKCOMPARATOR_H
 
-// TODO: Split chain.h apart and only include CBlockIndex
-#include <chain.h>
+#include <blockindex.h>
 
 struct CBlockIndexWorkComparator {
     bool operator()(const CBlockIndex *pa, const CBlockIndex *pb) const {
diff --git a/src/chain.h b/src/chain.h
--- a/src/chain.h
+++ b/src/chain.h
@@ -7,6 +7,7 @@
 #define BITCOIN_CHAIN_H
 
 #include <arith_uint256.h>
+#include <blockindex.h>
 #include <blockstatus.h>
 #include <blockvalidity.h>
 #include <consensus/params.h>
@@ -42,201 +43,6 @@
  */
 static constexpr int64_t MAX_BLOCK_TIME_GAP = 90 * 60;
 
-/**
- * The block chain is a tree shaped structure starting with the genesis block at
- * the root, with each block potentially having multiple candidates to be the
- * next block. A blockindex may have multiple pprev pointing to it, but at most
- * one of them can be part of the currently active branch.
- */
-class CBlockIndex {
-public:
-    //! pointer to the hash of the block, if any. Memory is owned by this
-    //! CBlockIndex
-    const BlockHash *phashBlock = nullptr;
-
-    //! pointer to the index of the predecessor of this block
-    CBlockIndex *pprev = nullptr;
-
-    //! pointer to the index of some further predecessor of this block
-    CBlockIndex *pskip = nullptr;
-
-    //! height of the entry in the chain. The genesis block has height 0
-    int nHeight = 0;
-
-    //! Which # file this block is stored in (blk?????.dat)
-    int nFile = 0;
-
-    //! Byte offset within blk?????.dat where this block's data is stored
-    unsigned int nDataPos = 0;
-
-    //! Byte offset within rev?????.dat where this block's undo data is stored
-    unsigned int nUndoPos = 0;
-
-    //! (memory only) Total amount of work (expected number of hashes) in the
-    //! chain up to and including this block
-    arith_uint256 nChainWork = arith_uint256();
-
-    //! Number of transactions in this block.
-    //! Note: in a potential headers-first mode, this number cannot be relied
-    //! upon
-    unsigned int nTx = 0;
-
-private:
-    //! (memory only) Number of transactions in the chain up to and including
-    //! this block.
-    //! This value will be non-zero only if and only if transactions for this
-    //! block and all its parents are available. Change to 64-bit type when
-    //! necessary; won't happen before 2030
-    unsigned int nChainTx = 0;
-
-public:
-    //! Verification status of this block. See enum BlockStatus
-    BlockStatus nStatus = BlockStatus();
-
-    //! block header
-    int32_t nVersion = 0;
-    uint256 hashMerkleRoot = uint256();
-    uint32_t nTime = 0;
-    uint32_t nBits = 0;
-    uint32_t nNonce = 0;
-
-    //! (memory only) Sequential id assigned to distinguish order in which
-    //! blocks are received.
-    int32_t nSequenceId = 0;
-
-    //! (memory only) block header metadata
-    uint64_t nTimeReceived = 0;
-
-    //! (memory only) Maximum nTime in the chain up to and including this block.
-    unsigned int nTimeMax = 0;
-
-    explicit CBlockIndex() = default;
-
-    explicit CBlockIndex(const CBlockHeader &block) : CBlockIndex() {
-        nVersion = block.nVersion;
-        hashMerkleRoot = block.hashMerkleRoot;
-        nTime = block.nTime;
-        nTimeReceived = 0;
-        nBits = block.nBits;
-        nNonce = block.nNonce;
-    }
-
-    FlatFilePos GetBlockPos() const {
-        FlatFilePos ret;
-        if (nStatus.hasData()) {
-            ret.nFile = nFile;
-            ret.nPos = nDataPos;
-        }
-        return ret;
-    }
-
-    FlatFilePos GetUndoPos() const {
-        FlatFilePos ret;
-        if (nStatus.hasUndo()) {
-            ret.nFile = nFile;
-            ret.nPos = nUndoPos;
-        }
-        return ret;
-    }
-
-    CBlockHeader GetBlockHeader() const {
-        CBlockHeader block;
-        block.nVersion = nVersion;
-        if (pprev) {
-            block.hashPrevBlock = pprev->GetBlockHash();
-        }
-        block.hashMerkleRoot = hashMerkleRoot;
-        block.nTime = nTime;
-        block.nBits = nBits;
-        block.nNonce = nNonce;
-        return block;
-    }
-
-    BlockHash GetBlockHash() const { return *phashBlock; }
-
-    /**
-     * Get the number of transaction in the chain so far.
-     */
-    int64_t GetChainTxCount() const { return nChainTx; }
-
-    /**
-     * Update chain tx stats.
-     */
-    bool UpdateChainStats();
-
-    /**
-     * Check whether this block's and all previous blocks' transactions have
-     * been downloaded (and stored to disk) at some point.
-     *
-     * Does not imply the transactions are consensus-valid (ConnectTip might
-     * fail) Does not imply the transactions are still stored on disk.
-     * (IsBlockPruned might return true)
-     */
-    bool HaveTxsDownloaded() const { return GetChainTxCount() != 0; }
-
-    int64_t GetBlockTime() const { return int64_t(nTime); }
-
-    int64_t GetBlockTimeMax() const { return int64_t(nTimeMax); }
-
-    int64_t GetHeaderReceivedTime() const { return nTimeReceived; }
-
-    int64_t GetReceivedTimeDiff() const {
-        return GetHeaderReceivedTime() - GetBlockTime();
-    }
-
-    static constexpr int nMedianTimeSpan = 11;
-
-    int64_t GetMedianTimePast() const {
-        int64_t pmedian[nMedianTimeSpan];
-        int64_t *pbegin = &pmedian[nMedianTimeSpan];
-        int64_t *pend = &pmedian[nMedianTimeSpan];
-
-        const CBlockIndex *pindex = this;
-        for (int i = 0; i < nMedianTimeSpan && pindex;
-             i++, pindex = pindex->pprev) {
-            *(--pbegin) = pindex->GetBlockTime();
-        }
-
-        std::sort(pbegin, pend);
-        return pbegin[(pend - pbegin) / 2];
-    }
-
-    std::string ToString() const {
-        return strprintf(
-            "CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)", pprev,
-            nHeight, hashMerkleRoot.ToString(), GetBlockHash().ToString());
-    }
-
-    //! Check whether this block index entry is valid up to the passed validity
-    //! level.
-    bool IsValid(enum BlockValidity nUpTo = BlockValidity::TRANSACTIONS) const {
-        return nStatus.isValid(nUpTo);
-    }
-
-    //! Raise the validity level of this block index entry.
-    //! Returns true if the validity was changed.
-    bool RaiseValidity(enum BlockValidity nUpTo) {
-        // Only validity flags allowed.
-        if (nStatus.isInvalid()) {
-            return false;
-        }
-
-        if (nStatus.getValidity() >= nUpTo) {
-            return false;
-        }
-
-        nStatus = nStatus.withValidity(nUpTo);
-        return true;
-    }
-
-    //! Build the skiplist pointer for this entry.
-    void BuildSkip();
-
-    //! Efficiently find an ancestor of this block.
-    CBlockIndex *GetAncestor(int height);
-    const CBlockIndex *GetAncestor(int height) const;
-};
-
 /**
  * Maintain a map of CBlockIndex for all known headers.
  */
diff --git a/src/chain.cpp b/src/chain.cpp
--- a/src/chain.cpp
+++ b/src/chain.cpp
@@ -77,78 +77,6 @@
     return (lower == vChain.end() ? nullptr : *lower);
 }
 
-bool CBlockIndex::UpdateChainStats() {
-    if (pprev == nullptr) {
-        nChainTx = nTx;
-        return true;
-    }
-
-    if (pprev->HaveTxsDownloaded()) {
-        nChainTx = pprev->nChainTx + nTx;
-        return true;
-    }
-
-    nChainTx = 0;
-    return false;
-}
-
-/**
- * Turn the lowest '1' bit in the binary representation of a number into a '0'.
- */
-static inline int InvertLowestOne(int n) {
-    return n & (n - 1);
-}
-
-/** Compute what height to jump back to with the CBlockIndex::pskip pointer. */
-static inline int GetSkipHeight(int height) {
-    if (height < 2) {
-        return 0;
-    }
-
-    // Determine which height to jump back to. Any number strictly lower than
-    // height is acceptable, but the following expression seems to perform well
-    // in simulations (max 110 steps to go back up to 2**18 blocks).
-    return (height & 1) ? InvertLowestOne(InvertLowestOne(height - 1)) + 1
-                        : InvertLowestOne(height);
-}
-
-const CBlockIndex *CBlockIndex::GetAncestor(int height) const {
-    if (height > nHeight || height < 0) {
-        return nullptr;
-    }
-
-    const CBlockIndex *pindexWalk = this;
-    int heightWalk = nHeight;
-    while (heightWalk > height) {
-        int heightSkip = GetSkipHeight(heightWalk);
-        int heightSkipPrev = GetSkipHeight(heightWalk - 1);
-        if (pindexWalk->pskip != nullptr &&
-            (heightSkip == height ||
-             (heightSkip > height && !(heightSkipPrev < heightSkip - 2 &&
-                                       heightSkipPrev >= height)))) {
-            // Only follow pskip if pprev->pskip isn't better than pskip->pprev.
-            pindexWalk = pindexWalk->pskip;
-            heightWalk = heightSkip;
-        } else {
-            assert(pindexWalk->pprev);
-            pindexWalk = pindexWalk->pprev;
-            heightWalk--;
-        }
-    }
-    return pindexWalk;
-}
-
-CBlockIndex *CBlockIndex::GetAncestor(int height) {
-    return const_cast<CBlockIndex *>(
-        const_cast<const CBlockIndex *>(this)->GetAncestor(height));
-}
-
-void CBlockIndex::BuildSkip() {
-    if (pprev) {
-        pskip = pprev->GetAncestor(GetSkipHeight(nHeight));
-    }
-}
-
 arith_uint256 GetBlockProof(const CBlockIndex &block) {
     arith_uint256 bnTarget;
     bool fNegative;