Skip to content

Commit

Permalink
feat: add page migrating from 0.x
Browse files Browse the repository at this point in the history
  • Loading branch information
ValuedMammal committed Dec 13, 2024
1 parent ded34a7 commit a5da578
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"examples/rust/quickstart/Cargo.toml",
"examples/rust/descriptors/Cargo.toml",
"examples/rust/seed-phrase/Cargo.toml",
"examples/rust/full-wallet/Cargo.toml"
"examples/rust/full-wallet/Cargo.toml",
"examples/rust/migrate-version/Cargo.toml"
]
}
84 changes: 84 additions & 0 deletions docs/getting-started/migrating.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Migrating from 0.X

!!! tip
This page is up-to-date with version `1.0.0-beta.5` of bdk.

So you're ready to migrate to BDK version 1.0, congratulations!
This document contains some helpful tips that, with the help of some automation, should make the process as seamless as possible.

The below steps are for migrating wallet details from the old [`bdk` v0.30][0] to the new [`bdk_wallet` v1.0][1].
This procedure can be applied to wallets backed by a SQLite database.
Of particular concern is the ability to restore the _last known address index_ for each keychain.
This is important because without that metadata the new wallet may end up reusing receive addresses, which should be avoided for privacy reasons, although it should not cause loss of funds.

!!! tip
NB: The migration process outlined below will not automatically restore the wallet's transaction data or local view of the blockchain.
Thanks to the public ledger however, we can restore all the pertinent information for this wallet using one of the blockchain client libraries supported by BDK.

## Overview

1. Load an old database
1. Get last revealed addresses
1. Create new wallet
1. Restore revealed addresses
1. Write to new database
1. Sync

<!-- overview -->
```rust title="examples/rust/migrate-version/src/main.rs"
--8<-- "examples/rust/migrate-version/src/main.rs:main"
```

## Walkthrough

In a new rust project add these dependencies to Cargo.toml

<!-- deps -->
```toml title="Cargo.toml"
--8<-- "examples/rust/migrate-version/Cargo.toml:deps"
```

Because there are two versions of bdk in the same project, we need to pay attention to how types are imported.
To avoid name clashes or any sort of mismatch resolving types that appear similar, we use fully qualified syntax, for example `bdk::bitcoin::Network::Testnet`.
You'll notice in some cases we can get around this annoyance by casting a value to another rust primitive or standard library type such as `String`.

<!-- imports -->
```rust title="examples/rust/migrate-version/src/main.rs"
--8<-- "examples/rust/migrate-version/src/main.rs:use"
```

<!-- setup -->
Take a minute to define a few constants, for example the file path to the current database and the path to be used for the new database.
The descriptors and network shown here are for illustration; you should substitute them with your own.
Note that because we'll be creating a fresh database there should not already exist a persisted wallet at the new path.

```rust title="examples/rust/migrate-version/src/main.rs"
--8<-- "examples/rust/migrate-version/src/main.rs:setup"
```

<!-- old -->
Now retrieve the last revealed addresses from the `old_wallet`.

```rust title="examples/rust/migrate-version/src/main.rs"
--8<-- "examples/rust/migrate-version/src/main.rs:old"
```

<!-- new -->
For the `new_wallet` we should be using the same descriptors and network as before.
If the given descriptors contain secret keys, then the wallet will be able to sign transactions as well.

```rust title="examples/rust/migrate-version/src/main.rs"
--8<-- "examples/rust/migrate-version/src/main.rs:new"
```

<!-- sync -->
Now that we have a new database and have properly restored our addresses, you will want to sync with the blockchain to recover the wallet's transactions.
Below is an example of doing a `sync` using `bdk_esplora` but the exact method of syncing will depend on your application.
Happy migrating and see you on [v1.0][1]!

```rust title="examples/rust/migrate-version/src/main.rs"
--8<-- "examples/rust/migrate-version/src/main.rs:sync"
```

[0]: https://docs.rs/bdk/0.30.0/bdk/
[1]: https://docs.rs/bdk_wallet/1.0.0-beta.5/bdk_wallet/
3 changes: 3 additions & 0 deletions examples/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ architecture:

descriptors:
cd descriptors/ && cargo run --bin descriptors

migrate:
cd rust/migrate-version/ && cargo run --bin migrate-version
11 changes: 11 additions & 0 deletions examples/rust/migrate-version/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "migrate-version"
version = "0.1.0"
edition = "2021"

# --8<-- [start:deps]
[dependencies]
anyhow = "1"
bdk = { version = "0.30", features = ["sqlite"] }
bdk_wallet = { version = "=1.0.0-beta.5", features = ["rusqlite"] }
# --8<-- [end:deps]
112 changes: 112 additions & 0 deletions examples/rust/migrate-version/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// --8<-- [start:use]
use anyhow::Context;

use bdk::database::SqliteDatabase;
use bdk::wallet::AddressIndex;

use bdk_wallet::bitcoin::Network;
use bdk_wallet::rusqlite;
use bdk_wallet::KeychainKind;
use bdk_wallet::Wallet;
// --8<-- [end:use]

// --8<-- [start:setup]
const EXTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
const INTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
const NETWORK: Network = Network::Testnet;

// path to old db
const BDK_DB_PATH: &str = "./.bdk-example.sqlite";
// path to new db
const BDK_WALLET_DB_PATH: &str = "./.bdk-wallet-example.sqlite";
// --8<-- [end:setup]

// Steps for migrating wallet state from the old `bdk` 0.30 to the new `bdk_wallet` 1.0.

// To run: change `BDK_DB_PATH` to point to the location of the old database file and
// modify the descriptors and network above to fit your setup. Before running, there
// should not be any persisted data at the new path `BDK_WALLET_DB_PATH`.

// --8<-- [start:main]
fn main() -> anyhow::Result<()> {
// --8<-- [start:old]
// Open old wallet
let db = SqliteDatabase::new(BDK_DB_PATH);
let old_wallet = bdk::Wallet::new(
EXTERNAL_DESCRIPTOR,
Some(INTERNAL_DESCRIPTOR),
bdk::bitcoin::Network::Testnet,
db,
)?;

// Get last revealed addresses for each keychain
let addr = old_wallet.get_address(AddressIndex::LastUnused)?;
println!("Last revealed external {} {}", addr.index, addr.address);
let external_derivation_index = addr.index;
let last_revealed_external = addr.address.to_string();

let addr = old_wallet.get_internal_address(AddressIndex::LastUnused)?;
println!("Last revealed internal {} {}", addr.index, addr.address);
let internal_derivation_index = addr.index;
let last_revealed_internal = addr.address.to_string();
// --8<-- [end:old]

// --8<-- [start:new]
// Create new wallet
let mut db = rusqlite::Connection::open(BDK_WALLET_DB_PATH)?;
let mut new_wallet = Wallet::create(EXTERNAL_DESCRIPTOR, INTERNAL_DESCRIPTOR)
.network(NETWORK)
.create_wallet(&mut db)
.context("failed to create wallet")?;

// Retore revealed addresses
let _ = new_wallet.reveal_addresses_to(KeychainKind::External, external_derivation_index);
let _ = new_wallet.reveal_addresses_to(KeychainKind::Internal, internal_derivation_index);

// Persist new wallet
new_wallet.persist(&mut db)?;

println!("\n========== New database created. ==========");

let addr = new_wallet
.list_unused_addresses(KeychainKind::External)
.last()
.unwrap();
assert_eq!(addr.to_string(), last_revealed_external);
println!("Last revealed external {} {}", addr.index, addr.address);
let addr = new_wallet
.list_unused_addresses(KeychainKind::Internal)
.last()
.unwrap();
println!("Last revealed internal {} {}", addr.index, addr.address);
assert_eq!(addr.to_string(), last_revealed_internal);
// --8<-- [end:new]

Ok(())
}
// --8<-- [end:main]

/* Extra: sync with esplora
// --8<-- [start:sync]
use bdk_esplora::{esplora_client, EsploraExt};
let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking();
let request = wallet
.start_sync_with_revealed_spks()
.inspect(|item, prog| {
if let SyncItem::Spk(index, script) = item {
let address = Address::from_script(script, NETWORK).unwrap();
let progress = prog.consumed() as f32 / prog.total() as f32;
eprintln!("[ SYNCING {:.2}% ] {:?} {}", 100.0 * progress, index, address);
std::io::stdout().flush().unwrap();
}
});
let update = client.sync(request, PARALLEL_REQUESTS)?;
wallet.apply_update(update)?;
wallet.persist(&mut db)?;
// --8<-- [end:sync]
*/
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ nav:
- Getting Started: getting-started/getting-started.md
- API Documentation: getting-started/api-documentation.md
- Build a Wallet: getting-started/build-a-wallet.md
- Migrating from 0.x: getting-started/migrating.md
- Cookbook:
- Quick Start Example: cookbook/quickstart.md
- Full Wallet Example: cookbook/full-wallet.md
Expand Down

0 comments on commit a5da578

Please sign in to comment.