Skip to content

Migrating from 0.X

So you're ready to migrate from a pre-1.0 bdk version to bdk_wallet, congratulations! This document contains some helpful tips that should make the process as seamless as possible. The process applies to pre-1.0 bdk wallets, such as bdk v0.30, backed by a SQLite database being migrated to a new bdk_wallet v2.4 or v3.0 based wallet. The bdk_wallet can use any database that implements WalletPersister.

To migrate your wallet data to a new version of bdk, essentially all you need to do is grab the last known address derivation index for each keychain from the old db and set them in your new db. After restoring your keychain derivation indexes, your next wallet sync will refetch the rest of your wallet's transaction data. Doing this means we don't need to perform a full scan because we already have the derivation indexes (doing a full scan would check for used addresses based on the stop gap which is unnecessary).

This migration is important because without that metadata the new wallet may end up reusing receive addresses, which should be avoided for privacy reasons.

Note

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. Create the new wallet using the same descriptors
  2. Get the keychain info from the old wallet db file
  3. Verify the old and new keychain descriptor checksums match
  4. Restore the keychain(s) revealed address indexes
  5. Persist the new database
examples/rust/migrate-version/src/main.rs
fn main() -> anyhow::Result<()> {
    // 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")?;

    // Get new wallet keychain descriptor hashes
    let external_checksum = new_wallet.descriptor_checksum(External);
    let internal_checksum = new_wallet.descriptor_checksum(Internal);

    // Get pre v1 wallet keychains and verify checksums match current wallet descriptors
    let mut pre_v1_db =
        rusqlite::Connection::open_with_flags(BDK_DB_PATH, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
    let pre_v1_keychains = bdk_wallet::migration::get_pre_v1_wallet_keychains(&mut pre_v1_db)?;
    assert!(!pre_v1_keychains.is_empty(), "no pre v1 keychain found");

    if let Some(pre_v1_external) = pre_v1_keychains.iter().find(|k| k.keychain == External) {
        assert_eq!(pre_v1_external.checksum, external_checksum);
        // Restore revealed external keychain to pre v1 address index
        let _ = new_wallet.reveal_addresses_to(
            KeychainKind::External,
            pre_v1_external.last_derivation_index,
        );
        println!(
            "Found and set pre v1 external keychain ({}) last derivation index to {}",
            external_checksum, pre_v1_external.last_derivation_index
        );
    } else {
        println!("no external pre v1 keychain found");
    }

    if let Some(pre_v1_internal) = pre_v1_keychains.iter().find(|k| k.keychain == Internal) {
        assert_eq!(pre_v1_internal.checksum.clone(), internal_checksum);
        // Restore revealed internal keychain to pre v1 address index
        let _ = new_wallet.reveal_addresses_to(Internal, pre_v1_internal.last_derivation_index);
        println!(
            "Found and set pre v1 internal keychain ({}) last derivation index to {}",
            internal_checksum, pre_v1_internal.last_derivation_index
        );
    } else {
        println!("no internal pre v1 keychain found");
    }

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

    Ok(())
}

Walkthrough

In your Rust project replace your pre-1.0 bdk dependency with bdk_wallet.

Cargo.toml
[dependencies]
bdk_wallet = { version = "3.0.0", features = ["rusqlite"] }

Take a minute to define a few constants, for example the file path to the current pre-1.0 bdk database and the path to be used for the new bdk_wallet 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 must not yet be a persisted bdk_wallet database file at the new path.

examples/rust/migrate-version/src/main.rs
const EXTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdufyJrFBAzSzoC5ANzovUKZ76md8EHq6hFEsVBv9SpgqaetP1WkD18VqF1xWza8kQGNtFZkNDuCDyXDMyNpLVJ7QXTqeiGG/84'/1'/0'/0/*)#72k0lrja";
const INTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdufyJrFBAzSzoC5ANzovUKZ76md8EHq6hFEsVBv9SpgqaetP1WkD18VqF1xWza8kQGNtFZkNDuCDyXDMyNpLVJ7QXTqeiGG/84'/1'/0'/1/*)#07nwzkz9";
const NETWORK: Network = Network::Testnet;

// path to old pre1 db
const BDK_DB_PATH: &str = "./bdk-example.sqlite";
// path to new db
const BDK_WALLET_DB_PATH: &str = "./bdk-wallet-example.sqlite";

Now create the new bdk_wallet wallet using the same descriptors as your original pre-1.0 bdk Wallet:

examples/rust/migrate-version/src/main.rs
// 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")?;

And then retrieve the keychain details from the original pre-1.0 bdk wallet sqlite database file, verify the descriptor checksums match, and set your new bdk_wallet based wallet to use the correct keychain address derivation indexes.

examples/rust/migrate-version/src/main.rs
// Get new wallet keychain descriptor hashes
let external_checksum = new_wallet.descriptor_checksum(External);
let internal_checksum = new_wallet.descriptor_checksum(Internal);

// Get pre v1 wallet keychains and verify checksums match current wallet descriptors
let mut pre_v1_db =
    rusqlite::Connection::open_with_flags(BDK_DB_PATH, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
let pre_v1_keychains = bdk_wallet::migration::get_pre_v1_wallet_keychains(&mut pre_v1_db)?;
assert!(!pre_v1_keychains.is_empty(), "no pre v1 keychain found");

if let Some(pre_v1_external) = pre_v1_keychains.iter().find(|k| k.keychain == External) {
    assert_eq!(pre_v1_external.checksum, external_checksum);
    // Restore revealed external keychain to pre v1 address index
    let _ = new_wallet.reveal_addresses_to(
        KeychainKind::External,
        pre_v1_external.last_derivation_index,
    );
    println!(
        "Found and set pre v1 external keychain ({}) last derivation index to {}",
        external_checksum, pre_v1_external.last_derivation_index
    );
} else {
    println!("no external pre v1 keychain found");
}

if let Some(pre_v1_internal) = pre_v1_keychains.iter().find(|k| k.keychain == Internal) {
    assert_eq!(pre_v1_internal.checksum.clone(), internal_checksum);
    // Restore revealed internal keychain to pre v1 address index
    let _ = new_wallet.reveal_addresses_to(Internal, pre_v1_internal.last_derivation_index);
    println!(
        "Found and set pre v1 internal keychain ({}) last derivation index to {}",
        internal_checksum, pre_v1_internal.last_derivation_index
    );
} else {
    println!("no internal pre v1 keychain found");
}

And finally you will need to persist your new wallet.

examples/rust/migrate-version/src/main.rs
// Persist new wallet
new_wallet.persist(&mut db)?;

Now that we have a new database and have properly restored our keychain address indexes, you will need to sync with the blockchain to recover the wallet's transactions. See the page on how to sync your wallet with electrum as an example, but you can use any blockchain client backend to re-sync your wallet.