From f001fd45ad55ec8b9108214d2e41b2a7973e8ba9 Mon Sep 17 00:00:00 2001
From: valued mammal <valuedmammal@protonmail.com>
Date: Sun, 15 Sep 2024 17:42:16 -0400
Subject: [PATCH] test(rpc): add bip158 tests

---
 crates/bitcoind_rpc/tests/bip158.rs | 114 ++++++++++++++++++++++++++++
 1 file changed, 114 insertions(+)
 create mode 100644 crates/bitcoind_rpc/tests/bip158.rs

diff --git a/crates/bitcoind_rpc/tests/bip158.rs b/crates/bitcoind_rpc/tests/bip158.rs
new file mode 100644
index 000000000..ed0cb0c45
--- /dev/null
+++ b/crates/bitcoind_rpc/tests/bip158.rs
@@ -0,0 +1,114 @@
+use bitcoin::{constants, Network};
+
+use bdk_bitcoind_rpc::bip158::FilterIter;
+use bdk_core::{BlockId, CheckPoint};
+use bdk_testenv::{anyhow, bitcoind, TestEnv};
+use bitcoincore_rpc::RpcApi;
+
+macro_rules! block_id {
+    ($height:expr, $hash:literal) => {{
+        BlockId {
+            height: $height,
+            hash: bdk_core::bitcoin::hashes::Hash::hash($hash.as_bytes()),
+        }
+    }};
+}
+
+// Test the result of `chain_update` given a local checkpoint.
+//
+// new blocks
+//       2--3--4--5--6--7--8--9--10--11
+//
+// case 1: base below new blocks
+// 0-
+// case 2: base overlaps with new blocks
+// 0--1--2--3--4
+// case 3: stale tip (with overlap)
+// 0--1--2--3--4--x
+// case 4: stale tip (no overlap)
+// 0--x
+#[test]
+fn get_tip_and_chain_update() -> anyhow::Result<()> {
+    let mut conf = bitcoind::Conf::default();
+    conf.args.push("-blockfilterindex=1");
+    conf.args.push("-peerblockfilters=1");
+    let env = TestEnv::new_with_config(bdk_testenv::Config {
+        bitcoind: conf,
+        ..Default::default()
+    })?;
+
+    // Start by mining ten blocks
+    let hash = env.rpc_client().get_best_block_hash()?;
+    let header = env.rpc_client().get_block_header_info(&hash)?;
+    assert_eq!(header.height, 1);
+    let block_1 = BlockId {
+        height: header.height as u32,
+        hash,
+    };
+
+    let genesis_hash = constants::genesis_block(Network::Regtest).block_hash();
+    let genesis = BlockId {
+        height: 0,
+        hash: genesis_hash,
+    };
+
+    // `FilterIter` will try to return up to ten recent blocks
+    // so we keep them for reference
+    let new_blocks: Vec<BlockId> = (2..=11)
+        .zip(env.mine_blocks(10, None)?)
+        .map(BlockId::from)
+        .collect();
+
+    struct TestCase {
+        // name
+        name: &'static str,
+        // local blocks
+        chain: Vec<BlockId>,
+        // expected blocks
+        exp: Vec<BlockId>,
+    }
+
+    // For each test we create a new `FilterIter` with the checkpoint given
+    // by the blocks in the test chain. Then we sync to the remote tip and
+    // check the blocks that are returned in the chain update.
+    [
+        TestCase {
+            name: "point of agreement below new blocks, expect base + new",
+            chain: vec![genesis, block_1],
+            exp: [block_1].into_iter().chain(new_blocks.clone()).collect(),
+        },
+        TestCase {
+            name: "point of agreement genesis, expect base + new",
+            chain: vec![genesis],
+            exp: [genesis].into_iter().chain(new_blocks.clone()).collect(),
+        },
+        TestCase {
+            name: "point of agreement within new blocks, expect base + remaining",
+            chain: new_blocks[..=2].to_vec(),
+            exp: new_blocks[2..].to_vec(),
+        },
+        TestCase {
+            name: "stale tip within new blocks, expect base + corrected + remaining",
+            // base height: 4, stale height: 5
+            chain: vec![new_blocks[2], block_id!(5, "E")],
+            exp: new_blocks[2..].to_vec(),
+        },
+        TestCase {
+            name: "stale tip below new blocks, expect base + corrected + new",
+            chain: vec![genesis, block_id!(1, "A")],
+            exp: [genesis, block_1].into_iter().chain(new_blocks).collect(),
+        },
+    ]
+    .into_iter()
+    .for_each(|test| {
+        let cp = CheckPoint::from_block_ids(test.chain).unwrap();
+        let mut it = FilterIter::new_with_checkpoint(env.rpc_client(), cp);
+        let _ = it.get_tip().unwrap();
+        let update_cp = it.chain_update().unwrap().unwrap();
+        let mut update_blocks: Vec<_> = update_cp.iter().map(|cp| cp.block_id()).collect();
+        update_blocks.reverse();
+        assert_eq!(update_blocks, test.exp, "{}", test.name);
+    });
+
+    Ok(())
+}