]> Untitled Git - bdk/commitdiff
bdk_core integration initial commit 🔥
authorLLFourn <lloyd.fourn@gmail.com>
Thu, 3 Nov 2022 07:59:38 +0000 (15:59 +0800)
committerDaniela Brozzoni <danielabrozzoni@protonmail.com>
Thu, 2 Mar 2023 09:55:07 +0000 (10:55 +0100)
We prepare the BDK repo for a major restructuring.

- database modules removed
- blockchain module removed
- minimal API changes.
- Many macros removed.
- no longer applicable examples removed.
- Much conditional compilation removed. Can compile with --all-features.
- delete verify module

48 files changed:
.github/workflows/code_coverage.yml
.github/workflows/cont_integration.yml
.github/workflows/nightly_docs.yml
Cargo.toml
README.md
bdk_test_client/Cargo.toml [new file with mode: 0644]
bdk_test_client/src/lib.rs [new file with mode: 0644]
examples/compact_filters_balance.rs [deleted file]
examples/compiler.rs
examples/esplora.rs [new file with mode: 0644]
examples/hardware_signer.rs
examples/psbt_signer.rs
examples/rpcwallet.rs [deleted file]
macros/Cargo.toml [deleted file]
macros/src/lib.rs [deleted file]
src/blockchain/any.rs [deleted file]
src/blockchain/compact_filters/mod.rs [deleted file]
src/blockchain/compact_filters/peer.rs [deleted file]
src/blockchain/compact_filters/store.rs [deleted file]
src/blockchain/compact_filters/sync.rs [deleted file]
src/blockchain/electrum.rs [deleted file]
src/blockchain/esplora/async.rs [deleted file]
src/blockchain/esplora/blocking.rs [deleted file]
src/blockchain/esplora/mod.rs [deleted file]
src/blockchain/mod.rs [deleted file]
src/blockchain/rpc.rs [deleted file]
src/blockchain/script_sync.rs [deleted file]
src/database/any.rs [deleted file]
src/database/keyvalue.rs [deleted file]
src/database/memory.rs [deleted file]
src/database/mod.rs [deleted file]
src/database/sqlite.rs [deleted file]
src/descriptor/mod.rs
src/descriptor/spk_iter.rs [new file with mode: 0644]
src/descriptor/template.rs
src/error.rs
src/lib.rs
src/psbt/mod.rs
src/testutils/blockchain_tests.rs [deleted file]
src/testutils/configurable_blockchain_tests.rs [deleted file]
src/testutils/mod.rs [deleted file]
src/types.rs
src/wallet/coin_selection.rs
src/wallet/export.rs
src/wallet/mod.rs
src/wallet/time.rs [deleted file]
src/wallet/tx_builder.rs
src/wallet/verify.rs [deleted file]

index b53c47a6385a793a5d09acf35ca030dd36fbf8ef..8236bedeae8083707739947744374538cc54c77e 100644 (file)
@@ -38,13 +38,7 @@ jobs:
       - name: Install grcov
         run: if [[ ! -e ~/.cargo/bin/grcov ]]; then cargo install grcov; fi
       - name: Test
-        # WARNING: this is not testing the following features: test-esplora, test-hardware-signer, async-interface
-        # This is because some of our features are mutually exclusive, and generating various reports and
-        # merging them doesn't seem to be working very well.
-        # For more info, see:
-        # - https://github.com/bitcoindevkit/bdk/issues/696
-        # - https://github.com/bitcoindevkit/bdk/pull/748#issuecomment-1242721040
-        run: cargo test --features all-keys,compact_filters,compiler,key-value-db,sqlite,sqlite-bundled,test-electrum,test-rpc,verify
+        run: cargo test --all-features
       - name: Run grcov
         run: mkdir coverage; grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore '/*' -o ./coverage/lcov.info
       - name: Generate HTML coverage report
index 458270ab0d83575e5fffb5fed2bbf152140d0708..2c86094d272b11abcc7c92a3c4d324f1bbd94e98 100644 (file)
@@ -14,21 +14,9 @@ jobs:
             clippy: true
           - version: 1.57.0 # MSRV
         features:
-          - default
-          - minimal
-          - all-keys
-          - minimal,use-esplora-blocking
-          - key-value-db
-          - electrum
-          - compact_filters
-          - use-esplora-blocking,key-value-db,electrum
-          - compiler
-          - rpc
-          - verify
-          - async-interface
-          - use-esplora-async
-          - sqlite
-          - sqlite-bundled
+          - --no-default-features
+          - --all-features
+          - --features=default
     steps:
       - name: checkout
         uses: actions/checkout@v2
@@ -82,47 +70,6 @@ jobs:
       - name: Test
         run: cargo test --features test-md-docs --no-default-features -- doctest::ReadmeDoctests
 
-  test-blockchains:
-    name: Blockchain ${{ matrix.blockchain.features }}
-    runs-on: ubuntu-20.04
-    strategy:
-      fail-fast: false
-      matrix:
-        blockchain:
-          - name: electrum
-            testprefix: blockchain::electrum::test
-            features: test-electrum,verify
-          - name: rpc
-            testprefix: blockchain::rpc::test
-            features: test-rpc
-          - name: rpc-legacy
-            testprefix: blockchain::rpc::test
-            features: test-rpc-legacy
-          - name: esplora
-            testprefix: esplora
-            features: test-esplora,use-esplora-async,verify
-          - name: esplora
-            testprefix: esplora
-            features: test-esplora,use-esplora-blocking,verify
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-      - name: Cache
-        uses: actions/cache@v2
-        with:
-          path: |
-            ~/.cargo/registry
-            ~/.cargo/git
-            target
-          key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
-      - name: Setup rust toolchain
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          override: true
-      - name: Test
-        run: cargo test --no-default-features --features ${{ matrix.blockchain.features }} ${{ matrix.blockchain.testprefix }}::bdk_blockchain_tests
-
   check-wasm:
     name: Check WASM
     runs-on: ubuntu-20.04
index 1b07937d96ef0dfc84f48e49146cc1d6e932c96d..1e3f6139465865d487e2c740d117bb1f17062661 100644 (file)
@@ -24,7 +24,7 @@ jobs:
       - name: Update toolchain
         run: rustup update
       - name: Build docs
-        run: cargo rustdoc --verbose --features=compiler,electrum,esplora,use-esplora-blocking,compact_filters,rpc,key-value-db,sqlite,all-keys,verify,hardware-signer -- --cfg docsrs -Dwarnings
+        run: cargo rustdoc --verbose --all-features -- --cfg docsrs -Dwarnings
       - name: Upload artifact
         uses: actions/upload-artifact@v2
         with:
index 18fcfef6228ddfb187fb57226d228dceaef76ec7..bda2d3ebcb93335ca45573bb0bf92ff806d700ea 100644 (file)
@@ -10,34 +10,21 @@ description = "A modern, lightweight, descriptor-based wallet library"
 keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
 readme = "README.md"
 license = "MIT OR Apache-2.0"
+# TODO: remove this when examples all work
+autoexamples = false
 
 [dependencies]
-bdk-macros = "^0.6"
 log = "^0.4"
-miniscript = { version = "9.0", features = ["serde"] }
-bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] }
+miniscript = { version = "9", features = ["serde"] }
+bitcoin = { version = "0.29", features = ["serde", "base64", "rand"] }
 serde = { version = "^1.0", features = ["derive"] }
 serde_json = { version = "^1.0" }
+bdk_chain = { version = "0.1", features = ["miniscript", "serde"] }
 rand = "^0.8"
 
 # Optional dependencies
-sled = { version = "0.34", optional = true }
-electrum-client = { version = "0.12", optional = true }
-esplora-client = { version = "0.3", default-features = false, optional = true }
-rusqlite = { version = "0.28.0", optional = true }
-ahash = { version = "0.7.6", optional = true }
-futures = { version = "0.3", optional = true }
-async-trait = { version = "0.1", optional = true }
-rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
-cc = { version = ">=1.0.64", optional = true }
-socks = { version = "0.3", optional = true }
 hwi = { version = "0.5", optional = true, features = [ "use-miniscript"] }
-
 bip39 = { version = "1.0.1", optional = true }
-bitcoinconsensus = { version = "0.19.0-3", optional = true }
-
-# Needed by bdk_blockchain_tests macro and the `rpc` feature
-bitcoincore-rpc = { version = "0.16", optional = true }
 
 # Platform-specific dependencies
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@@ -45,57 +32,16 @@ tokio = { version = "1", features = ["rt", "macros"] }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 getrandom = "0.2"
-async-trait = "0.1"
 js-sys = "0.3"
 
 [features]
-minimal = []
 compiler = ["miniscript/compiler"]
-verify = ["bitcoinconsensus"]
-default = ["key-value-db", "electrum"]
-sqlite = ["rusqlite", "ahash"]
-sqlite-bundled = ["sqlite", "rusqlite/bundled"]
-compact_filters = ["rocksdb", "socks", "cc"]
-key-value-db = ["sled"]
 all-keys = ["keys-bip39"]
 keys-bip39 = ["bip39"]
-rpc = ["bitcoincore-rpc"]
 hardware-signer = ["hwi"]
 
-# We currently provide mulitple implementations of `Blockchain`, all are
-# blocking except for the `EsploraBlockchain` which can be either async or
-# blocking, depending on the HTTP client in use.
-#
-# - Users wanting asynchronous HTTP calls should enable `async-interface` to get
-#   access to the asynchronous method implementations. Then, if Esplora is wanted,
-#   enable the `use-esplora-async` feature.
-# - Users wanting blocking HTTP calls can use any of the other blockchain
-#   implementations (`compact_filters`, `electrum`, or `esplora`). Users wanting to
-#   use Esplora should enable the `use-esplora-blocking` feature.
-#
-# WARNING: Please take care with the features below, various combinations will
-# fail to build. We cannot currently build `bdk` with `--all-features`.
-async-interface = ["async-trait"]
-electrum = ["electrum-client"]
-# MUST ALSO USE `--no-default-features`.
-use-esplora-async = ["esplora", "esplora-client/async", "futures"]
-use-esplora-blocking = ["esplora", "esplora-client/blocking"]
-# Deprecated aliases
-use-esplora-reqwest = ["use-esplora-async"]
-use-esplora-ureq = ["use-esplora-blocking"]
-# Typical configurations will not need to use `esplora` feature directly.
-esplora = []
-
-# Use below feature with `use-esplora-async` to enable reqwest default TLS support
-reqwest-default-tls = ["esplora-client/async-https"]
-
 # Debug/Test features
-test-blockchains = ["bitcoincore-rpc", "electrum-client"]
-test-electrum = ["electrum", "electrsd/electrs_0_8_10", "electrsd/bitcoind_22_0", "test-blockchains"]
-test-rpc = ["rpc", "electrsd/electrs_0_8_10", "electrsd/bitcoind_22_0", "test-blockchains"]
-test-rpc-legacy = ["rpc", "electrsd/electrs_0_8_10", "electrsd/bitcoind_0_20_0", "test-blockchains"]
-test-esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1", "electrsd/bitcoind_22_0", "test-blockchains"]
-test-md-docs = ["electrum"]
+test-md-docs = []
 test-hardware-signer = ["hardware-signer"]
 
 # This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
@@ -106,17 +52,12 @@ dev-getrandom-wasm = ["getrandom/js"]
 [dev-dependencies]
 lazy_static = "1.4"
 env_logger = "0.7"
-electrsd = "0.22"
 # Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
 base64 = "^0.13"
 assert_matches = "1.5.0"
 # zip versions after 0.6.3 don't work with our MSRV 1.57.0
 zip = "=0.6.3"
 
-[[example]]
-name = "compact_filters_balance"
-required-features = ["compact_filters"]
-
 [[example]]
 name = "miniscriptc"
 path = "examples/compiler.rs"
@@ -126,44 +67,14 @@ required-features = ["compiler"]
 name = "policy"
 path = "examples/policy.rs"
 
-[[example]]
-name = "rpcwallet"
-path = "examples/rpcwallet.rs"
-required-features = ["keys-bip39", "key-value-db", "rpc", "electrsd/bitcoind_22_0"]
-
-[[example]]
-name = "psbt_signer"
-path = "examples/psbt_signer.rs"
-required-features = ["electrum"]
-
-[[example]]
-name = "hardware_signer"
-path = "examples/hardware_signer.rs"
-required-features = ["electrum", "hardware-signer"]
-
-[[example]]
-name = "electrum_backend"
-path = "examples/electrum_backend.rs"
-required-features = ["electrum"]
-
-[[example]]
-name = "esplora_backend_synchronous"
-path = "examples/esplora_backend_synchronous.rs"
-required-features = ["use-esplora-ureq"]
-
-[[example]]
-name = "esplora_backend_asynchronous"
-path = "examples/esplora_backend_asynchronous.rs"
-required-features = ["use-esplora-reqwest", "reqwest-default-tls", "async-interface"]
-
 [[example]]
 name = "mnemonic_to_descriptors"
 path = "examples/mnemonic_to_descriptors.rs"
 required-features = ["all-keys"]
 
 [workspace]
-members = ["macros"]
+members = ["bdk_test_client"]
 [package.metadata.docs.rs]
-features = ["compiler", "electrum", "esplora", "use-esplora-blocking", "compact_filters", "rpc", "key-value-db", "sqlite", "all-keys", "verify", "hardware-signer"]
+all-feautres = true
 # defines the configuration attribute `docsrs`
 rustdoc-args = ["--cfg", "docsrs"]
index 096d98e1d11fc34b3c39f84433d1344fd8217796..0f15de96e865207e36d5634298a10b008a491e36 100644 (file)
--- a/README.md
+++ b/README.md
@@ -78,9 +78,9 @@ fn main() -> Result<(), bdk::Error> {
         MemoryDatabase::default(),
     )?;
 
-    println!("Address #0: {}", wallet.get_address(New)?);
-    println!("Address #1: {}", wallet.get_address(New)?);
-    println!("Address #2: {}", wallet.get_address(New)?);
+    println!("Address #0: {}", wallet.get_address(New));
+    println!("Address #1: {}", wallet.get_address(New));
+    println!("Address #2: {}", wallet.get_address(New));
 
     Ok(())
 }
@@ -111,7 +111,7 @@ fn main() -> Result<(), bdk::Error> {
 
     wallet.sync(&blockchain, SyncOptions::default())?;
 
-    let send_to = wallet.get_address(New)?;
+    let send_to = wallet.get_address(New);
     let (psbt, details) = {
         let mut builder = wallet.build_tx();
         builder
diff --git a/bdk_test_client/Cargo.toml b/bdk_test_client/Cargo.toml
new file mode 100644 (file)
index 0000000..bc14b1e
--- /dev/null
@@ -0,0 +1,19 @@
+[package]
+name = "bdk_test_client"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+electrsd = { version = "0.22" }
+bitcoincore-rpc = { version = "0.16"}
+log = "^0.4"
+bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] }
+electrum-client = "0.12"
+
+
+[features]
+bitcoind_22_0 = ["electrsd/bitcoind_22_0"]
+electrs_0_8_10 = ["electrsd/electrs_0_8_10"]
+esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1" ]
diff --git a/bdk_test_client/src/lib.rs b/bdk_test_client/src/lib.rs
new file mode 100644 (file)
index 0000000..aebcb45
--- /dev/null
@@ -0,0 +1,295 @@
+use bitcoin::consensus::encode::serialize;
+use bitcoin::hashes::hex::{FromHex, ToHex};
+use bitcoin::hashes::sha256d;
+use bitcoin::{Address, PackedLockTime, Script, Sequence, Transaction, Txid, Witness};
+pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
+use bitcoincore_rpc::jsonrpc::serde_json::{self, json};
+pub use bitcoincore_rpc::{Auth, Client as RpcClient, Error as RpcError, RpcApi};
+use core::str::FromStr;
+use electrsd::bitcoind::BitcoinD;
+use electrsd::{bitcoind, ElectrsD};
+pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
+#[allow(unused_imports)]
+use log::{debug, error, info, log_enabled, trace, Level};
+use std::env;
+use std::ops::Deref;
+use std::time::Duration;
+
+pub struct TestClient {
+    pub bitcoind: BitcoinD,
+    pub electrsd: ElectrsD,
+}
+
+impl TestClient {
+    pub fn new(bitcoind_exe: String, electrs_exe: String) -> Self {
+        debug!("launching {} and {}", &bitcoind_exe, &electrs_exe);
+
+        let mut conf = bitcoind::Conf::default();
+        conf.view_stdout = log_enabled!(Level::Debug);
+        let bitcoind = BitcoinD::with_conf(bitcoind_exe, &conf).unwrap();
+
+        let mut conf = electrsd::Conf::default();
+        conf.view_stderr = log_enabled!(Level::Debug);
+        conf.http_enabled = cfg!(feature = "esplora");
+
+        let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap();
+
+        let node_address = bitcoind.client.get_new_address(None, None).unwrap();
+        bitcoind
+            .client
+            .generate_to_address(101, &node_address)
+            .unwrap();
+
+        let mut test_client = TestClient { bitcoind, electrsd };
+        TestClient::wait_for_block(&mut test_client, 101);
+        test_client
+    }
+
+    fn wait_for_tx(&mut self, txid: Txid, monitor_script: &Script) {
+        // wait for electrs to index the tx
+        exponential_backoff_poll(|| {
+            self.electrsd.trigger().unwrap();
+            trace!("wait_for_tx {}", txid);
+
+            self.electrsd
+                .client
+                .script_get_history(monitor_script)
+                .unwrap()
+                .iter()
+                .position(|entry| entry.tx_hash == txid)
+        });
+    }
+
+    fn wait_for_block(&mut self, min_height: usize) {
+        self.electrsd.client.block_headers_subscribe().unwrap();
+
+        loop {
+            let header = exponential_backoff_poll(|| {
+                self.electrsd.trigger().unwrap();
+                self.electrsd.client.ping().unwrap();
+                self.electrsd.client.block_headers_pop().unwrap()
+            });
+            if header.height >= min_height {
+                break;
+            }
+        }
+    }
+
+    pub fn bump_fee(&mut self, txid: &Txid) -> Txid {
+        let tx = self.get_raw_transaction_info(txid, None).unwrap();
+        assert!(
+            tx.confirmations.is_none(),
+            "Can't bump tx {} because it's already confirmed",
+            txid
+        );
+
+        let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap();
+        let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap();
+        let monitor_script = Script::from_hex(&mut tx.vout[0].script_pub_key.hex.to_hex()).unwrap();
+        self.wait_for_tx(new_txid, &monitor_script);
+
+        debug!("Bumped {}, new txid {}", txid, new_txid);
+
+        new_txid
+    }
+
+    pub fn generate_manually(&mut self, txs: Vec<Transaction>) -> String {
+        use bitcoin::blockdata::block::{Block, BlockHeader};
+        use bitcoin::blockdata::script::Builder;
+        use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
+        use bitcoin::hash_types::{BlockHash, TxMerkleNode};
+        use bitcoin::hashes::Hash;
+
+        let block_template: serde_json::Value = self
+            .call("getblocktemplate", &[json!({"rules": ["segwit"]})])
+            .unwrap();
+        trace!("getblocktemplate: {:#?}", block_template);
+
+        let header = BlockHeader {
+            version: block_template["version"].as_i64().unwrap() as i32,
+            prev_blockhash: BlockHash::from_hex(
+                block_template["previousblockhash"].as_str().unwrap(),
+            )
+            .unwrap(),
+            merkle_root: TxMerkleNode::all_zeros(),
+            time: block_template["curtime"].as_u64().unwrap() as u32,
+            bits: u32::from_str_radix(block_template["bits"].as_str().unwrap(), 16).unwrap(),
+            nonce: 0,
+        };
+        debug!("header: {:#?}", header);
+
+        let height = block_template["height"].as_u64().unwrap() as i64;
+        let witness_reserved_value: Vec<u8> = sha256d::Hash::all_zeros().as_ref().into();
+        // burn block subsidy and fees, not a big deal
+        let mut coinbase_tx = Transaction {
+            version: 1,
+            lock_time: PackedLockTime(0),
+            input: vec![TxIn {
+                previous_output: OutPoint::null(),
+                script_sig: Builder::new().push_int(height).into_script(),
+                sequence: Sequence(0xFFFFFFFF),
+                witness: Witness::from_vec(vec![witness_reserved_value]),
+            }],
+            output: vec![],
+        };
+
+        let mut txdata = vec![coinbase_tx.clone()];
+        txdata.extend_from_slice(&txs);
+
+        let mut block = Block { header, txdata };
+
+        if let Some(witness_root) = block.witness_root() {
+            let witness_commitment = Block::compute_witness_commitment(
+                &witness_root,
+                &coinbase_tx.input[0]
+                    .witness
+                    .last()
+                    .expect("Should contain the witness reserved value"),
+            );
+
+            // now update and replace the coinbase tx
+            let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
+            coinbase_witness_commitment_script.extend_from_slice(&witness_commitment);
+
+            coinbase_tx.output.push(TxOut {
+                value: 0,
+                script_pubkey: coinbase_witness_commitment_script.into(),
+            });
+        }
+
+        block.txdata[0] = coinbase_tx;
+
+        // set merkle root
+        if let Some(merkle_root) = block.compute_merkle_root() {
+            block.header.merkle_root = merkle_root;
+        }
+
+        assert!(block.check_merkle_root());
+        assert!(block.check_witness_commitment());
+
+        // now do PoW :)
+        let target = block.header.target();
+        while block.header.validate_pow(&target).is_err() {
+            block.header.nonce = block.header.nonce.checked_add(1).unwrap(); // panic if we run out of nonces
+        }
+
+        let block_hex: String = serialize(&block).to_hex();
+        debug!("generated block hex: {}", block_hex);
+
+        self.electrsd.client.block_headers_subscribe().unwrap();
+
+        let submit_result: serde_json::Value =
+            self.call("submitblock", &[block_hex.into()]).unwrap();
+        debug!("submitblock: {:?}", submit_result);
+        assert!(
+            submit_result.is_null(),
+            "submitblock error: {:?}",
+            submit_result.as_str()
+        );
+
+        self.wait_for_block(height as usize);
+
+        block.header.block_hash().to_hex()
+    }
+
+    pub fn generate(&mut self, num_blocks: u64, address: Option<Address>) -> u32 {
+        let address = address.unwrap_or_else(|| self.get_new_address(None, None).unwrap());
+        let hashes = self.generate_to_address(num_blocks, &address).unwrap();
+        let best_hash = hashes.last().unwrap();
+        let height = self.get_block_info(best_hash).unwrap().height;
+
+        self.wait_for_block(height);
+
+        debug!("Generated blocks to new height {}", height);
+        height as u32
+    }
+
+    pub fn invalidate(&mut self, num_blocks: u64) {
+        self.electrsd.client.block_headers_subscribe().unwrap();
+
+        let best_hash = self.get_best_block_hash().unwrap();
+        let initial_height = self.get_block_info(&best_hash).unwrap().height;
+
+        let mut to_invalidate = best_hash;
+        for i in 1..=num_blocks {
+            trace!(
+                "Invalidating block {}/{} ({})",
+                i,
+                num_blocks,
+                to_invalidate
+            );
+
+            self.invalidate_block(&to_invalidate).unwrap();
+            to_invalidate = self.get_best_block_hash().unwrap();
+        }
+
+        self.wait_for_block(initial_height - num_blocks as usize);
+
+        debug!(
+            "Invalidated {} blocks to new height of {}",
+            num_blocks,
+            initial_height - num_blocks as usize
+        );
+    }
+
+    pub fn reorg(&mut self, num_blocks: u64) {
+        self.invalidate(num_blocks);
+        self.generate(num_blocks, None);
+    }
+
+    pub fn get_node_address(&self, address_type: Option<AddressType>) -> Address {
+        Address::from_str(
+            &self
+                .get_new_address(None, address_type)
+                .unwrap()
+                .to_string(),
+        )
+        .unwrap()
+    }
+}
+
+pub fn get_electrum_url() -> String {
+    env::var("BDK_ELECTRUM_URL").unwrap_or_else(|_| "tcp://127.0.0.1:50001".to_string())
+}
+
+impl Deref for TestClient {
+    type Target = RpcClient;
+
+    fn deref(&self) -> &Self::Target {
+        &self.bitcoind.client
+    }
+}
+
+impl Default for TestClient {
+    fn default() -> Self {
+        let bitcoind_exe = env::var("BITCOIND_EXE")
+            .ok()
+            .or(bitcoind::downloaded_exe_path().ok())
+            .expect(
+                "you should provide env var BITCOIND_EXE or specifiy a bitcoind version feature",
+            );
+        let electrs_exe = env::var("ELECTRS_EXE")
+            .ok()
+            .or(electrsd::downloaded_exe_path())
+            .expect(
+                "you should provide env var ELECTRS_EXE or specifiy a electrsd version feature",
+            );
+        Self::new(bitcoind_exe, electrs_exe)
+    }
+}
+
+fn exponential_backoff_poll<T, F>(mut poll: F) -> T
+where
+    F: FnMut() -> Option<T>,
+{
+    let mut delay = Duration::from_millis(64);
+    loop {
+        match poll() {
+            Some(data) => break data,
+            None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
+            None => {}
+        }
+
+        std::thread::sleep(delay);
+    }
+}
diff --git a/examples/compact_filters_balance.rs b/examples/compact_filters_balance.rs
deleted file mode 100644 (file)
index ce875b4..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use bdk::blockchain::compact_filters::*;
-use bdk::database::MemoryDatabase;
-use bdk::*;
-use bitcoin::*;
-use blockchain::compact_filters::CompactFiltersBlockchain;
-use blockchain::compact_filters::CompactFiltersError;
-use log::info;
-use std::sync::Arc;
-
-/// This will return wallet balance using compact filters
-/// Requires a synced local bitcoin node 0.21 running on testnet with blockfilterindex=1 and peerblockfilters=1
-fn main() -> Result<(), CompactFiltersError> {
-    env_logger::init();
-    info!("start");
-
-    let num_threads = 4;
-    let mempool = Arc::new(Mempool::default());
-    let peers = (0..num_threads)
-        .map(|_| Peer::connect("localhost:18333", Arc::clone(&mempool), Network::Testnet))
-        .collect::<Result<_, _>>()?;
-    let blockchain = CompactFiltersBlockchain::new(peers, "./wallet-filters", Some(500_000))?;
-    info!("done {:?}", blockchain);
-    let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
-
-    let database = MemoryDatabase::default();
-    let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap());
-    wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-    info!("balance: {}", wallet.get_balance()?);
-    Ok(())
-}
index e17feb3ad752eb93c299ec1ab8e74f81d744b5c3..98f433941c10ef30c9bc7c01956b16e6e6e6ea95 100644 (file)
@@ -24,7 +24,6 @@ use bitcoin::Network;
 use miniscript::policy::Concrete;
 use miniscript::Descriptor;
 
-use bdk::database::memory::MemoryDatabase;
 use bdk::wallet::AddressIndex::New;
 use bdk::{KeychainKind, Wallet};
 
@@ -54,14 +53,12 @@ fn main() -> Result<(), Box<dyn Error>> {
 
     info!("Compiled into following Descriptor: \n{}", descriptor);
 
-    let database = MemoryDatabase::new();
-
     // Create a new wallet from this descriptor
-    let wallet = Wallet::new(&format!("{}", descriptor), None, Network::Regtest, database)?;
+    let wallet = Wallet::new(&format!("{}", descriptor), None, Network::Regtest)?;
 
     info!(
         "First derived address from the descriptor: \n{}",
-        wallet.get_address(New)?
+        wallet.get_address(New)
     );
 
     // BDK also has it's own `Policy` structure to represent the spending condition in a more
diff --git a/examples/esplora.rs b/examples/esplora.rs
new file mode 100644 (file)
index 0000000..1659666
--- /dev/null
@@ -0,0 +1,112 @@
+use bdk::{
+    blockchain::esplora::{esplora_client, BlockingClientExt},
+    wallet::AddressIndex,
+    Wallet,
+};
+use bdk_test_client::{RpcApi, TestClient};
+use bitcoin::{Amount, Network};
+use rand::Rng;
+use std::error::Error;
+
+fn main() -> Result<(), Box<dyn Error>> {
+    let _ = env_logger::init();
+    const DESCRIPTOR: &'static str ="tr([73c5da0a/86'/0'/0']tprv8cSrHfiTQQWzKVejDHvBcvW4pdLEDLMvtVdbUXFfceQ4kbZKMsuFWbd3LUN3omNrQfafQaPwXUFXtcofkE9UjFZ3i9deezBHQTGvYV2xUzz/0/*)";
+    const CHANGE_DESCRIPTOR: &'static str = "tr(tprv8ZgxMBicQKsPeQe98SGJ53vEJ7MNEFkQ4CkZmrr6PNom3vn6GqxuyoE78smkzpuP347zR9MXPg38PoZ8tbxLqSx4CufufHAGbQ9Hf7yTTwn/44'/0'/0'/1/*)#pxy2d75a";
+
+    let mut test_client = TestClient::default();
+    let esplora_url = format!(
+        "http://{}",
+        test_client.electrsd.esplora_url.as_ref().unwrap()
+    );
+    let client = esplora_client::Builder::new(&esplora_url).build_blocking()?;
+
+    let wallet = Wallet::new(DESCRIPTOR, Some(CHANGE_DESCRIPTOR), Network::Regtest)
+        .expect("parsing descriptors failed");
+    // note we don't *need* the Mutex for this example but it helps to show when the wallet does and
+    // doesn't need to be mutablek
+    let wallet = std::sync::Mutex::new(wallet);
+    let n_initial_transactions = 10;
+
+    let addresses = {
+        // we need it to be mutable to get a new address.
+        // This incremenents the derivatoin index of the keychain.
+        let mut wallet = wallet.lock().unwrap();
+        core::iter::repeat_with(|| wallet.get_address(AddressIndex::New))
+            .filter(|_| rand::thread_rng().gen_bool(0.5))
+            .take(n_initial_transactions)
+            .collect::<Vec<_>>()
+    };
+
+    // get some coins for the internal node
+    test_client.generate(100, None);
+
+    for address in addresses {
+        let exp_txid = test_client
+            .send_to_address(
+                &address,
+                Amount::from_sat(10_000),
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+            )
+            .expect("tx should send");
+        eprintln!(
+            "💸 sending some coins to: {} (index {}) in tx {}",
+            address, address.index, exp_txid
+        );
+        // sometimes generate a block after we send coins to the address
+        if rand::thread_rng().gen_bool(0.3) {
+            let height = test_client.generate(1, None);
+            eprintln!("📦 created a block at height {}", height);
+        }
+    }
+
+    let wait_for_esplora_sync = std::time::Duration::from_secs(5);
+
+    println!("⏳ waiting {}s for esplora to catch up..", wait_for_esplora_sync.as_secs());
+    std::thread::sleep(wait_for_esplora_sync);
+
+
+    let wallet_scan_input = {
+        let wallet = wallet.lock().unwrap();
+        wallet.start_wallet_scan()
+    };
+
+    let start = std::time::Instant::now();
+    let stop_gap = 5;
+    eprintln!(
+        "🔎 startig scanning all keychains with stop gap of {}",
+        stop_gap
+    );
+    let wallet_scan = client.wallet_scan(wallet_scan_input, stop_gap, &Default::default(), 5)?;
+
+    // we've got an update so briefly take a lock the wallet to apply it
+    {
+        let mut wallet = wallet.lock().unwrap();
+        match wallet.apply_wallet_scan(wallet_scan) {
+            Ok(changes) => {
+                eprintln!("🎉 success! ({}ms)", start.elapsed().as_millis());
+                eprintln!("wallet balance after: {:?}", wallet.get_balance());
+                //XXX: esplora is not indexing mempool transactions right now (or not doing it fast enough)
+                eprintln!(
+                    "wallet found {} new transactions",
+                    changes.tx_additions().count(),
+                );
+                if changes.tx_additions().count() != n_initial_transactions {
+                    eprintln!(
+                        "(it should have found {} but maybe stop gap wasn't large enough?)",
+                        n_initial_transactions
+                    );
+                }
+            }
+            Err(reason) => {
+                eprintln!("❌ esplora produced invalid wallet scan {}", reason);
+            }
+        }
+    }
+
+    Ok(())
+}
index d1c25f1ab89d8a69aa805adb3c2d04a76edde846..514f99a64de6c061a78e93f4316fa05b3c92d3d6 100644 (file)
@@ -46,7 +46,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         descriptors.receive[0].clone(),
         Some(descriptors.internal[0].clone()),
         Network::Testnet,
-        MemoryDatabase::default(),
     )?;
 
     // Adding the hardware signer to the BDK wallet
@@ -64,7 +63,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     wallet.sync(&blockchain, SyncOptions::default())?;
 
     // get deposit address
-    let deposit_address = wallet.get_address(AddressIndex::New)?;
+    let deposit_address = wallet.get_address(AddressIndex::New);
 
     let balance = wallet.get_balance()?;
     println!("Wallet balances in SATs: {}", balance);
index 35c539dad511a69a023780fc56ab8f779a40de2a..c7d4156df5cb7c2f572e4a4986a767cfaaa5f3dd 100644 (file)
@@ -47,26 +47,24 @@ fn main() -> Result<(), Box<dyn Error>> {
         ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
 
     // create watch only wallet
-    let watch_only_wallet: Wallet<MemoryDatabase> = Wallet::new(
+    let watch_only_wallet: Wallet = Wallet::new(
         watch_only_external_descriptor,
         Some(watch_only_internal_descriptor),
         Network::Testnet,
-        MemoryDatabase::default(),
     )?;
 
     // create signing wallet
-    let signing_wallet: Wallet<MemoryDatabase> = Wallet::new(
+    let signing_wallet: Wallet = Wallet::new(
         signing_external_descriptor,
         Some(signing_internal_descriptor),
         Network::Testnet,
-        MemoryDatabase::default(),
     )?;
 
     println!("Syncing watch only wallet.");
     watch_only_wallet.sync(&blockchain, SyncOptions::default())?;
 
     // get deposit address
-    let deposit_address = watch_only_wallet.get_address(AddressIndex::New)?;
+    let deposit_address = watch_only_wallet.get_address(AddressIndex::New);
 
     let balance = watch_only_wallet.get_balance()?;
     println!("Watch only wallet balances in SATs: {}", balance);
@@ -81,7 +79,7 @@ fn main() -> Result<(), Box<dyn Error>> {
             "Wait for at least 10000 SATs of your wallet transactions to be confirmed...\nBe patient, this could take 10 mins or longer depending on how testnet is behaving."
         );
         for tx_details in watch_only_wallet
-            .list_transactions(false)?
+            .transactions()
             .iter()
             .filter(|txd| txd.received > 0 && txd.confirmation_time.is_none())
         {
diff --git a/examples/rpcwallet.rs b/examples/rpcwallet.rs
deleted file mode 100644 (file)
index 24a5559..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use bdk::bitcoin::secp256k1::Secp256k1;
-use bdk::bitcoin::Amount;
-use bdk::bitcoin::Network;
-use bdk::bitcoincore_rpc::RpcApi;
-
-use bdk::blockchain::rpc::{Auth, RpcBlockchain, RpcConfig};
-use bdk::blockchain::ConfigurableBlockchain;
-
-use bdk::keys::bip39::{Language, Mnemonic, WordCount};
-use bdk::keys::{DerivableKey, GeneratableKey, GeneratedKey};
-
-use bdk::miniscript::miniscript::Segwitv0;
-
-use bdk::sled;
-use bdk::template::Bip84;
-use bdk::wallet::{signer::SignOptions, wallet_name_from_descriptor, AddressIndex, SyncOptions};
-use bdk::KeychainKind;
-use bdk::Wallet;
-
-use bdk::blockchain::Blockchain;
-
-use electrsd;
-
-use std::error::Error;
-use std::path::PathBuf;
-use std::str::FromStr;
-
-/// This example demonstrates a typical way to create a wallet and work with bdk.
-///
-/// This example bdk wallet is connected to a bitcoin core rpc regtest node,
-/// and will attempt to receive, create and broadcast transactions.
-///
-/// To start a bitcoind regtest node programmatically, this example uses
-/// `electrsd` library, which is also a bdk dev-dependency.
-///
-/// But you can start your own bitcoind backend, and the rest of the example should work fine.
-
-fn main() -> Result<(), Box<dyn Error>> {
-    // -- Setting up background bitcoind process
-
-    println!(">> Setting up bitcoind");
-
-    // Start the bitcoind process
-    let bitcoind_conf = electrsd::bitcoind::Conf::default();
-
-    // electrsd will automatically download the bitcoin core binaries
-    let bitcoind_exe =
-        electrsd::bitcoind::downloaded_exe_path().expect("We should always have downloaded path");
-
-    // Launch bitcoind and gather authentication access
-    let bitcoind = electrsd::bitcoind::BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap();
-    let bitcoind_auth = Auth::Cookie {
-        file: bitcoind.params.cookie_file.clone(),
-    };
-
-    // Get a new core address
-    let core_address = bitcoind.client.get_new_address(None, None)?;
-
-    // Generate 101 blocks and use the above address as coinbase
-    bitcoind.client.generate_to_address(101, &core_address)?;
-
-    println!(">> bitcoind setup complete");
-    println!(
-        "Available coins in Core wallet : {}",
-        bitcoind.client.get_balance(None, None)?
-    );
-
-    // -- Setting up the Wallet
-
-    println!("\n>> Setting up BDK wallet");
-
-    // Get a random private key
-    let xprv = generate_random_ext_privkey()?;
-
-    // Use the derived descriptors from the privatekey to
-    // create unique wallet name.
-    // This is a special utility function exposed via `bdk::wallet_name_from_descriptor()`
-    let wallet_name = wallet_name_from_descriptor(
-        Bip84(xprv.clone(), KeychainKind::External),
-        Some(Bip84(xprv.clone(), KeychainKind::Internal)),
-        Network::Regtest,
-        &Secp256k1::new(),
-    )?;
-
-    // Create a database (using default sled type) to store wallet data
-    let mut datadir = PathBuf::from_str("/tmp/")?;
-    datadir.push(".bdk-example");
-    let database = sled::open(datadir)?;
-    let database = database.open_tree(wallet_name.clone())?;
-
-    // Create a RPC configuration of the running bitcoind backend we created in last step
-    // Note: If you are using custom regtest node, use the appropriate url and auth
-    let rpc_config = RpcConfig {
-        url: bitcoind.params.rpc_socket.to_string(),
-        auth: bitcoind_auth,
-        network: Network::Regtest,
-        wallet_name,
-        sync_params: None,
-    };
-
-    // Use the above configuration to create a RPC blockchain backend
-    let blockchain = RpcBlockchain::from_config(&rpc_config)?;
-
-    // Combine Database + Descriptor to create the final wallet
-    let wallet = Wallet::new(
-        Bip84(xprv.clone(), KeychainKind::External),
-        Some(Bip84(xprv.clone(), KeychainKind::Internal)),
-        Network::Regtest,
-        database,
-    )?;
-
-    // The `wallet` and the `blockchain` are independent structs.
-    // The wallet will be used to do all wallet level actions
-    // The blockchain can be used to do all blockchain level actions.
-    // For certain actions (like sync) the wallet will ask for a blockchain.
-
-    // Sync the wallet
-    // The first sync is important as this will instantiate the
-    // wallet files.
-    wallet.sync(&blockchain, SyncOptions::default())?;
-
-    println!(">> BDK wallet setup complete.");
-    println!(
-        "Available initial coins in BDK wallet : {} sats",
-        wallet.get_balance()?
-    );
-
-    // -- Wallet transaction demonstration
-
-    println!("\n>> Sending coins: Core --> BDK, 10 BTC");
-    // Get a new address to receive coins
-    let bdk_new_addr = wallet.get_address(AddressIndex::New)?.address;
-
-    // Send 10 BTC from core wallet to bdk wallet
-    bitcoind.client.send_to_address(
-        &bdk_new_addr,
-        Amount::from_btc(10.0)?,
-        None,
-        None,
-        None,
-        None,
-        None,
-        None,
-    )?;
-
-    // Confirm transaction by generating 1 block
-    bitcoind.client.generate_to_address(1, &core_address)?;
-
-    // Sync the BDK wallet
-    // This time the sync will fetch the new transaction and update it in
-    // wallet database
-    wallet.sync(&blockchain, SyncOptions::default())?;
-
-    println!(">> Received coins in BDK wallet");
-    println!(
-        "Available balance in BDK wallet: {} sats",
-        wallet.get_balance()?
-    );
-
-    println!("\n>> Sending coins: BDK --> Core, 5 BTC");
-    // Attempt to send back 5.0 BTC to core address by creating a transaction
-    //
-    // Transactions are created using a `TxBuilder`.
-    // This helps us to systematically build a transaction with all
-    // required customization.
-    // A full list of APIs offered by `TxBuilder` can be found at
-    // https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html
-    let mut tx_builder = wallet.build_tx();
-
-    // For a regular transaction, just set the recipient and amount
-    tx_builder.set_recipients(vec![(core_address.script_pubkey(), 500000000)]);
-
-    // Finalize the transaction and extract the PSBT
-    let (mut psbt, _) = tx_builder.finish()?;
-
-    // Set signing option
-    let signopt = SignOptions {
-        assume_height: None,
-        ..Default::default()
-    };
-
-    // Sign the psbt
-    wallet.sign(&mut psbt, signopt)?;
-
-    // Extract the signed transaction
-    let tx = psbt.extract_tx();
-
-    // Broadcast the transaction
-    blockchain.broadcast(&tx)?;
-
-    // Confirm transaction by generating some blocks
-    bitcoind.client.generate_to_address(1, &core_address)?;
-
-    // Sync the BDK wallet
-    wallet.sync(&blockchain, SyncOptions::default())?;
-
-    println!(">> Coins sent to Core wallet");
-    println!(
-        "Remaining BDK wallet balance: {} sats",
-        wallet.get_balance()?
-    );
-    println!("\nCongrats!! you made your first test transaction with bdk and bitcoin core.");
-
-    Ok(())
-}
-
-// Helper function demonstrating privatekey extraction using bip39 mnemonic
-// The mnemonic can be shown to user to safekeeping and the same wallet
-// private descriptors can be recreated from it.
-fn generate_random_ext_privkey() -> Result<impl DerivableKey<Segwitv0> + Clone, Box<dyn Error>> {
-    // a Bip39 passphrase can be set optionally
-    let password = Some("random password".to_string());
-
-    // Generate a random mnemonic, and use that to create a "DerivableKey"
-    let mnemonic: GeneratedKey<_, _> = Mnemonic::generate((WordCount::Words12, Language::English))
-        .map_err(|e| e.expect("Unknown Error"))?;
-
-    // `Ok(mnemonic)` would also work if there's no passphrase and it would
-    // yield the same result as this construct with `password` = `None`.
-    Ok((mnemonic, password))
-}
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
deleted file mode 100644 (file)
index d5b2f5f..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-[package]
-name = "bdk-macros"
-version = "0.6.0"
-authors = ["Alekos Filini <alekos.filini@gmail.com>"]
-edition = "2018"
-homepage = "https://bitcoindevkit.org"
-repository = "https://github.com/bitcoindevkit/bdk"
-documentation = "https://docs.rs/bdk-macros"
-description = "Supporting macros for `bdk`"
-keywords = ["bdk"]
-license = "MIT OR Apache-2.0"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-syn = { version = "1.0", features = ["parsing", "full"] }
-proc-macro2 = "1.0"
-quote = "1.0"
-
-[features]
-debug = ["syn/extra-traits"]
-
-[lib]
-proc-macro = true
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
deleted file mode 100644 (file)
index 2fabf2c..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-#[macro_use]
-extern crate quote;
-
-use proc_macro::TokenStream;
-
-use syn::spanned::Spanned;
-use syn::{parse, ImplItemMethod, ItemImpl, ItemTrait, Token};
-
-fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
-    let output = quote! {
-        #[cfg(not(feature = "async-interface"))]
-        #parsed
-    };
-
-    for mut item in &mut parsed.items {
-        if let syn::TraitItem::Method(m) = &mut item {
-            m.sig.asyncness = Some(Token![async](m.span()));
-        }
-    }
-
-    let output = quote! {
-        #output
-
-        #[cfg(feature = "async-interface")]
-        #[async_trait(?Send)]
-        #parsed
-    };
-
-    output.into()
-}
-
-fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
-    let output = quote! {
-        #[cfg(not(feature = "async-interface"))]
-        #parsed
-    };
-
-    parsed.sig.asyncness = Some(Token![async](parsed.span()));
-
-    let output = quote! {
-        #output
-
-        #[cfg(feature = "async-interface")]
-        #parsed
-    };
-
-    output.into()
-}
-
-fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
-    let output = quote! {
-        #[cfg(not(feature = "async-interface"))]
-        #parsed
-    };
-
-    for mut item in &mut parsed.items {
-        if let syn::ImplItem::Method(m) = &mut item {
-            m.sig.asyncness = Some(Token![async](m.span()));
-        }
-    }
-
-    let output = quote! {
-        #output
-
-        #[cfg(feature = "async-interface")]
-        #[async_trait(?Send)]
-        #parsed
-    };
-
-    output.into()
-}
-
-/// Makes a method or every method of a trait `async`, if the `async-interface` feature is enabled.
-///
-/// Requires the `async-trait` crate as a dependency whenever this attribute is used on a trait
-/// definition or trait implementation.
-#[proc_macro_attribute]
-pub fn maybe_async(_attr: TokenStream, item: TokenStream) -> TokenStream {
-    if let Ok(parsed) = parse(item.clone()) {
-        add_async_trait(parsed)
-    } else if let Ok(parsed) = parse(item.clone()) {
-        add_async_method(parsed)
-    } else if let Ok(parsed) = parse(item) {
-        add_async_impl_trait(parsed)
-    } else {
-        (quote! {
-            compile_error!("#[maybe_async] can only be used on methods, trait or trait impl blocks")
-        })
-        .into()
-    }
-}
-
-/// Awaits, if the `async-interface` feature is enabled.
-#[proc_macro]
-pub fn maybe_await(expr: TokenStream) -> TokenStream {
-    let expr: proc_macro2::TokenStream = expr.into();
-    let quoted = quote! {
-        {
-            #[cfg(not(feature = "async-interface"))]
-            {
-                #expr
-            }
-
-            #[cfg(feature = "async-interface")]
-            {
-                #expr.await
-            }
-        }
-    };
-
-    quoted.into()
-}
-
-/// Awaits, if the `async-interface` feature is enabled, uses `tokio::Runtime::block_on()` otherwise
-///
-/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build.
-#[proc_macro]
-pub fn await_or_block(expr: TokenStream) -> TokenStream {
-    let expr: proc_macro2::TokenStream = expr.into();
-    let quoted = quote! {
-        {
-            #[cfg(not(feature = "async-interface"))]
-            {
-                tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(#expr)
-            }
-
-            #[cfg(feature = "async-interface")]
-            {
-                #expr.await
-            }
-        }
-    };
-
-    quoted.into()
-}
diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs
deleted file mode 100644 (file)
index 38b5f11..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Runtime-checked blockchain types
-//!
-//! This module provides the implementation of [`AnyBlockchain`] which allows switching the
-//! inner [`Blockchain`] type at runtime.
-//!
-//! ## Example
-//!
-//! When paired with the use of [`ConfigurableBlockchain`], it allows creating any
-//! blockchain type supported using a single line of code:
-//!
-//! ```no_run
-//! # use bitcoin::Network;
-//! # use bdk::blockchain::*;
-//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
-//! # {
-//! let config = serde_json::from_str("...")?;
-//! let blockchain = AnyBlockchain::from_config(&config)?;
-//! let height = blockchain.get_height();
-//! # }
-//! # Ok::<(), bdk::Error>(())
-//! ```
-
-use super::*;
-
-macro_rules! impl_from {
-    ( boxed $from:ty, $to:ty, $variant:ident, $( $cfg:tt )* ) => {
-        $( $cfg )*
-        impl From<$from> for $to {
-            fn from(inner: $from) -> Self {
-                <$to>::$variant(Box::new(inner))
-            }
-        }
-    };
-    ( $from:ty, $to:ty, $variant:ident, $( $cfg:tt )* ) => {
-        $( $cfg )*
-        impl From<$from> for $to {
-            fn from(inner: $from) -> Self {
-                <$to>::$variant(inner)
-            }
-        }
-    };
-}
-
-macro_rules! impl_inner_method {
-    ( $self:expr, $name:ident $(, $args:expr)* ) => {
-        match $self {
-            #[cfg(feature = "electrum")]
-            AnyBlockchain::Electrum(inner) => inner.$name( $($args, )* ),
-            #[cfg(feature = "esplora")]
-            AnyBlockchain::Esplora(inner) => inner.$name( $($args, )* ),
-            #[cfg(feature = "compact_filters")]
-            AnyBlockchain::CompactFilters(inner) => inner.$name( $($args, )* ),
-            #[cfg(feature = "rpc")]
-            AnyBlockchain::Rpc(inner) => inner.$name( $($args, )* ),
-        }
-    }
-}
-
-/// Type that can contain any of the [`Blockchain`] types defined by the library
-///
-/// It allows switching backend at runtime
-///
-/// See [this module](crate::blockchain::any)'s documentation for a usage example.
-pub enum AnyBlockchain {
-    #[cfg(feature = "electrum")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]
-    /// Electrum client
-    Electrum(Box<electrum::ElectrumBlockchain>),
-    #[cfg(feature = "esplora")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
-    /// Esplora client
-    Esplora(Box<esplora::EsploraBlockchain>),
-    #[cfg(feature = "compact_filters")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
-    /// Compact filters client
-    CompactFilters(Box<compact_filters::CompactFiltersBlockchain>),
-    #[cfg(feature = "rpc")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "rpc")))]
-    /// RPC client
-    Rpc(Box<rpc::RpcBlockchain>),
-}
-
-#[maybe_async]
-impl Blockchain for AnyBlockchain {
-    fn get_capabilities(&self) -> HashSet<Capability> {
-        maybe_await!(impl_inner_method!(self, get_capabilities))
-    }
-
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        maybe_await!(impl_inner_method!(self, broadcast, tx))
-    }
-
-    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
-        maybe_await!(impl_inner_method!(self, estimate_fee, target))
-    }
-}
-
-#[maybe_async]
-impl GetHeight for AnyBlockchain {
-    fn get_height(&self) -> Result<u32, Error> {
-        maybe_await!(impl_inner_method!(self, get_height))
-    }
-}
-
-#[maybe_async]
-impl GetTx for AnyBlockchain {
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        maybe_await!(impl_inner_method!(self, get_tx, txid))
-    }
-}
-
-#[maybe_async]
-impl GetBlockHash for AnyBlockchain {
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
-        maybe_await!(impl_inner_method!(self, get_block_hash, height))
-    }
-}
-
-#[maybe_async]
-impl WalletSync for AnyBlockchain {
-    fn wallet_sync<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        maybe_await!(impl_inner_method!(
-            self,
-            wallet_sync,
-            database,
-            progress_update
-        ))
-    }
-
-    fn wallet_setup<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        maybe_await!(impl_inner_method!(
-            self,
-            wallet_setup,
-            database,
-            progress_update
-        ))
-    }
-}
-
-impl_from!(boxed electrum::ElectrumBlockchain, AnyBlockchain, Electrum, #[cfg(feature = "electrum")]);
-impl_from!(boxed esplora::EsploraBlockchain, AnyBlockchain, Esplora, #[cfg(feature = "esplora")]);
-impl_from!(boxed compact_filters::CompactFiltersBlockchain, AnyBlockchain, CompactFilters, #[cfg(feature = "compact_filters")]);
-impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]);
-
-/// Type that can contain any of the blockchain configurations defined by the library
-///
-/// This allows storing a single configuration that can be loaded into an [`AnyBlockchain`]
-/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime
-/// will find this particularly useful.
-///
-/// This type can be serialized from a JSON object like:
-///
-/// ```
-/// # #[cfg(feature = "electrum")]
-/// # {
-/// use bdk::blockchain::{electrum::ElectrumBlockchainConfig, AnyBlockchainConfig};
-/// let config: AnyBlockchainConfig = serde_json::from_str(
-///     r#"{
-///    "type" : "electrum",
-///    "url" : "ssl://electrum.blockstream.info:50002",
-///    "retry": 2,
-///    "stop_gap": 20,
-///    "validate_domain": true
-/// }"#,
-/// )
-/// .unwrap();
-/// assert_eq!(
-///     config,
-///     AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
-///         url: "ssl://electrum.blockstream.info:50002".into(),
-///         retry: 2,
-///         socks5: None,
-///         timeout: None,
-///         stop_gap: 20,
-///         validate_domain: true,
-///     })
-/// );
-/// # }
-/// ```
-#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
-#[serde(tag = "type", rename_all = "snake_case")]
-pub enum AnyBlockchainConfig {
-    #[cfg(feature = "electrum")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]
-    /// Electrum client
-    Electrum(electrum::ElectrumBlockchainConfig),
-    #[cfg(feature = "esplora")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
-    /// Esplora client
-    Esplora(esplora::EsploraBlockchainConfig),
-    #[cfg(feature = "compact_filters")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
-    /// Compact filters client
-    CompactFilters(compact_filters::CompactFiltersBlockchainConfig),
-    #[cfg(feature = "rpc")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "rpc")))]
-    /// RPC client configuration
-    Rpc(rpc::RpcConfig),
-}
-
-impl ConfigurableBlockchain for AnyBlockchain {
-    type Config = AnyBlockchainConfig;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        Ok(match config {
-            #[cfg(feature = "electrum")]
-            AnyBlockchainConfig::Electrum(inner) => {
-                AnyBlockchain::Electrum(Box::new(electrum::ElectrumBlockchain::from_config(inner)?))
-            }
-            #[cfg(feature = "esplora")]
-            AnyBlockchainConfig::Esplora(inner) => {
-                AnyBlockchain::Esplora(Box::new(esplora::EsploraBlockchain::from_config(inner)?))
-            }
-            #[cfg(feature = "compact_filters")]
-            AnyBlockchainConfig::CompactFilters(inner) => AnyBlockchain::CompactFilters(Box::new(
-                compact_filters::CompactFiltersBlockchain::from_config(inner)?,
-            )),
-            #[cfg(feature = "rpc")]
-            AnyBlockchainConfig::Rpc(inner) => {
-                AnyBlockchain::Rpc(Box::new(rpc::RpcBlockchain::from_config(inner)?))
-            }
-        })
-    }
-}
-
-impl_from!(electrum::ElectrumBlockchainConfig, AnyBlockchainConfig, Electrum, #[cfg(feature = "electrum")]);
-impl_from!(esplora::EsploraBlockchainConfig, AnyBlockchainConfig, Esplora, #[cfg(feature = "esplora")]);
-impl_from!(compact_filters::CompactFiltersBlockchainConfig, AnyBlockchainConfig, CompactFilters, #[cfg(feature = "compact_filters")]);
-impl_from!(rpc::RpcConfig, AnyBlockchainConfig, Rpc, #[cfg(feature = "rpc")]);
diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs
deleted file mode 100644 (file)
index 7845d51..0000000
+++ /dev/null
@@ -1,618 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Compact Filters
-//!
-//! This module contains a multithreaded implementation of an [`Blockchain`] backend that
-//! uses BIP157 (aka "Neutrino") to populate the wallet's [database](crate::database::Database)
-//! by downloading compact filters from the P2P network.
-//!
-//! Since there are currently very few peers "in the wild" that advertise the required service
-//! flag, this implementation requires that one or more known peers are provided by the user.
-//! No dns or other kinds of peer discovery are done internally.
-//!
-//! Moreover, this module doesn't currently support detecting and resolving conflicts between
-//! messages received by different peers. Thus, it's recommended to use this module by only
-//! connecting to a single peer at a time, optionally by opening multiple connections if it's
-//! desirable to use multiple threads at once to sync in parallel.
-//!
-//! This is an **EXPERIMENTAL** feature, API and other major changes are expected.
-//!
-//! ## Example
-//!
-//! ```no_run
-//! # use std::sync::Arc;
-//! # use bitcoin::*;
-//! # use bdk::*;
-//! # use bdk::blockchain::compact_filters::*;
-//! let num_threads = 4;
-//!
-//! let mempool = Arc::new(Mempool::default());
-//! let peers = (0..num_threads)
-//!     .map(|_| {
-//!         Peer::connect(
-//!             "btcd-mainnet.lightning.computer:8333",
-//!             Arc::clone(&mempool),
-//!             Network::Bitcoin,
-//!         )
-//!     })
-//!     .collect::<Result<_, _>>()?;
-//! let blockchain = CompactFiltersBlockchain::new(peers, "./wallet-filters", Some(500_000))?;
-//! # Ok::<(), CompactFiltersError>(())
-//! ```
-
-use std::collections::HashSet;
-use std::fmt;
-use std::ops::DerefMut;
-use std::path::Path;
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::{Arc, Mutex};
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-use bitcoin::network::message_blockdata::Inventory;
-use bitcoin::{Network, OutPoint, Transaction, Txid};
-
-use rocksdb::{Options, SliceTransform, DB};
-
-mod peer;
-mod store;
-mod sync;
-
-use crate::blockchain::*;
-use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
-use crate::error::Error;
-use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
-use crate::{BlockTime, FeeRate};
-
-use peer::*;
-use store::*;
-use sync::*;
-
-pub use peer::{Mempool, Peer};
-
-const SYNC_HEADERS_COST: f32 = 1.0;
-const SYNC_FILTERS_COST: f32 = 11.6 * 1_000.0;
-const PROCESS_BLOCKS_COST: f32 = 20_000.0;
-
-/// Structure implementing the required blockchain traits
-///
-/// ## Example
-/// See the [`blockchain::compact_filters`](crate::blockchain::compact_filters) module for a usage example.
-#[derive(Debug)]
-pub struct CompactFiltersBlockchain {
-    peers: Vec<Arc<Peer>>,
-    headers: Arc<ChainStore<Full>>,
-    skip_blocks: Option<usize>,
-}
-
-impl CompactFiltersBlockchain {
-    /// Construct a new instance given a list of peers, a path to store headers and block
-    /// filters downloaded during the sync and optionally a number of blocks to ignore starting
-    /// from the genesis while scanning for the wallet's outputs.
-    ///
-    /// For each [`Peer`] specified a new thread will be spawned to download and verify the filters
-    /// in parallel. It's currently recommended to only connect to a single peer to avoid
-    /// inconsistencies in the data returned, optionally with multiple connections in parallel to
-    /// speed-up the sync process.
-    pub fn new<P: AsRef<Path>>(
-        peers: Vec<Peer>,
-        storage_dir: P,
-        skip_blocks: Option<usize>,
-    ) -> Result<Self, CompactFiltersError> {
-        if peers.is_empty() {
-            return Err(CompactFiltersError::NoPeers);
-        }
-
-        let mut opts = Options::default();
-        opts.create_if_missing(true);
-        opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(16));
-
-        let network = peers[0].get_network();
-
-        let cfs = DB::list_cf(&opts, &storage_dir).unwrap_or_else(|_| vec!["default".to_string()]);
-        let db = DB::open_cf(&opts, &storage_dir, &cfs)?;
-        let headers = Arc::new(ChainStore::new(db, network)?);
-
-        // try to recover partial snapshots
-        for cf_name in &cfs {
-            if !cf_name.starts_with("_headers:") {
-                continue;
-            }
-
-            info!("Trying to recover: {:?}", cf_name);
-            headers.recover_snapshot(cf_name)?;
-        }
-
-        Ok(CompactFiltersBlockchain {
-            peers: peers.into_iter().map(Arc::new).collect(),
-            headers,
-            skip_blocks,
-        })
-    }
-
-    /// Process a transaction by looking for inputs that spend from a UTXO in the database or
-    /// outputs that send funds to a know script_pubkey.
-    fn process_tx<D: BatchDatabase>(
-        &self,
-        database: &mut D,
-        tx: &Transaction,
-        height: Option<u32>,
-        timestamp: Option<u64>,
-        internal_max_deriv: &mut Option<u32>,
-        external_max_deriv: &mut Option<u32>,
-    ) -> Result<(), Error> {
-        let mut updates = database.begin_batch();
-
-        let mut incoming: u64 = 0;
-        let mut outgoing: u64 = 0;
-
-        let mut inputs_sum: u64 = 0;
-        let mut outputs_sum: u64 = 0;
-
-        // look for our own inputs
-        for (i, input) in tx.input.iter().enumerate() {
-            if let Some(previous_output) = database.get_previous_output(&input.previous_output)? {
-                inputs_sum += previous_output.value;
-
-                // this output is ours, we have a path to derive it
-                if let Some((keychain, _)) =
-                    database.get_path_from_script_pubkey(&previous_output.script_pubkey)?
-                {
-                    outgoing += previous_output.value;
-
-                    debug!("{} input #{} is mine, setting utxo as spent", tx.txid(), i);
-                    updates.set_utxo(&LocalUtxo {
-                        outpoint: input.previous_output,
-                        txout: previous_output.clone(),
-                        keychain,
-                        is_spent: true,
-                    })?;
-                }
-            }
-        }
-
-        for (i, output) in tx.output.iter().enumerate() {
-            // to compute the fees later
-            outputs_sum += output.value;
-
-            // this output is ours, we have a path to derive it
-            if let Some((keychain, child)) =
-                database.get_path_from_script_pubkey(&output.script_pubkey)?
-            {
-                debug!("{} output #{} is mine, adding utxo", tx.txid(), i);
-                updates.set_utxo(&LocalUtxo {
-                    outpoint: OutPoint::new(tx.txid(), i as u32),
-                    txout: output.clone(),
-                    keychain,
-                    is_spent: false,
-                })?;
-                incoming += output.value;
-
-                if keychain == KeychainKind::Internal
-                    && (internal_max_deriv.is_none() || child > internal_max_deriv.unwrap_or(0))
-                {
-                    *internal_max_deriv = Some(child);
-                } else if keychain == KeychainKind::External
-                    && (external_max_deriv.is_none() || child > external_max_deriv.unwrap_or(0))
-                {
-                    *external_max_deriv = Some(child);
-                }
-            }
-        }
-
-        if incoming > 0 || outgoing > 0 {
-            let tx = TransactionDetails {
-                txid: tx.txid(),
-                transaction: Some(tx.clone()),
-                received: incoming,
-                sent: outgoing,
-                confirmation_time: BlockTime::new(height, timestamp),
-                fee: Some(inputs_sum.saturating_sub(outputs_sum)),
-            };
-
-            info!("Saving tx {}", tx.txid);
-            updates.set_tx(&tx)?;
-        }
-
-        database.commit_batch(updates)?;
-
-        Ok(())
-    }
-}
-
-impl Blockchain for CompactFiltersBlockchain {
-    fn get_capabilities(&self) -> HashSet<Capability> {
-        vec![Capability::FullHistory].into_iter().collect()
-    }
-
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        self.peers[0].broadcast_tx(tx.clone())?;
-
-        Ok(())
-    }
-
-    fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
-        // TODO
-        Ok(FeeRate::default())
-    }
-}
-
-impl GetHeight for CompactFiltersBlockchain {
-    fn get_height(&self) -> Result<u32, Error> {
-        Ok(self.headers.get_height()? as u32)
-    }
-}
-
-impl GetTx for CompactFiltersBlockchain {
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        Ok(self.peers[0]
-            .get_mempool()
-            .get_tx(&Inventory::Transaction(*txid)))
-    }
-}
-
-impl GetBlockHash for CompactFiltersBlockchain {
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
-        self.headers
-            .get_block_hash(height as usize)?
-            .ok_or(Error::CompactFilters(
-                CompactFiltersError::BlockHashNotFound,
-            ))
-    }
-}
-
-impl WalletSync for CompactFiltersBlockchain {
-    #[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
-    fn wallet_setup<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        let first_peer = &self.peers[0];
-
-        let skip_blocks = self.skip_blocks.unwrap_or(0);
-
-        let cf_sync = Arc::new(CfSync::new(Arc::clone(&self.headers), skip_blocks, 0x00)?);
-
-        let initial_height = self.headers.get_height()?;
-        let total_bundles = (first_peer.get_version().start_height as usize)
-            .checked_sub(skip_blocks)
-            .map(|x| x / 1000)
-            .unwrap_or(0)
-            + 1;
-        let expected_bundles_to_sync = total_bundles.saturating_sub(cf_sync.pruned_bundles()?);
-
-        let headers_cost = (first_peer.get_version().start_height as usize)
-            .saturating_sub(initial_height) as f32
-            * SYNC_HEADERS_COST;
-        let filters_cost = expected_bundles_to_sync as f32 * SYNC_FILTERS_COST;
-
-        let total_cost = headers_cost + filters_cost + PROCESS_BLOCKS_COST;
-
-        if let Some(snapshot) = sync::sync_headers(
-            Arc::clone(first_peer),
-            Arc::clone(&self.headers),
-            |new_height| {
-                let local_headers_cost =
-                    new_height.saturating_sub(initial_height) as f32 * SYNC_HEADERS_COST;
-                progress_update.update(
-                    local_headers_cost / total_cost * 100.0,
-                    Some(format!("Synced headers to {}", new_height)),
-                )
-            },
-        )? {
-            if snapshot.work()? > self.headers.work()? {
-                info!("Applying snapshot with work: {}", snapshot.work()?);
-                self.headers.apply_snapshot(snapshot)?;
-            }
-        }
-
-        let synced_height = self.headers.get_height()?;
-        let buried_height = synced_height.saturating_sub(sync::BURIED_CONFIRMATIONS);
-        info!("Synced headers to height: {}", synced_height);
-
-        cf_sync.prepare_sync(Arc::clone(first_peer))?;
-
-        let mut database = database.borrow_mut();
-        let database = database.deref_mut();
-
-        let all_scripts = Arc::new(
-            database
-                .iter_script_pubkeys(None)?
-                .into_iter()
-                .map(|s| s.to_bytes())
-                .collect::<Vec<_>>(),
-        );
-
-        #[allow(clippy::mutex_atomic)]
-        let last_synced_block = Arc::new(Mutex::new(synced_height));
-
-        let synced_bundles = Arc::new(AtomicUsize::new(0));
-        let progress_update = Arc::new(Mutex::new(progress_update));
-
-        let mut threads = Vec::with_capacity(self.peers.len());
-        for peer in &self.peers {
-            let cf_sync = Arc::clone(&cf_sync);
-            let peer = Arc::clone(peer);
-            let headers = Arc::clone(&self.headers);
-            let all_scripts = Arc::clone(&all_scripts);
-            let last_synced_block = Arc::clone(&last_synced_block);
-            let progress_update = Arc::clone(&progress_update);
-            let synced_bundles = Arc::clone(&synced_bundles);
-
-            let thread = std::thread::spawn(move || {
-                cf_sync.capture_thread_for_sync(
-                    peer,
-                    |block_hash, filter| {
-                        if !filter
-                            .match_any(block_hash, &mut all_scripts.iter().map(AsRef::as_ref))?
-                        {
-                            return Ok(false);
-                        }
-
-                        let block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
-                        let saved_correct_block = matches!(headers.get_full_block(block_height)?, Some(block) if &block.block_hash() == block_hash);
-
-                        if saved_correct_block {
-                            Ok(false)
-                        } else {
-                            let mut last_synced_block = last_synced_block.lock().unwrap();
-
-                            // If we download a block older than `last_synced_block`, we update it so that
-                            // we know to delete and re-process all txs starting from that height
-                            if block_height < *last_synced_block {
-                                *last_synced_block = block_height;
-                            }
-
-                            Ok(true)
-                        }
-                    },
-                    |index| {
-                        let synced_bundles = synced_bundles.fetch_add(1, Ordering::SeqCst);
-                        let local_filters_cost = synced_bundles as f32 * SYNC_FILTERS_COST;
-                        progress_update.lock().unwrap().update(
-                            (headers_cost + local_filters_cost) / total_cost * 100.0,
-                            Some(format!(
-                                "Synced filters {} - {}",
-                                index * 1000 + 1,
-                                (index + 1) * 1000
-                            )),
-                        )
-                    },
-                )
-            });
-
-            threads.push(thread);
-        }
-
-        for t in threads {
-            t.join().unwrap()?;
-        }
-
-        progress_update.lock().unwrap().update(
-            (headers_cost + filters_cost) / total_cost * 100.0,
-            Some("Processing downloaded blocks and mempool".into()),
-        )?;
-
-        // delete all txs newer than last_synced_block
-        let last_synced_block = *last_synced_block.lock().unwrap();
-        log::debug!(
-            "Dropping transactions newer than `last_synced_block` = {}",
-            last_synced_block
-        );
-        let mut updates = database.begin_batch();
-        for details in database.iter_txs(false)? {
-            match details.confirmation_time {
-                Some(c) if (c.height as usize) < last_synced_block => continue,
-                _ => updates.del_tx(&details.txid, false)?,
-            };
-        }
-        database.commit_batch(updates)?;
-
-        match first_peer.ask_for_mempool() {
-            Err(CompactFiltersError::PeerBloomDisabled) => {
-                log::warn!("Peer has BLOOM disabled, we can't ask for the mempool")
-            }
-            e => e?,
-        };
-
-        let mut internal_max_deriv = None;
-        let mut external_max_deriv = None;
-
-        for (height, block) in self.headers.iter_full_blocks()? {
-            for tx in &block.txdata {
-                self.process_tx(
-                    database,
-                    tx,
-                    Some(height as u32),
-                    None,
-                    &mut internal_max_deriv,
-                    &mut external_max_deriv,
-                )?;
-            }
-        }
-        for tx in first_peer.get_mempool().iter_txs().iter() {
-            self.process_tx(
-                database,
-                tx,
-                None,
-                None,
-                &mut internal_max_deriv,
-                &mut external_max_deriv,
-            )?;
-        }
-
-        let current_ext = database
-            .get_last_index(KeychainKind::External)?
-            .unwrap_or(0);
-        let first_ext_new = external_max_deriv.map(|x| x + 1).unwrap_or(0);
-        if first_ext_new > current_ext {
-            info!("Setting external index to {}", first_ext_new);
-            database.set_last_index(KeychainKind::External, first_ext_new)?;
-        }
-
-        let current_int = database
-            .get_last_index(KeychainKind::Internal)?
-            .unwrap_or(0);
-        let first_int_new = internal_max_deriv.map(|x| x + 1).unwrap_or(0);
-        if first_int_new > current_int {
-            info!("Setting internal index to {}", first_int_new);
-            database.set_last_index(KeychainKind::Internal, first_int_new)?;
-        }
-
-        info!("Dropping blocks until {}", buried_height);
-        self.headers.delete_blocks_until(buried_height)?;
-
-        progress_update
-            .lock()
-            .unwrap()
-            .update(100.0, Some("Done".into()))?;
-
-        Ok(())
-    }
-}
-
-/// Data to connect to a Bitcoin P2P peer
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
-pub struct BitcoinPeerConfig {
-    /// Peer address such as 127.0.0.1:18333
-    pub address: String,
-    /// Optional socks5 proxy
-    pub socks5: Option<String>,
-    /// Optional socks5 proxy credentials
-    pub socks5_credentials: Option<(String, String)>,
-}
-
-/// Configuration for a [`CompactFiltersBlockchain`]
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
-pub struct CompactFiltersBlockchainConfig {
-    /// List of peers to try to connect to for asking headers and filters
-    pub peers: Vec<BitcoinPeerConfig>,
-    /// Network used
-    pub network: Network,
-    /// Storage dir to save partially downloaded headers and full blocks. Should be a separate directory per descriptor. Consider using [crate::wallet::wallet_name_from_descriptor] for this.
-    pub storage_dir: String,
-    /// Optionally skip initial `skip_blocks` blocks (default: 0)
-    pub skip_blocks: Option<usize>,
-}
-
-impl ConfigurableBlockchain for CompactFiltersBlockchain {
-    type Config = CompactFiltersBlockchainConfig;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        let mempool = Arc::new(Mempool::default());
-        let peers = config
-            .peers
-            .iter()
-            .map(|peer_conf| match &peer_conf.socks5 {
-                None => Peer::connect(&peer_conf.address, Arc::clone(&mempool), config.network),
-                Some(proxy) => Peer::connect_proxy(
-                    peer_conf.address.as_str(),
-                    proxy,
-                    peer_conf
-                        .socks5_credentials
-                        .as_ref()
-                        .map(|(a, b)| (a.as_str(), b.as_str())),
-                    Arc::clone(&mempool),
-                    config.network,
-                ),
-            })
-            .collect::<Result<_, _>>()?;
-
-        Ok(CompactFiltersBlockchain::new(
-            peers,
-            &config.storage_dir,
-            config.skip_blocks,
-        )?)
-    }
-}
-
-/// An error that can occur during sync with a [`CompactFiltersBlockchain`]
-#[derive(Debug)]
-pub enum CompactFiltersError {
-    /// A peer sent an invalid or unexpected response
-    InvalidResponse,
-    /// The headers returned are invalid
-    InvalidHeaders,
-    /// The compact filter headers returned are invalid
-    InvalidFilterHeader,
-    /// The compact filter returned is invalid
-    InvalidFilter,
-    /// The peer is missing a block in the valid chain
-    MissingBlock,
-    /// Block hash at specified height not found
-    BlockHashNotFound,
-    /// The data stored in the block filters storage are corrupted
-    DataCorruption,
-
-    /// A peer is not connected
-    NotConnected,
-    /// A peer took too long to reply to one of our messages
-    Timeout,
-    /// The peer doesn't advertise the [`BLOOM`](bitcoin::network::constants::ServiceFlags::BLOOM) service flag
-    PeerBloomDisabled,
-
-    /// No peers have been specified
-    NoPeers,
-
-    /// Internal database error
-    Db(rocksdb::Error),
-    /// Internal I/O error
-    Io(std::io::Error),
-    /// Invalid BIP158 filter
-    Bip158(bitcoin::util::bip158::Error),
-    /// Internal system time error
-    Time(std::time::SystemTimeError),
-
-    /// Wrapper for [`crate::error::Error`]
-    Global(Box<crate::error::Error>),
-}
-
-impl fmt::Display for CompactFiltersError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::InvalidResponse => write!(f, "A peer sent an invalid or unexpected response"),
-            Self::InvalidHeaders => write!(f, "Invalid headers"),
-            Self::InvalidFilterHeader => write!(f, "Invalid filter header"),
-            Self::InvalidFilter => write!(f, "Invalid filters"),
-            Self::MissingBlock => write!(f, "The peer is missing a block in the valid chain"),
-            Self::BlockHashNotFound => write!(f, "Block hash not found"),
-            Self::DataCorruption => write!(
-                f,
-                "The data stored in the block filters storage are corrupted"
-            ),
-            Self::NotConnected => write!(f, "A peer is not connected"),
-            Self::Timeout => write!(f, "A peer took too long to reply to one of our messages"),
-            Self::PeerBloomDisabled => write!(f, "Peer doesn't advertise the BLOOM service flag"),
-            Self::NoPeers => write!(f, "No peers have been specified"),
-            Self::Db(err) => write!(f, "Internal database error: {}", err),
-            Self::Io(err) => write!(f, "Internal I/O error: {}", err),
-            Self::Bip158(err) => write!(f, "Invalid BIP158 filter: {}", err),
-            Self::Time(err) => write!(f, "Invalid system time: {}", err),
-            Self::Global(err) => write!(f, "Generic error: {}", err),
-        }
-    }
-}
-
-impl std::error::Error for CompactFiltersError {}
-
-impl_error!(rocksdb::Error, Db, CompactFiltersError);
-impl_error!(std::io::Error, Io, CompactFiltersError);
-impl_error!(bitcoin::util::bip158::Error, Bip158, CompactFiltersError);
-impl_error!(std::time::SystemTimeError, Time, CompactFiltersError);
-
-impl From<crate::error::Error> for CompactFiltersError {
-    fn from(err: crate::error::Error) -> Self {
-        CompactFiltersError::Global(Box::new(err))
-    }
-}
diff --git a/src/blockchain/compact_filters/peer.rs b/src/blockchain/compact_filters/peer.rs
deleted file mode 100644 (file)
index 665a033..0000000
+++ /dev/null
@@ -1,576 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use std::collections::HashMap;
-use std::io::BufReader;
-use std::net::{TcpStream, ToSocketAddrs};
-use std::sync::{Arc, Condvar, Mutex, RwLock};
-use std::thread;
-use std::time::{Duration, SystemTime, UNIX_EPOCH};
-
-use socks::{Socks5Stream, ToTargetAddr};
-
-use rand::{thread_rng, Rng};
-
-use bitcoin::consensus::{Decodable, Encodable};
-use bitcoin::hash_types::BlockHash;
-use bitcoin::network::constants::ServiceFlags;
-use bitcoin::network::message::{NetworkMessage, RawNetworkMessage};
-use bitcoin::network::message_blockdata::*;
-use bitcoin::network::message_filter::*;
-use bitcoin::network::message_network::VersionMessage;
-use bitcoin::network::Address;
-use bitcoin::{Block, Network, Transaction, Txid, Wtxid};
-
-use super::CompactFiltersError;
-
-type ResponsesMap = HashMap<&'static str, Arc<(Mutex<Vec<NetworkMessage>>, Condvar)>>;
-
-pub(crate) const TIMEOUT_SECS: u64 = 30;
-
-/// Container for unconfirmed, but valid Bitcoin transactions
-///
-/// It is normally shared between [`Peer`]s with the use of [`Arc`], so that transactions are not
-/// duplicated in memory.
-#[derive(Debug, Default)]
-pub struct Mempool(RwLock<InnerMempool>);
-
-#[derive(Debug, Default)]
-struct InnerMempool {
-    txs: HashMap<Txid, Transaction>,
-    wtxids: HashMap<Wtxid, Txid>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-enum TxIdentifier {
-    Wtxid(Wtxid),
-    Txid(Txid),
-}
-
-impl Mempool {
-    /// Create a new empty mempool
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    /// Add a transaction to the mempool
-    ///
-    /// Note that this doesn't propagate the transaction to other
-    /// peers. To do that, [`broadcast`](crate::blockchain::Blockchain::broadcast) should be used.
-    pub fn add_tx(&self, tx: Transaction) {
-        let mut guard = self.0.write().unwrap();
-
-        guard.wtxids.insert(tx.wtxid(), tx.txid());
-        guard.txs.insert(tx.txid(), tx);
-    }
-
-    /// Look-up a transaction in the mempool given an [`Inventory`] request
-    pub fn get_tx(&self, inventory: &Inventory) -> Option<Transaction> {
-        let identifer = match inventory {
-            Inventory::Error
-            | Inventory::Block(_)
-            | Inventory::WitnessBlock(_)
-            | Inventory::CompactBlock(_) => return None,
-            Inventory::Transaction(txid) => TxIdentifier::Txid(*txid),
-            Inventory::WitnessTransaction(txid) => TxIdentifier::Txid(*txid),
-            Inventory::WTx(wtxid) => TxIdentifier::Wtxid(*wtxid),
-            Inventory::Unknown { inv_type, hash } => {
-                log::warn!(
-                    "Unknown inventory request type `{}`, hash `{:?}`",
-                    inv_type,
-                    hash
-                );
-                return None;
-            }
-        };
-
-        let txid = match identifer {
-            TxIdentifier::Txid(txid) => Some(txid),
-            TxIdentifier::Wtxid(wtxid) => self.0.read().unwrap().wtxids.get(&wtxid).cloned(),
-        };
-
-        txid.and_then(|txid| self.0.read().unwrap().txs.get(&txid).cloned())
-    }
-
-    /// Return whether or not the mempool contains a transaction with a given txid
-    pub fn has_tx(&self, txid: &Txid) -> bool {
-        self.0.read().unwrap().txs.contains_key(txid)
-    }
-
-    /// Return the list of transactions contained in the mempool
-    pub fn iter_txs(&self) -> Vec<Transaction> {
-        self.0.read().unwrap().txs.values().cloned().collect()
-    }
-}
-
-/// A Bitcoin peer
-#[derive(Debug)]
-#[allow(dead_code)]
-pub struct Peer {
-    writer: Arc<Mutex<TcpStream>>,
-    responses: Arc<RwLock<ResponsesMap>>,
-
-    reader_thread: thread::JoinHandle<()>,
-    connected: Arc<RwLock<bool>>,
-
-    mempool: Arc<Mempool>,
-
-    version: VersionMessage,
-    network: Network,
-}
-
-impl Peer {
-    /// Connect to a peer over a plaintext TCP connection
-    ///
-    /// This function internally spawns a new thread that will monitor incoming messages from the
-    /// peer, and optionally reply to some of them transparently, like [pings](bitcoin::network::message::NetworkMessage::Ping)
-    pub fn connect<A: ToSocketAddrs>(
-        address: A,
-        mempool: Arc<Mempool>,
-        network: Network,
-    ) -> Result<Self, CompactFiltersError> {
-        let stream = TcpStream::connect(address)?;
-
-        Peer::from_stream(stream, mempool, network)
-    }
-
-    /// Connect to a peer through a SOCKS5 proxy, optionally by using some credentials, specified
-    /// as a tuple of `(username, password)`
-    ///
-    /// This function internally spawns a new thread that will monitor incoming messages from the
-    /// peer, and optionally reply to some of them transparently, like [pings](NetworkMessage::Ping)
-    pub fn connect_proxy<T: ToTargetAddr, P: ToSocketAddrs>(
-        target: T,
-        proxy: P,
-        credentials: Option<(&str, &str)>,
-        mempool: Arc<Mempool>,
-        network: Network,
-    ) -> Result<Self, CompactFiltersError> {
-        let socks_stream = if let Some((username, password)) = credentials {
-            Socks5Stream::connect_with_password(proxy, target, username, password)?
-        } else {
-            Socks5Stream::connect(proxy, target)?
-        };
-
-        Peer::from_stream(socks_stream.into_inner(), mempool, network)
-    }
-
-    /// Create a [`Peer`] from an already connected TcpStream
-    fn from_stream(
-        stream: TcpStream,
-        mempool: Arc<Mempool>,
-        network: Network,
-    ) -> Result<Self, CompactFiltersError> {
-        let writer = Arc::new(Mutex::new(stream.try_clone()?));
-        let responses: Arc<RwLock<ResponsesMap>> = Arc::new(RwLock::new(HashMap::new()));
-        let connected = Arc::new(RwLock::new(true));
-
-        let mut locked_writer = writer.lock().unwrap();
-
-        let reader_thread_responses = Arc::clone(&responses);
-        let reader_thread_writer = Arc::clone(&writer);
-        let reader_thread_mempool = Arc::clone(&mempool);
-        let reader_thread_connected = Arc::clone(&connected);
-        let reader_thread = thread::spawn(move || {
-            Self::reader_thread(
-                network,
-                stream,
-                reader_thread_responses,
-                reader_thread_writer,
-                reader_thread_mempool,
-                reader_thread_connected,
-            )
-        });
-
-        let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
-        let nonce = thread_rng().gen();
-        let receiver = Address::new(&locked_writer.peer_addr()?, ServiceFlags::NONE);
-        let sender = Address {
-            services: ServiceFlags::NONE,
-            address: [0u16; 8],
-            port: 0,
-        };
-
-        Self::_send(
-            &mut locked_writer,
-            network.magic(),
-            NetworkMessage::Version(VersionMessage::new(
-                ServiceFlags::WITNESS,
-                timestamp,
-                receiver,
-                sender,
-                nonce,
-                "MagicalBitcoinWallet".into(),
-                0,
-            )),
-        )?;
-        let version = if let NetworkMessage::Version(version) =
-            Self::_recv(&responses, "version", None).unwrap()
-        {
-            version
-        } else {
-            return Err(CompactFiltersError::InvalidResponse);
-        };
-
-        if let NetworkMessage::Verack = Self::_recv(&responses, "verack", None).unwrap() {
-            Self::_send(&mut locked_writer, network.magic(), NetworkMessage::Verack)?;
-        } else {
-            return Err(CompactFiltersError::InvalidResponse);
-        }
-
-        std::mem::drop(locked_writer);
-
-        Ok(Peer {
-            writer,
-            responses,
-            reader_thread,
-            connected,
-            mempool,
-            version,
-            network,
-        })
-    }
-
-    /// Send a Bitcoin network message
-    fn _send(
-        writer: &mut TcpStream,
-        magic: u32,
-        payload: NetworkMessage,
-    ) -> Result<(), CompactFiltersError> {
-        log::trace!("==> {:?}", payload);
-
-        let raw_message = RawNetworkMessage { magic, payload };
-
-        raw_message
-            .consensus_encode(writer)
-            .map_err(|_| CompactFiltersError::DataCorruption)?;
-
-        Ok(())
-    }
-
-    /// Wait for a specific incoming Bitcoin message, optionally with a timeout
-    fn _recv(
-        responses: &Arc<RwLock<ResponsesMap>>,
-        wait_for: &'static str,
-        timeout: Option<Duration>,
-    ) -> Option<NetworkMessage> {
-        let message_resp = {
-            let mut lock = responses.write().unwrap();
-            let message_resp = lock.entry(wait_for).or_default();
-            Arc::clone(message_resp)
-        };
-
-        let (lock, cvar) = &*message_resp;
-
-        let mut messages = lock.lock().unwrap();
-        while messages.is_empty() {
-            match timeout {
-                None => messages = cvar.wait(messages).unwrap(),
-                Some(t) => {
-                    let result = cvar.wait_timeout(messages, t).unwrap();
-                    if result.1.timed_out() {
-                        return None;
-                    }
-                    messages = result.0;
-                }
-            }
-        }
-
-        messages.pop()
-    }
-
-    /// Return the [`VersionMessage`] sent by the peer
-    pub fn get_version(&self) -> &VersionMessage {
-        &self.version
-    }
-
-    /// Return the Bitcoin [`Network`] in use
-    pub fn get_network(&self) -> Network {
-        self.network
-    }
-
-    /// Return the mempool used by this peer
-    pub fn get_mempool(&self) -> Arc<Mempool> {
-        Arc::clone(&self.mempool)
-    }
-
-    /// Return whether or not the peer is still connected
-    pub fn is_connected(&self) -> bool {
-        *self.connected.read().unwrap()
-    }
-
-    /// Internal function called once the `reader_thread` is spawned
-    fn reader_thread(
-        network: Network,
-        connection: TcpStream,
-        reader_thread_responses: Arc<RwLock<ResponsesMap>>,
-        reader_thread_writer: Arc<Mutex<TcpStream>>,
-        reader_thread_mempool: Arc<Mempool>,
-        reader_thread_connected: Arc<RwLock<bool>>,
-    ) {
-        macro_rules! check_disconnect {
-            ($call:expr) => {
-                match $call {
-                    Ok(good) => good,
-                    Err(e) => {
-                        log::debug!("Error {:?}", e);
-                        *reader_thread_connected.write().unwrap() = false;
-
-                        break;
-                    }
-                }
-            };
-        }
-
-        let mut reader = BufReader::new(connection);
-        loop {
-            let raw_message: RawNetworkMessage =
-                check_disconnect!(Decodable::consensus_decode(&mut reader));
-
-            let in_message = if raw_message.magic != network.magic() {
-                continue;
-            } else {
-                raw_message.payload
-            };
-
-            log::trace!("<== {:?}", in_message);
-
-            match in_message {
-                NetworkMessage::Ping(nonce) => {
-                    check_disconnect!(Self::_send(
-                        &mut reader_thread_writer.lock().unwrap(),
-                        network.magic(),
-                        NetworkMessage::Pong(nonce),
-                    ));
-
-                    continue;
-                }
-                NetworkMessage::Alert(_) => continue,
-                NetworkMessage::GetData(ref inv) => {
-                    let (found, not_found): (Vec<_>, Vec<_>) = inv
-                        .iter()
-                        .map(|item| (*item, reader_thread_mempool.get_tx(item)))
-                        .partition(|(_, d)| d.is_some());
-                    for (_, found_tx) in found {
-                        check_disconnect!(Self::_send(
-                            &mut reader_thread_writer.lock().unwrap(),
-                            network.magic(),
-                            NetworkMessage::Tx(found_tx.unwrap()),
-                        ));
-                    }
-
-                    if !not_found.is_empty() {
-                        check_disconnect!(Self::_send(
-                            &mut reader_thread_writer.lock().unwrap(),
-                            network.magic(),
-                            NetworkMessage::NotFound(
-                                not_found.into_iter().map(|(i, _)| i).collect(),
-                            ),
-                        ));
-                    }
-                }
-                _ => {}
-            }
-
-            let message_resp = {
-                let mut lock = reader_thread_responses.write().unwrap();
-                let message_resp = lock.entry(in_message.cmd()).or_default();
-                Arc::clone(message_resp)
-            };
-
-            let (lock, cvar) = &*message_resp;
-            let mut messages = lock.lock().unwrap();
-            messages.push(in_message);
-            cvar.notify_all();
-        }
-    }
-
-    /// Send a raw Bitcoin message to the peer
-    pub fn send(&self, payload: NetworkMessage) -> Result<(), CompactFiltersError> {
-        let mut writer = self.writer.lock().unwrap();
-        Self::_send(&mut writer, self.network.magic(), payload)
-    }
-
-    /// Waits for a specific incoming Bitcoin message, optionally with a timeout
-    pub fn recv(
-        &self,
-        wait_for: &'static str,
-        timeout: Option<Duration>,
-    ) -> Result<Option<NetworkMessage>, CompactFiltersError> {
-        Ok(Self::_recv(&self.responses, wait_for, timeout))
-    }
-}
-
-pub trait CompactFiltersPeer {
-    fn get_cf_checkpt(
-        &self,
-        filter_type: u8,
-        stop_hash: BlockHash,
-    ) -> Result<CFCheckpt, CompactFiltersError>;
-    fn get_cf_headers(
-        &self,
-        filter_type: u8,
-        start_height: u32,
-        stop_hash: BlockHash,
-    ) -> Result<CFHeaders, CompactFiltersError>;
-    fn get_cf_filters(
-        &self,
-        filter_type: u8,
-        start_height: u32,
-        stop_hash: BlockHash,
-    ) -> Result<(), CompactFiltersError>;
-    fn pop_cf_filter_resp(&self) -> Result<CFilter, CompactFiltersError>;
-}
-
-impl CompactFiltersPeer for Peer {
-    fn get_cf_checkpt(
-        &self,
-        filter_type: u8,
-        stop_hash: BlockHash,
-    ) -> Result<CFCheckpt, CompactFiltersError> {
-        self.send(NetworkMessage::GetCFCheckpt(GetCFCheckpt {
-            filter_type,
-            stop_hash,
-        }))?;
-
-        let response = self
-            .recv("cfcheckpt", Some(Duration::from_secs(TIMEOUT_SECS)))?
-            .ok_or(CompactFiltersError::Timeout)?;
-        let response = match response {
-            NetworkMessage::CFCheckpt(response) => response,
-            _ => return Err(CompactFiltersError::InvalidResponse),
-        };
-
-        if response.filter_type != filter_type {
-            return Err(CompactFiltersError::InvalidResponse);
-        }
-
-        Ok(response)
-    }
-
-    fn get_cf_headers(
-        &self,
-        filter_type: u8,
-        start_height: u32,
-        stop_hash: BlockHash,
-    ) -> Result<CFHeaders, CompactFiltersError> {
-        self.send(NetworkMessage::GetCFHeaders(GetCFHeaders {
-            filter_type,
-            start_height,
-            stop_hash,
-        }))?;
-
-        let response = self
-            .recv("cfheaders", Some(Duration::from_secs(TIMEOUT_SECS)))?
-            .ok_or(CompactFiltersError::Timeout)?;
-        let response = match response {
-            NetworkMessage::CFHeaders(response) => response,
-            _ => return Err(CompactFiltersError::InvalidResponse),
-        };
-
-        if response.filter_type != filter_type {
-            return Err(CompactFiltersError::InvalidResponse);
-        }
-
-        Ok(response)
-    }
-
-    fn pop_cf_filter_resp(&self) -> Result<CFilter, CompactFiltersError> {
-        let response = self
-            .recv("cfilter", Some(Duration::from_secs(TIMEOUT_SECS)))?
-            .ok_or(CompactFiltersError::Timeout)?;
-        let response = match response {
-            NetworkMessage::CFilter(response) => response,
-            _ => return Err(CompactFiltersError::InvalidResponse),
-        };
-
-        Ok(response)
-    }
-
-    fn get_cf_filters(
-        &self,
-        filter_type: u8,
-        start_height: u32,
-        stop_hash: BlockHash,
-    ) -> Result<(), CompactFiltersError> {
-        self.send(NetworkMessage::GetCFilters(GetCFilters {
-            filter_type,
-            start_height,
-            stop_hash,
-        }))?;
-
-        Ok(())
-    }
-}
-
-pub trait InvPeer {
-    fn get_block(&self, block_hash: BlockHash) -> Result<Option<Block>, CompactFiltersError>;
-    fn ask_for_mempool(&self) -> Result<(), CompactFiltersError>;
-    fn broadcast_tx(&self, tx: Transaction) -> Result<(), CompactFiltersError>;
-}
-
-impl InvPeer for Peer {
-    fn get_block(&self, block_hash: BlockHash) -> Result<Option<Block>, CompactFiltersError> {
-        self.send(NetworkMessage::GetData(vec![Inventory::WitnessBlock(
-            block_hash,
-        )]))?;
-
-        match self.recv("block", Some(Duration::from_secs(TIMEOUT_SECS)))? {
-            None => Ok(None),
-            Some(NetworkMessage::Block(response)) => Ok(Some(response)),
-            _ => Err(CompactFiltersError::InvalidResponse),
-        }
-    }
-
-    fn ask_for_mempool(&self) -> Result<(), CompactFiltersError> {
-        if !self.version.services.has(ServiceFlags::BLOOM) {
-            return Err(CompactFiltersError::PeerBloomDisabled);
-        }
-
-        self.send(NetworkMessage::MemPool)?;
-        let inv = match self.recv("inv", Some(Duration::from_secs(5)))? {
-            None => return Ok(()), // empty mempool
-            Some(NetworkMessage::Inv(inv)) => inv,
-            _ => return Err(CompactFiltersError::InvalidResponse),
-        };
-
-        let getdata = inv
-            .iter()
-            .cloned()
-            .filter(
-                |item| matches!(item, Inventory::Transaction(txid) if !self.mempool.has_tx(txid)),
-            )
-            .collect::<Vec<_>>();
-        let num_txs = getdata.len();
-        self.send(NetworkMessage::GetData(getdata))?;
-
-        for _ in 0..num_txs {
-            let tx = self
-                .recv("tx", Some(Duration::from_secs(TIMEOUT_SECS)))?
-                .ok_or(CompactFiltersError::Timeout)?;
-            let tx = match tx {
-                NetworkMessage::Tx(tx) => tx,
-                _ => return Err(CompactFiltersError::InvalidResponse),
-            };
-
-            self.mempool.add_tx(tx);
-        }
-
-        Ok(())
-    }
-
-    fn broadcast_tx(&self, tx: Transaction) -> Result<(), CompactFiltersError> {
-        self.mempool.add_tx(tx.clone());
-        self.send(NetworkMessage::Tx(tx))?;
-
-        Ok(())
-    }
-}
diff --git a/src/blockchain/compact_filters/store.rs b/src/blockchain/compact_filters/store.rs
deleted file mode 100644 (file)
index 6b8d494..0000000
+++ /dev/null
@@ -1,836 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use std::convert::TryInto;
-use std::fmt;
-use std::io::{Read, Write};
-use std::marker::PhantomData;
-use std::sync::Arc;
-use std::sync::RwLock;
-
-use rand::distributions::Alphanumeric;
-use rand::{thread_rng, Rng};
-
-use rocksdb::{Direction, IteratorMode, ReadOptions, WriteBatch, DB};
-
-use bitcoin::blockdata::constants::genesis_block;
-use bitcoin::consensus::{deserialize, encode::VarInt, serialize, Decodable, Encodable};
-use bitcoin::hash_types::{FilterHash, FilterHeader};
-use bitcoin::hashes::Hash;
-use bitcoin::util::bip158::BlockFilter;
-use bitcoin::util::uint::Uint256;
-use bitcoin::Block;
-use bitcoin::BlockHash;
-use bitcoin::BlockHeader;
-use bitcoin::Network;
-
-use super::CompactFiltersError;
-
-pub trait StoreType: Default + fmt::Debug {}
-
-#[derive(Default, Debug)]
-pub struct Full;
-impl StoreType for Full {}
-#[derive(Default, Debug)]
-pub struct Snapshot;
-impl StoreType for Snapshot {}
-
-pub enum StoreEntry {
-    BlockHeader(Option<usize>),
-    Block(Option<usize>),
-    BlockHeaderIndex(Option<BlockHash>),
-    CFilterTable((u8, Option<usize>)),
-}
-
-impl StoreEntry {
-    pub fn get_prefix(&self) -> Vec<u8> {
-        match self {
-            StoreEntry::BlockHeader(_) => b"z",
-            StoreEntry::Block(_) => b"x",
-            StoreEntry::BlockHeaderIndex(_) => b"i",
-            StoreEntry::CFilterTable(_) => b"t",
-        }
-        .to_vec()
-    }
-
-    pub fn get_key(&self) -> Vec<u8> {
-        let mut prefix = self.get_prefix();
-        match self {
-            StoreEntry::BlockHeader(Some(height)) => {
-                prefix.extend_from_slice(&height.to_be_bytes())
-            }
-            StoreEntry::Block(Some(height)) => prefix.extend_from_slice(&height.to_be_bytes()),
-            StoreEntry::BlockHeaderIndex(Some(hash)) => {
-                prefix.extend_from_slice(&hash.into_inner())
-            }
-            StoreEntry::CFilterTable((filter_type, bundle_index)) => {
-                prefix.push(*filter_type);
-                if let Some(bundle_index) = bundle_index {
-                    prefix.extend_from_slice(&bundle_index.to_be_bytes());
-                }
-            }
-            _ => {}
-        }
-
-        prefix
-    }
-}
-
-pub trait SerializeDb: Sized {
-    fn serialize(&self) -> Vec<u8>;
-    fn deserialize(data: &[u8]) -> Result<Self, CompactFiltersError>;
-}
-
-impl<T> SerializeDb for T
-where
-    T: Encodable + Decodable,
-{
-    fn serialize(&self) -> Vec<u8> {
-        serialize(self)
-    }
-
-    fn deserialize(data: &[u8]) -> Result<Self, CompactFiltersError> {
-        deserialize(data).map_err(|_| CompactFiltersError::DataCorruption)
-    }
-}
-
-impl Encodable for BundleStatus {
-    fn consensus_encode<W: Write + ?Sized>(&self, e: &mut W) -> Result<usize, std::io::Error> {
-        let mut written = 0;
-
-        match self {
-            BundleStatus::Init => {
-                written += 0x00u8.consensus_encode(e)?;
-            }
-            BundleStatus::CfHeaders { cf_headers } => {
-                written += 0x01u8.consensus_encode(e)?;
-                written += VarInt(cf_headers.len() as u64).consensus_encode(e)?;
-                for header in cf_headers {
-                    written += header.consensus_encode(e)?;
-                }
-            }
-            BundleStatus::CFilters { cf_filters } => {
-                written += 0x02u8.consensus_encode(e)?;
-                written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
-                for filter in cf_filters {
-                    written += filter.consensus_encode(e)?;
-                }
-            }
-            BundleStatus::Processed { cf_filters } => {
-                written += 0x03u8.consensus_encode(e)?;
-                written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
-                for filter in cf_filters {
-                    written += filter.consensus_encode(e)?;
-                }
-            }
-            BundleStatus::Pruned => {
-                written += 0x04u8.consensus_encode(e)?;
-            }
-            BundleStatus::Tip { cf_filters } => {
-                written += 0x05u8.consensus_encode(e)?;
-                written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
-                for filter in cf_filters {
-                    written += filter.consensus_encode(e)?;
-                }
-            }
-        }
-
-        Ok(written)
-    }
-}
-
-impl Decodable for BundleStatus {
-    fn consensus_decode<D: Read + ?Sized>(
-        d: &mut D,
-    ) -> Result<Self, bitcoin::consensus::encode::Error> {
-        let byte_type = u8::consensus_decode(d)?;
-        match byte_type {
-            0x00 => Ok(BundleStatus::Init),
-            0x01 => {
-                let num = VarInt::consensus_decode(d)?;
-                let num = num.0 as usize;
-
-                let mut cf_headers = Vec::with_capacity(num);
-                for _ in 0..num {
-                    cf_headers.push(FilterHeader::consensus_decode(d)?);
-                }
-
-                Ok(BundleStatus::CfHeaders { cf_headers })
-            }
-            0x02 => {
-                let num = VarInt::consensus_decode(d)?;
-                let num = num.0 as usize;
-
-                let mut cf_filters = Vec::with_capacity(num);
-                for _ in 0..num {
-                    cf_filters.push(Vec::<u8>::consensus_decode(d)?);
-                }
-
-                Ok(BundleStatus::CFilters { cf_filters })
-            }
-            0x03 => {
-                let num = VarInt::consensus_decode(d)?;
-                let num = num.0 as usize;
-
-                let mut cf_filters = Vec::with_capacity(num);
-                for _ in 0..num {
-                    cf_filters.push(Vec::<u8>::consensus_decode(d)?);
-                }
-
-                Ok(BundleStatus::Processed { cf_filters })
-            }
-            0x04 => Ok(BundleStatus::Pruned),
-            0x05 => {
-                let num = VarInt::consensus_decode(d)?;
-                let num = num.0 as usize;
-
-                let mut cf_filters = Vec::with_capacity(num);
-                for _ in 0..num {
-                    cf_filters.push(Vec::<u8>::consensus_decode(d)?);
-                }
-
-                Ok(BundleStatus::Tip { cf_filters })
-            }
-            _ => Err(bitcoin::consensus::encode::Error::ParseFailed(
-                "Invalid byte type",
-            )),
-        }
-    }
-}
-
-pub struct ChainStore<T: StoreType> {
-    store: Arc<RwLock<DB>>,
-    cf_name: String,
-    min_height: usize,
-    network: Network,
-    phantom: PhantomData<T>,
-}
-
-impl ChainStore<Full> {
-    pub fn new(store: DB, network: Network) -> Result<Self, CompactFiltersError> {
-        let genesis = genesis_block(network);
-
-        let cf_name = "default".to_string();
-        let cf_handle = store.cf_handle(&cf_name).unwrap();
-
-        let genesis_key = StoreEntry::BlockHeader(Some(0)).get_key();
-
-        if store.get_pinned_cf(cf_handle, &genesis_key)?.is_none() {
-            let mut batch = WriteBatch::default();
-            batch.put_cf(
-                cf_handle,
-                genesis_key,
-                (genesis.header, genesis.header.work()).serialize(),
-            );
-            batch.put_cf(
-                cf_handle,
-                StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
-                0usize.to_be_bytes(),
-            );
-            store.write(batch)?;
-        }
-
-        Ok(ChainStore {
-            store: Arc::new(RwLock::new(store)),
-            cf_name,
-            min_height: 0,
-            network,
-            phantom: PhantomData,
-        })
-    }
-
-    pub fn get_locators(&self) -> Result<Vec<(BlockHash, usize)>, CompactFiltersError> {
-        let mut step = 1;
-        let mut index = self.get_height()?;
-        let mut answer = Vec::new();
-
-        let store_read = self.store.read().unwrap();
-        let cf_handle = store_read.cf_handle(&self.cf_name).unwrap();
-
-        loop {
-            if answer.len() > 10 {
-                step *= 2;
-            }
-
-            let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(
-                &store_read
-                    .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())?
-                    .unwrap(),
-            )?;
-            answer.push((header.block_hash(), index));
-
-            if let Some(new_index) = index.checked_sub(step) {
-                index = new_index;
-            } else {
-                break;
-            }
-        }
-
-        Ok(answer)
-    }
-
-    pub fn start_snapshot(&self, from: usize) -> Result<ChainStore<Snapshot>, CompactFiltersError> {
-        let new_cf_name: String = thread_rng()
-            .sample_iter(&Alphanumeric)
-            .map(|byte| byte as char)
-            .take(16)
-            .collect();
-        let new_cf_name = format!("_headers:{}", new_cf_name);
-
-        let mut write_store = self.store.write().unwrap();
-
-        write_store.create_cf(&new_cf_name, &Default::default())?;
-
-        let cf_handle = write_store.cf_handle(&self.cf_name).unwrap();
-        let new_cf_handle = write_store.cf_handle(&new_cf_name).unwrap();
-
-        let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(
-            &write_store
-                .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
-                .ok_or(CompactFiltersError::DataCorruption)?,
-        )?;
-
-        let mut batch = WriteBatch::default();
-        batch.put_cf(
-            new_cf_handle,
-            StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
-            from.to_be_bytes(),
-        );
-        batch.put_cf(
-            new_cf_handle,
-            StoreEntry::BlockHeader(Some(from)).get_key(),
-            (header, work).serialize(),
-        );
-        write_store.write(batch)?;
-
-        let store = Arc::clone(&self.store);
-        Ok(ChainStore {
-            store,
-            cf_name: new_cf_name,
-            min_height: from,
-            network: self.network,
-            phantom: PhantomData,
-        })
-    }
-
-    pub fn recover_snapshot(&self, cf_name: &str) -> Result<(), CompactFiltersError> {
-        let mut write_store = self.store.write().unwrap();
-        let snapshot_cf_handle = write_store.cf_handle(cf_name).unwrap();
-
-        let prefix = StoreEntry::BlockHeader(None).get_key();
-        let mut iterator = write_store.prefix_iterator_cf(snapshot_cf_handle, prefix);
-
-        let min_height = match iterator
-            .next()
-            .and_then(|(k, _)| k[1..].try_into().ok())
-            .map(usize::from_be_bytes)
-        {
-            None => {
-                std::mem::drop(iterator);
-                write_store.drop_cf(cf_name).ok();
-
-                return Ok(());
-            }
-            Some(x) => x,
-        };
-        std::mem::drop(iterator);
-        std::mem::drop(write_store);
-
-        let snapshot = ChainStore {
-            store: Arc::clone(&self.store),
-            cf_name: cf_name.into(),
-            min_height,
-            network: self.network,
-            phantom: PhantomData,
-        };
-        if snapshot.work()? > self.work()? {
-            self.apply_snapshot(snapshot)?;
-        }
-
-        Ok(())
-    }
-
-    pub fn apply_snapshot(
-        &self,
-        snaphost: ChainStore<Snapshot>,
-    ) -> Result<(), CompactFiltersError> {
-        let mut batch = WriteBatch::default();
-
-        let read_store = self.store.read().unwrap();
-        let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
-        let snapshot_cf_handle = read_store.cf_handle(&snaphost.cf_name).unwrap();
-
-        let from_key = StoreEntry::BlockHeader(Some(snaphost.min_height)).get_key();
-        let to_key = StoreEntry::BlockHeader(Some(usize::MAX)).get_key();
-
-        let mut opts = ReadOptions::default();
-        opts.set_iterate_upper_bound(to_key.clone());
-
-        log::debug!("Removing items");
-        batch.delete_range_cf(cf_handle, &from_key, &to_key);
-        for (_, v) in read_store.iterator_cf_opt(
-            cf_handle,
-            opts,
-            IteratorMode::From(&from_key, Direction::Forward),
-        ) {
-            let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
-
-            batch.delete_cf(
-                cf_handle,
-                StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
-            );
-        }
-
-        // Delete full blocks overridden by snapshot
-        let from_key = StoreEntry::Block(Some(snaphost.min_height)).get_key();
-        let to_key = StoreEntry::Block(Some(usize::MAX)).get_key();
-        batch.delete_range(&from_key, &to_key);
-
-        log::debug!("Copying over new items");
-        for (k, v) in read_store.iterator_cf(snapshot_cf_handle, IteratorMode::Start) {
-            batch.put_cf(cf_handle, k, v);
-        }
-
-        read_store.write(batch)?;
-        std::mem::drop(read_store);
-
-        self.store.write().unwrap().drop_cf(&snaphost.cf_name)?;
-
-        Ok(())
-    }
-
-    pub fn get_height_for(
-        &self,
-        block_hash: &BlockHash,
-    ) -> Result<Option<usize>, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-        let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
-
-        let key = StoreEntry::BlockHeaderIndex(Some(*block_hash)).get_key();
-        let data = read_store.get_pinned_cf(cf_handle, key)?;
-        data.map(|data| {
-            Ok::<_, CompactFiltersError>(usize::from_be_bytes(
-                data.as_ref()
-                    .try_into()
-                    .map_err(|_| CompactFiltersError::DataCorruption)?,
-            ))
-        })
-        .transpose()
-    }
-
-    pub fn get_block_hash(&self, height: usize) -> Result<Option<BlockHash>, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-        let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
-
-        let key = StoreEntry::BlockHeader(Some(height)).get_key();
-        let data = read_store.get_pinned_cf(cf_handle, key)?;
-        data.map(|data| {
-            let (header, _): (BlockHeader, Uint256) =
-                deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
-            Ok::<_, CompactFiltersError>(header.block_hash())
-        })
-        .transpose()
-    }
-
-    pub fn save_full_block(&self, block: &Block, height: usize) -> Result<(), CompactFiltersError> {
-        let key = StoreEntry::Block(Some(height)).get_key();
-        self.store.read().unwrap().put(key, block.serialize())?;
-
-        Ok(())
-    }
-
-    pub fn get_full_block(&self, height: usize) -> Result<Option<Block>, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-
-        let key = StoreEntry::Block(Some(height)).get_key();
-        let opt_block = read_store.get_pinned(key)?;
-
-        opt_block
-            .map(|data| deserialize(&data))
-            .transpose()
-            .map_err(|_| CompactFiltersError::DataCorruption)
-    }
-
-    pub fn delete_blocks_until(&self, height: usize) -> Result<(), CompactFiltersError> {
-        let from_key = StoreEntry::Block(Some(0)).get_key();
-        let to_key = StoreEntry::Block(Some(height)).get_key();
-
-        let mut batch = WriteBatch::default();
-        batch.delete_range(&from_key, &to_key);
-
-        self.store.read().unwrap().write(batch)?;
-
-        Ok(())
-    }
-
-    pub fn iter_full_blocks(&self) -> Result<Vec<(usize, Block)>, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-
-        let prefix = StoreEntry::Block(None).get_key();
-
-        let iterator = read_store.prefix_iterator(&prefix);
-        // FIXME: we have to filter manually because rocksdb sometimes returns stuff that doesn't
-        // have the right prefix
-        iterator
-            .filter(|(k, _)| k.starts_with(&prefix))
-            .map(|(k, v)| {
-                let height: usize = usize::from_be_bytes(
-                    k[1..]
-                        .try_into()
-                        .map_err(|_| CompactFiltersError::DataCorruption)?,
-                );
-                let block = SerializeDb::deserialize(&v)?;
-
-                Ok((height, block))
-            })
-            .collect::<Result<_, _>>()
-    }
-}
-
-impl<T: StoreType> ChainStore<T> {
-    pub fn work(&self) -> Result<Uint256, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-        let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
-
-        let prefix = StoreEntry::BlockHeader(None).get_key();
-        let iterator = read_store.prefix_iterator_cf(cf_handle, prefix);
-
-        Ok(iterator
-            .last()
-            .map(|(_, v)| -> Result<_, CompactFiltersError> {
-                let (_, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
-
-                Ok(work)
-            })
-            .transpose()?
-            .unwrap_or_default())
-    }
-
-    pub fn get_height(&self) -> Result<usize, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-        let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
-
-        let prefix = StoreEntry::BlockHeader(None).get_key();
-        let iterator = read_store.prefix_iterator_cf(cf_handle, prefix);
-
-        Ok(iterator
-            .last()
-            .map(|(k, _)| -> Result<_, CompactFiltersError> {
-                let height = usize::from_be_bytes(
-                    k[1..]
-                        .try_into()
-                        .map_err(|_| CompactFiltersError::DataCorruption)?,
-                );
-
-                Ok(height)
-            })
-            .transpose()?
-            .unwrap_or_default())
-    }
-
-    pub fn get_tip_hash(&self) -> Result<Option<BlockHash>, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-        let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
-
-        let prefix = StoreEntry::BlockHeader(None).get_key();
-        let iterator = read_store.prefix_iterator_cf(cf_handle, prefix);
-
-        iterator
-            .last()
-            .map(|(_, v)| -> Result<_, CompactFiltersError> {
-                let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
-
-                Ok(header.block_hash())
-            })
-            .transpose()
-    }
-
-    pub fn apply(
-        &mut self,
-        from: usize,
-        headers: Vec<BlockHeader>,
-    ) -> Result<BlockHash, CompactFiltersError> {
-        let mut batch = WriteBatch::default();
-
-        let read_store = self.store.read().unwrap();
-        let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
-
-        let (mut last_hash, mut accumulated_work) = read_store
-            .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
-            .map(|result| {
-                let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?;
-                Ok::<_, CompactFiltersError>((header.block_hash(), work))
-            })
-            .transpose()?
-            .ok_or(CompactFiltersError::DataCorruption)?;
-
-        for (index, header) in headers.into_iter().enumerate() {
-            if header.prev_blockhash != last_hash {
-                return Err(CompactFiltersError::InvalidHeaders);
-            }
-
-            last_hash = header.block_hash();
-            accumulated_work = accumulated_work + header.work();
-
-            let height = from + index + 1;
-            batch.put_cf(
-                cf_handle,
-                StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
-                (height).to_be_bytes(),
-            );
-            batch.put_cf(
-                cf_handle,
-                StoreEntry::BlockHeader(Some(height)).get_key(),
-                (header, accumulated_work).serialize(),
-            );
-        }
-
-        std::mem::drop(read_store);
-
-        self.store.write().unwrap().write(batch)?;
-        Ok(last_hash)
-    }
-}
-
-impl<T: StoreType> fmt::Debug for ChainStore<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct(&format!("ChainStore<{:?}>", T::default()))
-            .field("cf_name", &self.cf_name)
-            .field("min_height", &self.min_height)
-            .field("network", &self.network)
-            .field("headers_height", &self.get_height())
-            .field("tip_hash", &self.get_tip_hash())
-            .finish()
-    }
-}
-
-pub enum BundleStatus {
-    Init,
-    CfHeaders { cf_headers: Vec<FilterHeader> },
-    CFilters { cf_filters: Vec<Vec<u8>> },
-    Processed { cf_filters: Vec<Vec<u8>> },
-    Tip { cf_filters: Vec<Vec<u8>> },
-    Pruned,
-}
-
-pub struct CfStore {
-    store: Arc<RwLock<DB>>,
-    filter_type: u8,
-}
-
-type BundleEntry = (BundleStatus, FilterHeader);
-
-impl CfStore {
-    pub fn new(
-        headers_store: &ChainStore<Full>,
-        filter_type: u8,
-    ) -> Result<Self, CompactFiltersError> {
-        let cf_store = CfStore {
-            store: Arc::clone(&headers_store.store),
-            filter_type,
-        };
-
-        let genesis = genesis_block(headers_store.network);
-
-        let filter = BlockFilter::new_script_filter(&genesis, |utxo| {
-            Err(bitcoin::util::bip158::Error::UtxoMissing(*utxo))
-        })?;
-        let first_key = StoreEntry::CFilterTable((filter_type, Some(0))).get_key();
-
-        // Add the genesis' filter
-        {
-            let read_store = cf_store.store.read().unwrap();
-            if read_store.get_pinned(&first_key)?.is_none() {
-                read_store.put(
-                    &first_key,
-                    (
-                        BundleStatus::Init,
-                        filter.filter_header(&FilterHeader::from_hash(Hash::all_zeros())),
-                    )
-                        .serialize(),
-                )?;
-            }
-        }
-
-        Ok(cf_store)
-    }
-
-    pub fn get_filter_type(&self) -> u8 {
-        self.filter_type
-    }
-
-    pub fn get_bundles(&self) -> Result<Vec<BundleEntry>, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-
-        let prefix = StoreEntry::CFilterTable((self.filter_type, None)).get_key();
-        let iterator = read_store.prefix_iterator(&prefix);
-
-        // FIXME: we have to filter manually because rocksdb sometimes returns stuff that doesn't
-        // have the right prefix
-        iterator
-            .filter(|(k, _)| k.starts_with(&prefix))
-            .map(|(_, data)| BundleEntry::deserialize(&data))
-            .collect::<Result<_, _>>()
-    }
-
-    pub fn get_checkpoints(&self) -> Result<Vec<FilterHeader>, CompactFiltersError> {
-        let read_store = self.store.read().unwrap();
-
-        let prefix = StoreEntry::CFilterTable((self.filter_type, None)).get_key();
-        let iterator = read_store.prefix_iterator(&prefix);
-
-        // FIXME: we have to filter manually because rocksdb sometimes returns stuff that doesn't
-        // have the right prefix
-        iterator
-            .filter(|(k, _)| k.starts_with(&prefix))
-            .skip(1)
-            .map(|(_, data)| Ok::<_, CompactFiltersError>(BundleEntry::deserialize(&data)?.1))
-            .collect::<Result<_, _>>()
-    }
-
-    pub fn replace_checkpoints(
-        &self,
-        checkpoints: Vec<FilterHeader>,
-    ) -> Result<(), CompactFiltersError> {
-        let current_checkpoints = self.get_checkpoints()?;
-
-        let mut equal_bundles = 0;
-        for (index, (our, their)) in current_checkpoints
-            .iter()
-            .zip(checkpoints.iter())
-            .enumerate()
-        {
-            equal_bundles = index;
-
-            if our != their {
-                break;
-            }
-        }
-
-        let read_store = self.store.read().unwrap();
-        let mut batch = WriteBatch::default();
-
-        for (index, filter_hash) in checkpoints.iter().enumerate().skip(equal_bundles) {
-            let key = StoreEntry::CFilterTable((self.filter_type, Some(index + 1))).get_key(); // +1 to skip the genesis' filter
-
-            if let Some((BundleStatus::Tip { .. }, _)) = read_store
-                .get_pinned(&key)?
-                .map(|data| BundleEntry::deserialize(&data))
-                .transpose()?
-            {
-                println!("Keeping bundle #{} as Tip", index);
-            } else {
-                batch.put(&key, (BundleStatus::Init, *filter_hash).serialize());
-            }
-        }
-
-        read_store.write(batch)?;
-
-        Ok(())
-    }
-
-    pub fn advance_to_cf_headers(
-        &self,
-        bundle: usize,
-        checkpoint: FilterHeader,
-        filter_hashes: Vec<FilterHash>,
-    ) -> Result<BundleStatus, CompactFiltersError> {
-        let cf_headers: Vec<FilterHeader> = filter_hashes
-            .into_iter()
-            .scan(checkpoint, |prev_header, filter_hash| {
-                let filter_header = filter_hash.filter_header(prev_header);
-                *prev_header = filter_header;
-
-                Some(filter_header)
-            })
-            .collect();
-
-        let read_store = self.store.read().unwrap();
-
-        let next_key = StoreEntry::CFilterTable((self.filter_type, Some(bundle + 1))).get_key(); // +1 to skip the genesis' filter
-        if let Some((_, next_checkpoint)) = read_store
-            .get_pinned(&next_key)?
-            .map(|data| BundleEntry::deserialize(&data))
-            .transpose()?
-        {
-            // check connection with the next bundle if present
-            if cf_headers.iter().last() != Some(&next_checkpoint) {
-                return Err(CompactFiltersError::InvalidFilterHeader);
-            }
-        }
-
-        let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key();
-        let value = (BundleStatus::CfHeaders { cf_headers }, checkpoint);
-
-        read_store.put(key, value.serialize())?;
-
-        Ok(value.0)
-    }
-
-    pub fn advance_to_cf_filters(
-        &self,
-        bundle: usize,
-        checkpoint: FilterHeader,
-        headers: Vec<FilterHeader>,
-        filters: Vec<(usize, Vec<u8>)>,
-    ) -> Result<BundleStatus, CompactFiltersError> {
-        let cf_filters = filters
-            .into_iter()
-            .zip(headers.into_iter())
-            .scan(checkpoint, |prev_header, ((_, filter_content), header)| {
-                let filter = BlockFilter::new(&filter_content);
-                if header != filter.filter_header(prev_header) {
-                    return Some(Err(CompactFiltersError::InvalidFilter));
-                }
-                *prev_header = header;
-
-                Some(Ok::<_, CompactFiltersError>(filter_content))
-            })
-            .collect::<Result<_, _>>()?;
-
-        let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key();
-        let value = (BundleStatus::CFilters { cf_filters }, checkpoint);
-
-        let read_store = self.store.read().unwrap();
-        read_store.put(key, value.serialize())?;
-
-        Ok(value.0)
-    }
-
-    pub fn prune_filters(
-        &self,
-        bundle: usize,
-        checkpoint: FilterHeader,
-    ) -> Result<BundleStatus, CompactFiltersError> {
-        let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key();
-        let value = (BundleStatus::Pruned, checkpoint);
-
-        let read_store = self.store.read().unwrap();
-        read_store.put(key, value.serialize())?;
-
-        Ok(value.0)
-    }
-
-    pub fn mark_as_tip(
-        &self,
-        bundle: usize,
-        cf_filters: Vec<Vec<u8>>,
-        checkpoint: FilterHeader,
-    ) -> Result<BundleStatus, CompactFiltersError> {
-        let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key();
-        let value = (BundleStatus::Tip { cf_filters }, checkpoint);
-
-        let read_store = self.store.read().unwrap();
-        read_store.put(key, value.serialize())?;
-
-        Ok(value.0)
-    }
-}
diff --git a/src/blockchain/compact_filters/sync.rs b/src/blockchain/compact_filters/sync.rs
deleted file mode 100644 (file)
index ba4e004..0000000
+++ /dev/null
@@ -1,297 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use std::collections::{BTreeMap, HashMap, VecDeque};
-use std::sync::{Arc, Mutex};
-use std::time::Duration;
-
-use bitcoin::hash_types::{BlockHash, FilterHeader};
-use bitcoin::hashes::Hash;
-use bitcoin::network::message::NetworkMessage;
-use bitcoin::network::message_blockdata::GetHeadersMessage;
-use bitcoin::util::bip158::BlockFilter;
-
-use super::peer::*;
-use super::store::*;
-use super::CompactFiltersError;
-use crate::error::Error;
-
-pub(crate) const BURIED_CONFIRMATIONS: usize = 100;
-
-pub struct CfSync {
-    headers_store: Arc<ChainStore<Full>>,
-    cf_store: Arc<CfStore>,
-    skip_blocks: usize,
-    bundles: Mutex<VecDeque<(BundleStatus, FilterHeader, usize)>>,
-}
-
-impl CfSync {
-    pub fn new(
-        headers_store: Arc<ChainStore<Full>>,
-        skip_blocks: usize,
-        filter_type: u8,
-    ) -> Result<Self, CompactFiltersError> {
-        let cf_store = Arc::new(CfStore::new(&headers_store, filter_type)?);
-
-        Ok(CfSync {
-            headers_store,
-            cf_store,
-            skip_blocks,
-            bundles: Mutex::new(VecDeque::new()),
-        })
-    }
-
-    pub fn pruned_bundles(&self) -> Result<usize, CompactFiltersError> {
-        Ok(self
-            .cf_store
-            .get_bundles()?
-            .into_iter()
-            .skip(self.skip_blocks / 1000)
-            .fold(0, |acc, (status, _)| match status {
-                BundleStatus::Pruned => acc + 1,
-                _ => acc,
-            }))
-    }
-
-    pub fn prepare_sync(&self, peer: Arc<Peer>) -> Result<(), CompactFiltersError> {
-        let mut bundles_lock = self.bundles.lock().unwrap();
-
-        let resp = peer.get_cf_checkpt(
-            self.cf_store.get_filter_type(),
-            self.headers_store.get_tip_hash()?.unwrap(),
-        )?;
-        self.cf_store.replace_checkpoints(resp.filter_headers)?;
-
-        bundles_lock.clear();
-        for (index, (status, checkpoint)) in self.cf_store.get_bundles()?.into_iter().enumerate() {
-            bundles_lock.push_back((status, checkpoint, index));
-        }
-
-        Ok(())
-    }
-
-    pub fn capture_thread_for_sync<F, Q>(
-        &self,
-        peer: Arc<Peer>,
-        process: F,
-        completed_bundle: Q,
-    ) -> Result<(), CompactFiltersError>
-    where
-        F: Fn(&BlockHash, &BlockFilter) -> Result<bool, CompactFiltersError>,
-        Q: Fn(usize) -> Result<(), Error>,
-    {
-        let current_height = self.headers_store.get_height()?; // TODO: we should update it in case headers_store is also updated
-
-        loop {
-            let (mut status, checkpoint, index) = match self.bundles.lock().unwrap().pop_front() {
-                None => break,
-                Some(x) => x,
-            };
-
-            log::debug!(
-                "Processing bundle #{} - height {} to {}",
-                index,
-                index * 1000 + 1,
-                (index + 1) * 1000
-            );
-
-            let process_received_filters =
-                |expected_filters| -> Result<BTreeMap<usize, Vec<u8>>, CompactFiltersError> {
-                    let mut filters_map = BTreeMap::new();
-                    for _ in 0..expected_filters {
-                        let filter = peer.pop_cf_filter_resp()?;
-                        if filter.filter_type != self.cf_store.get_filter_type() {
-                            return Err(CompactFiltersError::InvalidResponse);
-                        }
-
-                        match self.headers_store.get_height_for(&filter.block_hash)? {
-                            Some(height) => filters_map.insert(height, filter.filter),
-                            None => return Err(CompactFiltersError::InvalidFilter),
-                        };
-                    }
-
-                    Ok(filters_map)
-                };
-
-            let start_height = index * 1000 + 1;
-            let mut already_processed = 0;
-
-            if start_height < self.skip_blocks {
-                status = self.cf_store.prune_filters(index, checkpoint)?;
-            }
-
-            let stop_height = std::cmp::min(current_height, start_height + 999);
-            let stop_hash = self.headers_store.get_block_hash(stop_height)?.unwrap();
-
-            if let BundleStatus::Init = status {
-                log::trace!("status: Init");
-
-                let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
-
-                assert_eq!(resp.previous_filter_header, checkpoint);
-                status =
-                    self.cf_store
-                        .advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
-            }
-            if let BundleStatus::Tip { cf_filters } = status {
-                log::trace!("status: Tip (beginning) ");
-
-                already_processed = cf_filters.len();
-                let headers_resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
-
-                let cf_headers = match self.cf_store.advance_to_cf_headers(
-                    index,
-                    checkpoint,
-                    headers_resp.filter_hashes,
-                )? {
-                    BundleStatus::CfHeaders { cf_headers } => cf_headers,
-                    _ => return Err(CompactFiltersError::InvalidResponse),
-                };
-
-                peer.get_cf_filters(
-                    self.cf_store.get_filter_type(),
-                    (start_height + cf_filters.len()) as u32,
-                    stop_hash,
-                )?;
-                let expected_filters = stop_height - start_height + 1 - cf_filters.len();
-                let filters_map = process_received_filters(expected_filters)?;
-                let filters = cf_filters
-                    .into_iter()
-                    .enumerate()
-                    .chain(filters_map.into_iter())
-                    .collect();
-                status = self
-                    .cf_store
-                    .advance_to_cf_filters(index, checkpoint, cf_headers, filters)?;
-            }
-            if let BundleStatus::CfHeaders { cf_headers } = status {
-                log::trace!("status: CFHeaders");
-
-                peer.get_cf_filters(
-                    self.cf_store.get_filter_type(),
-                    start_height as u32,
-                    stop_hash,
-                )?;
-                let expected_filters = stop_height - start_height + 1;
-                let filters_map = process_received_filters(expected_filters)?;
-                status = self.cf_store.advance_to_cf_filters(
-                    index,
-                    checkpoint,
-                    cf_headers,
-                    filters_map.into_iter().collect(),
-                )?;
-            }
-            if let BundleStatus::CFilters { cf_filters } = status {
-                log::trace!("status: CFilters");
-
-                let last_sync_buried_height =
-                    (start_height + already_processed).saturating_sub(BURIED_CONFIRMATIONS);
-
-                for (filter_index, filter) in cf_filters.iter().enumerate() {
-                    let height = filter_index + start_height;
-
-                    // do not download blocks that were already "buried" since the last sync
-                    if height < last_sync_buried_height {
-                        continue;
-                    }
-
-                    let block_hash = self.headers_store.get_block_hash(height)?.unwrap();
-
-                    // TODO: also download random blocks?
-                    if process(&block_hash, &BlockFilter::new(filter))? {
-                        log::debug!("Downloading block {}", block_hash);
-
-                        let block = peer
-                            .get_block(block_hash)?
-                            .ok_or(CompactFiltersError::MissingBlock)?;
-                        self.headers_store.save_full_block(&block, height)?;
-                    }
-                }
-
-                status = BundleStatus::Processed { cf_filters };
-            }
-            if let BundleStatus::Processed { cf_filters } = status {
-                log::trace!("status: Processed");
-
-                if current_height - stop_height > 1000 {
-                    status = self.cf_store.prune_filters(index, checkpoint)?;
-                } else {
-                    status = self.cf_store.mark_as_tip(index, cf_filters, checkpoint)?;
-                }
-
-                completed_bundle(index)?;
-            }
-            if let BundleStatus::Pruned = status {
-                log::trace!("status: Pruned");
-            }
-            if let BundleStatus::Tip { .. } = status {
-                log::trace!("status: Tip");
-            }
-        }
-
-        Ok(())
-    }
-}
-
-pub fn sync_headers<F>(
-    peer: Arc<Peer>,
-    store: Arc<ChainStore<Full>>,
-    sync_fn: F,
-) -> Result<Option<ChainStore<Snapshot>>, CompactFiltersError>
-where
-    F: Fn(usize) -> Result<(), Error>,
-{
-    let locators = store.get_locators()?;
-    let locators_vec = locators.iter().map(|(hash, _)| hash).cloned().collect();
-    let locators_map: HashMap<_, _> = locators.into_iter().collect();
-
-    peer.send(NetworkMessage::GetHeaders(GetHeadersMessage::new(
-        locators_vec,
-        Hash::all_zeros(),
-    )))?;
-    let (mut snapshot, mut last_hash) = if let NetworkMessage::Headers(headers) = peer
-        .recv("headers", Some(Duration::from_secs(TIMEOUT_SECS)))?
-        .ok_or(CompactFiltersError::Timeout)?
-    {
-        if headers.is_empty() {
-            return Ok(None);
-        }
-
-        match locators_map.get(&headers[0].prev_blockhash) {
-            None => return Err(CompactFiltersError::InvalidHeaders),
-            Some(from) => (store.start_snapshot(*from)?, headers[0].prev_blockhash),
-        }
-    } else {
-        return Err(CompactFiltersError::InvalidResponse);
-    };
-
-    let mut sync_height = store.get_height()?;
-    while sync_height < peer.get_version().start_height as usize {
-        peer.send(NetworkMessage::GetHeaders(GetHeadersMessage::new(
-            vec![last_hash],
-            Hash::all_zeros(),
-        )))?;
-        if let NetworkMessage::Headers(headers) = peer
-            .recv("headers", Some(Duration::from_secs(TIMEOUT_SECS)))?
-            .ok_or(CompactFiltersError::Timeout)?
-        {
-            let batch_len = headers.len();
-            last_hash = snapshot.apply(sync_height, headers)?;
-
-            sync_height += batch_len;
-            sync_fn(sync_height)?;
-        } else {
-            return Err(CompactFiltersError::InvalidResponse);
-        }
-    }
-
-    Ok(Some(snapshot))
-}
diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs
deleted file mode 100644 (file)
index 845bb64..0000000
+++ /dev/null
@@ -1,432 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Electrum
-//!
-//! This module defines a [`Blockchain`] struct that wraps an [`electrum_client::Client`]
-//! and implements the logic required to populate the wallet's [database](crate::database::Database) by
-//! querying the inner client.
-//!
-//! ## Example
-//!
-//! ```no_run
-//! # use bdk::blockchain::electrum::ElectrumBlockchain;
-//! let client = electrum_client::Client::new("ssl://electrum.blockstream.info:50002")?;
-//! let blockchain = ElectrumBlockchain::from(client);
-//! # Ok::<(), bdk::Error>(())
-//! ```
-
-use std::collections::{HashMap, HashSet};
-use std::ops::{Deref, DerefMut};
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-use bitcoin::{Transaction, Txid};
-
-use electrum_client::{Client, ConfigBuilder, ElectrumApi, Socks5Config};
-
-use super::script_sync::Request;
-use super::*;
-use crate::database::{BatchDatabase, Database};
-use crate::error::Error;
-use crate::{BlockTime, FeeRate};
-
-/// Wrapper over an Electrum Client that implements the required blockchain traits
-///
-/// ## Example
-/// See the [`blockchain::electrum`](crate::blockchain::electrum) module for a usage example.
-pub struct ElectrumBlockchain {
-    client: Client,
-    stop_gap: usize,
-}
-
-impl std::convert::From<Client> for ElectrumBlockchain {
-    fn from(client: Client) -> Self {
-        ElectrumBlockchain {
-            client,
-            stop_gap: 20,
-        }
-    }
-}
-
-impl Blockchain for ElectrumBlockchain {
-    fn get_capabilities(&self) -> HashSet<Capability> {
-        vec![
-            Capability::FullHistory,
-            Capability::GetAnyTx,
-            Capability::AccurateFees,
-        ]
-        .into_iter()
-        .collect()
-    }
-
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
-    }
-
-    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
-        Ok(FeeRate::from_btc_per_kvb(
-            self.client.estimate_fee(target)? as f32
-        ))
-    }
-}
-
-impl Deref for ElectrumBlockchain {
-    type Target = Client;
-
-    fn deref(&self) -> &Self::Target {
-        &self.client
-    }
-}
-
-impl StatelessBlockchain for ElectrumBlockchain {}
-
-impl GetHeight for ElectrumBlockchain {
-    fn get_height(&self) -> Result<u32, Error> {
-        // TODO: unsubscribe when added to the client, or is there a better call to use here?
-
-        Ok(self
-            .client
-            .block_headers_subscribe()
-            .map(|data| data.height as u32)?)
-    }
-}
-
-impl GetTx for ElectrumBlockchain {
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        Ok(self.client.transaction_get(txid).map(Option::Some)?)
-    }
-}
-
-impl GetBlockHash for ElectrumBlockchain {
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
-        let block_header = self.client.block_header(height as usize)?;
-        Ok(block_header.block_hash())
-    }
-}
-
-impl WalletSync for ElectrumBlockchain {
-    fn wallet_setup<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        _progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        let mut database = database.borrow_mut();
-        let database = database.deref_mut();
-        let mut request = script_sync::start(database, self.stop_gap)?;
-        let mut block_times = HashMap::<u32, u32>::new();
-        let mut txid_to_height = HashMap::<Txid, u32>::new();
-        let mut tx_cache = TxCache::new(database, &self.client);
-
-        // Set chunk_size to the smallest value capable of finding a gap greater than stop_gap.
-        let chunk_size = self.stop_gap + 1;
-
-        // The electrum server has been inconsistent somehow in its responses during sync. For
-        // example, we do a batch request of transactions and the response contains less
-        // tranascations than in the request. This should never happen but we don't want to panic.
-        let electrum_goof = || Error::Generic("electrum server misbehaving".to_string());
-
-        let batch_update = loop {
-            request = match request {
-                Request::Script(script_req) => {
-                    let scripts = script_req.request().take(chunk_size);
-                    let txids_per_script: Vec<Vec<_>> = self
-                        .client
-                        .batch_script_get_history(scripts)
-                        .map_err(Error::Electrum)?
-                        .into_iter()
-                        .map(|txs| {
-                            txs.into_iter()
-                                .map(|tx| {
-                                    let tx_height = match tx.height {
-                                        none if none <= 0 => None,
-                                        height => {
-                                            txid_to_height.insert(tx.tx_hash, height as u32);
-                                            Some(height as u32)
-                                        }
-                                    };
-                                    (tx.tx_hash, tx_height)
-                                })
-                                .collect()
-                        })
-                        .collect();
-
-                    script_req.satisfy(txids_per_script)?
-                }
-
-                Request::Conftime(conftime_req) => {
-                    // collect up to chunk_size heights to fetch from electrum
-                    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
-                        .batch_block_header(needs_block_height.iter().cloned())?;
-
-                    for (height, header) in needs_block_height.into_iter().zip(new_block_headers) {
-                        block_times.insert(height, header.time);
-                    }
-
-                    let conftimes = conftime_req
-                        .request()
-                        .take(chunk_size)
-                        .map(|txid| {
-                            let confirmation_time = txid_to_height
-                                .get(txid)
-                                .map(|height| {
-                                    let timestamp =
-                                        *block_times.get(height).ok_or_else(electrum_goof)?;
-                                    Result::<_, Error>::Ok(BlockTime {
-                                        height: *height,
-                                        timestamp: timestamp.into(),
-                                    })
-                                })
-                                .transpose()?;
-                            Ok(confirmation_time)
-                        })
-                        .collect::<Result<_, Error>>()?;
-
-                    conftime_req.satisfy(conftimes)?
-                }
-                Request::Tx(tx_req) => {
-                    let needs_full = tx_req.request().take(chunk_size);
-                    tx_cache.save_txs(needs_full.clone())?;
-                    let full_transactions = needs_full
-                        .map(|txid| tx_cache.get(*txid).ok_or_else(electrum_goof))
-                        .collect::<Result<Vec<_>, _>>()?;
-                    let input_txs = full_transactions.iter().flat_map(|tx| {
-                        tx.input
-                            .iter()
-                            .filter(|input| !input.previous_output.is_null())
-                            .map(|input| &input.previous_output.txid)
-                    });
-                    tx_cache.save_txs(input_txs)?;
-
-                    let full_details = full_transactions
-                        .into_iter()
-                        .map(|tx| {
-                            let mut input_index = 0usize;
-                            let prev_outputs = tx
-                                .input
-                                .iter()
-                                .map(|input| {
-                                    if input.previous_output.is_null() {
-                                        return Ok(None);
-                                    }
-                                    let prev_tx = tx_cache
-                                        .get(input.previous_output.txid)
-                                        .ok_or_else(electrum_goof)?;
-                                    let txout = prev_tx
-                                        .output
-                                        .get(input.previous_output.vout as usize)
-                                        .ok_or_else(electrum_goof)?;
-                                    input_index += 1;
-                                    Ok(Some(txout.clone()))
-                                })
-                                .collect::<Result<Vec<_>, Error>>()?;
-                            Ok((prev_outputs, tx))
-                        })
-                        .collect::<Result<Vec<_>, Error>>()?;
-
-                    tx_req.satisfy(full_details)?
-                }
-                Request::Finish(batch_update) => break batch_update,
-            }
-        };
-
-        database.commit_batch(batch_update)?;
-        Ok(())
-    }
-}
-
-struct TxCache<'a, 'b, D> {
-    db: &'a D,
-    client: &'b Client,
-    cache: HashMap<Txid, Transaction>,
-}
-
-impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
-    fn new(db: &'a D, client: &'b Client) -> Self {
-        TxCache {
-            db,
-            client,
-            cache: HashMap::default(),
-        }
-    }
-    fn save_txs<'c>(&mut self, txids: impl Iterator<Item = &'c Txid>) -> Result<(), Error> {
-        let mut need_fetch = vec![];
-        for txid in txids {
-            if self.cache.get(txid).is_some() {
-                continue;
-            } else if let Some(transaction) = self.db.get_raw_tx(txid)? {
-                self.cache.insert(*txid, transaction);
-            } else {
-                need_fetch.push(txid);
-            }
-        }
-
-        if !need_fetch.is_empty() {
-            let txs = self
-                .client
-                .batch_transaction_get(need_fetch.clone())
-                .map_err(Error::Electrum)?;
-            let mut txs: HashMap<_, _> = txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
-            for txid in need_fetch {
-                if let Some(tx) = txs.remove(txid) {
-                    self.cache.insert(*txid, tx);
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    fn get(&self, txid: Txid) -> Option<Transaction> {
-        self.cache.get(&txid).map(Clone::clone)
-    }
-}
-
-/// Configuration for an [`ElectrumBlockchain`]
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
-pub struct ElectrumBlockchainConfig {
-    /// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port
-    ///
-    /// eg. `ssl://electrum.blockstream.info:60002`
-    pub url: String,
-    /// URL of the socks5 proxy server or a Tor service
-    pub socks5: Option<String>,
-    /// Request retry count
-    pub retry: u8,
-    /// Request timeout (seconds)
-    pub timeout: Option<u8>,
-    /// Stop searching addresses for transactions after finding an unused gap of this length
-    pub stop_gap: usize,
-    /// Validate the domain when using SSL
-    pub validate_domain: bool,
-}
-
-impl ConfigurableBlockchain for ElectrumBlockchain {
-    type Config = ElectrumBlockchainConfig;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        let socks5 = config.socks5.as_ref().map(Socks5Config::new);
-        let electrum_config = ConfigBuilder::new()
-            .retry(config.retry)
-            .timeout(config.timeout)?
-            .socks5(socks5)?
-            .validate_domain(config.validate_domain)
-            .build();
-
-        Ok(ElectrumBlockchain {
-            client: Client::from_config(config.url.as_str(), electrum_config)?,
-            stop_gap: config.stop_gap,
-        })
-    }
-}
-
-#[cfg(test)]
-#[cfg(feature = "test-electrum")]
-mod test {
-    use std::sync::Arc;
-
-    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! {
-        fn test_instance(test_client: &TestClient) -> ElectrumBlockchain {
-            ElectrumBlockchain::from(Client::new(&test_client.electrsd.electrum_url).unwrap())
-        }
-    }
-
-    fn get_factory() -> (TestClient, Arc<ElectrumBlockchain>) {
-        let test_client = TestClient::default();
-
-        let factory = Arc::new(ElectrumBlockchain::from(
-            Client::new(&test_client.electrsd.electrum_url).unwrap(),
-        ));
-
-        (test_client, factory)
-    }
-
-    #[test]
-    fn test_electrum_blockchain_factory() {
-        let (_test_client, factory) = get_factory();
-
-        let a = factory.build("aaaaaa", None).unwrap();
-        let b = factory.build("bbbbbb", None).unwrap();
-
-        assert_eq!(
-            a.client.block_headers_subscribe().unwrap().height,
-            b.client.block_headers_subscribe().unwrap().height
-        );
-    }
-
-    #[test]
-    fn test_electrum_blockchain_factory_sync_wallet() {
-        let (mut test_client, factory) = get_factory();
-
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new(
-            "wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
-            None,
-            bitcoin::Network::Regtest,
-            db,
-        )
-        .unwrap();
-
-        let address = wallet.get_address(AddressIndex::New).unwrap();
-
-        let tx = testutils! {
-            @tx ( (@addr address.address) => 50_000 )
-        };
-        test_client.receive(tx);
-
-        factory
-            .sync_wallet(&wallet, None, Default::default())
-            .unwrap();
-
-        assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
-    }
-
-    #[test]
-    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,
-                    validate_domain: true,
-                })
-            }
-        }
-
-        ElectrumTester.run();
-    }
-}
diff --git a/src/blockchain/esplora/async.rs b/src/blockchain/esplora/async.rs
deleted file mode 100644 (file)
index 0101074..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Esplora by way of `reqwest` HTTP client.
-
-use std::collections::{HashMap, HashSet};
-use std::ops::{Deref, DerefMut};
-
-use bitcoin::{Transaction, Txid};
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-use esplora_client::{convert_fee_rate, AsyncClient, Builder, Tx};
-use futures::stream::{FuturesOrdered, TryStreamExt};
-
-use crate::blockchain::*;
-use crate::database::BatchDatabase;
-use crate::error::Error;
-use crate::FeeRate;
-
-/// Structure that implements the logic to sync with Esplora
-///
-/// ## Example
-/// See the [`blockchain::esplora`](crate::blockchain::esplora) module for a usage example.
-#[derive(Debug)]
-pub struct EsploraBlockchain {
-    url_client: AsyncClient,
-    stop_gap: usize,
-    concurrency: u8,
-}
-
-impl std::convert::From<AsyncClient> for EsploraBlockchain {
-    fn from(url_client: AsyncClient) -> Self {
-        EsploraBlockchain {
-            url_client,
-            stop_gap: 20,
-            concurrency: super::DEFAULT_CONCURRENT_REQUESTS,
-        }
-    }
-}
-
-impl EsploraBlockchain {
-    /// Create a new instance of the client from a base URL and `stop_gap`.
-    pub fn new(base_url: &str, stop_gap: usize) -> Self {
-        let url_client = Builder::new(base_url)
-            .build_async()
-            .expect("Should never fail with no proxy and timeout");
-
-        Self::from_client(url_client, stop_gap)
-    }
-
-    /// Build a new instance given a client
-    pub fn from_client(url_client: AsyncClient, stop_gap: usize) -> Self {
-        EsploraBlockchain {
-            url_client,
-            stop_gap,
-            concurrency: super::DEFAULT_CONCURRENT_REQUESTS,
-        }
-    }
-
-    /// Set the concurrency to use when doing batch queries against the Esplora instance.
-    pub fn with_concurrency(mut self, concurrency: u8) -> Self {
-        self.concurrency = concurrency;
-        self
-    }
-}
-
-#[maybe_async]
-impl Blockchain for EsploraBlockchain {
-    fn get_capabilities(&self) -> HashSet<Capability> {
-        vec![
-            Capability::FullHistory,
-            Capability::GetAnyTx,
-            Capability::AccurateFees,
-        ]
-        .into_iter()
-        .collect()
-    }
-
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        Ok(await_or_block!(self.url_client.broadcast(tx))?)
-    }
-
-    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
-        let estimates = await_or_block!(self.url_client.get_fee_estimates())?;
-        Ok(FeeRate::from_sat_per_vb(convert_fee_rate(
-            target, estimates,
-        )?))
-    }
-}
-
-impl Deref for EsploraBlockchain {
-    type Target = AsyncClient;
-
-    fn deref(&self) -> &Self::Target {
-        &self.url_client
-    }
-}
-
-impl StatelessBlockchain for EsploraBlockchain {}
-
-#[maybe_async]
-impl GetHeight for EsploraBlockchain {
-    fn get_height(&self) -> Result<u32, Error> {
-        Ok(await_or_block!(self.url_client.get_height())?)
-    }
-}
-
-#[maybe_async]
-impl GetTx for EsploraBlockchain {
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        Ok(await_or_block!(self.url_client.get_tx(txid))?)
-    }
-}
-
-#[maybe_async]
-impl GetBlockHash for EsploraBlockchain {
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
-        Ok(await_or_block!(self
-            .url_client
-            .get_block_hash(height as u32))?)
-    }
-}
-
-#[maybe_async]
-impl WalletSync for EsploraBlockchain {
-    fn wallet_setup<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        _progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        use crate::blockchain::script_sync::Request;
-        let mut database = database.borrow_mut();
-        let database = database.deref_mut();
-        let mut request = script_sync::start(database, self.stop_gap)?;
-        let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
-
-        let batch_update = loop {
-            request = match request {
-                Request::Script(script_req) => {
-                    let futures: FuturesOrdered<_> = script_req
-                        .request()
-                        .take(self.concurrency as usize)
-                        .map(|script| async move {
-                            let mut related_txs: Vec<Tx> =
-                                self.url_client.scripthash_txs(script, None).await?;
-
-                            let n_confirmed =
-                                related_txs.iter().filter(|tx| tx.status.confirmed).count();
-                            // esplora pages on 25 confirmed transactions. If there's 25 or more we
-                            // keep requesting to see if there's more.
-                            if n_confirmed >= 25 {
-                                loop {
-                                    let new_related_txs: Vec<Tx> = self
-                                        .url_client
-                                        .scripthash_txs(
-                                            script,
-                                            Some(related_txs.last().unwrap().txid),
-                                        )
-                                        .await?;
-                                    let n = new_related_txs.len();
-                                    related_txs.extend(new_related_txs);
-                                    // we've reached the end
-                                    if n < 25 {
-                                        break;
-                                    }
-                                }
-                            }
-                            Result::<_, Error>::Ok(related_txs)
-                        })
-                        .collect();
-                    let txs_per_script: Vec<Vec<Tx>> = await_or_block!(futures.try_collect())?;
-                    let mut satisfaction = vec![];
-
-                    for txs in txs_per_script {
-                        satisfaction.push(
-                            txs.iter()
-                                .map(|tx| (tx.txid, tx.status.block_height))
-                                .collect(),
-                        );
-                        for tx in txs {
-                            tx_index.insert(tx.txid, tx);
-                        }
-                    }
-
-                    script_req.satisfy(satisfaction)?
-                }
-                Request::Conftime(conftime_req) => {
-                    let conftimes = conftime_req
-                        .request()
-                        .map(|txid| {
-                            tx_index
-                                .get(txid)
-                                .expect("must be in index")
-                                .confirmation_time()
-                                .map(Into::into)
-                        })
-                        .collect();
-                    conftime_req.satisfy(conftimes)?
-                }
-                Request::Tx(tx_req) => {
-                    let full_txs = tx_req
-                        .request()
-                        .map(|txid| {
-                            let tx = tx_index.get(txid).expect("must be in index");
-                            Ok((tx.previous_outputs(), tx.to_tx()))
-                        })
-                        .collect::<Result<_, Error>>()?;
-                    tx_req.satisfy(full_txs)?
-                }
-                Request::Finish(batch_update) => break batch_update,
-            }
-        };
-
-        database.commit_batch(batch_update)?;
-        Ok(())
-    }
-}
-
-impl ConfigurableBlockchain for EsploraBlockchain {
-    type Config = super::EsploraBlockchainConfig;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        let mut builder = Builder::new(config.base_url.as_str());
-
-        if let Some(timeout) = config.timeout {
-            builder = builder.timeout(timeout);
-        }
-
-        if let Some(proxy) = &config.proxy {
-            builder = builder.proxy(proxy);
-        }
-
-        let mut blockchain =
-            EsploraBlockchain::from_client(builder.build_async()?, config.stop_gap);
-
-        if let Some(concurrency) = config.concurrency {
-            blockchain = blockchain.with_concurrency(concurrency);
-        }
-
-        Ok(blockchain)
-    }
-}
diff --git a/src/blockchain/esplora/blocking.rs b/src/blockchain/esplora/blocking.rs
deleted file mode 100644 (file)
index e75e545..0000000
+++ /dev/null
@@ -1,241 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Esplora by way of `ureq` HTTP client.
-
-use std::collections::{HashMap, HashSet};
-use std::ops::DerefMut;
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-use bitcoin::{Transaction, Txid};
-
-use esplora_client::{convert_fee_rate, BlockingClient, Builder, Tx};
-
-use crate::blockchain::*;
-use crate::database::BatchDatabase;
-use crate::error::Error;
-use crate::FeeRate;
-
-/// Structure that implements the logic to sync with Esplora
-///
-/// ## Example
-/// See the [`blockchain::esplora`](crate::blockchain::esplora) module for a usage example.
-#[derive(Debug)]
-pub struct EsploraBlockchain {
-    url_client: BlockingClient,
-    stop_gap: usize,
-    concurrency: u8,
-}
-
-impl EsploraBlockchain {
-    /// Create a new instance of the client from a base URL and the `stop_gap`.
-    pub fn new(base_url: &str, stop_gap: usize) -> Self {
-        let url_client = Builder::new(base_url)
-            .build_blocking()
-            .expect("Should never fail with no proxy and timeout");
-
-        Self::from_client(url_client, stop_gap)
-    }
-
-    /// Build a new instance given a client
-    pub fn from_client(url_client: BlockingClient, stop_gap: usize) -> Self {
-        EsploraBlockchain {
-            url_client,
-            concurrency: super::DEFAULT_CONCURRENT_REQUESTS,
-            stop_gap,
-        }
-    }
-
-    /// Set the number of parallel requests the client can make.
-    pub fn with_concurrency(mut self, concurrency: u8) -> Self {
-        self.concurrency = concurrency;
-        self
-    }
-}
-
-impl Blockchain for EsploraBlockchain {
-    fn get_capabilities(&self) -> HashSet<Capability> {
-        vec![
-            Capability::FullHistory,
-            Capability::GetAnyTx,
-            Capability::AccurateFees,
-        ]
-        .into_iter()
-        .collect()
-    }
-
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        self.url_client.broadcast(tx)?;
-        Ok(())
-    }
-
-    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
-        let estimates = self.url_client.get_fee_estimates()?;
-        Ok(FeeRate::from_sat_per_vb(convert_fee_rate(
-            target, estimates,
-        )?))
-    }
-}
-
-impl Deref for EsploraBlockchain {
-    type Target = BlockingClient;
-
-    fn deref(&self) -> &Self::Target {
-        &self.url_client
-    }
-}
-
-impl StatelessBlockchain for EsploraBlockchain {}
-
-impl GetHeight for EsploraBlockchain {
-    fn get_height(&self) -> Result<u32, Error> {
-        Ok(self.url_client.get_height()?)
-    }
-}
-
-impl GetTx for EsploraBlockchain {
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        Ok(self.url_client.get_tx(txid)?)
-    }
-}
-
-impl GetBlockHash for EsploraBlockchain {
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
-        Ok(self.url_client.get_block_hash(height as u32)?)
-    }
-}
-
-impl WalletSync for EsploraBlockchain {
-    fn wallet_setup<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        _progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        use crate::blockchain::script_sync::Request;
-        let mut database = database.borrow_mut();
-        let database = database.deref_mut();
-        let mut request = script_sync::start(database, self.stop_gap)?;
-        let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
-        let batch_update = loop {
-            request = match request {
-                Request::Script(script_req) => {
-                    let scripts = script_req
-                        .request()
-                        .take(self.concurrency as usize)
-                        .cloned();
-
-                    let mut handles = vec![];
-                    for script in scripts {
-                        let client = self.url_client.clone();
-                        // make each request in its own thread.
-                        handles.push(std::thread::spawn(move || {
-                            let mut related_txs: Vec<Tx> = client.scripthash_txs(&script, None)?;
-
-                            let n_confirmed =
-                                related_txs.iter().filter(|tx| tx.status.confirmed).count();
-                            // esplora pages on 25 confirmed transactions. If there's 25 or more we
-                            // keep requesting to see if there's more.
-                            if n_confirmed >= 25 {
-                                loop {
-                                    let new_related_txs: Vec<Tx> = client.scripthash_txs(
-                                        &script,
-                                        Some(related_txs.last().unwrap().txid),
-                                    )?;
-                                    let n = new_related_txs.len();
-                                    related_txs.extend(new_related_txs);
-                                    // we've reached the end
-                                    if n < 25 {
-                                        break;
-                                    }
-                                }
-                            }
-                            Result::<_, Error>::Ok(related_txs)
-                        }));
-                    }
-
-                    let txs_per_script: Vec<Vec<Tx>> = handles
-                        .into_iter()
-                        .map(|handle| handle.join().unwrap())
-                        .collect::<Result<_, _>>()?;
-                    let mut satisfaction = vec![];
-
-                    for txs in txs_per_script {
-                        satisfaction.push(
-                            txs.iter()
-                                .map(|tx| (tx.txid, tx.status.block_height))
-                                .collect(),
-                        );
-                        for tx in txs {
-                            tx_index.insert(tx.txid, tx);
-                        }
-                    }
-
-                    script_req.satisfy(satisfaction)?
-                }
-                Request::Conftime(conftime_req) => {
-                    let conftimes = conftime_req
-                        .request()
-                        .map(|txid| {
-                            tx_index
-                                .get(txid)
-                                .expect("must be in index")
-                                .confirmation_time()
-                                .map(Into::into)
-                        })
-                        .collect();
-                    conftime_req.satisfy(conftimes)?
-                }
-                Request::Tx(tx_req) => {
-                    let full_txs = tx_req
-                        .request()
-                        .map(|txid| {
-                            let tx = tx_index.get(txid).expect("must be in index");
-                            Ok((tx.previous_outputs(), tx.to_tx()))
-                        })
-                        .collect::<Result<_, Error>>()?;
-                    tx_req.satisfy(full_txs)?
-                }
-                Request::Finish(batch_update) => break batch_update,
-            }
-        };
-
-        database.commit_batch(batch_update)?;
-
-        Ok(())
-    }
-}
-
-impl ConfigurableBlockchain for EsploraBlockchain {
-    type Config = super::EsploraBlockchainConfig;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        let mut builder = Builder::new(config.base_url.as_str());
-
-        if let Some(timeout) = config.timeout {
-            builder = builder.timeout(timeout);
-        }
-
-        if let Some(proxy) = &config.proxy {
-            builder = builder.proxy(proxy);
-        }
-
-        let mut blockchain =
-            EsploraBlockchain::from_client(builder.build_blocking()?, config.stop_gap);
-
-        if let Some(concurrency) = config.concurrency {
-            blockchain = blockchain.with_concurrency(concurrency);
-        }
-
-        Ok(blockchain)
-    }
-}
diff --git a/src/blockchain/esplora/mod.rs b/src/blockchain/esplora/mod.rs
deleted file mode 100644 (file)
index c430840..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-//! Esplora
-//!
-//! This module defines a [`EsploraBlockchain`] struct that can query an Esplora
-//! backend populate the wallet's [database](crate::database::Database) by:
-//!
-//! ## Example
-//!
-//! ```no_run
-//! # use bdk::blockchain::esplora::EsploraBlockchain;
-//! let blockchain = EsploraBlockchain::new("https://blockstream.info/testnet/api", 20);
-//! # Ok::<(), bdk::Error>(())
-//! ```
-//!
-//! Esplora blockchain can use either `ureq` or `reqwest` for the HTTP client
-//! depending on your needs (blocking or async respectively).
-//!
-//! Please note, to configure the Esplora HTTP client correctly use one of:
-//! Blocking:  --features='use-esplora-blocking'
-//! Async:     --features='async-interface,use-esplora-async' --no-default-features
-
-pub use esplora_client::Error as EsploraError;
-
-#[cfg(feature = "use-esplora-async")]
-mod r#async;
-
-#[cfg(feature = "use-esplora-async")]
-pub use self::r#async::*;
-
-#[cfg(feature = "use-esplora-blocking")]
-mod blocking;
-
-#[cfg(feature = "use-esplora-blocking")]
-pub use self::blocking::*;
-
-/// Configuration for an [`EsploraBlockchain`]
-#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
-pub struct EsploraBlockchainConfig {
-    /// Base URL of the esplora service
-    ///
-    /// eg. `https://blockstream.info/api/`
-    pub base_url: String,
-    /// Optional URL of the proxy to use to make requests to the Esplora server
-    ///
-    /// The string should be formatted as: `<protocol>://<user>:<password>@host:<port>`.
-    ///
-    /// Note that the format of this value and the supported protocols change slightly between the
-    /// sync version of esplora (using `ureq`) and the async version (using `reqwest`). For more
-    /// details check with the documentation of the two crates. Both of them are compiled with
-    /// the `socks` feature enabled.
-    ///
-    /// The proxy is ignored when targeting `wasm32`.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub proxy: Option<String>,
-    /// Number of parallel requests sent to the esplora service (default: 4)
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub concurrency: Option<u8>,
-    /// Stop searching addresses for transactions after finding an unused gap of this length.
-    pub stop_gap: usize,
-    /// Socket timeout.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub timeout: Option<u64>,
-}
-
-impl EsploraBlockchainConfig {
-    /// create a config with default values given the base url and stop gap
-    pub fn new(base_url: String, stop_gap: usize) -> Self {
-        Self {
-            base_url,
-            proxy: None,
-            timeout: None,
-            stop_gap,
-            concurrency: None,
-        }
-    }
-}
-
-impl From<esplora_client::BlockTime> for crate::BlockTime {
-    fn from(esplora_client::BlockTime { timestamp, height }: esplora_client::BlockTime) -> Self {
-        Self { timestamp, height }
-    }
-}
-
-#[cfg(test)]
-#[cfg(feature = "test-esplora")]
-crate::bdk_blockchain_tests! {
-    fn test_instance(test_client: &TestClient) -> EsploraBlockchain {
-        EsploraBlockchain::new(&format!("http://{}",test_client.electrsd.esplora_url.as_ref().unwrap()), 20)
-    }
-}
-
-const DEFAULT_CONCURRENT_REQUESTS: u8 = 4;
-
-#[cfg(test)]
-mod test {
-    #[test]
-    #[cfg(feature = "test-esplora")]
-    fn test_esplora_with_variable_configs() {
-        use super::*;
-
-        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();
-    }
-}
diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs
deleted file mode 100644 (file)
index 13c0b2a..0000000
+++ /dev/null
@@ -1,391 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Blockchain backends
-//!
-//! This module provides the implementation of a few commonly-used backends like
-//! [Electrum](crate::blockchain::electrum), [Esplora](crate::blockchain::esplora) and
-//! [Compact Filters/Neutrino](crate::blockchain::compact_filters), along with a generalized trait
-//! [`Blockchain`] that can be implemented to build customized backends.
-
-use std::cell::RefCell;
-use std::collections::HashSet;
-use std::ops::Deref;
-use std::sync::mpsc::{channel, Receiver, Sender};
-use std::sync::Arc;
-
-use bitcoin::{BlockHash, Transaction, Txid};
-
-use crate::database::BatchDatabase;
-use crate::error::Error;
-use crate::wallet::{wallet_name_from_descriptor, Wallet};
-use crate::{FeeRate, KeychainKind};
-
-#[cfg(any(
-    feature = "electrum",
-    feature = "esplora",
-    feature = "compact_filters",
-    feature = "rpc"
-))]
-pub mod any;
-mod script_sync;
-
-#[cfg(any(
-    feature = "electrum",
-    feature = "esplora",
-    feature = "compact_filters",
-    feature = "rpc"
-))]
-pub use any::{AnyBlockchain, AnyBlockchainConfig};
-
-#[cfg(feature = "electrum")]
-#[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]
-pub mod electrum;
-#[cfg(feature = "electrum")]
-pub use self::electrum::ElectrumBlockchain;
-#[cfg(feature = "electrum")]
-pub use self::electrum::ElectrumBlockchainConfig;
-
-#[cfg(feature = "rpc")]
-#[cfg_attr(docsrs, doc(cfg(feature = "rpc")))]
-pub mod rpc;
-#[cfg(feature = "rpc")]
-pub use self::rpc::RpcBlockchain;
-#[cfg(feature = "rpc")]
-pub use self::rpc::RpcConfig;
-
-#[cfg(feature = "esplora")]
-#[cfg_attr(docsrs, doc(cfg(feature = "esplora")))]
-pub mod esplora;
-#[cfg(feature = "esplora")]
-pub use self::esplora::EsploraBlockchain;
-
-#[cfg(feature = "compact_filters")]
-#[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))]
-pub mod compact_filters;
-
-#[cfg(feature = "compact_filters")]
-pub use self::compact_filters::CompactFiltersBlockchain;
-
-/// Capabilities that can be supported by a [`Blockchain`] backend
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum Capability {
-    /// Can recover the full history of a wallet and not only the set of currently spendable UTXOs
-    FullHistory,
-    /// Can fetch any historical transaction given its txid
-    GetAnyTx,
-    /// Can compute accurate fees for the transactions found during sync
-    AccurateFees,
-}
-
-/// Trait that defines the actions that must be supported by a blockchain backend
-#[maybe_async]
-pub trait Blockchain: WalletSync + GetHeight + GetTx + GetBlockHash {
-    /// Return the set of [`Capability`] supported by this backend
-    fn get_capabilities(&self) -> HashSet<Capability>;
-    /// Broadcast a transaction
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
-    /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
-    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
-}
-
-/// Trait for getting the current height of the blockchain.
-#[maybe_async]
-pub trait GetHeight {
-    /// Return the current height
-    fn get_height(&self) -> Result<u32, Error>;
-}
-
-#[maybe_async]
-/// Trait for getting a transaction by txid
-pub trait GetTx {
-    /// Fetch a transaction given its txid
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
-}
-
-#[maybe_async]
-/// Trait for getting block hash by block height
-pub trait GetBlockHash {
-    /// fetch block hash given its height
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error>;
-}
-
-/// Trait for blockchains that can sync by updating the database directly.
-#[maybe_async]
-pub trait WalletSync {
-    /// Setup the backend and populate the internal database for the first time
-    ///
-    /// This method is the equivalent of [`Self::wallet_sync`], but it's guaranteed to only be
-    /// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync).
-    ///
-    /// The rationale behind the distinction between `sync` and `setup` is that some custom backends
-    /// might need to perform specific actions only the first time they are synced.
-    ///
-    /// For types that do not have that distinction, only this method can be implemented, since
-    /// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden.
-    /// Populate the internal database with transactions and UTXOs
-    fn wallet_setup<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error>;
-
-    /// If not overridden, it defaults to calling [`Self::wallet_setup`] internally.
-    ///
-    /// This method should implement the logic required to iterate over the list of the wallet's
-    /// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
-    /// in the blockchain to populate the database with [`BatchOperations::set_tx`] and
-    /// [`BatchOperations::set_utxo`].
-    ///
-    /// This method should also take care of removing UTXOs that are seen as spent in the
-    /// blockchain, using [`BatchOperations::del_utxo`].
-    ///
-    /// The `progress_update` object can be used to give the caller updates about the progress by using
-    /// [`Progress::update`].
-    ///
-    /// [`Database::iter_script_pubkeys`]: crate::database::Database::iter_script_pubkeys
-    /// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx
-    /// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo
-    /// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
-    fn wallet_sync<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        maybe_await!(self.wallet_setup(database, progress_update))
-    }
-}
-
-/// Trait for [`Blockchain`] types that can be created given a configuration
-pub trait ConfigurableBlockchain: Blockchain + Sized {
-    /// Type that contains the configuration
-    type Config: std::fmt::Debug;
-
-    /// Create a new instance given a configuration
-    fn from_config(config: &Self::Config) -> Result<Self, Error>;
-}
-
-/// Trait for blockchains that don't contain any state
-///
-/// Statless blockchains can be used to sync multiple wallets with different descriptors.
-///
-/// [`BlockchainFactory`] is automatically implemented for `Arc<T>` where `T` is a stateless
-/// blockchain.
-pub trait StatelessBlockchain: Blockchain {}
-
-/// Trait for a factory of blockchains that share the underlying connection or configuration
-#[cfg_attr(
-    not(feature = "async-interface"),
-    doc = r##"
-## Example
-
-This example shows how to sync multiple walles and return the sum of their balances
-
-```no_run
-# use bdk::Error;
-# use bdk::blockchain::*;
-# use bdk::database::*;
-# use bdk::wallet::*;
-# use bdk::*;
-fn sum_of_balances<B: BlockchainFactory>(blockchain_factory: B, wallets: &[Wallet<MemoryDatabase>]) -> Result<Balance, Error> {
-    Ok(wallets
-        .iter()
-        .map(|w| -> Result<_, Error> {
-            blockchain_factory.sync_wallet(&w, None, SyncOptions::default())?;
-            w.get_balance()
-        })
-        .collect::<Result<Vec<_>, _>>()?
-        .into_iter()
-        .sum())
-}
-```
-"##
-)]
-pub trait BlockchainFactory {
-    /// The type returned when building a blockchain from this factory
-    type Inner: Blockchain;
-
-    /// Build a new blockchain for the given descriptor wallet_name
-    ///
-    /// If `override_skip_blocks` is `None`, the returned blockchain will inherit the number of blocks
-    /// from the factory. Since it's not possible to override the value to `None`, set it to
-    /// `Some(0)` to rescan from the genesis.
-    fn build(
-        &self,
-        wallet_name: &str,
-        override_skip_blocks: Option<u32>,
-    ) -> Result<Self::Inner, Error>;
-
-    /// Build a new blockchain for a given wallet
-    ///
-    /// Internally uses [`wallet_name_from_descriptor`] to derive the name, and then calls
-    /// [`BlockchainFactory::build`] to create the blockchain instance.
-    fn build_for_wallet<D: BatchDatabase>(
-        &self,
-        wallet: &Wallet<D>,
-        override_skip_blocks: Option<u32>,
-    ) -> Result<Self::Inner, Error> {
-        let wallet_name = wallet_name_from_descriptor(
-            wallet.public_descriptor(KeychainKind::External)?.unwrap(),
-            wallet.public_descriptor(KeychainKind::Internal)?,
-            wallet.network(),
-            wallet.secp_ctx(),
-        )?;
-        self.build(&wallet_name, override_skip_blocks)
-    }
-
-    /// Use [`BlockchainFactory::build_for_wallet`] to get a blockchain, then sync the wallet
-    ///
-    /// This can be used when a new blockchain would only be used to sync a wallet and then
-    /// immediately dropped. Keep in mind that specific blockchain factories may perform slow
-    /// operations to build a blockchain for a given wallet, so if a wallet needs to be synced
-    /// often it's recommended to use [`BlockchainFactory::build_for_wallet`] to reuse the same
-    /// blockchain multiple times.
-    #[cfg(not(feature = "async-interface"))]
-    #[cfg_attr(docsrs, doc(cfg(not(feature = "async-interface"))))]
-    fn sync_wallet<D: BatchDatabase>(
-        &self,
-        wallet: &Wallet<D>,
-        override_skip_blocks: Option<u32>,
-        sync_options: crate::wallet::SyncOptions,
-    ) -> Result<(), Error> {
-        let blockchain = self.build_for_wallet(wallet, override_skip_blocks)?;
-        wallet.sync(&blockchain, sync_options)
-    }
-}
-
-impl<T: StatelessBlockchain> BlockchainFactory for Arc<T> {
-    type Inner = Self;
-
-    fn build(&self, _wallet_name: &str, _override_skip_blocks: Option<u32>) -> Result<Self, Error> {
-        Ok(Arc::clone(self))
-    }
-}
-
-/// Data sent with a progress update over a [`channel`]
-pub type ProgressData = (f32, Option<String>);
-
-/// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and
-/// [`WalletSync::wallet_setup`]
-pub trait Progress: Send + 'static + core::fmt::Debug {
-    /// Send a new progress update
-    ///
-    /// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an
-    /// optional text message that can be displayed to the user.
-    fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error>;
-}
-
-/// Shortcut to create a [`channel`] (pair of [`Sender`] and [`Receiver`]) that can transport [`ProgressData`]
-pub fn progress() -> (Sender<ProgressData>, Receiver<ProgressData>) {
-    channel()
-}
-
-impl Progress for Sender<ProgressData> {
-    fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
-        if !(0.0..=100.0).contains(&progress) {
-            return Err(Error::InvalidProgressValue(progress));
-        }
-
-        self.send((progress, message))
-            .map_err(|_| Error::ProgressUpdateError)
-    }
-}
-
-/// Type that implements [`Progress`] and drops every update received
-#[derive(Clone, Copy, Default, Debug)]
-pub struct NoopProgress;
-
-/// Create a new instance of [`NoopProgress`]
-pub fn noop_progress() -> NoopProgress {
-    NoopProgress
-}
-
-impl Progress for NoopProgress {
-    fn update(&self, _progress: f32, _message: Option<String>) -> Result<(), Error> {
-        Ok(())
-    }
-}
-
-/// Type that implements [`Progress`] and logs at level `INFO` every update received
-#[derive(Clone, Copy, Default, Debug)]
-pub struct LogProgress;
-
-/// Create a new instance of [`LogProgress`]
-pub fn log_progress() -> LogProgress {
-    LogProgress
-}
-
-impl Progress for LogProgress {
-    fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
-        log::info!(
-            "Sync {:.3}%: `{}`",
-            progress,
-            message.unwrap_or_else(|| "".into())
-        );
-
-        Ok(())
-    }
-}
-
-#[maybe_async]
-impl<T: Blockchain> Blockchain for Arc<T> {
-    fn get_capabilities(&self) -> HashSet<Capability> {
-        maybe_await!(self.deref().get_capabilities())
-    }
-
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        maybe_await!(self.deref().broadcast(tx))
-    }
-
-    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
-        maybe_await!(self.deref().estimate_fee(target))
-    }
-}
-
-#[maybe_async]
-impl<T: GetTx> GetTx for Arc<T> {
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        maybe_await!(self.deref().get_tx(txid))
-    }
-}
-
-#[maybe_async]
-impl<T: GetHeight> GetHeight for Arc<T> {
-    fn get_height(&self) -> Result<u32, Error> {
-        maybe_await!(self.deref().get_height())
-    }
-}
-
-#[maybe_async]
-impl<T: GetBlockHash> GetBlockHash for Arc<T> {
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
-        maybe_await!(self.deref().get_block_hash(height))
-    }
-}
-
-#[maybe_async]
-impl<T: WalletSync> WalletSync for Arc<T> {
-    fn wallet_setup<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        maybe_await!(self.deref().wallet_setup(database, progress_update))
-    }
-
-    fn wallet_sync<D: BatchDatabase>(
-        &self,
-        database: &RefCell<D>,
-        progress_update: Box<dyn Progress>,
-    ) -> Result<(), Error> {
-        maybe_await!(self.deref().wallet_sync(database, progress_update))
-    }
-}
diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs
deleted file mode 100644 (file)
index da19e20..0000000
+++ /dev/null
@@ -1,1003 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2021 by Riccardo Casatta <riccardo@casatta.it>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Rpc Blockchain
-//!
-//! Backend that gets blockchain data from Bitcoin Core RPC
-//!
-//! This is an **EXPERIMENTAL** feature, API and other major changes are expected.
-//!
-//! ## Example
-//!
-//! ```no_run
-//! # use bdk::blockchain::{RpcConfig, RpcBlockchain, ConfigurableBlockchain, rpc::Auth};
-//! let config = RpcConfig {
-//!     url: "127.0.0.1:18332".to_string(),
-//!     auth: Auth::Cookie {
-//!         file: "/home/user/.bitcoin/.cookie".into(),
-//!     },
-//!     network: bdk::bitcoin::Network::Testnet,
-//!     wallet_name: "wallet_name".to_string(),
-//!     sync_params: None,
-//! };
-//! let blockchain = RpcBlockchain::from_config(&config);
-//! ```
-
-use crate::bitcoin::hashes::hex::ToHex;
-use crate::bitcoin::{Network, OutPoint, Transaction, TxOut, Txid};
-use crate::blockchain::*;
-use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
-use crate::descriptor::calc_checksum;
-use crate::error::MissingCachedScripts;
-use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
-use bitcoin::Script;
-use bitcoincore_rpc::json::{
-    GetTransactionResultDetailCategory, ImportMultiOptions, ImportMultiRequest,
-    ImportMultiRequestScriptPubkey, ImportMultiRescanSince, ListTransactionResult,
-    ListUnspentResultEntry, ScanningDetails,
-};
-use bitcoincore_rpc::jsonrpc::serde_json::{json, Value};
-use bitcoincore_rpc::Auth as RpcAuth;
-use bitcoincore_rpc::{Client, RpcApi};
-use log::{debug, info};
-use serde::{Deserialize, Serialize};
-use std::cell::RefCell;
-use std::collections::{HashMap, HashSet};
-use std::ops::{Deref, DerefMut};
-use std::path::PathBuf;
-use std::thread;
-use std::time::Duration;
-
-/// The main struct for RPC backend implementing the [crate::blockchain::Blockchain] trait
-#[derive(Debug)]
-pub struct RpcBlockchain {
-    /// Rpc client to the node, includes the wallet name
-    client: Client,
-    /// Whether the wallet is a "descriptor" or "legacy" wallet in Core
-    is_descriptors: bool,
-    /// Blockchain capabilities, cached here at startup
-    capabilities: HashSet<Capability>,
-    /// Sync parameters.
-    sync_params: RpcSyncParams,
-}
-
-impl Deref for RpcBlockchain {
-    type Target = Client;
-
-    fn deref(&self) -> &Self::Target {
-        &self.client
-    }
-}
-
-/// RpcBlockchain configuration options
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
-pub struct RpcConfig {
-    /// The bitcoin node url
-    pub url: String,
-    /// The bitcoin node authentication mechanism
-    pub auth: Auth,
-    /// The network we are using (it will be checked the bitcoin node network matches this)
-    pub network: Network,
-    /// The wallet name in the bitcoin node, consider using [crate::wallet::wallet_name_from_descriptor] for this
-    pub wallet_name: String,
-    /// Sync parameters
-    pub sync_params: Option<RpcSyncParams>,
-}
-
-/// Sync parameters for Bitcoin Core RPC.
-///
-/// In general, BDK tries to sync `scriptPubKey`s cached in [`crate::database::Database`] with
-/// `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining
-/// how the `importdescriptors` RPC calls are to be made.
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
-pub struct RpcSyncParams {
-    /// The minimum number of scripts to scan for on initial sync.
-    pub start_script_count: usize,
-    /// Time in unix seconds in which initial sync will start scanning from (0 to start from genesis).
-    pub start_time: u64,
-    /// Forces every sync to use `start_time` as import timestamp.
-    pub force_start_time: bool,
-    /// RPC poll rate (in seconds) to get state updates.
-    pub poll_rate_sec: u64,
-}
-
-impl Default for RpcSyncParams {
-    fn default() -> Self {
-        Self {
-            start_script_count: 100,
-            start_time: 0,
-            force_start_time: false,
-            poll_rate_sec: 3,
-        }
-    }
-}
-
-/// This struct is equivalent to [bitcoincore_rpc::Auth] but it implements [serde::Serialize]
-/// To be removed once upstream equivalent is implementing Serialize (json serialization format
-/// should be the same), see [rust-bitcoincore-rpc/pull/181](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/181)
-#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-#[serde(untagged)]
-pub enum Auth {
-    /// None authentication
-    None,
-    /// Authentication with username and password, usually [Auth::Cookie] should be preferred
-    UserPass {
-        /// Username
-        username: String,
-        /// Password
-        password: String,
-    },
-    /// Authentication with a cookie file
-    Cookie {
-        /// Cookie file
-        file: PathBuf,
-    },
-}
-
-impl From<Auth> for RpcAuth {
-    fn from(auth: Auth) -> Self {
-        match auth {
-            Auth::None => RpcAuth::None,
-            Auth::UserPass { username, password } => RpcAuth::UserPass(username, password),
-            Auth::Cookie { file } => RpcAuth::CookieFile(file),
-        }
-    }
-}
-
-impl Blockchain for RpcBlockchain {
-    fn get_capabilities(&self) -> HashSet<Capability> {
-        self.capabilities.clone()
-    }
-
-    fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
-    }
-
-    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
-        let sat_per_kb = self
-            .client
-            .estimate_smart_fee(target as u16, None)?
-            .fee_rate
-            .ok_or(Error::FeeRateUnavailable)?
-            .to_sat() as f64;
-
-        Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
-    }
-}
-
-impl GetTx for RpcBlockchain {
-    fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        Ok(Some(self.client.get_raw_transaction(txid, None)?))
-    }
-}
-
-impl GetHeight for RpcBlockchain {
-    fn get_height(&self) -> Result<u32, Error> {
-        Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
-    }
-}
-
-impl GetBlockHash for RpcBlockchain {
-    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
-        Ok(self.client.get_block_hash(height)?)
-    }
-}
-
-impl WalletSync for RpcBlockchain {
-    fn wallet_setup<D>(&self, db: &RefCell<D>, prog: Box<dyn Progress>) -> Result<(), Error>
-    where
-        D: BatchDatabase,
-    {
-        let mut db = db.borrow_mut();
-        let db = db.deref_mut();
-        let batch = DbState::new(db, &self.sync_params, &*prog)?
-            .sync_with_core(&self.client, self.is_descriptors)?
-            .as_db_batch()?;
-
-        db.commit_batch(batch)
-    }
-}
-
-impl ConfigurableBlockchain for RpcBlockchain {
-    type Config = RpcConfig;
-
-    /// Returns RpcBlockchain backend creating an RPC client to a specific wallet named as the descriptor's checksum
-    /// if it's the first time it creates the wallet in the node and upon return is granted the wallet is loaded
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        let wallet_url = format!("{}/wallet/{}", config.url, &config.wallet_name);
-
-        let client = Client::new(wallet_url.as_str(), config.auth.clone().into())?;
-        let rpc_version = client.version()?;
-
-        info!("connected to '{}' with auth: {:?}", wallet_url, config.auth);
-
-        if client.list_wallets()?.contains(&config.wallet_name) {
-            info!("wallet already loaded: {}", config.wallet_name);
-        } else if list_wallet_dir(&client)?.contains(&config.wallet_name) {
-            client.load_wallet(&config.wallet_name)?;
-            info!("wallet loaded: {}", config.wallet_name);
-        } else {
-            // pre-0.21 use legacy wallets
-            if rpc_version < 210_000 {
-                client.create_wallet(&config.wallet_name, Some(true), None, None, None)?;
-            } else {
-                // TODO: move back to api call when https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/225 is closed
-                let args = [
-                    Value::String(config.wallet_name.clone()),
-                    Value::Bool(true),
-                    Value::Bool(false),
-                    Value::Null,
-                    Value::Bool(false),
-                    Value::Bool(true),
-                ];
-                let _: Value = client.call("createwallet", &args)?;
-            }
-
-            info!("wallet created: {}", config.wallet_name);
-        }
-
-        let is_descriptors = is_wallet_descriptor(&client)?;
-
-        let blockchain_info = client.get_blockchain_info()?;
-        let network = match blockchain_info.chain.as_str() {
-            "main" => Network::Bitcoin,
-            "test" => Network::Testnet,
-            "regtest" => Network::Regtest,
-            "signet" => Network::Signet,
-            _ => return Err(Error::Generic("Invalid network".to_string())),
-        };
-        if network != config.network {
-            return Err(Error::InvalidNetwork {
-                requested: config.network,
-                found: network,
-            });
-        }
-
-        let mut capabilities: HashSet<_> = vec![Capability::FullHistory].into_iter().collect();
-        if rpc_version >= 210_000 {
-            let info: HashMap<String, Value> = client.call("getindexinfo", &[]).unwrap();
-            if info.contains_key("txindex") {
-                capabilities.insert(Capability::GetAnyTx);
-                capabilities.insert(Capability::AccurateFees);
-            }
-        }
-
-        Ok(RpcBlockchain {
-            client,
-            capabilities,
-            is_descriptors,
-            sync_params: config.sync_params.clone().unwrap_or_default(),
-        })
-    }
-}
-
-/// return the wallets available in default wallet directory
-//TODO use bitcoincore_rpc method when PR #179 lands
-fn list_wallet_dir(client: &Client) -> Result<Vec<String>, Error> {
-    #[derive(Deserialize)]
-    struct Name {
-        name: String,
-    }
-    #[derive(Deserialize)]
-    struct CallResult {
-        wallets: Vec<Name>,
-    }
-
-    let result: CallResult = client.call("listwalletdir", &[])?;
-    Ok(result.wallets.into_iter().map(|n| n.name).collect())
-}
-
-/// Represents the state of the [`crate::database::Database`].
-struct DbState<'a, D> {
-    db: &'a D,
-    params: &'a RpcSyncParams,
-    prog: &'a dyn Progress,
-
-    ext_spks: Vec<Script>,
-    int_spks: Vec<Script>,
-    txs: HashMap<Txid, TransactionDetails>,
-    utxos: HashSet<LocalUtxo>,
-    last_indexes: HashMap<KeychainKind, u32>,
-
-    // "deltas" to apply to database
-    retained_txs: HashSet<Txid>, // txs to retain (everything else should be deleted)
-    updated_txs: HashSet<Txid>,  // txs to update
-    updated_utxos: HashSet<LocalUtxo>, // utxos to update
-}
-
-impl<'a, D: BatchDatabase> DbState<'a, D> {
-    /// Obtain [DbState] from [crate::database::Database].
-    fn new(db: &'a D, params: &'a RpcSyncParams, prog: &'a dyn Progress) -> Result<Self, Error> {
-        let ext_spks = db.iter_script_pubkeys(Some(KeychainKind::External))?;
-        let int_spks = db.iter_script_pubkeys(Some(KeychainKind::Internal))?;
-
-        // This is a hack to see whether atleast one of the keychains comes from a derivable
-        // descriptor. We assume that non-derivable descriptors always has a script count of 1.
-        let last_count = std::cmp::max(ext_spks.len(), int_spks.len());
-        let has_derivable = last_count > 1;
-
-        // If at least one descriptor is derivable, we need to ensure scriptPubKeys are sufficiently
-        // cached.
-        if has_derivable && last_count < params.start_script_count {
-            let inner_err = MissingCachedScripts {
-                last_count,
-                missing_count: params.start_script_count - last_count,
-            };
-            debug!("requesting more spks with: {:?}", inner_err);
-            return Err(Error::MissingCachedScripts(inner_err));
-        }
-
-        let txs = db
-            .iter_txs(true)?
-            .into_iter()
-            .map(|tx| (tx.txid, tx))
-            .collect::<HashMap<_, _>>();
-
-        let utxos = db.iter_utxos()?.into_iter().collect::<HashSet<_>>();
-
-        let last_indexes = [KeychainKind::External, KeychainKind::Internal]
-            .iter()
-            .filter_map(|keychain| match db.get_last_index(*keychain) {
-                Ok(li_opt) => li_opt.map(|li| Ok((*keychain, li))),
-                Err(err) => Some(Err(err)),
-            })
-            .collect::<Result<HashMap<_, _>, Error>>()?;
-
-        info!("initial db state: txs={} utxos={}", txs.len(), utxos.len());
-
-        // "delta" fields
-        let retained_txs = HashSet::with_capacity(txs.len());
-        let updated_txs = HashSet::with_capacity(txs.len());
-        let updated_utxos = HashSet::with_capacity(utxos.len());
-
-        Ok(Self {
-            db,
-            params,
-            prog,
-            ext_spks,
-            int_spks,
-            txs,
-            utxos,
-            last_indexes,
-            retained_txs,
-            updated_txs,
-            updated_utxos,
-        })
-    }
-
-    /// Sync states of [BatchDatabase] and Core wallet.
-    /// First we import all `scriptPubKey`s from database into core wallet
-    fn sync_with_core(&mut self, client: &Client, is_descriptor: bool) -> Result<&mut Self, Error> {
-        // this tells Core wallet where to sync from for imported scripts
-        let start_epoch = if self.params.force_start_time {
-            self.params.start_time
-        } else {
-            self.db
-                .get_sync_time()?
-                .map_or(self.params.start_time, |st| st.block_time.timestamp)
-        };
-
-        // sync scriptPubKeys from Database to Core wallet
-        let scripts_iter = self.ext_spks.iter().chain(&self.int_spks);
-        if is_descriptor {
-            import_descriptors(client, start_epoch, scripts_iter)?;
-        } else {
-            import_multi(client, start_epoch, scripts_iter)?;
-        }
-
-        // wait for Core wallet to rescan (TODO: maybe make this async)
-        await_wallet_scan(client, self.params.poll_rate_sec, self.prog)?;
-
-        // obtain iterator of pagenated `listtransactions` RPC calls
-        const LIST_TX_PAGE_SIZE: usize = 100; // item count per page
-        let tx_iter = list_transactions(client, LIST_TX_PAGE_SIZE)?.filter(|item| {
-            // filter out conflicting transactions - only accept transactions that are already
-            // confirmed, or exists in mempool
-            item.info.confirmations > 0 || client.get_mempool_entry(&item.info.txid).is_ok()
-        });
-
-        // iterate through chronological results of `listtransactions`
-        for tx_res in tx_iter {
-            let mut updated = false;
-
-            let db_tx = self.txs.entry(tx_res.info.txid).or_insert_with(|| {
-                updated = true;
-                TransactionDetails {
-                    txid: tx_res.info.txid,
-                    transaction: None,
-
-                    received: 0,
-                    sent: 0,
-                    fee: None,
-                    confirmation_time: None,
-                }
-            });
-
-            // update raw tx (if needed)
-            let raw_tx =
-                &*match &mut db_tx.transaction {
-                    Some(raw_tx) => raw_tx,
-                    db_tx_opt => {
-                        updated = true;
-                        db_tx_opt.insert(client.get_raw_transaction(
-                            &tx_res.info.txid,
-                            tx_res.info.blockhash.as_ref(),
-                        )?)
-                    }
-                };
-
-            // update fee (if needed)
-            if let (None, Some(new_fee)) = (db_tx.fee, tx_res.detail.fee) {
-                updated = true;
-                db_tx.fee = Some(new_fee.to_sat().unsigned_abs());
-            }
-
-            // update confirmation time (if needed)
-            let conf_time = BlockTime::new(tx_res.info.blockheight, tx_res.info.blocktime);
-            if db_tx.confirmation_time != conf_time {
-                updated = true;
-                db_tx.confirmation_time = conf_time;
-            }
-
-            // update received (if needed)
-            let received = Self::received_from_raw_tx(self.db, raw_tx)?;
-            if db_tx.received != received {
-                updated = true;
-                db_tx.received = received;
-            }
-
-            // check if tx has an immature coinbase output (add to updated UTXOs)
-            // this is required because `listunspent` does not include immature coinbase outputs
-            if tx_res.detail.category == GetTransactionResultDetailCategory::Immature {
-                let txout = raw_tx
-                    .output
-                    .get(tx_res.detail.vout as usize)
-                    .cloned()
-                    .ok_or_else(|| {
-                        Error::Generic(format!(
-                            "Core RPC returned detail with invalid vout '{}' for tx '{}'",
-                            tx_res.detail.vout, tx_res.info.txid,
-                        ))
-                    })?;
-
-                if let Some((keychain, index)) =
-                    self.db.get_path_from_script_pubkey(&txout.script_pubkey)?
-                {
-                    let utxo = LocalUtxo {
-                        outpoint: OutPoint::new(tx_res.info.txid, tx_res.detail.vout),
-                        txout,
-                        keychain,
-                        is_spent: false,
-                    };
-                    self.updated_utxos.insert(utxo);
-                    self.update_last_index(keychain, index);
-                }
-            }
-
-            // update tx deltas
-            self.retained_txs.insert(tx_res.info.txid);
-            if updated {
-                self.updated_txs.insert(tx_res.info.txid);
-            }
-        }
-
-        // obtain vector of `TransactionDetails::sent` changes
-        let sent_updates = self
-            .txs
-            .values()
-            // only bother to update txs that are retained
-            .filter(|db_tx| self.retained_txs.contains(&db_tx.txid))
-            // only bother to update txs where the raw tx is accessable
-            .filter_map(|db_tx| (db_tx.transaction.as_ref().map(|tx| (tx, db_tx.sent))))
-            // recalcuate sent value, only update txs in which sent value is changed
-            .filter_map(|(raw_tx, old_sent)| {
-                self.sent_from_raw_tx(raw_tx)
-                    .map(|sent| {
-                        if sent != old_sent {
-                            Some((raw_tx.txid(), sent))
-                        } else {
-                            None
-                        }
-                    })
-                    .transpose()
-            })
-            .collect::<Result<Vec<_>, _>>()?;
-
-        // record send updates
-        sent_updates.iter().for_each(|&(txid, sent)| {
-            // apply sent field changes
-            self.txs.entry(txid).and_modify(|db_tx| db_tx.sent = sent);
-            // mark tx as modified
-            self.updated_txs.insert(txid);
-        });
-
-        // obtain UTXOs from Core wallet
-        let core_utxos = client
-            .list_unspent(Some(0), None, None, Some(true), None)?
-            .into_iter()
-            .filter_map(|utxo_entry| {
-                let path_result = self
-                    .db
-                    .get_path_from_script_pubkey(&utxo_entry.script_pub_key)
-                    .transpose()?;
-
-                let utxo_result = match path_result {
-                    Ok((keychain, index)) => {
-                        self.update_last_index(keychain, index);
-                        Ok(Self::make_local_utxo(utxo_entry, keychain, false))
-                    }
-                    Err(err) => Err(err),
-                };
-
-                Some(utxo_result)
-            })
-            .collect::<Result<HashSet<_>, Error>>()?;
-
-        // mark "spent utxos" to be updated in database
-        let spent_utxos = self.utxos.difference(&core_utxos).cloned().map(|mut utxo| {
-            utxo.is_spent = true;
-            utxo
-        });
-
-        // mark new utxos to be added in database
-        let new_utxos = core_utxos.difference(&self.utxos).cloned();
-
-        // add to updated utxos
-        self.updated_utxos.extend(spent_utxos.chain(new_utxos));
-
-        Ok(self)
-    }
-
-    /// Calculates received amount from raw tx.
-    fn received_from_raw_tx(db: &D, raw_tx: &Transaction) -> Result<u64, Error> {
-        raw_tx.output.iter().try_fold(0_u64, |recv, txo| {
-            let v = if db.is_mine(&txo.script_pubkey)? {
-                txo.value
-            } else {
-                0
-            };
-            Ok(recv + v)
-        })
-    }
-
-    /// Calculates sent from raw tx.
-    fn sent_from_raw_tx(&self, raw_tx: &Transaction) -> Result<u64, Error> {
-        let get_output = |outpoint: &OutPoint| {
-            let raw_tx = self.txs.get(&outpoint.txid)?.transaction.as_ref()?;
-            raw_tx.output.get(outpoint.vout as usize)
-        };
-
-        raw_tx.input.iter().try_fold(0_u64, |sent, txin| {
-            let v = match get_output(&txin.previous_output) {
-                Some(prev_txo) => {
-                    if self.db.is_mine(&prev_txo.script_pubkey)? {
-                        prev_txo.value
-                    } else {
-                        0
-                    }
-                }
-                None => 0_u64,
-            };
-            Ok(sent + v)
-        })
-    }
-
-    // updates the db state's last_index for the given keychain (if larger than current last_index)
-    fn update_last_index(&mut self, keychain: KeychainKind, index: u32) {
-        self.last_indexes
-            .entry(keychain)
-            .and_modify(|last| {
-                if *last < index {
-                    *last = index;
-                }
-            })
-            .or_insert_with(|| index);
-    }
-
-    fn make_local_utxo(
-        entry: ListUnspentResultEntry,
-        keychain: KeychainKind,
-        is_spent: bool,
-    ) -> LocalUtxo {
-        LocalUtxo {
-            outpoint: OutPoint::new(entry.txid, entry.vout),
-            txout: TxOut {
-                value: entry.amount.to_sat(),
-                script_pubkey: entry.script_pub_key,
-            },
-            keychain,
-            is_spent,
-        }
-    }
-
-    /// Prepare db batch operations.
-    fn as_db_batch(&self) -> Result<D::Batch, Error> {
-        let mut batch = self.db.begin_batch();
-        let mut del_txs = 0_u32;
-
-        // delete stale (not retained) txs from db
-        self.txs
-            .keys()
-            .filter(|&txid| !self.retained_txs.contains(txid))
-            .try_for_each(|txid| -> Result<(), Error> {
-                batch.del_tx(txid, false)?;
-                del_txs += 1;
-                Ok(())
-            })?;
-
-        // update txs
-        self.updated_txs
-            .iter()
-            .inspect(|&txid| debug!("updating tx: {}", txid))
-            .try_for_each(|txid| batch.set_tx(self.txs.get(txid).unwrap()))?;
-
-        // update utxos
-        self.updated_utxos
-            .iter()
-            .inspect(|&utxo| debug!("updating utxo: {}", utxo.outpoint))
-            .try_for_each(|utxo| batch.set_utxo(utxo))?;
-
-        // update last indexes
-        self.last_indexes
-            .iter()
-            .try_for_each(|(&keychain, &index)| batch.set_last_index(keychain, index))?;
-
-        info!(
-            "db batch updates: del_txs={}, update_txs={}, update_utxos={}",
-            del_txs,
-            self.updated_txs.len(),
-            self.updated_utxos.len()
-        );
-
-        Ok(batch)
-    }
-}
-
-fn import_descriptors<'a, S>(
-    client: &Client,
-    start_epoch: u64,
-    scripts_iter: S,
-) -> Result<(), Error>
-where
-    S: Iterator<Item = &'a Script>,
-{
-    let requests = Value::Array(
-        scripts_iter
-            .map(|script| {
-                let desc = descriptor_from_script_pubkey(script);
-                json!({ "timestamp": start_epoch, "desc": desc })
-            })
-            .collect(),
-    );
-    for v in client.call::<Vec<Value>>("importdescriptors", &[requests])? {
-        match v["success"].as_bool() {
-            Some(true) => continue,
-            Some(false) => {
-                return Err(Error::Generic(
-                    v["error"]["message"]
-                        .as_str()
-                        .map_or("unknown error".into(), ToString::to_string),
-                ))
-            }
-            _ => return Err(Error::Generic("Unexpected response form Core".to_string())),
-        }
-    }
-    Ok(())
-}
-
-fn import_multi<'a, S>(client: &Client, start_epoch: u64, scripts_iter: S) -> Result<(), Error>
-where
-    S: Iterator<Item = &'a Script>,
-{
-    let requests = scripts_iter
-        .map(|script| ImportMultiRequest {
-            timestamp: ImportMultiRescanSince::Timestamp(start_epoch),
-            script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(script)),
-            watchonly: Some(true),
-            ..Default::default()
-        })
-        .collect::<Vec<_>>();
-    let options = ImportMultiOptions { rescan: Some(true) };
-    for v in client.import_multi(&requests, Some(&options))? {
-        if let Some(err) = v.error {
-            return Err(Error::Generic(format!(
-                "{} (code: {})",
-                err.message, err.code
-            )));
-        }
-    }
-    Ok(())
-}
-
-/// Calls the `listtransactions` RPC method in `page_size`s and returns iterator of the tx results
-/// in chronological order.
-///
-/// `page_size` cannot be less than 1 and cannot be greater than 1000.
-fn list_transactions(
-    client: &Client,
-    page_size: usize,
-) -> Result<impl Iterator<Item = ListTransactionResult>, Error> {
-    if !(1..=1000).contains(&page_size) {
-        return Err(Error::Generic(format!(
-            "Core RPC method `listtransactions` must have `page_size` in range [1 to 1000]: got {}",
-            page_size
-        )));
-    }
-
-    // `.take_while` helper to obtain the first error (TODO: remove when we can use `.map_while`)
-    let mut got_err = false;
-
-    // obtain results in batches (of `page_size`)
-    let nested_list = (0_usize..)
-        .map(|page_index| {
-            client.list_transactions(
-                None,
-                Some(page_size),
-                Some(page_size * page_index),
-                Some(true),
-            )
-        })
-        // take until returned rpc call is empty or until error
-        // TODO: replace with the following when MSRV is 1.57.0:
-        // `.map_while(|res| res.map(|l| if l.is_empty() { None } else { Some(l) }).transpose())`
-        .take_while(|res| {
-            if got_err || matches!(res, Ok(list) if list.is_empty()) {
-                // break if last iteration was an error, or if the current result is empty
-                false
-            } else {
-                // record whether result is error or not
-                got_err = res.is_err();
-                // continue on non-empty result or first error
-                true
-            }
-        })
-        .collect::<Result<Vec<_>, _>>()
-        .map_err(Error::Rpc)?;
-
-    // reverse here to have txs in chronological order
-    Ok(nested_list.into_iter().rev().flatten())
-}
-
-fn await_wallet_scan(client: &Client, rate_sec: u64, progress: &dyn Progress) -> Result<(), Error> {
-    #[derive(Deserialize)]
-    struct CallResult {
-        scanning: ScanningDetails,
-    }
-
-    let dur = Duration::from_secs(rate_sec);
-    loop {
-        match client.call::<CallResult>("getwalletinfo", &[])?.scanning {
-            ScanningDetails::Scanning {
-                duration,
-                progress: pc,
-            } => {
-                debug!("scanning: duration={}, progress={}", duration, pc);
-                progress.update(pc, Some(format!("elapsed for {} seconds", duration)))?;
-                thread::sleep(dur);
-            }
-            ScanningDetails::NotScanning(_) => {
-                progress.update(1.0, None)?;
-                info!("scanning: done!");
-                return Ok(());
-            }
-        };
-    }
-}
-
-/// Returns whether a wallet is legacy or descriptors by calling `getwalletinfo`.
-///
-/// This API is mapped by bitcoincore_rpc, but it doesn't have the fields we need (either
-/// "descriptors" or "format") so we have to call the RPC manually
-fn is_wallet_descriptor(client: &Client) -> Result<bool, Error> {
-    #[derive(Deserialize)]
-    struct CallResult {
-        descriptors: Option<bool>,
-    }
-
-    let result: CallResult = client.call("getwalletinfo", &[])?;
-    Ok(result.descriptors.unwrap_or(false))
-}
-
-fn descriptor_from_script_pubkey(script: &Script) -> String {
-    let desc = format!("raw({})", script.to_hex());
-    format!("{}#{}", desc, calc_checksum(&desc).unwrap())
-}
-
-/// Factory of [`RpcBlockchain`] instances, implements [`BlockchainFactory`]
-///
-/// Internally caches the node url and authentication params and allows getting many different [`RpcBlockchain`]
-/// objects for different wallet names and with different rescan heights.
-///
-/// ## Example
-///
-/// ```no_run
-/// # use bdk::bitcoin::Network;
-/// # use bdk::blockchain::BlockchainFactory;
-/// # use bdk::blockchain::rpc::{Auth, RpcBlockchainFactory};
-/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
-/// let factory = RpcBlockchainFactory {
-///     url: "http://127.0.0.1:18332".to_string(),
-///     auth: Auth::Cookie {
-///         file: "/home/user/.bitcoin/.cookie".into(),
-///     },
-///     network: Network::Testnet,
-///     wallet_name_prefix: Some("prefix-".to_string()),
-///     default_skip_blocks: 100_000,
-///     sync_params: None,
-/// };
-/// let main_wallet_blockchain = factory.build("main_wallet", Some(200_000))?;
-/// # Ok(())
-/// # }
-/// ```
-#[derive(Debug, Clone)]
-pub struct RpcBlockchainFactory {
-    /// The bitcoin node url
-    pub url: String,
-    /// The bitcoin node authentication mechanism
-    pub auth: Auth,
-    /// The network we are using (it will be checked the bitcoin node network matches this)
-    pub network: Network,
-    /// The optional prefix used to build the full wallet name for blockchains
-    pub wallet_name_prefix: Option<String>,
-    /// Default number of blocks to skip which will be inherited by blockchain unless overridden
-    pub default_skip_blocks: u32,
-    /// Sync parameters
-    pub sync_params: Option<RpcSyncParams>,
-}
-
-impl BlockchainFactory for RpcBlockchainFactory {
-    type Inner = RpcBlockchain;
-
-    fn build(
-        &self,
-        checksum: &str,
-        _override_skip_blocks: Option<u32>,
-    ) -> Result<Self::Inner, Error> {
-        RpcBlockchain::from_config(&RpcConfig {
-            url: self.url.clone(),
-            auth: self.auth.clone(),
-            network: self.network,
-            wallet_name: format!(
-                "{}{}",
-                self.wallet_name_prefix.as_ref().unwrap_or(&String::new()),
-                checksum
-            ),
-            sync_params: self.sync_params.clone(),
-        })
-    }
-}
-
-#[cfg(test)]
-#[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))]
-mod test {
-    use super::*;
-    use crate::{
-        descriptor::into_wallet_descriptor_checked, testutils::blockchain_tests::TestClient,
-        wallet::utils::SecpCtx,
-    };
-
-    use bitcoin::{Address, Network};
-    use bitcoincore_rpc::RpcApi;
-    use log::LevelFilter;
-
-    crate::bdk_blockchain_tests! {
-        fn test_instance(test_client: &TestClient) -> RpcBlockchain {
-            let config = RpcConfig {
-                url: test_client.bitcoind.rpc_url(),
-                auth: Auth::Cookie { file: test_client.bitcoind.params.cookie_file.clone() },
-                network: Network::Regtest,
-                wallet_name: format!("client-wallet-test-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() ),
-                sync_params: None,
-            };
-            RpcBlockchain::from_config(&config).unwrap()
-        }
-    }
-
-    fn get_factory() -> (TestClient, RpcBlockchainFactory) {
-        let test_client = TestClient::default();
-
-        let factory = RpcBlockchainFactory {
-            url: test_client.bitcoind.rpc_url(),
-            auth: Auth::Cookie {
-                file: test_client.bitcoind.params.cookie_file.clone(),
-            },
-            network: Network::Regtest,
-            wallet_name_prefix: Some("prefix-".into()),
-            default_skip_blocks: 0,
-            sync_params: None,
-        };
-
-        (test_client, factory)
-    }
-
-    #[test]
-    fn test_rpc_blockchain_factory() {
-        let (_test_client, factory) = get_factory();
-
-        let a = factory.build("aaaaaa", None).unwrap();
-        assert_eq!(
-            a.client
-                .get_wallet_info()
-                .expect("Node connection isn't working")
-                .wallet_name,
-            "prefix-aaaaaa"
-        );
-
-        let b = factory.build("bbbbbb", Some(100)).unwrap();
-        assert_eq!(
-            b.client
-                .get_wallet_info()
-                .expect("Node connection isn't working")
-                .wallet_name,
-            "prefix-bbbbbb"
-        );
-    }
-
-    /// This test ensures that [list_transactions] always iterates through transactions in
-    /// chronological order, independent of the `page_size`.
-    #[test]
-    fn test_list_transactions() {
-        let _ = env_logger::builder()
-            .filter_level(LevelFilter::Info)
-            .default_format()
-            .try_init();
-
-        const DESC: &'static str = "wpkh(tpubD9zMNV59kgbWgKK55SHJugmKKSt6wQXczxpucGYqNKwGmJp1x7Ar2nrLUXYHDdCctXmyDoSCn2JVMzMUDfib3FaDhwxCEMUELoq19xLSx66/*)";
-        const AMOUNT_PER_TX: u64 = 10_000;
-        const TX_COUNT: u32 = 50;
-
-        let secp = SecpCtx::default();
-        let network = Network::Regtest;
-        let (desc, ..) = into_wallet_descriptor_checked(DESC, &secp, network).unwrap();
-
-        let (mut test_client, factory) = get_factory();
-        let bc = factory.build("itertest", None).unwrap();
-
-        // generate scripts (1 tx per script)
-        let scripts = (0..TX_COUNT)
-            .map(|index| desc.at_derivation_index(index).script_pubkey())
-            .collect::<Vec<_>>();
-
-        // import scripts and wait
-        if bc.is_descriptors {
-            import_descriptors(&bc.client, 0, scripts.iter()).unwrap();
-        } else {
-            import_multi(&bc.client, 0, scripts.iter()).unwrap();
-        }
-        await_wallet_scan(&bc.client, 2, &NoopProgress).unwrap();
-
-        // create and broadcast txs
-        let expected_txids = scripts
-            .iter()
-            .map(|script| {
-                let addr = Address::from_script(script, network).unwrap();
-                let txid =
-                    test_client.receive(testutils! { @tx ( (@addr addr) => AMOUNT_PER_TX ) });
-                test_client.generate(1, None);
-                txid
-            })
-            .collect::<Vec<_>>();
-
-        // iterate through different page sizes - should always return txs in chronological order
-        [1000, 1, 2, 6, 25, 49, 50].iter().for_each(|page_size| {
-            println!("trying with page_size: {}", page_size);
-
-            let txids = list_transactions(&bc.client, *page_size)
-                .unwrap()
-                .map(|res| res.info.txid)
-                .collect::<Vec<_>>();
-
-            assert_eq!(txids.len(), expected_txids.len());
-            assert_eq!(txids, expected_txids);
-        });
-    }
-}
diff --git a/src/blockchain/script_sync.rs b/src/blockchain/script_sync.rs
deleted file mode 100644 (file)
index 2c4b26c..0000000
+++ /dev/null
@@ -1,467 +0,0 @@
-/*!
-This models a how a sync happens where you have a server that you send your script pubkeys to and it
-returns associated transactions i.e. electrum.
-*/
-#![allow(dead_code)]
-use crate::{
-    database::{BatchDatabase, BatchOperations, DatabaseUtils},
-    error::MissingCachedScripts,
-    wallet::time::Instant,
-    BlockTime, Error, KeychainKind, LocalUtxo, TransactionDetails,
-};
-use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
-use log::*;
-use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
-
-/// A request for on-chain information
-pub enum Request<'a, D: BatchDatabase> {
-    /// A request for transactions related to script pubkeys.
-    Script(ScriptReq<'a, D>),
-    /// A request for confirmation times for some transactions.
-    Conftime(ConftimeReq<'a, D>),
-    /// A request for full transaction details of some transactions.
-    Tx(TxReq<'a, D>),
-    /// Requests are finished here's a batch database update to reflect data gathered.
-    Finish(D::Batch),
-}
-
-/// starts a sync
-pub fn start<D: BatchDatabase>(db: &D, stop_gap: usize) -> Result<Request<'_, D>, Error> {
-    use rand::seq::SliceRandom;
-    let mut keychains = vec![KeychainKind::Internal, KeychainKind::External];
-    // shuffling improve privacy, the server doesn't know my first request is from my internal or external addresses
-    keychains.shuffle(&mut rand::thread_rng());
-    let keychain = keychains.pop().unwrap();
-    let scripts_needed = db
-        .iter_script_pubkeys(Some(keychain))?
-        .into_iter()
-        .collect::<VecDeque<_>>();
-    let state = State::new(db);
-
-    Ok(Request::Script(ScriptReq {
-        state,
-        initial_scripts_needed: scripts_needed.len(),
-        scripts_needed,
-        script_index: 0,
-        stop_gap,
-        keychain,
-        next_keychains: keychains,
-    }))
-}
-
-pub struct ScriptReq<'a, D: BatchDatabase> {
-    state: State<'a, D>,
-    script_index: usize,
-    initial_scripts_needed: usize, // if this is 1, we assume the descriptor is not derivable
-    scripts_needed: VecDeque<Script>,
-    stop_gap: usize,
-    keychain: KeychainKind,
-    next_keychains: Vec<KeychainKind>,
-}
-
-/// The sync starts by returning script pubkeys we are interested in.
-impl<'a, D: BatchDatabase> ScriptReq<'a, D> {
-    pub fn request(&self) -> impl Iterator<Item = &Script> + Clone {
-        self.scripts_needed.iter()
-    }
-
-    pub fn satisfy(
-        mut self,
-        // we want to know the txids assoiciated with the script and their height
-        txids: Vec<Vec<(Txid, Option<u32>)>>,
-    ) -> Result<Request<'a, D>, Error> {
-        for (txid_list, script) in txids.iter().zip(self.scripts_needed.iter()) {
-            debug!(
-                "found {} transactions for script pubkey {}",
-                txid_list.len(),
-                script
-            );
-            if !txid_list.is_empty() {
-                // the address is active
-                self.state
-                    .last_active_index
-                    .insert(self.keychain, self.script_index);
-            }
-
-            for (txid, height) in txid_list {
-                // have we seen this txid already?
-                match self.state.db.get_tx(txid, true)? {
-                    Some(mut details) => {
-                        let old_height = details.confirmation_time.as_ref().map(|x| x.height);
-                        match (old_height, height) {
-                            (None, Some(_)) => {
-                                // It looks like the tx has confirmed since we last saw it -- we
-                                // need to know the confirmation time.
-                                self.state.tx_missing_conftime.insert(*txid, details);
-                            }
-                            (Some(old_height), Some(new_height)) if old_height != *new_height => {
-                                // The height of the tx has changed !? -- It's a reorg get the new confirmation time.
-                                self.state.tx_missing_conftime.insert(*txid, details);
-                            }
-                            (Some(_), None) => {
-                                // A re-org where the tx is not in the chain anymore.
-                                details.confirmation_time = None;
-                                self.state.finished_txs.push(details);
-                            }
-                            _ => self.state.finished_txs.push(details),
-                        }
-                    }
-                    None => {
-                        // we've never seen it let's get the whole thing
-                        self.state.tx_needed.insert(*txid);
-                    }
-                };
-            }
-
-            self.script_index += 1;
-        }
-
-        self.scripts_needed.drain(..txids.len());
-
-        // last active index: 0 => No last active
-        let last = self
-            .state
-            .last_active_index
-            .get(&self.keychain)
-            .map(|&l| l + 1)
-            .unwrap_or(0);
-        // remaining scripts left to check
-        let remaining = self.scripts_needed.len();
-        // difference between current index and last active index
-        let current_gap = self.script_index - last;
-
-        // this is a hack to check whether the scripts are coming from a derivable descriptor
-        // we assume for non-derivable descriptors, the initial script count is always 1
-        let is_derivable = self.initial_scripts_needed > 1;
-
-        debug!(
-            "sync: last={}, remaining={}, diff={}, stop_gap={}",
-            last, remaining, current_gap, self.stop_gap
-        );
-
-        if is_derivable {
-            if remaining > 0 {
-                // we still have scriptPubKeys to do requests for
-                return Ok(Request::Script(self));
-            }
-
-            if last > 0 && current_gap < self.stop_gap {
-                // current gap is not large enough to stop, but we are unable to keep checking since
-                // we have exhausted cached scriptPubKeys, so return error
-                let err = MissingCachedScripts {
-                    last_count: self.script_index,
-                    missing_count: self.stop_gap - current_gap,
-                };
-                return Err(Error::MissingCachedScripts(err));
-            }
-
-            // we have exhausted cached scriptPubKeys and found no txs, continue
-        }
-
-        debug!(
-            "finished scanning for txs of keychain {:?} at index {:?}",
-            self.keychain, last
-        );
-
-        if let Some(keychain) = self.next_keychains.pop() {
-            // we still have another keychain to request txs with
-            let scripts_needed = self
-                .state
-                .db
-                .iter_script_pubkeys(Some(keychain))?
-                .into_iter()
-                .collect::<VecDeque<_>>();
-
-            self.keychain = keychain;
-            self.script_index = 0;
-            self.initial_scripts_needed = scripts_needed.len();
-            self.scripts_needed = scripts_needed;
-            return Ok(Request::Script(self));
-        }
-
-        // We have finished requesting txids, let's get the actual txs.
-        Ok(Request::Tx(TxReq { state: self.state }))
-    }
-}
-
-/// Then we get full transactions
-pub struct TxReq<'a, D> {
-    state: State<'a, D>,
-}
-
-impl<'a, D: BatchDatabase> TxReq<'a, D> {
-    pub fn request(&self) -> impl Iterator<Item = &Txid> + Clone {
-        self.state.tx_needed.iter()
-    }
-
-    pub fn satisfy(
-        mut self,
-        tx_details: Vec<(Vec<Option<TxOut>>, Transaction)>,
-    ) -> Result<Request<'a, D>, Error> {
-        let tx_details: Vec<TransactionDetails> = tx_details
-            .into_iter()
-            .zip(self.state.tx_needed.iter())
-            .map(|((vout, tx), txid)| {
-                debug!("found tx_details for {}", txid);
-                assert_eq!(tx.txid(), *txid);
-                let mut sent: u64 = 0;
-                let mut received: u64 = 0;
-                let mut inputs_sum: u64 = 0;
-                let mut outputs_sum: u64 = 0;
-
-                for (txout, (_input_index, input)) in
-                    vout.into_iter().zip(tx.input.iter().enumerate())
-                {
-                    let txout = match txout {
-                        Some(txout) => txout,
-                        None => {
-                            // skip coinbase inputs
-                            debug_assert!(
-                                input.previous_output.is_null(),
-                                "prevout should only be missing for coinbase"
-                            );
-                            continue;
-                        }
-                    };
-                    // Verify this input if requested via feature flag
-                    #[cfg(feature = "verify")]
-                    {
-                        use crate::wallet::verify::VerifyError;
-                        let serialized_tx = bitcoin::consensus::serialize(&tx);
-                        bitcoinconsensus::verify(
-                            txout.script_pubkey.to_bytes().as_ref(),
-                            txout.value,
-                            &serialized_tx,
-                            _input_index,
-                        )
-                        .map_err(VerifyError::from)?;
-                    }
-                    inputs_sum += txout.value;
-                    if self.state.db.is_mine(&txout.script_pubkey)? {
-                        sent += txout.value;
-                    }
-                }
-
-                for out in &tx.output {
-                    outputs_sum += out.value;
-                    if self.state.db.is_mine(&out.script_pubkey)? {
-                        received += out.value;
-                    }
-                }
-                // we need to saturating sub since we want coinbase txs to map to 0 fee and
-                // this subtraction will be negative for coinbase txs.
-                let fee = inputs_sum.saturating_sub(outputs_sum);
-                Result::<_, Error>::Ok(TransactionDetails {
-                    txid: *txid,
-                    transaction: Some(tx),
-                    received,
-                    sent,
-                    // we're going to fill this in later
-                    confirmation_time: None,
-                    fee: Some(fee),
-                })
-            })
-            .collect::<Result<Vec<_>, _>>()?;
-
-        for tx_detail in tx_details {
-            self.state.tx_needed.remove(&tx_detail.txid);
-            self.state
-                .tx_missing_conftime
-                .insert(tx_detail.txid, tx_detail);
-        }
-
-        if !self.state.tx_needed.is_empty() {
-            Ok(Request::Tx(self))
-        } else {
-            Ok(Request::Conftime(ConftimeReq { state: self.state }))
-        }
-    }
-}
-
-/// Final step is to get confirmation times
-pub struct ConftimeReq<'a, D> {
-    state: State<'a, D>,
-}
-
-impl<'a, D: BatchDatabase> ConftimeReq<'a, D> {
-    pub fn request(&self) -> impl Iterator<Item = &Txid> + Clone {
-        self.state.tx_missing_conftime.keys()
-    }
-
-    pub fn satisfy(
-        mut self,
-        confirmation_times: Vec<Option<BlockTime>>,
-    ) -> Result<Request<'a, D>, Error> {
-        let conftime_needed = self
-            .request()
-            .cloned()
-            .take(confirmation_times.len())
-            .collect::<Vec<_>>();
-        for (confirmation_time, txid) in confirmation_times.into_iter().zip(conftime_needed.iter())
-        {
-            debug!("confirmation time for {} was {:?}", txid, confirmation_time);
-            if let Some(mut tx_details) = self.state.tx_missing_conftime.remove(txid) {
-                tx_details.confirmation_time = confirmation_time;
-                self.state.finished_txs.push(tx_details);
-            }
-        }
-
-        if self.state.tx_missing_conftime.is_empty() {
-            Ok(Request::Finish(self.state.into_db_update()?))
-        } else {
-            Ok(Request::Conftime(self))
-        }
-    }
-}
-
-struct State<'a, D> {
-    db: &'a D,
-    last_active_index: HashMap<KeychainKind, usize>,
-    /// Transactions where we need to get the full details
-    tx_needed: BTreeSet<Txid>,
-    /// Transacitions that we know everything about
-    finished_txs: Vec<TransactionDetails>,
-    /// Transactions that discovered conftimes should be inserted into
-    tx_missing_conftime: BTreeMap<Txid, TransactionDetails>,
-    /// The start of the sync
-    start_time: Instant,
-    /// Missing number of scripts to cache per keychain
-    missing_script_counts: HashMap<KeychainKind, usize>,
-}
-
-impl<'a, D: BatchDatabase> State<'a, D> {
-    fn new(db: &'a D) -> Self {
-        State {
-            db,
-            last_active_index: HashMap::default(),
-            finished_txs: vec![],
-            tx_needed: BTreeSet::default(),
-            tx_missing_conftime: BTreeMap::default(),
-            start_time: Instant::new(),
-            missing_script_counts: HashMap::default(),
-        }
-    }
-    fn into_db_update(self) -> Result<D::Batch, Error> {
-        debug_assert!(self.tx_needed.is_empty() && self.tx_missing_conftime.is_empty());
-        let existing_txs = self.db.iter_txs(false)?;
-        let existing_txids: HashSet<Txid> = existing_txs.iter().map(|tx| tx.txid).collect();
-        let finished_txs = make_txs_consistent(&self.finished_txs);
-        let observed_txids: HashSet<Txid> = finished_txs.iter().map(|tx| tx.txid).collect();
-        let txids_to_delete = existing_txids.difference(&observed_txids);
-
-        // Ensure `last_active_index` does not decrement database's current state.
-        let index_updates = self
-            .last_active_index
-            .iter()
-            .map(|(keychain, sync_index)| {
-                let sync_index = *sync_index as u32;
-                let index_res = match self.db.get_last_index(*keychain) {
-                    Ok(Some(db_index)) => Ok(std::cmp::max(db_index, sync_index)),
-                    Ok(None) => Ok(sync_index),
-                    Err(err) => Err(err),
-                };
-                index_res.map(|index| (*keychain, index))
-            })
-            .collect::<Result<Vec<(KeychainKind, u32)>, _>>()?;
-
-        let mut batch = self.db.begin_batch();
-
-        // Delete old txs that no longer exist
-        for txid in txids_to_delete {
-            if let Some(raw_tx) = self.db.get_raw_tx(txid)? {
-                for i in 0..raw_tx.output.len() {
-                    // Also delete any utxos from the txs that no longer exist.
-                    let _ = batch.del_utxo(&OutPoint {
-                        txid: *txid,
-                        vout: i as u32,
-                    })?;
-                }
-            } else {
-                unreachable!("we should always have the raw tx");
-            }
-            batch.del_tx(txid, true)?;
-        }
-
-        let mut spent_utxos = HashSet::new();
-
-        // track all the spent utxos
-        for finished_tx in &finished_txs {
-            let tx = finished_tx
-                .transaction
-                .as_ref()
-                .expect("transaction will always be present here");
-            for input in &tx.input {
-                spent_utxos.insert(&input.previous_output);
-            }
-        }
-
-        // set every utxo we observed, unless it's already spent
-        // we don't do this in the loop above as we want to know all the spent outputs before
-        // adding the non-spent to the batch in case there are new tranasactions
-        // that spend form each other.
-        for finished_tx in &finished_txs {
-            let tx = finished_tx
-                .transaction
-                .as_ref()
-                .expect("transaction will always be present here");
-            for (i, output) in tx.output.iter().enumerate() {
-                if let Some((keychain, _)) =
-                    self.db.get_path_from_script_pubkey(&output.script_pubkey)?
-                {
-                    // add utxos we own from the new transactions we've seen.
-                    let outpoint = OutPoint {
-                        txid: finished_tx.txid,
-                        vout: i as u32,
-                    };
-
-                    batch.set_utxo(&LocalUtxo {
-                        outpoint,
-                        txout: output.clone(),
-                        keychain,
-                        // Is this UTXO in the spent_utxos set?
-                        is_spent: spent_utxos.get(&outpoint).is_some(),
-                    })?;
-                }
-            }
-
-            batch.set_tx(finished_tx)?;
-        }
-
-        // apply index updates
-        for (keychain, new_index) in index_updates {
-            debug!("updating index ({}, {})", keychain.as_byte(), new_index);
-            batch.set_last_index(keychain, new_index)?;
-        }
-
-        info!(
-            "finished setup, elapsed {:?}ms",
-            self.start_time.elapsed().as_millis()
-        );
-        Ok(batch)
-    }
-}
-
-/// Remove conflicting transactions -- tie breaking them by fee.
-fn make_txs_consistent(txs: &[TransactionDetails]) -> Vec<&TransactionDetails> {
-    let mut utxo_index: HashMap<OutPoint, &TransactionDetails> = HashMap::default();
-    for tx in txs {
-        for input in &tx.transaction.as_ref().unwrap().input {
-            utxo_index
-                .entry(input.previous_output)
-                .and_modify(|existing| match (tx.fee, existing.fee) {
-                    (Some(fee), Some(existing_fee)) if fee > existing_fee => *existing = tx,
-                    (Some(_), None) => *existing = tx,
-                    _ => { /* leave it the same */ }
-                })
-                .or_insert(tx);
-        }
-    }
-
-    utxo_index
-        .into_iter()
-        .map(|(_, tx)| (tx.txid, tx))
-        .collect::<HashMap<_, _>>()
-        .into_iter()
-        .map(|(_, tx)| tx)
-        .collect()
-}
diff --git a/src/database/any.rs b/src/database/any.rs
deleted file mode 100644 (file)
index bbd9d41..0000000
+++ /dev/null
@@ -1,427 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Runtime-checked database types
-//!
-//! This module provides the implementation of [`AnyDatabase`] which allows switching the
-//! inner [`Database`] type at runtime.
-//!
-//! ## Example
-//!
-//! In this example, `wallet_memory` and `wallet_sled` have the same type of `Wallet<(), AnyDatabase>`.
-//!
-//! ```no_run
-//! # use bitcoin::Network;
-//! # use bdk::database::{AnyDatabase, MemoryDatabase};
-//! # use bdk::{Wallet};
-//! let memory = MemoryDatabase::default();
-//! let wallet_memory = Wallet::new("...", None, Network::Testnet, memory)?;
-//!
-//! # #[cfg(feature = "key-value-db")]
-//! # {
-//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
-//! let wallet_sled = Wallet::new("...", None, Network::Testnet, sled)?;
-//! # }
-//! # Ok::<(), bdk::Error>(())
-//! ```
-//!
-//! When paired with the use of [`ConfigurableDatabase`], it allows creating wallets with any
-//! database supported using a single line of code:
-//!
-//! ```no_run
-//! # use bitcoin::Network;
-//! # use bdk::database::*;
-//! # use bdk::{Wallet};
-//! let config = serde_json::from_str("...")?;
-//! let database = AnyDatabase::from_config(&config)?;
-//! let wallet = Wallet::new("...", None, Network::Testnet, database)?;
-//! # Ok::<(), bdk::Error>(())
-//! ```
-
-use super::*;
-
-macro_rules! impl_from {
-    ( $from:ty, $to:ty, $variant:ident, $( $cfg:tt )* ) => {
-        $( $cfg )*
-        impl From<$from> for $to {
-            fn from(inner: $from) -> Self {
-                <$to>::$variant(inner)
-            }
-        }
-    };
-}
-
-macro_rules! impl_inner_method {
-    ( $enum_name:ident, $self:expr, $name:ident $(, $args:expr)* ) => {
-        #[allow(deprecated)]
-        match $self {
-            $enum_name::Memory(inner) => inner.$name( $($args, )* ),
-            #[cfg(feature = "key-value-db")]
-            $enum_name::Sled(inner) => inner.$name( $($args, )* ),
-            #[cfg(feature = "sqlite")]
-            $enum_name::Sqlite(inner) => inner.$name( $($args, )* ),
-        }
-    }
-}
-
-/// Type that can contain any of the [`Database`] types defined by the library
-///
-/// It allows switching database type at runtime.
-///
-/// See [this module](crate::database::any)'s documentation for a usage example.
-#[derive(Debug)]
-pub enum AnyDatabase {
-    /// In-memory ephemeral database
-    Memory(memory::MemoryDatabase),
-    #[cfg(feature = "key-value-db")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
-    /// Simple key-value embedded database based on [`sled`]
-    Sled(sled::Tree),
-    #[cfg(feature = "sqlite")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
-    /// Sqlite embedded database using [`rusqlite`]
-    Sqlite(sqlite::SqliteDatabase),
-}
-
-impl_from!(memory::MemoryDatabase, AnyDatabase, Memory,);
-impl_from!(sled::Tree, AnyDatabase, Sled, #[cfg(feature = "key-value-db")]);
-impl_from!(sqlite::SqliteDatabase, AnyDatabase, Sqlite, #[cfg(feature = "sqlite")]);
-
-/// Type that contains any of the [`BatchDatabase::Batch`] types defined by the library
-pub enum AnyBatch {
-    /// In-memory ephemeral database
-    Memory(<memory::MemoryDatabase as BatchDatabase>::Batch),
-    #[cfg(feature = "key-value-db")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
-    /// Simple key-value embedded database based on [`sled`]
-    Sled(<sled::Tree as BatchDatabase>::Batch),
-    #[cfg(feature = "sqlite")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
-    /// Sqlite embedded database using [`rusqlite`]
-    Sqlite(<sqlite::SqliteDatabase as BatchDatabase>::Batch),
-}
-
-impl_from!(
-    <memory::MemoryDatabase as BatchDatabase>::Batch,
-    AnyBatch,
-    Memory,
-);
-impl_from!(<sled::Tree as BatchDatabase>::Batch, AnyBatch, Sled, #[cfg(feature = "key-value-db")]);
-impl_from!(<sqlite::SqliteDatabase as BatchDatabase>::Batch, AnyBatch, Sqlite, #[cfg(feature = "sqlite")]);
-
-impl BatchOperations for AnyDatabase {
-    fn set_script_pubkey(
-        &mut self,
-        script: &Script,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<(), Error> {
-        impl_inner_method!(
-            AnyDatabase,
-            self,
-            set_script_pubkey,
-            script,
-            keychain,
-            child
-        )
-    }
-    fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
-        impl_inner_method!(AnyDatabase, self, set_utxo, utxo)
-    }
-    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
-        impl_inner_method!(AnyDatabase, self, set_raw_tx, transaction)
-    }
-    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
-        impl_inner_method!(AnyDatabase, self, set_tx, transaction)
-    }
-    fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
-        impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value)
-    }
-    fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> {
-        impl_inner_method!(AnyDatabase, self, set_sync_time, sync_time)
-    }
-
-    fn del_script_pubkey_from_path(
-        &mut self,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<Option<Script>, Error> {
-        impl_inner_method!(
-            AnyDatabase,
-            self,
-            del_script_pubkey_from_path,
-            keychain,
-            child
-        )
-    }
-    fn del_path_from_script_pubkey(
-        &mut self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        impl_inner_method!(AnyDatabase, self, del_path_from_script_pubkey, script)
-    }
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        impl_inner_method!(AnyDatabase, self, del_utxo, outpoint)
-    }
-    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        impl_inner_method!(AnyDatabase, self, del_raw_tx, txid)
-    }
-    fn del_tx(
-        &mut self,
-        txid: &Txid,
-        include_raw: bool,
-    ) -> Result<Option<TransactionDetails>, Error> {
-        impl_inner_method!(AnyDatabase, self, del_tx, txid, include_raw)
-    }
-    fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        impl_inner_method!(AnyDatabase, self, del_last_index, keychain)
-    }
-    fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
-        impl_inner_method!(AnyDatabase, self, del_sync_time)
-    }
-}
-
-impl Database for AnyDatabase {
-    fn check_descriptor_checksum<B: AsRef<[u8]>>(
-        &mut self,
-        keychain: KeychainKind,
-        bytes: B,
-    ) -> Result<(), Error> {
-        impl_inner_method!(
-            AnyDatabase,
-            self,
-            check_descriptor_checksum,
-            keychain,
-            bytes
-        )
-    }
-
-    fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
-        impl_inner_method!(AnyDatabase, self, iter_script_pubkeys, keychain)
-    }
-    fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
-        impl_inner_method!(AnyDatabase, self, iter_utxos)
-    }
-    fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> {
-        impl_inner_method!(AnyDatabase, self, iter_raw_txs)
-    }
-    fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
-        impl_inner_method!(AnyDatabase, self, iter_txs, include_raw)
-    }
-
-    fn get_script_pubkey_from_path(
-        &self,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<Option<Script>, Error> {
-        impl_inner_method!(
-            AnyDatabase,
-            self,
-            get_script_pubkey_from_path,
-            keychain,
-            child
-        )
-    }
-    fn get_path_from_script_pubkey(
-        &self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        impl_inner_method!(AnyDatabase, self, get_path_from_script_pubkey, script)
-    }
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        impl_inner_method!(AnyDatabase, self, get_utxo, outpoint)
-    }
-    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        impl_inner_method!(AnyDatabase, self, get_raw_tx, txid)
-    }
-    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
-        impl_inner_method!(AnyDatabase, self, get_tx, txid, include_raw)
-    }
-    fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        impl_inner_method!(AnyDatabase, self, get_last_index, keychain)
-    }
-    fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
-        impl_inner_method!(AnyDatabase, self, get_sync_time)
-    }
-
-    fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
-        impl_inner_method!(AnyDatabase, self, increment_last_index, keychain)
-    }
-}
-
-impl BatchOperations for AnyBatch {
-    fn set_script_pubkey(
-        &mut self,
-        script: &Script,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<(), Error> {
-        impl_inner_method!(AnyBatch, self, set_script_pubkey, script, keychain, child)
-    }
-    fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
-        impl_inner_method!(AnyBatch, self, set_utxo, utxo)
-    }
-    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
-        impl_inner_method!(AnyBatch, self, set_raw_tx, transaction)
-    }
-    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
-        impl_inner_method!(AnyBatch, self, set_tx, transaction)
-    }
-    fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
-        impl_inner_method!(AnyBatch, self, set_last_index, keychain, value)
-    }
-    fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> {
-        impl_inner_method!(AnyBatch, self, set_sync_time, sync_time)
-    }
-
-    fn del_script_pubkey_from_path(
-        &mut self,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<Option<Script>, Error> {
-        impl_inner_method!(AnyBatch, self, del_script_pubkey_from_path, keychain, child)
-    }
-    fn del_path_from_script_pubkey(
-        &mut self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        impl_inner_method!(AnyBatch, self, del_path_from_script_pubkey, script)
-    }
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        impl_inner_method!(AnyBatch, self, del_utxo, outpoint)
-    }
-    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        impl_inner_method!(AnyBatch, self, del_raw_tx, txid)
-    }
-    fn del_tx(
-        &mut self,
-        txid: &Txid,
-        include_raw: bool,
-    ) -> Result<Option<TransactionDetails>, Error> {
-        impl_inner_method!(AnyBatch, self, del_tx, txid, include_raw)
-    }
-    fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        impl_inner_method!(AnyBatch, self, del_last_index, keychain)
-    }
-    fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
-        impl_inner_method!(AnyBatch, self, del_sync_time)
-    }
-}
-
-impl BatchDatabase for AnyDatabase {
-    type Batch = AnyBatch;
-
-    fn begin_batch(&self) -> Self::Batch {
-        match self {
-            AnyDatabase::Memory(inner) => inner.begin_batch().into(),
-            #[cfg(feature = "key-value-db")]
-            AnyDatabase::Sled(inner) => inner.begin_batch().into(),
-            #[cfg(feature = "sqlite")]
-            AnyDatabase::Sqlite(inner) => inner.begin_batch().into(),
-        }
-    }
-    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
-        match self {
-            AnyDatabase::Memory(db) => match batch {
-                AnyBatch::Memory(batch) => db.commit_batch(batch),
-                #[cfg(any(feature = "key-value-db", feature = "sqlite"))]
-                _ => unimplemented!("Other batch shouldn't be used with Memory db."),
-            },
-            #[cfg(feature = "key-value-db")]
-            AnyDatabase::Sled(db) => match batch {
-                AnyBatch::Sled(batch) => db.commit_batch(batch),
-                _ => unimplemented!("Other batch shouldn't be used with Sled db."),
-            },
-            #[cfg(feature = "sqlite")]
-            AnyDatabase::Sqlite(db) => match batch {
-                AnyBatch::Sqlite(batch) => db.commit_batch(batch),
-                _ => unimplemented!("Other batch shouldn't be used with Sqlite db."),
-            },
-        }
-    }
-}
-
-/// Configuration type for a [`sled::Tree`] database
-#[cfg(feature = "key-value-db")]
-#[derive(Debug, serde::Serialize, serde::Deserialize)]
-pub struct SledDbConfiguration {
-    /// Main directory of the db
-    pub path: String,
-    /// Name of the database tree, a separated namespace for the data
-    pub tree_name: String,
-}
-
-#[cfg(feature = "key-value-db")]
-impl ConfigurableDatabase for sled::Tree {
-    type Config = SledDbConfiguration;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        Ok(sled::open(&config.path)?.open_tree(&config.tree_name)?)
-    }
-}
-
-/// Configuration type for a [`sqlite::SqliteDatabase`] database
-#[cfg(feature = "sqlite")]
-#[derive(Debug, serde::Serialize, serde::Deserialize)]
-pub struct SqliteDbConfiguration {
-    /// Main directory of the db
-    pub path: String,
-}
-
-#[cfg(feature = "sqlite")]
-impl ConfigurableDatabase for sqlite::SqliteDatabase {
-    type Config = SqliteDbConfiguration;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        Ok(sqlite::SqliteDatabase::new(config.path.clone()))
-    }
-}
-
-/// Type that can contain any of the database configurations defined by the library
-///
-/// This allows storing a single configuration that can be loaded into an [`AnyDatabase`]
-/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime
-/// will find this particularly useful.
-#[derive(Debug, serde::Serialize, serde::Deserialize)]
-pub enum AnyDatabaseConfig {
-    /// Memory database has no config
-    Memory(()),
-    #[cfg(feature = "key-value-db")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
-    /// Simple key-value embedded database based on [`sled`]
-    Sled(SledDbConfiguration),
-    #[cfg(feature = "sqlite")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
-    /// Sqlite embedded database using [`rusqlite`]
-    Sqlite(SqliteDbConfiguration),
-}
-
-impl ConfigurableDatabase for AnyDatabase {
-    type Config = AnyDatabaseConfig;
-
-    fn from_config(config: &Self::Config) -> Result<Self, Error> {
-        Ok(match config {
-            AnyDatabaseConfig::Memory(inner) => {
-                AnyDatabase::Memory(memory::MemoryDatabase::from_config(inner)?)
-            }
-            #[cfg(feature = "key-value-db")]
-            AnyDatabaseConfig::Sled(inner) => AnyDatabase::Sled(sled::Tree::from_config(inner)?),
-            #[cfg(feature = "sqlite")]
-            AnyDatabaseConfig::Sqlite(inner) => {
-                AnyDatabase::Sqlite(sqlite::SqliteDatabase::from_config(inner)?)
-            }
-        })
-    }
-}
-
-impl_from!((), AnyDatabaseConfig, Memory,);
-impl_from!(SledDbConfiguration, AnyDatabaseConfig, Sled, #[cfg(feature = "key-value-db")]);
-impl_from!(SqliteDbConfiguration, AnyDatabaseConfig, Sqlite, #[cfg(feature = "sqlite")]);
diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs
deleted file mode 100644 (file)
index f586ebe..0000000
+++ /dev/null
@@ -1,535 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use std::convert::TryInto;
-
-use sled::{Batch, Tree};
-
-use bitcoin::consensus::encode::{deserialize, serialize};
-use bitcoin::hash_types::Txid;
-use bitcoin::{OutPoint, Script, Transaction};
-
-use crate::database::memory::MapKey;
-use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
-use crate::error::Error;
-use crate::types::*;
-
-macro_rules! impl_batch_operations {
-    ( { $($after_insert:tt)* }, $process_delete:ident ) => {
-        fn set_script_pubkey(&mut self, script: &Script, keychain: KeychainKind, path: u32) -> Result<(), Error> {
-            let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
-            self.insert(key, serialize(script))$($after_insert)*;
-
-            let key = MapKey::Script(Some(script)).as_map_key();
-            let value = json!({
-                "t": keychain,
-                "p": path,
-            });
-            self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
-            let key = MapKey::Utxo(Some(&utxo.outpoint)).as_map_key();
-            let value = json!({
-                "t": utxo.txout,
-                "i": utxo.keychain,
-                "s": utxo.is_spent,
-            });
-            self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
-            let key = MapKey::RawTx(Some(&transaction.txid())).as_map_key();
-            let value = serialize(transaction);
-            self.insert(key, value)$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
-            let key = MapKey::Transaction(Some(&transaction.txid)).as_map_key();
-
-            // remove the raw tx from the serialized version
-            let mut value = serde_json::to_value(transaction)?;
-            value["transaction"] = serde_json::Value::Null;
-            let value = serde_json::to_vec(&value)?;
-
-            self.insert(key, value)$($after_insert)*;
-
-            // insert the raw_tx if present
-            if let Some(ref tx) = transaction.transaction {
-                self.set_raw_tx(tx)?;
-            }
-
-            Ok(())
-        }
-
-        fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
-            let key = MapKey::LastIndex(keychain).as_map_key();
-            self.insert(key, &value.to_be_bytes())$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> {
-            let key = MapKey::SyncTime.as_map_key();
-            self.insert(key, serde_json::to_vec(&data)?)$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result<Option<Script>, Error> {
-            let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            Ok(res.map_or(Ok(None), |x| Some(deserialize(&x)).transpose())?)
-        }
-
-        fn del_path_from_script_pubkey(&mut self, script: &Script) -> Result<Option<(KeychainKind, u32)>, Error> {
-            let key = MapKey::Script(Some(script)).as_map_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            match res {
-                None => Ok(None),
-                Some(b) => {
-                    let mut val: serde_json::Value = serde_json::from_slice(&b)?;
-                    let st = serde_json::from_value(val["t"].take())?;
-                    let path = serde_json::from_value(val["p"].take())?;
-
-                    Ok(Some((st, path)))
-                }
-            }
-        }
-
-        fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-            let key = MapKey::Utxo(Some(outpoint)).as_map_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            match res {
-                None => Ok(None),
-                Some(b) => {
-                    let mut val: serde_json::Value = serde_json::from_slice(&b)?;
-                    let txout = serde_json::from_value(val["t"].take())?;
-                    let keychain = serde_json::from_value(val["i"].take())?;
-                    let is_spent = val.get_mut("s").and_then(|s| s.take().as_bool()).unwrap_or(false);
-
-                    Ok(Some(LocalUtxo { outpoint: outpoint.clone(), txout, keychain, is_spent, }))
-                }
-            }
-        }
-
-        fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-            let key = MapKey::RawTx(Some(txid)).as_map_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            Ok(res.map_or(Ok(None), |x| Some(deserialize(&x)).transpose())?)
-        }
-
-        fn del_tx(&mut self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
-            let raw_tx = if include_raw {
-                self.del_raw_tx(txid)?
-            } else {
-                None
-            };
-
-            let key = MapKey::Transaction(Some(txid)).as_map_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            match res {
-                None => Ok(None),
-                Some(b) => {
-                    let mut val: TransactionDetails = serde_json::from_slice(&b)?;
-                    val.transaction = raw_tx;
-
-                    Ok(Some(val))
-                }
-            }
-        }
-
-        fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-            let key = MapKey::LastIndex(keychain).as_map_key();
-            let res = self.remove(key);
-            $process_delete!(res)
-            .map(ivec_to_u32)
-            .transpose()
-        }
-
-        fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
-            let key = MapKey::SyncTime.as_map_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            Ok(res.map(|b| serde_json::from_slice(&b)).transpose()?)
-        }
-    }
-}
-
-macro_rules! process_delete_tree {
-    ($res:expr) => {
-        $res?
-    };
-}
-impl BatchOperations for Tree {
-    impl_batch_operations!({?}, process_delete_tree);
-}
-
-macro_rules! process_delete_batch {
-    ($res:expr) => {
-        None as Option<sled::IVec>
-    };
-}
-#[allow(unused_variables)]
-impl BatchOperations for Batch {
-    impl_batch_operations!({}, process_delete_batch);
-}
-
-impl Database for Tree {
-    fn check_descriptor_checksum<B: AsRef<[u8]>>(
-        &mut self,
-        keychain: KeychainKind,
-        bytes: B,
-    ) -> Result<(), Error> {
-        let key = MapKey::DescriptorChecksum(keychain).as_map_key();
-
-        let prev = self.get(&key)?.map(|x| x.to_vec());
-        if let Some(val) = prev {
-            if val == bytes.as_ref() {
-                Ok(())
-            } else {
-                Err(Error::ChecksumMismatch)
-            }
-        } else {
-            self.insert(&key, bytes.as_ref())?;
-            Ok(())
-        }
-    }
-
-    fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
-        let key = MapKey::Path((keychain, None)).as_map_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (_, v) = x?;
-                Ok(deserialize(&v)?)
-            })
-            .collect()
-    }
-
-    fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
-        let key = MapKey::Utxo(None).as_map_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (k, v) = x?;
-                let outpoint = deserialize(&k[1..])?;
-
-                let mut val: serde_json::Value = serde_json::from_slice(&v)?;
-                let txout = serde_json::from_value(val["t"].take())?;
-                let keychain = serde_json::from_value(val["i"].take())?;
-                let is_spent = val
-                    .get_mut("s")
-                    .and_then(|s| s.take().as_bool())
-                    .unwrap_or(false);
-
-                Ok(LocalUtxo {
-                    outpoint,
-                    txout,
-                    keychain,
-                    is_spent,
-                })
-            })
-            .collect()
-    }
-
-    fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> {
-        let key = MapKey::RawTx(None).as_map_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (_, v) = x?;
-                Ok(deserialize(&v)?)
-            })
-            .collect()
-    }
-
-    fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
-        let key = MapKey::Transaction(None).as_map_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (k, v) = x?;
-                let mut txdetails: TransactionDetails = serde_json::from_slice(&v)?;
-                if include_raw {
-                    let txid = deserialize(&k[1..])?;
-                    txdetails.transaction = self.get_raw_tx(&txid)?;
-                }
-
-                Ok(txdetails)
-            })
-            .collect()
-    }
-
-    fn get_script_pubkey_from_path(
-        &self,
-        keychain: KeychainKind,
-        path: u32,
-    ) -> Result<Option<Script>, Error> {
-        let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
-        Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
-    }
-
-    fn get_path_from_script_pubkey(
-        &self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        let key = MapKey::Script(Some(script)).as_map_key();
-        self.get(key)?
-            .map(|b| -> Result<_, Error> {
-                let mut val: serde_json::Value = serde_json::from_slice(&b)?;
-                let st = serde_json::from_value(val["t"].take())?;
-                let path = serde_json::from_value(val["p"].take())?;
-
-                Ok((st, path))
-            })
-            .transpose()
-    }
-
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        let key = MapKey::Utxo(Some(outpoint)).as_map_key();
-        self.get(key)?
-            .map(|b| -> Result<_, Error> {
-                let mut val: serde_json::Value = serde_json::from_slice(&b)?;
-                let txout = serde_json::from_value(val["t"].take())?;
-                let keychain = serde_json::from_value(val["i"].take())?;
-                let is_spent = val
-                    .get_mut("s")
-                    .and_then(|s| s.take().as_bool())
-                    .unwrap_or(false);
-
-                Ok(LocalUtxo {
-                    outpoint: *outpoint,
-                    txout,
-                    keychain,
-                    is_spent,
-                })
-            })
-            .transpose()
-    }
-
-    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        let key = MapKey::RawTx(Some(txid)).as_map_key();
-        Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
-    }
-
-    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
-        let key = MapKey::Transaction(Some(txid)).as_map_key();
-        self.get(key)?
-            .map(|b| -> Result<_, Error> {
-                let mut txdetails: TransactionDetails = serde_json::from_slice(&b)?;
-                if include_raw {
-                    txdetails.transaction = self.get_raw_tx(txid)?;
-                }
-
-                Ok(txdetails)
-            })
-            .transpose()
-    }
-
-    fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        let key = MapKey::LastIndex(keychain).as_map_key();
-        self.get(key)?.map(ivec_to_u32).transpose()
-    }
-
-    fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
-        let key = MapKey::SyncTime.as_map_key();
-        Ok(self
-            .get(key)?
-            .map(|b| serde_json::from_slice(&b))
-            .transpose()?)
-    }
-
-    // inserts 0 if not present
-    fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
-        let key = MapKey::LastIndex(keychain).as_map_key();
-        self.update_and_fetch(key, |prev| {
-            let new = match prev {
-                Some(b) => {
-                    let array: [u8; 4] = b.try_into().unwrap_or([0; 4]);
-                    let val = u32::from_be_bytes(array);
-
-                    val + 1
-                }
-                None => 0,
-            };
-
-            Some(new.to_be_bytes().to_vec())
-        })?
-        .map_or(Ok(0), ivec_to_u32)
-    }
-}
-
-fn ivec_to_u32(b: sled::IVec) -> Result<u32, Error> {
-    let array: [u8; 4] = b
-        .as_ref()
-        .try_into()
-        .map_err(|_| Error::InvalidU32Bytes(b.to_vec()))?;
-    let val = u32::from_be_bytes(array);
-    Ok(val)
-}
-
-impl BatchDatabase for Tree {
-    type Batch = sled::Batch;
-
-    fn begin_batch(&self) -> Self::Batch {
-        sled::Batch::default()
-    }
-
-    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
-        Ok(self.apply_batch(batch)?)
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use lazy_static::lazy_static;
-    use std::sync::{Arc, Condvar, Mutex, Once};
-    use std::time::{SystemTime, UNIX_EPOCH};
-
-    use sled::{Db, Tree};
-
-    static mut COUNT: usize = 0;
-
-    lazy_static! {
-        static ref DB: Arc<(Mutex<Option<Db>>, Condvar)> =
-            Arc::new((Mutex::new(None), Condvar::new()));
-        static ref INIT: Once = Once::new();
-    }
-
-    fn get_tree() -> Tree {
-        unsafe {
-            let cloned = DB.clone();
-            let (mutex, cvar) = &*cloned;
-
-            INIT.call_once(|| {
-                let mut db = mutex.lock().unwrap();
-
-                let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-                let mut dir = std::env::temp_dir();
-                dir.push(format!("mbw_{}", time.as_nanos()));
-
-                *db = Some(sled::open(dir).unwrap());
-                cvar.notify_all();
-            });
-
-            let mut db = mutex.lock().unwrap();
-            while !db.is_some() {
-                db = cvar.wait(db).unwrap();
-            }
-
-            COUNT += 1;
-
-            db.as_ref()
-                .unwrap()
-                .open_tree(format!("tree_{}", COUNT))
-                .unwrap()
-        }
-    }
-
-    #[test]
-    fn test_script_pubkey() {
-        crate::database::test::test_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_batch_script_pubkey() {
-        crate::database::test::test_batch_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_iter_script_pubkey() {
-        crate::database::test::test_iter_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_del_script_pubkey() {
-        crate::database::test::test_del_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_utxo() {
-        crate::database::test::test_utxo(get_tree());
-    }
-
-    #[test]
-    fn test_raw_tx() {
-        crate::database::test::test_raw_tx(get_tree());
-    }
-
-    #[test]
-    fn test_tx() {
-        crate::database::test::test_tx(get_tree());
-    }
-
-    #[test]
-    fn test_last_index() {
-        crate::database::test::test_last_index(get_tree());
-    }
-
-    #[test]
-    fn test_sync_time() {
-        crate::database::test::test_sync_time(get_tree());
-    }
-
-    #[test]
-    fn test_iter_raw_txs() {
-        crate::database::test::test_iter_raw_txs(get_tree());
-    }
-
-    #[test]
-    fn test_del_path_from_script_pubkey() {
-        crate::database::test::test_del_path_from_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_iter_script_pubkeys() {
-        crate::database::test::test_iter_script_pubkeys(get_tree());
-    }
-
-    #[test]
-    fn test_del_utxo() {
-        crate::database::test::test_del_utxo(get_tree());
-    }
-
-    #[test]
-    fn test_del_raw_tx() {
-        crate::database::test::test_del_raw_tx(get_tree());
-    }
-
-    #[test]
-    fn test_del_tx() {
-        crate::database::test::test_del_tx(get_tree());
-    }
-
-    #[test]
-    fn test_del_last_index() {
-        crate::database::test::test_del_last_index(get_tree());
-    }
-
-    #[test]
-    fn test_check_descriptor_checksum() {
-        crate::database::test::test_check_descriptor_checksum(get_tree());
-    }
-}
diff --git a/src/database/memory.rs b/src/database/memory.rs
deleted file mode 100644 (file)
index 6cfca6f..0000000
+++ /dev/null
@@ -1,690 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! In-memory ephemeral database
-//!
-//! This module defines an in-memory database type called [`MemoryDatabase`] that is based on a
-//! [`BTreeMap`].
-
-use std::any::Any;
-use std::collections::BTreeMap;
-use std::ops::Bound::{Excluded, Included};
-
-use bitcoin::consensus::encode::{deserialize, serialize};
-use bitcoin::hash_types::Txid;
-use bitcoin::{OutPoint, Script, Transaction};
-
-use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database, SyncTime};
-use crate::error::Error;
-use crate::types::*;
-
-// path -> script       p{i,e}<path> -> script
-// script -> path       s<script> -> {i,e}<path>
-// outpoint             u<outpoint> -> txout
-// rawtx                r<txid> -> tx
-// transactions         t<txid> -> tx details
-// deriv indexes        c{i,e} -> u32
-// descriptor checksum  d{i,e} -> vec<u8>
-// last sync time       l -> { height, timestamp }
-
-pub(crate) enum MapKey<'a> {
-    Path((Option<KeychainKind>, Option<u32>)),
-    Script(Option<&'a Script>),
-    Utxo(Option<&'a OutPoint>),
-    RawTx(Option<&'a Txid>),
-    Transaction(Option<&'a Txid>),
-    LastIndex(KeychainKind),
-    SyncTime,
-    DescriptorChecksum(KeychainKind),
-}
-
-impl MapKey<'_> {
-    fn as_prefix(&self) -> Vec<u8> {
-        match self {
-            MapKey::Path((st, _)) => {
-                let mut v = b"p".to_vec();
-                if let Some(st) = st {
-                    v.push(st.as_byte());
-                }
-                v
-            }
-            MapKey::Script(_) => b"s".to_vec(),
-            MapKey::Utxo(_) => b"u".to_vec(),
-            MapKey::RawTx(_) => b"r".to_vec(),
-            MapKey::Transaction(_) => b"t".to_vec(),
-            MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(),
-            MapKey::SyncTime => b"l".to_vec(),
-            MapKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(),
-        }
-    }
-
-    fn serialize_content(&self) -> Vec<u8> {
-        match self {
-            MapKey::Path((_, Some(child))) => child.to_be_bytes().to_vec(),
-            MapKey::Script(Some(s)) => serialize(*s),
-            MapKey::Utxo(Some(s)) => serialize(*s),
-            MapKey::RawTx(Some(s)) => serialize(*s),
-            MapKey::Transaction(Some(s)) => serialize(*s),
-            _ => vec![],
-        }
-    }
-
-    pub fn as_map_key(&self) -> Vec<u8> {
-        let mut v = self.as_prefix();
-        v.extend_from_slice(&self.serialize_content());
-
-        v
-    }
-}
-
-fn after(key: &[u8]) -> Vec<u8> {
-    let mut key = key.to_owned();
-    let mut idx = key.len();
-    while idx > 0 {
-        if key[idx - 1] == 0xFF {
-            idx -= 1;
-            continue;
-        } else {
-            key[idx - 1] += 1;
-            break;
-        }
-    }
-
-    key
-}
-
-/// In-memory ephemeral database
-///
-/// This database can be used as a temporary storage for wallets that are not kept permanently on
-/// a device, or on platforms that don't provide a filesystem, like `wasm32`.
-///
-/// Once it's dropped its content will be lost.
-///
-/// If you are looking for a permanent storage solution, you can try with the default key-value
-/// database called [`sled`]. See the [`database`] module documentation for more details.
-///
-/// [`database`]: crate::database
-#[derive(Debug, Default)]
-pub struct MemoryDatabase {
-    map: BTreeMap<Vec<u8>, Box<dyn Any + Send + Sync>>,
-    deleted_keys: Vec<Vec<u8>>,
-}
-
-impl MemoryDatabase {
-    /// Create a new empty database
-    pub fn new() -> Self {
-        MemoryDatabase {
-            map: BTreeMap::new(),
-            deleted_keys: Vec::new(),
-        }
-    }
-}
-
-impl BatchOperations for MemoryDatabase {
-    fn set_script_pubkey(
-        &mut self,
-        script: &Script,
-        keychain: KeychainKind,
-        path: u32,
-    ) -> Result<(), Error> {
-        let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
-        self.map.insert(key, Box::new(script.clone()));
-
-        let key = MapKey::Script(Some(script)).as_map_key();
-        let value = json!({
-            "t": keychain,
-            "p": path,
-        });
-        self.map.insert(key, Box::new(value));
-
-        Ok(())
-    }
-
-    fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
-        let key = MapKey::Utxo(Some(&utxo.outpoint)).as_map_key();
-        self.map.insert(
-            key,
-            Box::new((utxo.txout.clone(), utxo.keychain, utxo.is_spent)),
-        );
-
-        Ok(())
-    }
-    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
-        let key = MapKey::RawTx(Some(&transaction.txid())).as_map_key();
-        self.map.insert(key, Box::new(transaction.clone()));
-
-        Ok(())
-    }
-    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
-        let key = MapKey::Transaction(Some(&transaction.txid)).as_map_key();
-
-        // insert the raw_tx if present
-        if let Some(ref tx) = transaction.transaction {
-            self.set_raw_tx(tx)?;
-        }
-
-        // remove the raw tx from the serialized version
-        let mut transaction = transaction.clone();
-        transaction.transaction = None;
-
-        self.map.insert(key, Box::new(transaction));
-
-        Ok(())
-    }
-    fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
-        let key = MapKey::LastIndex(keychain).as_map_key();
-        self.map.insert(key, Box::new(value));
-
-        Ok(())
-    }
-    fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> {
-        let key = MapKey::SyncTime.as_map_key();
-        self.map.insert(key, Box::new(data));
-
-        Ok(())
-    }
-
-    fn del_script_pubkey_from_path(
-        &mut self,
-        keychain: KeychainKind,
-        path: u32,
-    ) -> Result<Option<Script>, Error> {
-        let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
-        let res = self.map.remove(&key);
-        self.deleted_keys.push(key);
-
-        Ok(res.map(|x| x.downcast_ref().cloned().unwrap()))
-    }
-    fn del_path_from_script_pubkey(
-        &mut self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        let key = MapKey::Script(Some(script)).as_map_key();
-        let res = self.map.remove(&key);
-        self.deleted_keys.push(key);
-
-        match res {
-            None => Ok(None),
-            Some(b) => {
-                let mut val: serde_json::Value = b.downcast_ref().cloned().unwrap();
-                let st = serde_json::from_value(val["t"].take())?;
-                let path = serde_json::from_value(val["p"].take())?;
-
-                Ok(Some((st, path)))
-            }
-        }
-    }
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        let key = MapKey::Utxo(Some(outpoint)).as_map_key();
-        let res = self.map.remove(&key);
-        self.deleted_keys.push(key);
-
-        match res {
-            None => Ok(None),
-            Some(b) => {
-                let (txout, keychain, is_spent) = b.downcast_ref().cloned().unwrap();
-                Ok(Some(LocalUtxo {
-                    outpoint: *outpoint,
-                    txout,
-                    keychain,
-                    is_spent,
-                }))
-            }
-        }
-    }
-    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        let key = MapKey::RawTx(Some(txid)).as_map_key();
-        let res = self.map.remove(&key);
-        self.deleted_keys.push(key);
-
-        Ok(res.map(|x| x.downcast_ref().cloned().unwrap()))
-    }
-    fn del_tx(
-        &mut self,
-        txid: &Txid,
-        include_raw: bool,
-    ) -> Result<Option<TransactionDetails>, Error> {
-        let raw_tx = if include_raw {
-            self.del_raw_tx(txid)?
-        } else {
-            None
-        };
-
-        let key = MapKey::Transaction(Some(txid)).as_map_key();
-        let res = self.map.remove(&key);
-        self.deleted_keys.push(key);
-
-        match res {
-            None => Ok(None),
-            Some(b) => {
-                let mut val: TransactionDetails = b.downcast_ref().cloned().unwrap();
-                val.transaction = raw_tx;
-
-                Ok(Some(val))
-            }
-        }
-    }
-    fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        let key = MapKey::LastIndex(keychain).as_map_key();
-        let res = self.map.remove(&key);
-        self.deleted_keys.push(key);
-
-        match res {
-            None => Ok(None),
-            Some(b) => Ok(Some(*b.downcast_ref().unwrap())),
-        }
-    }
-    fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
-        let key = MapKey::SyncTime.as_map_key();
-        let res = self.map.remove(&key);
-        self.deleted_keys.push(key);
-
-        Ok(res.map(|b| b.downcast_ref().cloned().unwrap()))
-    }
-}
-
-impl Database for MemoryDatabase {
-    fn check_descriptor_checksum<B: AsRef<[u8]>>(
-        &mut self,
-        keychain: KeychainKind,
-        bytes: B,
-    ) -> Result<(), Error> {
-        let key = MapKey::DescriptorChecksum(keychain).as_map_key();
-
-        let prev = self
-            .map
-            .get(&key)
-            .map(|x| x.downcast_ref::<Vec<u8>>().unwrap());
-        if let Some(val) = prev {
-            if val == &bytes.as_ref().to_vec() {
-                Ok(())
-            } else {
-                Err(Error::ChecksumMismatch)
-            }
-        } else {
-            self.map.insert(key, Box::new(bytes.as_ref().to_vec()));
-            Ok(())
-        }
-    }
-
-    fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
-        let key = MapKey::Path((keychain, None)).as_map_key();
-        self.map
-            .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
-            .map(|(_, v)| Ok(v.downcast_ref().cloned().unwrap()))
-            .collect()
-    }
-
-    fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
-        let key = MapKey::Utxo(None).as_map_key();
-        self.map
-            .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
-            .map(|(k, v)| {
-                let outpoint = deserialize(&k[1..]).unwrap();
-                let (txout, keychain, is_spent) = v.downcast_ref().cloned().unwrap();
-                Ok(LocalUtxo {
-                    outpoint,
-                    txout,
-                    keychain,
-                    is_spent,
-                })
-            })
-            .collect()
-    }
-
-    fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> {
-        let key = MapKey::RawTx(None).as_map_key();
-        self.map
-            .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
-            .map(|(_, v)| Ok(v.downcast_ref().cloned().unwrap()))
-            .collect()
-    }
-
-    fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
-        let key = MapKey::Transaction(None).as_map_key();
-        self.map
-            .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
-            .map(|(k, v)| {
-                let mut txdetails: TransactionDetails = v.downcast_ref().cloned().unwrap();
-                if include_raw {
-                    let txid = deserialize(&k[1..])?;
-                    txdetails.transaction = self.get_raw_tx(&txid)?;
-                }
-
-                Ok(txdetails)
-            })
-            .collect()
-    }
-
-    fn get_script_pubkey_from_path(
-        &self,
-        keychain: KeychainKind,
-        path: u32,
-    ) -> Result<Option<Script>, Error> {
-        let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
-        Ok(self
-            .map
-            .get(&key)
-            .map(|b| b.downcast_ref().cloned().unwrap()))
-    }
-
-    fn get_path_from_script_pubkey(
-        &self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        let key = MapKey::Script(Some(script)).as_map_key();
-        Ok(self.map.get(&key).map(|b| {
-            let mut val: serde_json::Value = b.downcast_ref().cloned().unwrap();
-            let st = serde_json::from_value(val["t"].take()).unwrap();
-            let path = serde_json::from_value(val["p"].take()).unwrap();
-
-            (st, path)
-        }))
-    }
-
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        let key = MapKey::Utxo(Some(outpoint)).as_map_key();
-        Ok(self.map.get(&key).map(|b| {
-            let (txout, keychain, is_spent) = b.downcast_ref().cloned().unwrap();
-            LocalUtxo {
-                outpoint: *outpoint,
-                txout,
-                keychain,
-                is_spent,
-            }
-        }))
-    }
-
-    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        let key = MapKey::RawTx(Some(txid)).as_map_key();
-        Ok(self
-            .map
-            .get(&key)
-            .map(|b| b.downcast_ref().cloned().unwrap()))
-    }
-
-    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
-        let key = MapKey::Transaction(Some(txid)).as_map_key();
-        Ok(self.map.get(&key).map(|b| {
-            let mut txdetails: TransactionDetails = b.downcast_ref().cloned().unwrap();
-            if include_raw {
-                txdetails.transaction = self.get_raw_tx(txid).unwrap();
-            }
-
-            txdetails
-        }))
-    }
-
-    fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        let key = MapKey::LastIndex(keychain).as_map_key();
-        Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap()))
-    }
-
-    fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
-        let key = MapKey::SyncTime.as_map_key();
-        Ok(self
-            .map
-            .get(&key)
-            .map(|b| b.downcast_ref().cloned().unwrap()))
-    }
-
-    // inserts 0 if not present
-    fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
-        let key = MapKey::LastIndex(keychain).as_map_key();
-        let value = self
-            .map
-            .entry(key)
-            .and_modify(|x| *x.downcast_mut::<u32>().unwrap() += 1)
-            .or_insert_with(|| Box::<u32>::new(0))
-            .downcast_mut()
-            .unwrap();
-
-        Ok(*value)
-    }
-}
-
-impl BatchDatabase for MemoryDatabase {
-    type Batch = Self;
-
-    fn begin_batch(&self) -> Self::Batch {
-        MemoryDatabase::new()
-    }
-
-    fn commit_batch(&mut self, mut batch: Self::Batch) -> Result<(), Error> {
-        for key in batch.deleted_keys.iter() {
-            self.map.remove(key);
-        }
-        self.map.append(&mut batch.map);
-        Ok(())
-    }
-}
-
-impl ConfigurableDatabase for MemoryDatabase {
-    type Config = ();
-
-    fn from_config(_config: &Self::Config) -> Result<Self, Error> {
-        Ok(MemoryDatabase::default())
-    }
-}
-
-#[macro_export]
-#[doc(hidden)]
-/// Artificially insert a tx in the database, as if we had found it with a `sync`. This is a hidden
-/// macro and not a `[cfg(test)]` function so it can be called within the context of doctests which
-/// don't have `test` set.
-macro_rules! populate_test_db {
-    ($db:expr, $tx_meta:expr, $current_height:expr$(,)?) => {{
-        $crate::populate_test_db!($db, $tx_meta, $current_height, (@coinbase false))
-    }};
-    ($db:expr, $tx_meta:expr, $current_height:expr, (@coinbase $is_coinbase:expr)$(,)?) => {{
-        use std::str::FromStr;
-        use $crate::database::SyncTime;
-        use $crate::database::{BatchOperations, Database};
-        let mut db = $db;
-        let tx_meta = $tx_meta;
-        let current_height: Option<u32> = $current_height;
-        let mut input = vec![$crate::bitcoin::TxIn::default()];
-        if !$is_coinbase {
-            input[0].previous_output.vout = 0;
-        }
-        let tx = $crate::bitcoin::Transaction {
-            version: 1,
-            lock_time: bitcoin::PackedLockTime(0),
-            input,
-            output: tx_meta
-                .output
-                .iter()
-                .map(|out_meta| $crate::bitcoin::TxOut {
-                    value: out_meta.value,
-                    script_pubkey: $crate::bitcoin::Address::from_str(&out_meta.to_address)
-                        .unwrap()
-                        .script_pubkey(),
-                })
-                .collect(),
-        };
-
-        let txid = tx.txid();
-        // Set Confirmation time only if current height is provided.
-        // panics if `tx_meta.min_confirmation` is Some, and current_height is None.
-        let confirmation_time = tx_meta
-            .min_confirmations
-            .and_then(|v| if v == 0 { None } else { Some(v) })
-            .map(|conf| $crate::BlockTime {
-                height: current_height.expect("Current height is needed for testing transaction with min-confirmation values").checked_sub(conf as u32).unwrap() + 1,
-                timestamp: 0,
-            });
-
-        // Set the database sync_time.
-        // Check if the current_height is less than already known sync height, apply the max
-        // If any of them is None, the other will be applied instead.
-        // If both are None, this will not be set.
-        if let Some(height) = db.get_sync_time().unwrap()
-                                .map(|sync_time| sync_time.block_time.height)
-                                .max(current_height) {
-            let sync_time = SyncTime {
-                block_time: BlockTime {
-                    height,
-                    timestamp: 0
-                }
-            };
-            db.set_sync_time(sync_time).unwrap();
-        }
-
-        let tx_details = $crate::TransactionDetails {
-            transaction: Some(tx.clone()),
-            txid,
-            fee: Some(0),
-            received: 0,
-            sent: 0,
-            confirmation_time,
-        };
-
-        db.set_tx(&tx_details).unwrap();
-        for (vout, out) in tx.output.iter().enumerate() {
-            db.set_utxo(&$crate::LocalUtxo {
-                txout: out.clone(),
-                outpoint: $crate::bitcoin::OutPoint {
-                    txid,
-                    vout: vout as u32,
-                },
-                keychain: $crate::KeychainKind::External,
-                is_spent: false,
-            })
-            .unwrap();
-        }
-
-        txid
-    }};
-}
-
-#[macro_export]
-#[doc(hidden)]
-/// Macro for getting a wallet for use in a doctest
-macro_rules! doctest_wallet {
-    () => {{
-        use $crate::bitcoin::Network;
-        use $crate::database::MemoryDatabase;
-        use $crate::testutils;
-        let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
-        let descriptors = testutils!(@descriptors (descriptor) (descriptor));
-
-        let mut db = MemoryDatabase::new();
-        let txid = populate_test_db!(
-            &mut db,
-            testutils! {
-                @tx ( (@external descriptors, 0) => 500_000 ) (@confirmations 1)
-            },
-            Some(100),
-        );
-
-        $crate::Wallet::new(
-            &descriptors.0,
-            descriptors.1.as_ref(),
-            Network::Regtest,
-            db
-        )
-        .unwrap()
-    }}
-}
-
-#[cfg(test)]
-mod test {
-    use super::MemoryDatabase;
-
-    fn get_tree() -> MemoryDatabase {
-        MemoryDatabase::new()
-    }
-
-    #[test]
-    fn test_script_pubkey() {
-        crate::database::test::test_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_batch_script_pubkey() {
-        crate::database::test::test_batch_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_iter_script_pubkey() {
-        crate::database::test::test_iter_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_del_script_pubkey() {
-        crate::database::test::test_del_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_utxo() {
-        crate::database::test::test_utxo(get_tree());
-    }
-
-    #[test]
-    fn test_raw_tx() {
-        crate::database::test::test_raw_tx(get_tree());
-    }
-
-    #[test]
-    fn test_tx() {
-        crate::database::test::test_tx(get_tree());
-    }
-
-    #[test]
-    fn test_last_index() {
-        crate::database::test::test_last_index(get_tree());
-    }
-
-    #[test]
-    fn test_sync_time() {
-        crate::database::test::test_sync_time(get_tree());
-    }
-
-    #[test]
-    fn test_iter_raw_txs() {
-        crate::database::test::test_iter_raw_txs(get_tree());
-    }
-
-    #[test]
-    fn test_del_path_from_script_pubkey() {
-        crate::database::test::test_del_path_from_script_pubkey(get_tree());
-    }
-
-    #[test]
-    fn test_iter_script_pubkeys() {
-        crate::database::test::test_iter_script_pubkeys(get_tree());
-    }
-
-    #[test]
-    fn test_del_utxo() {
-        crate::database::test::test_del_utxo(get_tree());
-    }
-
-    #[test]
-    fn test_del_raw_tx() {
-        crate::database::test::test_del_raw_tx(get_tree());
-    }
-
-    #[test]
-    fn test_del_tx() {
-        crate::database::test::test_del_tx(get_tree());
-    }
-
-    #[test]
-    fn test_del_last_index() {
-        crate::database::test::test_del_last_index(get_tree());
-    }
-
-    #[test]
-    fn test_check_descriptor_checksum() {
-        crate::database::test::test_check_descriptor_checksum(get_tree());
-    }
-}
diff --git a/src/database/mod.rs b/src/database/mod.rs
deleted file mode 100644 (file)
index bf53e94..0000000
+++ /dev/null
@@ -1,657 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Database types
-//!
-//! This module provides the implementation of some defaults database types, along with traits that
-//! can be implemented externally to let [`Wallet`]s use customized databases.
-//!
-//! It's important to note that the databases defined here only contains "blockchain-related" data.
-//! They can be seen more as a cache than a critical piece of storage that contains secrets and
-//! keys.
-//!
-//! The currently recommended database is [`sled`], which is a pretty simple key-value embedded
-//! database written in Rust. If the `key-value-db` feature is enabled (which by default is),
-//! this library automatically implements all the required traits for [`sled::Tree`].
-//!
-//! [`Wallet`]: crate::wallet::Wallet
-
-use serde::{Deserialize, Serialize};
-
-use bitcoin::hash_types::Txid;
-use bitcoin::{OutPoint, Script, Transaction, TxOut};
-
-use crate::error::Error;
-use crate::types::*;
-
-pub mod any;
-pub use any::{AnyDatabase, AnyDatabaseConfig};
-
-#[cfg(feature = "key-value-db")]
-pub(crate) mod keyvalue;
-
-#[cfg(feature = "sqlite")]
-pub(crate) mod sqlite;
-#[cfg(feature = "sqlite")]
-pub use sqlite::SqliteDatabase;
-
-pub mod memory;
-pub use memory::MemoryDatabase;
-
-/// Blockchain state at the time of syncing
-///
-/// Contains only the block time and height at the moment
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct SyncTime {
-    /// Block timestamp and height at the time of sync
-    pub block_time: BlockTime,
-}
-
-/// Trait for operations that can be batched
-///
-/// This trait defines the list of operations that must be implemented on the [`Database`] type and
-/// the [`BatchDatabase::Batch`] type.
-pub trait BatchOperations {
-    /// Store a script_pubkey along with its keychain and child number.
-    fn set_script_pubkey(
-        &mut self,
-        script: &Script,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<(), Error>;
-    /// Store a [`LocalUtxo`]
-    fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error>;
-    /// Store a raw transaction
-    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>;
-    /// Store the metadata of a transaction
-    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>;
-    /// Store the last derivation index for a given keychain.
-    fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>;
-    /// Store the sync time
-    fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error>;
-
-    /// Delete a script_pubkey given the keychain and its child number.
-    fn del_script_pubkey_from_path(
-        &mut self,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<Option<Script>, Error>;
-    /// Delete the data related to a specific script_pubkey, meaning the keychain and the child
-    /// number.
-    fn del_path_from_script_pubkey(
-        &mut self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error>;
-    /// Delete a [`LocalUtxo`] given its [`OutPoint`]
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>;
-    /// Delete a raw transaction given its [`Txid`]
-    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>;
-    /// Delete the metadata of a transaction and optionally the raw transaction itself
-    fn del_tx(
-        &mut self,
-        txid: &Txid,
-        include_raw: bool,
-    ) -> Result<Option<TransactionDetails>, Error>;
-    /// Delete the last derivation index for a keychain.
-    fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
-    /// Reset the sync time to `None`
-    ///
-    /// Returns the removed value
-    fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error>;
-}
-
-/// Trait for reading data from a database
-///
-/// This traits defines the operations that can be used to read data out of a database
-pub trait Database: BatchOperations {
-    /// Read and checks the descriptor checksum for a given keychain.
-    ///
-    /// Should return [`Error::ChecksumMismatch`](crate::error::Error::ChecksumMismatch) if the
-    /// checksum doesn't match. If there's no checksum in the database, simply store it for the
-    /// next time.
-    fn check_descriptor_checksum<B: AsRef<[u8]>>(
-        &mut self,
-        keychain: KeychainKind,
-        bytes: B,
-    ) -> Result<(), Error>;
-
-    /// Return the list of script_pubkeys
-    fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error>;
-    /// Return the list of [`LocalUtxo`]s
-    fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error>;
-    /// Return the list of raw transactions
-    fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>;
-    /// Return the list of transactions metadata
-    fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error>;
-
-    /// Fetch a script_pubkey given the child number of a keychain.
-    fn get_script_pubkey_from_path(
-        &self,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<Option<Script>, Error>;
-    /// Fetch the keychain and child number of a given script_pubkey
-    fn get_path_from_script_pubkey(
-        &self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error>;
-    /// Fetch a [`LocalUtxo`] given its [`OutPoint`]
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>;
-    /// Fetch a raw transaction given its [`Txid`]
-    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
-    /// Fetch the transaction metadata and optionally also the raw transaction
-    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
-    /// Return the last derivation index for a keychain.
-    fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
-    /// Return the sync time, if present
-    fn get_sync_time(&self) -> Result<Option<SyncTime>, Error>;
-
-    /// Increment the last derivation index for a keychain and return it
-    ///
-    /// It should insert and return `0` if not present in the database
-    fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error>;
-}
-
-/// Trait for a database that supports batch operations
-///
-/// This trait defines the methods to start and apply a batch of operations.
-pub trait BatchDatabase: Database {
-    /// Container for the operations
-    type Batch: BatchOperations;
-
-    /// Create a new batch container
-    fn begin_batch(&self) -> Self::Batch;
-    /// Consume and apply a batch of operations
-    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>;
-}
-
-/// Trait for [`Database`] types that can be created given a configuration
-pub trait ConfigurableDatabase: Database + Sized {
-    /// Type that contains the configuration
-    type Config: std::fmt::Debug;
-
-    /// Create a new instance given a configuration
-    fn from_config(config: &Self::Config) -> Result<Self, Error>;
-}
-
-pub(crate) trait DatabaseUtils: Database {
-    fn is_mine(&self, script: &Script) -> Result<bool, Error> {
-        self.get_path_from_script_pubkey(script)
-            .map(|o| o.is_some())
-    }
-
-    fn get_raw_tx_or<D>(&self, txid: &Txid, default: D) -> Result<Option<Transaction>, Error>
-    where
-        D: FnOnce() -> Result<Option<Transaction>, Error>,
-    {
-        self.get_tx(txid, true)?
-            .and_then(|t| t.transaction)
-            .map_or_else(default, |t| Ok(Some(t)))
-    }
-
-    fn get_previous_output(&self, outpoint: &OutPoint) -> Result<Option<TxOut>, Error> {
-        self.get_raw_tx(&outpoint.txid)?
-            .map(|previous_tx| {
-                if outpoint.vout as usize >= previous_tx.output.len() {
-                    Err(Error::InvalidOutpoint(*outpoint))
-                } else {
-                    Ok(previous_tx.output[outpoint.vout as usize].clone())
-                }
-            })
-            .transpose()
-    }
-}
-
-impl<T: Database> DatabaseUtils for T {}
-
-#[cfg(test)]
-pub mod test {
-    use std::str::FromStr;
-
-    use bitcoin::consensus::encode::deserialize;
-    use bitcoin::consensus::serialize;
-    use bitcoin::hashes::hex::*;
-    use bitcoin::*;
-
-    use super::*;
-
-    pub fn test_script_pubkey<D: Database>(mut db: D) {
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let keychain = KeychainKind::External;
-
-        db.set_script_pubkey(&script, keychain, path).unwrap();
-
-        assert_eq!(
-            db.get_script_pubkey_from_path(keychain, path).unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            db.get_path_from_script_pubkey(&script).unwrap(),
-            Some((keychain, path))
-        );
-    }
-
-    pub fn test_batch_script_pubkey<D: BatchDatabase>(mut db: D) {
-        let mut batch = db.begin_batch();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let keychain = KeychainKind::External;
-
-        batch.set_script_pubkey(&script, keychain, path).unwrap();
-
-        assert_eq!(
-            db.get_script_pubkey_from_path(keychain, path).unwrap(),
-            None
-        );
-        assert_eq!(db.get_path_from_script_pubkey(&script).unwrap(), None);
-
-        db.commit_batch(batch).unwrap();
-
-        assert_eq!(
-            db.get_script_pubkey_from_path(keychain, path).unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            db.get_path_from_script_pubkey(&script).unwrap(),
-            Some((keychain, path))
-        );
-    }
-
-    pub fn test_iter_script_pubkey<D: Database>(mut db: D) {
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let keychain = KeychainKind::External;
-
-        db.set_script_pubkey(&script, keychain, path).unwrap();
-
-        assert_eq!(db.iter_script_pubkeys(None).unwrap().len(), 1);
-    }
-
-    pub fn test_del_script_pubkey<D: Database>(mut db: D) {
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let keychain = KeychainKind::External;
-
-        db.set_script_pubkey(&script, keychain, path).unwrap();
-        assert_eq!(db.iter_script_pubkeys(None).unwrap().len(), 1);
-
-        db.del_script_pubkey_from_path(keychain, path).unwrap();
-        assert_eq!(db.iter_script_pubkeys(None).unwrap().len(), 0);
-    }
-
-    pub fn test_utxo<D: Database>(mut db: D) {
-        let outpoint = OutPoint::from_str(
-            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
-        )
-        .unwrap();
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let txout = TxOut {
-            value: 133742,
-            script_pubkey: script,
-        };
-        let utxo = LocalUtxo {
-            txout,
-            outpoint,
-            keychain: KeychainKind::External,
-            is_spent: true,
-        };
-
-        db.set_utxo(&utxo).unwrap();
-        db.set_utxo(&utxo).unwrap();
-        assert_eq!(db.iter_utxos().unwrap().len(), 1);
-        assert_eq!(db.get_utxo(&outpoint).unwrap(), Some(utxo));
-    }
-
-    pub fn test_raw_tx<D: Database>(mut db: D) {
-        let hex_tx = Vec::<u8>::from_hex("02000000000101f58c18a90d7a76b30c7e47d4e817adfdd79a6a589a615ef36e360f913adce2cd0000000000feffffff0210270000000000001600145c9a1816d38db5cbdd4b067b689dc19eb7d930e2cf70aa2b080000001600140f48b63160043047f4f60f7f8f551f80458f693f024730440220413f42b7bc979945489a38f5221e5527d4b8e3aa63eae2099e01945896ad6c10022024ceec492d685c31d8adb64e935a06933877c5ae0e21f32efe029850914c5bad012102361caae96f0e9f3a453d354bb37a5c3244422fb22819bf0166c0647a38de39f21fca2300").unwrap();
-        let mut tx: Transaction = deserialize(&hex_tx).unwrap();
-
-        db.set_raw_tx(&tx).unwrap();
-
-        let txid = tx.txid();
-
-        assert_eq!(db.get_raw_tx(&txid).unwrap(), Some(tx.clone()));
-
-        // mutate transaction's witnesses
-        for tx_in in tx.input.iter_mut() {
-            tx_in.witness = Witness::new();
-        }
-
-        let updated_hex_tx = serialize(&tx);
-
-        // verify that mutation was successful
-        assert_ne!(hex_tx, updated_hex_tx);
-
-        db.set_raw_tx(&tx).unwrap();
-
-        let txid = tx.txid();
-
-        assert_eq!(db.get_raw_tx(&txid).unwrap(), Some(tx));
-    }
-
-    pub fn test_tx<D: Database>(mut db: D) {
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-        let txid = tx.txid();
-        let mut tx_details = TransactionDetails {
-            transaction: Some(tx),
-            txid,
-            received: 1337,
-            sent: 420420,
-            fee: Some(140),
-            confirmation_time: Some(BlockTime {
-                timestamp: 123456,
-                height: 1000,
-            }),
-        };
-
-        db.set_tx(&tx_details).unwrap();
-
-        // get with raw tx too
-        assert_eq!(
-            db.get_tx(&tx_details.txid, true).unwrap(),
-            Some(tx_details.clone())
-        );
-        // get only raw_tx
-        assert_eq!(
-            db.get_raw_tx(&tx_details.txid).unwrap(),
-            tx_details.transaction
-        );
-
-        // now get without raw_tx
-        tx_details.transaction = None;
-        assert_eq!(
-            db.get_tx(&tx_details.txid, false).unwrap(),
-            Some(tx_details)
-        );
-    }
-
-    pub fn test_list_transaction<D: Database>(mut db: D) {
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-        let txid = tx.txid();
-        let mut tx_details = TransactionDetails {
-            transaction: Some(tx),
-            txid,
-            received: 1337,
-            sent: 420420,
-            fee: Some(140),
-            confirmation_time: Some(BlockTime {
-                timestamp: 123456,
-                height: 1000,
-            }),
-        };
-
-        db.set_tx(&tx_details).unwrap();
-
-        // get raw tx
-        assert_eq!(db.iter_txs(true).unwrap(), vec![tx_details.clone()]);
-
-        // now get without raw tx
-        tx_details.transaction = None;
-
-        // get not raw tx
-        assert_eq!(db.iter_txs(false).unwrap(), vec![tx_details.clone()]);
-    }
-
-    pub fn test_last_index<D: Database>(mut db: D) {
-        db.set_last_index(KeychainKind::External, 1337).unwrap();
-
-        assert_eq!(
-            db.get_last_index(KeychainKind::External).unwrap(),
-            Some(1337)
-        );
-        assert_eq!(db.get_last_index(KeychainKind::Internal).unwrap(), None);
-
-        let res = db.increment_last_index(KeychainKind::External).unwrap();
-        assert_eq!(res, 1338);
-        let res = db.increment_last_index(KeychainKind::Internal).unwrap();
-        assert_eq!(res, 0);
-
-        assert_eq!(
-            db.get_last_index(KeychainKind::External).unwrap(),
-            Some(1338)
-        );
-        assert_eq!(db.get_last_index(KeychainKind::Internal).unwrap(), Some(0));
-    }
-
-    pub fn test_sync_time<D: Database>(mut db: D) {
-        assert!(db.get_sync_time().unwrap().is_none());
-
-        db.set_sync_time(SyncTime {
-            block_time: BlockTime {
-                height: 100,
-                timestamp: 1000,
-            },
-        })
-        .unwrap();
-
-        let extracted = db.get_sync_time().unwrap();
-        assert!(extracted.is_some());
-        assert_eq!(extracted.as_ref().unwrap().block_time.height, 100);
-        assert_eq!(extracted.as_ref().unwrap().block_time.timestamp, 1000);
-
-        db.del_sync_time().unwrap();
-        assert!(db.get_sync_time().unwrap().is_none());
-    }
-
-    pub fn test_iter_raw_txs<D: Database>(mut db: D) {
-        let txs = db.iter_raw_txs().unwrap();
-        assert!(txs.is_empty());
-
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let first_tx: Transaction = deserialize(&hex_tx).unwrap();
-
-        let hex_tx = Vec::<u8>::from_hex("02000000000101f58c18a90d7a76b30c7e47d4e817adfdd79a6a589a615ef36e360f913adce2cd0000000000feffffff0210270000000000001600145c9a1816d38db5cbdd4b067b689dc19eb7d930e2cf70aa2b080000001600140f48b63160043047f4f60f7f8f551f80458f693f024730440220413f42b7bc979945489a38f5221e5527d4b8e3aa63eae2099e01945896ad6c10022024ceec492d685c31d8adb64e935a06933877c5ae0e21f32efe029850914c5bad012102361caae96f0e9f3a453d354bb37a5c3244422fb22819bf0166c0647a38de39f21fca2300").unwrap();
-        let second_tx: Transaction = deserialize(&hex_tx).unwrap();
-
-        db.set_raw_tx(&first_tx).unwrap();
-        db.set_raw_tx(&second_tx).unwrap();
-
-        let txs = db.iter_raw_txs().unwrap();
-
-        assert!(txs.contains(&first_tx));
-        assert!(txs.contains(&second_tx));
-        assert_eq!(txs.len(), 2);
-    }
-
-    pub fn test_del_path_from_script_pubkey<D: Database>(mut db: D) {
-        let keychain = KeychainKind::External;
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-
-        let res = db.del_path_from_script_pubkey(&script).unwrap();
-
-        assert!(res.is_none());
-
-        db.set_script_pubkey(&script, keychain, path).unwrap();
-        let (chain, child) = db.del_path_from_script_pubkey(&script).unwrap().unwrap();
-
-        assert_eq!(chain, keychain);
-        assert_eq!(child, path);
-
-        let res = db.get_path_from_script_pubkey(&script).unwrap();
-        assert!(res.is_none());
-    }
-
-    pub fn test_iter_script_pubkeys<D: Database>(mut db: D) {
-        let keychain = KeychainKind::External;
-        let scripts = db.iter_script_pubkeys(Some(keychain)).unwrap();
-        assert!(scripts.is_empty());
-
-        let first_script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-
-        db.set_script_pubkey(&first_script, keychain, path).unwrap();
-
-        let second_script = Script::from(
-            Vec::<u8>::from_hex("00145c9a1816d38db5cbdd4b067b689dc19eb7d930e2").unwrap(),
-        );
-        let path = 57;
-
-        db.set_script_pubkey(&second_script, keychain, path)
-            .unwrap();
-        let scripts = db.iter_script_pubkeys(Some(keychain)).unwrap();
-
-        assert!(scripts.contains(&first_script));
-        assert!(scripts.contains(&second_script));
-        assert_eq!(scripts.len(), 2);
-    }
-
-    pub fn test_del_utxo<D: Database>(mut db: D) {
-        let outpoint = OutPoint::from_str(
-            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
-        )
-        .unwrap();
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let txout = TxOut {
-            value: 133742,
-            script_pubkey: script,
-        };
-        let utxo = LocalUtxo {
-            txout,
-            outpoint,
-            keychain: KeychainKind::External,
-            is_spent: true,
-        };
-
-        let res = db.del_utxo(&outpoint).unwrap();
-        assert!(res.is_none());
-
-        db.set_utxo(&utxo).unwrap();
-
-        let res = db.del_utxo(&outpoint).unwrap();
-
-        assert_eq!(res.unwrap(), utxo);
-
-        let res = db.get_utxo(&outpoint).unwrap();
-        assert!(res.is_none());
-    }
-
-    pub fn test_del_raw_tx<D: Database>(mut db: D) {
-        let hex_tx = Vec::<u8>::from_hex("02000000000101f58c18a90d7a76b30c7e47d4e817adfdd79a6a589a615ef36e360f913adce2cd0000000000feffffff0210270000000000001600145c9a1816d38db5cbdd4b067b689dc19eb7d930e2cf70aa2b080000001600140f48b63160043047f4f60f7f8f551f80458f693f024730440220413f42b7bc979945489a38f5221e5527d4b8e3aa63eae2099e01945896ad6c10022024ceec492d685c31d8adb64e935a06933877c5ae0e21f32efe029850914c5bad012102361caae96f0e9f3a453d354bb37a5c3244422fb22819bf0166c0647a38de39f21fca2300").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-
-        let res = db.del_raw_tx(&tx.txid()).unwrap();
-
-        assert!(res.is_none());
-
-        db.set_raw_tx(&tx).unwrap();
-
-        let res = db.del_raw_tx(&tx.txid()).unwrap();
-
-        assert_eq!(res.unwrap(), tx);
-
-        let res = db.get_raw_tx(&tx.txid()).unwrap();
-        assert!(res.is_none());
-    }
-
-    pub fn test_del_tx<D: Database>(mut db: D) {
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-        let txid = tx.txid();
-        let mut tx_details = TransactionDetails {
-            transaction: Some(tx.clone()),
-            txid,
-            received: 1337,
-            sent: 420420,
-            fee: Some(140),
-            confirmation_time: Some(BlockTime {
-                timestamp: 123456,
-                height: 1000,
-            }),
-        };
-
-        let res = db.del_tx(&tx.txid(), true).unwrap();
-
-        assert!(res.is_none());
-
-        db.set_tx(&tx_details).unwrap();
-
-        let res = db.del_tx(&tx.txid(), false).unwrap();
-        tx_details.transaction = None;
-        assert_eq!(res.unwrap(), tx_details);
-
-        let res = db.get_tx(&tx.txid(), true).unwrap();
-        assert!(res.is_none());
-
-        let res = db.get_raw_tx(&tx.txid()).unwrap();
-        assert_eq!(res.unwrap(), tx);
-
-        db.set_tx(&tx_details).unwrap();
-        let res = db.del_tx(&tx.txid(), true).unwrap();
-        tx_details.transaction = Some(tx.clone());
-        assert_eq!(res.unwrap(), tx_details);
-
-        let res = db.get_tx(&tx.txid(), true).unwrap();
-        assert!(res.is_none());
-
-        let res = db.get_raw_tx(&tx.txid()).unwrap();
-        assert!(res.is_none());
-    }
-
-    pub fn test_del_last_index<D: Database>(mut db: D) {
-        let keychain = KeychainKind::External;
-
-        db.increment_last_index(keychain).unwrap();
-
-        let res = db.get_last_index(keychain).unwrap().unwrap();
-
-        assert_eq!(res, 0);
-
-        db.increment_last_index(keychain).unwrap();
-
-        let res = db.del_last_index(keychain).unwrap().unwrap();
-
-        assert_eq!(res, 1);
-
-        let res = db.get_last_index(keychain).unwrap();
-        assert!(res.is_none());
-    }
-
-    pub fn test_check_descriptor_checksum<D: Database>(mut db: D) {
-        // insert checksum associated to keychain
-        let checksum = "1cead456".as_bytes();
-        let keychain = KeychainKind::External;
-        db.check_descriptor_checksum(keychain, checksum).unwrap();
-
-        // check if `check_descriptor_checksum` throws
-        // `Error::ChecksumMismatch` error if the
-        // function is passed a checksum that does
-        // not match the one initially inserted
-        let checksum = "1cead454".as_bytes();
-        let keychain = KeychainKind::External;
-        let res = db.check_descriptor_checksum(keychain, checksum);
-
-        assert!(res.is_err());
-    }
-
-    // TODO: more tests...
-}
diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs
deleted file mode 100644 (file)
index fc71508..0000000
+++ /dev/null
@@ -1,1147 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-use std::path::Path;
-use std::path::PathBuf;
-
-use bitcoin::consensus::encode::{deserialize, serialize};
-use bitcoin::hash_types::Txid;
-use bitcoin::{OutPoint, Script, Transaction, TxOut};
-
-use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
-use crate::error::Error;
-use crate::types::*;
-
-use rusqlite::{named_params, Connection};
-
-static MIGRATIONS: &[&str] = &[
-    "CREATE TABLE version (version INTEGER)",
-    "INSERT INTO version VALUES (1)",
-    "CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
-    "CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
-    "CREATE INDEX idx_script ON script_pubkeys(script);",
-    "CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB);",
-    "CREATE INDEX idx_txid_vout ON utxos(txid, vout);",
-    "CREATE TABLE transactions (txid BLOB, raw_tx BLOB);",
-    "CREATE INDEX idx_txid ON transactions(txid);",
-    "CREATE TABLE transaction_details (txid BLOB, timestamp INTEGER, received INTEGER, sent INTEGER, fee INTEGER, height INTEGER, verified INTEGER DEFAULT 0);",
-    "CREATE INDEX idx_txdetails_txid ON transaction_details(txid);",
-    "CREATE TABLE last_derivation_indices (keychain TEXT, value INTEGER);",
-    "CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);",
-    "CREATE TABLE checksums (keychain TEXT, checksum BLOB);",
-    "CREATE INDEX idx_checksums_keychain ON checksums(keychain);",
-    "CREATE TABLE sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);",
-    "ALTER TABLE transaction_details RENAME TO transaction_details_old;",
-    "CREATE TABLE transaction_details (txid BLOB, timestamp INTEGER, received INTEGER, sent INTEGER, fee INTEGER, height INTEGER);",
-    "INSERT INTO transaction_details SELECT txid, timestamp, received, sent, fee, height FROM transaction_details_old;",
-    "DROP TABLE transaction_details_old;",
-    "ALTER TABLE utxos ADD COLUMN is_spent;",
-    // drop all data due to possible inconsistencies with duplicate utxos, re-sync required
-    "DELETE FROM checksums;",
-    "DELETE FROM last_derivation_indices;",
-    "DELETE FROM script_pubkeys;",
-    "DELETE FROM sync_time;",
-    "DELETE FROM transaction_details;",
-    "DELETE FROM transactions;",
-    "DELETE FROM utxos;",
-    "DROP INDEX idx_txid_vout;",
-    "CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
-    "ALTER TABLE utxos RENAME TO utxos_old;",
-    "CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
-    "INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
-    "DROP TABLE utxos_old;",
-    "CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
-    // Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys
-    "ALTER TABLE script_pubkeys RENAME TO script_pubkeys_old;",
-    "DROP INDEX idx_keychain_child;",
-    "DROP INDEX idx_script;",
-    "CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
-    "CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
-    "CREATE INDEX idx_script ON script_pubkeys(script);",
-    "CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);",
-    "INSERT OR REPLACE INTO script_pubkeys SELECT keychain, child, script FROM script_pubkeys_old;",
-    "DROP TABLE script_pubkeys_old;"
-];
-
-/// Sqlite database stored on filesystem
-///
-/// This is a permanent storage solution for devices and platforms that provide a filesystem.
-/// [`crate::database`]
-#[derive(Debug)]
-pub struct SqliteDatabase {
-    /// Path on the local filesystem to store the sqlite file
-    pub path: PathBuf,
-    /// A rusqlite connection object to the sqlite database
-    pub connection: Connection,
-}
-
-impl SqliteDatabase {
-    /// Instantiate a new SqliteDatabase instance by creating a connection
-    /// to the database stored at path
-    pub fn new<T: AsRef<Path>>(path: T) -> Self {
-        let connection = get_connection(&path).unwrap();
-        SqliteDatabase {
-            path: PathBuf::from(path.as_ref()),
-            connection,
-        }
-    }
-    fn insert_script_pubkey(
-        &self,
-        keychain: String,
-        child: u32,
-        script: &[u8],
-    ) -> Result<i64, Error> {
-        let mut statement = self.connection.prepare_cached("INSERT OR REPLACE INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
-        statement.execute(named_params! {
-            ":keychain": keychain,
-            ":child": child,
-            ":script": script
-        })?;
-
-        Ok(self.connection.last_insert_rowid())
-    }
-    fn insert_utxo(
-        &self,
-        value: u64,
-        keychain: String,
-        vout: u32,
-        txid: &[u8],
-        script: &[u8],
-        is_spent: bool,
-    ) -> Result<i64, Error> {
-        let mut statement = self.connection.prepare_cached("INSERT INTO utxos (value, keychain, vout, txid, script, is_spent) VALUES (:value, :keychain, :vout, :txid, :script, :is_spent) ON CONFLICT(txid, vout) DO UPDATE SET value=:value, keychain=:keychain, script=:script, is_spent=:is_spent")?;
-        statement.execute(named_params! {
-            ":value": value,
-            ":keychain": keychain,
-            ":vout": vout,
-            ":txid": txid,
-            ":script": script,
-            ":is_spent": is_spent,
-        })?;
-
-        Ok(self.connection.last_insert_rowid())
-    }
-    fn insert_transaction(&self, txid: &[u8], raw_tx: &[u8]) -> Result<i64, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("INSERT INTO transactions (txid, raw_tx) VALUES (:txid, :raw_tx)")?;
-        statement.execute(named_params! {
-            ":txid": txid,
-            ":raw_tx": raw_tx,
-        })?;
-
-        Ok(self.connection.last_insert_rowid())
-    }
-
-    fn update_transaction(&self, txid: &[u8], raw_tx: &[u8]) -> Result<(), Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("UPDATE transactions SET raw_tx=:raw_tx WHERE txid=:txid")?;
-
-        statement.execute(named_params! {
-            ":txid": txid,
-            ":raw_tx": raw_tx,
-        })?;
-
-        Ok(())
-    }
-
-    fn insert_transaction_details(&self, transaction: &TransactionDetails) -> Result<i64, Error> {
-        let (timestamp, height) = match &transaction.confirmation_time {
-            Some(confirmation_time) => (
-                Some(confirmation_time.timestamp),
-                Some(confirmation_time.height),
-            ),
-            None => (None, None),
-        };
-
-        let txid: &[u8] = &transaction.txid;
-
-        let mut statement = self.connection.prepare_cached("INSERT INTO transaction_details (txid, timestamp, received, sent, fee, height) VALUES (:txid, :timestamp, :received, :sent, :fee, :height)")?;
-
-        statement.execute(named_params! {
-            ":txid": txid,
-            ":timestamp": timestamp,
-            ":received": transaction.received,
-            ":sent": transaction.sent,
-            ":fee": transaction.fee,
-            ":height": height,
-        })?;
-
-        Ok(self.connection.last_insert_rowid())
-    }
-
-    fn update_transaction_details(&self, transaction: &TransactionDetails) -> Result<(), Error> {
-        let (timestamp, height) = match &transaction.confirmation_time {
-            Some(confirmation_time) => (
-                Some(confirmation_time.timestamp),
-                Some(confirmation_time.height),
-            ),
-            None => (None, None),
-        };
-
-        let txid: &[u8] = &transaction.txid;
-
-        let mut statement = self.connection.prepare_cached("UPDATE transaction_details SET timestamp=:timestamp, received=:received, sent=:sent, fee=:fee, height=:height WHERE txid=:txid")?;
-
-        statement.execute(named_params! {
-            ":txid": txid,
-            ":timestamp": timestamp,
-            ":received": transaction.received,
-            ":sent": transaction.sent,
-            ":fee": transaction.fee,
-            ":height": height,
-        })?;
-
-        Ok(())
-    }
-
-    fn insert_last_derivation_index(&self, keychain: String, value: u32) -> Result<i64, Error> {
-        let mut statement = self.connection.prepare_cached(
-            "INSERT INTO last_derivation_indices (keychain, value) VALUES (:keychain, :value)",
-        )?;
-
-        statement.execute(named_params! {
-            ":keychain": keychain,
-            ":value": value,
-        })?;
-
-        Ok(self.connection.last_insert_rowid())
-    }
-
-    fn insert_checksum(&self, keychain: String, checksum: &[u8]) -> Result<i64, Error> {
-        let mut statement = self.connection.prepare_cached(
-            "INSERT INTO checksums (keychain, checksum) VALUES (:keychain, :checksum)",
-        )?;
-        statement.execute(named_params! {
-            ":keychain": keychain,
-            ":checksum": checksum,
-        })?;
-
-        Ok(self.connection.last_insert_rowid())
-    }
-
-    fn update_last_derivation_index(&self, keychain: String, value: u32) -> Result<(), Error> {
-        let mut statement = self.connection.prepare_cached(
-            "INSERT INTO last_derivation_indices (keychain, value) VALUES (:keychain, :value) ON CONFLICT(keychain) DO UPDATE SET value=:value WHERE keychain=:keychain",
-        )?;
-
-        statement.execute(named_params! {
-            ":keychain": keychain,
-            ":value": value,
-        })?;
-
-        Ok(())
-    }
-
-    fn update_sync_time(&self, data: SyncTime) -> Result<i64, Error> {
-        let mut statement = self.connection.prepare_cached(
-            "INSERT INTO sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0",
-        )?;
-
-        statement.execute(named_params! {
-            ":height": data.block_time.height,
-            ":timestamp": data.block_time.timestamp,
-        })?;
-
-        Ok(self.connection.last_insert_rowid())
-    }
-
-    fn select_script_pubkeys(&self) -> Result<Vec<Script>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT script FROM script_pubkeys")?;
-        let mut scripts: Vec<Script> = vec![];
-        let mut rows = statement.query([])?;
-        while let Some(row) = rows.next()? {
-            let raw_script: Vec<u8> = row.get(0)?;
-            scripts.push(raw_script.into());
-        }
-
-        Ok(scripts)
-    }
-
-    fn select_script_pubkeys_by_keychain(&self, keychain: String) -> Result<Vec<Script>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT script FROM script_pubkeys WHERE keychain=:keychain")?;
-        let mut scripts: Vec<Script> = vec![];
-        let mut rows = statement.query(named_params! {":keychain": keychain})?;
-        while let Some(row) = rows.next()? {
-            let raw_script: Vec<u8> = row.get(0)?;
-            scripts.push(raw_script.into());
-        }
-
-        Ok(scripts)
-    }
-
-    fn select_script_pubkey_by_path(
-        &self,
-        keychain: String,
-        child: u32,
-    ) -> Result<Option<Script>, Error> {
-        let mut statement = self.connection.prepare_cached(
-            "SELECT script FROM script_pubkeys WHERE keychain=:keychain AND child=:child",
-        )?;
-        let mut rows = statement.query(named_params! {":keychain": keychain,":child": child})?;
-
-        match rows.next()? {
-            Some(row) => {
-                let script: Vec<u8> = row.get(0)?;
-                let script: Script = script.into();
-                Ok(Some(script))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn select_script_pubkey_by_script(
-        &self,
-        script: &[u8],
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT keychain, child FROM script_pubkeys WHERE script=:script")?;
-        let mut rows = statement.query(named_params! {":script": script})?;
-        match rows.next()? {
-            Some(row) => {
-                let keychain: String = row.get(0)?;
-                let keychain: KeychainKind = serde_json::from_str(&keychain)?;
-                let child: u32 = row.get(1)?;
-                Ok(Some((keychain, child)))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn select_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT value, keychain, vout, txid, script, is_spent FROM utxos")?;
-        let mut utxos: Vec<LocalUtxo> = vec![];
-        let mut rows = statement.query([])?;
-        while let Some(row) = rows.next()? {
-            let value = row.get(0)?;
-            let keychain: String = row.get(1)?;
-            let vout = row.get(2)?;
-            let txid: Vec<u8> = row.get(3)?;
-            let script: Vec<u8> = row.get(4)?;
-            let is_spent: bool = row.get(5)?;
-
-            let keychain: KeychainKind = serde_json::from_str(&keychain)?;
-
-            utxos.push(LocalUtxo {
-                outpoint: OutPoint::new(deserialize(&txid)?, vout),
-                txout: TxOut {
-                    value,
-                    script_pubkey: script.into(),
-                },
-                keychain,
-                is_spent,
-            })
-        }
-
-        Ok(utxos)
-    }
-
-    fn select_utxo_by_outpoint(&self, txid: &[u8], vout: u32) -> Result<Option<LocalUtxo>, Error> {
-        let mut statement = self.connection.prepare_cached(
-            "SELECT value, keychain, script, is_spent FROM utxos WHERE txid=:txid AND vout=:vout",
-        )?;
-        let mut rows = statement.query(named_params! {":txid": txid,":vout": vout})?;
-        match rows.next()? {
-            Some(row) => {
-                let value: u64 = row.get(0)?;
-                let keychain: String = row.get(1)?;
-                let keychain: KeychainKind = serde_json::from_str(&keychain)?;
-                let script: Vec<u8> = row.get(2)?;
-                let script_pubkey: Script = script.into();
-                let is_spent: bool = row.get(3)?;
-
-                Ok(Some(LocalUtxo {
-                    outpoint: OutPoint::new(deserialize(txid)?, vout),
-                    txout: TxOut {
-                        value,
-                        script_pubkey,
-                    },
-                    keychain,
-                    is_spent,
-                }))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn select_transactions(&self) -> Result<Vec<Transaction>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT raw_tx FROM transactions")?;
-        let mut txs: Vec<Transaction> = vec![];
-        let mut rows = statement.query([])?;
-        while let Some(row) = rows.next()? {
-            let raw_tx: Vec<u8> = row.get(0)?;
-            let tx: Transaction = deserialize(&raw_tx)?;
-            txs.push(tx);
-        }
-        Ok(txs)
-    }
-
-    fn select_transaction_by_txid(&self, txid: &[u8]) -> Result<Option<Transaction>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT raw_tx FROM transactions WHERE txid=:txid")?;
-        let mut rows = statement.query(named_params! {":txid": txid})?;
-        match rows.next()? {
-            Some(row) => {
-                let raw_tx: Vec<u8> = row.get(0)?;
-                let tx: Transaction = deserialize(&raw_tx)?;
-                Ok(Some(tx))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn select_transaction_details_with_raw(&self) -> Result<Vec<TransactionDetails>, Error> {
-        let mut statement = self.connection.prepare_cached("SELECT transaction_details.txid, transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid = transactions.txid")?;
-        let mut transaction_details: Vec<TransactionDetails> = vec![];
-        let mut rows = statement.query([])?;
-        while let Some(row) = rows.next()? {
-            let txid: Vec<u8> = row.get(0)?;
-            let txid: Txid = deserialize(&txid)?;
-            let timestamp: Option<u64> = row.get(1)?;
-            let received: u64 = row.get(2)?;
-            let sent: u64 = row.get(3)?;
-            let fee: Option<u64> = row.get(4)?;
-            let height: Option<u32> = row.get(5)?;
-            let raw_tx: Option<Vec<u8>> = row.get(6)?;
-            let tx: Option<Transaction> = match raw_tx {
-                Some(raw_tx) => {
-                    let tx: Transaction = deserialize(&raw_tx)?;
-                    Some(tx)
-                }
-                None => None,
-            };
-
-            let confirmation_time = match (height, timestamp) {
-                (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
-                _ => None,
-            };
-
-            transaction_details.push(TransactionDetails {
-                transaction: tx,
-                txid,
-                received,
-                sent,
-                fee,
-                confirmation_time,
-            });
-        }
-        Ok(transaction_details)
-    }
-
-    fn select_transaction_details(&self) -> Result<Vec<TransactionDetails>, Error> {
-        let mut statement = self.connection.prepare_cached(
-            "SELECT txid, timestamp, received, sent, fee, height FROM transaction_details",
-        )?;
-        let mut transaction_details: Vec<TransactionDetails> = vec![];
-        let mut rows = statement.query([])?;
-        while let Some(row) = rows.next()? {
-            let txid: Vec<u8> = row.get(0)?;
-            let txid: Txid = deserialize(&txid)?;
-            let timestamp: Option<u64> = row.get(1)?;
-            let received: u64 = row.get(2)?;
-            let sent: u64 = row.get(3)?;
-            let fee: Option<u64> = row.get(4)?;
-            let height: Option<u32> = row.get(5)?;
-
-            let confirmation_time = match (height, timestamp) {
-                (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
-                _ => None,
-            };
-
-            transaction_details.push(TransactionDetails {
-                transaction: None,
-                txid,
-                received,
-                sent,
-                fee,
-                confirmation_time,
-            });
-        }
-        Ok(transaction_details)
-    }
-
-    fn select_transaction_details_by_txid(
-        &self,
-        txid: &[u8],
-    ) -> Result<Option<TransactionDetails>, Error> {
-        let mut statement = self.connection.prepare_cached("SELECT transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid=transactions.txid AND transaction_details.txid=:txid")?;
-        let mut rows = statement.query(named_params! { ":txid": txid })?;
-
-        match rows.next()? {
-            Some(row) => {
-                let timestamp: Option<u64> = row.get(0)?;
-                let received: u64 = row.get(1)?;
-                let sent: u64 = row.get(2)?;
-                let fee: Option<u64> = row.get(3)?;
-                let height: Option<u32> = row.get(4)?;
-
-                let raw_tx: Option<Vec<u8>> = row.get(5)?;
-                let tx: Option<Transaction> = match raw_tx {
-                    Some(raw_tx) => {
-                        let tx: Transaction = deserialize(&raw_tx)?;
-                        Some(tx)
-                    }
-                    None => None,
-                };
-
-                let confirmation_time = match (height, timestamp) {
-                    (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
-                    _ => None,
-                };
-
-                Ok(Some(TransactionDetails {
-                    transaction: tx,
-                    txid: deserialize(txid)?,
-                    received,
-                    sent,
-                    fee,
-                    confirmation_time,
-                }))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn select_last_derivation_index_by_keychain(
-        &self,
-        keychain: String,
-    ) -> Result<Option<u32>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT value FROM last_derivation_indices WHERE keychain=:keychain")?;
-        let mut rows = statement.query(named_params! {":keychain": keychain})?;
-        match rows.next()? {
-            Some(row) => {
-                let value: u32 = row.get(0)?;
-                Ok(Some(value))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn select_sync_time(&self) -> Result<Option<SyncTime>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT height, timestamp FROM sync_time WHERE id = 0")?;
-        let mut rows = statement.query([])?;
-
-        if let Some(row) = rows.next()? {
-            Ok(Some(SyncTime {
-                block_time: BlockTime {
-                    height: row.get(0)?,
-                    timestamp: row.get(1)?,
-                },
-            }))
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn select_checksum_by_keychain(&self, keychain: String) -> Result<Option<Vec<u8>>, Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("SELECT checksum FROM checksums WHERE keychain=:keychain")?;
-        let mut rows = statement.query(named_params! {":keychain": keychain})?;
-
-        match rows.next()? {
-            Some(row) => {
-                let checksum: Vec<u8> = row.get(0)?;
-                Ok(Some(checksum))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn delete_script_pubkey_by_path(&self, keychain: String, child: u32) -> Result<(), Error> {
-        let mut statement = self.connection.prepare_cached(
-            "DELETE FROM script_pubkeys WHERE keychain=:keychain AND child=:child",
-        )?;
-        statement.execute(named_params! {
-            ":keychain": keychain,
-            ":child": child
-        })?;
-
-        Ok(())
-    }
-
-    fn delete_script_pubkey_by_script(&self, script: &[u8]) -> Result<(), Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("DELETE FROM script_pubkeys WHERE script=:script")?;
-        statement.execute(named_params! {
-            ":script": script
-        })?;
-
-        Ok(())
-    }
-
-    fn delete_utxo_by_outpoint(&self, txid: &[u8], vout: u32) -> Result<(), Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("DELETE FROM utxos WHERE txid=:txid AND vout=:vout")?;
-        statement.execute(named_params! {
-            ":txid": txid,
-            ":vout": vout
-        })?;
-
-        Ok(())
-    }
-
-    fn delete_transaction_by_txid(&self, txid: &[u8]) -> Result<(), Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("DELETE FROM transactions WHERE txid=:txid")?;
-        statement.execute(named_params! {":txid": txid})?;
-        Ok(())
-    }
-
-    fn delete_transaction_details_by_txid(&self, txid: &[u8]) -> Result<(), Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("DELETE FROM transaction_details WHERE txid=:txid")?;
-        statement.execute(named_params! {":txid": txid})?;
-        Ok(())
-    }
-
-    fn delete_last_derivation_index_by_keychain(&self, keychain: String) -> Result<(), Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("DELETE FROM last_derivation_indices WHERE keychain=:keychain")?;
-        statement.execute(named_params! {
-            ":keychain": &keychain
-        })?;
-
-        Ok(())
-    }
-
-    fn delete_sync_time(&self) -> Result<(), Error> {
-        let mut statement = self
-            .connection
-            .prepare_cached("DELETE FROM sync_time WHERE id = 0")?;
-        statement.execute([])?;
-        Ok(())
-    }
-}
-
-impl BatchOperations for SqliteDatabase {
-    fn set_script_pubkey(
-        &mut self,
-        script: &Script,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<(), Error> {
-        let keychain = serde_json::to_string(&keychain)?;
-        self.insert_script_pubkey(keychain, child, script.as_bytes())?;
-        Ok(())
-    }
-
-    fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
-        self.insert_utxo(
-            utxo.txout.value,
-            serde_json::to_string(&utxo.keychain)?,
-            utxo.outpoint.vout,
-            &utxo.outpoint.txid,
-            utxo.txout.script_pubkey.as_bytes(),
-            utxo.is_spent,
-        )?;
-        Ok(())
-    }
-
-    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
-        match self.select_transaction_by_txid(&transaction.txid())? {
-            Some(_) => {
-                self.update_transaction(&transaction.txid(), &serialize(transaction))?;
-            }
-            None => {
-                self.insert_transaction(&transaction.txid(), &serialize(transaction))?;
-            }
-        }
-        Ok(())
-    }
-
-    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
-        match self.select_transaction_details_by_txid(&transaction.txid)? {
-            Some(_) => {
-                self.update_transaction_details(transaction)?;
-            }
-            None => {
-                self.insert_transaction_details(transaction)?;
-            }
-        }
-
-        if let Some(tx) = &transaction.transaction {
-            self.set_raw_tx(tx)?;
-        }
-
-        Ok(())
-    }
-
-    fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
-        self.update_last_derivation_index(serde_json::to_string(&keychain)?, value)?;
-        Ok(())
-    }
-
-    fn set_sync_time(&mut self, ct: SyncTime) -> Result<(), Error> {
-        self.update_sync_time(ct)?;
-        Ok(())
-    }
-
-    fn del_script_pubkey_from_path(
-        &mut self,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<Option<Script>, Error> {
-        let keychain = serde_json::to_string(&keychain)?;
-        let script = self.select_script_pubkey_by_path(keychain.clone(), child)?;
-        match script {
-            Some(script) => {
-                self.delete_script_pubkey_by_path(keychain, child)?;
-                Ok(Some(script))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn del_path_from_script_pubkey(
-        &mut self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        match self.select_script_pubkey_by_script(script.as_bytes())? {
-            Some((keychain, child)) => {
-                self.delete_script_pubkey_by_script(script.as_bytes())?;
-                Ok(Some((keychain, child)))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        match self.select_utxo_by_outpoint(&outpoint.txid, outpoint.vout)? {
-            Some(local_utxo) => {
-                self.delete_utxo_by_outpoint(&outpoint.txid, outpoint.vout)?;
-                Ok(Some(local_utxo))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        match self.select_transaction_by_txid(txid)? {
-            Some(tx) => {
-                self.delete_transaction_by_txid(txid)?;
-                Ok(Some(tx))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn del_tx(
-        &mut self,
-        txid: &Txid,
-        include_raw: bool,
-    ) -> Result<Option<TransactionDetails>, Error> {
-        match self.select_transaction_details_by_txid(txid)? {
-            Some(mut transaction_details) => {
-                self.delete_transaction_details_by_txid(txid)?;
-
-                if include_raw {
-                    self.delete_transaction_by_txid(txid)?;
-                } else {
-                    transaction_details.transaction = None;
-                }
-                Ok(Some(transaction_details))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        let keychain = serde_json::to_string(&keychain)?;
-        match self.select_last_derivation_index_by_keychain(keychain.clone())? {
-            Some(value) => {
-                self.delete_last_derivation_index_by_keychain(keychain)?;
-
-                Ok(Some(value))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
-        match self.select_sync_time()? {
-            Some(value) => {
-                self.delete_sync_time()?;
-
-                Ok(Some(value))
-            }
-            None => Ok(None),
-        }
-    }
-}
-
-impl Database for SqliteDatabase {
-    fn check_descriptor_checksum<B: AsRef<[u8]>>(
-        &mut self,
-        keychain: KeychainKind,
-        bytes: B,
-    ) -> Result<(), Error> {
-        let keychain = serde_json::to_string(&keychain)?;
-
-        match self.select_checksum_by_keychain(keychain.clone())? {
-            Some(checksum) => {
-                if checksum == bytes.as_ref().to_vec() {
-                    Ok(())
-                } else {
-                    Err(Error::ChecksumMismatch)
-                }
-            }
-            None => {
-                self.insert_checksum(keychain, bytes.as_ref())?;
-                Ok(())
-            }
-        }
-    }
-
-    fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
-        match keychain {
-            Some(keychain) => {
-                let keychain = serde_json::to_string(&keychain)?;
-                self.select_script_pubkeys_by_keychain(keychain)
-            }
-            None => self.select_script_pubkeys(),
-        }
-    }
-
-    fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
-        self.select_utxos()
-    }
-
-    fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> {
-        self.select_transactions()
-    }
-
-    fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
-        match include_raw {
-            true => self.select_transaction_details_with_raw(),
-            false => self.select_transaction_details(),
-        }
-    }
-
-    fn get_script_pubkey_from_path(
-        &self,
-        keychain: KeychainKind,
-        child: u32,
-    ) -> Result<Option<Script>, Error> {
-        let keychain = serde_json::to_string(&keychain)?;
-        match self.select_script_pubkey_by_path(keychain, child)? {
-            Some(script) => Ok(Some(script)),
-            None => Ok(None),
-        }
-    }
-
-    fn get_path_from_script_pubkey(
-        &self,
-        script: &Script,
-    ) -> Result<Option<(KeychainKind, u32)>, Error> {
-        match self.select_script_pubkey_by_script(script.as_bytes())? {
-            Some((keychain, child)) => Ok(Some((keychain, child))),
-            None => Ok(None),
-        }
-    }
-
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        self.select_utxo_by_outpoint(&outpoint.txid, outpoint.vout)
-    }
-
-    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        match self.select_transaction_by_txid(txid)? {
-            Some(tx) => Ok(Some(tx)),
-            None => Ok(None),
-        }
-    }
-
-    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
-        match self.select_transaction_details_by_txid(txid)? {
-            Some(mut transaction_details) => {
-                if !include_raw {
-                    transaction_details.transaction = None;
-                }
-                Ok(Some(transaction_details))
-            }
-            None => Ok(None),
-        }
-    }
-
-    fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
-        let keychain = serde_json::to_string(&keychain)?;
-        let value = self.select_last_derivation_index_by_keychain(keychain)?;
-        Ok(value)
-    }
-
-    fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
-        self.select_sync_time()
-    }
-
-    fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
-        let keychain_string = serde_json::to_string(&keychain)?;
-        match self.get_last_index(keychain)? {
-            Some(value) => {
-                self.update_last_derivation_index(keychain_string, value + 1)?;
-                Ok(value + 1)
-            }
-            None => {
-                self.insert_last_derivation_index(keychain_string, 0)?;
-                Ok(0)
-            }
-        }
-    }
-}
-
-impl BatchDatabase for SqliteDatabase {
-    type Batch = SqliteDatabase;
-
-    fn begin_batch(&self) -> Self::Batch {
-        let db = SqliteDatabase::new(self.path.clone());
-        db.connection.execute("BEGIN TRANSACTION", []).unwrap();
-        db
-    }
-
-    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
-        batch.connection.execute("COMMIT TRANSACTION", [])?;
-        Ok(())
-    }
-}
-
-pub fn get_connection<T: AsRef<Path>>(path: &T) -> Result<Connection, Error> {
-    let mut connection = Connection::open(path)?;
-    migrate(&mut connection)?;
-    Ok(connection)
-}
-
-pub fn get_schema_version(conn: &Connection) -> rusqlite::Result<i32> {
-    let statement = conn.prepare_cached("SELECT version FROM version");
-    match statement {
-        Err(rusqlite::Error::SqliteFailure(e, Some(msg))) => {
-            if msg == "no such table: version" {
-                Ok(0)
-            } else {
-                Err(rusqlite::Error::SqliteFailure(e, Some(msg)))
-            }
-        }
-        Ok(mut stmt) => {
-            let mut rows = stmt.query([])?;
-            match rows.next()? {
-                Some(row) => {
-                    let version: i32 = row.get(0)?;
-                    Ok(version)
-                }
-                None => Ok(0),
-            }
-        }
-        _ => Ok(0),
-    }
-}
-
-pub fn set_schema_version(conn: &Connection, version: i32) -> rusqlite::Result<usize> {
-    conn.execute(
-        "UPDATE version SET version=:version",
-        named_params! {":version": version},
-    )
-}
-
-pub fn migrate(conn: &mut Connection) -> Result<(), Error> {
-    let version = get_schema_version(conn)?;
-    let stmts = &MIGRATIONS[(version as usize)..];
-
-    // begin transaction, all migration statements and new schema version commit or rollback
-    let tx = conn.transaction()?;
-
-    // execute every statement and return `Some` new schema version
-    // if execution fails, return `Error::Rusqlite`
-    // if no statements executed returns `None`
-    let new_version = stmts
-        .iter()
-        .enumerate()
-        .map(|version_stmt| {
-            log::info!(
-                "executing db migration {}: `{}`",
-                version + version_stmt.0 as i32 + 1,
-                version_stmt.1
-            );
-            tx.execute(version_stmt.1, [])
-                // map result value to next migration version
-                .map(|_| version_stmt.0 as i32 + version + 1)
-        })
-        .last()
-        .transpose()?;
-
-    // if `Some` new statement version, set new schema version
-    if let Some(version) = new_version {
-        set_schema_version(&tx, version)?;
-    } else {
-        log::info!("db up to date, no migration needed");
-    }
-
-    // commit transaction
-    tx.commit()?;
-    Ok(())
-}
-
-#[cfg(test)]
-pub mod test {
-    use crate::database::SqliteDatabase;
-    use std::time::{SystemTime, UNIX_EPOCH};
-
-    fn get_database() -> SqliteDatabase {
-        let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-        let mut dir = std::env::temp_dir();
-        dir.push(format!("bdk_{}", time.as_nanos()));
-        SqliteDatabase::new(String::from(dir.to_str().unwrap()))
-    }
-
-    #[test]
-    fn test_script_pubkey() {
-        crate::database::test::test_script_pubkey(get_database());
-    }
-
-    #[test]
-    fn test_batch_script_pubkey() {
-        crate::database::test::test_batch_script_pubkey(get_database());
-    }
-
-    #[test]
-    fn test_iter_script_pubkey() {
-        crate::database::test::test_iter_script_pubkey(get_database());
-    }
-
-    #[test]
-    fn test_del_script_pubkey() {
-        crate::database::test::test_del_script_pubkey(get_database());
-    }
-
-    #[test]
-    fn test_utxo() {
-        crate::database::test::test_utxo(get_database());
-    }
-
-    #[test]
-    fn test_raw_tx() {
-        crate::database::test::test_raw_tx(get_database());
-    }
-
-    #[test]
-    fn test_tx() {
-        crate::database::test::test_tx(get_database());
-    }
-
-    #[test]
-    fn test_last_index() {
-        crate::database::test::test_last_index(get_database());
-    }
-
-    #[test]
-    fn test_sync_time() {
-        crate::database::test::test_sync_time(get_database());
-    }
-
-    #[test]
-    fn test_txs() {
-        crate::database::test::test_list_transaction(get_database());
-    }
-
-    #[test]
-    fn test_iter_raw_txs() {
-        crate::database::test::test_iter_raw_txs(get_database());
-    }
-
-    #[test]
-    fn test_del_path_from_script_pubkey() {
-        crate::database::test::test_del_path_from_script_pubkey(get_database());
-    }
-
-    #[test]
-    fn test_iter_script_pubkeys() {
-        crate::database::test::test_iter_script_pubkeys(get_database());
-    }
-
-    #[test]
-    fn test_del_utxo() {
-        crate::database::test::test_del_utxo(get_database());
-    }
-
-    #[test]
-    fn test_del_raw_tx() {
-        crate::database::test::test_del_raw_tx(get_database());
-    }
-
-    #[test]
-    fn test_del_tx() {
-        crate::database::test::test_del_tx(get_database());
-    }
-
-    #[test]
-    fn test_del_last_index() {
-        crate::database::test::test_del_last_index(get_database());
-    }
-
-    #[test]
-    fn test_check_descriptor_checksum() {
-        crate::database::test::test_check_descriptor_checksum(get_database());
-    }
-
-    // Issue 801: https://github.com/bitcoindevkit/bdk/issues/801
-    #[test]
-    fn test_unique_spks() {
-        use crate::bitcoin::hashes::hex::FromHex;
-        use crate::database::*;
-
-        let mut db = get_database();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = 42;
-        let keychain = KeychainKind::External;
-
-        for _ in 0..100 {
-            db.set_script_pubkey(&script, keychain, path).unwrap();
-        }
-
-        let mut statement = db
-            .connection
-            .prepare_cached(
-                "select keychain,child,count(child) from script_pubkeys group by keychain,child;",
-            )
-            .unwrap();
-        let mut rows = statement.query([]).unwrap();
-        while let Some(row) = rows.next().unwrap() {
-            let keychain: String = row.get(0).unwrap();
-            let child: u32 = row.get(1).unwrap();
-            let count: usize = row.get(2).unwrap();
-
-            assert_eq!(
-                count, 1,
-                "keychain={}, child={}, count={}",
-                keychain, child, count
-            );
-        }
-    }
-}
index b307bbf1c6d0667cea436bf47441216b252f3762..dd53f9f910ca3ded91b65ace459c7a4df3a1a93c 100644 (file)
@@ -37,7 +37,9 @@ pub mod checksum;
 pub mod dsl;
 pub mod error;
 pub mod policy;
+mod spk_iter;
 pub mod template;
+pub use spk_iter::SpkIter;
 
 pub use self::checksum::calc_checksum;
 use self::checksum::calc_checksum_bytes;
@@ -353,7 +355,7 @@ where
 pub(crate) trait DescriptorMeta {
     fn is_witness(&self) -> bool;
     fn is_taproot(&self) -> bool;
-    fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
+    fn get_extended_keys(&self) -> Vec<DescriptorXKey<ExtendedPubKey>>;
     fn derive_from_hd_keypaths<'s>(
         &self,
         hd_keypaths: &HdKeyPaths,
@@ -394,7 +396,7 @@ impl DescriptorMeta for ExtendedDescriptor {
         self.desc_type() == DescriptorType::Tr
     }
 
-    fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
+    fn get_extended_keys(&self) -> Vec<DescriptorXKey<ExtendedPubKey>> {
         let mut answer = Vec::new();
 
         self.for_each_key(|pk| {
@@ -405,7 +407,7 @@ impl DescriptorMeta for ExtendedDescriptor {
             true
         });
 
-        Ok(answer)
+        answer
     }
 
     fn derive_from_psbt_key_origins<'s>(
diff --git a/src/descriptor/spk_iter.rs b/src/descriptor/spk_iter.rs
new file mode 100644 (file)
index 0000000..59dbe56
--- /dev/null
@@ -0,0 +1,63 @@
+use bitcoin::{
+    secp256k1::{Secp256k1, VerifyOnly},
+    Script,
+};
+use miniscript::{Descriptor, DescriptorPublicKey};
+
+/// An iterator over a descriptor's script pubkeys.
+///
+// TODO: put this into miniscript
+#[derive(Clone, Debug)]
+pub struct SpkIter {
+    descriptor: Descriptor<DescriptorPublicKey>,
+    index: usize,
+    secp: Secp256k1<VerifyOnly>,
+    end: usize,
+}
+
+impl SpkIter {
+    /// Creates a new script pubkey iterator starting at 0 from a descriptor
+    pub fn new(descriptor: Descriptor<DescriptorPublicKey>) -> Self {
+        let secp = Secp256k1::verification_only();
+        let end = if descriptor.has_wildcard() {
+            // Because we only iterate over non-hardened indexes there are 2^31 values
+            (1 << 31) - 1
+        } else {
+            0
+        };
+
+        Self {
+            descriptor,
+            index: 0,
+            secp,
+            end,
+        }
+    }
+}
+
+impl Iterator for SpkIter {
+    type Item = (u32, Script);
+
+    fn nth(&mut self, n: usize) -> Option<Self::Item> {
+        self.index = self.index.saturating_add(n);
+        self.next()
+    }
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let index = self.index;
+        if index > self.end {
+            return None;
+        }
+
+        let script = self
+            .descriptor
+            .at_derivation_index(self.index as u32)
+            .derived_descriptor(&self.secp)
+            .expect("the descritpor cannot need hardened derivation")
+            .script_pubkey();
+
+        self.index += 1;
+
+        Some((index as u32, script))
+    }
+}
index 060cd997112ba7d85dc09a06c305a289109a7865..996c357aec1345409a9fad87623abb2db5c507f6 100644 (file)
@@ -88,7 +88,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
 /// )?;
 ///
 /// assert_eq!(
-///     wallet.get_address(New)?.to_string(),
+///     wallet.get_address(New).to_string(),
 ///     "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
 /// );
 /// # Ok::<_, Box<dyn std::error::Error>>(())
@@ -122,7 +122,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
 /// )?;
 ///
 /// assert_eq!(
-///     wallet.get_address(New)?.to_string(),
+///     wallet.get_address(New).to_string(),
 ///     "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
 /// );
 /// # Ok::<_, Box<dyn std::error::Error>>(())
@@ -157,7 +157,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
 /// )?;
 ///
 /// assert_eq!(
-///     wallet.get_address(New)?.to_string(),
+///     wallet.get_address(New).to_string(),
 ///     "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
 /// );
 /// # Ok::<_, Box<dyn std::error::Error>>(())
@@ -194,7 +194,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_address(New)?.to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
+/// assert_eq!(wallet.get_address(New).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -234,8 +234,8 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
+/// assert_eq!(wallet.get_address(New).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@@ -273,7 +273,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_address(New)?.to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
+/// assert_eq!(wallet.get_address(New).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -313,8 +313,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
+/// assert_eq!(wallet.get_address(New).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@@ -352,7 +352,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
+/// assert_eq!(wallet.get_address(New).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -392,8 +392,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
+/// assert_eq!(wallet.get_address(New).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
index fba0fd2d7c0673f8670d343170e9c7dab08a1d2e..707a04333e1f712df85927c316b3e86bcbc393ee 100644 (file)
@@ -86,9 +86,6 @@ pub enum Error {
         /// found network, for example the network of the bitcoin node
         found: Network,
     },
-    #[cfg(feature = "verify")]
-    /// Transaction verification error
-    Verification(crate::wallet::verify::VerifyError),
 
     /// Progress value must be between `0.0` (included) and `100.0` (included)
     InvalidProgressValue(f32),
@@ -128,25 +125,6 @@ pub enum Error {
     /// [`crate::blockchain::WalletSync`] sync attempt failed due to missing scripts in cache which
     /// are needed to satisfy `stop_gap`.
     MissingCachedScripts(MissingCachedScripts),
-
-    #[cfg(feature = "electrum")]
-    /// Electrum client error
-    Electrum(electrum_client::Error),
-    #[cfg(feature = "esplora")]
-    /// Esplora client error
-    Esplora(Box<crate::blockchain::esplora::EsploraError>),
-    #[cfg(feature = "compact_filters")]
-    /// Compact filters client error)
-    CompactFilters(crate::blockchain::compact_filters::CompactFiltersError),
-    #[cfg(feature = "key-value-db")]
-    /// Sled database error
-    Sled(sled::Error),
-    #[cfg(feature = "rpc")]
-    /// Rpc client error
-    Rpc(bitcoincore_rpc::Error),
-    #[cfg(feature = "sqlite")]
-    /// Rusqlite client error
-    Rusqlite(rusqlite::Error),
 }
 
 /// Errors returned by miniscript when updating inconsistent PSBTs
@@ -310,39 +288,3 @@ impl_error!(serde_json::Error, Json);
 impl_error!(bitcoin::hashes::hex::Error, Hex);
 impl_error!(bitcoin::util::psbt::Error, Psbt);
 impl_error!(bitcoin::util::psbt::PsbtParseError, PsbtParse);
-
-#[cfg(feature = "electrum")]
-impl_error!(electrum_client::Error, Electrum);
-#[cfg(feature = "key-value-db")]
-impl_error!(sled::Error, Sled);
-#[cfg(feature = "rpc")]
-impl_error!(bitcoincore_rpc::Error, Rpc);
-#[cfg(feature = "sqlite")]
-impl_error!(rusqlite::Error, Rusqlite);
-
-#[cfg(feature = "compact_filters")]
-impl From<crate::blockchain::compact_filters::CompactFiltersError> for Error {
-    fn from(other: crate::blockchain::compact_filters::CompactFiltersError) -> Self {
-        match other {
-            crate::blockchain::compact_filters::CompactFiltersError::Global(e) => *e,
-            err => Error::CompactFilters(err),
-        }
-    }
-}
-
-#[cfg(feature = "verify")]
-impl From<crate::wallet::verify::VerifyError> for Error {
-    fn from(other: crate::wallet::verify::VerifyError) -> Self {
-        match other {
-            crate::wallet::verify::VerifyError::Global(inner) => *inner,
-            err => Error::Verification(err),
-        }
-    }
-}
-
-#[cfg(feature = "esplora")]
-impl From<crate::blockchain::esplora::EsploraError> for Error {
-    fn from(other: crate::blockchain::esplora::EsploraError) -> Self {
-        Error::Esplora(Box::new(other))
-    }
-}
index 0cdea4a529f70a5a2bbbf8f70b5682aad11bff3c..280001fc5bcfb07d98f01f007af138fd77c7341d 100644 (file)
@@ -91,9 +91,9 @@ fn main() -> Result<(), bdk::Error> {
 //!         MemoryDatabase::default(),
 //!     )?;
 //!
-//!     println!("Address #0: {}", wallet.get_address(New)?);
-//!     println!("Address #1: {}", wallet.get_address(New)?);
-//!     println!("Address #2: {}", wallet.get_address(New)?);
+//!     println!("Address #0: {}", wallet.get_address(New));
+//!     println!("Address #1: {}", wallet.get_address(New));
+//!     println!("Address #2: {}", wallet.get_address(New));
 //!
 //!     Ok(())
 //! }
@@ -124,7 +124,7 @@ fn main() -> Result<(), bdk::Error> {
 
     wallet.sync(&blockchain, SyncOptions::default())?;
 
-    let send_to = wallet.get_address(New)?;
+    let send_to = wallet.get_address(New);
     let (psbt, details) = {
         let mut builder =  wallet.build_tx();
         builder
@@ -187,83 +187,21 @@ fn main() -> Result<(), bdk::Error> {
 //! * `async-interface`: async functions in bdk traits
 //! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys
 //!
-//! # Internal features
-//!
-//! These features do not expose any new API, but influence internal implementation aspects of
-//! BDK.
-//!
-//! * `compact_filters`: [`compact_filters`](crate::blockchain::compact_filters) client protocol for interacting with the bitcoin P2P network
-//! * `electrum`: [`electrum`](crate::blockchain::electrum) client protocol for interacting with electrum servers
-//! * `esplora`: [`esplora`](crate::blockchain::esplora) client protocol for interacting with blockstream [electrs](https://github.com/Blockstream/electrs) servers
-//! * `key-value-db`: key value [`database`](crate::database) based on [`sled`](crate::sled) for caching blockchain data
 
 pub extern crate bitcoin;
+#[cfg(feature = "hardware-signer")]
+pub extern crate hwi;
 extern crate log;
 pub extern crate miniscript;
 extern crate serde;
-#[macro_use]
 extern crate serde_json;
-#[cfg(feature = "hardware-signer")]
-pub extern crate hwi;
-
-#[cfg(all(feature = "reqwest", feature = "ureq"))]
-compile_error!("Features reqwest and ureq are mutually exclusive and cannot be enabled together");
-
-#[cfg(all(feature = "async-interface", feature = "electrum"))]
-compile_error!(
-    "Features async-interface and electrum are mutually exclusive and cannot be enabled together"
-);
-
-#[cfg(all(feature = "async-interface", feature = "ureq"))]
-compile_error!(
-    "Features async-interface and ureq are mutually exclusive and cannot be enabled together"
-);
-
-#[cfg(all(feature = "async-interface", feature = "compact_filters"))]
-compile_error!(
-    "Features async-interface and compact_filters are mutually exclusive and cannot be enabled together"
-);
 
 #[cfg(feature = "keys-bip39")]
 extern crate bip39;
 
-#[cfg(feature = "async-interface")]
-#[macro_use]
-extern crate async_trait;
-#[macro_use]
-extern crate bdk_macros;
-
-#[cfg(feature = "rpc")]
-pub extern crate bitcoincore_rpc;
-
-#[cfg(feature = "electrum")]
-pub extern crate electrum_client;
-
-#[cfg(feature = "esplora")]
-pub extern crate esplora_client;
-
-#[cfg(feature = "key-value-db")]
-pub extern crate sled;
-
-#[cfg(feature = "sqlite")]
-pub extern crate rusqlite;
-
-// We should consider putting this under a feature flag but we need the macro in doctests so we need
-// to wait until https://github.com/rust-lang/rust/issues/67295 is fixed.
-//
-// Stuff in here is too rough to document atm
-#[doc(hidden)]
-#[macro_use]
-pub mod testutils;
-
-#[cfg(test)]
-extern crate assert_matches;
-
 #[allow(unused_imports)]
 #[macro_use]
 pub(crate) mod error;
-pub mod blockchain;
-pub mod database;
 pub mod descriptor;
 #[cfg(feature = "test-md-docs")]
 mod doctest;
@@ -279,10 +217,11 @@ pub use types::*;
 pub use wallet::signer;
 pub use wallet::signer::SignOptions;
 pub use wallet::tx_builder::TxBuilder;
-pub use wallet::SyncOptions;
 pub use wallet::Wallet;
 
 /// Get the version of BDK at runtime
 pub fn version() -> &'static str {
     env!("CARGO_PKG_VERSION", "unknown")
 }
+
+pub use bdk_chain as chain;
index 04aa84160018f8ee96dd48e108daf13556d20182..6f9860567e5558cc2fec263fda569226f740e080 100644 (file)
@@ -81,9 +81,9 @@ impl PsbtUtils for Psbt {
 mod test {
     use crate::bitcoin::TxIn;
     use crate::psbt::Psbt;
+    use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
     use crate::wallet::AddressIndex;
     use crate::wallet::AddressIndex::New;
-    use crate::wallet::{get_funded_wallet, test::get_test_wpkh};
     use crate::{psbt, FeeRate, SignOptions};
     use std::str::FromStr;
 
@@ -94,8 +94,8 @@ mod test {
     #[should_panic(expected = "InputIndexOutOfRange")]
     fn test_psbt_malformed_psbt_input_legacy() {
         let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let send_to = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let send_to = wallet.get_address(AddressIndex::New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(send_to.script_pubkey(), 10_000);
         let (mut psbt, _) = builder.finish().unwrap();
@@ -111,8 +111,8 @@ mod test {
     #[should_panic(expected = "InputIndexOutOfRange")]
     fn test_psbt_malformed_psbt_input_segwit() {
         let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let send_to = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let send_to = wallet.get_address(AddressIndex::New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(send_to.script_pubkey(), 10_000);
         let (mut psbt, _) = builder.finish().unwrap();
@@ -127,8 +127,8 @@ mod test {
     #[test]
     #[should_panic(expected = "InputIndexOutOfRange")]
     fn test_psbt_malformed_tx_input() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let send_to = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let send_to = wallet.get_address(AddressIndex::New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(send_to.script_pubkey(), 10_000);
         let (mut psbt, _) = builder.finish().unwrap();
@@ -143,8 +143,8 @@ mod test {
     #[test]
     fn test_psbt_sign_with_finalized() {
         let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let send_to = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let send_to = wallet.get_address(AddressIndex::New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(send_to.script_pubkey(), 10_000);
         let (mut psbt, _) = builder.finish().unwrap();
@@ -164,8 +164,8 @@ mod test {
 
         let expected_fee_rate = 1.2345;
 
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate));
@@ -189,8 +189,8 @@ mod test {
 
         let expected_fee_rate = 1.2345;
 
-        let (wallet, _, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate));
@@ -213,8 +213,8 @@ mod test {
 
         let expected_fee_rate = 1.2345;
 
-        let (wpkh_wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wpkh_wallet.get_address(New).unwrap();
+        let (mut wpkh_wallet,  _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wpkh_wallet.get_address(New);
         let mut builder = wpkh_wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate));
@@ -225,8 +225,8 @@ mod test {
         assert!(wpkh_psbt.fee_amount().is_none());
         assert!(wpkh_psbt.fee_rate().is_none());
 
-        let (pkh_wallet, _, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = pkh_wallet.get_address(New).unwrap();
+        let (mut pkh_wallet,  _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = pkh_wallet.get_address(New);
         let mut builder = pkh_wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate));
diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs
deleted file mode 100644 (file)
index a048802..0000000
+++ /dev/null
@@ -1,1480 +0,0 @@
-// Bitcoin Dev Kit
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use crate::testutils::TestIncomingTx;
-use bitcoin::consensus::encode::{deserialize, serialize};
-use bitcoin::hashes::hex::{FromHex, ToHex};
-use bitcoin::hashes::sha256d;
-use bitcoin::{Address, Amount, PackedLockTime, Script, Sequence, Transaction, Txid, Witness};
-pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
-pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
-use core::str::FromStr;
-use electrsd::bitcoind::BitcoinD;
-use electrsd::{bitcoind, ElectrsD};
-pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
-#[allow(unused_imports)]
-use log::{debug, error, info, log_enabled, trace, Level};
-use std::collections::HashMap;
-use std::env;
-use std::ops::Deref;
-use std::time::Duration;
-
-pub struct TestClient {
-    pub bitcoind: BitcoinD,
-    pub electrsd: ElectrsD,
-}
-
-impl TestClient {
-    pub fn new(bitcoind_exe: String, electrs_exe: String) -> Self {
-        debug!("launching {} and {}", &bitcoind_exe, &electrs_exe);
-
-        let mut conf = bitcoind::Conf::default();
-        conf.view_stdout = log_enabled!(Level::Debug);
-        let bitcoind = BitcoinD::with_conf(bitcoind_exe, &conf).unwrap();
-
-        let mut conf = electrsd::Conf::default();
-        conf.view_stderr = log_enabled!(Level::Debug);
-        conf.http_enabled = cfg!(feature = "test-esplora");
-
-        let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap();
-
-        let node_address = bitcoind.client.get_new_address(None, None).unwrap();
-        bitcoind
-            .client
-            .generate_to_address(101, &node_address)
-            .unwrap();
-
-        let mut test_client = TestClient { bitcoind, electrsd };
-        TestClient::wait_for_block(&mut test_client, 101);
-        test_client
-    }
-
-    fn wait_for_tx(&mut self, txid: Txid, monitor_script: &Script) {
-        // wait for electrs to index the tx
-        exponential_backoff_poll(|| {
-            self.electrsd.trigger().unwrap();
-            trace!("wait_for_tx {}", txid);
-
-            self.electrsd
-                .client
-                .script_get_history(monitor_script)
-                .unwrap()
-                .iter()
-                .position(|entry| entry.tx_hash == txid)
-        });
-    }
-
-    fn wait_for_block(&mut self, min_height: usize) {
-        self.electrsd.client.block_headers_subscribe().unwrap();
-
-        loop {
-            let header = exponential_backoff_poll(|| {
-                self.electrsd.trigger().unwrap();
-                self.electrsd.client.ping().unwrap();
-                self.electrsd.client.block_headers_pop().unwrap()
-            });
-            if header.height >= min_height {
-                break;
-            }
-        }
-    }
-
-    pub fn receive(&mut self, meta_tx: TestIncomingTx) -> Txid {
-        assert!(
-            !meta_tx.output.is_empty(),
-            "can't create a transaction with no outputs"
-        );
-
-        let mut map = HashMap::new();
-
-        let mut required_balance = 0;
-        for out in &meta_tx.output {
-            required_balance += out.value;
-            map.insert(out.to_address.clone(), Amount::from_sat(out.value));
-        }
-
-        let input: Vec<_> = meta_tx
-            .input
-            .into_iter()
-            .map(|x| x.into_raw_tx_input())
-            .collect();
-
-        if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) {
-            panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap());
-        }
-
-        // FIXME: core can't create a tx with two outputs to the same address
-        let tx = self
-            .create_raw_transaction_hex(&input, &map, meta_tx.locktime, meta_tx.replaceable)
-            .unwrap();
-        let tx = self.fund_raw_transaction(tx, None, None).unwrap();
-        let mut tx: Transaction = deserialize(&tx.hex).unwrap();
-
-        if let Some(true) = meta_tx.replaceable {
-            // for some reason core doesn't set this field right
-            for input in &mut tx.input {
-                input.sequence = Sequence(0xFFFFFFFD);
-            }
-        }
-
-        let tx = self
-            .sign_raw_transaction_with_wallet(&serialize(&tx), None, None)
-            .unwrap();
-
-        // broadcast through electrum so that it caches the tx immediately
-
-        let txid = self
-            .electrsd
-            .client
-            .transaction_broadcast(&deserialize(&tx.hex).unwrap())
-            .unwrap();
-        debug!("broadcasted to electrum {}", txid);
-
-        if let Some(num) = meta_tx.min_confirmations {
-            self.generate(num, None);
-        }
-
-        let monitor_script = Address::from_str(&meta_tx.output[0].to_address)
-            .unwrap()
-            .script_pubkey();
-        self.wait_for_tx(txid, &monitor_script);
-
-        debug!("Sent tx: {}", txid);
-
-        txid
-    }
-
-    pub fn bump_fee(&mut self, txid: &Txid) -> Txid {
-        let tx = self.get_raw_transaction_info(txid, None).unwrap();
-        assert!(
-            tx.confirmations.is_none(),
-            "Can't bump tx {} because it's already confirmed",
-            txid
-        );
-
-        let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap();
-        let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap();
-        let monitor_script = Script::from_hex(&mut tx.vout[0].script_pub_key.hex.to_hex()).unwrap();
-        self.wait_for_tx(new_txid, &monitor_script);
-
-        debug!("Bumped {}, new txid {}", txid, new_txid);
-
-        new_txid
-    }
-
-    pub fn generate_manually(&mut self, txs: Vec<Transaction>) -> String {
-        use bitcoin::blockdata::block::{Block, BlockHeader};
-        use bitcoin::blockdata::script::Builder;
-        use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
-        use bitcoin::hash_types::{BlockHash, TxMerkleNode};
-        use bitcoin::hashes::Hash;
-
-        let block_template: serde_json::Value = self
-            .call("getblocktemplate", &[json!({"rules": ["segwit"]})])
-            .unwrap();
-        trace!("getblocktemplate: {:#?}", block_template);
-
-        let header = BlockHeader {
-            version: block_template["version"].as_i64().unwrap() as i32,
-            prev_blockhash: BlockHash::from_hex(
-                block_template["previousblockhash"].as_str().unwrap(),
-            )
-            .unwrap(),
-            merkle_root: TxMerkleNode::all_zeros(),
-            time: block_template["curtime"].as_u64().unwrap() as u32,
-            bits: u32::from_str_radix(block_template["bits"].as_str().unwrap(), 16).unwrap(),
-            nonce: 0,
-        };
-        debug!("header: {:#?}", header);
-
-        let height = block_template["height"].as_u64().unwrap() as i64;
-        let witness_reserved_value: Vec<u8> = sha256d::Hash::all_zeros().as_ref().into();
-        // burn block subsidy and fees, not a big deal
-        let mut coinbase_tx = Transaction {
-            version: 1,
-            lock_time: PackedLockTime(0),
-            input: vec![TxIn {
-                previous_output: OutPoint::null(),
-                script_sig: Builder::new().push_int(height).into_script(),
-                sequence: Sequence(0xFFFFFFFF),
-                witness: Witness::from_vec(vec![witness_reserved_value]),
-            }],
-            output: vec![],
-        };
-
-        let mut txdata = vec![coinbase_tx.clone()];
-        txdata.extend_from_slice(&txs);
-
-        let mut block = Block { header, txdata };
-
-        if let Some(witness_root) = block.witness_root() {
-            let witness_commitment = Block::compute_witness_commitment(
-                &witness_root,
-                &coinbase_tx.input[0]
-                    .witness
-                    .last()
-                    .expect("Should contain the witness reserved value"),
-            );
-
-            // now update and replace the coinbase tx
-            let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
-            coinbase_witness_commitment_script.extend_from_slice(&witness_commitment);
-
-            coinbase_tx.output.push(TxOut {
-                value: 0,
-                script_pubkey: coinbase_witness_commitment_script.into(),
-            });
-        }
-
-        block.txdata[0] = coinbase_tx;
-
-        // set merkle root
-        if let Some(merkle_root) = block.compute_merkle_root() {
-            block.header.merkle_root = merkle_root;
-        }
-
-        assert!(block.check_merkle_root());
-        assert!(block.check_witness_commitment());
-
-        // now do PoW :)
-        let target = block.header.target();
-        while block.header.validate_pow(&target).is_err() {
-            block.header.nonce = block.header.nonce.checked_add(1).unwrap(); // panic if we run out of nonces
-        }
-
-        let block_hex: String = serialize(&block).to_hex();
-        debug!("generated block hex: {}", block_hex);
-
-        self.electrsd.client.block_headers_subscribe().unwrap();
-
-        let submit_result: serde_json::Value =
-            self.call("submitblock", &[block_hex.into()]).unwrap();
-        debug!("submitblock: {:?}", submit_result);
-        assert!(
-            submit_result.is_null(),
-            "submitblock error: {:?}",
-            submit_result.as_str()
-        );
-
-        self.wait_for_block(height as usize);
-
-        block.header.block_hash().to_hex()
-    }
-
-    pub fn generate(&mut self, num_blocks: u64, address: Option<Address>) {
-        let address = address.unwrap_or_else(|| self.get_new_address(None, None).unwrap());
-        let hashes = self.generate_to_address(num_blocks, &address).unwrap();
-        let best_hash = hashes.last().unwrap();
-        let height = self.get_block_info(best_hash).unwrap().height;
-
-        self.wait_for_block(height);
-
-        debug!("Generated blocks to new height {}", height);
-    }
-
-    pub fn invalidate(&mut self, num_blocks: u64) {
-        self.electrsd.client.block_headers_subscribe().unwrap();
-
-        let best_hash = self.get_best_block_hash().unwrap();
-        let initial_height = self.get_block_info(&best_hash).unwrap().height;
-
-        let mut to_invalidate = best_hash;
-        for i in 1..=num_blocks {
-            trace!(
-                "Invalidating block {}/{} ({})",
-                i,
-                num_blocks,
-                to_invalidate
-            );
-
-            self.invalidate_block(&to_invalidate).unwrap();
-            to_invalidate = self.get_best_block_hash().unwrap();
-        }
-
-        self.wait_for_block(initial_height - num_blocks as usize);
-
-        debug!(
-            "Invalidated {} blocks to new height of {}",
-            num_blocks,
-            initial_height - num_blocks as usize
-        );
-    }
-
-    pub fn reorg(&mut self, num_blocks: u64) {
-        self.invalidate(num_blocks);
-        self.generate(num_blocks, None);
-    }
-
-    pub fn get_node_address(&self, address_type: Option<AddressType>) -> Address {
-        Address::from_str(
-            &self
-                .get_new_address(None, address_type)
-                .unwrap()
-                .to_string(),
-        )
-        .unwrap()
-    }
-}
-
-pub fn get_electrum_url() -> String {
-    env::var("BDK_ELECTRUM_URL").unwrap_or_else(|_| "tcp://127.0.0.1:50001".to_string())
-}
-
-impl Deref for TestClient {
-    type Target = RpcClient;
-
-    fn deref(&self) -> &Self::Target {
-        &self.bitcoind.client
-    }
-}
-
-impl Default for TestClient {
-    fn default() -> Self {
-        let bitcoind_exe = env::var("BITCOIND_EXE")
-            .ok()
-            .or(bitcoind::downloaded_exe_path().ok())
-            .expect(
-                "you should provide env var BITCOIND_EXE or specifiy a bitcoind version feature",
-            );
-        let electrs_exe = env::var("ELECTRS_EXE")
-            .ok()
-            .or(electrsd::downloaded_exe_path())
-            .expect(
-                "you should provide env var ELECTRS_EXE or specifiy a electrsd version feature",
-            );
-        Self::new(bitcoind_exe, electrs_exe)
-    }
-}
-
-fn exponential_backoff_poll<T, F>(mut poll: F) -> T
-where
-    F: FnMut() -> Option<T>,
-{
-    let mut delay = Duration::from_millis(64);
-    loop {
-        match poll() {
-            Some(data) => break data,
-            None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
-            None => {}
-        }
-
-        std::thread::sleep(delay);
-    }
-}
-
-/// This macro runs blockchain tests against a `Blockchain` implementation. It requires access to a
-/// Bitcoin core wallet via RPC. At the moment you have to dig into the code yourself and look at
-/// the setup required to run the tests yourself.
-#[macro_export]
-macro_rules! bdk_blockchain_tests {
-    (
-     fn $_fn_name:ident ( $( $test_client:ident : &TestClient )? $(,)? ) -> $blockchain:ty $block:block) => {
-        #[cfg(test)]
-        mod bdk_blockchain_tests {
-            use $crate::bitcoin::{Transaction, Network};
-            use $crate::testutils::blockchain_tests::TestClient;
-            use $crate::blockchain::Blockchain;
-            use $crate::database::MemoryDatabase;
-            use $crate::types::KeychainKind;
-            use $crate::wallet::AddressIndex;
-            use $crate::{Wallet, FeeRate, SyncOptions};
-            use $crate::testutils;
-
-            use super::*;
-
-            #[allow(unused_variables)]
-            fn get_blockchain(test_client: &TestClient) -> $blockchain {
-                $( let $test_client = test_client; )?
-                $block
-            }
-
-            fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<MemoryDatabase> {
-                Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap()
-            }
-
-            #[allow(dead_code)]
-            enum WalletType {
-                WpkhSingleSig,
-                TaprootKeySpend,
-                TaprootScriptSpend,
-                TaprootScriptSpend2,
-                TaprootScriptSpend3,
-            }
-
-            fn init_wallet(ty: WalletType) -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
-                let _ = env_logger::try_init();
-
-                let descriptors = match ty {
-                    WalletType::WpkhSingleSig => testutils! {
-                        @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
-                    },
-                    WalletType::TaprootKeySpend => testutils! {
-                        @descriptors ( "tr(Alice)" ) ( "tr(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
-                    },
-                    WalletType::TaprootScriptSpend => testutils! {
-                        @descriptors ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( @keys ( "Key" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Script" => (@generate_xprv "/0/*", "/1/*") ) )
-                    },
-                    WalletType::TaprootScriptSpend2 => testutils! {
-                        @descriptors ( "tr(Alice,pk(Bob))" ) ( "tr(Alice,pk(Bob))" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*") ) )
-                    },
-                    WalletType::TaprootScriptSpend3 => testutils! {
-                        @descriptors ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*"), "Carol" => (@generate_xprv "/0/*", "/1/*") ) )
-                    },
-                };
-
-                let test_client = TestClient::default();
-                let blockchain = get_blockchain(&test_client);
-                let wallet = get_wallet_from_descriptors(&descriptors);
-
-                // rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
-                #[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))]
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                (wallet, blockchain, descriptors, test_client)
-            }
-
-            fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
-                init_wallet(WalletType::WpkhSingleSig)
-            }
-
-            #[test]
-            fn test_sync_simple() {
-                use std::ops::Deref;
-                use crate::database::Database;
-
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                let tx = testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                };
-                println!("{:?}", tx);
-                let txid = test_client.receive(tx);
-
-                // the RPC blockchain needs to call `sync()` during initialization to import the
-                // addresses (see `init_single_sig()`), so we skip this assertion
-                #[cfg(not(any(feature = "test-rpc", feature = "test-rpc-legacy")))]
-                assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-                assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind");
-
-                let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
-                assert_eq!(list_tx_item.txid, txid, "incorrect txid");
-                assert_eq!(list_tx_item.received, 50_000, "incorrect received");
-                assert_eq!(list_tx_item.sent, 0, "incorrect sent");
-                assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation time");
-            }
-
-            #[test]
-            fn test_sync_stop_gap_20() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 5) => 50_000 )
-                });
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 25) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 100_000, "incorrect balance");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
-            }
-
-            #[test]
-            fn test_sync_before_and_after_receive() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_total(), 0);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().confirmed, 100_000, "incorrect balance");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
-            }
-
-            #[test]
-            fn test_sync_multiple_outputs_same_tx() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                let txid = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 105_000, "incorrect balance");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
-                assert_eq!(wallet.list_unspent().unwrap().len(), 3, "incorrect number of unspents");
-
-                let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
-                assert_eq!(list_tx_item.txid, txid, "incorrect txid");
-                assert_eq!(list_tx_item.received, 105_000, "incorrect received");
-                assert_eq!(list_tx_item.sent, 0, "incorrect sent");
-                assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation_time");
-            }
-
-            #[test]
-            fn test_sync_receive_multi() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 5) => 25_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 75_000, "incorrect balance");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
-                assert_eq!(wallet.list_unspent().unwrap().len(), 2, "incorrect number of unspent");
-            }
-
-            #[test]
-            fn test_sync_address_reuse() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 25_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 75_000, "incorrect balance");
-            }
-
-            #[test]
-            fn test_sync_receive_rbf_replaced() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                let txid = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
-                assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent");
-
-                let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
-                assert_eq!(list_tx_item.txid, txid, "incorrect txid");
-                assert_eq!(list_tx_item.received, 50_000, "incorrect received");
-                assert_eq!(list_tx_item.sent, 0, "incorrect sent");
-                assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation_time");
-
-                let new_txid = test_client.bump_fee(&txid);
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance after bump");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
-                assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent after bump");
-
-                let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
-                assert_eq!(list_tx_item.txid, new_txid, "incorrect txid after bump");
-                assert_eq!(list_tx_item.received, 50_000, "incorrect received after bump");
-                assert_eq!(list_tx_item.sent, 0, "incorrect sent after bump");
-                assert_eq!(list_tx_item.confirmation_time, None, "incorrect height after bump");
-            }
-
-            // FIXME: I would like this to be cfg_attr(not(feature = "test-esplora"), ignore) but it
-            // doesn't work for some reason.
-            #[cfg(not(feature = "esplora"))]
-            #[test]
-            fn test_sync_reorg_block() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                let txid = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
-                assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents");
-
-                let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
-                assert_eq!(list_tx_item.txid, txid, "incorrect txid");
-                assert!(list_tx_item.confirmation_time.is_some(), "incorrect confirmation_time");
-
-                // Invalidate 1 block
-                test_client.invalidate(1);
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance after invalidate");
-
-                let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
-                assert_eq!(list_tx_item.txid, txid, "incorrect txid after invalidate");
-                assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation time after invalidate");
-            }
-
-            #[test]
-            fn test_sync_after_send() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                println!("{}", descriptors.0);
-                let node_addr = test_client.get_node_address(None);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey(), 25_000);
-                let (mut psbt, details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                let tx = psbt.extract_tx();
-                println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
-                blockchain.broadcast(&tx).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().trusted_pending, details.received, "incorrect balance after send");
-
-                test_client.generate(1, Some(node_addr));
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().confirmed, details.received, "incorrect balance after send");
-
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
-                assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents");
-            }
-
-            // Syncing wallet should not result in wallet address index to decrement.
-            // This is critical as we should always ensure to not reuse addresses.
-            #[test]
-            fn test_sync_address_index_should_not_decrement() {
-                let (wallet, blockchain, _descriptors, mut test_client) = init_single_sig();
-
-                const ADDRS_TO_FUND: u32 = 7;
-                const ADDRS_TO_IGNORE: u32 = 11;
-
-                let mut first_addr_index: u32 = 0;
-
-                (0..ADDRS_TO_FUND + ADDRS_TO_IGNORE).for_each(|i| {
-                    let new_addr = wallet.get_address(AddressIndex::New).unwrap();
-
-                    if i == 0 {
-                        first_addr_index = new_addr.index;
-                    }
-                    assert_eq!(new_addr.index, i+first_addr_index, "unexpected new address index (before sync)");
-
-                    if i < ADDRS_TO_FUND {
-                        test_client.receive(testutils! {
-                            @tx ((@addr new_addr.address) => 50_000)
-                        });
-                    }
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                let new_addr = wallet.get_address(AddressIndex::New).unwrap();
-                assert_eq!(new_addr.index, ADDRS_TO_FUND+ADDRS_TO_IGNORE+first_addr_index, "unexpected new address index (after sync)");
-            }
-
-            // Even if user does not explicitly grab new addresses, the address index should
-            // increment after sync (if wallet has a balance).
-            #[test]
-            fn test_sync_address_index_should_increment() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                const START_FUND: u32 = 4;
-                const END_FUND: u32 = 20;
-
-                // "secretly" fund wallet via given range
-                (START_FUND..END_FUND).for_each(|addr_index| {
-                    test_client.receive(testutils! {
-                        @tx ((@external descriptors, addr_index) => 50_000)
-                    });
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                let address = wallet.get_address(AddressIndex::New).unwrap();
-                assert_eq!(address.index, END_FUND, "unexpected new address index (after sync)");
-            }
-
-            /// Send two conflicting transactions to the same address twice in a row.
-            /// The coins should only be received once!
-            #[test]
-            fn test_sync_double_receive() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None));
-                // need to sync so rpc can start watching
-                receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).expect("sync");
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance");
-                let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
-
-                let tx1 = {
-                    let mut builder = wallet.build_tx();
-                    builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf();
-                    let (mut psbt, _details) = builder.finish().expect("building first tx");
-                    let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing first tx");
-                    assert!(finalized, "Cannot finalize transaction");
-                    psbt.extract_tx()
-                };
-
-                let tx2 = {
-                    let mut builder = wallet.build_tx();
-                    builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf().fee_rate(FeeRate::from_sat_per_vb(5.0));
-                    let (mut psbt, _details) = builder.finish().expect("building replacement tx");
-                    let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing replacement tx");
-                    assert!(finalized, "Cannot finalize transaction");
-                    psbt.extract_tx()
-                };
-
-                blockchain.broadcast(&tx1).expect("broadcasting first");
-                blockchain.broadcast(&tx2).expect("broadcasting replacement");
-                receiver_wallet.sync(&blockchain, SyncOptions::default()).expect("syncing receiver");
-                assert_eq!(receiver_wallet.get_balance().expect("balance").untrusted_pending, 49_000, "should have received coins once and only once");
-            }
-
-            #[test]
-            fn test_sync_many_sends_to_a_single_address() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                for _ in 0..4 {
-                    // split this up into multiple blocks so rpc doesn't get angry
-                    for _ in 0..20 {
-                        test_client.receive(testutils! {
-                            @tx ( (@external descriptors, 0) => 1_000 )
-                        });
-                    }
-                    test_client.generate(1, None);
-                }
-
-                // add some to the mempool as well.
-                for _ in 0..20 {
-                    test_client.receive(testutils! {
-                        @tx ( (@external descriptors, 0) => 1_000 )
-                    });
-                }
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                let balance = wallet.get_balance().unwrap();
-                assert_eq!(balance.untrusted_pending + balance.get_spendable(), 100_000);
-            }
-
-            #[test]
-            fn test_update_confirmation_time_after_generate() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                println!("{}", descriptors.0);
-                let node_addr = test_client.get_node_address(None);
-
-                let received_txid = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-
-                let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
-                let details = tx_map.get(&received_txid).unwrap();
-                assert!(details.confirmation_time.is_none());
-
-                test_client.generate(1, Some(node_addr));
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
-                let details = tx_map.get(&received_txid).unwrap();
-                assert!(details.confirmation_time.is_some());
-
-            }
-
-            #[test]
-            fn test_sync_outgoing_from_scratch() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                let node_addr = test_client.get_node_address(None);
-                let received_txid = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey(), 25_000);
-                let (mut psbt, details) = builder.finish().unwrap();
-
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                let sent_tx = psbt.extract_tx();
-                blockchain.broadcast(&sent_tx).unwrap();
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect balance after receive");
-
-                // empty wallet
-                let wallet = get_wallet_from_descriptors(&descriptors);
-
-                #[cfg(feature = "rpc")]  // rpc cannot see mempool tx before importmulti
-                test_client.generate(1, Some(node_addr));
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
-
-                let received = tx_map.get(&received_txid).unwrap();
-                assert_eq!(received.received, 50_000, "incorrect received from receiver");
-                assert_eq!(received.sent, 0, "incorrect sent from receiver");
-
-                let sent = tx_map.get(&sent_tx.txid()).unwrap();
-                assert_eq!(sent.received, details.received, "incorrect received from sender");
-                assert_eq!(sent.sent, details.sent, "incorrect sent from sender");
-                assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender");
-            }
-
-            #[test]
-            fn test_sync_long_change_chain() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                let node_addr = test_client.get_node_address(None);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-
-                let mut total_sent = 0;
-                for _ in 0..5 {
-                    let mut builder = wallet.build_tx();
-                    builder.add_recipient(node_addr.script_pubkey(), 5_000);
-                    let (mut psbt, details) = builder.finish().unwrap();
-                    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                    assert!(finalized, "Cannot finalize transaction");
-                    blockchain.broadcast(&psbt.extract_tx()).unwrap();
-
-                    wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                    total_sent += 5_000 + details.fee.unwrap_or(0);
-                }
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - total_sent, "incorrect balance after chain");
-
-                // empty wallet
-
-                let wallet = get_wallet_from_descriptors(&descriptors);
-
-                #[cfg(feature = "rpc")]  // rpc cannot see mempool tx before importmulti
-                test_client.generate(1, Some(node_addr));
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - total_sent, "incorrect balance empty wallet");
-
-            }
-
-            #[test]
-            fn test_sync_bump_fee_basic() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                let node_addr = test_client.get_node_address(None);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance");
-
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
-                let (mut psbt, details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect balance from received");
-
-                let mut builder = wallet.build_fee_bump(details.txid).unwrap();
-                builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
-                let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx");
-                let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), new_details.received, "incorrect balance from received after bump");
-
-                assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees");
-            }
-
-            #[test]
-            fn test_sync_bump_fee_remove_change() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                let node_addr = test_client.get_node_address(None);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance");
-
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
-                let (mut psbt, details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect received after send");
-
-                let mut builder = wallet.build_fee_bump(details.txid).unwrap();
-                builder.fee_rate(FeeRate::from_sat_per_vb(5.1));
-                let (mut new_psbt, new_details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 0, "incorrect balance after change removal");
-                assert_eq!(new_details.received, 0, "incorrect received after change removal");
-
-                assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees");
-            }
-
-            #[test]
-            fn test_sync_bump_fee_add_input_simple() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                let node_addr = test_client.get_node_address(None);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance");
-
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
-                let (mut psbt, details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
-                assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
-
-                let mut builder = wallet.build_fee_bump(details.txid).unwrap();
-                builder.fee_rate(FeeRate::from_sat_per_vb(10.0));
-                let (mut new_psbt, new_details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(new_details.sent, 75_000, "incorrect sent");
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), new_details.received, "incorrect balance after add input");
-            }
-
-            #[test]
-            fn test_sync_bump_fee_add_input_no_change() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                let node_addr = test_client.get_node_address(None);
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance");
-
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
-                let (mut psbt, details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
-                assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
-
-                let mut builder = wallet.build_fee_bump(details.txid).unwrap();
-                builder.fee_rate(FeeRate::from_sat_per_vb(123.0));
-                let (mut new_psbt, new_details) = builder.finish().unwrap();
-                println!("{:#?}", new_details);
-
-                let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(new_details.sent, 75_000, "incorrect sent");
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 0, "incorrect balance after add input");
-                assert_eq!(new_details.received, 0, "incorrect received after add input");
-            }
-
-
-            #[test]
-            fn test_add_data() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                                let node_addr = test_client.get_node_address(None);
-                let _ = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
-
-                let mut builder = wallet.build_tx();
-                let data = [42u8;80];
-                builder.add_data(&data);
-                let (mut psbt, details) = builder.finish().unwrap();
-
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                let tx = psbt.extract_tx();
-                let serialized_tx = bitcoin::consensus::encode::serialize(&tx);
-                assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction");
-                blockchain.broadcast(&tx).unwrap();
-                test_client.generate(1, Some(node_addr));
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
-
-                let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
-                let _ = tx_map.get(&tx.txid()).unwrap();
-            }
-
-            #[test]
-            fn test_sync_receive_coinbase() {
-                let (wallet, blockchain, _, mut test_client) = init_single_sig();
-
-                let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
-                println!("wallet addr: {}", wallet_addr);
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().immature, 0, "incorrect balance");
-
-                test_client.generate(1, Some(wallet_addr));
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert!(wallet.get_balance().unwrap().immature > 0, "incorrect balance after receiving coinbase");
-
-                // make coinbase mature (100 blocks)
-                let node_addr = test_client.get_node_address(None);
-                test_client.generate(100, Some(node_addr));
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert!(wallet.get_balance().unwrap().confirmed > 0, "incorrect balance after maturing coinbase");
-
-            }
-
-            #[test]
-            #[cfg(not(feature = "test-rpc-legacy"))]
-            fn test_send_to_bech32m_addr() {
-                use std::str::FromStr;
-                use serde;
-                use serde_json;
-                use serde::Serialize;
-                use bitcoincore_rpc::jsonrpc::serde_json::Value;
-                use bitcoincore_rpc::{Auth, Client, RpcApi};
-
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-
-                // TODO remove once rust-bitcoincore-rpc with PR 199 released
-                // https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
-                /// Import Descriptor Request
-                #[derive(Serialize, Clone, PartialEq, Eq, Debug)]
-                pub struct ImportDescriptorRequest {
-                    pub active: bool,
-                    #[serde(rename = "desc")]
-                    pub descriptor: String,
-                    pub range: [i64; 2],
-                    pub next_index: i64,
-                    pub timestamp: String,
-                    pub internal: bool,
-                }
-
-                // TODO remove once rust-bitcoincore-rpc with PR 199 released
-                impl ImportDescriptorRequest {
-                    /// Create a new Import Descriptor request providing just the descriptor and internal flags
-                    pub fn new(descriptor: &str, internal: bool) -> Self {
-                        ImportDescriptorRequest {
-                            descriptor: descriptor.to_string(),
-                            internal,
-                            active: true,
-                            range: [0, 100],
-                            next_index: 0,
-                            timestamp: "now".to_string(),
-                        }
-                    }
-                }
-
-                // 1. Create and add descriptors to a test bitcoind node taproot wallet
-
-                // TODO replace once rust-bitcoincore-rpc with PR 174 released
-                // https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/174
-                let _createwallet_result: Value = test_client.bitcoind.client.call("createwallet", &["taproot_wallet".into(),false.into(),true.into(),serde_json::to_value("").unwrap(), false.into(), true.into()]).unwrap();
-
-                // TODO replace once bitcoind released with support for rust-bitcoincore-rpc PR 174
-                let taproot_wallet_client = Client::new(&test_client.bitcoind.rpc_url_with_wallet("taproot_wallet"), Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone())).unwrap();
-
-                let wallet_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/0/*)#y283ssmn";
-                let change_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/1/*)#47zsd9tt";
-
-                let tr_descriptors = vec![
-                            ImportDescriptorRequest::new(wallet_descriptor, false),
-                            ImportDescriptorRequest::new(change_descriptor, false),
-                        ];
-
-                // TODO replace once rust-bitcoincore-rpc with PR 199 released
-                let _import_result: Value = taproot_wallet_client.call("importdescriptors", &[serde_json::to_value(tr_descriptors).unwrap()]).unwrap();
-
-                // 2. Get a new bech32m address from test bitcoind node taproot wallet
-
-                // TODO replace once rust-bitcoincore-rpc with PR 199 released
-                let node_addr: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap();
-                assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap());
-
-                // 3. Send 50_000 sats from test bitcoind node to test BDK wallet
-
-                test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "wallet has incorrect balance");
-
-                // 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
-
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey(), 25_000);
-                let (mut psbt, details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "wallet cannot finalize transaction");
-                let tx = psbt.extract_tx();
-                blockchain.broadcast(&tx).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "wallet has incorrect balance after send");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs");
-                assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
-                test_client.generate(1, None);
-
-                // 5. Verify 25_000 sats are received by test bitcoind node taproot wallet
-
-                let taproot_balance = taproot_wallet_client.get_balance(None, None).unwrap();
-                assert_eq!(taproot_balance.to_sat(), 25_000, "node has incorrect taproot wallet balance");
-            }
-
-            #[test]
-            fn test_tx_chain() {
-                use bitcoincore_rpc::RpcApi;
-                use bitcoin::consensus::encode::deserialize;
-                use $crate::wallet::AddressIndex;
-
-                // Here we want to test that we set correctly the send and receive
-                // fields in the transaction object. For doing so, we create two
-                // different txs, the second one spending from the first:
-                // 1.
-                // Core (#1) -> Core (#2)
-                //           -> Us   (#3)
-                // 2.
-                // Core (#2) -> Us   (#4)
-
-                let (wallet, blockchain, _, mut test_client) = init_single_sig();
-                let bdk_address = wallet.get_address(AddressIndex::New).unwrap().address;
-                let core_address = test_client.get_new_address(None, None).unwrap();
-                let tx = testutils! {
-                    @tx ( (@addr bdk_address.clone()) => 50_000, (@addr core_address.clone()) => 40_000 )
-                };
-
-                // Tx one: from Core #1 to Core #2 and Us #3.
-                let txid_1 = test_client.receive(tx);
-                let tx_1: Transaction = deserialize(&test_client.get_transaction(&txid_1, None).unwrap().hex).unwrap();
-                let vout_1 = tx_1.output.into_iter().position(|o| o.script_pubkey == core_address.script_pubkey()).unwrap() as u32;
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                let tx_1 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_1).unwrap();
-                assert_eq!(tx_1.received, 50_000);
-                assert_eq!(tx_1.sent, 0);
-
-                // Tx two: from Core #2 to Us #4.
-                let tx = testutils! {
-                    @tx ( (@addr bdk_address) => 10_000 ) ( @inputs (txid_1,vout_1))
-                };
-                let txid_2 = test_client.receive(tx);
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                let tx_2 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_2).unwrap();
-                assert_eq!(tx_2.received, 10_000);
-                assert_eq!(tx_2.sent, 0);
-            }
-
-            #[test]
-            fn test_double_spend() {
-                // We create a tx and then we try to double spend it; BDK will always allow
-                // us to do so, as it never forgets about spent UTXOs
-                let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
-                let node_addr = test_client.get_node_address(None);
-                let _ = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                let mut builder = wallet.build_tx();
-                builder.add_recipient(node_addr.script_pubkey(), 25_000);
-                let (mut psbt, _details) = builder.finish().unwrap();
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert!(finalized, "Cannot finalize transaction");
-                let initial_tx = psbt.extract_tx();
-                let _sent_txid = blockchain.broadcast(&initial_tx).unwrap();
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                for utxo in wallet.list_unspent().unwrap() {
-                    // Making sure the TXO we just spent is not returned by list_unspent
-                    assert!(utxo.outpoint != initial_tx.input[0].previous_output, "wallet displays spent txo in unspents");
-                }
-                // We can still create a transaction double spending `initial_tx`
-                let mut builder = wallet.build_tx();
-                builder
-                    .add_utxo(initial_tx.input[0].previous_output)
-                    .expect("Can't manually add an UTXO spent");
-                test_client.generate(1, Some(node_addr));
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                // Even after confirmation, we can still create a tx double spend it
-                let mut builder = wallet.build_tx();
-                builder
-                    .add_utxo(initial_tx.input[0].previous_output)
-                    .expect("Can't manually add an UTXO spent");
-                for utxo in wallet.list_unspent().unwrap() {
-                    // Making sure the TXO we just spent is not returned by list_unspent
-                    assert!(utxo.outpoint != initial_tx.input[0].previous_output, "wallet displays spent txo in unspents");
-                }
-            }
-
-            #[test]
-            fn test_send_receive_pkh() {
-                let descriptors = ("pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None);
-                let mut test_client = TestClient::default();
-                let blockchain = get_blockchain(&test_client);
-
-                let wallet = get_wallet_from_descriptors(&descriptors);
-                #[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))]
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                let _ = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
-
-                let tx = {
-                    let mut builder = wallet.build_tx();
-                    builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000);
-                    let (mut psbt, _details) = builder.finish().unwrap();
-                    wallet.sign(&mut psbt, Default::default()).unwrap();
-                    psbt.extract_tx()
-                };
-                blockchain.broadcast(&tx).unwrap();
-
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-            }
-
-            #[test]
-            #[cfg(not(feature = "test-rpc-legacy"))]
-            fn test_taproot_key_spend() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_wallet(WalletType::TaprootKeySpend);
-
-                let _ = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 )
-                });
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
-
-                let tx = {
-                    let mut builder = wallet.build_tx();
-                    builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000);
-                    let (mut psbt, _details) = builder.finish().unwrap();
-                    wallet.sign(&mut psbt, Default::default()).unwrap();
-                    psbt.extract_tx()
-                };
-                blockchain.broadcast(&tx).unwrap();
-            }
-
-            #[test]
-            #[cfg(not(feature = "test-rpc-legacy"))]
-            fn test_taproot_script_spend() {
-                let (wallet, blockchain, descriptors, mut test_client) = init_wallet(WalletType::TaprootScriptSpend);
-
-                let _ = test_client.receive(testutils! {
-                    @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 6 )
-                });
-                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000);
-
-                let ext_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
-                let int_policy = wallet.policies(KeychainKind::Internal).unwrap().unwrap();
-
-                let ext_path = vec![(ext_policy.id.clone(), vec![1])].into_iter().collect();
-                let int_path = vec![(int_policy.id.clone(), vec![1])].into_iter().collect();
-
-                let tx = {
-                    let mut builder = wallet.build_tx();
-                    builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000)
-                        .policy_path(ext_path, KeychainKind::External)
-                        .policy_path(int_path, KeychainKind::Internal);
-                    let (mut psbt, _details) = builder.finish().unwrap();
-                    wallet.sign(&mut psbt, Default::default()).unwrap();
-                    psbt.extract_tx()
-                };
-                blockchain.broadcast(&tx).unwrap();
-            }
-
-            #[test]
-            #[cfg(not(feature = "test-rpc-legacy"))]
-            fn test_sign_taproot_core_keyspend_psbt() {
-                test_sign_taproot_core_psbt(WalletType::TaprootKeySpend);
-            }
-
-            #[test]
-            #[cfg(not(feature = "test-rpc-legacy"))]
-            fn test_sign_taproot_core_scriptspend2_psbt() {
-                test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend2);
-            }
-
-            #[test]
-            #[cfg(not(feature = "test-rpc-legacy"))]
-            fn test_sign_taproot_core_scriptspend3_psbt() {
-                test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend3);
-            }
-
-            #[cfg(not(feature = "test-rpc-legacy"))]
-            fn test_sign_taproot_core_psbt(wallet_type: WalletType) {
-                use std::str::FromStr;
-                use serde_json;
-                use bitcoincore_rpc::jsonrpc::serde_json::Value;
-                use bitcoincore_rpc::{Auth, Client, RpcApi};
-
-                let (wallet, _blockchain, _descriptors, test_client) = init_wallet(wallet_type);
-
-                // TODO replace once rust-bitcoincore-rpc with PR 174 released
-                // https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/174
-                let _createwallet_result: Value = test_client.bitcoind.client.call("createwallet", &["taproot_wallet".into(), true.into(), true.into(), serde_json::to_value("").unwrap(), false.into(), true.into(), true.into(), false.into()]).expect("created wallet");
-
-                let external_descriptor = wallet.get_descriptor_for_keychain(KeychainKind::External);
-
-                // TODO replace once bitcoind released with support for rust-bitcoincore-rpc PR 174
-                let taproot_wallet_client = Client::new(&test_client.bitcoind.rpc_url_with_wallet("taproot_wallet"), Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone())).unwrap();
-
-                let descriptor_info = taproot_wallet_client.get_descriptor_info(external_descriptor.to_string().as_str()).expect("descriptor info");
-
-                let import_descriptor_args = json!([{
-                    "desc": descriptor_info.descriptor,
-                    "active": true,
-                    "timestamp": "now",
-                    "label":"taproot key spend",
-                }]);
-                let _importdescriptors_result: Value = taproot_wallet_client.call("importdescriptors", &[import_descriptor_args]).expect("import wallet");
-                let generate_to_address: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).expect("new address");
-                let _generatetoaddress_result = taproot_wallet_client.generate_to_address(101, &generate_to_address).expect("generated to address");
-                let send_to_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string();
-                let change_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string();
-                let send_addr_amounts = json!([{
-                    send_to_address: "0.4321"
-                }]);
-                let send_options = json!({
-                    "change_address": change_address,
-                    "psbt": true,
-                });
-                let send_result: Value = taproot_wallet_client.call("send", &[send_addr_amounts, Value::Null, "unset".into(), Value::Null, send_options]).expect("send psbt");
-                let core_psbt = send_result["psbt"].as_str().expect("core psbt str");
-
-                use bitcoin::util::psbt::PartiallySignedTransaction;
-
-                // Test parsing core created PSBT
-                let mut psbt = PartiallySignedTransaction::from_str(&core_psbt).expect("core taproot psbt");
-
-                // Test signing core created PSBT
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert_eq!(finalized, true);
-
-                // Test with updated psbt
-                let update_result: Value = taproot_wallet_client.call("utxoupdatepsbt", &[core_psbt.into()]).expect("update psbt utxos");
-                let core_updated_psbt = update_result.as_str().expect("core updated psbt");
-
-                // Test parsing core created and updated PSBT
-                let mut psbt = PartiallySignedTransaction::from_str(&core_updated_psbt).expect("core taproot psbt");
-
-                // Test signing core created and updated PSBT
-                let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-                assert_eq!(finalized, true);
-            }
-
-            #[test]
-            fn test_get_block_hash() {
-                use bitcoincore_rpc::{ RpcApi };
-                use crate::blockchain::GetBlockHash;
-
-                // create wallet with init_wallet
-                let (_, blockchain, _descriptors, mut test_client) = init_single_sig();
-
-                let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64;
-                let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap();
-
-                // use get_block_hash to get best block hash and compare with best_hash above
-                let block_hash = blockchain.get_block_hash(height).unwrap();
-                assert_eq!(best_hash, block_hash);
-
-                // generate blocks to address
-                let node_addr = test_client.get_node_address(None);
-                test_client.generate(10, Some(node_addr));
-
-                let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64;
-                let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap();
-
-                let block_hash = blockchain.get_block_hash(height).unwrap();
-                assert_eq!(best_hash, block_hash);
-
-                // try to get hash for block that has not yet been created.
-                assert!(blockchain.get_block_hash(height + 1).is_err());
-            }
-        }
-    };
-
-    ( fn $fn_name:ident ($( $tt:tt )+) -> $blockchain:ty $block:block) => {
-        compile_error!(concat!("Invalid arguments `", stringify!($($tt)*), "` in the blockchain tests fn."));
-        compile_error!("Only the exact `&TestClient` type is supported, **without** any leading path items.");
-    };
-}
diff --git a/src/testutils/configurable_blockchain_tests.rs b/src/testutils/configurable_blockchain_tests.rs
deleted file mode 100644 (file)
index 423c55d..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-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 available 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);
-            test_wallet_sync_fulfills_missing_script_cache(test_client, self);
-            test_wallet_sync_self_transfer_tx(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
-        };
-        let details = format!(
-            "test_vector: [stop_gap: {}, actual_gap: {}, addrs_before: {}, addrs_after: {}]",
-            stop_gap, actual_gap, addrs_before, addrs_after,
-        );
-        println!("{}", details);
-
-        // perform wallet sync
-        wallet.sync(&blockchain, Default::default()).unwrap();
-
-        let wallet_balance = wallet.get_balance().unwrap().get_total();
-        println!(
-            "max: {}, min: {}, actual: {}",
-            max_balance, min_balance, wallet_balance
-        );
-
-        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);
-    }
-}
-
-/// With a `stop_gap` of x and every x addresses having a balance of 1000 (for y addresses),
-/// we expect `Wallet::sync` to correctly self-cache addresses, so that the resulting balance,
-/// after sync, should be y * 1000.
-fn test_wallet_sync_fulfills_missing_script_cache<T, B>(test_client: &mut TestClient, tester: &T)
-where
-    T: ConfigurableBlockchainTester<B>,
-    B: ConfigurableBlockchain,
-{
-    // wallet descriptor
-    let descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/200/*)";
-
-    // amount in sats per tx
-    const AMOUNT_PER_TX: u64 = 1000;
-
-    // addr constants
-    const ADDR_COUNT: usize = 6;
-    const ADDR_GAP: usize = 60;
-
-    let blockchain =
-        B::from_config(&tester.config_with_stop_gap(test_client, ADDR_GAP).unwrap()).unwrap();
-
-    let wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new()).unwrap();
-
-    let expected_balance = (0..ADDR_COUNT).fold(0_u64, |sum, i| {
-        let addr_i = i * ADDR_GAP;
-        let address = wallet.get_address(AddressIndex::Peek(addr_i as _)).unwrap();
-
-        println!(
-            "tx: {} sats => [{}] {}",
-            AMOUNT_PER_TX,
-            addr_i,
-            address.to_string()
-        );
-
-        test_client.receive(testutils! {
-            @tx ( (@addr address.address) => AMOUNT_PER_TX )
-        });
-        test_client.generate(1, None);
-
-        sum + AMOUNT_PER_TX
-    });
-    println!("expected balance: {}, syncing...", expected_balance);
-
-    // perform sync
-    wallet.sync(&blockchain, Default::default()).unwrap();
-    println!("sync done!");
-
-    let balance = wallet.get_balance().unwrap().get_total();
-    assert_eq!(balance, expected_balance);
-}
-
-/// Given a `stop_gap`, a wallet with a 2 transactions, one sending to `scriptPubKey` at derivation
-/// index of `stop_gap`, and the other spending from the same `scriptPubKey` into another
-/// `scriptPubKey` at derivation index of `stop_gap * 2`, we expect `Wallet::sync` to perform
-/// correctly, so that we detect the total balance.
-fn test_wallet_sync_self_transfer_tx<T, B>(test_client: &mut TestClient, tester: &T)
-where
-    T: ConfigurableBlockchainTester<B>,
-    B: ConfigurableBlockchain,
-{
-    const TRANSFER_AMOUNT: u64 = 10_000;
-    const STOP_GAP: usize = 75;
-
-    let descriptor = "wpkh(tprv8i8F4EhYDMquzqiecEX8SKYMXqfmmb1Sm7deoA1Hokxzn281XgTkwsd6gL8aJevLE4aJugfVf9MKMvrcRvPawGMenqMBA3bRRfp4s1V7Eg3/*)";
-
-    let blockchain =
-        B::from_config(&tester.config_with_stop_gap(test_client, STOP_GAP).unwrap()).unwrap();
-
-    let wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new()).unwrap();
-
-    let address1 = wallet
-        .get_address(AddressIndex::Peek(STOP_GAP as _))
-        .unwrap();
-    let address2 = wallet
-        .get_address(AddressIndex::Peek((STOP_GAP * 2) as _))
-        .unwrap();
-
-    test_client.receive(testutils! {
-        @tx ( (@addr address1.address) => TRANSFER_AMOUNT )
-    });
-    test_client.generate(1, None);
-
-    wallet.sync(&blockchain, Default::default()).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(address2.script_pubkey(), TRANSFER_AMOUNT / 2);
-    let (mut psbt, details) = builder.finish().unwrap();
-    assert!(wallet.sign(&mut psbt, Default::default()).unwrap());
-    blockchain.broadcast(&psbt.extract_tx()).unwrap();
-
-    test_client.generate(1, None);
-
-    // obtain what is expected
-    let fee = details.fee.unwrap();
-    let expected_balance = TRANSFER_AMOUNT - fee;
-    println!("fee={}, expected_balance={}", fee, expected_balance);
-
-    // actually test the wallet
-    wallet.sync(&blockchain, Default::default()).unwrap();
-    let balance = wallet.get_balance().unwrap().get_total();
-    assert_eq!(balance, expected_balance);
-
-    // now try with a fresh wallet
-    let fresh_wallet =
-        Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new()).unwrap();
-    fresh_wallet.sync(&blockchain, Default::default()).unwrap();
-    let fresh_balance = fresh_wallet.get_balance().unwrap().get_total();
-    assert_eq!(fresh_balance, expected_balance);
-}
diff --git a/src/testutils/mod.rs b/src/testutils/mod.rs
deleted file mode 100644 (file)
index 32f3951..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-#![allow(missing_docs)]
-
-#[cfg(test)]
-#[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)]
-pub struct TestIncomingInput {
-    pub txid: Txid,
-    pub vout: u32,
-    pub sequence: Option<u32>,
-}
-
-impl TestIncomingInput {
-    pub fn new(txid: Txid, vout: u32, sequence: Option<u32>) -> Self {
-        Self {
-            txid,
-            vout,
-            sequence,
-        }
-    }
-
-    #[cfg(feature = "test-blockchains")]
-    pub fn into_raw_tx_input(self) -> bitcoincore_rpc::json::CreateRawTransactionInput {
-        bitcoincore_rpc::json::CreateRawTransactionInput {
-            txid: self.txid,
-            vout: self.vout,
-            sequence: self.sequence,
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct TestIncomingOutput {
-    pub value: u64,
-    pub to_address: String,
-}
-
-impl TestIncomingOutput {
-    pub fn new(value: u64, to_address: Address) -> Self {
-        Self {
-            value,
-            to_address: to_address.to_string(),
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct TestIncomingTx {
-    pub input: Vec<TestIncomingInput>,
-    pub output: Vec<TestIncomingOutput>,
-    pub min_confirmations: Option<u64>,
-    pub locktime: Option<i64>,
-    pub replaceable: Option<bool>,
-}
-
-impl TestIncomingTx {
-    pub fn new(
-        input: Vec<TestIncomingInput>,
-        output: Vec<TestIncomingOutput>,
-        min_confirmations: Option<u64>,
-        locktime: Option<i64>,
-        replaceable: Option<bool>,
-    ) -> Self {
-        Self {
-            input,
-            output,
-            min_confirmations,
-            locktime,
-            replaceable,
-        }
-    }
-
-    pub fn add_input(&mut self, input: TestIncomingInput) {
-        self.input.push(input);
-    }
-
-    pub fn add_output(&mut self, output: TestIncomingOutput) {
-        self.output.push(output);
-    }
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! testutils {
-    ( @external $descriptors:expr, $child:expr ) => ({
-        use $crate::bitcoin::secp256k1::Secp256k1;
-        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
-
-        let secp = Secp256k1::new();
-
-        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
-        parsed.at_derivation_index($child).address(bitcoin::Network::Regtest).expect("No address form")
-    });
-    ( @internal $descriptors:expr, $child:expr ) => ({
-        use $crate::bitcoin::secp256k1::Secp256k1;
-        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
-
-        let secp = Secp256k1::new();
-
-        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
-        parsed.at_derivation_index($child).address($crate::bitcoin::Network::Regtest).expect("No address form")
-    });
-    ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
-    ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
-    ( @addr $addr:expr ) => ({ $addr });
-
-    ( @tx ( $( ( $( $addr:tt )* ) => $amount:expr ),+ ) $( ( @inputs $( ($txid:expr, $vout:expr) ),+ ) )? $( ( @locktime $locktime:expr ) )? $( ( @confirmations $confirmations:expr ) )? $( ( @replaceable $replaceable:expr ) )? ) => ({
-        let outs = vec![$( $crate::testutils::TestIncomingOutput::new($amount, testutils!( $($addr)* ))),+];
-        let _ins: Vec<$crate::testutils::TestIncomingInput> = vec![];
-        $(
-            let _ins = vec![$( $crate::testutils::TestIncomingInput { txid: $txid, vout: $vout, sequence: None }),+];
-        )?
-
-        let locktime = None::<i64>$(.or(Some($locktime)))?;
-
-        let min_confirmations = None::<u64>$(.or(Some($confirmations)))?;
-        let replaceable = None::<bool>$(.or(Some($replaceable)))?;
-
-        $crate::testutils::TestIncomingTx::new(_ins, outs, min_confirmations, locktime, replaceable)
-    });
-
-    ( @literal $key:expr ) => ({
-        let key = $key.to_string();
-        (key, None::<String>, None::<String>)
-    });
-    ( @generate_xprv $( $external_path:expr )? $( ,$internal_path:expr )? ) => ({
-        use rand::Rng;
-
-        let mut seed = [0u8; 32];
-        rand::thread_rng().fill(&mut seed[..]);
-
-        let key = $crate::bitcoin::util::bip32::ExtendedPrivKey::new_master(
-            $crate::bitcoin::Network::Testnet,
-            &seed,
-        );
-
-        let external_path = None::<String>$(.or(Some($external_path.to_string())))?;
-        let internal_path = None::<String>$(.or(Some($internal_path.to_string())))?;
-
-        (key.unwrap().to_string(), external_path, internal_path)
-    });
-    ( @generate_wif ) => ({
-        use rand::Rng;
-
-        let mut key = [0u8; $crate::bitcoin::secp256k1::constants::SECRET_KEY_SIZE];
-        rand::thread_rng().fill(&mut key[..]);
-
-        ($crate::bitcoin::PrivateKey {
-            compressed: true,
-            network: $crate::bitcoin::Network::Testnet,
-            key: $crate::bitcoin::secp256k1::SecretKey::from_slice(&key).unwrap(),
-        }.to_string(), None::<String>, None::<String>)
-    });
-
-    ( @keys ( $( $alias:expr => ( $( $key_type:tt )* ) ),+ ) ) => ({
-        let mut map = std::collections::HashMap::new();
-        $(
-            let alias: &str = $alias;
-            map.insert(alias, testutils!( $($key_type)* ));
-        )+
-
-        map
-    });
-
-    ( @descriptors ( $external_descriptor:expr ) $( ( $internal_descriptor:expr ) )? $( ( @keys $( $keys:tt )* ) )* ) => ({
-        use std::str::FromStr;
-        use std::collections::HashMap;
-        use std::convert::Infallible;
-
-        use $crate::miniscript::descriptor::Descriptor;
-        use $crate::miniscript::TranslatePk;
-
-        struct Translator {
-            keys: HashMap<&'static str, (String, Option<String>, Option<String>)>,
-            is_internal: bool,
-        }
-
-        impl $crate::miniscript::Translator<String, String, Infallible> for Translator {
-            fn pk(&mut self, pk: &String) -> Result<String, Infallible> {
-                match self.keys.get(pk.as_str()) {
-                    Some((key, ext_path, int_path)) => {
-                        let path = if self.is_internal { int_path } else { ext_path };
-                        Ok(format!("{}{}", key, path.clone().unwrap_or_default()))
-                    }
-                    None => Ok(pk.clone()),
-                }
-            }
-            fn sha256(&mut self, sha256: &String) -> Result<String, Infallible> { Ok(sha256.clone()) }
-            fn hash256(&mut self, hash256: &String) -> Result<String, Infallible> { Ok(hash256.clone()) }
-            fn ripemd160(&mut self, ripemd160: &String) -> Result<String, Infallible> { Ok(ripemd160.clone()) }
-            fn hash160(&mut self, hash160: &String) -> Result<String, Infallible> { Ok(hash160.clone()) }
-        }
-
-        #[allow(unused_assignments, unused_mut)]
-        let mut keys = HashMap::new();
-        $(
-            keys = testutils!{ @keys $( $keys )* };
-        )*
-
-        let mut translator = Translator { keys, is_internal: false };
-
-        let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap();
-        let external = external.translate_pk(&mut translator).expect("Infallible conversion");
-        let external = external.to_string();
-
-        translator.is_internal = true;
-
-        let internal = None::<String>$(.or({
-            let internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
-            let internal = internal.translate_pk(&mut translator).expect("Infallible conversion");
-            Some(internal.to_string())
-        }))?;
-
-        (external, internal)
-    })
-}
index cd2ad5b36825b47195025ddb9653ef65861bede4..47d1c2b8e3366d10cc292d723fdb819420e882c6 100644 (file)
 use std::convert::AsRef;
 use std::ops::Sub;
 
+use bdk_chain::ConfirmationTime;
 use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
 use bitcoin::{hash_types::Txid, util::psbt};
 
 use serde::{Deserialize, Serialize};
 
 /// Types of keychains
-#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
 pub enum KeychainKind {
     /// External
     External = 0,
@@ -163,6 +164,10 @@ pub struct LocalUtxo {
     pub keychain: KeychainKind,
     /// Whether this UTXO is spent or not
     pub is_spent: bool,
+    /// The derivation index for the script pubkey in the wallet
+    pub derivation_index: u32,
+    /// The confirmation time for transaction containing this utxo
+    pub confirmation_time: ConfirmationTime,
 }
 
 /// A [`Utxo`] with its `satisfaction_weight`.
@@ -236,14 +241,11 @@ pub struct TransactionDetails {
     /// Sent value (sats)
     /// Sum of owned inputs of this transaction.
     pub sent: u64,
-    /// Fee value (sats) if confirmed.
-    /// The availability of the fee depends on the backend. It's never `None` with an Electrum
-    /// Server backend, but it could be `None` with a Bitcoin RPC node without txindex that receive
-    /// funds while offline.
+    /// Fee value in sats if it was available.
     pub fee: Option<u64>,
     /// If the transaction is confirmed, contains height and Unix timestamp of the block containing the
     /// transaction, unconfirmed transaction contains `None`.
-    pub confirmation_time: Option<BlockTime>,
+    pub confirmation_time: ConfirmationTime,
 }
 
 impl PartialOrd for TransactionDetails {
@@ -260,45 +262,6 @@ impl Ord for TransactionDetails {
     }
 }
 
-/// Block height and timestamp of a block
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
-pub struct BlockTime {
-    /// confirmation block height
-    pub height: u32,
-    /// confirmation block timestamp
-    pub timestamp: u64,
-}
-
-impl PartialOrd for BlockTime {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for BlockTime {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.height
-            .cmp(&other.height)
-            .then_with(|| self.timestamp.cmp(&other.timestamp))
-    }
-}
-
-/// **DEPRECATED**: Confirmation time of a transaction
-///
-/// The structure has been renamed to `BlockTime`
-#[deprecated(note = "This structure has been renamed to `BlockTime`")]
-pub type ConfirmationTime = BlockTime;
-
-impl BlockTime {
-    /// Returns `Some` `BlockTime` if both `height` and `timestamp` are `Some`
-    pub fn new(height: Option<u32>, timestamp: Option<u64>) -> Option<Self> {
-        match (height, timestamp) {
-            (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
-            _ => None,
-        }
-    }
-}
-
 /// Balance differentiated in various categories
 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
 pub struct Balance {
@@ -361,95 +324,6 @@ impl std::iter::Sum for Balance {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use bitcoin::hashes::Hash;
-
-    #[test]
-    fn sort_block_time() {
-        let block_time_a = BlockTime {
-            height: 100,
-            timestamp: 100,
-        };
-
-        let block_time_b = BlockTime {
-            height: 100,
-            timestamp: 110,
-        };
-
-        let block_time_c = BlockTime {
-            height: 0,
-            timestamp: 0,
-        };
-
-        let mut vec = vec![
-            block_time_a.clone(),
-            block_time_b.clone(),
-            block_time_c.clone(),
-        ];
-        vec.sort();
-        let expected = vec![block_time_c, block_time_a, block_time_b];
-
-        assert_eq!(vec, expected)
-    }
-
-    #[test]
-    fn sort_tx_details() {
-        let block_time_a = BlockTime {
-            height: 100,
-            timestamp: 100,
-        };
-
-        let block_time_b = BlockTime {
-            height: 0,
-            timestamp: 0,
-        };
-
-        let tx_details_a = TransactionDetails {
-            transaction: None,
-            txid: Txid::from_inner([0; 32]),
-            received: 0,
-            sent: 0,
-            fee: None,
-            confirmation_time: None,
-        };
-
-        let tx_details_b = TransactionDetails {
-            transaction: None,
-            txid: Txid::from_inner([0; 32]),
-            received: 0,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(block_time_a),
-        };
-
-        let tx_details_c = TransactionDetails {
-            transaction: None,
-            txid: Txid::from_inner([0; 32]),
-            received: 0,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(block_time_b.clone()),
-        };
-
-        let tx_details_d = TransactionDetails {
-            transaction: None,
-            txid: Txid::from_inner([1; 32]),
-            received: 0,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(block_time_b),
-        };
-
-        let mut vec = vec![
-            tx_details_a.clone(),
-            tx_details_b.clone(),
-            tx_details_c.clone(),
-            tx_details_d.clone(),
-        ];
-        vec.sort();
-        let expected = vec![tx_details_a, tx_details_c, tx_details_d, tx_details_b];
-
-        assert_eq!(vec, expected)
-    }
 
     #[test]
     fn can_store_feerate_in_const() {
index 8482f96a9de20fdc8d11f3d9b577750d1a6bdcaf..7f2861cbac40c193b73260c7e576430930497e93 100644 (file)
@@ -34,7 +34,7 @@
 //! #[derive(Debug)]
 //! struct AlwaysSpendEverything;
 //!
-//! impl<D: Database> CoinSelectionAlgorithm<D> for AlwaysSpendEverything {
+//! impl CoinSelectionAlgorithm for AlwaysSpendEverything {
 //!     fn coin_select(
 //!         &self,
 //!         database: &D,
@@ -96,7 +96,7 @@
 
 use crate::types::FeeRate;
 use crate::wallet::utils::IsDust;
-use crate::{database::Database, WeightedUtxo};
+use crate::WeightedUtxo;
 use crate::{error::Error, Utxo};
 
 use bitcoin::consensus::encode::serialize;
@@ -107,7 +107,7 @@ use assert_matches::assert_matches;
 use rand::seq::SliceRandom;
 #[cfg(not(test))]
 use rand::thread_rng;
-use std::collections::HashMap;
+#[cfg(test)]
 use std::convert::TryInto;
 
 /// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
@@ -177,7 +177,7 @@ impl CoinSelectionResult {
 /// selection algorithm when it creates transactions.
 ///
 /// For an example see [this module](crate::wallet::coin_selection)'s documentation.
-pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
+pub trait CoinSelectionAlgorithm: std::fmt::Debug {
     /// Perform the coin selection
     ///
     /// - `database`: a reference to the wallet's database that can be used to lookup additional
@@ -193,7 +193,6 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
     #[allow(clippy::too_many_arguments)]
     fn coin_select(
         &self,
-        database: &D,
         required_utxos: Vec<WeightedUtxo>,
         optional_utxos: Vec<WeightedUtxo>,
         fee_rate: FeeRate,
@@ -209,10 +208,9 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
 #[derive(Debug, Default, Clone, Copy)]
 pub struct LargestFirstCoinSelection;
 
-impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
+impl CoinSelectionAlgorithm for LargestFirstCoinSelection {
     fn coin_select(
         &self,
-        _database: &D,
         required_utxos: Vec<WeightedUtxo>,
         mut optional_utxos: Vec<WeightedUtxo>,
         fee_rate: FeeRate,
@@ -246,46 +244,22 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
 #[derive(Debug, Default, Clone, Copy)]
 pub struct OldestFirstCoinSelection;
 
-impl<D: Database> CoinSelectionAlgorithm<D> for OldestFirstCoinSelection {
+impl CoinSelectionAlgorithm for OldestFirstCoinSelection {
     fn coin_select(
         &self,
-        database: &D,
         required_utxos: Vec<WeightedUtxo>,
         mut optional_utxos: Vec<WeightedUtxo>,
         fee_rate: FeeRate,
         target_amount: u64,
         drain_script: &Script,
     ) -> Result<CoinSelectionResult, Error> {
-        // query db and create a blockheight lookup table
-        let blockheights = optional_utxos
-            .iter()
-            .map(|wu| wu.utxo.outpoint().txid)
-            // fold is used so we can skip db query for txid that already exist in hashmap acc
-            .fold(Ok(HashMap::new()), |bh_result_acc, txid| {
-                bh_result_acc.and_then(|mut bh_acc| {
-                    if bh_acc.contains_key(&txid) {
-                        Ok(bh_acc)
-                    } else {
-                        database.get_tx(&txid, false).map(|details| {
-                            bh_acc.insert(
-                                txid,
-                                details.and_then(|d| d.confirmation_time.map(|ct| ct.height)),
-                            );
-                            bh_acc
-                        })
-                    }
-                })
-            })?;
-
         // We put the "required UTXOs" first and make sure the optional UTXOs are sorted from
         // oldest to newest according to blocktime
         // For utxo that doesn't exist in DB, they will have lowest priority to be selected
         let utxos = {
-            optional_utxos.sort_unstable_by_key(|wu| {
-                match blockheights.get(&wu.utxo.outpoint().txid) {
-                    Some(Some(blockheight)) => blockheight,
-                    _ => &u32::MAX,
-                }
+            optional_utxos.sort_unstable_by_key(|wu| match &wu.utxo {
+                Utxo::Local(local) => Some(local.confirmation_time),
+                Utxo::Foreign { .. } => None,
             });
 
             required_utxos
@@ -422,10 +396,9 @@ impl BranchAndBoundCoinSelection {
 
 const BNB_TOTAL_TRIES: usize = 100_000;
 
-impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
+impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection {
     fn coin_select(
         &self,
-        _database: &D,
         required_utxos: Vec<WeightedUtxo>,
         optional_utxos: Vec<WeightedUtxo>,
         fee_rate: FeeRate,
@@ -724,16 +697,16 @@ impl BranchAndBoundCoinSelection {
 mod test {
     use std::str::FromStr;
 
+    use bdk_chain::ConfirmationTime;
     use bitcoin::{OutPoint, Script, TxOut};
 
     use super::*;
-    use crate::database::{BatchOperations, MemoryDatabase};
     use crate::types::*;
     use crate::wallet::Vbytes;
 
     use rand::rngs::StdRng;
     use rand::seq::SliceRandom;
-    use rand::{Rng, SeedableRng};
+    use rand::{Rng, RngCore, SeedableRng};
 
     // n. of items on witness (1WU) + signature len (1WU) + signature and sighash (72WU)
     // + pubkey len (1WU) + pubkey (33WU) + script sig len (1 byte, 4WU)
@@ -741,7 +714,7 @@ mod test {
 
     const FEE_AMOUNT: u64 = 50;
 
-    fn utxo(value: u64, index: u32) -> WeightedUtxo {
+    fn utxo(value: u64, index: u32, confirmation_time: ConfirmationTime) -> WeightedUtxo {
         assert!(index < 10);
         let outpoint = OutPoint::from_str(&format!(
             "000000000000000000000000000000000000000000000000000000000000000{}:0",
@@ -758,70 +731,46 @@ mod test {
                 },
                 keychain: KeychainKind::External,
                 is_spent: false,
+                derivation_index: 42,
+                confirmation_time,
             }),
         }
     }
 
     fn get_test_utxos() -> Vec<WeightedUtxo> {
         vec![
-            utxo(100_000, 0),
-            utxo(FEE_AMOUNT as u64 - 40, 1),
-            utxo(200_000, 2),
+            utxo(100_000, 0, ConfirmationTime::Unconfirmed),
+            utxo(FEE_AMOUNT as u64 - 40, 1, ConfirmationTime::Unconfirmed),
+            utxo(200_000, 2, ConfirmationTime::Unconfirmed),
         ]
     }
 
-    fn setup_database_and_get_oldest_first_test_utxos<D: Database>(
-        database: &mut D,
-    ) -> Vec<WeightedUtxo> {
+    fn get_oldest_first_test_utxos() -> Vec<WeightedUtxo> {
         // ensure utxos are from different tx
-        let utxo1 = utxo(120_000, 1);
-        let utxo2 = utxo(80_000, 2);
-        let utxo3 = utxo(300_000, 3);
-
-        // add tx to DB so utxos are sorted by blocktime asc
-        // utxos will be selected by the following order
-        // utxo1(blockheight 1) -> utxo2(blockheight 2), utxo3 (blockheight 3)
-        // timestamp are all set as the same to ensure that only block height is used in sorting
-        let utxo1_tx_details = TransactionDetails {
-            transaction: None,
-            txid: utxo1.utxo.outpoint().txid,
-            received: 1,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(BlockTime {
+        let utxo1 = utxo(
+            120_000,
+            1,
+            ConfirmationTime::Confirmed {
                 height: 1,
-                timestamp: 1231006505,
-            }),
-        };
-
-        let utxo2_tx_details = TransactionDetails {
-            transaction: None,
-            txid: utxo2.utxo.outpoint().txid,
-            received: 1,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(BlockTime {
+                time: 1231006505,
+            },
+        );
+        let utxo2 = utxo(
+            80_000,
+            2,
+            ConfirmationTime::Confirmed {
                 height: 2,
-                timestamp: 1231006505,
-            }),
-        };
-
-        let utxo3_tx_details = TransactionDetails {
-            transaction: None,
-            txid: utxo3.utxo.outpoint().txid,
-            received: 1,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(BlockTime {
+                time: 1231006505,
+            },
+        );
+        let utxo3 = utxo(
+            300_000,
+            3,
+            ConfirmationTime::Confirmed {
                 height: 3,
-                timestamp: 1231006505,
-            }),
-        };
-
-        database.set_tx(&utxo1_tx_details).unwrap();
-        database.set_tx(&utxo2_tx_details).unwrap();
-        database.set_tx(&utxo3_tx_details).unwrap();
-
+                time: 1231006505,
+            },
+        );
         vec![utxo1, utxo2, utxo3]
     }
 
@@ -841,6 +790,15 @@ mod test {
                     },
                     keychain: KeychainKind::External,
                     is_spent: false,
+                    derivation_index: rng.next_u32(),
+                    confirmation_time: if rng.gen_bool(0.5) {
+                        ConfirmationTime::Confirmed {
+                            height: rng.next_u32(),
+                            time: rng.next_u64(),
+                        }
+                    } else {
+                        ConfirmationTime::Unconfirmed
+                    },
                 }),
             });
         }
@@ -861,6 +819,8 @@ mod test {
                 },
                 keychain: KeychainKind::External,
                 is_spent: false,
+                derivation_index: 42,
+                confirmation_time: ConfirmationTime::Unconfirmed,
             }),
         };
         vec![utxo; utxos_number]
@@ -878,13 +838,11 @@ mod test {
     #[test]
     fn test_largest_first_coin_selection_success() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 250_000 + FEE_AMOUNT;
 
         let result = LargestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 utxos,
                 vec![],
                 FeeRate::from_sat_per_vb(1.0),
@@ -901,13 +859,11 @@ mod test {
     #[test]
     fn test_largest_first_coin_selection_use_all() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 20_000 + FEE_AMOUNT;
 
         let result = LargestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 utxos,
                 vec![],
                 FeeRate::from_sat_per_vb(1.0),
@@ -924,13 +880,11 @@ mod test {
     #[test]
     fn test_largest_first_coin_selection_use_only_necessary() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 20_000 + FEE_AMOUNT;
 
         let result = LargestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -948,13 +902,11 @@ mod test {
     #[should_panic(expected = "InsufficientFunds")]
     fn test_largest_first_coin_selection_insufficient_funds() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 500_000 + FEE_AMOUNT;
 
         LargestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -968,13 +920,11 @@ mod test {
     #[should_panic(expected = "InsufficientFunds")]
     fn test_largest_first_coin_selection_insufficient_funds_high_fees() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 250_000 + FEE_AMOUNT;
 
         LargestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1000.0),
@@ -986,14 +936,12 @@ mod test {
 
     #[test]
     fn test_oldest_first_coin_selection_success() {
-        let mut database = MemoryDatabase::default();
-        let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
+        let utxos = get_oldest_first_test_utxos();
         let drain_script = Script::default();
         let target_amount = 180_000 + FEE_AMOUNT;
 
         let result = OldestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1007,75 +955,14 @@ mod test {
         assert_eq!(result.fee_amount, 136)
     }
 
-    #[test]
-    fn test_oldest_first_coin_selection_utxo_not_in_db_will_be_selected_last() {
-        // ensure utxos are from different tx
-        let utxo1 = utxo(120_000, 1);
-        let utxo2 = utxo(80_000, 2);
-        let utxo3 = utxo(300_000, 3);
-        let drain_script = Script::default();
-
-        let mut database = MemoryDatabase::default();
-
-        // add tx to DB so utxos are sorted by blocktime asc
-        // utxos will be selected by the following order
-        // utxo1(blockheight 1) -> utxo2(blockheight 2), utxo3 (not exist in DB)
-        // timestamp are all set as the same to ensure that only block height is used in sorting
-        let utxo1_tx_details = TransactionDetails {
-            transaction: None,
-            txid: utxo1.utxo.outpoint().txid,
-            received: 1,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(BlockTime {
-                height: 1,
-                timestamp: 1231006505,
-            }),
-        };
-
-        let utxo2_tx_details = TransactionDetails {
-            transaction: None,
-            txid: utxo2.utxo.outpoint().txid,
-            received: 1,
-            sent: 0,
-            fee: None,
-            confirmation_time: Some(BlockTime {
-                height: 2,
-                timestamp: 1231006505,
-            }),
-        };
-
-        database.set_tx(&utxo1_tx_details).unwrap();
-        database.set_tx(&utxo2_tx_details).unwrap();
-
-        let target_amount = 180_000 + FEE_AMOUNT;
-
-        let result = OldestFirstCoinSelection::default()
-            .coin_select(
-                &database,
-                vec![],
-                vec![utxo3, utxo1, utxo2],
-                FeeRate::from_sat_per_vb(1.0),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 2);
-        assert_eq!(result.selected_amount(), 200_000);
-        assert_eq!(result.fee_amount, 136)
-    }
-
     #[test]
     fn test_oldest_first_coin_selection_use_all() {
-        let mut database = MemoryDatabase::default();
-        let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
+        let utxos = get_oldest_first_test_utxos();
         let drain_script = Script::default();
         let target_amount = 20_000 + FEE_AMOUNT;
 
         let result = OldestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 utxos,
                 vec![],
                 FeeRate::from_sat_per_vb(1.0),
@@ -1091,14 +978,12 @@ mod test {
 
     #[test]
     fn test_oldest_first_coin_selection_use_only_necessary() {
-        let mut database = MemoryDatabase::default();
-        let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
+        let utxos = get_oldest_first_test_utxos();
         let drain_script = Script::default();
         let target_amount = 20_000 + FEE_AMOUNT;
 
         let result = OldestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1115,14 +1000,12 @@ mod test {
     #[test]
     #[should_panic(expected = "InsufficientFunds")]
     fn test_oldest_first_coin_selection_insufficient_funds() {
-        let mut database = MemoryDatabase::default();
-        let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
+        let utxos = get_oldest_first_test_utxos();
         let drain_script = Script::default();
         let target_amount = 600_000 + FEE_AMOUNT;
 
         OldestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1135,15 +1018,13 @@ mod test {
     #[test]
     #[should_panic(expected = "InsufficientFunds")]
     fn test_oldest_first_coin_selection_insufficient_funds_high_fees() {
-        let mut database = MemoryDatabase::default();
-        let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
+        let utxos = get_oldest_first_test_utxos();
 
         let target_amount: u64 = utxos.iter().map(|wu| wu.utxo.txout().value).sum::<u64>() - 50;
         let drain_script = Script::default();
 
         OldestFirstCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1000.0),
@@ -1159,14 +1040,12 @@ mod test {
         // select three outputs
         let utxos = generate_same_value_utxos(100_000, 20);
 
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
 
         let target_amount = 250_000 + FEE_AMOUNT;
 
         let result = BranchAndBoundCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1183,13 +1062,11 @@ mod test {
     #[test]
     fn test_bnb_coin_selection_required_are_enough() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 20_000 + FEE_AMOUNT;
 
         let result = BranchAndBoundCoinSelection::default()
             .coin_select(
-                &database,
                 utxos.clone(),
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1206,13 +1083,11 @@ mod test {
     #[test]
     fn test_bnb_coin_selection_optional_are_enough() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 299756 + FEE_AMOUNT;
 
         let result = BranchAndBoundCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1230,11 +1105,10 @@ mod test {
     #[ignore]
     fn test_bnb_coin_selection_required_not_enough() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
 
         let required = vec![utxos[0].clone()];
         let mut optional = utxos[1..].to_vec();
-        optional.push(utxo(500_000, 3));
+        optional.push(utxo(500_000, 3, ConfirmationTime::Unconfirmed));
 
         // Defensive assertions, for sanity and in case someone changes the test utxos vector.
         let amount: u64 = required.iter().map(|u| u.utxo.txout().value).sum();
@@ -1247,7 +1121,6 @@ mod test {
 
         let result = BranchAndBoundCoinSelection::default()
             .coin_select(
-                &database,
                 required,
                 optional,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1265,13 +1138,11 @@ mod test {
     #[should_panic(expected = "InsufficientFunds")]
     fn test_bnb_coin_selection_insufficient_funds() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 500_000 + FEE_AMOUNT;
 
         BranchAndBoundCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1285,13 +1156,11 @@ mod test {
     #[should_panic(expected = "InsufficientFunds")]
     fn test_bnb_coin_selection_insufficient_funds_high_fees() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 250_000 + FEE_AMOUNT;
 
         BranchAndBoundCoinSelection::default()
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1000.0),
@@ -1304,13 +1173,11 @@ mod test {
     #[test]
     fn test_bnb_coin_selection_check_fee_rate() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
         let target_amount = 99932; // first utxo's effective value
 
         let result = BranchAndBoundCoinSelection::new(0)
             .coin_select(
-                &database,
                 vec![],
                 utxos,
                 FeeRate::from_sat_per_vb(1.0),
@@ -1330,7 +1197,6 @@ mod test {
     fn test_bnb_coin_selection_exact_match() {
         let seed = [0; 32];
         let mut rng: StdRng = SeedableRng::from_seed(seed);
-        let database = MemoryDatabase::default();
 
         for _i in 0..200 {
             let mut optional_utxos = generate_random_utxos(&mut rng, 16);
@@ -1338,7 +1204,6 @@ mod test {
             let drain_script = Script::default();
             let result = BranchAndBoundCoinSelection::new(0)
                 .coin_select(
-                    &database,
                     vec![],
                     optional_utxos,
                     FeeRate::from_sat_per_vb(0.0),
@@ -1520,11 +1385,9 @@ mod test {
     #[test]
     fn test_bnb_exclude_negative_effective_value() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
 
         let selection = BranchAndBoundCoinSelection::default().coin_select(
-            &database,
             vec![],
             utxos,
             FeeRate::from_sat_per_vb(10.0),
@@ -1544,7 +1407,6 @@ mod test {
     #[test]
     fn test_bnb_include_negative_effective_value_when_required() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
 
         let (required, optional) = utxos
@@ -1552,7 +1414,6 @@ mod test {
             .partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
 
         let selection = BranchAndBoundCoinSelection::default().coin_select(
-            &database,
             required,
             optional,
             FeeRate::from_sat_per_vb(10.0),
@@ -1572,11 +1433,9 @@ mod test {
     #[test]
     fn test_bnb_sum_of_effective_value_negative() {
         let utxos = get_test_utxos();
-        let database = MemoryDatabase::default();
         let drain_script = Script::default();
 
         let selection = BranchAndBoundCoinSelection::default().coin_select(
-            &database,
             utxos,
             vec![],
             FeeRate::from_sat_per_vb(10_000.0),
index d7c68f7ed7872d6dedcd8ea01b23772ff793ea41..f5d552b2641a50591c7ed20555246c25b90e70d0 100644 (file)
 
 use std::str::FromStr;
 
+use bdk_chain::sparse_chain::ChainPosition;
 use serde::{Deserialize, Serialize};
 
 use miniscript::descriptor::{ShInner, WshInner};
 use miniscript::{Descriptor, ScriptContext, Terminal};
 
-use crate::database::BatchDatabase;
 use crate::types::KeychainKind;
 use crate::wallet::Wallet;
 
@@ -116,8 +116,8 @@ impl FullyNodedExport {
     ///
     /// If the database is empty or `include_blockheight` is false, the `blockheight` field
     /// returned will be `0`.
-    pub fn export_wallet<D: BatchDatabase>(
-        wallet: &Wallet<D>,
+    pub fn export_wallet(
+        wallet: &Wallet,
         label: &str,
         include_blockheight: bool,
     ) -> Result<Self, &'static str> {
@@ -131,14 +131,14 @@ impl FullyNodedExport {
         let descriptor = remove_checksum(descriptor);
         Self::is_compatible_with_core(&descriptor)?;
 
-        let blockheight = match wallet.database.borrow().iter_txs(false) {
-            _ if !include_blockheight => 0,
-            Err(_) => 0,
-            Ok(txs) => txs
-                .into_iter()
-                .filter_map(|tx| tx.confirmation_time.map(|c| c.height))
-                .min()
-                .unwrap_or(0),
+        let blockheight = if include_blockheight {
+            wallet
+                .transactions()
+                .next()
+                .and_then(|(pos, _)| pos.height().into())
+                .unwrap_or(0)
+        } else {
+            0
         };
 
         let export = FullyNodedExport {
@@ -147,11 +147,7 @@ impl FullyNodedExport {
             blockheight,
         };
 
-        let change_descriptor = match wallet
-            .public_descriptor(KeychainKind::Internal)
-            .map_err(|_| "Invalid change descriptor")?
-            .is_some()
-        {
+        let change_descriptor = match wallet.public_descriptor(KeychainKind::Internal).is_some() {
             false => None,
             true => {
                 let descriptor = wallet
@@ -223,50 +219,41 @@ impl FullyNodedExport {
 mod test {
     use std::str::FromStr;
 
-    use bitcoin::{Network, Txid};
+    use bdk_chain::{BlockId, ConfirmationTime};
+    use bitcoin::hashes::Hash;
+    use bitcoin::{BlockHash, Network, Transaction};
 
     use super::*;
-    use crate::database::{memory::MemoryDatabase, BatchOperations};
-    use crate::types::TransactionDetails;
     use crate::wallet::Wallet;
-    use crate::BlockTime;
-
-    fn get_test_db() -> MemoryDatabase {
-        let mut db = MemoryDatabase::new();
-        db.set_tx(&TransactionDetails {
-            transaction: None,
-            txid: Txid::from_str(
-                "4ddff1fa33af17f377f62b72357b43107c19110a8009b36fb832af505efed98a",
-            )
-            .unwrap(),
 
-            received: 100_000,
-            sent: 0,
-            fee: Some(500),
-            confirmation_time: Some(BlockTime {
-                timestamp: 12345678,
+    fn get_test_wallet(
+        descriptor: &str,
+        change_descriptor: Option<&str>,
+        network: Network,
+    ) -> Wallet {
+        let mut wallet = Wallet::new(descriptor, change_descriptor, network).unwrap();
+        let transaction = Transaction {
+            input: vec![],
+            output: vec![],
+            version: 0,
+            lock_time: bitcoin::PackedLockTime::ZERO,
+        };
+        wallet
+            .insert_checkpoint(BlockId {
                 height: 5001,
-            }),
-        })
-        .unwrap();
-
-        db.set_tx(&TransactionDetails {
-            transaction: None,
-            txid: Txid::from_str(
-                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+                hash: BlockHash::all_zeros(),
+            })
+            .unwrap();
+        wallet
+            .insert_tx(
+                transaction,
+                ConfirmationTime::Confirmed {
+                    height: 5000,
+                    time: 0,
+                },
             )
-            .unwrap(),
-            received: 25_000,
-            sent: 0,
-            fee: Some(300),
-            confirmation_time: Some(BlockTime {
-                timestamp: 12345677,
-                height: 5000,
-            }),
-        })
-        .unwrap();
-
-        db
+            .unwrap();
+        wallet
     }
 
     #[test]
@@ -274,13 +261,7 @@ mod test {
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
         let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
 
-        let wallet = Wallet::new(
-            descriptor,
-            Some(change_descriptor),
-            Network::Bitcoin,
-            get_test_db(),
-        )
-        .unwrap();
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
         let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
 
         assert_eq!(export.descriptor(), descriptor);
@@ -298,7 +279,7 @@ mod test {
 
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
 
-        let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
+        let wallet = get_test_wallet(descriptor, None, Network::Bitcoin);
         FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
     }
 
@@ -311,13 +292,7 @@ mod test {
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
         let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
 
-        let wallet = Wallet::new(
-            descriptor,
-            Some(change_descriptor),
-            Network::Bitcoin,
-            get_test_db(),
-        )
-        .unwrap();
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
         FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
     }
 
@@ -334,13 +309,7 @@ mod test {
                                        [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
                                  ))";
 
-        let wallet = Wallet::new(
-            descriptor,
-            Some(change_descriptor),
-            Network::Testnet,
-            get_test_db(),
-        )
-        .unwrap();
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet);
         let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
 
         assert_eq!(export.descriptor(), descriptor);
@@ -354,13 +323,7 @@ mod test {
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
         let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
 
-        let wallet = Wallet::new(
-            descriptor,
-            Some(change_descriptor),
-            Network::Bitcoin,
-            get_test_db(),
-        )
-        .unwrap();
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
         let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
 
         assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}");
index f6fd26b49e569a1b64c6a9aae3bb297b18dec9b2..8e7e1198cf48aa82f06ddfa00a1a83763f508752 100644 (file)
 //! Wallet
 //!
 //! This module defines the [`Wallet`] structure.
-
-use std::cell::RefCell;
-use std::collections::HashMap;
-use std::collections::{BTreeMap, HashSet};
+use bdk_chain::chain_graph;
+use bdk_chain::{keychain::KeychainTracker, sparse_chain, BlockId, ConfirmationTime};
+use bitcoin::secp256k1::Secp256k1;
+use core::convert::TryInto;
+use std::collections::{BTreeMap, HashMap, HashSet};
 use std::fmt;
 use std::ops::Deref;
-use std::str::FromStr;
 use std::sync::Arc;
 
-use bitcoin::secp256k1::Secp256k1;
-
 use bitcoin::consensus::encode::serialize;
 use bitcoin::util::psbt;
 use bitcoin::{
-    Address, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script, Sequence,
-    Transaction, TxOut, Txid, Witness,
+    Address, BlockHash, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script,
+    Sequence, Transaction, TxOut, Txid, Witness,
 };
 
 use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
@@ -38,12 +36,8 @@ use log::{debug, error, info, trace};
 pub mod coin_selection;
 pub mod export;
 pub mod signer;
-pub mod time;
 pub mod tx_builder;
 pub(crate) mod utils;
-#[cfg(feature = "verify")]
-#[cfg_attr(docsrs, doc(cfg(feature = "verify")))]
-pub mod verify;
 
 #[cfg(feature = "hardware-signer")]
 #[cfg_attr(docsrs, doc(cfg(feature = "hardware-signer")))]
@@ -51,28 +45,23 @@ pub mod hardwaresigner;
 
 pub use utils::IsDust;
 
+#[allow(deprecated)]
 use coin_selection::DefaultCoinSelectionAlgorithm;
 use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
 use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
 use utils::{check_nsequence_rbf, After, Older, SecpCtx};
 
-use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync};
-use crate::database::memory::MemoryDatabase;
-use crate::database::{AnyDatabase, BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
-use crate::descriptor::checksum::calc_checksum_bytes_internal;
 use crate::descriptor::policy::BuildSatisfaction;
 use crate::descriptor::{
     calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
-    ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
+    ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, SpkIter, XKeyUtils,
 };
 use crate::error::{Error, MiniscriptPsbtError};
 use crate::psbt::PsbtUtils;
 use crate::signer::SignerError;
-use crate::testutils;
 use crate::types::*;
 use crate::wallet::coin_selection::Excess::{Change, NoChange};
 
-const CACHE_ADDR_BATCH_SIZE: u32 = 100;
 const COINBASE_MATURITY: u32 = 100;
 
 /// A Bitcoin wallet
@@ -87,17 +76,13 @@ const COINBASE_MATURITY: u32 = 100;
 /// [`Database`]: crate::database::Database
 /// [`signer`]: crate::signer
 #[derive(Debug)]
-pub struct Wallet<D> {
-    descriptor: ExtendedDescriptor,
-    change_descriptor: Option<ExtendedDescriptor>,
-
+pub struct Wallet {
     signers: Arc<SignersContainer>,
     change_signers: Arc<SignersContainer>,
+    keychain_tracker: KeychainTracker<KeychainKind, ConfirmationTime>,
 
     network: Network,
 
-    database: RefCell<D>,
-
     secp: SecpCtx,
 }
 
@@ -121,15 +106,6 @@ pub enum AddressIndex {
     /// Use with caution, if an index is given that is less than the current descriptor index
     /// then the returned address may have already been used.
     Peek(u32),
-    /// Return the address for a specific descriptor index and reset the current descriptor index
-    /// used by `AddressIndex::New` and `AddressIndex::LastUsed` to this value.
-    ///
-    /// Use with caution, if an index is given that is less than the current descriptor index
-    /// then the returned address and subsequent addresses returned by calls to `AddressIndex::New`
-    /// and `AddressIndex::LastUsed` may have already been used. Also if the index is reset to a
-    /// value earlier than the [`crate::blockchain::Blockchain`] stop_gap (default is 20) then a
-    /// larger stop_gap should be used to monitor for all possibly used addresses.
-    Reset(u32),
 }
 
 /// A derived address and the index it was found at.
@@ -158,30 +134,7 @@ impl fmt::Display for AddressInfo {
     }
 }
 
-#[derive(Debug, Default)]
-/// Options to a [`sync`].
-///
-/// [`sync`]: Wallet::sync
-pub struct SyncOptions {
-    /// The progress tracker which may be informed when progress is made.
-    pub progress: Option<Box<dyn Progress>>,
-}
-
-impl<D> Wallet<D>
-where
-    D: BatchDatabase,
-{
-    #[deprecated = "Just use Wallet::new -- all wallets are offline now!"]
-    /// Create a new "offline" wallet
-    pub fn new_offline<E: IntoWalletDescriptor>(
-        descriptor: E,
-        change_descriptor: Option<E>,
-        network: Network,
-        database: D,
-    ) -> Result<Self, Error> {
-        Self::new(descriptor, change_descriptor, network, database)
-    }
-
+impl Wallet {
     /// Create a wallet.
     ///
     /// The only way this can fail is if the descriptors passed in do not match the checksums in `database`.
@@ -189,26 +142,19 @@ where
         descriptor: E,
         change_descriptor: Option<E>,
         network: Network,
-        mut database: D,
     ) -> Result<Self, Error> {
         let secp = Secp256k1::new();
 
+        let mut keychain_tracker = KeychainTracker::default();
         let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
-        Self::db_checksum(
-            &mut database,
-            &descriptor.to_string(),
-            KeychainKind::External,
-        )?;
+        keychain_tracker
+            .txout_index
+            .add_keychain(KeychainKind::External, descriptor.clone());
         let signers = Arc::new(SignersContainer::build(keymap, &descriptor, &secp));
-        let (change_descriptor, change_signers) = match change_descriptor {
+        let change_signers = match change_descriptor {
             Some(desc) => {
                 let (change_descriptor, change_keymap) =
                     into_wallet_descriptor_checked(desc, &secp, network)?;
-                Self::db_checksum(
-                    &mut database,
-                    &change_descriptor.to_string(),
-                    KeychainKind::Internal,
-                )?;
 
                 let change_signers = Arc::new(SignersContainer::build(
                     change_keymap,
@@ -216,123 +162,83 @@ where
                     &secp,
                 ));
 
-                (Some(change_descriptor), change_signers)
+                keychain_tracker
+                    .txout_index
+                    .add_keychain(KeychainKind::Internal, change_descriptor);
+
+                change_signers
             }
-            None => (None, Arc::new(SignersContainer::new())),
+            None => Arc::new(SignersContainer::new()),
         };
 
         Ok(Wallet {
-            descriptor,
-            change_descriptor,
             signers,
             change_signers,
             network,
-            database: RefCell::new(database),
             secp,
+            keychain_tracker,
         })
     }
 
-    /// This checks the checksum within [`BatchDatabase`] twice (if needed). The first time with the
-    /// actual checksum, and the second time with the checksum of `descriptor+checksum`. The second
-    /// check is necessary for backwards compatibility of a checksum-inception bug.
-    fn db_checksum(db: &mut D, desc: &str, kind: KeychainKind) -> Result<(), Error> {
-        let checksum = calc_checksum_bytes_internal(desc, true)?;
-        if db.check_descriptor_checksum(kind, checksum).is_ok() {
-            return Ok(());
-        }
-
-        let checksum_inception = calc_checksum_bytes_internal(desc, false)?;
-        db.check_descriptor_checksum(kind, checksum_inception)
-    }
-
     /// Get the Bitcoin network the wallet is using.
     pub fn network(&self) -> Network {
         self.network
     }
 
-    // Return a newly derived address for the specified `keychain`.
-    fn get_new_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> {
-        let incremented_index = self.fetch_and_increment_index(keychain)?;
+    /// Iterator over all keychains in this wallet
+    pub fn keychanins(&self) -> &BTreeMap<KeychainKind, ExtendedDescriptor> {
+        self.keychain_tracker.txout_index.keychains()
+    }
 
-        let address_result = self
-            .get_descriptor_for_keychain(keychain)
-            .at_derivation_index(incremented_index)
-            .address(self.network);
-
-        address_result
-            .map(|address| AddressInfo {
-                address,
-                index: incremented_index,
-                keychain,
-            })
-            .map_err(|_| Error::ScriptDoesntHaveAddressForm)
+    // Return a newly derived address for the specified `keychain`.
+    fn get_new_address(&mut self, keychain: KeychainKind) -> AddressInfo {
+        let ((index, spk), _) = self.keychain_tracker.txout_index.reveal_next_spk(&keychain);
+        let address =
+            Address::from_script(&spk, self.network).expect("descriptor must have address form");
+
+        AddressInfo {
+            address,
+            index,
+            keychain,
+        }
     }
 
     // Return the the last previously derived address for `keychain` if it has not been used in a
     // received transaction. Otherwise return a new address using [`Wallet::get_new_address`].
-    fn get_unused_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> {
-        let current_index = self.fetch_index(keychain)?;
-
-        let derived_key = self
-            .get_descriptor_for_keychain(keychain)
-            .at_derivation_index(current_index);
-
-        let script_pubkey = derived_key.script_pubkey();
-
-        let found_used = self
-            .list_transactions(true)?
-            .iter()
-            .flat_map(|tx_details| tx_details.transaction.as_ref())
-            .flat_map(|tx| tx.output.iter())
-            .any(|o| o.script_pubkey == script_pubkey);
-
-        if found_used {
-            self.get_new_address(keychain)
-        } else {
-            derived_key
-                .address(self.network)
-                .map(|address| AddressInfo {
-                    address,
-                    index: current_index,
-                    keychain,
-                })
-                .map_err(|_| Error::ScriptDoesntHaveAddressForm)
+    fn get_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo {
+        let index = self.derivation_index(KeychainKind::External);
+
+        match index {
+            Some(index)
+                if !self
+                    .keychain_tracker
+                    .txout_index
+                    .is_used(&(keychain, index)) =>
+            {
+                self.peek_address(index, keychain)
+            }
+            _ => self.get_new_address(keychain),
         }
     }
 
     // Return derived address for the descriptor of given [`KeychainKind`] at a specific index
-    fn peek_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
-        self.get_descriptor_for_keychain(keychain)
-            .at_derivation_index(index)
-            .address(self.network)
-            .map(|address| AddressInfo {
-                index,
-                address,
-                keychain,
-            })
-            .map_err(|_| Error::ScriptDoesntHaveAddressForm)
-    }
-
-    // Return derived address for `keychain` at a specific index and reset current
-    // address index
-    fn reset_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
-        self.set_index(keychain, index)?;
-
-        self.get_descriptor_for_keychain(keychain)
+    fn peek_address(&self, index: u32, keychain: KeychainKind) -> AddressInfo {
+        let address = self
+            .get_descriptor_for_keychain(keychain)
             .at_derivation_index(index)
             .address(self.network)
-            .map(|address| AddressInfo {
-                index,
-                address,
-                keychain,
-            })
-            .map_err(|_| Error::ScriptDoesntHaveAddressForm)
+            .expect("descriptor must have address form");
+        AddressInfo {
+            index,
+            address,
+            keychain,
+        }
     }
 
     /// Return a derived address using the external descriptor, see [`AddressIndex`] for
     /// available address index selection strategies. If none of the keys in the descriptor are derivable
     /// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
-    pub fn get_address(&self, address_index: AddressIndex) -> Result<AddressInfo, Error> {
+    pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
         self._get_address(address_index, KeychainKind::External)
     }
 
@@ -343,90 +249,83 @@ where
     /// see [`AddressIndex`] for available address index selection strategies. If none of the keys
     /// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
     /// be returned for any [`AddressIndex`].
-    pub fn get_internal_address(&self, address_index: AddressIndex) -> Result<AddressInfo, Error> {
+    pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo {
         self._get_address(address_index, KeychainKind::Internal)
     }
 
-    fn _get_address(
-        &self,
-        address_index: AddressIndex,
-        keychain: KeychainKind,
-    ) -> Result<AddressInfo, Error> {
-        match address_index {
-            AddressIndex::New => self.get_new_address(keychain),
-            AddressIndex::LastUnused => self.get_unused_address(keychain),
-            AddressIndex::Peek(index) => self.peek_address(index, keychain),
-            AddressIndex::Reset(index) => self.reset_address(index, keychain),
-        }
-    }
-
-    /// Ensures that there are at least `max_addresses` addresses cached in the database if the
-    /// descriptor is derivable, or 1 address if it is not.
-    /// Will return `Ok(true)` if there are new addresses generated (either external or internal),
-    /// and `Ok(false)` if all the required addresses are already cached. This function is useful to
-    /// explicitly cache addresses in a wallet to do things like check [`Wallet::is_mine`] on
-    /// transaction output scripts.
-    pub fn ensure_addresses_cached(&self, max_addresses: u32) -> Result<bool, Error> {
-        let mut new_addresses_cached = false;
-        let max_address = match self.descriptor.has_wildcard() {
-            false => 0,
-            true => max_addresses,
+    fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo {
+        // TODO: Fix this mess!
+        let _keychain = self.map_keychain(keychain);
+        let mut info = match address_index {
+            AddressIndex::New => self.get_new_address(_keychain),
+            AddressIndex::LastUnused => self.get_unused_address(_keychain),
+            AddressIndex::Peek(index) => self.peek_address(index, _keychain),
         };
-        debug!("max_address {}", max_address);
-        if self
-            .database
-            .borrow()
-            .get_script_pubkey_from_path(KeychainKind::External, max_address.saturating_sub(1))?
-            .is_none()
-        {
-            debug!("caching external addresses");
-            new_addresses_cached = true;
-            self.cache_addresses(KeychainKind::External, 0, max_address)?;
-        }
-
-        if let Some(change_descriptor) = &self.change_descriptor {
-            let max_address = match change_descriptor.has_wildcard() {
-                false => 0,
-                true => max_addresses,
-            };
-
-            if self
-                .database
-                .borrow()
-                .get_script_pubkey_from_path(KeychainKind::Internal, max_address.saturating_sub(1))?
-                .is_none()
-            {
-                debug!("caching internal addresses");
-                new_addresses_cached = true;
-                self.cache_addresses(KeychainKind::Internal, 0, max_address)?;
-            }
-        }
-        Ok(new_addresses_cached)
+        info.keychain = keychain;
+        info
     }
 
     /// Return whether or not a `script` is part of this wallet (either internal or external)
-    pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
-        self.database.borrow().is_mine(script)
+    pub fn is_mine(&self, script: &Script) -> bool {
+        self.keychain_tracker
+            .txout_index
+            .index_of_spk(script)
+            .is_some()
     }
 
     /// Return the list of unspent outputs of this wallet
     ///
     /// Note that this method only operates on the internal database, which first needs to be
     /// [`Wallet::sync`] manually.
-    pub fn list_unspent(&self) -> Result<Vec<LocalUtxo>, Error> {
-        Ok(self
-            .database
-            .borrow()
-            .iter_utxos()?
-            .into_iter()
-            .filter(|l| !l.is_spent)
-            .collect())
+    pub fn list_unspent(&self) -> Vec<LocalUtxo> {
+        self.keychain_tracker
+            .full_utxos()
+            .map(|(&(keychain, derivation_index), utxo)| LocalUtxo {
+                outpoint: utxo.outpoint,
+                txout: utxo.txout,
+                keychain: keychain.clone(),
+                is_spent: false,
+                derivation_index,
+                confirmation_time: utxo.chain_position,
+            })
+            .collect()
+    }
+
+    /// Iterate over all checkpoints.
+    pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
+        self.keychain_tracker.chain().checkpoints()
+    }
+
+    /// Returns the latest checkpoint.
+    pub fn latest_checkpoint(&self) -> Option<BlockId> {
+        self.keychain_tracker.chain().latest_checkpoint()
+    }
+
+    /// Create an iterator over all the script pubkeys starting at index 0 for a particular
+    /// keychain.
+    pub fn iter_all_script_pubkeys(&self, keychain: KeychainKind) -> SpkIter {
+        SpkIter::new(self.get_descriptor_for_keychain(keychain).clone())
     }
 
     /// Returns the `UTXO` owned by this wallet corresponding to `outpoint` if it exists in the
     /// wallet's database.
-    pub fn get_utxo(&self, outpoint: OutPoint) -> Result<Option<LocalUtxo>, Error> {
-        self.database.borrow().get_utxo(&outpoint)
+    pub fn get_utxo(&self, op: OutPoint) -> Option<LocalUtxo> {
+        self.keychain_tracker
+            .full_utxos()
+            .find_map(|(&(keychain, derivation_index), txo)| {
+                if op == txo.outpoint {
+                    Some(LocalUtxo {
+                        outpoint: txo.outpoint,
+                        txout: txo.txout,
+                        keychain,
+                        is_spent: txo.spent_by.is_none(),
+                        derivation_index,
+                        confirmation_time: txo.chain_position,
+                    })
+                } else {
+                    None
+                }
+            })
     }
 
     /// Return a single transactions made and received by the wallet
@@ -436,34 +335,104 @@ where
     ///
     /// Note that this method only operates on the internal database, which first needs to be
     /// [`Wallet::sync`] manually.
-    pub fn get_tx(
-        &self,
-        txid: &Txid,
-        include_raw: bool,
-    ) -> Result<Option<TransactionDetails>, Error> {
-        self.database.borrow().get_tx(txid, include_raw)
+    pub fn get_tx(&self, txid: Txid, include_raw: bool) -> Option<TransactionDetails> {
+        let (&confirmation_time, tx) = self.keychain_tracker.chain_graph().get_tx_in_chain(txid)?;
+        let graph = self.keychain_tracker.graph();
+        let txout_index = &self.keychain_tracker.txout_index;
+
+        let received = tx
+            .output
+            .iter()
+            .map(|txout| {
+                if txout_index.index_of_spk(&txout.script_pubkey).is_some() {
+                    txout.value
+                } else {
+                    0
+                }
+            })
+            .sum();
+
+        let sent = tx
+            .input
+            .iter()
+            .map(|txin| {
+                if let Some((_, txout)) = txout_index.txout(txin.previous_output) {
+                    txout.value
+                } else {
+                    0
+                }
+            })
+            .sum();
+
+        let inputs = tx
+            .input
+            .iter()
+            .map(|txin| {
+                graph
+                    .get_txout(txin.previous_output)
+                    .map(|txout| txout.value)
+            })
+            .sum::<Option<u64>>();
+        let outputs = tx.output.iter().map(|txout| txout.value).sum();
+        let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
+
+        Some(TransactionDetails {
+            transaction: if include_raw { Some(tx.clone()) } else { None },
+            txid,
+            received,
+            sent,
+            fee,
+            confirmation_time,
+        })
     }
 
-    /// Return an unsorted list of transactions made and received by the wallet
-    ///
-    /// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
-    /// `include_raw` is `true`.
-    ///
-    /// To sort transactions, the following code can be used:
-    /// ```no_run
-    /// # let mut tx_list: Vec<bdk::TransactionDetails> = vec![];
-    /// tx_list.sort_by(|a, b| {
-    ///     b.confirmation_time
-    ///         .as_ref()
-    ///         .map(|t| t.height)
-    ///         .cmp(&a.confirmation_time.as_ref().map(|t| t.height))
-    /// });
-    /// ```
-    ///
-    /// Note that this method only operates on the internal database, which first needs to be
-    /// [`Wallet::sync`] manually.
-    pub fn list_transactions(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
-        self.database.borrow().iter_txs(include_raw)
+    /// Add a new checkpoint to the wallet
+    pub fn insert_checkpoint(
+        &mut self,
+        block_id: BlockId,
+    ) -> Result<bool, sparse_chain::InsertCheckpointError> {
+        Ok(!self
+            .keychain_tracker
+            .insert_checkpoint(block_id)?
+            .is_empty())
+    }
+
+    /// Add a transaction to the wallet. Will only work if height <= latest checkpoint
+    pub fn insert_tx(
+        &mut self,
+        tx: Transaction,
+        position: ConfirmationTime,
+    ) -> Result<bool, chain_graph::InsertTxError<ConfirmationTime>> {
+        Ok(!self.keychain_tracker.insert_tx(tx, position)?.is_empty())
+    }
+
+    #[deprecated(note = "use Wallet::transactions instead")]
+    /// Deprecated. use `Wallet::transactions` instead.
+    pub fn list_transactions(&self, include_raw: bool) -> Vec<TransactionDetails> {
+        self.keychain_tracker
+            .chain()
+            .txids()
+            .map(|&(_, txid)| self.get_tx(txid, include_raw).expect("must exist"))
+            .collect()
+    }
+
+    /// Iterate over the transactions in the wallet in order of ascending confirmation time with
+    /// unconfirmed transactions last.
+    fn transactions(
+        &self,
+    ) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
+        self.keychain_tracker
+            .chain()
+            .txids()
+            .map(move |&(pos, txid)| {
+                (
+                    pos,
+                    self.keychain_tracker
+                        .graph()
+                        .get_tx(txid)
+                        .expect("must exist"),
+                )
+            })
     }
 
     /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
@@ -471,47 +440,47 @@ where
     ///
     /// Note that this method only operates on the internal database, which first needs to be
     /// [`Wallet::sync`] manually.
-    pub fn get_balance(&self) -> Result<Balance, Error> {
+    pub fn get_balance(&self) -> Balance {
         let mut immature = 0;
         let mut trusted_pending = 0;
         let mut untrusted_pending = 0;
         let mut confirmed = 0;
-        let utxos = self.list_unspent()?;
-        let database = self.database.borrow();
-        let last_sync_height = match database
-            .get_sync_time()?
-            .map(|sync_time| sync_time.block_time.height)
-        {
-            Some(height) => height,
-            // None means database was never synced
-            None => return Ok(Balance::default()),
+        let last_sync_height = match self.keychain_tracker.chain().latest_checkpoint() {
+            Some(last_sync_height) => last_sync_height.height,
+            // TODO: update this when we're allowed to add arbitary stuff to the mempool
+            None => return Balance::default(),
         };
-        for u in utxos {
-            // Unwrap used since utxo set is created from database
-            let tx = database
-                .get_tx(&u.outpoint.txid, true)?
-                .expect("Transaction not found in database");
-            if let Some(tx_conf_time) = &tx.confirmation_time {
-                if tx.transaction.expect("No transaction").is_coin_base()
-                    && (last_sync_height - tx_conf_time.height) < COINBASE_MATURITY
-                {
-                    immature += u.txout.value;
-                } else {
-                    confirmed += u.txout.value;
+
+        for ((keychain, _), utxo) in self.keychain_tracker.full_utxos() {
+            let confirmation_time = utxo.chain_position;
+            let is_coinbase = self
+                .keychain_tracker
+                .graph()
+                .get_tx(utxo.outpoint.txid)
+                .expect("must exist")
+                .is_coin_base();
+
+            match confirmation_time {
+                ConfirmationTime::Confirmed { height, .. } => {
+                    if is_coinbase && last_sync_height - height < COINBASE_MATURITY {
+                        immature += utxo.txout.value;
+                    } else {
+                        confirmed += utxo.txout.value;
+                    }
                 }
-            } else if u.keychain == KeychainKind::Internal {
-                trusted_pending += u.txout.value;
-            } else {
-                untrusted_pending += u.txout.value;
+                ConfirmationTime::Unconfirmed => match keychain {
+                    KeychainKind::External => untrusted_pending += utxo.txout.value,
+                    KeychainKind::Internal => trusted_pending += utxo.txout.value,
+                },
             }
         }
 
-        Ok(Balance {
+        Balance {
             immature,
             trusted_pending,
             untrusted_pending,
             confirmed,
-        })
+        }
     }
 
     /// Add an external signer
@@ -580,26 +549,36 @@ where
     /// ```
     ///
     /// [`TxBuilder`]: crate::TxBuilder
-    pub fn build_tx(&self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
+    pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm, CreateTx> {
         TxBuilder {
-            wallet: self,
+            wallet: std::rc::Rc::new(core::cell::RefCell::new(self)),
             params: TxParams::default(),
             coin_selection: DefaultCoinSelectionAlgorithm::default(),
             phantom: core::marker::PhantomData,
         }
     }
 
-    pub(crate) fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm<D>>(
-        &self,
+    pub(crate) fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm>(
+        &mut self,
         coin_selection: Cs,
         params: TxParams,
     ) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error> {
-        let external_policy = self
-            .descriptor
+        let external_descriptor = self
+            .keychain_tracker
+            .txout_index
+            .keychains()
+            .get(&KeychainKind::External)
+            .expect("must exist");
+        let internal_descriptor = self
+            .keychain_tracker
+            .txout_index
+            .keychains()
+            .get(&KeychainKind::Internal);
+
+        let external_policy = external_descriptor
             .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
             .unwrap();
-        let internal_policy = self
-            .change_descriptor
+        let internal_policy = internal_descriptor
             .as_ref()
             .map(|desc| {
                 Ok::<_, Error>(
@@ -668,9 +647,12 @@ where
         // We use a match here instead of a map_or_else as it's way more readable :)
         let current_height = match params.current_height {
             // If they didn't tell us the current height, we assume it's the latest sync height.
-            None => self.database().get_sync_time()?.map(|sync_time| {
-                LockTime::from_height(sync_time.block_time.height).expect("Invalid height")
-            }),
+            None => self
+                .keychain_tracker
+                .chain()
+                .latest_checkpoint()
+                .and_then(|cp| cp.height.into())
+                .map(|height| LockTime::from_height(height).expect("Invalid height")),
             h => h,
         };
 
@@ -790,7 +772,7 @@ where
                 return Err(Error::OutputBelowDustLimit(index));
             }
 
-            if self.is_mine(script_pubkey)? {
+            if self.is_mine(script_pubkey) {
                 received += value;
             }
 
@@ -818,7 +800,7 @@ where
         fee_amount += fee_rate.fee_wu(2);
 
         if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
-            && self.change_descriptor.is_none()
+            && internal_descriptor.is_none()
         {
             return Err(Error::Generic(
                 "The `change_policy` can be set only if the wallet has a change_descriptor".into(),
@@ -833,19 +815,18 @@ where
             params.manually_selected_only,
             params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
             current_height.map(LockTime::to_consensus_u32),
-        )?;
+        );
 
         // get drain script
         let drain_script = match params.drain_to {
             Some(ref drain_recipient) => drain_recipient.clone(),
             None => self
-                .get_internal_address(AddressIndex::New)?
+                .get_internal_address(AddressIndex::New)
                 .address
                 .script_pubkey(),
         };
 
         let coin_selection = coin_selection.coin_select(
-            self.database.borrow().deref(),
             required_utxos,
             optional_utxos,
             fee_rate,
@@ -896,7 +877,7 @@ where
                 remaining_amount, ..
             } => fee_amount += remaining_amount,
             Change { amount, fee } => {
-                if self.is_mine(&drain_script)? {
+                if self.is_mine(&drain_script) {
                     received += amount;
                 }
                 fee_amount += fee;
@@ -924,7 +905,7 @@ where
         let transaction_details = TransactionDetails {
             transaction: None,
             txid,
-            confirmation_time: None,
+            confirmation_time: ConfirmationTime::Unconfirmed,
             received,
             sent,
             fee: Some(fee_amount),
@@ -974,16 +955,20 @@ where
     /// ```
     // TODO: support for merging multiple transactions while bumping the fees
     pub fn build_fee_bump(
-        &self,
+        &mut self,
         txid: Txid,
-    ) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
-        let mut details = match self.database.borrow().get_tx(&txid, true)? {
+    ) -> Result<TxBuilder<'_, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
+        let graph = self.keychain_tracker.graph();
+        let txout_index = &self.keychain_tracker.txout_index;
+        let tx_and_height = self.keychain_tracker.chain_graph().get_tx_in_chain(txid);
+        let mut tx = match tx_and_height {
             None => return Err(Error::TransactionNotFound),
-            Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
-            Some(tx) if tx.confirmation_time.is_some() => return Err(Error::TransactionConfirmed),
-            Some(tx) => tx,
+            Some((ConfirmationTime::Confirmed { .. }, _tx)) => {
+                return Err(Error::TransactionConfirmed)
+            }
+            Some((_, tx)) => tx.clone(),
         };
-        let mut tx = details.transaction.take().unwrap();
+
         if !tx
             .input
             .iter()
@@ -992,64 +977,70 @@ where
             return Err(Error::IrreplaceableTransaction);
         }
 
-        let feerate = FeeRate::from_wu(details.fee.ok_or(Error::FeeRateUnavailable)?, tx.weight());
+        let fee = graph
+            .calculate_fee(&tx)
+            .ok_or(Error::FeeRateUnavailable)?
+            .try_into()
+            .map_err(|_| Error::FeeRateUnavailable)?;
+        let feerate = FeeRate::from_wu(fee, tx.weight());
 
         // remove the inputs from the tx and process them
         let original_txin = tx.input.drain(..).collect::<Vec<_>>();
         let original_utxos = original_txin
             .iter()
             .map(|txin| -> Result<_, Error> {
-                let txout = self
-                    .database
-                    .borrow()
-                    .get_previous_output(&txin.previous_output)?
+                let (&confirmation_time, prev_tx) = self
+                    .keychain_tracker
+                    .chain_graph()
+                    .get_tx_in_chain(txin.previous_output.txid)
                     .ok_or(Error::UnknownUtxo)?;
+                let txout = &prev_tx.output[txin.previous_output.vout as usize];
 
-                let (weight, keychain) = match self
-                    .database
-                    .borrow()
-                    .get_path_from_script_pubkey(&txout.script_pubkey)?
-                {
-                    Some((keychain, _)) => (
-                        self._get_descriptor_for_keychain(keychain)
-                            .0
+                let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
+                    Some(&(keychain, derivation_index)) => {
+                        let satisfaction_weight = self
+                            .get_descriptor_for_keychain(keychain)
                             .max_satisfaction_weight()
-                            .unwrap(),
-                        keychain,
-                    ),
+                            .unwrap();
+                        WeightedUtxo {
+                            utxo: Utxo::Local(LocalUtxo {
+                                outpoint: txin.previous_output,
+                                txout: txout.clone(),
+                                keychain,
+                                is_spent: true,
+                                derivation_index,
+                                confirmation_time,
+                            }),
+                            satisfaction_weight,
+                        }
+                    }
                     None => {
-                        // estimate the weight based on the scriptsig/witness size present in the
-                        // original transaction
-                        let weight =
+                        let satisfaction_weight =
                             serialize(&txin.script_sig).len() * 4 + serialize(&txin.witness).len();
-                        (weight, KeychainKind::External)
+                        WeightedUtxo {
+                            satisfaction_weight,
+                            utxo: Utxo::Foreign {
+                                outpoint: txin.previous_output,
+                                psbt_input: Box::new(psbt::Input {
+                                    witness_utxo: Some(txout.clone()),
+                                    non_witness_utxo: Some(prev_tx.clone()),
+                                    ..Default::default()
+                                }),
+                            },
+                        }
                     }
                 };
 
-                let utxo = LocalUtxo {
-                    outpoint: txin.previous_output,
-                    txout,
-                    keychain,
-                    is_spent: true,
-                };
-
-                Ok(WeightedUtxo {
-                    satisfaction_weight: weight,
-                    utxo: Utxo::Local(utxo),
-                })
+                Ok(weighted_utxo)
             })
             .collect::<Result<Vec<_>, _>>()?;
 
         if tx.output.len() > 1 {
             let mut change_index = None;
             for (index, txout) in tx.output.iter().enumerate() {
-                let (_, change_type) = self._get_descriptor_for_keychain(KeychainKind::Internal);
-                match self
-                    .database
-                    .borrow()
-                    .get_path_from_script_pubkey(&txout.script_pubkey)?
-                {
-                    Some((keychain, _)) if keychain == change_type => change_index = Some(index),
+                let change_type = self.map_keychain(KeychainKind::Internal);
+                match txout_index.index_of_spk(&txout.script_pubkey) {
+                    Some(&(keychain, _)) if keychain == change_type => change_index = Some(index),
                     _ => {}
                 }
             }
@@ -1069,14 +1060,14 @@ where
                 .collect(),
             utxos: original_utxos,
             bumping_fee: Some(tx_builder::PreviousFee {
-                absolute: details.fee.ok_or(Error::FeeRateUnavailable)?,
+                absolute: fee,
                 rate: feerate.as_sat_per_vb(),
             }),
             ..Default::default()
         };
 
         Ok(TxBuilder {
-            wallet: self,
+            wallet: std::rc::Rc::new(std::cell::RefCell::new(self)),
             params,
             coin_selection: DefaultCoinSelectionAlgorithm::default(),
             phantom: core::marker::PhantomData,
@@ -1163,18 +1154,14 @@ where
 
     /// Return the spending policies for the wallet's descriptor
     pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, Error> {
-        match (keychain, self.change_descriptor.as_ref()) {
-            (KeychainKind::External, _) => Ok(self.descriptor.extract_policy(
-                &self.signers,
-                BuildSatisfaction::None,
-                &self.secp,
-            )?),
-            (KeychainKind::Internal, None) => Ok(None),
-            (KeychainKind::Internal, Some(desc)) => Ok(desc.extract_policy(
-                &self.change_signers,
-                BuildSatisfaction::None,
-                &self.secp,
-            )?),
+        let signers = match keychain {
+            KeychainKind::External => &self.signers,
+            KeychainKind::Internal => &self.change_signers,
+        };
+
+        match self.public_descriptor(keychain) {
+            Some(desc) => Ok(desc.extract_policy(signers, BuildSatisfaction::None, &self.secp)?),
+            None => Ok(None),
         }
     }
 
@@ -1182,15 +1169,8 @@ where
     /// the same structure but with every secret key removed
     ///
     /// This can be used to build a watch-only version of a wallet
-    pub fn public_descriptor(
-        &self,
-        keychain: KeychainKind,
-    ) -> Result<Option<ExtendedDescriptor>, Error> {
-        match (keychain, self.change_descriptor.as_ref()) {
-            (KeychainKind::External, _) => Ok(Some(self.descriptor.clone())),
-            (KeychainKind::Internal, None) => Ok(None),
-            (KeychainKind::Internal, Some(desc)) => Ok(Some(desc.clone())),
-        }
+    pub fn public_descriptor(&self, keychain: KeychainKind) -> Option<&ExtendedDescriptor> {
+        self.keychain_tracker.txout_index.keychains().get(&keychain)
     }
 
     /// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass
@@ -1217,22 +1197,24 @@ where
             if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() {
                 continue;
             }
-            // if the height is None in the database it means it's still unconfirmed, so consider
-            // that as a very high value
-            let create_height = self
-                .database
-                .borrow()
-                .get_tx(&input.previous_output.txid, false)?
-                .map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
+            let confirmation_height = self
+                .keychain_tracker
+                .chain()
+                .tx_position(input.previous_output.txid)
+                .map(|conftime| match conftime {
+                    &ConfirmationTime::Confirmed { height, .. } => height,
+                    ConfirmationTime::Unconfirmed => u32::MAX,
+                });
             let last_sync_height = self
-                .database()
-                .get_sync_time()?
-                .map(|sync_time| sync_time.block_time.height);
+                .keychain_tracker
+                .chain()
+                .latest_checkpoint()
+                .map(|block_id| block_id.height);
             let current_height = sign_options.assume_height.or(last_sync_height);
 
             debug!(
-                "Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
-                n, input.previous_output, create_height, current_height
+                "Input #{} - {}, using `confirmation_height` = {:?}, `current_height` = {:?}",
+                n, input.previous_output, confirmation_height, current_height
             );
 
             // - Try to derive the descriptor by looking at the txout. If it's in our database, we
@@ -1244,19 +1226,19 @@ where
             let desc = psbt
                 .get_utxo_for(n)
                 .map(|txout| self.get_descriptor_for_txout(&txout))
-                .transpose()?
                 .flatten()
                 .or_else(|| {
-                    self.descriptor.derive_from_psbt_input(
-                        psbt_input,
-                        psbt.get_utxo_for(n),
-                        &self.secp,
-                    )
-                })
-                .or_else(|| {
-                    self.change_descriptor.as_ref().and_then(|desc| {
-                        desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp)
-                    })
+                    self.keychain_tracker
+                        .txout_index
+                        .keychains()
+                        .iter()
+                        .find_map(|(_, desc)| {
+                            desc.derive_from_psbt_input(
+                                psbt_input,
+                                psbt.get_utxo_for(n),
+                                &self.secp,
+                            )
+                        })
                 });
 
             match desc {
@@ -1267,7 +1249,7 @@ where
                         (
                             PsbtInputSatisfier::new(psbt, n),
                             After::new(current_height, false),
-                            Older::new(current_height, create_height, false),
+                            Older::new(current_height, confirmation_height, false),
                         ),
                     ) {
                         Ok(_) => {
@@ -1298,113 +1280,44 @@ where
 
     /// Returns the descriptor used to create addresses for a particular `keychain`.
     pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
-        let (descriptor, _) = self._get_descriptor_for_keychain(keychain);
-        descriptor
+        self.public_descriptor(self.map_keychain(keychain))
+            .expect("we mapped it to external if it doesn't exist")
     }
 
-    // Internals
-
-    fn _get_descriptor_for_keychain(
-        &self,
-        keychain: KeychainKind,
-    ) -> (&ExtendedDescriptor, KeychainKind) {
-        match keychain {
-            KeychainKind::Internal if self.change_descriptor.is_some() => (
-                self.change_descriptor.as_ref().unwrap(),
-                KeychainKind::Internal,
-            ),
-            _ => (&self.descriptor, KeychainKind::External),
-        }
+    /// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
+    /// Otherwise, it will return the index of the highest address it has derived.
+    pub fn derivation_index(&self, keychain: KeychainKind) -> Option<u32> {
+        self.keychain_tracker
+            .txout_index
+            .last_revealed_index(&keychain)
     }
 
-    fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<DerivedDescriptor>, Error> {
-        Ok(self
-            .database
-            .borrow()
-            .get_path_from_script_pubkey(&txout.script_pubkey)?
-            .map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain), child))
-            .map(|(desc, child)| desc.at_derivation_index(child)))
+    /// The index of the next address that you would get if you were to ask the wallet for a new address
+    pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
+        self.keychain_tracker.txout_index.next_index(&keychain).0
     }
 
-    fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
-        let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
-        let index = match descriptor.has_wildcard() {
-            false => 0,
-            true => self.database.borrow_mut().increment_last_index(keychain)?,
-        };
-
-        if self
-            .database
-            .borrow()
-            .get_script_pubkey_from_path(keychain, index)?
-            .is_none()
+    fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind {
+        if keychain == KeychainKind::Internal
+            && self.public_descriptor(KeychainKind::Internal).is_none()
         {
-            self.cache_addresses(keychain, index, CACHE_ADDR_BATCH_SIZE)?;
-        }
-
-        Ok(index)
-    }
-
-    fn fetch_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
-        let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
-        let index = match descriptor.has_wildcard() {
-            false => Some(0),
-            true => self.database.borrow_mut().get_last_index(keychain)?,
-        };
-
-        if let Some(i) = index {
-            Ok(i)
+            return KeychainKind::External;
         } else {
-            self.fetch_and_increment_index(keychain)
+            keychain
         }
     }
 
-    fn set_index(&self, keychain: KeychainKind, index: u32) -> Result<(), Error> {
-        self.database.borrow_mut().set_last_index(keychain, index)?;
-        Ok(())
-    }
-
-    fn cache_addresses(
-        &self,
-        keychain: KeychainKind,
-        from: u32,
-        mut count: u32,
-    ) -> Result<(), Error> {
-        let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
-        if !descriptor.has_wildcard() {
-            if from > 0 {
-                return Ok(());
-            }
-
-            count = 1;
-        }
-
-        let mut address_batch = self.database.borrow().begin_batch();
-
-        let start_time = time::Instant::new();
-        for i in from..(from + count) {
-            address_batch.set_script_pubkey(
-                &descriptor.at_derivation_index(i).script_pubkey(),
-                keychain,
-                i,
-            )?;
-        }
-
-        info!(
-            "Derivation of {} addresses from {} took {} ms",
-            count,
-            from,
-            start_time.elapsed().as_millis()
-        );
-
-        self.database.borrow_mut().commit_batch(address_batch)?;
-
-        Ok(())
+    fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
+        let &(keychain, child) = self
+            .keychain_tracker
+            .txout_index
+            .index_of_spk(&txout.script_pubkey)?;
+        let descriptor = self.get_descriptor_for_keychain(keychain);
+        Some(descriptor.at_derivation_index(child))
     }
 
-    fn get_available_utxos(&self) -> Result<Vec<(LocalUtxo, usize)>, Error> {
-        Ok(self
-            .list_unspent()?
+    fn get_available_utxos(&self) -> Vec<(LocalUtxo, usize)> {
+        self.list_unspent()
             .into_iter()
             .map(|utxo| {
                 let keychain = utxo.keychain;
@@ -1415,7 +1328,7 @@ where
                         .unwrap(),
                 )
             })
-            .collect())
+            .collect()
     }
 
     /// Given the options returns the list of utxos that must be used to form the
@@ -1431,10 +1344,10 @@ where
         manual_only: bool,
         must_only_use_confirmed_tx: bool,
         current_height: Option<u32>,
-    ) -> Result<(Vec<WeightedUtxo>, Vec<WeightedUtxo>), Error> {
+    ) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
         //    must_spend <- manually selected utxos
         //    may_spend  <- all other available utxos
-        let mut may_spend = self.get_available_utxos()?;
+        let mut may_spend = self.get_available_utxos();
 
         may_spend.retain(|may_spend| {
             !manually_selected
@@ -1446,47 +1359,46 @@ where
         // NOTE: we are intentionally ignoring `unspendable` here. i.e manual
         // selection overrides unspendable.
         if manual_only {
-            return Ok((must_spend, vec![]));
+            return (must_spend, vec![]);
         }
 
-        let database = self.database.borrow();
         let satisfies_confirmed = may_spend
             .iter()
             .map(|u| {
-                database
-                    .get_tx(&u.0.outpoint.txid, true)
-                    .map(|tx| match tx {
-                        // We don't have the tx in the db for some reason,
-                        // so we can't know for sure if it's mature or not.
-                        // We prefer not to spend it.
-                        None => false,
-                        Some(tx) => {
-                            // Whether the UTXO is mature and, if needed, confirmed
-                            let mut spendable = true;
-                            if must_only_use_confirmed_tx && tx.confirmation_time.is_none() {
-                                return false;
-                            }
-                            if tx
-                                .transaction
-                                .expect("We specifically ask for the transaction above")
-                                .is_coin_base()
-                            {
-                                if let Some(current_height) = current_height {
-                                    match &tx.confirmation_time {
-                                        Some(t) => {
-                                            // https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
-                                            spendable &= (current_height.saturating_sub(t.height))
-                                                >= COINBASE_MATURITY;
-                                        }
-                                        None => spendable = false,
+                let txid = u.0.outpoint.txid;
+                let tx = self.keychain_tracker.chain_graph().get_tx_in_chain(txid);
+                match tx {
+                    // We don't have the tx in the db for some reason,
+                    // so we can't know for sure if it's mature or not.
+                    // We prefer not to spend it.
+                    None => false,
+                    Some((confirmation_time, tx)) => {
+                        // Whether the UTXO is mature and, if needed, confirmed
+                        let mut spendable = true;
+                        if must_only_use_confirmed_tx && !confirmation_time.is_confirmed() {
+                            return false;
+                        }
+                        if tx.is_coin_base() {
+                            debug_assert!(
+                                confirmation_time.is_confirmed(),
+                                "coinbase must always be confirmed"
+                            );
+                            if let Some(current_height) = current_height {
+                                match confirmation_time {
+                                    ConfirmationTime::Confirmed { height, .. } => {
+                                        // https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
+                                        spendable &= (current_height.saturating_sub(*height))
+                                            >= COINBASE_MATURITY;
                                     }
+                                    ConfirmationTime::Unconfirmed => spendable = false,
                                 }
                             }
-                            spendable
                         }
-                    })
+                        spendable
+                    }
+                }
             })
-            .collect::<Result<Vec<_>, _>>()?;
+            .collect::<Vec<_>>();
 
         let mut i = 0;
         may_spend.retain(|u| {
@@ -1509,7 +1421,7 @@ where
             must_spend.append(&mut may_spend);
         }
 
-        Ok((must_spend, may_spend))
+        (must_spend, may_spend)
     }
 
     fn complete_transaction(
@@ -1521,10 +1433,11 @@ where
         let mut psbt = psbt::PartiallySignedTransaction::from_unsigned_tx(tx)?;
 
         if params.add_global_xpubs {
-            let mut all_xpubs = self.descriptor.get_extended_keys()?;
-            if let Some(change_descriptor) = &self.change_descriptor {
-                all_xpubs.extend(change_descriptor.get_extended_keys()?);
-            }
+            let all_xpubs = self
+                .keychanins()
+                .iter()
+                .flat_map(|(_, desc)| desc.get_extended_keys())
+                .collect::<Vec<_>>();
 
             for xpub in all_xpubs {
                 let origin = match xpub.origin {
@@ -1602,10 +1515,10 @@ where
     ) -> Result<psbt::Input, Error> {
         // Try to find the prev_script in our db to figure out if this is internal or external,
         // and the derivation index
-        let (keychain, child) = self
-            .database
-            .borrow()
-            .get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
+        let &(keychain, child) = self
+            .keychain_tracker
+            .txout_index
+            .index_of_spk(&utxo.txout.script_pubkey)
             .ok_or(Error::UnknownUtxo)?;
 
         let mut psbt_input = psbt::Input {
@@ -1621,12 +1534,12 @@ where
             .map_err(MiniscriptPsbtError::Conversion)?;
 
         let prev_output = utxo.outpoint;
-        if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? {
+        if let Some(prev_tx) = self.keychain_tracker.graph().get_tx(prev_output.txid) {
             if desc.is_witness() || desc.is_taproot() {
                 psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
             }
             if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
-                psbt_input.non_witness_utxo = Some(prev_tx);
+                psbt_input.non_witness_utxo = Some(prev_tx.clone());
             }
         }
         Ok(psbt_input)
@@ -1654,10 +1567,10 @@ where
 
         // Try to figure out the keychain and derivation for every input and output
         for (is_input, index, out) in utxos.into_iter() {
-            if let Some((keychain, child)) = self
-                .database
-                .borrow()
-                .get_path_from_script_pubkey(&out.script_pubkey)?
+            if let Some(&(keychain, child)) = self
+                .keychain_tracker
+                .txout_index
+                .index_of_spk(&out.script_pubkey)
             {
                 debug!(
                     "Found descriptor for input #{} {:?}/{}",
@@ -1680,87 +1593,6 @@ where
         Ok(())
     }
 
-    /// Return an immutable reference to the internal database
-    pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
-        self.database.borrow()
-    }
-
-    /// Sync the internal database with the blockchain
-    #[maybe_async]
-    pub fn sync<B: WalletSync + GetHeight>(
-        &self,
-        blockchain: &B,
-        sync_opts: SyncOptions,
-    ) -> Result<(), Error> {
-        debug!("Begin sync...");
-
-        // TODO: for the next runs, we cannot reuse the `sync_opts.progress` object due to trait
-        // restrictions
-        let mut progress_iter = sync_opts.progress.into_iter();
-        let mut new_progress = || {
-            progress_iter
-                .next()
-                .unwrap_or_else(|| Box::new(NoopProgress))
-        };
-
-        let run_setup = self.ensure_addresses_cached(CACHE_ADDR_BATCH_SIZE)?;
-        debug!("run_setup: {}", run_setup);
-
-        // TODO: what if i generate an address first and cache some addresses?
-        // TODO: we should sync if generating an address triggers a new batch to be stored
-
-        // We need to ensure descriptor is derivable to fullfil "missing cache", otherwise we will
-        // end up with an infinite loop
-        let has_wildcard = self.descriptor.has_wildcard()
-            && (self.change_descriptor.is_none()
-                || self.change_descriptor.as_ref().unwrap().has_wildcard());
-
-        // Restrict max rounds in case of faulty "missing cache" implementation by blockchain
-        let max_rounds = if has_wildcard { 100 } else { 1 };
-
-        for _ in 0..max_rounds {
-            let sync_res = if run_setup {
-                maybe_await!(blockchain.wallet_setup(&self.database, new_progress()))
-            } else {
-                maybe_await!(blockchain.wallet_sync(&self.database, new_progress()))
-            };
-
-            // If the error is the special `MissingCachedScripts` error, we return the number of
-            // scripts we should ensure cached.
-            // On any other error, we should return the error.
-            // On no error, we say `ensure_cache` is 0.
-            let ensure_cache = sync_res.map_or_else(
-                |e| match e {
-                    Error::MissingCachedScripts(inner) => {
-                        // each call to `WalletSync` is expensive, maximize on scripts to search for
-                        let extra =
-                            std::cmp::max(inner.missing_count as u32, CACHE_ADDR_BATCH_SIZE);
-                        let last = inner.last_count as u32;
-                        Ok(extra + last)
-                    }
-                    _ => Err(e),
-                },
-                |_| Ok(0_u32),
-            )?;
-
-            // cache and try again, break when there is nothing to cache
-            if !self.ensure_addresses_cached(ensure_cache)? {
-                break;
-            }
-        }
-
-        let sync_time = SyncTime {
-            block_time: BlockTime {
-                height: maybe_await!(blockchain.get_height())?,
-                timestamp: time::get_timestamp(),
-            },
-        };
-        debug!("Saving `sync_time` = {:?}", sync_time);
-        self.database.borrow_mut().set_sync_time(sync_time)?;
-
-        Ok(())
-    }
-
     /// Return the checksum of the public descriptor associated to `keychain`
     ///
     /// Internally calls [`Self::get_descriptor_for_keychain`] to fetch the right descriptor
@@ -1805,200 +1637,121 @@ where
     Ok(wallet_name)
 }
 
-/// Return a fake wallet that appears to be funded for testing.
-pub fn get_funded_wallet(
-    descriptor: &str,
-) -> (Wallet<AnyDatabase>, (String, Option<String>), bitcoin::Txid) {
-    let descriptors = testutils!(@descriptors (descriptor));
-    let wallet = Wallet::new(
-        &descriptors.0,
-        None,
-        Network::Regtest,
-        AnyDatabase::Memory(MemoryDatabase::new()),
-    )
-    .unwrap();
-
-    let funding_address_kix = 0;
-
-    let tx_meta = testutils! {
-            @tx ( (@external descriptors, funding_address_kix) => 50_000 ) (@confirmations 1)
-    };
-
-    wallet
-        .database
-        .borrow_mut()
-        .set_script_pubkey(
-            &bitcoin::Address::from_str(&tx_meta.output.get(0).unwrap().to_address)
-                .unwrap()
-                .script_pubkey(),
-            KeychainKind::External,
-            funding_address_kix,
-        )
-        .unwrap();
-    wallet
-        .database
-        .borrow_mut()
-        .set_last_index(KeychainKind::External, funding_address_kix)
-        .unwrap();
-
-    let txid = crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, Some(100));
-
-    (wallet, descriptors, txid)
-}
-
 #[cfg(test)]
 pub(crate) mod test {
-    use assert_matches::assert_matches;
-    use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
-
-    use crate::database::Database;
     use crate::types::KeychainKind;
+    use assert_matches::assert_matches;
+    use bdk_chain::TxHeight;
+    use bitcoin::hashes::Hash;
+    use bitcoin::{util::psbt, Network};
+    use bitcoin::{PackedLockTime, TxIn};
+    use core::str::FromStr;
 
     use super::*;
-    use crate::signer::{SignOptions, SignerError};
-    use crate::wallet::AddressIndex::{LastUnused, New, Peek, Reset};
+    use crate::signer::SignOptions;
+    use crate::wallet::AddressIndex::*;
 
-    // The satisfaction size of a P2WPKH is 112 WU =
-    // 1 (elements in witness) + 1 (OP_PUSH) + 33 (pk) + 1 (OP_PUSH) + 72 (signature + sighash) + 1*4 (script len)
-    // On the witness itself, we have to push once for the pk (33WU) and once for signature + sighash (72WU), for
-    // a total of 105 WU.
-    // Here, we push just once for simplicity, so we have to add an extra byte for the missing
-    // OP_PUSH.
-    const P2WPKH_FAKE_WITNESS_SIZE: usize = 106;
+    /// Return a fake wallet that appears to be funded for testing.
+    pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) {
+        let mut wallet = Wallet::new(descriptor, None, Network::Regtest).unwrap();
+        let address = wallet.get_address(AddressIndex::New).address;
 
-    #[test]
-    fn test_descriptor_checksum() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let checksum = wallet.descriptor_checksum(KeychainKind::External);
-        assert_eq!(checksum.len(), 8);
-        assert_eq!(
-            calc_checksum(&wallet.descriptor.to_string()).unwrap(),
-            checksum
-        );
-    }
+        let tx = Transaction {
+            version: 1,
+            lock_time: bitcoin::PackedLockTime(0),
+            input: vec![],
+            output: vec![TxOut {
+                value: 50_000,
+                script_pubkey: address.script_pubkey(),
+            }],
+        };
 
-    #[test]
-    fn test_db_checksum() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let desc = wallet.descriptor.to_string();
+        wallet
+            .insert_checkpoint(BlockId {
+                height: 1_000,
+                hash: BlockHash::all_zeros(),
+            })
+            .unwrap();
+        wallet
+            .insert_tx(
+                tx.clone(),
+                ConfirmationTime::Confirmed {
+                    height: 1_000,
+                    time: 100,
+                },
+            )
+            .unwrap();
 
-        let checksum = calc_checksum_bytes_internal(&desc, true).unwrap();
-        let checksum_inception = calc_checksum_bytes_internal(&desc, false).unwrap();
-        let checksum_invalid = [b'q'; 8];
+        (wallet, tx.txid())
+    }
 
-        let mut db = MemoryDatabase::new();
-        db.check_descriptor_checksum(KeychainKind::External, checksum)
-            .expect("failed to save actual checksum");
-        Wallet::db_checksum(&mut db, &desc, KeychainKind::External)
-            .expect("db that uses actual checksum should be supported");
+    fn receive_output(wallet: &mut Wallet, value: u64, height: TxHeight) -> OutPoint {
+        let tx = Transaction {
+            version: 1,
+            lock_time: PackedLockTime(0),
+            input: vec![],
+            output: vec![TxOut {
+                script_pubkey: wallet.get_address(LastUnused).script_pubkey(),
+                value,
+            }],
+        };
 
-        let mut db = MemoryDatabase::new();
-        db.check_descriptor_checksum(KeychainKind::External, checksum_inception)
-            .expect("failed to save checksum inception");
-        Wallet::db_checksum(&mut db, &desc, KeychainKind::External)
-            .expect("db that uses checksum inception should be supported");
+        wallet
+            .insert_tx(
+                tx.clone(),
+                match height {
+                    TxHeight::Confirmed(height) => ConfirmationTime::Confirmed {
+                        height,
+                        time: 42_000,
+                    },
+                    TxHeight::Unconfirmed => ConfirmationTime::Unconfirmed,
+                },
+            )
+            .unwrap();
 
-        let mut db = MemoryDatabase::new();
-        db.check_descriptor_checksum(KeychainKind::External, checksum_invalid)
-            .expect("failed to save invalid checksum");
-        Wallet::db_checksum(&mut db, &desc, KeychainKind::External)
-            .expect_err("db that uses invalid checksum should fail");
+        OutPoint {
+            txid: tx.txid(),
+            vout: 0,
+        }
     }
 
-    #[test]
-    fn test_get_funded_wallet_balance() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        assert_eq!(wallet.get_balance().unwrap().confirmed, 50000);
+    fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
+        let height = wallet.latest_checkpoint().map(|id| id.height).into();
+        receive_output(wallet, value, height)
     }
 
-    #[test]
-    fn test_cache_addresses_fixed() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new(
-            "wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
-            None,
-            Network::Testnet,
-            db,
-        )
-        .unwrap();
-
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835"
-        );
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835"
-        );
-
-        assert!(wallet
-            .database
-            .borrow_mut()
-            .get_script_pubkey_from_path(KeychainKind::External, 0)
-            .unwrap()
-            .is_some());
-        assert!(wallet
-            .database
-            .borrow_mut()
-            .get_script_pubkey_from_path(KeychainKind::Internal, 0)
-            .unwrap()
-            .is_none());
-    }
+    // The satisfaction size of a P2WPKH is 112 WU =
+    // 1 (elements in witness) + 1 (OP_PUSH) + 33 (pk) + 1 (OP_PUSH) + 72 (signature + sighash) + 1*4 (script len)
+    // On the witness itself, we have to push once for the pk (33WU) and once for signature + sighash (72WU), for
+    // a total of 105 WU.
+    // Here, we push just once for simplicity, so we have to add an extra byte for the missing
+    // OP_PUSH.
+    const P2WPKH_FAKE_WITNESS_SIZE: usize = 106;
 
     #[test]
-    fn test_cache_addresses() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
-
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
+    fn test_descriptor_checksum() {
+        let (wallet, _) = get_funded_wallet(get_test_wpkh());
+        let checksum = wallet.descriptor_checksum(KeychainKind::External);
+        assert_eq!(checksum.len(), 8);
 
-        assert!(wallet
-            .database
-            .borrow_mut()
-            .get_script_pubkey_from_path(KeychainKind::External, CACHE_ADDR_BATCH_SIZE - 1)
+        let raw_descriptor = wallet
+            .keychanins()
+            .iter()
+            .next()
             .unwrap()
-            .is_some());
-        assert!(wallet
-            .database
-            .borrow_mut()
-            .get_script_pubkey_from_path(KeychainKind::External, CACHE_ADDR_BATCH_SIZE)
+            .1
+            .to_string()
+            .split_once('#')
             .unwrap()
-            .is_none());
+            .0
+            .to_string();
+        assert_eq!(calc_checksum(&raw_descriptor).unwrap(), checksum);
     }
 
     #[test]
-    fn test_cache_addresses_refill() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
-
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-        assert!(wallet
-            .database
-            .borrow_mut()
-            .get_script_pubkey_from_path(KeychainKind::External, CACHE_ADDR_BATCH_SIZE - 1)
-            .unwrap()
-            .is_some());
-
-        for _ in 0..CACHE_ADDR_BATCH_SIZE {
-            wallet.get_address(New).unwrap();
-        }
-
-        assert!(wallet
-            .database
-            .borrow_mut()
-            .get_script_pubkey_from_path(KeychainKind::External, CACHE_ADDR_BATCH_SIZE * 2 - 1)
-            .unwrap()
-            .is_some());
+    fn test_get_funded_wallet_balance() {
+        let (wallet, _) = get_funded_wallet(get_test_wpkh());
+        assert_eq!(wallet.get_balance().confirmed, 50000);
     }
 
     pub(crate) fn get_test_wpkh() -> &'static str {
@@ -2055,28 +1808,28 @@ pub(crate) mod test {
             let mut tx = $psbt.clone().extract_tx();
             $(
                 $( $add_signature )*
-                for txin in &mut tx.input {
-                    txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-                }
+                    for txin in &mut tx.input {
+                        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
+                    }
             )*
 
-            #[allow(unused_mut)]
+                #[allow(unused_mut)]
             #[allow(unused_assignments)]
             let mut dust_change = false;
             $(
                 $( $dust_change )*
-                dust_change = true;
+                    dust_change = true;
             )*
 
-            let fee_amount = psbt
+                let fee_amount = psbt
                 .inputs
                 .iter()
                 .fold(0, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value)
                 - psbt
-                    .unsigned_tx
-                    .output
-                    .iter()
-                    .fold(0, |acc, o| acc + o.value);
+                .unsigned_tx
+                .output
+                .iter()
+                .fold(0, |acc, o| acc + o.value);
 
             assert_eq!(fee_amount, $fees);
 
@@ -2105,15 +1858,15 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "NoRecipients")]
     fn test_create_tx_empty_recipients() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         wallet.build_tx().finish().unwrap();
     }
 
     #[test]
     #[should_panic(expected = "NoUtxosSelected")]
     fn test_create_tx_manually_selected_empty_utxos() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2124,8 +1877,8 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "Invalid version `0`")]
     fn test_create_tx_version_0() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2138,8 +1891,8 @@ pub(crate) mod test {
         expected = "TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
     )]
     fn test_create_tx_version_1_csv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2149,8 +1902,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_custom_version() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2161,99 +1914,50 @@ pub(crate) mod test {
     }
 
     #[test]
-    fn test_create_tx_default_locktime() {
-        let descriptors = testutils!(@descriptors (get_test_wpkh()));
-        let wallet = Wallet::new(
-            &descriptors.0,
-            None,
-            Network::Regtest,
-            AnyDatabase::Memory(MemoryDatabase::new()),
-        )
-        .unwrap();
+    fn test_create_tx_default_locktime_is_last_sync_height() {
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
 
-        let tx_meta = testutils! {
-                @tx ( (@external descriptors, 0) => 50_000 )
-        };
-
-        // Add the transaction to our db, but do not sync the db.
-        crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, None);
-
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
 
         // Since we never synced the wallet we don't have a last_sync_height
         // we could use to try to prevent fee sniping. We default to 0.
-        assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(0));
-    }
-
-    #[test]
-    fn test_create_tx_fee_sniping_locktime_provided_height() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let sync_time = SyncTime {
-            block_time: BlockTime {
-                height: 24,
-                timestamp: 0,
-            },
-        };
-        wallet
-            .database
-            .borrow_mut()
-            .set_sync_time(sync_time)
-            .unwrap();
-        let current_height = 25;
-        builder.current_height(current_height);
-        let (psbt, _) = builder.finish().unwrap();
-
-        // current_height will override the last sync height
-        assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(current_height));
+        assert_eq!(psbt.unsigned_tx.lock_time.0, 1_000);
     }
 
     #[test]
     fn test_create_tx_fee_sniping_locktime_last_sync() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
-        let sync_time = SyncTime {
-            block_time: BlockTime {
-                height: 25,
-                timestamp: 0,
-            },
-        };
-        wallet
-            .database
-            .borrow_mut()
-            .set_sync_time(sync_time.clone())
-            .unwrap();
+
         let (psbt, _) = builder.finish().unwrap();
 
         // If there's no current_height we're left with using the last sync height
         assert_eq!(
-            psbt.unsigned_tx.lock_time,
-            PackedLockTime(sync_time.block_time.height)
+            psbt.unsigned_tx.lock_time.0,
+            wallet.latest_checkpoint().unwrap().height
         );
     }
 
     #[test]
     fn test_create_tx_default_locktime_cltv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
 
-        assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(100_000));
+        assert_eq!(psbt.unsigned_tx.lock_time.0, 100_000);
     }
 
     #[test]
     fn test_create_tx_custom_locktime() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2264,20 +1968,20 @@ pub(crate) mod test {
         // When we explicitly specify a nlocktime
         // we don't try any fee sniping prevention trick
         // (we ignore the current_height)
-        assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(630_000));
+        assert_eq!(psbt.unsigned_tx.lock_time.0, 630_000);
     }
 
     #[test]
     fn test_create_tx_custom_locktime_compatible_with_cltv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
             .nlocktime(LockTime::from_height(630_000).unwrap());
         let (psbt, _) = builder.finish().unwrap();
 
-        assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(630_000));
+        assert_eq!(psbt.unsigned_tx.lock_time.0, 630_000);
     }
 
     #[test]
@@ -2285,8 +1989,8 @@ pub(crate) mod test {
         expected = "TxBuilder requested timelock of `Blocks(Height(50000))`, but at least `Blocks(Height(100000))` is required to spend from this script"
     )]
     fn test_create_tx_custom_locktime_incompatible_with_cltv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2296,8 +2000,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_no_rbf_csv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -2307,8 +2011,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_with_default_rbf_csv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2324,8 +2028,8 @@ pub(crate) mod test {
         expected = "Cannot enable RBF with nSequence `Sequence(3)` given a required OP_CSV of `Sequence(6)`"
     )]
     fn test_create_tx_with_custom_rbf_csv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2335,8 +2039,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_no_rbf_cltv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -2347,8 +2051,8 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
     fn test_create_tx_invalid_rbf_sequence() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2358,8 +2062,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_custom_rbf_sequence() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2371,29 +2075,13 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_default_sequence() {
-        let descriptors = testutils!(@descriptors (get_test_wpkh()));
-        let wallet = Wallet::new(
-            &descriptors.0,
-            None,
-            Network::Regtest,
-            AnyDatabase::Memory(MemoryDatabase::new()),
-        )
-        .unwrap();
-
-        let tx_meta = testutils! {
-                @tx ( (@external descriptors, 0) => 50_000 )
-        };
-
-        // Add the transaction to our db, but do not sync the db. Unsynced db
-        // should trigger the default sequence value for a new transaction as 0xFFFFFFFF
-        crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, None);
-
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
 
-        assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFF));
+        assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
     }
 
     #[test]
@@ -2401,8 +2089,8 @@ pub(crate) mod test {
         expected = "The `change_policy` can be set only if the wallet has a change_descriptor"
     )]
     fn test_create_tx_change_policy_no_internal() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2412,8 +2100,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_drain_wallet_and_drain_to() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, details) = builder.finish().unwrap();
@@ -2427,9 +2115,9 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
-        let drain_addr = wallet.get_address(New).unwrap();
+        let drain_addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 20_000)
@@ -2453,11 +2141,10 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_drain_to_and_utxos() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let utxos: Vec<_> = wallet
             .get_available_utxos()
-            .unwrap()
             .into_iter()
             .map(|(u, _)| u.outpoint)
             .collect();
@@ -2478,8 +2165,8 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "NoRecipients")]
     fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let drain_addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let drain_addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(drain_addr.script_pubkey());
         builder.finish().unwrap();
@@ -2487,8 +2174,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_default_fee_rate() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, details) = builder.finish().unwrap();
@@ -2498,8 +2185,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_custom_fee_rate() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2511,8 +2198,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_absolute_fee() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
@@ -2530,8 +2217,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_absolute_zero_fee() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
@@ -2550,8 +2237,8 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "InsufficientFunds")]
     fn test_create_tx_absolute_high_fee() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
@@ -2564,8 +2251,8 @@ pub(crate) mod test {
     fn test_create_tx_add_change() {
         use super::tx_builder::TxOrdering;
 
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2582,8 +2269,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_skip_change_dust() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 49_800);
         let (psbt, details) = builder.finish().unwrap();
@@ -2596,8 +2283,8 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "InsufficientFunds")]
     fn test_create_tx_drain_to_dust_amount() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         // very high fee rate, so that the only output would be below dust
         let mut builder = wallet.build_tx();
         builder
@@ -2609,8 +2296,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_ordering_respected() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 30_000)
@@ -2629,8 +2316,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_default_sighash() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 30_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -2640,8 +2327,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_custom_sighash() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 30_000)
@@ -2659,8 +2346,8 @@ pub(crate) mod test {
         use bitcoin::util::bip32::{DerivationPath, Fingerprint};
         use std::str::FromStr;
 
-        let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
@@ -2680,21 +2367,20 @@ pub(crate) mod test {
         use bitcoin::util::bip32::{DerivationPath, Fingerprint};
         use std::str::FromStr;
 
-        let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
-        // cache some addresses
-        wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
 
-        let addr = testutils!(@external descriptors, 5);
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
 
         assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1);
+        let expected_derivation_path = format!("m/44'/0'/0'/0/{}", addr.index);
         assert_eq!(
             psbt.outputs[0].bip32_derivation.values().next().unwrap(),
             &(
                 Fingerprint::from_str("d34db33f").unwrap(),
-                DerivationPath::from_str("m/44'/0'/0'/0/5").unwrap()
+                DerivationPath::from_str(&expected_derivation_path).unwrap()
             )
         );
     }
@@ -2703,9 +2389,9 @@ pub(crate) mod test {
     fn test_create_tx_set_redeem_script_p2sh() {
         use bitcoin::hashes::hex::FromHex;
 
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
@@ -2726,9 +2412,9 @@ pub(crate) mod test {
     fn test_create_tx_set_witness_script_p2wsh() {
         use bitcoin::hashes::hex::FromHex;
 
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
@@ -2749,9 +2435,9 @@ pub(crate) mod test {
     fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
         use bitcoin::hashes::hex::FromHex;
 
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
@@ -2769,9 +2455,9 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_non_witness_utxo() {
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
@@ -2782,9 +2468,9 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_only_witness_utxo() {
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
@@ -2798,9 +2484,9 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_shwpkh_has_witness_utxo() {
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
@@ -2810,9 +2496,9 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() {
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
@@ -2823,19 +2509,26 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_add_utxo() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        let small_output_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let small_output_tx = Transaction {
+            input: vec![],
+            output: vec![TxOut {
+                value: 25_000,
+                script_pubkey: wallet.get_address(New).address.script_pubkey(),
+            }],
+            version: 0,
+            lock_time: PackedLockTime(0),
+        };
+        wallet
+            .insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
+            .unwrap();
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 30_000)
             .add_utxo(OutPoint {
-                txid: small_output_txid,
+                txid: small_output_tx.txid(),
                 vout: 0,
             })
             .unwrap();
@@ -2852,19 +2545,27 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "InsufficientFunds")]
     fn test_create_tx_manually_selected_insufficient() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        let small_output_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let small_output_tx = Transaction {
+            input: vec![],
+            output: vec![TxOut {
+                value: 25_000,
+                script_pubkey: wallet.get_address(New).address.script_pubkey(),
+            }],
+            version: 0,
+            lock_time: PackedLockTime(0),
+        };
+
+        wallet
+            .insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
+            .unwrap();
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 30_000)
             .add_utxo(OutPoint {
-                txid: small_output_txid,
+                txid: small_output_tx.txid(),
                 vout: 0,
             })
             .unwrap()
@@ -2875,7 +2576,7 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "SpendingPolicyRequired(External)")]
     fn test_create_tx_policy_path_required() {
-        let (wallet, _, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
+        let (mut wallet, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
@@ -2885,22 +2586,19 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_policy_path_no_csv() {
-        let descriptors = testutils!(@descriptors (get_test_wpkh()));
-        let wallet = Wallet::new(
-            &descriptors.0,
-            None,
-            Network::Regtest,
-            AnyDatabase::Memory(MemoryDatabase::new()),
-        )
-        .unwrap();
+        let descriptors = get_test_wpkh();
+        let mut wallet = Wallet::new(descriptors, None, Network::Regtest).unwrap();
 
-        let tx_meta = testutils! {
-                @tx ( (@external descriptors, 0) => 50_000 )
+        let tx = Transaction {
+            version: 0,
+            lock_time: PackedLockTime(0),
+            input: vec![],
+            output: vec![TxOut {
+                value: 50_000,
+                script_pubkey: wallet.get_address(New).script_pubkey(),
+            }],
         };
-
-        // Add the transaction to our db, but do not sync the db. Unsynced db
-        // should trigger the default sequence value for a new transaction as 0xFFFFFFFF
-        crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, None);
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
         let root_id = external_policy.id;
@@ -2919,7 +2617,7 @@ pub(crate) mod test {
 
     #[test]
     fn test_create_tx_policy_path_use_csv() {
-        let (wallet, _, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
+        let (mut wallet, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
 
         let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
         let root_id = external_policy.id;
@@ -2941,8 +2639,8 @@ pub(crate) mod test {
         use bitcoin::hashes::hex::FromHex;
         use bitcoin::util::bip32;
 
-        let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2959,12 +2657,12 @@ pub(crate) mod test {
 
     #[test]
     fn test_add_foreign_utxo() {
-        let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
-        let (wallet2, _, _) =
+        let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+        let (wallet2, _) =
             get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let utxo = wallet2.list_unspent().unwrap().remove(0);
+        let utxo = wallet2.list_unspent().remove(0);
         let foreign_utxo_satisfaction = wallet2
             .get_descriptor_for_keychain(KeychainKind::External)
             .max_satisfaction_weight()
@@ -3027,13 +2725,14 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "Generic(\"Foreign utxo missing witness_utxo or non_witness_utxo\")")]
     fn test_add_foreign_utxo_invalid_psbt_input() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let mut builder = wallet.build_tx();
-        let outpoint = wallet.list_unspent().unwrap()[0].outpoint;
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let outpoint = wallet.list_unspent()[0].outpoint;
         let foreign_utxo_satisfaction = wallet
             .get_descriptor_for_keychain(KeychainKind::External)
             .max_satisfaction_weight()
             .unwrap();
+
+        let mut builder = wallet.build_tx();
         builder
             .add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction)
             .unwrap();
@@ -3041,27 +2740,13 @@ pub(crate) mod test {
 
     #[test]
     fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
-        let (wallet1, _, txid1) = get_funded_wallet(get_test_wpkh());
-        let (wallet2, _, txid2) =
+        let (mut wallet1, txid1) = get_funded_wallet(get_test_wpkh());
+        let (wallet2, txid2) =
             get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
 
-        let utxo2 = wallet2.list_unspent().unwrap().remove(0);
-        let tx1 = wallet1
-            .database
-            .borrow()
-            .get_tx(&txid1, true)
-            .unwrap()
-            .unwrap()
-            .transaction
-            .unwrap();
-        let tx2 = wallet2
-            .database
-            .borrow()
-            .get_tx(&txid2, true)
-            .unwrap()
-            .unwrap()
-            .transaction
-            .unwrap();
+        let utxo2 = wallet2.list_unspent().remove(0);
+        let tx1 = wallet1.get_tx(txid1, true).unwrap().transaction.unwrap();
+        let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap();
 
         let satisfaction_weight = wallet2
             .get_descriptor_for_keychain(KeychainKind::External)
@@ -3099,11 +2784,11 @@ pub(crate) mod test {
 
     #[test]
     fn test_add_foreign_utxo_only_witness_utxo() {
-        let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
-        let (wallet2, _, txid2) =
+        let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+        let (wallet2, txid2) =
             get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let utxo2 = wallet2.list_unspent().unwrap().remove(0);
+        let utxo2 = wallet2.list_unspent().remove(0);
 
         let satisfaction_weight = wallet2
             .get_descriptor_for_keychain(KeychainKind::External)
@@ -3146,14 +2831,7 @@ pub(crate) mod test {
 
         {
             let mut builder = builder.clone();
-            let tx2 = wallet2
-                .database
-                .borrow()
-                .get_tx(&txid2, true)
-                .unwrap()
-                .unwrap()
-                .transaction
-                .unwrap();
+            let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap();
             let psbt_input = psbt::Input {
                 non_witness_utxo: Some(tx2),
                 ..Default::default()
@@ -3171,8 +2849,8 @@ pub(crate) mod test {
     #[test]
     fn test_get_psbt_input() {
         // this should grab a known good utxo and set the input
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        for utxo in wallet.list_unspent().unwrap() {
+        let (wallet, _) = get_funded_wallet(get_test_wpkh());
+        for utxo in wallet.list_unspent() {
             let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap();
             assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some());
         }
@@ -3183,8 +2861,8 @@ pub(crate) mod test {
         expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")"
     )]
     fn test_create_tx_global_xpubs_origin_missing() {
-        let (wallet, _, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -3197,8 +2875,8 @@ pub(crate) mod test {
         use bitcoin::hashes::hex::FromHex;
         use bitcoin::util::bip32;
 
-        let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -3218,39 +2896,39 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "IrreplaceableTransaction")]
     fn test_bump_fee_irreplaceable_tx() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, mut details) = builder.finish().unwrap();
+        let (psbt, _) = builder.finish().unwrap();
 
         let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the utxos, we know they can't be used anyways
-        details.transaction = Some(tx);
-        wallet.database.borrow_mut().set_tx(&details).unwrap();
-
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
         wallet.build_fee_bump(txid).unwrap().finish().unwrap();
     }
 
     #[test]
     #[should_panic(expected = "TransactionConfirmed")]
     fn test_bump_fee_confirmed_tx() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, mut details) = builder.finish().unwrap();
+        let (psbt, _) = builder.finish().unwrap();
 
         let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the utxos, we know they can't be used anyways
-        details.transaction = Some(tx);
-        details.confirmation_time = Some(BlockTime {
-            timestamp: 12345678,
-            height: 42,
-        });
-        wallet.database.borrow_mut().set_tx(&details).unwrap();
+
+        wallet
+            .insert_tx(
+                tx,
+                ConfirmationTime::Confirmed {
+                    height: 42,
+                    time: 42_000,
+                },
+            )
+            .unwrap();
 
         wallet.build_fee_bump(txid).unwrap().finish().unwrap();
     }
@@ -3258,19 +2936,18 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "FeeRateTooLow")]
     fn test_bump_fee_low_fee_rate() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
             .enable_rbf();
-        let (psbt, mut details) = builder.finish().unwrap();
+        let (psbt, _) = builder.finish().unwrap();
 
         let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the utxos, we know they can't be used anyways
-        details.transaction = Some(tx);
-        wallet.database.borrow_mut().set_tx(&details).unwrap();
+
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_rate(FeeRate::from_sat_per_vb(1.0));
@@ -3280,19 +2957,18 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "FeeTooLow")]
     fn test_bump_fee_low_abs() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
             .enable_rbf();
-        let (psbt, mut details) = builder.finish().unwrap();
+        let (psbt, _) = builder.finish().unwrap();
 
         let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the utxos, we know they can't be used anyways
-        details.transaction = Some(tx);
-        wallet.database.borrow_mut().set_tx(&details).unwrap();
+
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_absolute(10);
@@ -3302,19 +2978,17 @@ pub(crate) mod test {
     #[test]
     #[should_panic(expected = "FeeTooLow")]
     fn test_bump_fee_zero_abs() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
             .enable_rbf();
-        let (psbt, mut details) = builder.finish().unwrap();
+        let (psbt, _) = builder.finish().unwrap();
 
         let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the utxos, we know they can't be used anyways
-        details.transaction = Some(tx);
-        wallet.database.borrow_mut().set_tx(&details).unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_absolute(0);
@@ -3323,30 +2997,16 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_reduce_change() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
+        let (psbt, original_details) = builder.finish().unwrap();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the new utxos, we know they can't be used anyways
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf();
@@ -3379,34 +3039,6 @@ pub(crate) mod test {
         );
 
         assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature);
-    }
-
-    #[test]
-    fn test_bump_fee_absolute_reduce_change() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
-        let txid = tx.txid();
-        // skip saving the new utxos, we know they can't be used anyways
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_absolute(200);
@@ -3449,30 +3081,17 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_reduce_single_recipient() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
             .drain_wallet()
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
+        let (psbt, original_details) = builder.finish().unwrap();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder
@@ -3493,30 +3112,17 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_absolute_reduce_single_recipient() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
             .drain_wallet()
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
+        let (psbt, original_details) = builder.finish().unwrap();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder
@@ -3537,42 +3143,42 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_drain_wallet() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         // receive an extra tx so that our wallet has two utxos.
-        let incoming_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
-        let outpoint = OutPoint {
-            txid: incoming_txid,
-            vout: 0,
+        let tx = Transaction {
+            version: 1,
+            lock_time: PackedLockTime(0),
+            input: vec![],
+            output: vec![TxOut {
+                value: 25_000,
+                script_pubkey: wallet.get_address(New).script_pubkey(),
+            }],
         };
+        wallet
+            .insert_tx(
+                tx.clone(),
+                ConfirmationTime::Confirmed {
+                    height: wallet.latest_checkpoint().unwrap().height,
+                    time: 42_000,
+                },
+            )
+            .unwrap();
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
-            .add_utxo(outpoint)
+            .add_utxo(OutPoint {
+                txid: tx.txid(),
+                vout: 0,
+            })
             .unwrap()
             .manually_selected_only()
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
+        let (psbt, original_details) = builder.finish().unwrap();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
         assert_eq!(original_details.sent, 25_000);
 
         // for the new feerate, it should be enough to reduce the output, but since we specify
@@ -3584,25 +3190,33 @@ pub(crate) mod test {
             .unwrap()
             .fee_rate(FeeRate::from_sat_per_vb(5.0));
         let (_, details) = builder.finish().unwrap();
+
         assert_eq!(details.sent, 75_000);
     }
 
     #[test]
     #[should_panic(expected = "InsufficientFunds")]
     fn test_bump_fee_remove_output_manually_selected_only() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         // receive an extra tx so that our wallet has two utxos. then we manually pick only one of
         // them, and make sure that `bump_fee` doesn't try to add more. This fails because we've
         // told the wallet it's not allowed to add more inputs AND it can't reduce the value of the
         // existing output. In other words, bump_fee + manually_selected_only is always an error
         // unless you've also set "allow_shrinking" OR there is a change output.
-        let incoming_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        let init_tx = Transaction {
+            version: 1,
+            lock_time: PackedLockTime(0),
+            input: vec![],
+            output: vec![TxOut {
+                script_pubkey: wallet.get_address(New).script_pubkey(),
+                value: 25_000,
+            }],
+        };
+        wallet
+            .insert_tx(init_tx.clone(), wallet.transactions().last().unwrap().0)
+            .unwrap();
         let outpoint = OutPoint {
-            txid: incoming_txid,
+            txid: init_tx.txid(),
             vout: 0,
         };
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
@@ -3613,23 +3227,10 @@ pub(crate) mod test {
             .unwrap()
             .manually_selected_only()
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
+        let (psbt, original_details) = builder.finish().unwrap();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
         assert_eq!(original_details.sent, 25_000);
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
@@ -3641,36 +3242,29 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_add_input() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let init_tx = Transaction {
+            version: 1,
+            lock_time: PackedLockTime(0),
+            input: vec![],
+            output: vec![TxOut {
+                script_pubkey: wallet.get_address(New).script_pubkey(),
+                value: 25_000,
+            }],
+        };
+        wallet
+            .insert_tx(init_tx.clone(), wallet.transactions().last().unwrap().0)
+            .unwrap();
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 45_000)
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
+        let (psbt, original_details) = builder.finish().unwrap();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the new utxos, we know they can't be used anyways
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
@@ -3704,36 +3298,17 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_absolute_add_input() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
-
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        receive_output_in_latest_block(&mut wallet, 25_000);
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 45_000)
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
+        let (psbt, original_details) = builder.finish().unwrap();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the new utxos, we know they can't be used anyways
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_absolute(6_000);
@@ -3767,44 +3342,23 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_no_change_add_input_and_change() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        let incoming_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let op = receive_output_in_latest_block(&mut wallet, 25_000);
 
         // initially make a tx without change by using `drain_to`
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
-            .add_utxo(OutPoint {
-                txid: incoming_txid,
-                vout: 0,
-            })
+            .add_utxo(op)
             .unwrap()
             .manually_selected_only()
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
+        let (psbt, original_details) = builder.finish().unwrap();
 
-        let mut tx = psbt.extract_tx();
+        let tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the new utxos, we know they can't be used anyways
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
-        }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         // now bump the fees without using `allow_shrinking`. the wallet should add an
         // extra input and a change output, and leave the original output untouched
@@ -3844,39 +3398,23 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_add_input_change_dust() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
-
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        receive_output_in_latest_block(&mut wallet, 25_000);
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 45_000)
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
+        let (psbt, original_details) = builder.finish().unwrap();
         let mut tx = psbt.extract_tx();
-        assert_eq!(tx.input.len(), 1);
-        assert_eq!(tx.output.len(), 2);
-        let txid = tx.txid();
-        // skip saving the new utxos, we know they can't be used anyways
         for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
+            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // to get realisitc weight
         }
         let original_tx_weight = tx.weight();
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        assert_eq!(tx.input.len(), 1);
+        assert_eq!(tx.output.len(), 2);
+        let txid = tx.txid();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         // We set a fee high enough that during rbf we are forced to add
@@ -3921,45 +3459,28 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_force_add_input() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        let incoming_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 45_000)
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
+        let (psbt, original_details) = builder.finish().unwrap();
         let mut tx = psbt.extract_tx();
         let txid = tx.txid();
-        // skip saving the new utxos, we know they can't be used anyways
         for txin in &mut tx.input {
             txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
         }
-        original_details.transaction = Some(tx);
         wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
+            .insert_tx(tx.clone(), ConfirmationTime::Unconfirmed)
             .unwrap();
-
         // the new fee_rate is low enough that just reducing the change would be fine, but we force
         // the addition of an extra input with `add_utxo()`
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder
-            .add_utxo(OutPoint {
-                txid: incoming_txid,
-                vout: 0,
-            })
+            .add_utxo(incoming_op)
             .unwrap()
             .fee_rate(FeeRate::from_sat_per_vb(5.0));
         let (psbt, details) = builder.finish().unwrap();
@@ -3992,47 +3513,29 @@ pub(crate) mod test {
 
     #[test]
     fn test_bump_fee_absolute_force_add_input() {
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
-        let incoming_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+        let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 45_000)
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
+        let (psbt, original_details) = builder.finish().unwrap();
         let mut tx = psbt.extract_tx();
         let txid = tx.txid();
         // skip saving the new utxos, we know they can't be used anyways
         for txin in &mut tx.input {
             txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
         }
-        original_details.transaction = Some(tx);
         wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
+            .insert_tx(tx.clone(), ConfirmationTime::Unconfirmed)
             .unwrap();
 
         // the new fee_rate is low enough that just reducing the change would be fine, but we force
         // the addition of an extra input with `add_utxo()`
         let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder
-            .add_utxo(OutPoint {
-                txid: incoming_txid,
-                vout: 0,
-            })
-            .unwrap()
-            .fee_absolute(250);
+        builder.add_utxo(incoming_op).unwrap().fee_absolute(250);
         let (psbt, details) = builder.finish().unwrap();
 
         assert_eq!(details.sent, original_details.sent + 25_000);
@@ -4070,38 +3573,23 @@ pub(crate) mod test {
         // So, we fail with "InsufficientFunds", as per RBF rule 2:
         // The replacement transaction may only include an unconfirmed input
         // if that input was included in one of the original transactions.
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
             .drain_wallet()
             .drain_to(addr.script_pubkey())
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
+        let (psbt, __details) = builder.finish().unwrap();
         // Now we receive one transaction with 0 confirmations. We won't be able to use that for
         // fee bumping, as it's still unconfirmed!
-        crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 0)),
-            Some(100),
-        );
+        receive_output(&mut wallet, 25_000, TxHeight::Unconfirmed);
         let mut tx = psbt.extract_tx();
         let txid = tx.txid();
         for txin in &mut tx.input {
             txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
         }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
-
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder.fee_rate(FeeRate::from_sat_per_vb(25.0));
         builder.finish().unwrap();
@@ -4114,37 +3602,23 @@ pub(crate) mod test {
         // (BIP125 rule 2 only apply to newly added unconfirmed input, you can
         // always fee bump with an unconfirmed input if it was included in the
         // original transaction)
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         // We receive a tx with 0 confirmations, which will be used as an input
         // in the drain tx.
-        crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 0)),
-            Some(100),
-        );
+        receive_output(&mut wallet, 25_000, TxHeight::Unconfirmed);
         let mut builder = wallet.build_tx();
         builder
             .drain_wallet()
             .drain_to(addr.script_pubkey())
             .enable_rbf();
-        let (psbt, mut original_details) = builder.finish().unwrap();
+        let (psbt, _) = builder.finish().unwrap();
         let mut tx = psbt.extract_tx();
         let txid = tx.txid();
         for txin in &mut tx.input {
             txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-            wallet
-                .database
-                .borrow_mut()
-                .del_utxo(&txin.previous_output)
-                .unwrap();
         }
-        original_details.transaction = Some(tx);
-        wallet
-            .database
-            .borrow_mut()
-            .set_tx(&original_details)
-            .unwrap();
+        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
 
         let mut builder = wallet.build_fee_bump(txid).unwrap();
         builder
@@ -4164,19 +3638,15 @@ pub(crate) mod test {
         // This caused a bug in master where we would calculate the wrong fee
         // for a transaction.
         // See https://github.com/bitcoindevkit/bdk/issues/660
-        let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap();
         let fee_rate = FeeRate::from_sat_per_vb(2.01);
-        let incoming_txid = crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 8859 ) (@confirmations 1)),
-            Some(100),
-        );
+        let incoming_op = receive_output_in_latest_block(&mut wallet, 8859);
 
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(send_to.script_pubkey(), 8630)
-            .add_utxo(OutPoint::new(incoming_txid, 0))
+            .add_utxo(incoming_op)
             .unwrap()
             .enable_rbf()
             .fee_rate(fee_rate);
@@ -4188,8 +3658,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_sign_single_xprv() {
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -4203,8 +3673,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_sign_single_xprv_with_master_fingerprint_and_path() {
-        let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -4218,8 +3688,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_sign_single_xprv_bip44_path() {
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -4233,8 +3703,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_sign_single_xprv_sh_wpkh() {
-        let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -4248,9 +3718,9 @@ pub(crate) mod test {
 
     #[test]
     fn test_sign_single_wif() {
-        let (wallet, _, _) =
+        let (mut wallet, _) =
             get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -4264,8 +3734,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_sign_single_xprv_no_hd_keypaths() {
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -4282,7 +3752,7 @@ pub(crate) mod test {
 
     #[test]
     fn test_include_output_redeem_witness_script() {
-        let (wallet, _, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))");
+        let (mut wallet, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))");
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
@@ -4299,7 +3769,7 @@ pub(crate) mod test {
 
     #[test]
     fn test_signing_only_one_of_multiple_inputs() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
@@ -4343,10 +3813,10 @@ pub(crate) mod test {
 
     #[test]
     fn test_remove_partial_sigs_after_finalize_sign_option() {
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
 
         for remove_partial_sigs in &[true, false] {
-            let addr = wallet.get_address(New).unwrap();
+            let addr = wallet.get_address(New);
             let mut builder = wallet.build_tx();
             builder.drain_to(addr.script_pubkey()).drain_wallet();
             let mut psbt = builder.finish().unwrap().0;
@@ -4373,10 +3843,10 @@ pub(crate) mod test {
 
     #[test]
     fn test_try_finalize_sign_option() {
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
 
         for try_finalize in &[true, false] {
-            let addr = wallet.get_address(New).unwrap();
+            let addr = wallet.get_address(New);
             let mut builder = wallet.build_tx();
             builder.drain_to(addr.script_pubkey()).drain_wallet();
             let mut psbt = builder.finish().unwrap().0;
@@ -4409,8 +3879,8 @@ pub(crate) mod test {
     fn test_sign_nonstandard_sighash() {
         let sighash = EcdsaSighashType::NonePlusAnyoneCanPay;
 
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
@@ -4453,16 +3923,15 @@ pub(crate) mod test {
 
     #[test]
     fn test_unused_address() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                         None, Network::Testnet, db).unwrap();
+        let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                             None, Network::Testnet).unwrap();
 
         assert_eq!(
-            wallet.get_address(LastUnused).unwrap().to_string(),
+            wallet.get_address(LastUnused).to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
         assert_eq!(
-            wallet.get_address(LastUnused).unwrap().to_string(),
+            wallet.get_address(LastUnused).to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
     }
@@ -4470,134 +3939,91 @@ pub(crate) mod test {
     #[test]
     fn test_next_unused_address() {
         let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
-        let descriptors = testutils!(@descriptors (descriptor));
-        let wallet = Wallet::new(
-            &descriptors.0,
-            None,
-            Network::Testnet,
-            MemoryDatabase::new(),
-        )
-        .unwrap();
+        let mut wallet = Wallet::new(descriptor, None, Network::Testnet).unwrap();
+        assert_eq!(wallet.derivation_index(KeychainKind::External), None);
 
         assert_eq!(
-            wallet.get_address(LastUnused).unwrap().to_string(),
+            wallet.get_address(LastUnused).to_string(),
+            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+        );
+        assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
+        assert_eq!(
+            wallet.get_address(LastUnused).to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
+        assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
 
         // use the above address
-        crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(100),
-        );
+        receive_output_in_latest_block(&mut wallet, 25_000);
 
         assert_eq!(
-            wallet.get_address(LastUnused).unwrap().to_string(),
+            wallet.get_address(LastUnused).to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
+        assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1));
     }
 
     #[test]
     fn test_peek_address_at_index() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                         None, Network::Testnet, db).unwrap();
+        let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                             None, Network::Testnet).unwrap();
 
         assert_eq!(
-            wallet.get_address(Peek(1)).unwrap().to_string(),
+            wallet.get_address(Peek(1)).to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
 
         assert_eq!(
-            wallet.get_address(Peek(0)).unwrap().to_string(),
+            wallet.get_address(Peek(0)).to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
 
         assert_eq!(
-            wallet.get_address(Peek(2)).unwrap().to_string(),
+            wallet.get_address(Peek(2)).to_string(),
             "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
         );
 
         // current new address is not affected
         assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
+            wallet.get_address(New).to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
 
         assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
+            wallet.get_address(New).to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
     }
 
     #[test]
     fn test_peek_address_at_index_not_derivable() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
-                                         None, Network::Testnet, db).unwrap();
-
-        assert_eq!(
-            wallet.get_address(Peek(1)).unwrap().to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
-
-        assert_eq!(
-            wallet.get_address(Peek(0)).unwrap().to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
+        let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
+                                             None, Network::Testnet).unwrap();
 
         assert_eq!(
-            wallet.get_address(Peek(2)).unwrap().to_string(),
+            wallet.get_address(Peek(1)).to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
-    }
-
-    #[test]
-    fn test_reset_address_index() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                         None, Network::Testnet, db).unwrap();
-
-        // new index 0
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
 
-        // new index 1
         assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
+            wallet.get_address(Peek(0)).to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
 
-        // new index 2
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
-        );
-
-        //  reset index 1 again
         assert_eq!(
-            wallet.get_address(Reset(1)).unwrap().to_string(),
+            wallet.get_address(Peek(2)).to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
-
-        // new index 2 again
-        assert_eq!(
-            wallet.get_address(New).unwrap().to_string(),
-            "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
-        );
     }
 
     #[test]
     fn test_returns_index_and_address() {
-        let db = MemoryDatabase::new();
-        let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                         None, Network::Testnet, db).unwrap();
+        let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                             None, Network::Testnet).unwrap();
 
         // new index 0
         assert_eq!(
-            wallet.get_address(New).unwrap(),
+            wallet.get_address(New),
             AddressInfo {
                 index: 0,
                 address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(),
@@ -4607,7 +4033,7 @@ pub(crate) mod test {
 
         // new index 1
         assert_eq!(
-            wallet.get_address(New).unwrap(),
+            wallet.get_address(New),
             AddressInfo {
                 index: 1,
                 address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
@@ -4617,7 +4043,7 @@ pub(crate) mod test {
 
         // peek index 25
         assert_eq!(
-            wallet.get_address(Peek(25)).unwrap(),
+            wallet.get_address(Peek(25)),
             AddressInfo {
                 index: 25,
                 address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap(),
@@ -4627,27 +4053,7 @@ pub(crate) mod test {
 
         // new index 2
         assert_eq!(
-            wallet.get_address(New).unwrap(),
-            AddressInfo {
-                index: 2,
-                address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
-                keychain: KeychainKind::External,
-            }
-        );
-
-        //  reset index 1 again
-        assert_eq!(
-            wallet.get_address(Reset(1)).unwrap(),
-            AddressInfo {
-                index: 1,
-                address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
-                keychain: KeychainKind::External,
-            }
-        );
-
-        // new index 2 again
-        assert_eq!(
-            wallet.get_address(New).unwrap(),
+            wallet.get_address(New),
             AddressInfo {
                 index: 2,
                 address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
@@ -4658,7 +4064,7 @@ pub(crate) mod test {
 
     #[test]
     fn test_sending_to_bip350_bech32m_address() {
-        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
         let addr =
             Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
                 .unwrap();
@@ -4671,16 +4077,15 @@ pub(crate) mod test {
     fn test_get_address() {
         use crate::descriptor::template::Bip84;
         let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let wallet = Wallet::new(
+        let mut wallet = Wallet::new(
             Bip84(key, KeychainKind::External),
             Some(Bip84(key, KeychainKind::Internal)),
             Network::Regtest,
-            MemoryDatabase::default(),
         )
         .unwrap();
 
         assert_eq!(
-            wallet.get_address(AddressIndex::New).unwrap(),
+            wallet.get_address(AddressIndex::New),
             AddressInfo {
                 index: 0,
                 address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
@@ -4689,7 +4094,7 @@ pub(crate) mod test {
         );
 
         assert_eq!(
-            wallet.get_internal_address(AddressIndex::New).unwrap(),
+            wallet.get_internal_address(AddressIndex::New),
             AddressInfo {
                 index: 0,
                 address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79").unwrap(),
@@ -4697,16 +4102,11 @@ pub(crate) mod test {
             }
         );
 
-        let wallet = Wallet::new(
-            Bip84(key, KeychainKind::External),
-            None,
-            Network::Regtest,
-            MemoryDatabase::default(),
-        )
-        .unwrap();
+        let mut wallet =
+            Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
 
         assert_eq!(
-            wallet.get_internal_address(AddressIndex::New).unwrap(),
+            wallet.get_internal_address(AddressIndex::New),
             AddressInfo {
                 index: 0,
                 address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
@@ -4722,32 +4122,24 @@ pub(crate) mod test {
         use std::collections::HashSet;
 
         let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let wallet = Wallet::new(
-            Bip84(key, KeychainKind::External),
-            None,
-            Network::Regtest,
-            MemoryDatabase::default(),
-        )
-        .unwrap();
+        let mut wallet =
+            Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
 
         let mut used_set = HashSet::new();
 
         (0..3).for_each(|_| {
-            let external_addr = wallet.get_address(AddressIndex::New).unwrap().address;
+            let external_addr = wallet.get_address(AddressIndex::New).address;
             assert!(used_set.insert(external_addr));
 
-            let internal_addr = wallet
-                .get_internal_address(AddressIndex::New)
-                .unwrap()
-                .address;
+            let internal_addr = wallet.get_internal_address(AddressIndex::New).address;
             assert!(used_set.insert(internal_addr));
         });
     }
 
     #[test]
     fn test_taproot_psbt_populate_tap_key_origins() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
@@ -4781,8 +4173,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_repeated_key());
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_repeated_key());
+        let addr = wallet.get_address(AddressIndex::New);
 
         let path = vec![("e5mmg3xh".to_string(), vec![0])]
             .into_iter()
@@ -4849,8 +4241,8 @@ pub(crate) mod test {
         use bitcoin::hashes::hex::FromHex;
         use bitcoin::util::taproot;
 
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree());
-        let addr = wallet.get_address(AddressIndex::Peek(0)).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
+        let addr = wallet.get_address(AddressIndex::Peek(0));
 
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
@@ -4894,8 +4286,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_sign_missing_witness_utxo() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -4934,14 +4326,14 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_sign_using_non_witness_utxo() {
-        let (wallet, _, prev_txid) = get_funded_wallet(get_test_tr_single_sig());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, prev_txid) = get_funded_wallet(get_test_tr_single_sig());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
 
         psbt.inputs[0].witness_utxo = None;
-        psbt.inputs[0].non_witness_utxo = wallet.database().get_raw_tx(&prev_txid).unwrap();
+        psbt.inputs[0].non_witness_utxo = wallet.get_tx(prev_txid, true).unwrap().transaction;
         assert!(
             psbt.inputs[0].non_witness_utxo.is_some(),
             "Previous tx should be present in the database"
@@ -4957,11 +4349,11 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_foreign_utxo() {
-        let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
-        let (wallet2, _, _) = get_funded_wallet(get_test_tr_single_sig());
+        let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+        let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig());
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let utxo = wallet2.list_unspent().unwrap().remove(0);
+        let utxo = wallet2.list_unspent().remove(0);
         let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
         let foreign_utxo_satisfaction = wallet2
             .get_descriptor_for_keychain(KeychainKind::External)
@@ -4995,8 +4387,8 @@ pub(crate) mod test {
         );
     }
 
-    fn test_spend_from_wallet(wallet: Wallet<AnyDatabase>) {
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+    fn test_spend_from_wallet(mut wallet: Wallet) {
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
@@ -5008,19 +4400,19 @@ pub(crate) mod test {
         );
     }
 
-    #[test]
-    fn test_taproot_key_spend() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
-        test_spend_from_wallet(wallet);
+    //     #[test]
+    //     fn test_taproot_key_spend() {
+    //         let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+    //         test_spend_from_wallet(wallet);
 
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
-        test_spend_from_wallet(wallet);
-    }
+    //         let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+    //         test_spend_from_wallet(wallet);
+    //     }
 
     #[test]
     fn test_taproot_no_key_spend() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
@@ -5044,18 +4436,18 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_script_spend() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree());
+        let (wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
         test_spend_from_wallet(wallet);
 
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_xprv());
+        let (wallet, _) = get_funded_wallet(get_test_tr_with_taptree_xprv());
         test_spend_from_wallet(wallet);
     }
 
     #[test]
     fn test_taproot_script_spend_sign_all_leaves() {
         use crate::signer::TapLeavesOptions;
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
@@ -5085,8 +4477,8 @@ pub(crate) mod test {
         use crate::signer::TapLeavesOptions;
         use bitcoin::util::taproot::TapLeafHash;
 
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
@@ -5127,8 +4519,8 @@ pub(crate) mod test {
         use crate::signer::TapLeavesOptions;
         use bitcoin::util::taproot::TapLeafHash;
 
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
@@ -5167,8 +4559,8 @@ pub(crate) mod test {
     #[test]
     fn test_taproot_script_spend_sign_no_leaves() {
         use crate::signer::TapLeavesOptions;
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
@@ -5189,22 +4581,17 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_sign_derive_index_from_psbt() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
 
-        let addr = wallet.get_address(AddressIndex::New).unwrap();
+        let addr = wallet.get_address(AddressIndex::New);
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (mut psbt, _) = builder.finish().unwrap();
 
         // re-create the wallet with an empty db
-        let wallet_empty = Wallet::new(
-            get_test_tr_single_sig_xprv(),
-            None,
-            Network::Regtest,
-            AnyDatabase::Memory(MemoryDatabase::new()),
-        )
-        .unwrap();
+        let wallet_empty =
+            Wallet::new(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap();
 
         // signing with an empty db means that we will only look at the psbt to infer the
         // derivation index
@@ -5216,8 +4603,8 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_sign_explicit_sighash_all() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
@@ -5236,8 +4623,8 @@ pub(crate) mod test {
     fn test_taproot_sign_non_default_sighash() {
         let sighash = SchnorrSighashType::NonePlusAnyoneCanPay;
 
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+        let addr = wallet.get_address(New);
         let mut builder = wallet.build_tx();
         builder
             .drain_to(addr.script_pubkey())
@@ -5303,39 +4690,42 @@ pub(crate) mod test {
 
     #[test]
     fn test_spend_coinbase() {
-        let descriptors = testutils!(@descriptors (get_test_wpkh()));
-        let wallet = Wallet::new(
-            &descriptors.0,
-            None,
-            Network::Regtest,
-            AnyDatabase::Memory(MemoryDatabase::new()),
-        )
-        .unwrap();
-
-        let confirmation_time = 5;
+        let descriptor = get_test_wpkh();
+        let mut wallet = Wallet::new(descriptor, None, Network::Regtest).unwrap();
 
-        crate::populate_test_db!(
-            wallet.database.borrow_mut(),
-            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
-            Some(confirmation_time),
-            (@coinbase true)
-        );
-        let sync_time = SyncTime {
-            block_time: BlockTime {
-                height: confirmation_time,
-                timestamp: 0,
-            },
+        let confirmation_height = 5;
+        wallet
+            .insert_checkpoint(BlockId {
+                height: confirmation_height,
+                hash: BlockHash::all_zeros(),
+            })
+            .unwrap();
+        let coinbase_tx = Transaction {
+            version: 1,
+            lock_time: bitcoin::PackedLockTime(0),
+            input: vec![TxIn {
+                previous_output: OutPoint::null(),
+                ..Default::default()
+            }],
+            output: vec![TxOut {
+                value: 25_000,
+                script_pubkey: wallet.get_address(New).address.script_pubkey(),
+            }],
         };
         wallet
-            .database
-            .borrow_mut()
-            .set_sync_time(sync_time)
+            .insert_tx(
+                coinbase_tx,
+                ConfirmationTime::Confirmed {
+                    height: confirmation_height,
+                    time: 30_000,
+                },
+            )
             .unwrap();
 
-        let not_yet_mature_time = confirmation_time + COINBASE_MATURITY - 1;
-        let maturity_time = confirmation_time + COINBASE_MATURITY;
+        let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 1;
+        let maturity_time = confirmation_height + COINBASE_MATURITY;
 
-        let balance = wallet.get_balance().unwrap();
+        let balance = wallet.get_balance();
         assert_eq!(
             balance,
             Balance {
@@ -5352,14 +4742,14 @@ pub(crate) mod test {
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), balance.immature / 2)
-            .current_height(confirmation_time);
-        assert_matches!(
+            .current_height(confirmation_height);
+        assert!(matches!(
             builder.finish(),
             Err(Error::InsufficientFunds {
                 needed: _,
                 available: 0
             })
-        );
+        ));
 
         // Still unspendable...
         let mut builder = wallet.build_tx();
@@ -5374,20 +4764,13 @@ pub(crate) mod test {
             })
         );
 
-        // ...Now the coinbase is mature :)
-        let sync_time = SyncTime {
-            block_time: BlockTime {
-                height: maturity_time,
-                timestamp: 0,
-            },
-        };
         wallet
-            .database
-            .borrow_mut()
-            .set_sync_time(sync_time)
+            .insert_checkpoint(BlockId {
+                height: maturity_time,
+                hash: BlockHash::all_zeros(),
+            })
             .unwrap();
-
-        let balance = wallet.get_balance().unwrap();
+        let balance = wallet.get_balance();
         assert_eq!(
             balance,
             Balance {
@@ -5406,9 +4789,9 @@ pub(crate) mod test {
 
     #[test]
     fn test_allow_dust_limit() {
-        let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
+        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
 
-        let addr = wallet.get_address(New).unwrap();
+        let addr = wallet.get_address(New);
 
         let mut builder = wallet.build_tx();
 
@@ -5430,8 +4813,8 @@ pub(crate) mod test {
         // Our goal is to obtain a transaction with a signature with high-R (71 bytes
         // instead of 70). We then check that our fee rate and fee calculation is
         // alright.
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let fee_rate = FeeRate::from_sat_per_vb(1.0);
         let mut builder = wallet.build_tx();
         let mut data = vec![0];
@@ -5495,8 +4878,8 @@ pub(crate) mod test {
         // by setting the `allow_grinding` signing option as true.
         // We then check that our fee rate and fee calculation is alright and that our
         // signature is 70 bytes.
-        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New);
         let fee_rate = FeeRate::from_sat_per_vb(1.0);
         let mut builder = wallet.build_tx();
         builder
@@ -5538,14 +4921,14 @@ pub(crate) mod test {
         let descriptors = client.get_descriptors::<String>(None).unwrap();
         let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
 
-        let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
+        let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
         wallet.add_signer(
             KeychainKind::External,
             SignerOrdering(200),
             Arc::new(custom_signer),
         );
 
-        let addr = wallet.get_address(LastUnused).unwrap();
+        let addr = wallet.get_address(LastUnused);
         let mut builder = wallet.build_tx();
         builder.drain_to(addr.script_pubkey()).drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
@@ -5560,8 +4943,8 @@ pub(crate) mod test {
         //
         // Having the same key in multiple taproot leaves is safe and should be accepted by BDK
 
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_dup_keys());
-        let addr = wallet.get_address(New).unwrap();
+        let (mut wallet, _) = get_funded_wallet(get_test_tr_dup_keys());
+        let addr = wallet.get_address(New);
 
         assert_eq!(
             addr.to_string(),
diff --git a/src/wallet/time.rs b/src/wallet/time.rs
deleted file mode 100644 (file)
index 0ba7670..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Cross-platform time
-//!
-//! This module provides a function to get the current timestamp that works on all the platforms
-//! supported by the library.
-//!
-//! It can be useful to compare it with the timestamps found in
-//! [`TransactionDetails`](crate::types::TransactionDetails).
-
-use std::time::Duration;
-
-#[cfg(target_arch = "wasm32")]
-use js_sys::Date;
-#[cfg(not(target_arch = "wasm32"))]
-use std::time::{Instant as SystemInstant, SystemTime, UNIX_EPOCH};
-
-/// Return the current timestamp in seconds
-#[cfg(not(target_arch = "wasm32"))]
-pub fn get_timestamp() -> u64 {
-    SystemTime::now()
-        .duration_since(UNIX_EPOCH)
-        .unwrap()
-        .as_secs()
-}
-/// Return the current timestamp in seconds
-#[cfg(target_arch = "wasm32")]
-pub fn get_timestamp() -> u64 {
-    let millis = Date::now();
-
-    (millis / 1000.0) as u64
-}
-
-#[cfg(not(target_arch = "wasm32"))]
-pub(crate) struct Instant(SystemInstant);
-#[cfg(target_arch = "wasm32")]
-pub(crate) struct Instant(Duration);
-
-impl Instant {
-    #[cfg(not(target_arch = "wasm32"))]
-    pub fn new() -> Self {
-        Instant(SystemInstant::now())
-    }
-    #[cfg(target_arch = "wasm32")]
-    pub fn new() -> Self {
-        let millis = Date::now();
-
-        let secs = millis / 1000.0;
-        let nanos = (millis % 1000.0) * 1e6;
-
-        Instant(Duration::new(secs as u64, nanos as u32))
-    }
-
-    #[cfg(not(target_arch = "wasm32"))]
-    pub fn elapsed(&self) -> Duration {
-        self.0.elapsed()
-    }
-    #[cfg(target_arch = "wasm32")]
-    pub fn elapsed(&self) -> Duration {
-        let now = Instant::new();
-
-        now.0.checked_sub(self.0).unwrap_or(Duration::new(0, 0))
-    }
-}
index 6d52b8d9040c1d3bca901bcdbf630c49b2d8500d..048336820d216ea54b9a2a49cd7ebc2907077c31 100644 (file)
 //! # Ok::<(), bdk::Error>(())
 //! ```
 
+use std::cell::RefCell;
 use std::collections::BTreeMap;
 use std::collections::HashSet;
 use std::default::Default;
 use std::marker::PhantomData;
+use std::rc::Rc;
 
 use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt};
 use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
 
 use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
-use crate::{database::BatchDatabase, Error, Utxo, Wallet};
 use crate::{
     types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo},
     TransactionDetails,
 };
+use crate::{Error, Utxo, Wallet};
 /// Context in which the [`TxBuilder`] is valid
 pub trait TxBuilderContext: std::fmt::Debug + Default + Clone {}
 
@@ -115,8 +117,8 @@ impl TxBuilderContext for BumpFee {}
 /// [`finish`]: Self::finish
 /// [`coin_selection`]: Self::coin_selection
 #[derive(Debug)]
-pub struct TxBuilder<'a, D, Cs, Ctx> {
-    pub(crate) wallet: &'a Wallet<D>,
+pub struct TxBuilder<'a, Cs, Ctx> {
+    pub(crate) wallet: Rc<RefCell<&'a mut Wallet>>,
     pub(crate) params: TxParams,
     pub(crate) coin_selection: Cs,
     pub(crate) phantom: PhantomData<Ctx>,
@@ -167,10 +169,10 @@ impl std::default::Default for FeePolicy {
     }
 }
 
-impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> {
+impl<'a, Cs: Clone, Ctx> Clone for TxBuilder<'a, Cs, Ctx> {
     fn clone(&self) -> Self {
         TxBuilder {
-            wallet: self.wallet,
+            wallet: self.wallet.clone(),
             params: self.params.clone(),
             coin_selection: self.coin_selection.clone(),
             phantom: PhantomData,
@@ -179,9 +181,7 @@ impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> {
 }
 
 // methods supported by both contexts, for any CoinSelectionAlgorithm
-impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
-    TxBuilder<'a, D, Cs, Ctx>
-{
+impl<'a, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, Cs, Ctx> {
     /// Set a custom fee rate
     pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
         self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate));
@@ -274,18 +274,21 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
     /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
     /// the "utxos" and the "unspendable" list, it will be spent.
     pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> {
-        let utxos = outpoints
-            .iter()
-            .map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUtxo))
-            .collect::<Result<Vec<_>, _>>()?;
-
-        for utxo in utxos {
-            let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
-            let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
-            self.params.utxos.push(WeightedUtxo {
-                satisfaction_weight,
-                utxo: Utxo::Local(utxo),
-            });
+        {
+            let wallet = self.wallet.borrow();
+            let utxos = outpoints
+                .iter()
+                .map(|outpoint| wallet.get_utxo(*outpoint).ok_or(Error::UnknownUtxo))
+                .collect::<Result<Vec<_>, _>>()?;
+
+            for utxo in utxos {
+                let descriptor = wallet.get_descriptor_for_keychain(utxo.keychain);
+                let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
+                self.params.utxos.push(WeightedUtxo {
+                    satisfaction_weight,
+                    utxo: Utxo::Local(utxo),
+                });
+            }
         }
 
         Ok(self)
@@ -503,10 +506,10 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
     /// Overrides the [`DefaultCoinSelectionAlgorithm`](super::coin_selection::DefaultCoinSelectionAlgorithm).
     ///
     /// Note that this function consumes the builder and returns it so it is usually best to put this as the first call on the builder.
-    pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
+    pub fn coin_selection<P: CoinSelectionAlgorithm>(
         self,
         coin_selection: P,
-    ) -> TxBuilder<'a, D, P, Ctx> {
+    ) -> TxBuilder<'a, P, Ctx> {
         TxBuilder {
             wallet: self.wallet,
             params: self.params,
@@ -521,7 +524,9 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
     ///
     /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
     pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error> {
-        self.wallet.create_tx(self.coin_selection, self.params)
+        self.wallet
+            .borrow_mut()
+            .create_tx(self.coin_selection, self.params)
     }
 
     /// Enable signaling RBF
@@ -569,7 +574,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
     }
 }
 
-impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
+impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs, CreateTx> {
     /// Replace the recipients already added with a new list
     pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
         self.params.recipients = recipients;
@@ -640,7 +645,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, C
 }
 
 // methods supported only by bump_fee
-impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> {
+impl<'a> TxBuilder<'a, DefaultCoinSelectionAlgorithm, BumpFee> {
     /// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this
     /// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
     /// will attempt to find a change output to shrink instead.
@@ -788,6 +793,7 @@ mod test {
         };
     }
 
+    use bdk_chain::ConfirmationTime;
     use bitcoin::consensus::deserialize;
     use bitcoin::hashes::hex::FromHex;
 
@@ -867,6 +873,8 @@ mod test {
                 txout: Default::default(),
                 keychain: KeychainKind::External,
                 is_spent: false,
+                confirmation_time: ConfirmationTime::Unconfirmed,
+                derivation_index: 0,
             },
             LocalUtxo {
                 outpoint: OutPoint {
@@ -876,6 +884,11 @@ mod test {
                 txout: Default::default(),
                 keychain: KeychainKind::Internal,
                 is_spent: false,
+                confirmation_time: ConfirmationTime::Confirmed {
+                    height: 32,
+                    time: 42,
+                },
+                derivation_index: 1,
             },
         ]
     }
diff --git a/src/wallet/verify.rs b/src/wallet/verify.rs
deleted file mode 100644 (file)
index d96fb15..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2021 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Verify transactions against the consensus rules
-
-use std::collections::HashMap;
-use std::fmt;
-
-use bitcoin::consensus::serialize;
-use bitcoin::{OutPoint, Transaction, Txid};
-
-use crate::blockchain::GetTx;
-use crate::database::Database;
-use crate::error::Error;
-
-/// Verify a transaction against the consensus rules
-///
-/// This function uses [`bitcoinconsensus`] to verify transactions by fetching the required data
-/// either from the [`Database`] or using the [`Blockchain`].
-///
-/// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the
-/// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or
-/// with unconfirmed transactions that have been evicted from the backend's memory.
-///
-/// [`Blockchain`]: crate::blockchain::Blockchain
-pub fn verify_tx<D: Database, B: GetTx>(
-    tx: &Transaction,
-    database: &D,
-    blockchain: &B,
-) -> Result<(), VerifyError> {
-    log::debug!("Verifying {}", tx.txid());
-
-    let serialized_tx = serialize(tx);
-    let mut tx_cache = HashMap::<_, Transaction>::new();
-
-    for (index, input) in tx.input.iter().enumerate() {
-        let prev_tx = if let Some(prev_tx) = tx_cache.get(&input.previous_output.txid) {
-            prev_tx.clone()
-        } else if let Some(prev_tx) = database.get_raw_tx(&input.previous_output.txid)? {
-            prev_tx
-        } else if let Some(prev_tx) = blockchain.get_tx(&input.previous_output.txid)? {
-            prev_tx
-        } else {
-            return Err(VerifyError::MissingInputTx(input.previous_output.txid));
-        };
-
-        let spent_output = prev_tx
-            .output
-            .get(input.previous_output.vout as usize)
-            .ok_or(VerifyError::InvalidInput(input.previous_output))?;
-
-        bitcoinconsensus::verify(
-            &spent_output.script_pubkey.to_bytes(),
-            spent_output.value,
-            &serialized_tx,
-            index,
-        )?;
-
-        // Since we have a local cache we might as well cache stuff from the db, as it will very
-        // likely decrease latency compared to reading from disk or performing an SQL query.
-        tx_cache.insert(prev_tx.txid(), prev_tx);
-    }
-
-    Ok(())
-}
-
-/// Error during validation of a tx agains the consensus rules
-#[derive(Debug)]
-pub enum VerifyError {
-    /// The transaction being spent is not available in the database or the blockchain client
-    MissingInputTx(Txid),
-    /// The transaction being spent doesn't have the requested output
-    InvalidInput(OutPoint),
-
-    /// Consensus error
-    Consensus(bitcoinconsensus::Error),
-
-    /// Generic error
-    ///
-    /// It has to be wrapped in a `Box` since `Error` has a variant that contains this enum
-    Global(Box<Error>),
-}
-
-impl fmt::Display for VerifyError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::MissingInputTx(txid) => write!(f, "The transaction being spent is not available in the database or the blockchain client: {}", txid),
-            Self::InvalidInput(outpoint) => write!(f, "The transaction being spent doesn't have the requested output: {}", outpoint),
-            Self::Consensus(err) => write!(f, "Consensus error: {:?}", err),
-            Self::Global(err) => write!(f, "Generic error: {}", err),
-        }
-    }
-}
-
-impl std::error::Error for VerifyError {}
-
-impl From<Error> for VerifyError {
-    fn from(other: Error) -> Self {
-        VerifyError::Global(Box::new(other))
-    }
-}
-impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
-
-#[cfg(test)]
-mod test {
-    use super::*;
-    use crate::database::{BatchOperations, MemoryDatabase};
-    use assert_matches::assert_matches;
-    use bitcoin::consensus::encode::deserialize;
-    use bitcoin::hashes::hex::FromHex;
-    use bitcoin::{Transaction, Txid};
-
-    struct DummyBlockchain;
-
-    impl GetTx for DummyBlockchain {
-        fn get_tx(&self, _txid: &Txid) -> Result<Option<Transaction>, Error> {
-            Ok(None)
-        }
-    }
-
-    #[test]
-    fn test_verify_fail_unsigned_tx() {
-        // https://blockstream.info/tx/95da344585fcf2e5f7d6cbf2c3df2dcce84f9196f7a7bb901a43275cd6eb7c3f
-        let prev_tx: Transaction = deserialize(&Vec::<u8>::from_hex("020000000101192dea5e66d444380e106f8e53acb171703f00d43fb6b3ae88ca5644bdb7e1000000006b48304502210098328d026ce138411f957966c1cf7f7597ccbb170f5d5655ee3e9f47b18f6999022017c3526fc9147830e1340e04934476a3d1521af5b4de4e98baf49ec4c072079e01210276f847f77ec8dd66d78affd3c318a0ed26d89dab33fa143333c207402fcec352feffffff023d0ac203000000001976a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988aca4b956050000000017a91494d5543c74a3ee98e0cf8e8caef5dc813a0f34b48768cb0700").unwrap()).unwrap();
-        // https://blockstream.info/tx/aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d
-        let signed_tx: Transaction = deserialize(&Vec::<u8>::from_hex("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700").unwrap()).unwrap();
-
-        let mut database = MemoryDatabase::new();
-        let blockchain = DummyBlockchain;
-
-        let mut unsigned_tx = signed_tx.clone();
-        for input in &mut unsigned_tx.input {
-            input.script_sig = Default::default();
-            input.witness = Default::default();
-        }
-
-        let result = verify_tx(&signed_tx, &database, &blockchain);
-        assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(),
-            "Error should be a `MissingInputTx` error"
-        );
-
-        // insert the prev_tx
-        database.set_raw_tx(&prev_tx).unwrap();
-
-        let result = verify_tx(&unsigned_tx, &database, &blockchain);
-        assert_matches!(
-            result,
-            Err(VerifyError::Consensus(_)),
-            "Error should be a `Consensus` error"
-        );
-
-        let result = verify_tx(&signed_tx, &database, &blockchain);
-        assert!(
-            result.is_ok(),
-            "Should work since the TX is correctly signed"
-        );
-    }
-}