Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add page Migrating from 0.X #86

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essentially the only thing we're doing correct? I would personally say something like
"to migrate your wallet data to a new version of bdk, all you essentially need to do is grab the [last used addresses] from the old db, add them to the new db and sync to refetch the rest of the data. Note that we don't need to perform a full scan because we will already have the last used addresses"
To make it extra clear

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like an important note, I would add a subheader here

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally I would mention again here that a scan is not necessary, just a sync will suffice (since it's not obvious that having the last used addresses is the only difference)

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