From d0d7007e8398035e43d6d32eff149f167301ae32 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Sat, 8 Mar 2025 10:54:41 +0100 Subject: [PATCH] import: implement import_backup() --- Cargo.lock | 481 ++++++++++++++++-- liana-gui/src/app/state/export.rs | 62 ++- liana-gui/src/app/state/settings/mod.rs | 12 +- liana-gui/src/app/state/settings/wallet.rs | 4 +- liana-gui/src/app/state/transactions.rs | 2 +- liana-gui/src/app/view/export.rs | 65 ++- liana-gui/src/backup.rs | 12 +- liana-gui/src/export.rs | 433 +++++++++++++++- .../src/installer/step/descriptor/mod.rs | 2 +- lianad/Cargo.toml | 4 +- lianad/src/database/mod.rs | 7 +- lianad/src/database/sqlite/schema.rs | 24 +- 12 files changed, 1018 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79b4137f..ba52bb17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,49 @@ dependencies = [ "subtle", ] +[[package]] +name = "age" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc171f4874fa10887e47088f81a55fcf030cd421aa31ec2b370cafebcc608a" +dependencies = [ + "age-core", + "base64 0.21.7", + "bech32 0.9.1", + "chacha20poly1305", + "cookie-factory", + "hmac", + "i18n-embed", + "i18n-embed-fl", + "lazy_static", + "nom", + "pin-project", + "rand", + "rust-embed", + "scrypt", + "sha2", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "age-core" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99" +dependencies = [ + "base64 0.21.7", + "chacha20poly1305", + "cookie-factory", + "hkdf", + "io_tee", + "nom", + "rand", + "secrecy", + "sha2", +] + [[package]] name = "ahash" version = "0.8.11" @@ -120,7 +163,7 @@ dependencies = [ "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -168,6 +211,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayref" version = "0.3.9" @@ -484,6 +533,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bdk_chain" version = "0.16.0" @@ -508,6 +566,12 @@ dependencies = [ "electrum-client", ] +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bech32" version = "0.11.0" @@ -516,13 +580,16 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bip329" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdacd3f71820072c8d76eb077a8177b9a001bf03723d797783f97e9feefc0515" +checksum = "8351c1cf438ae5814ad2a696f06fd07d7be4a1a133d889b1785260fa8797798c" dependencies = [ + "age", + "bitcoin", + "hex", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -579,7 +646,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "zeroize", ] @@ -592,7 +659,7 @@ checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" dependencies = [ "base58ck", "base64 0.21.7", - "bech32", + "bech32 0.11.0", "bitcoin-internals 0.3.0", "bitcoin-io", "bitcoin-units", @@ -804,7 +871,7 @@ dependencies = [ "polling", "rustix", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -938,7 +1005,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c" dependencies = [ - "thiserror", + "thiserror 1.0.69", "x11rb", ] @@ -1049,6 +1116,15 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1136,7 +1212,7 @@ dependencies = [ "rayon", "rustc-hash 1.1.0", "rustybuzz", - "self_cell", + "self_cell 1.1.0", "swash", "sys-locale", "ttf-parser 0.21.1", @@ -1275,6 +1351,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + [[package]] name = "data-url" version = "0.3.1" @@ -1695,6 +1785,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1723,6 +1822,50 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" +[[package]] +name = "fluent" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash 1.1.0", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2049,7 +2192,7 @@ checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" dependencies = [ "log", "presser", - "thiserror", + "thiserror 1.0.69", "winapi", "windows", ] @@ -2171,7 +2314,7 @@ dependencies = [ "com", "libc", "libloading 0.8.6", - "thiserror", + "thiserror 1.0.69", "widestring", "winapi", ] @@ -2240,6 +2383,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2330,6 +2482,73 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "i18n-config" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror 1.0.69", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0454970a5853f498e686cbd7bf9391aac2244928194780cb7a0af0f41937db6" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "log", + "parking_lot 0.12.3", + "rust-embed", + "thiserror 1.0.69", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7578cee2940492a648bd60fb49ca85ee8c821a63790e0ef5b604cfed353b2a" +dependencies = [ + "dashmap", + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "proc-macro-error2", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.98", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -2365,7 +2584,7 @@ dependencies = [ "iced_widget", "iced_winit", "image", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2383,7 +2602,7 @@ dependencies = [ "palette", "rustc-hash 2.1.1", "smol_str", - "thiserror", + "thiserror 1.0.69", "web-time", ] @@ -2434,7 +2653,7 @@ dependencies = [ "once_cell", "raw-window-handle", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", ] @@ -2448,7 +2667,7 @@ dependencies = [ "iced_tiny_skia", "iced_wgpu", "log", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2461,7 +2680,7 @@ dependencies = [ "iced_core", "iced_futures", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2499,7 +2718,7 @@ dependencies = [ "once_cell", "resvg", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "wgpu", ] @@ -2516,7 +2735,7 @@ dependencies = [ "ouroboros", "qrcode", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", ] @@ -2531,7 +2750,7 @@ dependencies = [ "iced_runtime", "log", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "tracing", "wasm-bindgen-futures", "web-sys", @@ -2731,6 +2950,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "intl-memoizer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + [[package]] name = "io-kit-sys" version = "0.4.1" @@ -2741,6 +2979,12 @@ dependencies = [ "mach2", ] +[[package]] +name = "io_tee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" + [[package]] name = "ipnet" version = "2.11.0" @@ -2782,7 +3026,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -2951,7 +3195,7 @@ dependencies = [ "ledger-transport", "libc", "log", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3292,13 +3536,19 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniscript" version = "12.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd3c9608217b0d6fa9c9c8ddd875b85ab72bd4311cfc8db35e1b5a08fc11f4d" dependencies = [ - "bech32", + "bech32 0.11.0", "bitcoin", "serde", ] @@ -3377,7 +3627,7 @@ dependencies = [ "rustc-hash 1.1.0", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -3393,7 +3643,7 @@ dependencies = [ "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3474,6 +3724,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3945,6 +4205,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4159,6 +4429,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -4419,7 +4711,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4594,6 +4886,40 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-embed" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.98", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust-ini" version = "0.19.0" @@ -4736,6 +5062,15 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4757,6 +5092,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sct" version = "0.7.1" @@ -4814,6 +5160,24 @@ dependencies = [ "cc", ] +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.1.0", +] + [[package]] name = "self_cell" version = "1.1.0" @@ -5033,7 +5397,7 @@ dependencies = [ "log", "memmap2", "rustix", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -5174,6 +5538,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -5315,7 +5685,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -5329,6 +5708,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -5601,6 +5991,15 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash 1.1.0", +] + [[package]] name = "typenum" version = "1.17.0" @@ -5624,7 +6023,26 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" dependencies = [ - "thiserror", + "thiserror 1.0.69", +] + +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "serde", + "tinystr", ] [[package]] @@ -6112,7 +6530,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror", + "thiserror 1.0.69", "web-sys", "wgpu-hal", "wgpu-types", @@ -6156,7 +6574,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types", @@ -6234,7 +6652,7 @@ dependencies = [ "clipboard_wayland", "clipboard_x11", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6602,6 +7020,7 @@ checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", "rand_core", + "serde", "zeroize", ] diff --git a/liana-gui/src/app/state/export.rs b/liana-gui/src/app/state/export.rs index e6f3ac0c..1e413705 100644 --- a/liana-gui/src/app/state/export.rs +++ b/liana-gui/src/app/state/export.rs @@ -48,6 +48,7 @@ impl ExportModal { ImportExportType::ExportLabels => "Export Labels", ImportExportType::ImportPsbt => "Import PSBT", ImportExportType::ImportDescriptor => "Import Descriptor", + ImportExportType::ImportBackup(..) => "Restore Backup", } } @@ -70,12 +71,14 @@ impl ExportModal { ImportExportType::ImportPsbt => "psbt.psbt".into(), ImportExportType::ImportDescriptor => "descriptor.descriptor".into(), ImportExportType::ExportLabels => format!("liana-labels-{date}.jsonl"), - ImportExportType::ExportBackup(_) => format!("liana-backup-{date}.json"), + ImportExportType::ExportBackup(_) | ImportExportType::ImportBackup(_, _) => { + format!("liana-backup-{date}.json") + } } } - pub fn launch + Send + 'static>(&self) -> Task { - Task::perform(get_path(self.default_filename()), move |m| { + pub fn launch + Send + 'static>(&self, write: bool) -> Task { + Task::perform(get_path(self.default_filename(), write), move |m| { ImportExportMessage::Path(m).into() }) } @@ -95,11 +98,21 @@ impl ExportModal { self.state = ImportExportState::Progress(p); } } - Progress::Finished | Progress::Ended => { - self.state = ImportExportState::Ended; + Progress::Finished | Progress::Ended => self.state = ImportExportState::Ended, + Progress::KeyAliasesConflict(ref sender) => { + if let ImportExportType::ImportBackup(_, aliases) = &self.import_export_type { + self.import_export_type = + ImportExportType::ImportBackup(Some(sender.clone()), aliases.clone()); + } + } + Progress::LabelsConflict(ref sender) => { + if let ImportExportType::ImportBackup(labels, _) = &self.import_export_type { + self.import_export_type = + ImportExportType::ImportBackup(labels.clone(), Some(sender.clone())); + } } Progress::Error(e) => { - self.error = Some(e); + self.error = Some(e.clone()); } Progress::None => {} Progress::Psbt(_) => { @@ -130,6 +143,36 @@ impl ExportModal { } } ImportExportMessage::Close | ImportExportMessage::Open => { /* unreachable */ } + ImportExportMessage::Overwrite => { + if let ImportExportType::ImportBackup(labels, aliases) = + &mut self.import_export_type + { + if let Some(sender) = labels.take() { + if sender.send(true).is_err() { + tracing::error!("ExportModal.update(): fail to send labels ACK"); + } + } else if let Some(sender) = aliases.take() { + if sender.send(true).is_err() { + tracing::error!("ExportModal.update(): fail to send aliases ACK"); + } + } + } + } + ImportExportMessage::Ignore => { + if let ImportExportType::ImportBackup(labels, aliases) = + &mut self.import_export_type + { + if let Some(sender) = labels.take() { + if sender.send(false).is_err() { + tracing::error!("ExportModal.update(): fail to send labels NACK"); + } + } else if let Some(sender) = aliases.take() { + if sender.send(false).is_err() { + tracing::error!("ExportModal.update(): fail to send aliases NACK"); + } + } + } + } } Task::none() } @@ -140,7 +183,12 @@ impl ExportModal { { let modal = Modal::new( content, - export_modal(&self.state, self.error.as_ref(), self.modal_title()), + export_modal( + &self.state, + self.error.as_ref(), + self.modal_title(), + &self.import_export_type, + ), ); match self.state { ImportExportState::TimedOut diff --git a/liana-gui/src/app/state/settings/mod.rs b/liana-gui/src/app/state/settings/mod.rs index faede757..d1bc6a0a 100644 --- a/liana-gui/src/app/state/settings/mod.rs +++ b/liana-gui/src/app/state/settings/mod.rs @@ -183,8 +183,8 @@ impl ImportExportSettingsState { } macro_rules! launch { - ($s:ident, $m: ident) => { - let launch = $m.launch(); + ($s:ident, $m: ident, $write:ident) => { + let launch = $m.launch($write); $s.modal = Some($m); return launch }; @@ -239,19 +239,19 @@ impl State for ImportExportSettingsState { Some(daemon), ImportExportType::Descriptor(self.wallet.main_descriptor.clone()), ); - launch!(self, modal); + launch!(self, modal, true); } } Message::View(view::Message::Settings(view::SettingsMessage::ExportTransactions)) => { if self.modal.is_none() { let modal = ExportModal::new(Some(daemon), ImportExportType::Transactions); - launch!(self, modal); + launch!(self, modal, true); } } Message::View(view::Message::Settings(view::SettingsMessage::ExportLabels)) => { if self.modal.is_none() { let modal = ExportModal::new(Some(daemon), ImportExportType::ExportLabels); - launch!(self, modal); + launch!(self, modal, true); } } Message::View(view::Message::Settings(view::SettingsMessage::ExportWallet)) => { @@ -281,7 +281,7 @@ impl State for ImportExportSettingsState { backup, )))) => { let modal = ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup)); - launch!(self, modal); + launch!(self, modal, true); } Message::View(view::Message::Settings(view::SettingsMessage::ImportWallet)) => { // TODO: diff --git a/liana-gui/src/app/state/settings/wallet.rs b/liana-gui/src/app/state/settings/wallet.rs index b1d65157..dd06a9c5 100644 --- a/liana-gui/src/app/state/settings/wallet.rs +++ b/liana-gui/src/app/state/settings/wallet.rs @@ -254,7 +254,7 @@ impl State for WalletSettingsState { if self.modal.is_none() { let modal = ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup)); - let launch = modal.launch(); + let launch = modal.launch(true); self.modal = Modal::ImportExport(modal); launch } else { @@ -267,7 +267,7 @@ impl State for WalletSettingsState { Some(daemon), ImportExportType::Descriptor(self.wallet.main_descriptor.clone()), ); - let launch = modal.launch(); + let launch = modal.launch(false); self.modal = Modal::ImportExport(modal); launch } else { diff --git a/liana-gui/src/app/state/transactions.rs b/liana-gui/src/app/state/transactions.rs index 03970daf..3aca960f 100644 --- a/liana-gui/src/app/state/transactions.rs +++ b/liana-gui/src/app/state/transactions.rs @@ -273,7 +273,7 @@ impl State for TransactionsPanel { ImportExportType::Transactions, )); if let TransactionsModal::Export(m) = &self.modal { - return m.launch(); + return m.launch(true); } } } diff --git a/liana-gui/src/app/view/export.rs b/liana-gui/src/app/view/export.rs index 62c63d6f..6df0f689 100644 --- a/liana-gui/src/app/view/export.rs +++ b/liana-gui/src/app/view/export.rs @@ -11,15 +11,17 @@ use liana_ui::{ widget::Element, }; -use crate::export::{Error, ImportExportMessage, ImportExportState}; +use crate::export::ImportExportState; +use crate::export::{Error, ImportExportMessage, ImportExportType}; /// Return the modal view for an export task pub fn export_modal<'a, Message: From + Clone + 'a>( state: &ImportExportState, error: Option<&'a Error>, - export_type: &str, + title: &str, + import_export_type: &ImportExportType, ) -> Element<'a, Message> { - let button = match state { + let cancel_close = match state { ImportExportState::Started | ImportExportState::Progress(_) => { Some(button::secondary(None, "Cancel").on_press(ImportExportMessage::UserStop.into())) } @@ -27,7 +29,9 @@ pub fn export_modal<'a, Message: From + Clone + 'a>( Some(button::secondary(None, "Close").on_press(ImportExportMessage::Close.into())) } _ => None, - }; + } + .map(Container::new); + let msg = if let Some(error) = error { format!("{:?}", error) } else { @@ -41,10 +45,53 @@ pub fn export_modal<'a, Message: From + Clone + 'a>( ImportExportState::Progress(p) => format!("Progress: {}%", p.round()), ImportExportState::TimedOut => "Export failed: timeout".into(), ImportExportState::Aborted => "Export canceled".into(), - ImportExportState::Ended => "Export successful!".into(), + ImportExportState::Ended => import_export_type.end_message().into(), ImportExportState::Closed => "".into(), } }; + let labels_btn = ( + "Labels conflict, what do you want to do?".to_string(), + Some(Container::new( + Row::new() + .push( + button::secondary(None, "Overwrite") + .on_press(ImportExportMessage::Overwrite.into()), + ) + .push(Space::with_width(30)) + .push( + button::secondary(None, "Ignore").on_press(ImportExportMessage::Ignore.into()), + ), + )), + ); + let aliases_btn = ( + "Aliases conflict, what do you want to do?".to_string(), + Some(Container::new( + Row::new() + .push( + button::secondary(None, "Overwrite") + .on_press(ImportExportMessage::Overwrite.into()), + ) + .push(Space::with_width(30)) + .push( + button::secondary(None, "Ignore").on_press(ImportExportMessage::Ignore.into()), + ), + )), + ); + let (msg, button) = match (error.is_none(), import_export_type) { + (true, ImportExportType::ImportBackup(labels, aliases)) => match (labels, aliases) { + (Some(_), _) => labels_btn, + + (_, Some(_)) => aliases_btn, + _ => (msg, cancel_close), + }, + _ => (msg, cancel_close), + }; + let button = button.map(|b| { + Container::new(b) + .align_x(Horizontal::Center) + .width(Length::Fill) + }); + let p = match state { ImportExportState::Init => 0.0, ImportExportState::ChoosePath | ImportExportState::Path(_) | ImportExportState::Started => { @@ -63,17 +110,13 @@ pub fn export_modal<'a, Message: From + Clone + 'a>( card::simple( Column::new() .spacing(10) - .push(Container::new(h4_bold(export_type)).width(Length::Fill)) + .push(Container::new(h4_bold(title)).width(Length::Fill)) .push(Space::with_height(Length::Fill)) .push(progress_bar_row) .push(Space::with_height(Length::Fill)) .push(Row::new().push(text(msg))) .push(Space::with_height(Length::Fill)) - .push_maybe(button.map(|b| { - Container::new(b) - .align_x(Horizontal::Right) - .width(Length::Fill) - })) + .push_maybe(button) .push(Space::with_height(5)), ) .width(Length::Fixed(500.0)) diff --git a/liana-gui/src/backup.rs b/liana-gui/src/backup.rs index ddea022e..a3ca2c48 100644 --- a/liana-gui/src/backup.rs +++ b/liana-gui/src/backup.rs @@ -72,6 +72,12 @@ pub enum Error { TxTimeMissing, } +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + impl From for Error { fn from(value: DaemonError) -> Self { Error::Daemon(value.to_string()) @@ -437,7 +443,7 @@ impl Account { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Key { pub key: Fingerprint, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -450,7 +456,7 @@ pub struct Key { pub metadata: Value, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub enum KeyRole { /// Key to be used in normal spending condition Main, @@ -462,7 +468,7 @@ pub enum KeyRole { Cosigning, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub enum KeyType { /// Main user Internal, diff --git a/liana-gui/src/export.rs b/liana-gui/src/export.rs index 6e546b90..fb797416 100644 --- a/liana-gui/src/export.rs +++ b/liana-gui/src/export.rs @@ -5,7 +5,7 @@ use std::{ path::PathBuf, str::FromStr, sync::{ - mpsc::{channel, Receiver, Sender}, + mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}, Arc, Mutex, }, time, @@ -16,7 +16,10 @@ use liana::{ descriptors::LianaDescriptor, miniscript::bitcoin::{Amount, Psbt, Txid}, }; -use lianad::bip329::{error::ExportError, Labels}; +use lianad::{ + bip329::{error::ExportError, Labels}, + commands::LabelItem, +}; use tokio::{ task::{JoinError, JoinHandle}, time::sleep, @@ -25,7 +28,11 @@ use tokio::{ use iced::futures::{SinkExt, Stream}; use crate::{ - app::view, + app::{ + settings::{KeySetting, Settings}, + view, + }, + backup::Backup, daemon::{ model::{HistoryTransaction, Labelled}, Daemon, DaemonBackend, DaemonError, @@ -61,7 +68,7 @@ macro_rules! send_progress { }; } -macro_rules! open_file { +macro_rules! open_file_write { ($path:ident, $sender:ident) => {{ let dir = match $path.parent() { Some(dir) => dir, @@ -86,6 +93,18 @@ macro_rules! open_file { }}; } +macro_rules! open_file_read { + ($path:ident, $sender:ident) => {{ + match File::open($path.as_path()) { + Ok(f) => f, + Err(e) => { + send_error!($sender, e.into()); + return; + } + } + }}; +} + #[derive(Debug, Clone)] pub enum ImportExportMessage { Open, @@ -94,6 +113,8 @@ pub enum ImportExportMessage { UserStop, Path(Option), Close, + Overwrite, + Ignore, } impl From for view::Message { @@ -129,19 +150,53 @@ pub enum Error { ParsePsbt, ParseDescriptor, Bip329Export(String), + BackupImport(String), } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub enum ImportExportType { Transactions, ExportPsbt(String), ExportBackup(String), + ImportBackup( + Option>, /*overwrite_labels*/ + Option>, /*overwrite_aliases*/ + ), Descriptor(LianaDescriptor), ExportLabels, ImportPsbt, ImportDescriptor, } +impl ImportExportType { + pub fn end_message(&self) -> &str { + match self { + ImportExportType::Transactions + | ImportExportType::ExportPsbt(_) + | ImportExportType::ExportBackup(_) + | ImportExportType::Descriptor(_) + | ImportExportType::ExportLabels => "Export successful!", + ImportExportType::ImportBackup(_, _) + | ImportExportType::ImportPsbt + | ImportExportType::ImportDescriptor => "Import successful", + } + } +} + +impl PartialEq for ImportExportType { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::ExportPsbt(l0), Self::ExportPsbt(r0)) => l0 == r0, + (Self::ExportBackup(l0), Self::ExportBackup(r0)) => l0 == r0, + (Self::ImportBackup(l0, l1), Self::ImportBackup(r0, r1)) => { + l0.is_some() == r0.is_some() && l1.is_some() == r1.is_some() + } + (Self::Descriptor(l0), Self::Descriptor(r0)) => l0 == r0, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + impl From for Error { fn from(value: JoinError) -> Self { Error::JoinError(format!("{:?}", value)) @@ -183,6 +238,8 @@ pub enum Progress { None, Psbt(Psbt), Descriptor(LianaDescriptor), + LabelsConflict(SyncSender), + KeyAliasesConflict(SyncSender), } pub struct Export { @@ -225,6 +282,7 @@ impl Export { ImportExportType::ImportPsbt => import_psbt(sender, path), ImportExportType::ImportDescriptor => import_descriptor(sender, path), ImportExportType::ExportBackup(str) => export_string(sender, path, str), + ImportExportType::ImportBackup(..) => import_backup(sender, path, daemon).await, }; } @@ -335,7 +393,7 @@ pub async fn export_transactions( return; } }; - let mut file = open_file!(path, sender); + let mut file = open_file_write!(path, sender); let header = "Date,Label,Value,Fee,Txid,Block\n".to_string(); if let Err(e) = file.write_all(header.as_bytes()) { @@ -488,7 +546,7 @@ pub async fn export_transactions( } pub fn export_descriptor(sender: Sender, path: PathBuf, descriptor: LianaDescriptor) { - let mut file = open_file!(path, sender); + let mut file = open_file_write!(path, sender); let descr_string = descriptor.to_string(); if let Err(e) = file.write_all(descr_string.as_bytes()) { @@ -500,7 +558,7 @@ pub fn export_descriptor(sender: Sender, path: PathBuf, descriptor: Li } pub fn export_string(sender: Sender, path: PathBuf, psbt: String) { - let mut file = open_file!(path, sender); + let mut file = open_file_write!(path, sender); if let Err(e) = file.write_all(psbt.as_bytes()) { send_error!(sender, e.into()); @@ -511,7 +569,7 @@ pub fn export_string(sender: Sender, path: PathBuf, psbt: String) { } pub fn import_psbt(sender: Sender, path: PathBuf) { - let mut file = open_file!(path, sender); + let mut file = open_file_read!(path, sender); let mut psbt_str = String::new(); if let Err(e) = file.read_to_string(&mut psbt_str) { @@ -532,7 +590,7 @@ pub fn import_psbt(sender: Sender, path: PathBuf) { } pub fn import_descriptor(sender: Sender, path: PathBuf) { - let mut file = open_file!(path, sender); + let mut file = open_file_read!(path, sender); let mut descr_str = String::new(); if let Err(e) = file.read_to_string(&mut descr_str) { @@ -552,6 +610,336 @@ pub fn import_descriptor(sender: Sender, path: PathBuf) { send_progress!(sender, Descriptor(descriptor)); } +/// Import a backup in an already existing wallet: +/// - Load backup from file +/// - check if networks matches +/// - check if descriptors matches +/// - check if labels can be imported w/o conflict, if conflic ask user to ACK +/// - check if aliases can be imported w/o conflict, if conflict ask user to ACK +/// - update receive and change indexes +/// - parse psbt from backup +/// - import PSBTs +/// - import labels if no conflict or user ACK +/// - update aliases if no conflict or user ACK +pub async fn import_backup( + sender: Sender, + path: PathBuf, + daemon: Option>, +) { + let daemon = match daemon { + Some(d) => d, + None => { + send_error!(sender, Error::DaemonMissing); + return; + } + }; + + // TODO: drop after support for restore to liana-connect + if matches!(daemon.backend(), DaemonBackend::RemoteBackend) { + send_error!( + sender, + Error::BackupImport("Restore to a Liana-connect backend is not yet supported!".into()) + ); + return; + } + + // Load backup from file + let mut file = open_file_read!(path, sender); + + let mut backup_str = String::new(); + if let Err(e) = file.read_to_string(&mut backup_str) { + send_error!(sender, e.into()); + return; + } + + let backup: Result = serde_json::from_str(&backup_str); + let backup = match backup { + Ok(psbt) => psbt, + Err(e) => { + send_error!(sender, Error::BackupImport(format!("{:?}", e))); + return; + } + }; + + // get backend info + let info = match daemon.get_info().await { + Ok(info) => info, + Err(e) => { + send_error!(sender, Error::Daemon(format!("{e:?}"))); + return; + } + }; + + // check if networks matches + let network = info.network; + if backup.network != network { + send_error!( + sender, + Error::BackupImport("The network of the backup don't match the wallet network!".into()) + ); + return; + } + + // check if descriptors matches + let descriptor = info.descriptors.main; + let account = match backup.accounts.len() { + 0 => { + send_error!( + sender, + Error::BackupImport("There is no account in the backup!".into()) + ); + return; + } + 1 => backup.accounts.first().expect("already checked"), + _ => { + send_error!( + sender, + Error::BackupImport( + "Liana is actually not supporting import of backup with several accounts!" + .into() + ) + ); + return; + } + }; + + let backup_descriptor = match LianaDescriptor::from_str(&account.descriptor) { + Ok(d) => d, + Err(_) => { + send_error!( + sender, + Error::BackupImport( + "The backup descriptor is not a valid Liana descriptor!".into() + ) + ); + return; + } + }; + + if backup_descriptor != descriptor { + send_error!( + sender, + Error::BackupImport("The backup descriptor do not match this wallet!".into()) + ); + return; + } + + // TODO: check if timestamp matches? + + // check if labels can be imported w/o conflict + let mut write_labels = true; + let backup_labels = if let Some(labels) = account.labels.clone() { + let db_labels = match daemon.get_labels_bip329(0, u32::MAX).await { + Ok(l) => l, + Err(_) => { + send_error!(sender, Error::BackupImport("Fail to dump DB labels".into())); + return; + } + }; + + let labels_map = db_labels.clone().into_map(); + let backup_labels_map = labels.clone().into_map(); + + // if there is a conflict, we ask user to ACK before overwrite + let (ack_sender, ack_receiver) = sync_channel(0); + let mut conflict = false; + for (k, l) in &backup_labels_map { + if let Some(lab) = labels_map.get(k) { + if lab != l { + send_progress!(sender, LabelsConflict(ack_sender)); + conflict = true; + break; + } + } + } + if conflict { + write_labels = match ack_receiver.recv() { + Ok(b) => b, + Err(_) => { + send_error!( + sender, + Error::BackupImport("Fail to receive labels ACK".into()) + ); + return; + } + } + } + + labels.into_vec() + } else { + Vec::new() + }; + + let datadir = match daemon.config() { + Some(c) => match &c.data_dir { + Some(dd) => dd, + None => { + send_error!( + sender, + Error::BackupImport("Fail to get Daemon config".into()) + ); + return; + } + }, + None => { + send_error!( + sender, + Error::BackupImport("Fail to get Daemon config".into()) + ); + return; + } + }; + + // check if key aliases can be imported w/o conflict + let mut write_aliases = true; + let settings = if !account.keys.is_empty() { + let settings = match Settings::from_file(datadir.to_path_buf(), network) { + Ok(s) => s, + Err(_) => { + send_error!( + sender, + Error::BackupImport("Fail to get App Settings".into()) + ); + return; + } + }; + + let settings_aliases: HashMap<_, _> = match settings.wallets.len() { + 1 => settings + .wallets + .first() + .expect("already checked") + .keys + .clone() + .into_iter() + .map(|s| (s.master_fingerprint, s)) + .collect(), + _ => { + send_error!( + sender, + Error::BackupImport("Settings.wallets.len() is not 1".into()) + ); + return; + } + }; + + let (ack_sender, ack_receiver) = sync_channel(0); + let mut conflict = false; + for (fg, key) in &account.keys { + if let Some(k) = settings_aliases.get(fg) { + let ks = k.to_backup(); + if ks != *key { + send_progress!(sender, KeyAliasesConflict(ack_sender)); + conflict = true; + break; + } + } + } + if conflict { + // wait for the user ACK/NACK + write_aliases = match ack_receiver.recv() { + Ok(a) => a, + Err(_) => { + send_error!( + sender, + Error::BackupImport("Fail to receive aliases ACK".into()) + ); + return; + } + }; + } + + Some((settings, settings_aliases)) + } else { + None + }; + + // update receive & change index + let db_receive = info.receive_index; + let i = account.receive_index.unwrap_or(0); + let receive = if db_receive < i { Some(i) } else { None }; + + let db_change = info.change_index; + let i = account.change_index.unwrap_or(0); + let change = if db_change < i { Some(i) } else { None }; + + if daemon.update_deriv_indexes(receive, change).await.is_err() { + send_error!( + sender, + Error::BackupImport("Fail to update derivation indexes".into()) + ); + return; + } + + // parse PSBTs + let mut psbts = Vec::new(); + for psbt_str in &account.psbts { + match Psbt::from_str(psbt_str) { + Ok(p) => { + psbts.push(p); + } + Err(_) => { + send_error!(sender, Error::BackupImport("Fail to parse PSBT".into())); + return; + } + } + } + + // import PSBTs + for psbt in psbts { + if daemon.update_spend_tx(&psbt).await.is_err() { + send_error!(sender, Error::BackupImport("Fail to store PSBT".into())); + return; + } + } + + // import labels if no conflict or user ACK + if write_labels { + let labels: HashMap> = backup_labels + .into_iter() + .filter_map(|l| { + if let Some((item, label)) = LabelItem::from_bip329(&l, network) { + Some((item, Some(label))) + } else { + None + } + }) + .collect(); + if daemon.update_labels(&labels).await.is_err() { + send_error!(sender, Error::BackupImport("Fail to import labels".into())); + return; + } + } + + // update aliases if no conflict or user ACK + if let (true, Some((mut settings, mut settings_aliases))) = (write_aliases, settings) { + for (k, v) in &account.keys { + if let Some(ks) = KeySetting::from_backup( + v.alias.clone().unwrap_or("".into()), + *k, + v.role.clone(), + v.key_type.clone(), + v.metadata.clone(), + ) { + settings_aliases.insert(*k, ks); + } + } + + settings.wallets.get_mut(0).expect("already checked").keys = + settings_aliases.into_values().collect(); + if settings.to_file(datadir.to_path_buf(), network).is_err() { + send_error!( + sender, + Error::BackupImport("Fail to import keys aliases".into()) + ); + return; + } + } + + send_progress!(sender, Progress(100.0)); + send_progress!(sender, Ended); +} + pub async fn export_labels( sender: Sender, daemon: Option>, @@ -590,7 +978,7 @@ pub async fn export_labels( return; } }; - let mut file = open_file!(path, sender); + let mut file = open_file_write!(path, sender); if let Err(e) = file.write_all(json.as_bytes()) { send_error!(sender, e.into()); @@ -600,11 +988,20 @@ pub async fn export_labels( send_progress!(sender, Ended); } -pub async fn get_path(filename: String) -> Option { - rfd::AsyncFileDialog::new() - .set_title("Choose a location to export...") - .set_file_name(filename) - .save_file() - .await - .map(|fh| fh.path().to_path_buf()) +pub async fn get_path(filename: String, write: bool) -> Option { + if write { + rfd::AsyncFileDialog::new() + .set_title("Choose a location to export...") + .set_file_name(filename) + .save_file() + .await + .map(|fh| fh.path().to_path_buf()) + } else { + rfd::AsyncFileDialog::new() + .set_title("Choose a file to import...") + .set_file_name(filename) + .pick_file() + .await + .map(|fh| fh.path().to_path_buf()) + } } diff --git a/liana-gui/src/installer/step/descriptor/mod.rs b/liana-gui/src/installer/step/descriptor/mod.rs index 0bf6d31e..c1dcd91e 100644 --- a/liana-gui/src/installer/step/descriptor/mod.rs +++ b/liana-gui/src/installer/step/descriptor/mod.rs @@ -345,7 +345,7 @@ impl Step for BackupDescriptor { } }; let modal = ExportModal::new(None, ImportExportType::ExportBackup(str)); - let launch = modal.launch(); + let launch = modal.launch(true); self.modal = Some(modal); return launch; } diff --git a/lianad/Cargo.toml b/lianad/Cargo.toml index 6d0d3727..d39b37a0 100644 --- a/lianad/Cargo.toml +++ b/lianad/Cargo.toml @@ -55,4 +55,6 @@ rusqlite = { version = "0.30", features = ["bundled", "unlock_notify"] } # To talk to bitcoind jsonrpc = { version = "0.17", features = ["minreq_http"], default-features = false } -bip329 = {version = "0.1.1", default-features = false } + +# import/export labels +bip329 = "0.3.0" diff --git a/lianad/src/database/mod.rs b/lianad/src/database/mod.rs index 4265364a..cd1bd22b 100644 --- a/lianad/src/database/mod.rs +++ b/lianad/src/database/mod.rs @@ -576,7 +576,7 @@ impl LabelItem { match label { bip329::Label::Transaction(tx_record) => { if let (Some(txid), Some(label)) = ( - Txid::from_str(&tx_record.ref_).ok(), + Txid::from_str(&tx_record.ref_.to_string()).ok(), tx_record.label.clone(), ) { Some((Self::Txid(txid), label)) @@ -586,7 +586,8 @@ impl LabelItem { } bip329::Label::Address(address_record) => { if let (Some(addr), Some(label)) = ( - Address::from_str(&address_record.ref_).ok(), + Address::from_str(&address_record.ref_.clone().assume_checked().to_string()) + .ok(), address_record.label.clone(), ) { if addr.is_valid_for_network(network) { @@ -600,7 +601,7 @@ impl LabelItem { } bip329::Label::Output(output_record) => { if let (Some(outpoint), Some(label)) = ( - OutPoint::from_str(&output_record.ref_).ok(), + OutPoint::from_str(&output_record.ref_.to_string()).ok(), output_record.label.clone(), ) { Some((Self::OutPoint(outpoint), label)) diff --git a/lianad/src/database/sqlite/schema.rs b/lianad/src/database/sqlite/schema.rs index 7b71990b..0eafed73 100644 --- a/lianad/src/database/sqlite/schema.rs +++ b/lianad/src/database/sqlite/schema.rs @@ -3,7 +3,14 @@ use liana::descriptors::LianaDescriptor; use std::{convert::TryFrom, str::FromStr}; -use miniscript::bitcoin::{self, address, bip32, consensus::encode, psbt::Psbt}; +use miniscript::bitcoin::{ + self, + address::{self, NetworkUnchecked}, + bip32, + consensus::encode, + psbt::Psbt, + Address, OutPoint, +}; // Due to limitations of Sqlite's ALTER TABLE command and in order not to recreate // tables during migration: @@ -376,14 +383,19 @@ impl From for Label { Some(value.value) }; match value.item_kind { - DbLabelledKind::Address => Label::Address(bip329::AddressRecord { ref_, label }), - DbLabelledKind::OutPoint => Label::Output(bip329::OutputRecord { - ref_, + DbLabelledKind::Address => Label::Address(bip329::AddressRecord { + ref_: Address::::from_str(&ref_) + .expect("db contains valid adresses"), label, - spendable: None, + }), + DbLabelledKind::OutPoint => Label::Output(bip329::OutputRecord { + ref_: OutPoint::from_str(&ref_).expect(" db contais valid outpoints"), + label, + spendable: true, }), DbLabelledKind::Txid => Label::Transaction(bip329::TransactionRecord { - ref_, + ref_: bitcoin::consensus::encode::deserialize_hex(&ref_) + .expect("db contains valid txid"), label, // FIXME: "Optional key origin information referencing the wallet associated with the label" origin: None,