Add a `seed` argument to `Synchronizer.Companion.new`
This enables callers to recover from an error that indicates the seed is needed for a database migration.
This commit is contained in:
parent
b19ee179c5
commit
c6fd783317
|
@ -27,6 +27,9 @@ Change Log
|
|||
- `Initializer.Config.newWallet`
|
||||
- `Initializer.Config.setViewingKeys`
|
||||
- `cash.z.ecc.android.sdk`:
|
||||
- `Synchronizer.Companion.new` now takes a `seed` argument. A non-null value should be
|
||||
provided if `Synchronizer.Companion.new` throws an error that a database migration
|
||||
requires the wallet seed.
|
||||
- `Synchronizer.shieldFunds` now takes a transparent account private key (representing
|
||||
all transparent secret keys within an account) instead of a transparent secret key.
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
|||
)
|
||||
}
|
||||
}.let { initializer ->
|
||||
synchronizer = Synchronizer.newBlocking(initializer)
|
||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
|
|||
ZcashNetwork.fromResources(requireApplicationContext())
|
||||
)
|
||||
}
|
||||
synchronizer = Synchronizer.newBlocking(initializer)
|
||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
||||
}
|
||||
|
||||
private fun initTransactionUI() {
|
||||
|
|
|
@ -79,7 +79,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||
it.alias = "Demo_Utxos"
|
||||
}
|
||||
}
|
||||
synchronizer = runBlocking { Synchronizer.new(initializer) }
|
||||
synchronizer = runBlocking { Synchronizer.new(initializer, seed) }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -80,7 +80,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
|||
}
|
||||
}
|
||||
}.let { initializer ->
|
||||
synchronizer = Synchronizer.newBlocking(initializer)
|
||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
||||
}
|
||||
spendingKey = runBlocking {
|
||||
DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first()
|
||||
|
|
|
@ -2025,7 +2025,9 @@ dependencies = [
|
|||
"jni",
|
||||
"log",
|
||||
"log-panics",
|
||||
"schemer",
|
||||
"secp256k1",
|
||||
"secrecy",
|
||||
"zcash_client_backend",
|
||||
"zcash_client_sqlite",
|
||||
"zcash_primitives",
|
||||
|
|
|
@ -18,7 +18,9 @@ hex = "0.4"
|
|||
jni = { version = "0.17", default-features = false }
|
||||
log = "0.4"
|
||||
log-panics = "2.0.0"
|
||||
schemer = "0.2"
|
||||
secp256k1 = "0.21"
|
||||
secrecy = "0.8"
|
||||
zcash_client_backend = { version = "0.5", features = ["transparent-inputs"] }
|
||||
zcash_client_sqlite = { version = "0.3", features = ["transparent-inputs"] }
|
||||
zcash_primitives = "0.7"
|
||||
|
|
|
@ -797,12 +797,13 @@ object DefaultSynchronizerFactory {
|
|||
// can touch its views. and is probably related to FlowPagedList
|
||||
// TODO [#242]: https://github.com/zcash/zcash-android-wallet-sdk/issues/242
|
||||
private const val DEFAULT_PAGE_SIZE = 1000
|
||||
suspend fun defaultTransactionRepository(initializer: Initializer): TransactionRepository =
|
||||
suspend fun defaultTransactionRepository(initializer: Initializer, seed: ByteArray?): TransactionRepository =
|
||||
PagedTransactionRepository.new(
|
||||
initializer.context,
|
||||
initializer.network,
|
||||
DEFAULT_PAGE_SIZE,
|
||||
initializer.rustBackend,
|
||||
seed,
|
||||
initializer.checkpoint,
|
||||
initializer.viewingKeys,
|
||||
initializer.overwriteVks
|
||||
|
|
|
@ -444,11 +444,14 @@ interface Synchronizer {
|
|||
* Synchronizer requires. It contains all information necessary to build a synchronizer and it is
|
||||
* mainly responsible for initializing the databases associated with this synchronizer and loading
|
||||
* the rust backend.
|
||||
* @param seed the wallet's seed phrase. This only needs to be provided if this method returns an
|
||||
* error indicating that the seed phrase is required for a database migration.
|
||||
*/
|
||||
suspend fun new(
|
||||
initializer: Initializer
|
||||
initializer: Initializer,
|
||||
seed: ByteArray,
|
||||
): Synchronizer {
|
||||
val repository = DefaultSynchronizerFactory.defaultTransactionRepository(initializer)
|
||||
val repository = DefaultSynchronizerFactory.defaultTransactionRepository(initializer, seed)
|
||||
val blockStore = DefaultSynchronizerFactory.defaultBlockStore(initializer)
|
||||
val service = DefaultSynchronizerFactory.defaultService(initializer)
|
||||
val encoder = DefaultSynchronizerFactory.defaultEncoder(initializer, repository)
|
||||
|
@ -472,6 +475,8 @@ interface Synchronizer {
|
|||
* This is a blocking call, so it should not be called from the main thread.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun newBlocking(initializer: Initializer): Synchronizer = runBlocking { new(initializer) }
|
||||
fun newBlocking(initializer: Initializer, seed: ByteArray): Synchronizer = runBlocking {
|
||||
new (initializer, seed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,6 +175,10 @@ sealed class BirthdayException(message: String, cause: Throwable? = null) : SdkE
|
|||
* Exceptions thrown by the initializer.
|
||||
*/
|
||||
sealed class InitializerException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||
object SeedRequired : InitializerException(
|
||||
"A pending database migration requires the wallet's seed. Call this initialization " +
|
||||
"method again with the seed."
|
||||
)
|
||||
class FalseStart(cause: Throwable?) : InitializerException("Failed to initialize accounts due to: $cause", cause)
|
||||
class AlreadyInitializedException(cause: Throwable, dbPath: String) : InitializerException(
|
||||
"Failed to initialize the blocks table" +
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.paging.PagedList
|
|||
import androidx.room.RoomDatabase
|
||||
import cash.z.ecc.android.sdk.db.commonDatabaseBuilder
|
||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||
import cash.z.ecc.android.sdk.exception.InitializerException.SeedRequired
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
||||
import cash.z.ecc.android.sdk.internal.SdkExecutors
|
||||
|
@ -122,11 +123,12 @@ internal class PagedTransactionRepository private constructor(
|
|||
zcashNetwork: ZcashNetwork,
|
||||
pageSize: Int = 10,
|
||||
rustBackend: RustBackend,
|
||||
seed: ByteArray?,
|
||||
birthday: Checkpoint,
|
||||
viewingKeys: List<UnifiedFullViewingKey>,
|
||||
overwriteVks: Boolean = false
|
||||
): PagedTransactionRepository {
|
||||
initMissingDatabases(rustBackend, birthday, viewingKeys)
|
||||
initMissingDatabases(rustBackend, seed, birthday, viewingKeys)
|
||||
|
||||
val db = buildDatabase(appContext.applicationContext, rustBackend.dataDbFile)
|
||||
applyKeyMigrations(rustBackend, overwriteVks, viewingKeys)
|
||||
|
@ -172,10 +174,11 @@ internal class PagedTransactionRepository private constructor(
|
|||
*/
|
||||
private suspend fun initMissingDatabases(
|
||||
rustBackend: RustBackend,
|
||||
seed: ByteArray?,
|
||||
birthday: Checkpoint,
|
||||
viewingKeys: List<UnifiedFullViewingKey>
|
||||
) {
|
||||
maybeCreateDataDb(rustBackend)
|
||||
maybeCreateDataDb(rustBackend, seed)
|
||||
maybeInitBlocksTable(rustBackend, birthday)
|
||||
maybeInitAccountsTable(rustBackend, viewingKeys)
|
||||
}
|
||||
|
@ -183,9 +186,15 @@ internal class PagedTransactionRepository private constructor(
|
|||
/**
|
||||
* Create the dataDb and its table, if it doesn't exist.
|
||||
*/
|
||||
private suspend fun maybeCreateDataDb(rustBackend: RustBackend) {
|
||||
tryWarn("Warning: did not create dataDb. It probably already exists.") {
|
||||
rustBackend.initDataDb()
|
||||
private suspend fun maybeCreateDataDb(rustBackend: RustBackend, seed: ByteArray?) {
|
||||
tryWarn(
|
||||
"Warning: did not create dataDb. It probably already exists.",
|
||||
unlessContains = "requires the wallet's seed"
|
||||
) {
|
||||
val res = rustBackend.initDataDb(seed)
|
||||
if (res == 1) {
|
||||
throw SeedRequired
|
||||
}
|
||||
twig("Initialized wallet for first run file: ${rustBackend.dataDbFile}")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,9 +44,10 @@ internal class RustBackend private constructor(
|
|||
// Wrapper Functions
|
||||
//
|
||||
|
||||
override suspend fun initDataDb() = withContext(SdkDispatchers.DATABASE_IO) {
|
||||
override suspend fun initDataDb(seed: ByteArray?) = withContext(SdkDispatchers.DATABASE_IO) {
|
||||
initDataDb(
|
||||
dataDbFile.absolutePath,
|
||||
seed,
|
||||
networkId = network.id
|
||||
)
|
||||
}
|
||||
|
@ -384,7 +385,7 @@ internal class RustBackend private constructor(
|
|||
//
|
||||
|
||||
@JvmStatic
|
||||
private external fun initDataDb(dbDataPath: String, networkId: Int): Boolean
|
||||
private external fun initDataDb(dbDataPath: String, seed: ByteArray?, networkId: Int): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun initAccountsTableWithKeys(
|
||||
|
|
|
@ -40,7 +40,7 @@ internal interface RustBackendWelding {
|
|||
|
||||
suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean
|
||||
|
||||
suspend fun initDataDb(): Boolean
|
||||
suspend fun initDataDb(seed: ByteArray?): Int
|
||||
|
||||
fun isValidShieldedAddr(addr: String): Boolean
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ use jni::{
|
|||
JNIEnv,
|
||||
};
|
||||
use log::Level;
|
||||
use schemer::MigratorError;
|
||||
use secp256k1::PublicKey;
|
||||
use secrecy::SecretVec;
|
||||
use zcash_client_backend::keys::UnifiedSpendingKey;
|
||||
use zcash_client_backend::{
|
||||
address::RecipientAddress,
|
||||
|
@ -36,6 +38,7 @@ use zcash_client_backend::{
|
|||
keys::{sapling, UnifiedFullViewingKey},
|
||||
wallet::{OvkPolicy, WalletTransparentOutput},
|
||||
};
|
||||
use zcash_client_sqlite::wallet::init::WalletMigrationError;
|
||||
#[allow(deprecated)]
|
||||
use zcash_client_sqlite::wallet::{delete_utxos_above, get_rewind_height};
|
||||
use zcash_client_sqlite::{
|
||||
|
@ -106,26 +109,40 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initLogs(
|
|||
print_debug_state()
|
||||
}
|
||||
|
||||
/// Sets up the internal structure of the data database.
|
||||
///
|
||||
/// If `seed` is `null`, database migrations will be attempted without it.
|
||||
///
|
||||
/// Returns 0 if successful, 1 if the seed must be provided in order to execute the requested
|
||||
/// migrations, or -1 otherwise.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initDataDb(
|
||||
env: JNIEnv<'_>,
|
||||
_: JClass<'_>,
|
||||
db_data: JString<'_>,
|
||||
seed: jbyteArray,
|
||||
network_id: jint,
|
||||
) -> jboolean {
|
||||
) -> jint {
|
||||
let res = panic::catch_unwind(|| {
|
||||
let network = parse_network(network_id as u32)?;
|
||||
let db_path = utils::java_string_to_rust(&env, db_data);
|
||||
let seed = None; // TODO Update
|
||||
WalletDb::for_path(db_path, network)
|
||||
.map_err(|e| format_err!("Error while opening data DB: {}", e))
|
||||
.and_then(|mut db| {
|
||||
init_wallet_db(&mut db, seed)
|
||||
.map_err(|e| format_err!("Error while initializing data DB: {}", e))
|
||||
})
|
||||
.map(|()| JNI_TRUE)
|
||||
|
||||
let mut db_data = WalletDb::for_path(db_path, network)
|
||||
.map_err(|e| format_err!("Error while opening data DB: {}", e))?;
|
||||
|
||||
let seed = (!seed.is_null()).then(|| SecretVec::new(env.convert_byte_array(seed).unwrap()));
|
||||
|
||||
match init_wallet_db(&mut db_data, seed) {
|
||||
Ok(()) => Ok(0),
|
||||
Err(MigratorError::Migration { error, .. })
|
||||
if matches!(error, WalletMigrationError::SeedRequired) =>
|
||||
{
|
||||
Ok(1)
|
||||
}
|
||||
Err(e) => Err(format_err!("Error while initializing data DB: {}", e)),
|
||||
}
|
||||
});
|
||||
unwrap_exc_or(&env, res, JNI_FALSE)
|
||||
unwrap_exc_or(&env, res, -1)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
Loading…
Reference in New Issue