diff --git a/Cargo.lock b/Cargo.lock
index 87e06e33..275da2d0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1109,6 +1109,7 @@ dependencies = [
  "serde",
  "serde_bytes",
  "serde_json",
+ "tempfile",
  "thiserror",
  "thread_local",
  "tokio",
diff --git a/binaries/cuprated/Cargo.toml b/binaries/cuprated/Cargo.toml
index 410cbb19..44c9c35a 100644
--- a/binaries/cuprated/Cargo.toml
+++ b/binaries/cuprated/Cargo.toml
@@ -78,5 +78,8 @@ tracing-appender      = { workspace = true }
 tracing-subscriber    = { workspace = true, features = ["std", "fmt", "default"] }
 tracing               = { workspace = true, features = ["default"] }
 
+[dev-dependencies]
+tempfile              = { workspace = true }
+
 [lints]
 workspace = true
diff --git a/binaries/cuprated/src/blockchain/manager.rs b/binaries/cuprated/src/blockchain/manager.rs
index 782e6efe..4e029152 100644
--- a/binaries/cuprated/src/blockchain/manager.rs
+++ b/binaries/cuprated/src/blockchain/manager.rs
@@ -33,6 +33,9 @@ use crate::{
 mod commands;
 mod handler;
 
+#[cfg(test)]
+mod tests;
+
 pub use commands::{BlockchainManagerCommand, IncomingBlockOk};
 
 /// Initialize the blockchain manager.
diff --git a/binaries/cuprated/src/blockchain/manager/handler.rs b/binaries/cuprated/src/blockchain/manager/handler.rs
index a6583844..5dab932d 100644
--- a/binaries/cuprated/src/blockchain/manager/handler.rs
+++ b/binaries/cuprated/src/blockchain/manager/handler.rs
@@ -396,7 +396,7 @@ impl super::BlockchainManager {
             .await
             .expect(PANIC_CRITICAL_SERVICE_ERROR)
             .call(BlockchainWriteRequest::PopBlocks(
-                current_main_chain_height - split_height + 1,
+                current_main_chain_height - split_height,
             ))
             .await
             .expect(PANIC_CRITICAL_SERVICE_ERROR)
@@ -409,7 +409,7 @@ impl super::BlockchainManager {
             .await
             .expect(PANIC_CRITICAL_SERVICE_ERROR)
             .call(BlockChainContextRequest::PopBlocks {
-                numb_blocks: current_main_chain_height - split_height + 1,
+                numb_blocks: current_main_chain_height - split_height,
             })
             .await
             .expect(PANIC_CRITICAL_SERVICE_ERROR);
diff --git a/binaries/cuprated/src/blockchain/manager/tests.rs b/binaries/cuprated/src/blockchain/manager/tests.rs
new file mode 100644
index 00000000..55acb5c3
--- /dev/null
+++ b/binaries/cuprated/src/blockchain/manager/tests.rs
@@ -0,0 +1,204 @@
+use std::{collections::HashMap, env::temp_dir, path::PathBuf, sync::Arc};
+
+use monero_serai::{
+    block::{Block, BlockHeader},
+    transaction::{Input, Output, Timelock, Transaction, TransactionPrefix},
+};
+use tokio::sync::{oneshot, watch};
+use tower::BoxError;
+
+use cuprate_consensus_context::{BlockchainContext, ContextConfig};
+use cuprate_consensus_rules::{hard_forks::HFInfo, miner_tx::calculate_block_reward, HFsInfo};
+use cuprate_helper::network::Network;
+use cuprate_p2p::BroadcastSvc;
+
+use crate::blockchain::{
+    check_add_genesis, manager::BlockchainManager, manager::BlockchainManagerCommand,
+    ConsensusBlockchainReadHandle,
+};
+
+async fn mock_manager(data_dir: PathBuf) -> BlockchainManager {
+    let blockchain_config = cuprate_blockchain::config::ConfigBuilder::new()
+        .data_directory(data_dir.clone())
+        .build();
+    let txpool_config = cuprate_txpool::config::ConfigBuilder::new()
+        .data_directory(data_dir)
+        .build();
+
+    let (mut blockchain_read_handle, mut blockchain_write_handle, _) =
+        cuprate_blockchain::service::init(blockchain_config).unwrap();
+    let (txpool_read_handle, txpool_write_handle, _) =
+        cuprate_txpool::service::init(txpool_config).unwrap();
+
+    check_add_genesis(
+        &mut blockchain_read_handle,
+        &mut blockchain_write_handle,
+        Network::Mainnet,
+    )
+    .await;
+
+    let mut context_config = ContextConfig::main_net();
+    context_config.difficulty_cfg.fixed_difficulty = Some(1);
+    context_config.hard_fork_cfg.info = HFsInfo::new([HFInfo::new(0, 0); 16]);
+
+    let blockchain_read_handle =
+        ConsensusBlockchainReadHandle::new(blockchain_read_handle, BoxError::from);
+
+    let blockchain_context_service = cuprate_consensus_context::initialize_blockchain_context(
+        context_config,
+        blockchain_read_handle.clone(),
+    )
+    .await
+    .unwrap();
+
+    BlockchainManager {
+        blockchain_write_handle,
+        blockchain_read_handle,
+        txpool_write_handle,
+        blockchain_context_service,
+        stop_current_block_downloader: Arc::new(Default::default()),
+        broadcast_svc: BroadcastSvc::mock(),
+    }
+}
+
+fn generate_block(context: &BlockchainContext) -> Block {
+    Block {
+        header: BlockHeader {
+            hardfork_version: 16,
+            hardfork_signal: 16,
+            timestamp: 1000,
+            previous: context.top_hash,
+            nonce: 0,
+        },
+        miner_transaction: Transaction::V2 {
+            prefix: TransactionPrefix {
+                additional_timelock: Timelock::Block(context.chain_height + 60),
+                inputs: vec![Input::Gen(context.chain_height)],
+                outputs: vec![Output {
+                    // we can set the block weight to 1 as the true value won't get us into the penalty zone.
+                    amount: Some(calculate_block_reward(
+                        1,
+                        context.median_weight_for_block_reward,
+                        context.already_generated_coins,
+                        context.current_hf,
+                    )),
+                    key: Default::default(),
+                    view_tag: Some(1),
+                }],
+                extra: rand::random::<[u8; 32]>().to_vec(),
+            },
+            proofs: None,
+        },
+        transactions: vec![],
+    }
+}
+
+#[tokio::test]
+async fn simple_reorg() {
+    // create 2 managers
+    let data_dir_1 = tempfile::tempdir().unwrap();
+    let mut manager_1 = mock_manager(data_dir_1.path().to_path_buf()).await;
+
+    let data_dir_2 = tempfile::tempdir().unwrap();
+    let mut manager_2 = mock_manager(data_dir_2.path().to_path_buf()).await;
+
+    // give both managers the same first non-genesis block
+    let block_1 = generate_block(manager_1.blockchain_context_service.blockchain_context());
+
+    manager_1
+        .handle_command(BlockchainManagerCommand::AddBlock {
+            block: block_1.clone(),
+            prepped_txs: HashMap::new(),
+            response_tx: oneshot::channel().0,
+        })
+        .await;
+
+    manager_2
+        .handle_command(BlockchainManagerCommand::AddBlock {
+            block: block_1,
+            prepped_txs: HashMap::new(),
+            response_tx: oneshot::channel().0,
+        })
+        .await;
+
+    assert_eq!(
+        manager_1.blockchain_context_service.blockchain_context(),
+        manager_2.blockchain_context_service.blockchain_context()
+    );
+
+    // give managers different 2nd block
+    let block_2a = generate_block(manager_1.blockchain_context_service.blockchain_context());
+    let block_2b = generate_block(manager_2.blockchain_context_service.blockchain_context());
+
+    manager_1
+        .handle_command(BlockchainManagerCommand::AddBlock {
+            block: block_2a,
+            prepped_txs: HashMap::new(),
+            response_tx: oneshot::channel().0,
+        })
+        .await;
+
+    manager_2
+        .handle_command(BlockchainManagerCommand::AddBlock {
+            block: block_2b.clone(),
+            prepped_txs: HashMap::new(),
+            response_tx: oneshot::channel().0,
+        })
+        .await;
+
+    let manager_1_context = manager_1
+        .blockchain_context_service
+        .blockchain_context()
+        .clone();
+    assert_ne!(
+        &manager_1_context,
+        manager_2.blockchain_context_service.blockchain_context()
+    );
+
+    // give manager 1 missing block
+
+    manager_1
+        .handle_command(BlockchainManagerCommand::AddBlock {
+            block: block_2b,
+            prepped_txs: HashMap::new(),
+            response_tx: oneshot::channel().0,
+        })
+        .await;
+    // make sure this didn't change the context
+    assert_eq!(
+        &manager_1_context,
+        manager_1.blockchain_context_service.blockchain_context()
+    );
+
+    // give both managers new block (built of manager 2's chain)
+    let block_3 = generate_block(manager_2.blockchain_context_service.blockchain_context());
+
+    manager_1
+        .handle_command(BlockchainManagerCommand::AddBlock {
+            block: block_3.clone(),
+            prepped_txs: HashMap::new(),
+            response_tx: oneshot::channel().0,
+        })
+        .await;
+
+    manager_2
+        .handle_command(BlockchainManagerCommand::AddBlock {
+            block: block_3,
+            prepped_txs: HashMap::new(),
+            response_tx: oneshot::channel().0,
+        })
+        .await;
+
+    // make sure manager 1 reorged.
+    assert_eq!(
+        manager_1.blockchain_context_service.blockchain_context(),
+        manager_2.blockchain_context_service.blockchain_context()
+    );
+    assert_eq!(
+        manager_1
+            .blockchain_context_service
+            .blockchain_context()
+            .chain_height,
+        4
+    );
+}
diff --git a/consensus/context/src/alt_chains.rs b/consensus/context/src/alt_chains.rs
index 560e1d66..f2bbc7a8 100644
--- a/consensus/context/src/alt_chains.rs
+++ b/consensus/context/src/alt_chains.rs
@@ -52,9 +52,10 @@ impl AltChainContextCache {
         block_weight: usize,
         long_term_block_weight: usize,
         timestamp: u64,
+        cumulative_difficulty: u128,
     ) {
         if let Some(difficulty_cache) = &mut self.difficulty_cache {
-            difficulty_cache.new_block(height, timestamp, difficulty_cache.cumulative_difficulty());
+            difficulty_cache.new_block(height, timestamp, cumulative_difficulty);
         }
 
         if let Some(weight_cache) = &mut self.weight_cache {
diff --git a/consensus/context/src/difficulty.rs b/consensus/context/src/difficulty.rs
index 3bbcb059..e3ec0219 100644
--- a/consensus/context/src/difficulty.rs
+++ b/consensus/context/src/difficulty.rs
@@ -36,17 +36,11 @@ pub struct DifficultyCacheConfig {
     pub window: usize,
     pub cut: usize,
     pub lag: usize,
+    /// If [`Some`] the difficulty cache will always return this value as the current difficulty.
+    pub fixed_difficulty: Option<u128>,
 }
 
 impl DifficultyCacheConfig {
-    /// Create a new difficulty cache config.
-    ///
-    /// # Notes
-    /// You probably do not need this, use [`DifficultyCacheConfig::main_net`] instead.
-    pub const fn new(window: usize, cut: usize, lag: usize) -> Self {
-        Self { window, cut, lag }
-    }
-
     /// Returns the total amount of blocks we need to track to calculate difficulty
     pub const fn total_block_count(&self) -> usize {
         self.window + self.lag
@@ -64,6 +58,7 @@ impl DifficultyCacheConfig {
             window: DIFFICULTY_WINDOW,
             cut: DIFFICULTY_CUT,
             lag: DIFFICULTY_LAG,
+            fixed_difficulty: None,
         }
     }
 }
@@ -297,6 +292,10 @@ fn next_difficulty(
     cumulative_difficulties: &VecDeque<u128>,
     hf: HardFork,
 ) -> u128 {
+    if let Some(fixed_difficulty) = config.fixed_difficulty {
+        return fixed_difficulty;
+    }
+
     if timestamps.len() <= 1 {
         return 1;
     }
diff --git a/consensus/src/block/alt_block.rs b/consensus/src/block/alt_block.rs
index 0907f88a..9143ccdb 100644
--- a/consensus/src/block/alt_block.rs
+++ b/consensus/src/block/alt_block.rs
@@ -173,6 +173,7 @@ where
         block_info.weight,
         block_info.long_term_weight,
         block_info.block.header.timestamp,
+        cumulative_difficulty,
     );
 
     // Add this alt cache back to the context service.
diff --git a/consensus/src/tests/context/difficulty.rs b/consensus/src/tests/context/difficulty.rs
index f1c0fd97..65be578e 100644
--- a/consensus/src/tests/context/difficulty.rs
+++ b/consensus/src/tests/context/difficulty.rs
@@ -17,8 +17,12 @@ const TEST_LAG: usize = 2;
 
 const TEST_TOTAL_ACCOUNTED_BLOCKS: usize = TEST_WINDOW + TEST_LAG;
 
-pub(crate) const TEST_DIFFICULTY_CONFIG: DifficultyCacheConfig =
-    DifficultyCacheConfig::new(TEST_WINDOW, TEST_CUT, TEST_LAG);
+pub(crate) const TEST_DIFFICULTY_CONFIG: DifficultyCacheConfig = DifficultyCacheConfig {
+    window: TEST_WINDOW,
+    cut: TEST_CUT,
+    lag: TEST_LAG,
+    fixed_difficulty: None,
+};
 
 #[tokio::test]
 async fn first_3_blocks_fixed_difficulty() -> Result<(), tower::BoxError> {
diff --git a/p2p/p2p/src/broadcast.rs b/p2p/p2p/src/broadcast.rs
index 07c88559..a12d7a2a 100644
--- a/p2p/p2p/src/broadcast.rs
+++ b/p2p/p2p/src/broadcast.rs
@@ -159,6 +159,13 @@ pub struct BroadcastSvc<N: NetworkZone> {
     tx_broadcast_channel_inbound: broadcast::Sender<BroadcastTxInfo<N>>,
 }
 
+impl<N: NetworkZone> BroadcastSvc<N> {
+    /// Create a mock [`BroadcastSvc`] that does nothing, useful for testing.
+    pub fn mock() -> Self {
+        init_broadcast_channels(BroadcastConfig::default()).0
+    }
+}
+
 impl<N: NetworkZone> Service<BroadcastRequest<N>> for BroadcastSvc<N> {
     type Response = ();
     type Error = std::convert::Infallible;
diff --git a/storage/blockchain/src/ops/blockchain.rs b/storage/blockchain/src/ops/blockchain.rs
index 54dd752a..35de36e3 100644
--- a/storage/blockchain/src/ops/blockchain.rs
+++ b/storage/blockchain/src/ops/blockchain.rs
@@ -4,8 +4,8 @@
 use cuprate_database::{DatabaseRo, DbResult, RuntimeError};
 
 use crate::{
-    ops::{block::block_exists, macros::doc_error},
-    tables::{BlockHeights, BlockInfos},
+    ops::{block, macros::doc_error},
+    tables::{AltBlockHeights, BlockHeights, BlockInfos},
     types::{BlockHash, BlockHeight},
 };
 
@@ -91,13 +91,21 @@ pub fn cumulative_generated_coins(
 pub fn find_split_point(
     block_ids: &[BlockHash],
     chronological_order: bool,
+    include_alt_blocks: bool,
     table_block_heights: &impl DatabaseRo<BlockHeights>,
+    table_alt_block_heights: &impl DatabaseRo<AltBlockHeights>,
 ) -> Result<usize, RuntimeError> {
     let mut err = None;
 
+    let block_exists = |block_id| {
+        block::block_exists(&block_id, table_block_heights).and_then(|exists| {
+            Ok(exists | (include_alt_blocks & table_alt_block_heights.contains(&block_id)?))
+        })
+    };
+
     // Do a binary search to find the first unknown/known block in the batch.
     let idx = block_ids.partition_point(|block_id| {
-        match block_exists(block_id, table_block_heights) {
+        match block_exists(*block_id) {
             Ok(exists) => exists == chronological_order,
             Err(e) => {
                 err.get_or_insert(e);
diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs
index c922465c..baffbd0f 100644
--- a/storage/blockchain/src/service/read.rs
+++ b/storage/blockchain/src/service/read.rs
@@ -649,9 +649,16 @@ fn next_chain_entry(
 
     let tables = env_inner.open_tables(&tx_ro)?;
     let table_block_heights = tables.block_heights();
+    let table_alt_block_heights = tables.alt_block_heights();
     let table_block_infos = tables.block_infos_iter();
 
-    let idx = find_split_point(block_ids, false, table_block_heights)?;
+    let idx = find_split_point(
+        block_ids,
+        false,
+        false,
+        table_block_heights,
+        table_alt_block_heights,
+    )?;
 
     // This will happen if we have a different genesis block.
     if idx == block_ids.len() {
@@ -712,8 +719,15 @@ fn find_first_unknown(env: &ConcreteEnv, block_ids: &[BlockHash]) -> ResponseRes
     let tx_ro = env_inner.tx_ro()?;
 
     let table_block_heights = env_inner.open_db_ro::<BlockHeights>(&tx_ro)?;
+    let table_alt_block_heights = env_inner.open_db_ro::<AltBlockHeights>(&tx_ro)?;
 
-    let idx = find_split_point(block_ids, true, &table_block_heights)?;
+    let idx = find_split_point(
+        block_ids,
+        true,
+        true,
+        &table_block_heights,
+        &table_alt_block_heights,
+    )?;
 
     Ok(if idx == block_ids.len() {
         BlockchainResponse::FindFirstUnknown(None)