Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/test-android.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
- name: "Run Android connected tests"
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
api-level: 34
target: google_apis
arch: x86_64
profile: Nexus 6
Expand Down
10 changes: 10 additions & 0 deletions bdk-android/lib/src/androidTest/assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

The wallet database `awesome_wallet_1.sqlite3` is used for testing. This wallet:

- Was created using bdk_wallet 2.X
- Is a Regtest wallet
- Was built using 2 descriptors
- Has a transaction on address index 0
Expand Down Expand Up @@ -31,3 +32,12 @@ The wallet database `wallet_pre_v1.sqlite3` is used for testing. This wallet:
- Has revealed the first 8 addresses on the external keychain (last revealed index is 7)
- Has revealed the first address on the internal keychain (last revealed index is 0)
- The descriptors are BIP86 descriptors with the MNEMONIC_AWESOME mnemonic

## Old Databases

The `old_databases` directory contains wallets created from different versions of BDK. These wallets:

- Are Regtest wallets
- Have revealed 7 addresses (0-6) on the external keychain (next address to come up is index 7)
- Have revealed the first address (index 0) on the internal keychain (next address to come up is index 1)
- The wallets were created with BIP84 descriptors with the MNEMONIC_ALL mnemonic
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package org.bitcoindevkit

import android.database.sqlite.SQLiteDatabase
import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

const val MNEMONIC_ALL = "all all all all all all all all all all all all"

class DatabaseVersionCompatTest {
private fun resolveAsset(dbFileName: String, subDir: String? = null): String {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val assetPath = if (subDir != null) "$subDir/$dbFileName" else dbFileName
val destFile = File(context.getDatabasePath(dbFileName).path)
context.assets.open(assetPath).use { input ->
destFile.outputStream().use { output ->
input.copyTo(output)
}
}
return destFile.absolutePath
}

val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(
NetworkKind.TEST,
Mnemonic.fromString(MNEMONIC_ALL),
null
)
val descriptor: Descriptor = Descriptor.newBip84(descriptorSecretKey, KeychainKind.EXTERNAL, NetworkKind.TEST)
val changeDescriptor: Descriptor = Descriptor.newBip84(descriptorSecretKey, KeychainKind.INTERNAL, NetworkKind.TEST)

// You can take a v1 Wallet database and use it to create a v3 Wallet.
@Test
fun loadV1WalletDBIntoV3Wallet() {
val dbV1 = Persister.newSqlite(resolveAsset("db_v1.sqlite3", "old_databases"))

val wallet = Wallet.load(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
persister = dbV1,
)

val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
val changeAddressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.INTERNAL)
println("Address info: $addressInfo")
println("Change address info: $changeAddressInfo")

assertEquals(addressInfo.index, 7u)
assertEquals(changeAddressInfo.index, 1u)
}

// You can take a v2 Wallet database and use it to create a v3 Wallet.
@Test
fun loadV2WalletDBIntoV3Wallet() {
val dbV2 = Persister.newSqlite(resolveAsset("db_v2.sqlite3", "old_databases"))

val wallet = Wallet.load(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
persister = dbV2,
)

val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
val changeAddressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.INTERNAL)
// println("Address info: $addressInfo")
// println("Change address info: $changeAddressInfo")

assertEquals(addressInfo.index, 7u)
assertEquals(changeAddressInfo.index, 1u)
}

// You can take a v1 Wallet database and use it to create a v3 Wallet, and the database migrates gracefully.
// The v3 database adds the bdk_descriptor_derived_spks and bdk_wallet_locked_outpoints tables.
@Test
fun v3WalletWillAddRequiredFieldsToV1DB() {
val dbPath = resolveAsset("db_v1.sqlite3", "old_databases")
val oldV1DB = Persister.newSqlite(dbPath)

val tablesBefore: List<String> = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY).use { db ->
db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null).use { cursor ->
generateSequence { if (cursor.moveToNext()) cursor.getString(0) else null }.toList()
}
}
// println("V1 Database Tables: $tablesBefore")
// V1 Database Tables: [bdk_schemas, bdk_wallet, bdk_blocks, bdk_txs, bdk_txouts, bdk_anchors, bdk_descriptor_last_revealed]

assertTrue(!tablesBefore.contains("bdk_descriptor_derived_spks"))
assertTrue(!tablesBefore.contains("bdk_wallet_locked_outpoints"))

val wallet1 = Wallet.load(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
persister = oldV1DB,
)

val addressInfoWallet1: AddressInfo = wallet1.revealNextAddress(KeychainKind.EXTERNAL)
val changeAddressInfoWallet1: AddressInfo = wallet1.revealNextAddress(KeychainKind.INTERNAL)

assertEquals(addressInfoWallet1.index, 7u)
assertEquals(changeAddressInfoWallet1.index, 1u)

wallet1.persist(oldV1DB)

val tables: List<String> = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY).use { db ->
db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null).use { cursor ->
generateSequence { if (cursor.moveToNext()) cursor.getString(0) else null }.toList()
}
}

// println("V3 Database Tables: $tables")
// V3 Database Tables: [bdk_schemas, bdk_wallet, bdk_blocks, bdk_txs, bdk_txouts, bdk_anchors, bdk_descriptor_last_revealed, bdk_wallet_locked_outpoints, bdk_descriptor_derived_spks]

assertTrue(tables.contains("bdk_descriptor_derived_spks"))
assertTrue(tables.contains("bdk_wallet_locked_outpoints"))
}

// You can take a v2 Wallet database and use it to create a v3 Wallet, and the database migrates gracefully.
// The v3 database adds the bdk_wallet_locked_outpoints table.
@Test
fun v3WalletWillAddRequiredFieldsToV2DB() {
val dbPath = resolveAsset("db_v2.sqlite3", "old_databases")
val oldV1DB = Persister.newSqlite(dbPath)

val tablesBefore: List<String> = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY).use { db ->
db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null).use { cursor ->
generateSequence { if (cursor.moveToNext()) cursor.getString(0) else null }.toList()
}
}
println("V2 Database Tables: $tablesBefore")
// V2 Database Tables: [bdk_schemas, bdk_wallet, bdk_blocks, bdk_txs, bdk_txouts, bdk_anchors, bdk_descriptor_last_revealed, bdk_descriptor_derived_spks]
assertTrue(!tablesBefore.contains("bdk_wallet_locked_outpoints"))

val wallet1 = Wallet.load(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
persister = oldV1DB,
)

val addressInfoWallet1: AddressInfo = wallet1.revealNextAddress(KeychainKind.EXTERNAL)
val changeAddressInfoWallet1: AddressInfo = wallet1.revealNextAddress(KeychainKind.INTERNAL)

assertEquals(addressInfoWallet1.index, 7u)
assertEquals(changeAddressInfoWallet1.index, 1u)

wallet1.persist(oldV1DB)

val tables: List<String> = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY).use { db ->
db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null).use { cursor ->
generateSequence { if (cursor.moveToNext()) cursor.getString(0) else null }.toList()
}
}

println("V3 Database Tables: $tables")
// V3 Database Tables: [bdk_schemas, bdk_wallet, bdk_blocks, bdk_txs, bdk_txouts, bdk_anchors, bdk_descriptor_last_revealed, bdk_wallet_locked_outpoints, bdk_descriptor_derived_spks]

assertTrue(tables.contains("bdk_wallet_locked_outpoints"))
}

// The v3 wallet can load a v3 database
@Test
fun loadV3Wallet() {
val v3DB = Persister.newSqlite(resolveAsset("db_v3.sqlite3"))

val wallet = Wallet.load(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
persister = v3DB,
)

val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
val changeAddressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.INTERNAL)
println("Address info: $addressInfo")
println("Change address info: $changeAddressInfo")

assertEquals(addressInfo.index, 7u)
assertEquals(changeAddressInfo.index, 1u)
}

// You can correctly migrate a v0.32 Wallet into a v3 Wallet
@Test
fun migrateToV3From032() {
val oldDB = Persister.newSqlite(resolveAsset("db_v032.sqlite3", "old_databases"))
val preV1Keychains: List<PreV1WalletKeychain> = oldDB.getPreV1WalletKeychains()

val externalPreV1Keychain = preV1Keychains.single { it.keychain == KeychainKind.EXTERNAL }
val internalPreV1Keychain = preV1Keychains.single { it.keychain == KeychainKind.INTERNAL }

assertEquals(2, preV1Keychains.size)
assertEquals(KeychainKind.EXTERNAL, externalPreV1Keychain.keychain)
assertEquals(KeychainKind.INTERNAL, internalPreV1Keychain.keychain)
assertEquals(6u, externalPreV1Keychain.lastDerivationIndex)
assertEquals(0u, internalPreV1Keychain.lastDerivationIndex)
assertEquals("rn0zejch", externalPreV1Keychain.checksum)
assertEquals("j82ry8g0", internalPreV1Keychain.checksum)

val newV3DB = Persister.newInMemory()

val wallet = Wallet(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
network = Network.REGTEST,
persister = newV3DB,
)

wallet.revealAddressesTo(KeychainKind.EXTERNAL, externalPreV1Keychain.lastDerivationIndex)
wallet.revealAddressesTo(KeychainKind.INTERNAL, internalPreV1Keychain.lastDerivationIndex)

val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
val changeAddressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.INTERNAL)
// println("Address info: $addressInfo")
// println("Change address info: $changeAddressInfo")

assertEquals(addressInfo.index, 7u)
assertEquals(changeAddressInfo.index, 1u)
}
}
Loading