]> Untitled Git - bdk/commitdiff
`Blockchain` stop_gap testing improvements
author志宇 <hello@evanlinjin.me>
Mon, 4 Jul 2022 12:37:21 +0000 (20:37 +0800)
committer志宇 <hello@evanlinjin.me>
Mon, 4 Jul 2022 23:53:19 +0000 (07:53 +0800)
This is a continuation of the #651 fix. We should also check whether the
same bug affects esplora as noted by @afilini. To achieve this, I've
introduced a `ConfigurableBlockchainTester` trait that can test multiple
blockchain implementations.

* Introduce `ConfigurableBlockchainTester` trait.
* Use the aforementioned trait to also test esplora.
* Change the electrum test to also use the new trait.
* Fix some complaints by clippy in ureq.rs file (why is CI not seeing
  this?).
* Refactor some code.

src/blockchain/electrum.rs
src/blockchain/esplora/mod.rs
src/blockchain/esplora/ureq.rs
src/testutils/configurable_blockchain_tests.rs [new file with mode: 0644]
src/testutils/mod.rs

index 784218e7268c07a5c677ce657b632325de9f046b..b71390cb917b6bd5e398d341b4aaf3cc812b69c8 100644 (file)
@@ -147,19 +147,12 @@ impl WalletSync for ElectrumBlockchain {
 
                 Request::Conftime(conftime_req) => {
                     // collect up to chunk_size heights to fetch from electrum
-                    let needs_block_height = {
-                        let mut needs_block_height = HashSet::with_capacity(chunk_size);
-                        conftime_req
-                            .request()
-                            .filter_map(|txid| txid_to_height.get(txid).cloned())
-                            .filter(|height| block_times.get(height).is_none())
-                            .take(chunk_size)
-                            .for_each(|height| {
-                                needs_block_height.insert(height);
-                            });
-
-                        needs_block_height
-                    };
+                    let needs_block_height = conftime_req
+                        .request()
+                        .filter_map(|txid| txid_to_height.get(txid).cloned())
+                        .filter(|height| block_times.get(height).is_none())
+                        .take(chunk_size)
+                        .collect::<HashSet<u32>>();
 
                     let new_block_headers = self
                         .client
@@ -329,6 +322,7 @@ mod test {
     use super::*;
     use crate::database::MemoryDatabase;
     use crate::testutils::blockchain_tests::TestClient;
+    use crate::testutils::configurable_blockchain_tests::ConfigurableBlockchainTester;
     use crate::wallet::{AddressIndex, Wallet};
 
     crate::bdk_blockchain_tests! {
@@ -388,114 +382,27 @@ mod test {
     }
 
     #[test]
-    fn test_electrum_blockchain_factory_sync_with_stop_gaps() {
-        // Test whether Electrum blockchain syncs with expected behaviour given different `stop_gap`
-        // parameters.
-        //
-        // For each test vector:
-        // * Fill wallet's derived addresses with balances (as specified by test vector).
-        //    * [0..addrs_before]          => 1000sats for each address
-        //    * [addrs_before..actual_gap] => empty addresses
-        //    * [actual_gap..addrs_after]  => 1000sats for each address
-        // * Then, perform wallet sync and obtain wallet balance
-        // * Check balance is within expected range (we can compare `stop_gap` and `actual_gap` to
-        //    determine this).
-
-        // Generates wallet descriptor
-        let descriptor_of_account = |account_index: usize| -> String {
-            format!("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/{account_index}/*)")
-        };
-
-        // Amount (in satoshis) provided to a single address (which expects to have a balance)
-        const AMOUNT_PER_TX: u64 = 1000;
-
-        // [stop_gap, actual_gap, addrs_before, addrs_after]
-        //
-        // [0]     stop_gap: Passed to [`ElectrumBlockchainConfig`]
-        // [1]   actual_gap: Range size of address indexes without a balance
-        // [2] addrs_before: Range size of address indexes (before gap) which contains a balance
-        // [3]  addrs_after: Range size of address indexes (after gap) which contains a balance
-        let test_vectors: Vec<[u64; 4]> = vec![
-            [0, 0, 0, 5],
-            [0, 0, 5, 5],
-            [0, 1, 5, 5],
-            [0, 2, 5, 5],
-            [1, 0, 5, 5],
-            [1, 1, 5, 5],
-            [1, 2, 5, 5],
-            [2, 1, 5, 5],
-            [2, 2, 5, 5],
-            [2, 3, 5, 5],
-        ];
-
-        let mut test_client = TestClient::default();
-
-        for (account_index, vector) in test_vectors.into_iter().enumerate() {
-            let [stop_gap, actual_gap, addrs_before, addrs_after] = vector;
-            let descriptor = descriptor_of_account(account_index);
-
-            let factory = Arc::new(
-                ElectrumBlockchain::from_config(&ElectrumBlockchainConfig {
+    fn test_electrum_with_variable_configs() {
+        struct ElectrumTester;
+
+        impl ConfigurableBlockchainTester<ElectrumBlockchain> for ElectrumTester {
+            const BLOCKCHAIN_NAME: &'static str = "Electrum";
+
+            fn config_with_stop_gap(
+                &self,
+                test_client: &mut TestClient,
+                stop_gap: usize,
+            ) -> Option<ElectrumBlockchainConfig> {
+                Some(ElectrumBlockchainConfig {
                     url: test_client.electrsd.electrum_url.clone(),
                     socks5: None,
                     retry: 0,
                     timeout: None,
-                    stop_gap: stop_gap as _,
+                    stop_gap: stop_gap,
                 })
-                .unwrap(),
-            );
-            let wallet = Wallet::new(
-                descriptor.as_str(),
-                None,
-                bitcoin::Network::Regtest,
-                MemoryDatabase::new(),
-            )
-            .unwrap();
-
-            // fill server-side with txs to specified address indexes
-            // return the max balance of the wallet (also the actual balance)
-            let max_balance = (0..addrs_before)
-                .chain(addrs_before + actual_gap..addrs_before + actual_gap + addrs_after)
-                .fold(0_u64, |sum, i| {
-                    let address = wallet.get_address(AddressIndex::Peek(i as _)).unwrap();
-                    let tx = testutils! {
-                        @tx ( (@addr address.address) => AMOUNT_PER_TX )
-                    };
-                    test_client.receive(tx);
-                    sum + AMOUNT_PER_TX
-                });
-
-            // generate blocks to confirm new transactions
-            test_client.generate(3, None);
-
-            // minimum allowed balance of wallet (based on stop gap)
-            let min_balance = if actual_gap > stop_gap {
-                addrs_before * AMOUNT_PER_TX
-            } else {
-                max_balance
-            };
-
-            // perform wallet sync
-            factory
-                .sync_wallet(&wallet, None, Default::default())
-                .unwrap();
-
-            let wallet_balance = wallet.get_balance().unwrap();
-
-            let details = format!(
-                "test_vector: [stop_gap: {}, actual_gap: {}, addrs_before: {}, addrs_after: {}]",
-                stop_gap, actual_gap, addrs_before, addrs_after,
-            );
-            assert!(
-                wallet_balance <= max_balance,
-                "wallet balance is greater than received amount: {}",
-                details
-            );
-            assert!(
-                wallet_balance >= min_balance,
-                "wallet balance is smaller than expected: {}",
-                details
-            );
+            }
         }
+
+        ElectrumTester.run();
     }
 }
index 83c5c3ddd89192d75fba755263df1548f9dbc100..30d29d6477e679257460587da9bbdd1fdc7da652 100644 (file)
@@ -209,4 +209,38 @@ mod test {
             "should inherit from value for 25"
         );
     }
+
+    #[test]
+    #[cfg(feature = "test-esplora")]
+    fn test_esplora_with_variable_configs() {
+        use crate::testutils::{
+            blockchain_tests::TestClient,
+            configurable_blockchain_tests::ConfigurableBlockchainTester,
+        };
+
+        struct EsploraTester;
+
+        impl ConfigurableBlockchainTester<EsploraBlockchain> for EsploraTester {
+            const BLOCKCHAIN_NAME: &'static str = "Esplora";
+
+            fn config_with_stop_gap(
+                &self,
+                test_client: &mut TestClient,
+                stop_gap: usize,
+            ) -> Option<EsploraBlockchainConfig> {
+                Some(EsploraBlockchainConfig {
+                    base_url: format!(
+                        "http://{}",
+                        test_client.electrsd.esplora_url.as_ref().unwrap()
+                    ),
+                    proxy: None,
+                    concurrency: None,
+                    stop_gap: stop_gap,
+                    timeout: None,
+                })
+            }
+        }
+
+        EsploraTester.run();
+    }
 }
index 50493f9cb353eca01a908199a264101f1f5d966c..7ce57a138e1a5b94b007575a814d369f6cecc10c 100644 (file)
@@ -88,7 +88,7 @@ impl Blockchain for EsploraBlockchain {
     }
 
     fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        let _txid = self.url_client._broadcast(tx)?;
+        self.url_client._broadcast(tx)?;
         Ok(())
     }
 
diff --git a/src/testutils/configurable_blockchain_tests.rs b/src/testutils/configurable_blockchain_tests.rs
new file mode 100644 (file)
index 0000000..a39608b
--- /dev/null
@@ -0,0 +1,140 @@
+use bitcoin::Network;
+
+use crate::{
+    blockchain::ConfigurableBlockchain, database::MemoryDatabase, testutils, wallet::AddressIndex,
+    Wallet,
+};
+
+use super::blockchain_tests::TestClient;
+
+/// Trait for testing [`ConfigurableBlockchain`] implementations.
+pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
+    /// Blockchain name for logging.
+    const BLOCKCHAIN_NAME: &'static str;
+
+    /// Generates a blockchain config with a given stop_gap.
+    ///
+    /// If this returns [`Option::None`], then the associated tests will not run.
+    fn config_with_stop_gap(
+        &self,
+        _test_client: &mut TestClient,
+        _stop_gap: usize,
+    ) -> Option<B::Config> {
+        None
+    }
+
+    /// Runs all avaliable tests.
+    fn run(&self) {
+        let test_client = &mut TestClient::default();
+
+        if self.config_with_stop_gap(test_client, 0).is_some() {
+            test_wallet_sync_with_stop_gaps(test_client, self);
+        } else {
+            println!(
+                "{}: Skipped tests requiring config_with_stop_gap.",
+                Self::BLOCKCHAIN_NAME
+            );
+        }
+    }
+}
+
+/// Test whether blockchain implementation syncs with expected behaviour given different `stop_gap`
+/// parameters.
+///
+/// For each test vector:
+/// * Fill wallet's derived addresses with balances (as specified by test vector).
+///    * [0..addrs_before]          => 1000sats for each address
+///    * [addrs_before..actual_gap] => empty addresses
+///    * [actual_gap..addrs_after]  => 1000sats for each address
+/// * Then, perform wallet sync and obtain wallet balance
+/// * Check balance is within expected range (we can compare `stop_gap` and `actual_gap` to
+///    determine this).
+fn test_wallet_sync_with_stop_gaps<T, B>(test_client: &mut TestClient, tester: &T)
+where
+    T: ConfigurableBlockchainTester<B>,
+    B: ConfigurableBlockchain,
+{
+    // Generates wallet descriptor
+    let descriptor_of_account = |account_index: usize| -> String {
+        format!("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/{account_index}/*)")
+    };
+
+    // Amount (in satoshis) provided to a single address (which expects to have a balance)
+    const AMOUNT_PER_TX: u64 = 1000;
+
+    // [stop_gap, actual_gap, addrs_before, addrs_after]
+    //
+    // [0]     stop_gap: Passed to [`ElectrumBlockchainConfig`]
+    // [1]   actual_gap: Range size of address indexes without a balance
+    // [2] addrs_before: Range size of address indexes (before gap) which contains a balance
+    // [3]  addrs_after: Range size of address indexes (after gap) which contains a balance
+    let test_vectors: Vec<[u64; 4]> = vec![
+        [0, 0, 0, 5],
+        [0, 0, 5, 5],
+        [0, 1, 5, 5],
+        [0, 2, 5, 5],
+        [1, 0, 5, 5],
+        [1, 1, 5, 5],
+        [1, 2, 5, 5],
+        [2, 1, 5, 5],
+        [2, 2, 5, 5],
+        [2, 3, 5, 5],
+    ];
+
+    for (account_index, vector) in test_vectors.into_iter().enumerate() {
+        let [stop_gap, actual_gap, addrs_before, addrs_after] = vector;
+        let descriptor = descriptor_of_account(account_index);
+
+        let blockchain = B::from_config(
+            &tester
+                .config_with_stop_gap(test_client, stop_gap as _)
+                .unwrap(),
+        )
+        .unwrap();
+
+        let wallet =
+            Wallet::new(&descriptor, None, Network::Regtest, MemoryDatabase::new()).unwrap();
+
+        // fill server-side with txs to specified address indexes
+        // return the max balance of the wallet (also the actual balance)
+        let max_balance = (0..addrs_before)
+            .chain(addrs_before + actual_gap..addrs_before + actual_gap + addrs_after)
+            .fold(0_u64, |sum, i| {
+                let address = wallet.get_address(AddressIndex::Peek(i as _)).unwrap();
+                test_client.receive(testutils! {
+                    @tx ( (@addr address.address) => AMOUNT_PER_TX )
+                });
+                sum + AMOUNT_PER_TX
+            });
+
+        // minimum allowed balance of wallet (based on stop gap)
+        let min_balance = if actual_gap > stop_gap {
+            addrs_before * AMOUNT_PER_TX
+        } else {
+            max_balance
+        };
+
+        // perform wallet sync
+        wallet.sync(&blockchain, Default::default()).unwrap();
+
+        let wallet_balance = wallet.get_balance().unwrap();
+
+        let details = format!(
+            "test_vector: [stop_gap: {}, actual_gap: {}, addrs_before: {}, addrs_after: {}]",
+            stop_gap, actual_gap, addrs_before, addrs_after,
+        );
+        assert!(
+            wallet_balance <= max_balance,
+            "wallet balance is greater than received amount: {}",
+            details
+        );
+        assert!(
+            wallet_balance >= min_balance,
+            "wallet balance is smaller than expected: {}",
+            details
+        );
+
+        // generate block to confirm new transactions
+        test_client.generate(1, None);
+    }
+}
index 3af478494cfc13a34574ee3db932e79759180d14..82949ecc1ebc91c8bf7db83d5d2f5f469b970335 100644 (file)
 #[cfg(feature = "test-blockchains")]
 pub mod blockchain_tests;
 
+#[cfg(test)]
+#[cfg(feature = "test-blockchains")]
+pub mod configurable_blockchain_tests;
+
 use bitcoin::{Address, Txid};
 
 #[derive(Clone, Debug)]