[blockchain] Add traits to reuse Blockchains across multiple wallets

Add two new traits:
- `StatelessBlockchain` is used to tag `Blockchain`s that don't have any
  wallet-specic state, i.e. they can be used as-is to sync multiple wallets.
- `BlockchainFactory` is a trait for objects that can build multiple
  blockchains for different descriptors. It's implemented automatically
  for every `Arc<T>` where `T` is a `StatelessBlockchain`. This allows a
  piece of code that deals with multiple sub-wallets to just get a
  `&B: BlockchainFactory` to sync all of them.

These new traits have been implemented for Electrum, Esplora and RPC
(the first two being stateless and the latter having a dedicated
`RpcBlockchainFactory` struct). It hasn't been implemented on the CBF
blockchain, because I don't think it would work in its current form
(it throws away old block filters, so it's hard to go back and rescan).

This is the first step for #549, as BIP47 needs to sync many different
descriptors internally.

It's also very useful for #486.
This commit is contained in:
Alekos Filini
2022-03-15 10:48:00 +01:00
parent 2d83af4905
commit 9c405e9c70
8 changed files with 294 additions and 18 deletions

View File

@@ -79,6 +79,8 @@ impl Blockchain for ElectrumBlockchain {
}
}
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?
@@ -320,8 +322,67 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
#[cfg(test)]
#[cfg(feature = "test-electrum")]
crate::bdk_blockchain_tests! {
fn test_instance(test_client: &TestClient) -> ElectrumBlockchain {
ElectrumBlockchain::from(Client::new(&test_client.electrsd.electrum_url).unwrap())
mod test {
use std::sync::Arc;
use super::*;
use crate::database::MemoryDatabase;
use crate::testutils::blockchain_tests::TestClient;
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(), 50_000);
}
}