From b3bc9435ceccf40cca0f8be1c107e9bbf9d3bb9e Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Fri, 25 Aug 2023 20:12:48 +0100 Subject: [PATCH 1/4] gui: add dependencies to download and install bitcoind --- gui/Cargo.lock | 455 +++++++++++++++++++++++++++++++++++++++++++++---- gui/Cargo.toml | 11 +- 2 files changed, 432 insertions(+), 34 deletions(-) diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 1658eef4c..175d3da78 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -159,7 +159,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -316,7 +316,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -331,6 +331,27 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "calloop" version = "0.10.5" @@ -690,7 +711,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -707,7 +728,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -890,6 +911,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "error-code" version = "2.3.1" @@ -956,6 +986,18 @@ dependencies = [ "log", ] +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", +] + [[package]] name = "find-crate" version = "0.6.3" @@ -1000,7 +1042,7 @@ dependencies = [ "futures-sink", "nanorand", "pin-project", - "spin", + "spin 0.9.8", ] [[package]] @@ -1057,7 +1099,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -1072,6 +1114,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "freetype-rs" version = "0.26.0" @@ -1151,7 +1202,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -1450,6 +1501,25 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.2.1" @@ -1552,6 +1622,78 @@ dependencies = [ "winapi", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "iana-time-zone" version = "0.1.56" @@ -1748,6 +1890,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "image" version = "0.24.6" @@ -1795,6 +1947,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + [[package]] name = "itoa" version = "1.0.6" @@ -1980,8 +2138,10 @@ dependencies = [ "async-hwi", "backtrace", "base64 0.13.1", + "bitcoin_hashes 0.12.0", "chrono", "dirs 3.0.2", + "flate2", "hex", "iced", "iced_lazy", @@ -1990,14 +2150,16 @@ dependencies = [ "liana", "liana_ui", "log", + "reqwest", "rust-ini", "serde", "serde_json", + "tar", "tokio", "toml", "tracing", "tracing-subscriber", - "which", + "zip", ] [[package]] @@ -2233,6 +2395,12 @@ dependencies = [ "objc", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2704,7 +2872,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] @@ -2717,7 +2885,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -2907,9 +3075,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" dependencies = [ "proc-macro2", ] @@ -3021,6 +3189,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -3028,7 +3205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] @@ -3055,6 +3232,45 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" +[[package]] +name = "reqwest" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + [[package]] name = "resvg" version = "0.29.0" @@ -3083,6 +3299,21 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rosvgtree" version = "0.1.0" @@ -3142,6 +3373,37 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustls" +version = "0.21.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustybuzz" version = "0.7.0" @@ -3191,6 +3453,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sctk-adwaita" version = "0.4.3" @@ -3225,22 +3497,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.159" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -3254,6 +3526,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serialport" version = "4.2.0" @@ -3426,6 +3710,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -3527,15 +3817,25 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -3562,7 +3862,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", ] [[package]] @@ -3697,7 +3997,17 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.29", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", ] [[package]] @@ -3713,6 +4023,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.11" @@ -3739,6 +4063,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -3797,6 +4127,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "ttf-parser" version = "0.18.1" @@ -3883,6 +4219,23 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "usvg" version = "0.29.0" @@ -3940,6 +4293,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -4126,6 +4488,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + [[package]] name = "weezl" version = "0.1.7" @@ -4244,17 +4612,6 @@ dependencies = [ "wgpu", ] -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "widestring" version = "0.5.1" @@ -4355,6 +4712,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -4541,6 +4907,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wio" version = "0.2.2" @@ -4600,6 +4976,19 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "crossbeam-utils", + "flate2", +] + [[package]] name = "zune-inflate" version = "0.2.53" diff --git a/gui/Cargo.toml b/gui/Cargo.toml index b1a4ea3b2..1668aff09 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -42,8 +42,17 @@ toml = "0.5" chrono = "0.4" +# Used for managing internal bitcoind +bitcoin_hashes = "0.12" +reqwest = { version = "0.11", default-features=false, features = ["rustls-tls"] } rust-ini = "0.19.0" -which = "4.4.0" + +[target.'cfg(windows)'.dependencies] +zip = { version = "0.6", default-features=false, features = ["bzip2", "deflate"] } + +[target.'cfg(unix)'.dependencies] +tar = { version = "0.4", default-features=false } +flate2 = { version = "1.0", default-features=false } [dev-dependencies] tokio = {version = "1.9.0", features = ["rt", "macros"]} From ae23fbc81ddc903183ce971afa5597f95e26b82a Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:41:10 +0100 Subject: [PATCH 2/4] gui: add download module This is based on https://github.com/iced-rs/iced/blob/master/examples/download_progress/src/download.rs. --- gui/src/download.rs | 135 ++++++++++++++++++++++++++++++++++++++++++++ gui/src/lib.rs | 1 + 2 files changed, 136 insertions(+) create mode 100644 gui/src/download.rs diff --git a/gui/src/download.rs b/gui/src/download.rs new file mode 100644 index 000000000..d2182a824 --- /dev/null +++ b/gui/src/download.rs @@ -0,0 +1,135 @@ +// This is based on https://github.com/iced-rs/iced/blob/master/examples/download_progress/src/download.rs +// with some modifications to store the downloaded bytes in `Progress::Finished` and `State::Downloading` +// and to keep track of any download errors. +use iced::subscription; + +use std::hash::Hash; + +// Just a little utility function +pub fn file( + id: I, + url: T, +) -> iced::Subscription<(I, Progress)> { + subscription::unfold(id, State::Ready(url.to_string()), move |state| { + download(id, state) + }) +} + +#[derive(Debug, Hash, Clone)] +pub struct Download { + id: I, + url: String, +} + +/// Possible errors with download. +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum DownloadError { + UnknownContentLength, + RequestError(String), +} + +impl std::fmt::Display for DownloadError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::UnknownContentLength => { + write!(f, "Response has unknown content length.") + } + Self::RequestError(e) => { + write!(f, "Request error: '{}'.", e) + } + } + } +} + +async fn download(id: I, state: State) -> ((I, Progress), State) { + match state { + State::Ready(url) => { + let response = reqwest::get(&url).await; + + match response { + Ok(response) => { + if let Some(total) = response.content_length() { + ( + (id, Progress::Started), + State::Downloading { + response, + total, + downloaded: 0, + bytes: Vec::new(), + }, + ) + } else { + ( + (id, Progress::Errored(DownloadError::UnknownContentLength)), + State::Finished, + ) + } + } + Err(e) => ( + ( + id, + Progress::Errored(DownloadError::RequestError(e.to_string())), + ), + State::Finished, + ), + } + } + State::Downloading { + mut response, + total, + downloaded, + mut bytes, + } => match response.chunk().await { + Ok(Some(chunk)) => { + let downloaded = downloaded + chunk.len() as u64; + + let percentage = (downloaded as f32 / total as f32) * 100.0; + + bytes.append(&mut chunk.to_vec()); + + ( + (id, Progress::Advanced(percentage)), + State::Downloading { + response, + total, + downloaded, + bytes, + }, + ) + } + Ok(None) => ((id, Progress::Finished(bytes)), State::Finished), + Err(e) => ( + ( + id, + Progress::Errored(DownloadError::RequestError(e.to_string())), + ), + State::Finished, + ), + }, + State::Finished => { + // We do not let the stream die, as it would start a + // new download repeatedly if the user is not careful + // in case of errors. + iced::futures::future::pending().await + } + } +} + +#[derive(Debug, Clone)] +pub enum Progress { + Started, + Advanced(f32), + Finished(Vec), + Errored(DownloadError), +} + +pub enum State { + Ready(String), + Downloading { + response: reqwest::Response, + total: u64, + downloaded: u64, + bytes: Vec, + }, + Finished, +} diff --git a/gui/src/lib.rs b/gui/src/lib.rs index a01f20f29..0a6fd3c48 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -1,6 +1,7 @@ pub mod app; pub mod bitcoind; pub mod daemon; +pub mod download; pub mod hw; pub mod installer; pub mod launcher; From d507d5760ac051cf264c95b4fdef9ced27dde9cd Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Fri, 25 Aug 2023 18:50:19 +0100 Subject: [PATCH 3/4] gui: add subscription to Step trait --- gui/src/installer/mod.rs | 5 ++++- gui/src/installer/step/mod.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 0d31ccc8e..c8ada36d3 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -71,7 +71,10 @@ impl Installer { } pub fn subscription(&self) -> Subscription { - Subscription::none() + self.steps + .get(self.current) + .expect("There is always a step") + .subscription() } pub fn stop(&mut self) { diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 4b0f359d3..7110e877e 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -14,7 +14,7 @@ pub use mnemonic::{BackupMnemonic, RecoverMnemonic}; use std::path::PathBuf; -use iced::Command; +use iced::{Command, Subscription}; use liana::miniscript::bitcoin::bip32::Fingerprint; use liana_ui::widget::*; @@ -25,6 +25,9 @@ pub trait Step { fn update(&mut self, _message: Message) -> Command { Command::none() } + fn subscription(&self) -> Subscription { + Subscription::none() + } fn view(&self, progress: (usize, usize)) -> Element; fn load_context(&mut self, _ctx: &Context) {} fn load(&self) -> Command { From bf67f94f56d6ae28973c27f0a8999ecd1eab7398 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Sat, 26 Aug 2023 09:12:02 +0100 Subject: [PATCH 4/4] installer: download and install bitcoind --- gui/src/bitcoind.rs | 41 +++- gui/src/installer/message.rs | 5 +- gui/src/installer/prompt.rs | 1 - gui/src/installer/step/bitcoind.rs | 368 +++++++++++++++++++++++++++-- gui/src/installer/step/mod.rs | 3 +- gui/src/installer/view.rs | 145 +++++++++--- 6 files changed, 491 insertions(+), 72 deletions(-) diff --git a/gui/src/bitcoind.rs b/gui/src/bitcoind.rs index 2f158e1b7..aba7cfb86 100644 --- a/gui/src/bitcoind.rs +++ b/gui/src/bitcoind.rs @@ -4,10 +4,17 @@ use tracing::{info, warn}; use crate::app::config::InternalBitcoindExeConfig; +#[cfg(target_os = "windows")] +use std::os::windows::process::CommandExt; + +#[cfg(target_os = "windows")] +const CREATE_NO_WINDOW: u32 = 0x08000000; + /// Possible errors when starting bitcoind. #[derive(PartialEq, Eq, Debug, Clone)] pub enum StartInternalBitcoindError { CommandError(String), + CouldNotCanonicalizeExePath(String), CouldNotCanonicalizeDataDir(String), CouldNotCanonicalizeCookiePath(String), CookieFileNotFound(String), @@ -20,6 +27,9 @@ impl std::fmt::Display for StartInternalBitcoindError { Self::CommandError(e) => { write!(f, "Command to start bitcoind returned an error: {}", e) } + Self::CouldNotCanonicalizeExePath(e) => { + write!(f, "Failed to canonicalize executable path: {}", e) + } Self::CouldNotCanonicalizeDataDir(e) => { write!(f, "Failed to canonicalize datadir: {}", e) } @@ -43,22 +53,31 @@ pub fn start_internal_bitcoind( network: &bitcoin::Network, exe_config: InternalBitcoindExeConfig, ) -> Result { + let datadir_path_str = exe_config + .data_dir + .canonicalize() + .map_err(|e| StartInternalBitcoindError::CouldNotCanonicalizeDataDir(e.to_string()))? + .to_str() + .ok_or_else(|| { + StartInternalBitcoindError::CouldNotCanonicalizeDataDir( + "Couldn't convert path to str.".to_string(), + ) + })? + .to_string(); + #[cfg(target_os = "windows")] + // See https://github.com/rust-lang/rust/issues/42869. + let datadir_path_str = datadir_path_str.replace("\\\\?\\", "").replace("\\\\?", ""); let args = vec![ format!("-chain={}", network.to_core_arg()), - format!( - "-datadir={}", - exe_config - .data_dir - .canonicalize() - .map_err(|e| StartInternalBitcoindError::CouldNotCanonicalizeDataDir( - e.to_string() - ))? - .to_string_lossy() - ), + format!("-datadir={}", datadir_path_str), ]; - std::process::Command::new(exe_config.exe_path) + let mut command = std::process::Command::new(exe_config.exe_path); + #[cfg(target_os = "windows")] + let command = command.creation_flags(CREATE_NO_WINDOW); + command .args(&args) .stdout(std::process::Stdio::null()) // We still get bitcoind's logs in debug.log. + .stderr(std::process::Stdio::piped()) .spawn() .map_err(|e| StartInternalBitcoindError::CommandError(e.to_string())) } diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index f1c0717c5..9aaf94776 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -5,7 +5,7 @@ use liana::miniscript::{ use std::path::PathBuf; use super::Error; -use crate::hw::HardwareWallet; +use crate::{download::Progress, hw::HardwareWallet}; use async_hwi::DeviceKind; #[derive(Debug, Clone)] @@ -55,6 +55,9 @@ pub enum InternalBitcoindMsg { Previous, Reload, DefineConfig, + Download, + DownloadProgressed(Progress), + Install, Start, } diff --git a/gui/src/installer/prompt.rs b/gui/src/installer/prompt.rs index 2b47a3399..6072b2640 100644 --- a/gui/src/installer/prompt.rs +++ b/gui/src/installer/prompt.rs @@ -9,4 +9,3 @@ pub const DEFINE_DESCRIPTOR_FINGERPRINT_TOOLTIP: &str = pub const REGISTER_DESCRIPTOR_HELP: &str = "To be used with the wallet, a signing device needs the descriptor. If the descriptor contains one or more keys imported from an external signing device, the descriptor must be registered on it. Registration confirms that the device is able to handle the policy. Registration on a device is not a substitute for backing up the descriptor."; pub const MNEMONIC_HELP: &str = "A hot key generated on this computer was used for creating this wallet. It needs to be backed up. \n Keep it in a safe place. Never share it with anyone."; pub const RECOVER_MNEMONIC_HELP: &str = "If you were using a hot key (a key stored on the computer) in your wallet, you will need to recover it from mnemonics to be able to sign transactions again. Otherwise you can directly go the next step."; -pub const SELECT_BITCOIND_TYPE: &str = "Liana requires a Bitcoin node to be running. You can either use your own node that you manage yourself or you can let Liana install and manage a pruned Bitcoin node for use while running Liana."; diff --git a/gui/src/installer/step/bitcoind.rs b/gui/src/installer/step/bitcoind.rs index 91358cd6c..1320be7ed 100644 --- a/gui/src/installer/step/bitcoind.rs +++ b/gui/src/installer/step/bitcoind.rs @@ -1,12 +1,18 @@ use std::collections::BTreeMap; +#[cfg(target_os = "windows")] +use std::io::{self, Cursor}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}; use std::path::{Path, PathBuf}; use std::str::FromStr; -use iced::Command; +use bitcoin_hashes::{sha256, Hash}; +#[cfg(any(target_os = "macos", target_os = "linux"))] +use flate2::read::GzDecoder; +use iced::{Command, Subscription}; use liana::{config::BitcoindConfig, miniscript::bitcoin::Network}; - -use tracing::info; +#[cfg(any(target_os = "macos", target_os = "linux"))] +use tar::Archive; +use tracing::{error, info}; use jsonrpc::{client::Client, simple_http::SimpleHttpTransport}; @@ -14,6 +20,7 @@ use liana_ui::{component::form, widget::*}; use crate::{ bitcoind::{start_internal_bitcoind, stop_internal_bitcoind, StartInternalBitcoindError}, + download, installer::{ context::Context, internal_bitcoind_datadir, @@ -24,6 +31,108 @@ use crate::{ utils::poll_for_file, }; +// The approach for tracking download progress is taken from +// https://github.com/iced-rs/iced/blob/master/examples/download_progress/src/main.rs. +#[derive(Debug)] +struct Download { + id: usize, + state: DownloadState, +} + +#[derive(Debug)] +pub enum DownloadState { + Idle, + Downloading { progress: f32 }, + Finished(Vec), + Errored(download::DownloadError), +} + +impl Download { + pub fn new(id: usize) -> Self { + Download { + id, + state: DownloadState::Idle, + } + } + + pub fn start(&mut self) { + match self.state { + DownloadState::Idle { .. } + | DownloadState::Finished { .. } + | DownloadState::Errored { .. } => { + self.state = DownloadState::Downloading { progress: 0.0 }; + } + _ => {} + } + } + + pub fn progress(&mut self, new_progress: download::Progress) { + if let DownloadState::Downloading { progress } = &mut self.state { + match new_progress { + download::Progress::Started => { + *progress = 0.0; + } + download::Progress::Advanced(percentage) => { + *progress = percentage; + } + download::Progress::Finished(bytes) => { + self.state = DownloadState::Finished(bytes); + } + download::Progress::Errored(e) => { + self.state = DownloadState::Errored(e); + } + } + } + } + + pub fn subscription(&self) -> Subscription { + match self.state { + DownloadState::Downloading { .. } => { + download::file(self.id, download_url()).map(|(_, progress)| { + Message::InternalBitcoind(message::InternalBitcoindMsg::DownloadProgressed( + progress, + )) + }) + } + _ => Subscription::none(), + } + } +} + +const VERSION: &str = "25.0"; + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +const SHA256SUM: &str = "5708fc639cdfc27347cccfd50db9b73b53647b36fb5f3a4a93537cbe8828c27f"; + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +const SHA256SUM: &str = "33930d432593e49d58a9bff4c30078823e9af5d98594d2935862788ce8a20aec"; + +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +const SHA256SUM: &str = "7154b35ecc8247589070ae739b7c73c4dee4794bea49eb18dc66faed65b819e7"; + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +fn download_filename() -> String { + format!("bitcoin-{}-x86_64-apple-darwin.tar.gz", &VERSION) +} + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +fn download_filename() -> String { + format!("bitcoin-{}-x86_64-linux-gnu.tar.gz", &VERSION) +} + +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +fn download_filename() -> String { + format!("bitcoin-{}-win64.zip", &VERSION) +} + +fn download_url() -> String { + format!( + "https://bitcoincore.org/bin/bitcoin-core-{}/{}", + &VERSION, + download_filename() + ) +} + pub struct DefineBitcoind { cookie_path: form::Value, address: form::Value, @@ -31,6 +140,7 @@ pub struct DefineBitcoind { } pub struct InternalBitcoindStep { + liana_datadir: PathBuf, bitcoind_datadir: PathBuf, network: Network, started: Option>, @@ -39,6 +149,8 @@ pub struct InternalBitcoindStep { exe_config: Option, internal_bitcoind_config: Option, error: Option, + exe_download: Option, + install_state: Option, } pub struct SelectBitcoindTypeStep { @@ -187,6 +299,117 @@ impl InternalBitcoindConfig { } } +#[derive(Debug)] +pub enum InstallState { + InProgress, + Finished, + Errored(InstallBitcoindError), +} + +/// Possible errors when installing bitcoind. +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum InstallBitcoindError { + HashMismatch, + UnpackingError(String), +} + +impl std::fmt::Display for InstallBitcoindError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::HashMismatch => { + write!(f, "Hashes do not match.") + } + Self::UnpackingError(e) => { + write!(f, "Error unpacking: '{}'.", e) + } + } + } +} + +// The functions below for unpacking the bitcoin download and verifying its hash are based on +// https://github.com/RCasatta/bitcoind/blob/bada7ebb7197b89fd67e607f815ce1e43e76da7f/build.rs#L73. + +/// Unpack the downloaded bytes in the specified directory. +fn unpack_bitcoind(install_dir: &PathBuf, bytes: &[u8]) -> Result<(), InstallBitcoindError> { + #[cfg(any(target_os = "macos", target_os = "linux"))] + { + let d = GzDecoder::new(bytes); + + let mut archive = Archive::new(d); + for mut entry in archive + .entries() + .map_err(|e| InstallBitcoindError::UnpackingError(e.to_string()))? + .flatten() + { + if let Ok(file) = entry.path() { + if file.ends_with("bitcoind") { + if let Err(e) = entry.unpack_in(install_dir) { + return Err(InstallBitcoindError::UnpackingError(e.to_string())); + } + } + } + } + } + #[cfg(target_os = "windows")] + { + let cursor = Cursor::new(bytes); + let mut archive = zip::ZipArchive::new(cursor) + .map_err(|e| InstallBitcoindError::UnpackingError(e.to_string()))?; + for i in 0..zip::ZipArchive::len(&archive) { + let mut file = archive + .by_index(i) + .map_err(|e| InstallBitcoindError::UnpackingError(e.to_string()))?; + let outpath = match file.enclosed_name() { + Some(path) => path.to_owned(), + None => continue, + }; + if outpath.file_name().map(|s| s.to_str()) == Some(Some("bitcoind.exe")) { + let mut exe_path = PathBuf::from(install_dir); + for d in outpath.iter() { + exe_path.push(d); + } + let parent = exe_path.parent().expect("bitcoind.exe should have parent."); + std::fs::create_dir_all(parent) + .map_err(|e| InstallBitcoindError::UnpackingError(e.to_string()))?; + let mut outfile = std::fs::File::create(&exe_path) + .map_err(|e| InstallBitcoindError::UnpackingError(e.to_string()))?; + io::copy(&mut file, &mut outfile) + .map_err(|e| InstallBitcoindError::UnpackingError(e.to_string()))?; + break; + } + } + } + Ok(()) +} + +/// Verify the download hash against the expected value. +fn verify_hash(bytes: &[u8]) -> bool { + let bytes_hash = sha256::Hash::hash(bytes); + info!("Download hash: '{}'.", bytes_hash); + let expected_hash = sha256::Hash::from_str(SHA256SUM).expect("This cannot fail."); + expected_hash == bytes_hash +} + +/// Install bitcoind by verifying the download hash and unpacking in the specified directory. +fn install_bitcoind(install_dir: &PathBuf, bytes: &[u8]) -> Result<(), InstallBitcoindError> { + if !verify_hash(bytes) { + return Err(InstallBitcoindError::HashMismatch); + }; + unpack_bitcoind(install_dir, bytes) +} + +/// Internal bitcoind executable path. +fn internal_bitcoind_exe_path(liana_datadir: &PathBuf) -> PathBuf { + PathBuf::from(liana_datadir) + .join(format!("bitcoin-{}", &VERSION)) + .join("bin") + .join(if cfg!(target_os = "windows") { + "bitcoind.exe" + } else { + "bitcoind" + }) +} + /// Path of the `bitcoin.conf` file used by internal bitcoind. fn internal_bitcoind_config_path(bitcoind_datadir: &PathBuf) -> PathBuf { let mut config_path = PathBuf::from(bitcoind_datadir); @@ -262,11 +485,6 @@ fn bitcoind_default_address(network: &Network) -> String { } } -/// Looks for bitcoind executable path and returns `None` if not found. -fn bitcoind_exe_path() -> Option { - which::which("bitcoind").ok() -} - /// Get available port that is valid for use by internal bitcoind. // Modified from https://github.com/RCasatta/bitcoind/blob/f047740d7d0af935ff7360cf77429c5f294cfd59/src/lib.rs#L435 pub fn get_available_port() -> Result { @@ -473,6 +691,7 @@ impl From for Box { impl InternalBitcoindStep { pub fn new(liana_datadir: &PathBuf) -> Self { Self { + liana_datadir: liana_datadir.clone(), bitcoind_datadir: internal_bitcoind_datadir(liana_datadir), network: Network::Bitcoin, started: None, @@ -481,6 +700,8 @@ impl InternalBitcoindStep { exe_config: None, internal_bitcoind_config: None, error: None, + exe_download: None, + install_state: None, } } } @@ -488,10 +709,10 @@ impl InternalBitcoindStep { impl Step for InternalBitcoindStep { fn load_context(&mut self, ctx: &Context) { if self.exe_path.is_none() { - self.exe_path = if let Some(exe_config) = ctx.internal_bitcoind_exe_config.clone() { - Some(exe_config.exe_path) - } else { - bitcoind_exe_path() + if internal_bitcoind_exe_path(&ctx.data_dir).exists() { + self.exe_path = Some(internal_bitcoind_exe_path(&ctx.data_dir)) + } else if self.exe_download.is_none() { + self.exe_download = Some(Download::new(0)); }; } self.network = ctx.bitcoin_config.network; @@ -509,6 +730,7 @@ impl Step for InternalBitcoindStep { if self.internal_bitcoind_config.is_some() { if let Some(bitcoind_config) = &self.bitcoind_config { stop_internal_bitcoind(bitcoind_config); + self.started = None; } } return Command::perform(async {}, |_| Message::Previous); @@ -571,11 +793,70 @@ impl Step for InternalBitcoindStep { Message::InternalBitcoind(message::InternalBitcoindMsg::Reload) }); } + message::InternalBitcoindMsg::Download => { + if let Some(download) = &mut self.exe_download { + if let DownloadState::Idle = download.state { + info!("Downloading bitcoind version {}...", &VERSION); + download.start(); + } + } + } + message::InternalBitcoindMsg::DownloadProgressed(progress) => { + if let Some(download) = self.exe_download.as_mut() { + download.progress(progress); + if let DownloadState::Finished(_) = &download.state { + info!("Download of bitcoind complete."); + return Command::perform(async {}, |_| { + Message::InternalBitcoind(message::InternalBitcoindMsg::Install) + }); + } + } + } + message::InternalBitcoindMsg::Install => { + if let Some(download) = &self.exe_download { + if let DownloadState::Finished(bytes) = &download.state { + info!("Installing bitcoind..."); + self.install_state = Some(InstallState::InProgress); + match install_bitcoind(&self.liana_datadir, bytes) { + Ok(_) => { + info!("Installation of bitcoind complete."); + self.install_state = Some(InstallState::Finished); + self.exe_path = + Some(internal_bitcoind_exe_path(&self.liana_datadir)); + return Command::perform(async {}, |_| { + Message::InternalBitcoind( + message::InternalBitcoindMsg::Start, + ) + }); + } + Err(e) => { + info!("Installation of bitcoind failed."); + self.install_state = Some(InstallState::Errored(e.clone())); + self.error = Some(e.to_string()); + return Command::none(); + } + }; + } + } + } message::InternalBitcoindMsg::Start => { - if let Some(path) = &self.exe_path { - let datadir = match self.bitcoind_datadir.canonicalize() { - Ok(datadir) => datadir, - Err(e) => { + if let Some(exe_path) = &self.exe_path { + let exe_config = match ( + exe_path.canonicalize(), + self.bitcoind_datadir.canonicalize(), + ) { + (Ok(exe_path), Ok(data_dir)) => { + InternalBitcoindExeConfig { exe_path, data_dir } + } + (Err(e), Ok(_)) | (Err(e), Err(_)) => { + self.started = Some(Err( + StartInternalBitcoindError::CouldNotCanonicalizeExePath( + e.to_string(), + ), + )); + return Command::none(); + } + (Ok(_), Err(e)) => { self.started = Some(Err( StartInternalBitcoindError::CouldNotCanonicalizeDataDir( e.to_string(), @@ -584,19 +865,30 @@ impl Step for InternalBitcoindStep { return Command::none(); } }; - let exe_config = InternalBitcoindExeConfig { - exe_path: path.to_path_buf(), - data_dir: datadir, - }; - if let Err(e) = start_internal_bitcoind(&self.network, exe_config.clone()) { - self.started = - Some(Err(StartInternalBitcoindError::CommandError(e.to_string()))); - return Command::none(); - } + let handle = + match start_internal_bitcoind(&self.network, exe_config.clone()) { + Err(e) => { + self.started = Some(Err( + StartInternalBitcoindError::CommandError(e.to_string()), + )); + return Command::none(); + } + Ok(h) => h, + }; // Need to wait for cookie file to appear. let cookie_path = internal_bitcoind_cookie_path(&self.bitcoind_datadir, &self.network); if !poll_for_file(&cookie_path, 200, 15) { + error!("Cookie file still not present after 3 seconds. Waiting for the bitcoind process to finish."); + match handle.wait_with_output() { + Err(e) => { + error!("Error while waiting for bitcoind to finish: {}", e) + } + Ok(o) => { + error!("Exit status: {}", o.status); + error!("stderr: {}", String::from_utf8_lossy(&o.stderr)); + } + } self.started = Some(Err(StartInternalBitcoindError::CookieFileNotFound( cookie_path.to_string_lossy().into_owned(), @@ -649,12 +941,26 @@ impl Step for InternalBitcoindStep { Command::none() } + fn subscription(&self) -> Subscription { + if let Some(download) = self.exe_download.as_ref() { + return download.subscription(); + } + Subscription::none() + } + fn load(&self) -> Command { if self.internal_bitcoind_config.is_none() { return Command::perform(async {}, |_| { Message::InternalBitcoind(message::InternalBitcoindMsg::DefineConfig) }); } + if let Some(download) = &self.exe_download { + if let DownloadState::Idle = download.state { + return Command::perform(async {}, |_| { + Message::InternalBitcoind(message::InternalBitcoindMsg::Download) + }); + } + } if self.started.is_none() { return Command::perform(async {}, |_| { Message::InternalBitcoind(message::InternalBitcoindMsg::Start) @@ -681,6 +987,8 @@ impl Step for InternalBitcoindStep { self.exe_path.as_ref(), self.started.as_ref(), self.error.as_ref(), + self.exe_download.as_ref().map(|d| &d.state), + self.install_state.as_ref(), ) } @@ -700,7 +1008,9 @@ impl Step for InternalBitcoindStep { #[cfg(test)] mod tests { - use crate::installer::step::bitcoind::{InternalBitcoindConfig, InternalBitcoindNetworkConfig}; + use crate::installer::step::bitcoind::{ + verify_hash, InternalBitcoindConfig, InternalBitcoindNetworkConfig, + }; use ini::Ini; use liana::miniscript::bitcoin::Network; @@ -767,4 +1077,10 @@ mod tests { } } } + + #[test] + fn hash() { + let bytes = "this is not bitcoin".as_bytes().to_vec(); + assert!(!verify_hash(&bytes)); + } } diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 7110e877e..8fbaadaa6 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -3,7 +3,8 @@ mod descriptor; mod mnemonic; pub use bitcoind::{ - DefineBitcoind, InternalBitcoindConfig, InternalBitcoindStep, SelectBitcoindTypeStep, + DefineBitcoind, DownloadState, InstallState, InternalBitcoindConfig, InternalBitcoindStep, + SelectBitcoindTypeStep, }; pub use descriptor::{ diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index af76d917c..ce7a0e4a5 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -1,7 +1,7 @@ use iced::widget::{ checkbox, container, pick_list, scrollable, scrollable::Properties, slider, Space, TextInput, }; -use iced::{alignment, Alignment, Length}; +use iced::{alignment, widget::progress_bar, Alignment, Length}; use async_hwi::DeviceKind; use std::path::PathBuf; @@ -26,7 +26,9 @@ use crate::{ installer::{ context::Context, message::{self, Message}, - prompt, Error, + prompt, + step::{DownloadState, InstallState}, + Error, }, }; @@ -907,8 +909,56 @@ pub fn define_bitcoin<'a>( pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message> { layout( progress, - "Choose Bitcoin installation type", - Column::new().push(text(prompt::SELECT_BITCOIND_TYPE)).push( + "Bitcoin node management", + Column::new().push( + Row::new() + .align_items(Alignment::Start) + .spacing(20) + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .push(text("Manage your own Bitcoin node").bold()) + ) + .padding(20), + ) + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .push(text("Have Liana manage and run a dedicated Bitcoin node").bold()) + ) + .padding(20), + ), + ) + .push( + Row::new() + .align_items(Alignment::Start) + .spacing(20) + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .align_items(Alignment::Start) + .push(text("Liana will connect to your existing instance of Bitcoin Core. You will have to make sure Bitcoin Core is running when you use Liana.\n\n(Use this if you already have a full node on your machine, and don't need a new instance)")) + ) + .padding(20), + ) + .push( + Container::new( + Column::new() + .spacing(20) + .width(Length::Fixed(300.0)) + .align_items(Alignment::Start) + .push(text("Liana will run its own instance of Bitcoin Core. This will use a pruned node, and perform the synchronization in the Liana folder.\n\nIf you select this option, Bitcoin Core will be downloaded, installed and started on the next step.\n\n(Use this if you don't want to deal with Bitcoin Core yourself, or need a new, dedicated instance for Liana)")) + ) + .padding(20), + ), + ) + .push( Row::new() .align_items(Alignment::End) .spacing(20) @@ -916,15 +966,15 @@ pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message Container::new( Column::new() .spacing(20) + .width(Length::Fixed(300.0)) .align_items(Alignment::Center) .push( - button::primary(None, "I want to manage my own node") - .width(Length::Fixed(300.0)) + button::primary(None, "Select") + .width(Length::Fixed(300.0)) .on_press(Message::SelectBitcoindType( message::SelectBitcoindTypeMsg::UseExternal(true), )), ) - .align_items(Alignment::Center), ) .padding(20), ) @@ -932,15 +982,15 @@ pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message Container::new( Column::new() .spacing(20) + .width(Length::Fixed(300.0)) .align_items(Alignment::Center) .push( - button::primary(None, "Let Liana manage my node") + button::primary(None, "Select") .width(Length::Fixed(300.0)) .on_press(Message::SelectBitcoindType( message::SelectBitcoindTypeMsg::UseExternal(false), )), ) - .align_items(Alignment::Center), ) .padding(20), ), @@ -955,9 +1005,9 @@ pub fn start_internal_bitcoind<'a>( exe_path: Option<&PathBuf>, started: Option<&Result<(), StartInternalBitcoindError>>, error: Option<&'a String>, + download_state: Option<&DownloadState>, + install_state: Option<&InstallState>, ) -> Element<'a, Message> { - let start_button = button::primary(None, "Start bitcoind").width(Length::Fixed(200.0)); - let mut next_button = button::primary(None, "Next").width(Length::Fixed(200.0)); if let Some(Ok(_)) = started { next_button = next_button.on_press(Message::Next); @@ -966,23 +1016,56 @@ pub fn start_internal_bitcoind<'a>( progress, "Start Bitcoin full node", Column::new() - .push(if exe_path.is_some() { - Container::new( - Row::new() + .push_maybe(download_state.map(|s| { + match s { + DownloadState::Finished(_) => Row::new() .spacing(10) .align_items(Alignment::Center) .push(icon::circle_check_icon().style(color::GREEN)) - .push(text("bitcoind already installed").style(color::GREEN)), - ) - } else { - Container::new( - Row::new() + .push(text("Download complete").style(color::GREEN)), + DownloadState::Downloading { progress } => Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(text(format!("Downloading... {progress:.2}%"))), + DownloadState::Errored(e) => Row::new() .spacing(10) .align_items(Alignment::Center) .push(icon::circle_cross_icon().style(color::RED)) - .push(text("Cannot find bitcoind").style(color::RED)), - ) - }) + .push(text(format!("Download failed: '{}'.", e)).style(color::RED)), + _ => Row::new().spacing(10).align_items(Alignment::Center), + } + })) + .push(Container::new(if let Some(state) = install_state { + match state { + InstallState::InProgress => Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push("Installing bitcoind..."), + InstallState::Finished => Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_check_icon().style(color::GREEN)) + .push(text("Installation complete").style(color::GREEN)), + InstallState::Errored(e) => Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_cross_icon().style(color::RED)) + .push(text(format!("Installation failed: '{}'.", e)).style(color::RED)), + } + } else if exe_path.is_some() { + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_check_icon().style(color::GREEN)) + .push(text("Liana-managed bitcoind already installed").style(color::GREEN)) + } else if let Some(DownloadState::Downloading { progress }) = download_state { + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(progress_bar(0.0..=100.0, *progress)) + } else { + Row::new().spacing(10).align_items(Alignment::Center) + })) .push_maybe(if started.is_some() { started.map(|res| { if res.is_ok() { @@ -991,7 +1074,7 @@ pub fn start_internal_bitcoind<'a>( .spacing(10) .align_items(Alignment::Center) .push(icon::circle_check_icon().style(color::GREEN)) - .push(text("bitcoind started").style(color::GREEN)), + .push(text("Started").style(color::GREEN)), ) } else { Container::new( @@ -1005,6 +1088,13 @@ pub fn start_internal_bitcoind<'a>( ) } }) + } else if let Some(InstallState::Finished) = install_state { + Some(Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(text("Starting...")), + )) } else { Some(Container::new(Space::with_height(Length::Fixed(25.0)))) }) @@ -1012,15 +1102,6 @@ pub fn start_internal_bitcoind<'a>( .push( Row::new() .spacing(10) - .push(Container::new( - if exe_path.is_some() && started.is_none() && error.is_none() { - start_button.on_press(Message::InternalBitcoind( - message::InternalBitcoindMsg::Start, - )) - } else { - start_button - }, - )) .push(Row::new().spacing(10).push(next_button)), ) .push_maybe(error.map(|e| card::invalid(text(e)))),