diff --git a/Cargo.lock b/Cargo.lock index 34ca6d6..e390872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,21 +269,18 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" + [[package]] name = "bitflags" version = "1.3.2" @@ -292,9 +289,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" dependencies = [ "serde_core", ] @@ -416,9 +413,9 @@ checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -599,15 +596,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -845,15 +833,6 @@ dependencies = [ "syn 2.0.86", ] -[[package]] -name = "doxygen-rs" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" -dependencies = [ - "phf", -] - [[package]] name = "easy-jsonrpc-mw" version = "0.5.4" @@ -965,9 +944,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" @@ -1170,6 +1149,12 @@ dependencies = [ "slab", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.12.4" @@ -1223,7 +1208,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "libc", "libgit2-sys", "log", @@ -1336,7 +1321,6 @@ name = "grin_p2p" version = "5.4.0" dependencies = [ "bitflags 1.3.2", - "built", "bytes 0.5.6", "chrono", "enum_primitive", @@ -1392,8 +1376,8 @@ dependencies = [ "croaring", "grin_core", "grin_util", - "heed", "libc", + "lmdb-zero", "log", "memmap", "serde", @@ -1488,6 +1472,7 @@ dependencies = [ "grin_core", "grin_util", "grin_wallet_util", + "log", "pretty_assertions", "rand 0.6.5", "serde", @@ -1689,13 +1674,19 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "heed" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad82d6598ccf1dac15c8b758a1bd282b755b6776be600429176757190a1b0202" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "byteorder", "heed-traits", "heed-types", @@ -2266,9 +2257,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libgit2-sys" @@ -2283,12 +2274,21 @@ dependencies = [ ] [[package]] -name = "libredox" -version = "0.1.3" +name = "liblmdb-sys" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "feed38a3a580f60bf61aaa067b0ff4123395966839adeaf67258a9e50c4d2e49" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.11.1", "libc", ] @@ -2334,14 +2334,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] -name = "lmdb-master-sys" -version = "0.2.6" +name = "lmdb-zero" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeb9bd22e73bd1babffff614994b341e9b2008de7bb73bf1f7e9154f1978f8b" +checksum = "13416eee745b087c22934f35f1f24da22da41ba2a5ce197143d168ce055cc58d" dependencies = [ - "cc", - "doxygen-rs", + "bitflags 0.9.1", "libc", + "liblmdb-sys", + "supercow", ] [[package]] @@ -2543,7 +2544,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c624fa1b7aab6bd2aff6e9b18565cc0363b6d45cbcd7465c9ed5e3740ebf097" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "libc", "nix 0.26.4", "smallstr", @@ -2735,9 +2736,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.4" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -2757,8 +2758,8 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.11.1", - "cfg-if 1.0.0", + "bitflags 2.12.1", + "cfg-if 1.0.4", "foreign-types", "libc", "once_cell", @@ -2813,16 +2814,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi 0.3.9", -] - [[package]] name = "parking_lot" version = "0.10.2" @@ -2915,7 +2906,6 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ - "phf_macros", "phf_shared", ] @@ -2939,19 +2929,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2 1.0.89", - "quote 1.0.37", - "syn 2.0.86", -] - [[package]] name = "phf_shared" version = "0.11.2" @@ -3357,7 +3334,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", ] [[package]] @@ -3578,7 +3555,7 @@ version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "errno", "libc", "linux-raw-sys", @@ -3757,8 +3734,21 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.11.1", - "core-foundation", + "bitflags 2.12.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3806,11 +3796,10 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ - "serde_core", "serde_derive", ] @@ -3824,20 +3813,11 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2 1.0.89", "quote 1.0.37", @@ -3846,15 +3826,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa 1.0.11", "memchr", + "ryu", "serde", - "serde_core", - "zmij", ] [[package]] @@ -4039,6 +4018,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "supercow" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171758edb47aa306a78dfa4ab9aeb5167405bd4e3dc2b64e88f6a84bbe98bd63" + [[package]] name = "syn" version = "0.15.44" @@ -4072,15 +4057,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "synchronoise" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2" -dependencies = [ - "crossbeam-queue", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -5074,9 +5050,3 @@ dependencies = [ "crc32fast", "thiserror", ] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 76b262e..f7b9620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,10 +54,10 @@ grin_wallet_util = { path = "./util", version = "5.4.0-alpha.1" } # grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # For bleeding edge -# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" } +grin_core = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_util = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_api = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } # For local testing # grin_core = { path = "../grin/core"} diff --git a/api/Cargo.toml b/api/Cargo.toml index ceb46b3..9cc0302 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -41,9 +41,9 @@ grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" } # grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # For bleeding edge -# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } +grin_core = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_util = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } # For local testing # grin_core = { path = "../../grin/core"} diff --git a/api/src/foreign.rs b/api/src/foreign.rs index 1d96256..d946d9e 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -146,7 +146,7 @@ where /// // by the reference wallet implementation. /// // These traits can be replaced with alternative implementations if desired /// - /// let mut wallet = Box::new(DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap()) + /// let mut wallet = Box::new(DefaultWalletImpl::::new(node_client.clone()).unwrap()) /// as Box, HTTPNodeClient, ExtKeychain>>; /// /// // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc... @@ -496,17 +496,16 @@ macro_rules! doctest_helper_setup_doc_env_foreign { let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None).unwrap(); - let mut wallet = Box::new( - DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(), - ) - as Box< - dyn WalletInst< - 'static, - DefaultLCProvider, - HTTPNodeClient, - ExtKeychain, - >, - >; + let mut wallet = + Box::new(DefaultWalletImpl::::new(node_client.clone()).unwrap()) + as Box< + dyn WalletInst< + 'static, + DefaultLCProvider, + HTTPNodeClient, + ExtKeychain, + >, + >; let lc = wallet.lc_provider().unwrap(); let _ = lc.set_top_level_directory(&wallet_config.data_file_dir); lc.open_wallet(None, pw, false, false); diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index 1d2b526..55004a9 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -381,7 +381,6 @@ pub fn run_doctest_foreign( Box::new(DefaultWalletImpl::::new(client1.clone()).unwrap()) as Box< dyn WalletInst< - 'static, DefaultLCProvider, LocalWalletClient, ExtKeychain, @@ -416,7 +415,6 @@ pub fn run_doctest_foreign( Box::new(DefaultWalletImpl::::new(client2.clone()).unwrap()) as Box< dyn WalletInst< - 'static, DefaultLCProvider, LocalWalletClient, ExtKeychain, @@ -473,7 +471,7 @@ pub fn run_doctest_foreign( amount, ..Default::default() }; - api_impl::owner::issue_invoice_tx(&mut **w, (&mask2).as_ref(), args, true).unwrap() + api_impl::owner::issue_invoice_tx(w, (&mask2).as_ref(), args, true).unwrap() }; slate = { let mut w_lock = wallet1.lock(); @@ -487,8 +485,7 @@ pub fn run_doctest_foreign( selection_strategy_is_use_all: true, ..Default::default() }; - api_impl::owner::process_invoice_tx(&mut **w, (&mask1).as_ref(), &slate, args, true) - .unwrap() + api_impl::owner::process_invoice_tx(w, (&mask1).as_ref(), &slate, args, true).unwrap() }; println!("INIT INVOICE SLATE"); // Spit out slate for input to finalize_tx @@ -508,7 +505,7 @@ pub fn run_doctest_foreign( selection_strategy_is_use_all: true, ..Default::default() }; - let slate = api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap(); + let slate = api_impl::owner::init_send_tx(w, (&mask1).as_ref(), args, true).unwrap(); println!("INIT SLATE"); // Spit out slate for input to finalize_tx println!("{}", serde_json::to_string_pretty(&slate).unwrap()); diff --git a/api/src/owner.rs b/api/src/owner.rs index d291b2b..d1e930c 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -97,7 +97,7 @@ where /// /// Each method will call the [`WalletBackend`](../grin_wallet_libwallet/types/trait.WalletBackend.html)'s /// [`open_with_credentials`](../grin_wallet_libwallet/types/trait.WalletBackend.html#tymethod.open_with_credentials) - /// (initialising a keychain with the master seed,) perform its operation, then close the keychain + /// initializing a keychain with the master seed, perform its operation, then close the keychain /// with a call to [`close`](../grin_wallet_libwallet/types/trait.WalletBackend.html#tymethod.close) /// /// # Arguments @@ -147,11 +147,11 @@ where /// let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None).unwrap(); /// /// // impls::DefaultWalletImpl is provided for convenience in instantiating the wallet - /// // It contains the LMDBBackend, DefaultLCProvider (lifecycle) and ExtKeychain used + /// // It contains the WalletBackend, DefaultLCProvider (lifecycle) and ExtKeychain used /// // by the reference wallet implementation. /// // These traits can be replaced with alternative implementations if desired /// - /// let mut wallet = Box::new(DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap()) + /// let mut wallet = Box::new(DefaultWalletImpl::::new(node_client.clone()).unwrap()) /// as Box, HTTPNodeClient, ExtKeychain>>; /// /// // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc... @@ -258,7 +258,7 @@ where let w = w_lock.lc_provider()?.wallet_inst()?; // Test keychain mask, to keep API consistent let _ = w.keychain(keychain_mask)?; - owner::accounts(&mut **w) + owner::accounts(w) } /// Creates a new 'account', which is a mapping of a user-specified @@ -308,7 +308,7 @@ where ) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::create_account_path(&mut **w, keychain_mask, label) + owner::create_account_path(w, keychain_mask, label) } /// Sets the wallet's currently active account. This sets the @@ -358,7 +358,7 @@ where let w = w_lock.lc_provider()?.wallet_inst()?; // Test keychain mask, to keep API consistent let _ = w.keychain(keychain_mask)?; - owner::set_active_account(&mut **w, label) + owner::set_active_account(w, label) } /// Returns a list of outputs from the active account in the wallet. @@ -665,7 +665,7 @@ where let slate = { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::init_send_tx(&mut **w, keychain_mask, args, self.doctest_mode)? + owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)? }; // Helper functionality. If send arguments exist, attempt to send sync and // finalize @@ -701,17 +701,17 @@ where match result { Ok(_) => { info!("Tx sent ok",); - return Ok(ret_slate); + Ok(ret_slate) } Err(e) => { error!("Tx sent fail: {}", e); - return Err(e); + Err(e) } } } else { self.tx_lock_outputs(keychain_mask, &s)?; let ret_slate = self.finalize_tx(keychain_mask, &s)?; - return Ok(ret_slate); + Ok(ret_slate) } } Ok(None) => Ok(slate), @@ -724,7 +724,7 @@ where /// Issues a new invoice transaction slate, essentially a `request for payment`. /// The slate created by this function will contain the amount, an output for the amount, - /// as well as round 1 of singature creation complete. The slate should then be send + /// as well as round 1 of signature creation complete. The slate should then be sent /// to the payer, who should add their inputs and signature data and return the slate /// via the [Foreign API's `finalize_tx`](struct.Foreign.html#method.finalize_tx) method. /// @@ -764,14 +764,14 @@ where ) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::issue_invoice_tx(&mut **w, keychain_mask, args, self.doctest_mode) + owner::issue_invoice_tx(w, keychain_mask, args, self.doctest_mode) } - /// Processes an invoice tranaction created by another party, essentially + /// Processes an invoice transaction created by another party, essentially /// a `request for payment`. The incoming slate should contain a requested - /// amount, an output created by the invoicer convering the amount, and + /// amount, an output created by the invoicer converting the amount, and /// part 1 of signature creation completed. This function will add inputs - /// equalling the amount + fees, as well as perform round 1 and 2 of signature + /// equaling the amount + fees, as well as perform round 1 and 2 of signature /// creation. /// /// Callers should note that no prompting of the user will be done by this function @@ -837,8 +837,7 @@ where let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; let send_args = args.send_args.clone(); - let slate = - owner::process_invoice_tx(&mut **w, keychain_mask, slate, args, self.doctest_mode)?; + let slate = owner::process_invoice_tx(w, keychain_mask, slate, args, self.doctest_mode)?; // Helper functionality. If send arguments exist, attempt to send match send_args { Some(sa) => { @@ -871,7 +870,7 @@ where /// Locks the outputs associated with the inputs to the transaction in the given /// [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html), /// making them unavailable for use in further transactions. This function is called - /// by the sender, (or more generally, all parties who have put inputs into the transaction,) + /// by the sender, (or more generally, all parties who have put inputs into the transaction) /// and must be called before the corresponding call to [`finalize_tx`](struct.Owner.html#method.finalize_tx) /// that completes the transaction. /// @@ -929,7 +928,7 @@ where ) -> Result<(), Error> { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::tx_lock_outputs(&mut **w, keychain_mask, slate) + owner::tx_lock_outputs(w, keychain_mask, slate) } /// Finalizes a transaction, after all parties @@ -995,7 +994,7 @@ where ) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::finalize_tx(&mut **w, keychain_mask, slate) + owner::finalize_tx(w, keychain_mask, slate) } /// Posts a completed transaction to the listening node for validation and inclusion in a block @@ -1186,7 +1185,7 @@ where let w = w_lock.lc_provider()?.wallet_inst()?; // Test keychain mask, to keep API consistent let _ = w.keychain(keychain_mask)?; - owner::get_stored_tx(&**w, tx_id, slate_id) + owner::get_stored_tx(w, tx_id, slate_id) } /// Return the rewind hash of the wallet. @@ -2422,7 +2421,7 @@ where ) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::build_output(&mut **w, keychain_mask, features, amount) + owner::build_output(w, keychain_mask, features, amount) } // MWIXNET @@ -2481,7 +2480,7 @@ where let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; owner::create_mwixnet_req( - &mut **w, + w, keychain_mask, params, commitment, @@ -2499,7 +2498,7 @@ pub fn try_slatepack_sync_workflow( tor_sender: Option, send_to_finalize: bool, test_mode: bool, -) -> Result, libwallet::Error> { +) -> Result, Error> { if let Some(tc) = &tor_config { if tc.skip_send_attempt == Some(true) { return Ok(None); @@ -2604,7 +2603,7 @@ macro_rules! doctest_helper_setup_doc_env { use uuid::Uuid; - // don't run on windows CI, which gives very inconsistent results + // don't run on Windows CI, which gives very inconsistent results if cfg!(windows) { return; } @@ -2624,17 +2623,16 @@ macro_rules! doctest_helper_setup_doc_env { let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None).unwrap(); - let mut wallet = Box::new( - DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(), - ) - as Box< - dyn WalletInst< - 'static, - DefaultLCProvider, - HTTPNodeClient, - ExtKeychain, - >, - >; + let mut wallet = + Box::new(DefaultWalletImpl::::new(node_client.clone()).unwrap()) + as Box< + dyn WalletInst< + 'static, + DefaultLCProvider, + HTTPNodeClient, + ExtKeychain, + >, + >; let lc = wallet.lc_provider().unwrap(); let _ = lc.set_top_level_directory(&wallet_config.data_file_dir); lc.open_wallet(None, pw, false, false); diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index c983c20..bd862fe 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -2137,6 +2137,14 @@ where VersionedSlate::into_version(out_slate, version) } + fn tx_lock_outputs(&self, token: Token, in_slate: VersionedSlate) -> Result<(), Error> { + Owner::tx_lock_outputs( + self, + (&token.keychain_mask).as_ref(), + &Slate::from(in_slate), + ) + } + fn finalize_tx(&self, token: Token, in_slate: VersionedSlate) -> Result { let out_slate = Owner::finalize_tx( self, @@ -2147,11 +2155,12 @@ where VersionedSlate::into_version(out_slate, version) } - fn tx_lock_outputs(&self, token: Token, in_slate: VersionedSlate) -> Result<(), Error> { - Owner::tx_lock_outputs( + fn post_tx(&self, token: Token, slate: VersionedSlate, fluff: bool) -> Result<(), Error> { + Owner::post_tx( self, (&token.keychain_mask).as_ref(), - &Slate::from(in_slate), + &Slate::from(slate), + fluff, ) } @@ -2185,15 +2194,6 @@ where } } - fn post_tx(&self, token: Token, slate: VersionedSlate, fluff: bool) -> Result<(), Error> { - Owner::post_tx( - self, - (&token.keychain_mask).as_ref(), - &Slate::from(slate), - fluff, - ) - } - fn get_rewind_hash(&self, token: Token) -> Result { Owner::get_rewind_hash(self, (&token.keychain_mask).as_ref()) } @@ -2625,26 +2625,25 @@ pub fn run_doctest_owner( payment_proof_recipient_address: proof_address, ..Default::default() }; - let mut slate = - api_impl::owner::init_send_tx(&mut **w, (&mask1).as_ref(), args, true).unwrap(); + let mut slate = api_impl::owner::init_send_tx(w, (&mask1).as_ref(), args, true).unwrap(); println!("INITIAL SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); { let mut w_lock = wallet2.lock(); let w2 = w_lock.lc_provider().unwrap().wallet_inst().unwrap(); - slate = api_impl::foreign::receive_tx(&mut **w2, (&mask2).as_ref(), &slate, None, true) - .unwrap(); + slate = + api_impl::foreign::receive_tx(w2, (&mask2).as_ref(), &slate, None, true).unwrap(); w2.close().unwrap(); } // Spit out slate for input to finalize_tx if lock_tx { println!("LOCKING TX"); - api_impl::owner::tx_lock_outputs(&mut **w, (&mask1).as_ref(), &slate).unwrap(); + api_impl::owner::tx_lock_outputs(w, (&mask1).as_ref(), &slate).unwrap(); } println!("RECEIPIENT SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); if finalize_tx { - slate = api_impl::owner::finalize_tx(&mut **w, (&mask1).as_ref(), &slate).unwrap(); + slate = api_impl::owner::finalize_tx(w, (&mask1).as_ref(), &slate).unwrap(); error!("FINALIZED TX SLATE"); println!("{}", serde_json::to_string_pretty(&slate).unwrap()); } diff --git a/config/Cargo.toml b/config/Cargo.toml index 1c8125a..abcac76 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -30,8 +30,8 @@ grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" } #grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # For bleeding edge -# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } +grin_core = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_util = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } # For local testing # grin_core = { path = "../../grin/core"} diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 7cc4d92..19d953b 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/mimblewimble/grin-wallet" keywords = [ "crypto", "grin", "mimblewimble" ] exclude = ["**/*.grin", "**/*.grin2"] #build = "src/build/build.rs" -edition = "2021" +edition = "2018" [dependencies] futures = "0.3" @@ -43,6 +43,7 @@ grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" } #grin_keychain = "5.4.0" #grin_util = "5.4.0" #grin_api = "5.4.0" +#grin_chain = "5.4.0" # For beta release @@ -50,48 +51,31 @@ grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" } # grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } +# grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # For bleeding edge -# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" } +#grin_core = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +#grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +#grin_util = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +#grin_api = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +#grin_chain = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } # For local testing # grin_core = { path = "../../grin/core"} # grin_keychain = { path = "../../grin/keychain"} # grin_util = { path = "../../grin/util"} # grin_api = { path = "../../grin/api"} +# grin_chain = { path = "../../grin/chain"} # Monorepo deps grin_core = { path = "../../node/core"} grin_keychain = { path = "../../node/keychain"} grin_util = { path = "../../node/util"} grin_api = { path = "../../node/api"} +grin_chain = { path = "../../node/chain"} ##### [dev-dependencies] ed25519-dalek = "1.0.0-pre.4" remove_dir_all = "0.7" - -##### Grin Imports - -# For Release -#grin_chain = "5.4.0" - -# For beta release - -# grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } - -# For bleeding edge -# grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" } - -# For local testing -# grin_chain = { path = "../../grin/chain"} - -# Monorepo deps -grin_chain = { path = "../../node/chain"} - -##### - diff --git a/controller/tests/check.rs b/controller/tests/check.rs index 634cdd2..c9c5990 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -26,6 +26,7 @@ use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; use impls::{PathToSlate, SlatePutter as _}; use libwallet::{InitTxArgs, NodeClient}; +use std::cmp; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -847,6 +848,200 @@ fn output_scanning_impl(test_dir: &'static str) -> Result<(), libwallet::Error> Ok(()) } +fn multi_batch_scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { + let mut wallet_proxy = create_wallet_proxy(test_dir); + let chain = wallet_proxy.chain.clone(); + let stopper = wallet_proxy.running.clone(); + + create_wallet_and_add!( + client1, + wallet1, + mask1_i, + test_dir, + "wallet1", + None, + &mut wallet_proxy, + false + ); + let mask1 = (&mask1_i).as_ref(); + + thread::spawn(move || { + if let Err(e) = wallet_proxy.run() { + error!("Wallet Proxy error: {}", e); + } + }); + + let reward = consensus::REWARD; + let bh = 12u64; + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + + let mut output_commits = vec![]; + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (_, info) = api.retrieve_summary_info(m, true, 1)?; + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, bh * reward); + output_commits = api.retrieve_outputs(m, false, true, None)?.1; + Ok(()) + })?; + + { + wallet_inst!(wallet1, w); + let mut batch = w.batch(mask1)?; + batch.delete(&output_commits[8].output.key_id, &None)?; + batch.commit()?; + } + + let tip = { + wallet_inst!(wallet1, w); + w.w2n_client().get_chain_tip()? + }; + + let start_height = 1; + let batch_size = 5; + let mut total_pmmr_range = None; + let mut batches = 0; + let status_send_channel = None; + for h in (start_height..tip.0).step_by((batch_size + 1) as usize) { + let batch_end_height = cmp::min(tip.0, h + batch_size); + let (mut info, range) = libwallet::scan( + wallet1.clone(), + mask1, + false, + h, + batch_end_height, + start_height, + tip.0, + total_pmmr_range, + &status_send_channel, + )?; + info.hash = if batch_end_height == tip.0 { + tip.1.clone() + } else { + "".to_owned() + }; + total_pmmr_range = Some(range); + batches += 1; + + wallet_inst!(wallet1, w); + let mut batch = w.batch(mask1)?; + batch.save_last_scanned_block(info)?; + batch.commit()?; + } + assert!(batches > 1); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; + let (_, info) = api.retrieve_summary_info(m, false, 1)?; + assert_eq!(info.total, bh * reward); + assert_eq!(restored_outputs.len(), bh as usize); + Ok(()) + })?; + + stopper.store(false, Ordering::Relaxed); + thread::sleep(Duration::from_millis(200)); + Ok(()) +} + +fn restore_corrupted_outputs_across_batches_impl( + test_dir: &'static str, +) -> Result<(), libwallet::Error> { + let mut wallet_proxy = create_wallet_proxy(test_dir); + let chain = wallet_proxy.chain.clone(); + let stopper = wallet_proxy.running.clone(); + + create_wallet_and_add!( + client1, + wallet1, + mask1_i, + test_dir, + "wallet1", + None, + &mut wallet_proxy, + false + ); + let mask1 = (&mask1_i).as_ref(); + + thread::spawn(move || { + if let Err(e) = wallet_proxy.run() { + error!("Wallet Proxy error: {}", e); + } + }); + + let reward = consensus::REWARD; + let bh = 12u64; + let _ = + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); + + let mut output_commits = vec![]; + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (_, info) = api.retrieve_summary_info(m, true, 1)?; + assert_eq!(info.total, bh * reward); + output_commits = api.retrieve_outputs(m, false, true, None)?.1; + Ok(()) + })?; + + { + wallet_inst!(wallet1, w); + let mut batch = w.batch(mask1)?; + batch.delete(&output_commits[2].output.key_id, &None)?; + batch.delete(&output_commits[8].output.key_id, &None)?; + batch.commit()?; + } + + let tip = { + wallet_inst!(wallet1, w); + w.w2n_client().get_chain_tip()? + }; + let status_send_channel = None; + let (info, _) = libwallet::scan( + wallet1.clone(), + mask1, + false, + 1, + 6, + 1, + tip.0, + None, + &status_send_channel, + )?; + { + wallet_inst!(wallet1, w); + { + let mut batch = w.batch(mask1)?; + batch.save_last_scanned_block(info)?; + batch.commit()?; + } + let last_scanned = w.last_scanned_block()?; + assert_eq!(last_scanned.height, 6); + assert_eq!(last_scanned.hash, ""); + } + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + let (_, outputs_after_first_batch) = api.retrieve_outputs(m, false, false, None)?; + assert_eq!(outputs_after_first_batch.len(), (bh - 1) as usize); + + let (_, info) = api.retrieve_summary_info(m, true, 1)?; + let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; + let unique_keys = restored_outputs + .iter() + .map(|o| o.output.key_id.clone()) + .collect::>(); + + assert_eq!(info.total, bh * reward); + assert_eq!(restored_outputs.len(), bh as usize); + assert_eq!(unique_keys.len(), bh as usize); + assert!(restored_outputs + .iter() + .all(|o| o.output.status == libwallet::OutputStatus::Unspent)); + Ok(()) + })?; + + stopper.store(false, Ordering::Relaxed); + thread::sleep(Duration::from_millis(200)); + Ok(()) +} + #[test] fn scan() { let test_dir = "test_output/scan"; @@ -876,3 +1071,23 @@ fn output_scanning() { } clean_output_dir(test_dir); } + +#[test] +fn multi_batch_scan() { + let test_dir = "test_output/multi_batch_scan"; + setup(test_dir); + if let Err(e) = multi_batch_scan_impl(test_dir) { + panic!("Libwallet Error: {}", e); + } + clean_output_dir(test_dir); +} + +#[test] +fn restore_corrupted_outputs_across_batches() { + let test_dir = "test_output/restore_corrupted_outputs_across_batches"; + setup(test_dir); + if let Err(e) = restore_corrupted_outputs_across_batches_impl(test_dir) { + panic!("Libwallet Error: {}", e); + } + clean_output_dir(test_dir); +} diff --git a/controller/tests/common/mod.rs b/controller/tests/common/mod.rs index 24f600d..ff1066e 100644 --- a/controller/tests/common/mod.rs +++ b/controller/tests/common/mod.rs @@ -98,7 +98,7 @@ pub fn create_wallet_proxy( test_dir: &str, ) -> WalletProxy< '_, - DefaultLCProvider<'_, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, > { @@ -117,7 +117,7 @@ pub fn create_local_wallet( Box< dyn WalletInst< 'static, - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, @@ -129,7 +129,7 @@ pub fn create_local_wallet( let mut wallet = Box::new(DefaultWalletImpl::::new(client).unwrap()) as Box< dyn WalletInst< - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, @@ -156,7 +156,7 @@ pub fn open_local_wallet( Box< dyn WalletInst< 'static, - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, @@ -168,7 +168,7 @@ pub fn open_local_wallet( let mut wallet = Box::new(DefaultWalletImpl::::new(client).unwrap()) as Box< dyn WalletInst< - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, diff --git a/controller/tests/revert.rs b/controller/tests/revert.rs index ea6c0b2..5286c85 100644 --- a/controller/tests/revert.rs +++ b/controller/tests/revert.rs @@ -41,7 +41,7 @@ type Wallet = Arc< Box< dyn WalletInst< 'static, - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, diff --git a/controller/tests/tx_list_filter.rs b/controller/tests/tx_list_filter.rs index 7a8dab0..d744694 100644 --- a/controller/tests/tx_list_filter.rs +++ b/controller/tests/tx_list_filter.rs @@ -45,7 +45,7 @@ fn test_wallet_tx_filtering( Box< dyn WalletInst< 'static, - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, diff --git a/doc/design/design.md b/doc/design/design.md index d113504..2da52d2 100644 --- a/doc/design/design.md +++ b/doc/design/design.md @@ -55,24 +55,22 @@ the default client only supports single-user - single recipient, an arbitrary nu ### Wallet Traits -In the current code, a Wallet implementation is just a combination of these three traits. The vast majority of functions within libwallet +In the current code, a Wallet implementation is just a combination of these two traits. The vast majority of functions within libwallet and libTX have a signature similar to the following: ```rust pub fn retrieve_outputs( -!·wallet: &mut T, +!·wallet: &mut WalletBackend, !·show_spent: bool, !·tx_id: Option, ) -> Result, Error> where -!·T: WalletBackend, !·C: NodeClient, !·K: Keychain, { ``` -With `T` in this instance being a class that implements the `WalletBackend` trait, which is further parameterized with implementations of -`NodeClient` and `Keychain`. +Provided `WalletBackend` parameterized with implementations of `NodeClient` and `Keychain`. There is currently only a single implementation of the Keychain trait within the Grin code, in the `keychain` crate exported as `ExtKeyChain`. The `Keychain` trait makes several assumptions about the underlying implementation, particularly that it will adhere to a @@ -81,9 +79,4 @@ The `Keychain` trait makes several assumptions about the underlying implementati There are two implementations of `NodeClient` within the code, the main version being the `HTTPNodeClient` found within `wallet/src/client.rs` and the seconds a test client that communicates with an in-process instance of a chain. The NodeClient isolates all network calls, so upgrading wallet communication from the current simple http interaction to a more secure protocol (or allowing for many options) should be a simple -matter of dropping in different `NodeClient` implementations. - -There are also two implementations of `WalletBackend` within the code at the base of the `wallet` crate. `LMDBBackend` found within -`wallet/src/lmdb_wallet.rs` is the main implementation, and is now used by all grin wallet commands. The earlier `FileWallet` still exists -within the code, however it is not invoked, and given there are no real advantages to running it over a DB implementation, development on it -has been dropped in favour of the LMDB implementation. \ No newline at end of file +matter of dropping in different `NodeClient` implementations. \ No newline at end of file diff --git a/impls/Cargo.toml b/impls/Cargo.toml index 98c5e1e..07616e6 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -60,12 +60,12 @@ grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" } # grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # For bleeding edge -# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" } -# grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" } +grin_core = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_keychain = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_chain = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_util = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_api = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } +grin_store = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } # For local testing # grin_core = { path = "../../grin/core"} diff --git a/impls/src/backends/lmdb.rs b/impls/src/backends/lmdb.rs index f9bcb0f..e69de29 100644 --- a/impls/src/backends/lmdb.rs +++ b/impls/src/backends/lmdb.rs @@ -1,803 +0,0 @@ -// Copyright 2021 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::cell::RefCell; -use std::{fs, path}; - -// for writing stored transaction files -use byteorder::{BigEndian, WriteBytesExt}; -use std::fs::File; -use std::io::{Read, Write}; -use std::marker::PhantomData; -use std::path::Path; -use uuid::Uuid; - -use crate::blake2::blake2b::{Blake2b, Blake2bResult}; - -use crate::keychain::{ChildNumber, ExtKeychain, Identifier, Keychain, SwitchCommitmentType}; -use crate::store::{self, option_to_not_found}; - -use crate::core::core::Transaction; -use crate::core::ser; -use crate::libwallet::{ - AcctPathMapping, Context, Error, NodeClient, OutputData, ScannedBlockInfo, TxLogEntry, - WalletBackend, WalletInitStatus, WalletOutputBatch, -}; -use crate::util::secp::constants::SECRET_KEY_SIZE; -use crate::util::secp::key::SecretKey; -use crate::util::{self, ToHex}; - -use rand::rngs::mock::StepRng; -use rand::thread_rng; - -pub const DB_DIR: &str = "db"; -pub const TX_SAVE_DIR: &str = "saved_txs"; - -const OUTPUT_PREFIX: u8 = b'o'; -const DERIV_PREFIX: u8 = b'd'; -const CONFIRMED_HEIGHT_PREFIX: u8 = b'c'; -const PRIVATE_TX_CONTEXT_PREFIX: u8 = b'p'; -const TX_LOG_ENTRY_PREFIX: u8 = b't'; -const TX_LOG_ID_PREFIX: u8 = b'i'; -const ACCOUNT_PATH_MAPPING_PREFIX: u8 = b'a'; -const LAST_SCANNED_BLOCK: u8 = b'l'; -const LAST_SCANNED_KEY: &str = "LAST_SCANNED_KEY"; -const WALLET_INIT_STATUS: u8 = b'w'; -const WALLET_INIT_STATUS_KEY: &str = "WALLET_INIT_STATUS"; - -const DB_PREFIXES: [u8; 9] = [ - OUTPUT_PREFIX, - DERIV_PREFIX, - CONFIRMED_HEIGHT_PREFIX, - PRIVATE_TX_CONTEXT_PREFIX, - TX_LOG_ENTRY_PREFIX, - TX_LOG_ID_PREFIX, - ACCOUNT_PATH_MAPPING_PREFIX, - LAST_SCANNED_BLOCK, - WALLET_INIT_STATUS, -]; - -/// test to see if database files exist in the current directory. If so, -/// use a DB backend for all operations -pub fn wallet_db_exists(data_file_dir: &str) -> bool { - let db_path = Path::new(data_file_dir).join(DB_DIR); - db_path.exists() -} - -/// Helper to derive XOR keys for storing private transaction keys in the DB -/// (blind_xor_key, nonce_xor_key) -fn private_ctx_xor_keys( - keychain: &K, - slate_id: &[u8], -) -> Result<([u8; SECRET_KEY_SIZE], [u8; SECRET_KEY_SIZE]), Error> -where - K: Keychain, -{ - let root_key = keychain.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; - - // derive XOR values for storing secret values in DB - // h(root_key|slate_id|"blind") - let mut hasher = Blake2b::new(SECRET_KEY_SIZE); - hasher.update(&root_key.0[..]); - hasher.update(&slate_id[..]); - hasher.update(&b"blind"[..]); - let blind_xor_key = hasher.finalize(); - let mut ret_blind = [0; SECRET_KEY_SIZE]; - ret_blind.copy_from_slice(&blind_xor_key.as_bytes()[0..SECRET_KEY_SIZE]); - - // h(root_key|slate_id|"nonce") - let mut hasher = Blake2b::new(SECRET_KEY_SIZE); - hasher.update(&root_key.0[..]); - hasher.update(&slate_id[..]); - hasher.update(&b"nonce"[..]); - let nonce_xor_key = hasher.finalize(); - let mut ret_nonce = [0; SECRET_KEY_SIZE]; - ret_nonce.copy_from_slice(&nonce_xor_key.as_bytes()[0..SECRET_KEY_SIZE]); - - Ok((ret_blind, ret_nonce)) -} - -pub struct LMDBBackend<'ck, C, K> -where - C: NodeClient + 'ck, - K: Keychain + 'ck, -{ - db: store::Store, - data_file_dir: String, - /// Keychain - pub keychain: Option, - /// Check value for XORed keychain seed - pub master_checksum: Box>, - /// Parent path to use by default for output operations - parent_key_id: Identifier, - /// wallet to node client - w2n_client: C, - ///phantom - _phantom: &'ck PhantomData, -} - -impl<'ck, C, K> LMDBBackend<'ck, C, K> -where - C: NodeClient + 'ck, - K: Keychain + 'ck, -{ - pub fn new(data_file_dir: &str, n_client: C) -> Result { - let db_path = Path::new(data_file_dir).join(DB_DIR); - fs::create_dir_all(&db_path).expect("Couldn't create wallet backend directory!"); - - let stored_tx_path = Path::new(data_file_dir).join(TX_SAVE_DIR); - fs::create_dir_all(&stored_tx_path) - .expect("Couldn't create wallet backend tx storage directory!"); - - let store = store::Store::new( - db_path.to_str().unwrap(), - None, - Some(DB_DIR), - DB_PREFIXES.to_vec(), - None, - )?; - - // Make sure default wallet derivation path always exists - // as well as path (so it can be retrieved by batches to know where to store - // completed transactions, for reference - let default_account = AcctPathMapping { - label: "default".to_owned(), - path: LMDBBackend::::default_path(), - }; - - { - let mut batch = store.batch()?; - batch.put_ser( - Some(ACCOUNT_PATH_MAPPING_PREFIX), - default_account.label.as_bytes(), - &default_account, - )?; - batch.commit()?; - } - - let res = LMDBBackend { - db: store, - data_file_dir: data_file_dir.to_owned(), - keychain: None, - master_checksum: Box::new(None), - parent_key_id: LMDBBackend::::default_path(), - w2n_client: n_client, - _phantom: &PhantomData, - }; - Ok(res) - } - - fn default_path() -> Identifier { - // return the default parent wallet path, corresponding to the default account - // in the BIP32 spec. Parent is account 0 at level 2, child output identifiers - // are all at level 3 - ExtKeychain::derive_key_id(2, 0, 0, 0, 0) - } - - /// Just test to see if database files exist in the current directory. If - /// so, use a DB backend for all operations - pub fn exists(data_file_dir: &str) -> bool { - let db_path = path::Path::new(data_file_dir).join(DB_DIR); - db_path.exists() - } -} - -impl<'ck, C, K> WalletBackend<'ck, C, K> for LMDBBackend<'ck, C, K> -where - C: NodeClient + 'ck, - K: Keychain + 'ck, -{ - /// Set the keychain, which should already have been opened - fn set_keychain( - &mut self, - mut k: Box, - mask: bool, - use_test_rng: bool, - ) -> Result, Error> { - // store hash of master key, so it can be verified later after unmasking - let root_key = k.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; - let mut hasher = Blake2b::new(SECRET_KEY_SIZE); - hasher.update(&root_key.0[..]); - self.master_checksum = Box::new(Some(hasher.finalize())); - - let mask_value = { - match mask { - true => { - // Random value that must be XORed against the stored wallet seed - // before it is used - let mask_value = match use_test_rng { - true => { - let mut test_rng = StepRng::new(1_234_567_890_u64, 1); - SecretKey::new(&k.secp(), &mut test_rng) - } - false => SecretKey::new(&k.secp(), &mut thread_rng()), - }; - k.mask_master_key(&mask_value)?; - Some(mask_value) - } - false => None, - } - }; - - self.keychain = Some(*k); - Ok(mask_value) - } - - /// Close wallet - fn close(&mut self) -> Result<(), Error> { - self.keychain = None; - Ok(()) - } - - /// Return the keychain being used, cloned with XORed token value - /// for temporary use - fn keychain(&self, mask: Option<&SecretKey>) -> Result { - match self.keychain.as_ref() { - Some(k) => { - let mut k_masked = k.clone(); - if let Some(m) = mask { - k_masked.mask_master_key(m)?; - } - // Check if master seed is what is expected (especially if it's been xored) - let root_key = - k_masked.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; - let mut hasher = Blake2b::new(SECRET_KEY_SIZE); - hasher.update(&root_key.0[..]); - if *self.master_checksum != Some(hasher.finalize()) { - error!("Supplied keychain mask is invalid"); - return Err(Error::InvalidKeychainMask); - } - Ok(k_masked) - } - None => Err(Error::KeychainDoesntExist), - } - } - - /// Return the node client being used - fn w2n_client(&mut self) -> &mut C { - &mut self.w2n_client - } - - /// return the version of the commit for caching - fn calc_commit_for_cache( - &mut self, - keychain_mask: Option<&SecretKey>, - amount: u64, - id: &Identifier, - ) -> Result, Error> { - //TODO: Check if this is really necessary, it's the only thing - //preventing removing the need for config in the wallet backend - /*if self.config.no_commit_cache == Some(true) { - Ok(None) - } else {*/ - Ok(Some( - self.keychain(keychain_mask)? - .commit(amount, &id, SwitchCommitmentType::Regular)? - .0 - .to_vec() - .to_hex(), // TODO: proper support for different switch commitment schemes - )) - /*}*/ - } - - /// Set parent path by account name - fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> { - let label = label.to_owned(); - let res = self.acct_path_iter().find(|l| l.label == label); - if let Some(a) = res { - self.set_parent_key_id(a.path); - Ok(()) - } else { - Err(Error::UnknownAccountLabel(label)) - } - } - - /// set parent path - fn set_parent_key_id(&mut self, id: Identifier) { - self.parent_key_id = id; - } - - fn parent_key_id(&mut self) -> Identifier { - self.parent_key_id.clone() - } - - fn get(&self, id: &Identifier, mmr_index: &Option) -> Result { - let key = match mmr_index { - Some(i) => to_key_u64(id.to_bytes(), *i), - None => id.to_bytes().to_vec(), - }; - option_to_not_found(self.db.get_ser(Some(OUTPUT_PREFIX), &key, None), || { - format!("Key Id: {}", id) - }) - .map_err(|e| e.into()) - } - - // TODO - fix this awkward conversion between PrefixIterator and our Box - fn iter<'a>(&'a self) -> Box + 'a> { - let protocol_version = self.db.protocol_version(); - let prefix_iter = self.db.iter(Some(OUTPUT_PREFIX), move |_, mut v| { - ser::deserialize( - &mut v, - protocol_version, - ser::DeserializationMode::default(), - ) - .map_err(From::from) - }); - let iter = prefix_iter - .expect("deserialize") - .into_iter() - .filter(|x| x.is_ok()) - .map(|x| x.unwrap()); - Box::new(iter) - } - - fn get_tx_log_entry(&self, u: &Uuid) -> Result, Error> { - self.db - .get_ser(Some(TX_LOG_ENTRY_PREFIX), u.as_bytes(), None) - .map_err(|e| e.into()) - } - - // TODO - fix this awkward conversion between PrefixIterator and our Box - fn tx_log_iter<'a>(&'a self) -> Box + 'a> { - let protocol_version = self.db.protocol_version(); - let prefix_iter = self.db.iter(Some(TX_LOG_ENTRY_PREFIX), move |_, mut v| { - ser::deserialize( - &mut v, - protocol_version, - ser::DeserializationMode::default(), - ) - .map_err(From::from) - }); - let iter = prefix_iter - .expect("deserialize") - .into_iter() - .filter(|x| x.is_ok()) - .map(|x| x.unwrap()); - Box::new(iter) - } - - fn get_private_context( - &mut self, - keychain_mask: Option<&SecretKey>, - slate_id: &[u8], - ) -> Result { - let ctx_key = to_key_u64(slate_id, 0); - let (blind_xor_key, nonce_xor_key) = - private_ctx_xor_keys(&self.keychain(keychain_mask)?, slate_id)?; - - let mut ctx: Context = option_to_not_found( - self.db - .get_ser(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key, None), - || format!("Slate id: {:x?}", slate_id.to_vec()), - )?; - - for i in 0..SECRET_KEY_SIZE { - ctx.sec_key.0[i] ^= blind_xor_key[i]; - ctx.sec_nonce.0[i] ^= nonce_xor_key[i]; - } - - Ok(ctx) - } - - // TODO - fix this awkward conversion between PrefixIterator and our Box - fn acct_path_iter<'a>(&'a self) -> Box + 'a> { - let protocol_version = self.db.protocol_version(); - let prefix_iter = self - .db - .iter(Some(ACCOUNT_PATH_MAPPING_PREFIX), move |_, mut v| { - ser::deserialize( - &mut v, - protocol_version, - ser::DeserializationMode::default(), - ) - .map_err(From::from) - }); - let iter = prefix_iter - .expect("deserialize") - .into_iter() - .filter(|x| x.is_ok()) - .map(|x| x.unwrap()); - Box::new(iter) - } - - fn get_acct_path(&self, label: String) -> Result, Error> { - self.db - .get_ser(Some(ACCOUNT_PATH_MAPPING_PREFIX), label.as_bytes(), None) - .map_err(|e| e.into()) - } - - fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error> { - let filename = format!("{}.grintx", uuid); - let path = Path::new(&self.data_file_dir) - .join(TX_SAVE_DIR) - .join(filename); - let path_buf = Path::new(&path).to_path_buf(); - let mut stored_tx = File::create(path_buf)?; - let tx_hex = ser::ser_vec(tx, ser::ProtocolVersion(1)).unwrap().to_hex(); - stored_tx.write_all(&tx_hex.as_bytes())?; - stored_tx.sync_all()?; - Ok(()) - } - - fn get_stored_tx(&self, uuid: &str) -> Result, Error> { - let filename = format!("{}.grintx", uuid); - let path = Path::new(&self.data_file_dir) - .join(TX_SAVE_DIR) - .join(filename); - let tx_file = Path::new(&path).to_path_buf(); - let mut tx_f = File::open(tx_file)?; - let mut content = String::new(); - tx_f.read_to_string(&mut content)?; - let tx_bin = util::from_hex(&content).unwrap(); - Ok(Some( - ser::deserialize( - &mut &tx_bin[..], - ser::ProtocolVersion(1), - ser::DeserializationMode::default(), - ) - .unwrap(), - )) - } - - fn batch<'a>( - &'a mut self, - keychain_mask: Option<&SecretKey>, - ) -> Result + 'a>, Error> { - Ok(Box::new(Batch { - _store: self, - db: RefCell::new(Some(self.db.batch()?)), - keychain: Some(self.keychain(keychain_mask)?), - })) - } - - fn batch_no_mask<'a>(&'a mut self) -> Result + 'a>, Error> { - Ok(Box::new(Batch { - _store: self, - db: RefCell::new(Some(self.db.batch()?)), - keychain: None, - })) - } - - fn current_child_index<'a>(&mut self, parent_key_id: &Identifier) -> Result { - let index = { - let batch = self.db.batch()?; - batch - .get_ser(Some(DERIV_PREFIX), &parent_key_id.to_bytes(), None)? - .unwrap_or_else(|| 0) - }; - Ok(index) - } - - fn next_child<'a>(&mut self, keychain_mask: Option<&SecretKey>) -> Result { - let parent_key_id = self.parent_key_id.clone(); - let mut deriv_idx = { - let batch = self.db.batch()?; - batch - .get_ser(Some(DERIV_PREFIX), &self.parent_key_id.to_bytes(), None)? - .unwrap_or_else(|| 0) - }; - let mut return_path = self.parent_key_id.to_path(); - return_path.depth += 1; - return_path.path[return_path.depth as usize - 1] = ChildNumber::from(deriv_idx); - deriv_idx += 1; - let mut batch = self.batch(keychain_mask)?; - batch.save_child_index(&parent_key_id, deriv_idx)?; - batch.commit()?; - Ok(Identifier::from_path(&return_path)) - } - - fn last_confirmed_height<'a>(&mut self) -> Result { - let batch = self.db.batch()?; - let last_confirmed_height = batch - .get_ser( - Some(CONFIRMED_HEIGHT_PREFIX), - &self.parent_key_id.to_bytes(), - None, - )? - .unwrap_or_else(|| 0); - Ok(last_confirmed_height) - } - - fn last_scanned_block<'a>(&mut self) -> Result { - let batch = self.db.batch()?; - let last_scanned_block = batch - .get_ser(Some(LAST_SCANNED_BLOCK), LAST_SCANNED_KEY.as_bytes(), None)? - .unwrap_or_else(|| ScannedBlockInfo { - height: 0, - hash: "".to_owned(), - start_pmmr_index: 0, - last_pmmr_index: 0, - }); - Ok(last_scanned_block) - } - - fn init_status<'a>(&mut self) -> Result { - let batch = self.db.batch()?; - let status = batch - .get_ser( - Some(WALLET_INIT_STATUS), - WALLET_INIT_STATUS_KEY.as_bytes(), - None, - )? - .unwrap_or_else(|| WalletInitStatus::InitComplete); - Ok(status) - } -} - -/// An atomic batch in which all changes can be committed all at once or -/// discarded on error. -pub struct Batch<'a, C, K> -where - C: NodeClient, - K: Keychain, -{ - _store: &'a LMDBBackend<'a, C, K>, - db: RefCell>>, - /// Keychain - keychain: Option, -} - -#[allow(missing_docs)] -impl<'a, C, K> WalletOutputBatch for Batch<'a, C, K> -where - C: NodeClient, - K: Keychain, -{ - fn keychain(&mut self) -> &mut K { - self.keychain.as_mut().unwrap() - } - - fn save(&mut self, out: OutputData) -> Result<(), Error> { - // Save the output data to the db. - let key = match out.mmr_index { - Some(i) => to_key_u64(out.key_id.to_bytes(), i), - None => out.key_id.to_bytes().to_vec(), - }; - self.db - .borrow_mut() - .as_mut() - .unwrap() - .put_ser(Some(OUTPUT_PREFIX), &key, &out)?; - - Ok(()) - } - - fn get(&self, id: &Identifier, mmr_index: &Option) -> Result { - let key = match mmr_index { - Some(i) => to_key_u64(id.to_bytes(), *i), - None => id.to_bytes().to_vec(), - }; - option_to_not_found( - self.db - .borrow() - .as_ref() - .unwrap() - .get_ser(Some(OUTPUT_PREFIX), &key, None), - || format!("Key ID: {}", id), - ) - .map_err(|e| e.into()) - } - - // TODO - fix this awkward conversion between PrefixIterator and our Box - fn iter(&self) -> Box> { - let db = self.db.borrow(); - let db = db.as_ref().unwrap(); - let protocol_version = db.protocol_version(); - let prefix_iter = db.iter(Some(OUTPUT_PREFIX), move |_, mut v| { - ser::deserialize( - &mut v, - protocol_version, - ser::DeserializationMode::default(), - ) - .map_err(From::from) - }); - let iter = prefix_iter - .expect("deserialize") - .into_iter() - .filter(|x| x.is_ok()) - .map(|x| x.unwrap()); - Box::new(iter) - } - - fn delete(&mut self, id: &Identifier, mmr_index: &Option) -> Result<(), Error> { - // Delete the output data. - let key = match mmr_index { - Some(i) => to_key_u64(id.to_bytes(), *i), - None => id.to_bytes().to_vec(), - }; - self.db - .borrow_mut() - .as_mut() - .unwrap() - .delete(Some(OUTPUT_PREFIX), &key)?; - Ok(()) - } - - fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result { - let last_tx_log_id = self - .db - .borrow_mut() - .as_mut() - .unwrap() - .get_ser(Some(TX_LOG_ID_PREFIX), &parent_key_id.to_bytes(), None)? - .unwrap_or_else(|| 0); - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(TX_LOG_ID_PREFIX), - &parent_key_id.to_bytes(), - &(last_tx_log_id + 1), - )?; - Ok(last_tx_log_id) - } - - // TODO - fix this awkward conversion between PrefixIterator and our Box - fn tx_log_iter(&self) -> Box> { - let db = self.db.borrow(); - let db = db.as_ref().unwrap(); - let protocol_version = db.protocol_version(); - let prefix_iter = db.iter(Some(TX_LOG_ENTRY_PREFIX), move |_, mut v| { - ser::deserialize( - &mut v, - protocol_version, - ser::DeserializationMode::default(), - ) - .map_err(From::from) - }); - let iter = prefix_iter - .expect("deserialize") - .into_iter() - .filter(|x| x.is_ok()) - .map(|x| x.unwrap()); - Box::new(iter) - } - - fn save_last_confirmed_height( - &mut self, - parent_key_id: &Identifier, - height: u64, - ) -> Result<(), Error> { - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(CONFIRMED_HEIGHT_PREFIX), - &parent_key_id.to_bytes(), - &height, - )?; - Ok(()) - } - - fn save_last_scanned_block(&mut self, block_info: ScannedBlockInfo) -> Result<(), Error> { - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(LAST_SCANNED_BLOCK), - LAST_SCANNED_KEY.as_bytes(), - &block_info, - )?; - Ok(()) - } - - fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error> { - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(WALLET_INIT_STATUS), - WALLET_INIT_STATUS_KEY.as_bytes(), - &value, - )?; - Ok(()) - } - - fn save_child_index(&mut self, parent_id: &Identifier, child_n: u32) -> Result<(), Error> { - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(DERIV_PREFIX), - &parent_id.to_bytes(), - &child_n, - )?; - Ok(()) - } - - fn save_tx_log_entry( - &mut self, - tx_in: TxLogEntry, - parent_id: &Identifier, - ) -> Result<(), Error> { - let tx_log_key = to_key_u64(parent_id.to_bytes(), tx_in.id as u64); - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(TX_LOG_ENTRY_PREFIX), - &tx_log_key, - &tx_in, - )?; - Ok(()) - } - - fn delete_tx_log_entry(&mut self, tx_id: u32, parent_id: &Identifier) -> Result<(), Error> { - let tx_log_key = to_key_u64(parent_id.to_bytes(), tx_id as u64); - self.db - .borrow_mut() - .as_mut() - .unwrap() - .delete(Some(TX_LOG_ENTRY_PREFIX), &tx_log_key)?; - Ok(()) - } - - fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error> { - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(ACCOUNT_PATH_MAPPING_PREFIX), - mapping.label.as_bytes(), - &mapping, - )?; - Ok(()) - } - - // TODO - fix this awkward conversion between PrefixIterator and our Box - fn acct_path_iter(&self) -> Box> { - let db = self.db.borrow(); - let db = db.as_ref().unwrap(); - let protocol_version = db.protocol_version(); - let prefix_iter = db.iter(Some(ACCOUNT_PATH_MAPPING_PREFIX), move |_, mut v| { - ser::deserialize( - &mut v, - protocol_version, - ser::DeserializationMode::default(), - ) - .map_err(From::from) - }); - let iter = prefix_iter - .expect("deserialize") - .into_iter() - .filter(|x| x.is_ok()) - .map(|x| x.unwrap()); - Box::new(iter) - } - - fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error> { - out.lock(); - self.save(out.clone()) - } - - fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error> { - let ctx_key = to_key_u64(slate_id, 0); - let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain(), slate_id)?; - - let mut s_ctx = ctx.clone(); - for i in 0..SECRET_KEY_SIZE { - s_ctx.sec_key.0[i] ^= blind_xor_key[i]; - s_ctx.sec_nonce.0[i] ^= nonce_xor_key[i]; - } - - self.db.borrow_mut().as_mut().unwrap().put_ser( - Some(PRIVATE_TX_CONTEXT_PREFIX), - &ctx_key, - &s_ctx, - )?; - Ok(()) - } - - fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error> { - let ctx_key = to_key_u64(slate_id, 0); - self.db - .borrow_mut() - .as_mut() - .unwrap() - .delete(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key) - .map_err(|e| e.into()) - } - - fn commit(&self) -> Result<(), Error> { - let db = self.db.replace(None); - db.unwrap().commit()?; - Ok(()) - } -} - -/// Build a db key from a byte vector identifier and numeric identifier -fn to_key_u64>(k: K, val: u64) -> Vec { - let mut res = k.as_ref().to_vec(); - res.write_u64::(val).unwrap(); - res -} diff --git a/impls/src/backends/mod.rs b/impls/src/backends/mod.rs deleted file mode 100644 index 56b5df6..0000000 --- a/impls/src/backends/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 The Grin Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod lmdb; - -pub use self::lmdb::{wallet_db_exists, LMDBBackend}; diff --git a/impls/src/lib.rs b/impls/src/lib.rs index b18ad7c..9e9c32c 100644 --- a/impls/src/lib.rs +++ b/impls/src/lib.rs @@ -28,14 +28,12 @@ use grin_api as api; use grin_chain as chain; use grin_core as core; use grin_keychain as keychain; -use grin_store as store; use grin_util as util; use grin_wallet_libwallet as libwallet; use grin_wallet_config as config; mod adapters; -mod backends; mod client_utils; mod error; mod lifecycle; @@ -47,7 +45,6 @@ pub use crate::adapters::{ HttpSlateSender, PathToSlate, PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver, SlateSender, }; -pub use crate::backends::{wallet_db_exists, LMDBBackend}; pub use crate::error::Error; pub use crate::lifecycle::DefaultLCProvider; pub use crate::node_clients::HTTPNodeClient; @@ -57,35 +54,31 @@ use crate::keychain::{ExtKeychain, Keychain}; use libwallet::{NodeClient, WalletInst, WalletLCProvider}; /// Main wallet instance -pub struct DefaultWalletImpl<'a, C> +pub struct DefaultWalletImpl where - C: NodeClient + 'a, + C: NodeClient, { - lc_provider: DefaultLCProvider<'a, C, ExtKeychain>, + lc_provider: DefaultLCProvider, } -impl<'a, C> DefaultWalletImpl<'a, C> +impl DefaultWalletImpl where - C: NodeClient + 'a, + C: NodeClient, { pub fn new(node_client: C) -> Result { let lc_provider = DefaultLCProvider::new(node_client); - Ok(DefaultWalletImpl { - lc_provider: lc_provider, - }) + Ok(DefaultWalletImpl { lc_provider }) } } -impl<'a, L, C, K> WalletInst<'a, L, C, K> for DefaultWalletImpl<'a, C> +impl<'a, L, C, K> WalletInst<'a, L, C, K> for DefaultWalletImpl where - DefaultLCProvider<'a, C, ExtKeychain>: WalletLCProvider<'a, C, K>, + DefaultLCProvider: WalletLCProvider<'a, C, K>, L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { - fn lc_provider( - &mut self, - ) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), libwallet::Error> { + fn lc_provider(&mut self) -> Result<&mut dyn WalletLCProvider<'a, C, K>, libwallet::Error> { Ok(&mut self.lc_provider) } } diff --git a/impls/src/lifecycle/default.rs b/impls/src/lifecycle/default.rs index 0b24b68..4514eb3 100644 --- a/impls/src/lifecycle/default.rs +++ b/impls/src/lifecycle/default.rs @@ -23,13 +23,12 @@ use crate::libwallet::{Error, NodeClient, WalletBackend, WalletInitStatus, Walle use crate::lifecycle::seed::WalletSeed; use crate::util::secp::key::SecretKey; use crate::util::ZeroingString; -use crate::LMDBBackend; use grin_util::logger::LoggingConfig; use std::fs; use std::path::PathBuf; use std::path::MAIN_SEPARATOR; -// Helper fuction to format paths according to OS, avoids bugs on Linux +/// Helper function to format paths according to OS, avoids bugs on Linux pub fn fmt_path(path: String) -> String { let sep = &MAIN_SEPARATOR.to_string(); let path = path.replace("/", &sep); @@ -37,20 +36,20 @@ pub fn fmt_path(path: String) -> String { path } -pub struct DefaultLCProvider<'a, C, K> +pub struct DefaultLCProvider where - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { data_dir: String, node_client: C, - backend: Option + 'a>>, + backend: Option>, } -impl<'a, C, K> DefaultLCProvider<'a, C, K> +impl DefaultLCProvider where - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { /// Create new provider pub fn new(node_client: C) -> Self { @@ -62,7 +61,7 @@ where } } -impl<'a, C, K> WalletLCProvider<'a, C, K> for DefaultLCProvider<'a, C, K> +impl<'a, C, K> WalletLCProvider<'a, C, K> for DefaultLCProvider where C: NodeClient + 'a, K: Keychain + 'a, @@ -217,8 +216,8 @@ where Error::Lifecycle("Error creating wallet seed (is mnemonic valid?)".to_owned()) })?; info!("Wallet seed file created"); - let mut wallet: LMDBBackend<'a, C, K> = - match LMDBBackend::new(&data_dir_name, self.node_client.clone()) { + let mut wallet: WalletBackend = + match WalletBackend::new(&data_dir_name, self.node_client.clone()) { Err(e) => { let msg = format!("Error creating wallet: {}, Data Dir: {}", e, &data_dir_name); error!("{}", msg); @@ -247,8 +246,8 @@ where let mut data_dir_name = PathBuf::from(self.data_dir.clone()); data_dir_name.push(GRIN_WALLET_DIR); let data_dir_name = fmt_path(data_dir_name.to_str().unwrap().to_string()); - let mut wallet: LMDBBackend<'a, C, K> = - match LMDBBackend::new(&data_dir_name, self.node_client.clone()) { + let mut wallet: WalletBackend = + match WalletBackend::new(&data_dir_name, self.node_client.clone()) { Err(e) => { let msg = format!("Error opening wallet: {}, Data Dir: {}", e, &data_dir_name); return Err(Error::Lifecycle(msg)); @@ -262,8 +261,8 @@ where .derive_keychain(global::is_testnet()) .map_err(|_| Error::Lifecycle("Error deriving keychain".to_owned()))?; - let mask = wallet.set_keychain(Box::new(keychain), create_mask, use_test_rng)?; - self.backend = Some(Box::new(wallet)); + let mask = wallet.set_keychain(keychain, create_mask, use_test_rng)?; + self.backend = Some(wallet); Ok(mask) } @@ -329,8 +328,7 @@ where let mut data_dir_name = PathBuf::from(self.data_dir.clone()); data_dir_name.push(GRIN_WALLET_DIR); let data_dir_name = data_dir_name.to_str().unwrap(); - // get seed for later check - + // Get seed for later check let orig_wallet_seed = WalletSeed::from_file(&data_dir_name, old) .map_err(|_| Error::Lifecycle("Error opening wallet seed file".into()))?; let orig_mnemonic = orig_wallet_seed @@ -366,7 +364,7 @@ where .to_string(); return Err(Error::Lifecycle(msg)); } - // Removin + // Removing old file info!("Password change confirmed, removing old seed file."); fs::remove_file(backup_name).map_err(|e| Error::IO(e.to_string()))?; @@ -383,13 +381,13 @@ where Ok(()) } - fn wallet_inst(&mut self) -> Result<&mut Box + 'a>, Error> { + fn wallet_inst(&mut self) -> Result<&mut WalletBackend, Error> { match self.backend.as_mut() { None => { let msg = "Wallet has not been opened".into(); Err(Error::Lifecycle(msg)) } - Some(_) => Ok(&mut *self.backend.as_mut().unwrap()), + Some(b) => Ok(b), } } } diff --git a/impls/src/lifecycle/seed.rs b/impls/src/lifecycle/seed.rs index 59dcd34..5dbbd6c 100644 --- a/impls/src/lifecycle/seed.rs +++ b/impls/src/lifecycle/seed.rs @@ -25,6 +25,7 @@ use ring::aead; use ring::pbkdf2; use crate::keychain::{mnemonic, Keychain}; +use crate::lifecycle::default::fmt_path; use crate::util::{self, ToHex}; use crate::Error; @@ -56,13 +57,6 @@ impl WalletSeed { self.0.to_vec().to_hex() } - // Helper fuction to format paths according to OS, avoids bugs on Linux - pub fn fmt_path(path: String) -> String { - let sep = &MAIN_SEPARATOR.to_string(); - let path = path.replace("/", &sep); - let path = path.replace("\\", &sep); - path - } pub fn to_mnemonic(&self) -> Result { let result = mnemonic::from_entropy(&self.0); match result { @@ -105,7 +99,7 @@ impl WalletSeed { pub fn seed_file_exists(data_file_dir: &str) -> Result { let seed_file_path = &format!( "{}{}{}", - Self::fmt_path(data_file_dir.to_string()), + fmt_path(data_file_dir.to_string()), MAIN_SEPARATOR, SEED_FILE, ); @@ -145,7 +139,7 @@ impl WalletSeed { ) -> Result<(), Error> { let seed_file_path = &format!( "{}{}{}", - Self::fmt_path(data_file_dir.to_string()), + fmt_path(data_file_dir.to_string()), MAIN_SEPARATOR, SEED_FILE, ); @@ -184,11 +178,11 @@ impl WalletSeed { let seed_file_path = &format!( "{}{}{}", - Self::fmt_path(data_file_dir.to_string()), + fmt_path(data_file_dir.to_string()), MAIN_SEPARATOR, SEED_FILE, ); - let data_file_dir = Self::fmt_path(data_file_dir.to_string()); + let data_file_dir = fmt_path(data_file_dir.to_string()); warn!("Generating wallet seed file at: {}", seed_file_path); let exists = WalletSeed::seed_file_exists(&data_file_dir)?; if exists && !test_mode { @@ -219,7 +213,7 @@ impl WalletSeed { let seed_file_path = &format!( "{}{}{}", - Self::fmt_path(data_file_dir.to_string()), + fmt_path(data_file_dir.to_string()), MAIN_SEPARATOR, SEED_FILE, ); @@ -247,7 +241,7 @@ impl WalletSeed { pub fn delete_seed_file(data_file_dir: &str) -> Result<(), Error> { let seed_file_path = &format!( "{}{}{}", - Self::fmt_path(data_file_dir.to_string()), + fmt_path(data_file_dir.to_string()), MAIN_SEPARATOR, SEED_FILE, ); diff --git a/impls/src/test_framework/mod.rs b/impls/src/test_framework/mod.rs index 2479793..c90eac5 100644 --- a/impls/src/test_framework/mod.rs +++ b/impls/src/test_framework/mod.rs @@ -18,7 +18,6 @@ use crate::chain::Chain; use crate::core; use crate::core::core::{Output, Transaction, TxKernel}; use crate::core::{consensus, global, pow}; -use crate::keychain; use crate::libwallet; use crate::libwallet::api_impl::{foreign, owner}; use crate::libwallet::{ @@ -28,6 +27,7 @@ use crate::util::secp::key::SecretKey; use crate::util::secp::pedersen; use crate::util::Mutex; use chrono::Duration; +use grin_keychain::Keychain; use std::sync::Arc; use std::thread; @@ -36,7 +36,7 @@ mod testclient; pub use self::{testclient::LocalWalletClient, testclient::WalletProxy}; /// Get an output from the chain locally and present it back as an API output -fn get_output_local(chain: &chain::Chain, commit: pedersen::Commitment) -> Option { +fn get_output_local(chain: &Chain, commit: pedersen::Commitment) -> Option { if chain.get_unspent(commit).unwrap().is_some() { let block_height = chain.get_header_for_output(commit).unwrap().height; let output_pos = chain.get_output_pos(&commit).unwrap_or(0); @@ -48,7 +48,7 @@ fn get_output_local(chain: &chain::Chain, commit: pedersen::Commitment) -> Optio /// Get a kernel from the chain locally fn get_kernel_local( - chain: Arc, + chain: Arc, excess: &pedersen::Commitment, min_height: Option, max_height: Option, @@ -65,7 +65,7 @@ fn get_kernel_local( /// get output listing traversing pmmr from local fn get_outputs_by_pmmr_index_local( - chain: Arc, + chain: Arc, start_index: u64, end_index: Option, max: u64, @@ -86,7 +86,7 @@ fn get_outputs_by_pmmr_index_local( /// get output listing in a given block range fn height_range_to_pmmr_indices_local( - chain: Arc, + chain: Arc, start_index: u64, end_index: Option, ) -> api::OutputListing { @@ -147,13 +147,13 @@ pub fn create_block_for_wallet<'a, L, C, K>( chain: &Chain, prev: core::core::BlockHeader, txs: &[Transaction], - wallet: Arc + 'a>>>, + wallet: Arc>>>, keychain_mask: Option<&SecretKey>, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, - K: keychain::Keychain + 'a, + K: Keychain + 'a, { // build block fees let fee_amt = txs.iter().map(|tx| tx.fee()).sum(); @@ -166,7 +166,7 @@ where let coinbase_tx = { let mut w_lock = wallet.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - foreign::build_coinbase(&mut **w, keychain_mask, &block_fees, false)? + foreign::build_coinbase(w, keychain_mask, &block_fees, false)? }; let block = create_block_with_reward(chain, prev, txs, coinbase_tx.output, coinbase_tx.kernel); Ok(block) @@ -178,13 +178,13 @@ where pub fn award_block_to_wallet<'a, L, C, K>( chain: &Chain, txs: &[Transaction], - wallet: Arc + 'a>>>, + wallet: Arc>>>, keychain_mask: Option<&SecretKey>, ) -> Result<(), libwallet::Error> where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, - K: keychain::Keychain + 'a, + K: Keychain + 'a, { let prev = chain.head_header().unwrap(); let block = create_block_for_wallet(chain, prev, txs, wallet, keychain_mask)?; @@ -200,7 +200,7 @@ pub fn process_block(chain: &Chain, block: core::core::Block) { /// Award a blocks to a wallet directly pub fn award_blocks_to_wallet<'a, L, C, K>( chain: &Chain, - wallet: Arc + 'a>>>, + wallet: Arc>>>, keychain_mask: Option<&SecretKey>, number: usize, pause_between: bool, @@ -208,7 +208,7 @@ pub fn award_blocks_to_wallet<'a, L, C, K>( where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, - K: keychain::Keychain + 'a, + K: Keychain + 'a, { for _ in 0..number { award_block_to_wallet(chain, &[], wallet.clone(), keychain_mask)?; @@ -231,7 +231,7 @@ pub fn send_to_dest<'a, L, C, K>( where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, - K: keychain::Keychain + 'a, + K: Keychain + 'a, { let slate = { let mut w_lock = wallet.lock(); @@ -245,10 +245,10 @@ where selection_strategy_is_use_all: true, ..Default::default() }; - let slate_i = owner::init_send_tx(&mut **w, keychain_mask, args, test_mode)?; + let slate_i = owner::init_send_tx(w, keychain_mask, args, test_mode)?; let slate = client.send_tx_slate_direct(dest, &slate_i)?; - owner::tx_lock_outputs(&mut **w, keychain_mask, &slate)?; - owner::finalize_tx(&mut **w, keychain_mask, &slate)? + owner::tx_lock_outputs(w, keychain_mask, &slate)?; + owner::finalize_tx(w, keychain_mask, &slate)? }; let client = { let mut w_lock = wallet.lock(); @@ -267,7 +267,7 @@ pub fn wallet_info<'a, L, C, K>( where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, - K: keychain::Keychain + 'a, + K: Keychain + 'a, { let (wallet_refreshed, wallet_info) = owner::retrieve_summary_info(wallet, keychain_mask, &None, true, 1)?; diff --git a/impls/src/test_framework/testclient.rs b/impls/src/test_framework/testclient.rs index 05eb083..dcd2ac1 100644 --- a/impls/src/test_framework/testclient.rs +++ b/impls/src/test_framework/testclient.rs @@ -70,7 +70,7 @@ where String, ( Sender, - Arc + 'a>>>, + Arc>>>, Option, ), >, @@ -100,14 +100,15 @@ where genesis_block, pow::verify_size, false, + None, ) .unwrap(); let (tx, rx) = channel(); WalletProxy { chain_dir: chain_dir.to_owned(), chain: Arc::new(c), - tx: tx, - rx: rx, + tx, + rx, wallets: HashMap::new(), running: Arc::new(AtomicBool::new(false)), } @@ -118,7 +119,7 @@ where &mut self, addr: &str, tx: Sender, - wallet: Arc + 'a>>>, + wallet: Arc>>>, keychain_mask: Option, ) { self.wallets @@ -218,8 +219,7 @@ where let w = w_lock.lc_provider()?.wallet_inst()?; let mask = wallet.2.clone(); // receive tx - match foreign::receive_tx(&mut **w, (&mask).as_ref(), &Slate::from(slate), None, false) - { + match foreign::receive_tx(w, (&mask).as_ref(), &Slate::from(slate), None, false) { Err(e) => { return Ok(WalletProxyMessage { sender_id: m.dest, diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index 848ed48..9496f9d 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/mimblewimble/grin-wallet" keywords = [ "crypto", "grin", "mimblewimble" ] exclude = ["**/*.grin", "**/*.grin2"] #build = "src/build/build.rs" -edition = "2021" +edition = "2018" [dependencies] blake2-rfc = "0.2" diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index 315091c..a116b84 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -15,7 +15,9 @@ //! Generic implementation of owner API functions use strum::IntoEnumIterator; +use super::owner::tx_lock_outputs; use crate::api_impl::owner::{check_ttl, post_tx}; +use crate::backend::WalletBackend; use crate::grin_core::core::FeeFields; use crate::grin_keychain::Keychain; use crate::grin_util::secp::key::SecretKey; @@ -23,11 +25,8 @@ use crate::internal::{selection, tx, updater}; use crate::slate_versions::SlateVersion; use crate::{ address, BlockFees, CbData, Error, NodeClient, Slate, SlateState, TxLogEntryType, VersionInfo, - WalletBackend, }; -use super::owner::tx_lock_outputs; - const FOREIGN_API_VERSION: u16 = 2; /// Return the version info @@ -39,32 +38,30 @@ pub fn check_version() -> VersionInfo { } /// Build a coinbase transaction -pub fn build_coinbase<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn build_coinbase( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, block_fees: &BlockFees, test_mode: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { updater::build_coinbase(&mut *w, keychain_mask, block_fees, test_mode) } /// Receive a tx as recipient -pub fn receive_tx<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn receive_tx( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &Slate, dest_acct_name: Option<&str>, use_test_rng: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut ret_slate = slate.clone(); check_ttl(w, &ret_slate)?; @@ -133,16 +130,15 @@ where } /// Receive a tx that this wallet has issued -pub fn finalize_tx<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn finalize_tx( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &Slate, post_automatically: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut sl = slate.clone(); let mut context = w.get_private_context(keychain_mask, sl.id.as_bytes())?; diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 9ff8e0f..95f4dc9 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -31,15 +31,14 @@ use crate::api_impl::owner_updater::StatusMessage; use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType}; use crate::internal::{keys, scan, selection, tx, updater}; use crate::slate::{PaymentInfo, Slate, SlateState}; -use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo}; -use crate::Error; +use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletInfo}; use crate::{ address, mwixnet::{create_onion, ComSignature, Hop, MixnetReqCreationParams, SwapReq}, - wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, + wallet_lock, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs, ScannedBlockInfo, Slatepack, - SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletInitStatus, - WalletInst, WalletLCProvider, + SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletBackend, + WalletInitStatus, WalletInst, WalletLCProvider, }; use ed25519_dalek::PublicKey as DalekPublicKey; @@ -52,35 +51,32 @@ use std::sync::mpsc::Sender; use std::sync::Arc; /// List of accounts -pub fn accounts<'a, T: ?Sized, C, K>(w: &mut T) -> Result, Error> +pub fn accounts(w: &mut WalletBackend) -> Result, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { keys::accounts(&mut *w) } /// new account path -pub fn create_account_path<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn create_account_path( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, label: &str, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { keys::new_acct_path(&mut *w, keychain_mask, label) } /// set active account -pub fn set_active_account<'a, T: ?Sized, C, K>(w: &mut T, label: &str) -> Result<(), Error> +pub fn set_active_account(w: &mut WalletBackend, label: &str) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { w.set_parent_key_id_by_name(label) } @@ -196,7 +192,7 @@ where dec_key: None, }); let slatepack = packer.deser_slatepack(slatepack.as_bytes(), true)?; - return packer.get_slate(&slatepack); + packer.get_slate(&slatepack) } else { for index in secret_indices { let dec_key = Some(get_slatepack_secret_key( @@ -218,11 +214,11 @@ where }; return packer.get_slate(&slatepack); } - return Err(Error::SlatepackDecryption( + Err(Error::SlatepackDecryption( "Could not decrypt slatepack with any provided index on the address derivation path" .to_owned(), ) - .into()); + .into()) } } @@ -302,13 +298,7 @@ where Ok(( validated, - updater::retrieve_outputs( - &mut **w, - keychain_mask, - include_spent, - tx_id, - Some(&parent_key_id), - )?, + updater::retrieve_outputs(w, keychain_mask, include_spent, tx_id, Some(&parent_key_id))?, )) } @@ -341,7 +331,7 @@ where wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); let txs = updater::retrieve_txs( - &mut **w, + w, tx_id, tx_slate_id, query_args, @@ -378,7 +368,7 @@ where wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); - let wallet_info = updater::retrieve_info(&mut **w, &parent_key_id, minimum_confirmations)?; + let wallet_info = updater::retrieve_info(w, &parent_key_id, minimum_confirmations)?; Ok((validated, wallet_info)) } @@ -469,8 +459,8 @@ where } }; Ok(PaymentProof { - amount: amount, - excess: excess, + amount, + excess, recipient_address: SlatepackAddress::new(&proof.receiver_address), recipient_sig: r_sig, sender_address: SlatepackAddress::new(&proof.sender_address), @@ -479,16 +469,15 @@ where } /// Initiate tx as sender -pub fn init_send_tx<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn init_send_tx( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, args: InitTxArgs, use_test_rng: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let parent_key_id = match &args.src_acct_name { Some(d) => { @@ -595,16 +584,15 @@ where } /// Initiate a transaction as the recipient (invoicing) -pub fn issue_invoice_tx<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn issue_invoice_tx( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, args: IssueInvoiceTxArgs, use_test_rng: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let parent_key_id = match args.dest_acct_name { Some(d) => { @@ -648,17 +636,16 @@ where /// Receive an invoice tx, essentially adding inputs to whatever /// output was specified -pub fn process_invoice_tx<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn process_invoice_tx( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &Slate, args: InitTxArgs, use_test_rng: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut ret_slate = slate.clone(); check_ttl(w, &ret_slate)?; @@ -769,15 +756,14 @@ where } /// Lock sender outputs -pub fn tx_lock_outputs<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn tx_lock_outputs( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &Slate, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; let mut excess_override = None; @@ -806,15 +792,14 @@ where } /// Finalize slate -pub fn finalize_tx<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn finalize_tx( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &Slate, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { foreign_finalize(w, keychain_mask, slate, false) } @@ -844,24 +829,23 @@ where } wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); - tx::cancel_tx(&mut **w, keychain_mask, &parent_key_id, tx_id, tx_slate_id) + tx::cancel_tx(w, keychain_mask, &parent_key_id, tx_id, tx_slate_id) } /// get stored tx /// crashes if stored tx has total fees exceeding 2^40 nanogrin -pub fn get_stored_tx<'a, T: ?Sized, C, K>( - w: &T, +pub fn get_stored_tx( + w: &WalletBackend, tx_id: Option, slate_id: Option<&Uuid>, ) -> Result, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut uuid = None; if let Some(i) = tx_id { - let tx = w.tx_log_iter().find(|t| t.id == i); + let tx = w.tx_log_iter()?.find(|t| t.id == i); if let Some(t) = tx { uuid = t.tx_slate_id; } @@ -896,9 +880,9 @@ where /// Posts a transaction to the chain /// take a client impl instead of wallet so as not to have to lock the wallet -pub fn post_tx<'a, C>(client: &C, tx: &Transaction, fluff: bool) -> Result<(), Error> +pub fn post_tx(client: &C, tx: &Transaction, fluff: bool) -> Result<(), Error> where - C: NodeClient + 'a, + C: NodeClient, { let res = client.post_tx(tx, fluff); if let Err(e) = res { @@ -930,8 +914,7 @@ where let is_hex = rewind_hash.chars().all(|c| c.is_ascii_hexdigit()); let rewind_hash = rewind_hash.to_lowercase(); if !(is_hex && rewind_hash.len() == 64) { - let msg = format!("Invalid Rewind Hash"); - return Err(Error::RewindHash(msg)); + return Err(Error::RewindHash("Invalid Rewind Hash".to_string())); } let tip = { @@ -939,10 +922,7 @@ where w.w2n_client().get_chain_tip()? }; - let start_height = match start_height { - Some(h) => h, - None => 1, - }; + let start_height = start_height.unwrap_or_else(|| 1); let info = scan::scan_rewind_hash( wallet_inst, @@ -1030,10 +1010,12 @@ where }), Err(_) => { let outputs = retrieve_outputs(wallet_inst, keychain_mask, &None, true, false, None)?; - let height = match outputs.1.iter().map(|m| m.output.height).max() { - Some(height) => height, - None => 0, - }; + let height = outputs + .1 + .iter() + .map(|m| m.output.height) + .max() + .unwrap_or_else(|| 0); Ok(NodeHeightResult { height, header_hash: "".to_owned(), @@ -1093,7 +1075,7 @@ where // Step 2: Update outstanding transactions with no change outputs by kernel let mut txs = { wallet_lock!(wallet_inst, w); - updater::retrieve_txs(&mut **w, None, None, None, Some(&parent_key_id), true)? + updater::retrieve_txs(w, None, None, None, Some(&parent_key_id), true)? }; result = update_txs_via_kernel(wallet_inst.clone(), keychain_mask, &mut txs)?; if !result { @@ -1186,7 +1168,7 @@ where if tip.0 >= e { wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); - tx::cancel_tx(&mut **w, keychain_mask, &parent_key_id, Some(tx.id), None)?; + tx::cancel_tx(w, keychain_mask, &parent_key_id, Some(tx.id), None)?; } } } @@ -1195,11 +1177,10 @@ where } /// Check TTL -pub fn check_ttl<'a, T: ?Sized, C, K>(w: &mut T, slate: &Slate) -> Result<(), Error> +pub fn check_ttl(w: &mut WalletBackend, slate: &Slate) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // Refuse if TTL is expired let last_confirmed_height = w.last_confirmed_height()?; @@ -1265,7 +1246,7 @@ where return Err(Error::PaymentProof("Invalid sender signature".to_owned())); }; - // for now, simple test as to whether one of the addresses belongs to this wallet + // for now, simple test whether one of the addresses belongs to this wallet let sec_key = address::address_from_derivation_path(&keychain, &parent_key_id, 0)?; let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) { Ok(k) => k, @@ -1294,7 +1275,7 @@ where { wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); - match updater::refresh_outputs(&mut **w, keychain_mask, &parent_key_id, update_all) { + match updater::refresh_outputs(w, keychain_mask, &parent_key_id, update_all) { Ok(_) => Ok(true), Err(e) => { if let Error::InvalidKeychainMask = e { @@ -1361,16 +1342,15 @@ where } /// Builds an output for the wallet's next available key -pub fn build_output<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn build_output( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, features: OutputFeatures, amount: u64, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let k = w.keychain(keychain_mask)?; @@ -1394,14 +1374,14 @@ where Ok(BuiltOutput { blind: BlindingFactor::from_secret_key(blind), - key_id: key_id, - output: output, + key_id, + output, }) } /// Create MXMixnet request -pub fn create_mwixnet_req<'a, T: ?Sized, C, K>( - w: &mut T, +pub fn create_mwixnet_req( + w: &mut WalletBackend, keychain_mask: Option<&SecretKey>, params: &MixnetReqCreationParams, commitment: &Commitment, @@ -1409,9 +1389,8 @@ pub fn create_mwixnet_req<'a, T: ?Sized, C, K>( use_test_rng: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let parent_key_id = w.parent_key_id(); let keychain = w.keychain(keychain_mask)?; diff --git a/libwallet/src/backend.rs b/libwallet/src/backend.rs new file mode 100644 index 0000000..a9f37c9 --- /dev/null +++ b/libwallet/src/backend.rs @@ -0,0 +1,769 @@ +// Copyright 2021 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use byteorder::{BigEndian, WriteBytesExt}; +use std::fs; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; +use uuid::Uuid; + +use crate::blake2::blake2b::{Blake2b, Blake2bResult}; + +use crate::{ + AcctPathMapping, Context, Error, NodeClient, OutputData, ScannedBlockInfo, TxLogEntry, + WalletInitStatus, +}; +use grin_core::core::Transaction; +use grin_core::ser; +use grin_keychain::{ChildNumber, ExtKeychain, Identifier, Keychain, SwitchCommitmentType}; +use grin_store::{option_to_not_found, Store}; +use grin_util::secp::constants::SECRET_KEY_SIZE; +use grin_util::secp::SecretKey; +use grin_util::ToHex; +use rand::rngs::mock::StepRng; +use rand::thread_rng; + +pub const DB_DIR: &str = "db"; +pub const TX_SAVE_DIR: &str = "saved_txs"; + +const OUTPUT_PREFIX: u8 = b'o'; +const DERIV_PREFIX: u8 = b'd'; +const CONFIRMED_HEIGHT_PREFIX: u8 = b'c'; +const PRIVATE_TX_CONTEXT_PREFIX: u8 = b'p'; +const TX_LOG_ENTRY_PREFIX: u8 = b't'; +const TX_LOG_ID_PREFIX: u8 = b'i'; +const ACCOUNT_PATH_MAPPING_PREFIX: u8 = b'a'; +const LAST_SCANNED_BLOCK: u8 = b'l'; +const LAST_SCANNED_KEY: &str = "LAST_SCANNED_KEY"; +const WALLET_INIT_STATUS: u8 = b'w'; +const WALLET_INIT_STATUS_KEY: &str = "WALLET_INIT_STATUS"; + +const DB_PREFIXES: [u8; 9] = [ + OUTPUT_PREFIX, + DERIV_PREFIX, + CONFIRMED_HEIGHT_PREFIX, + PRIVATE_TX_CONTEXT_PREFIX, + TX_LOG_ENTRY_PREFIX, + TX_LOG_ID_PREFIX, + ACCOUNT_PATH_MAPPING_PREFIX, + LAST_SCANNED_BLOCK, + WALLET_INIT_STATUS, +]; + +/// Helper to derive XOR keys for storing private transaction keys in the DB +/// (blind_xor_key, nonce_xor_key) +fn private_ctx_xor_keys( + keychain: &K, + slate_id: &[u8], +) -> Result<([u8; SECRET_KEY_SIZE], [u8; SECRET_KEY_SIZE]), Error> +where + K: Keychain, +{ + let root_key = keychain.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; + + // derive XOR values for storing secret values in DB + // h(root_key|slate_id|"blind") + let mut hasher = Blake2b::new(SECRET_KEY_SIZE); + hasher.update(&root_key.0[..]); + hasher.update(&slate_id[..]); + hasher.update(&b"blind"[..]); + let blind_xor_key = hasher.finalize(); + let mut ret_blind = [0; SECRET_KEY_SIZE]; + ret_blind.copy_from_slice(&blind_xor_key.as_bytes()[0..SECRET_KEY_SIZE]); + + // h(root_key|slate_id|"nonce") + let mut hasher = Blake2b::new(SECRET_KEY_SIZE); + hasher.update(&root_key.0[..]); + hasher.update(&slate_id[..]); + hasher.update(&b"nonce"[..]); + let nonce_xor_key = hasher.finalize(); + let mut ret_nonce = [0; SECRET_KEY_SIZE]; + ret_nonce.copy_from_slice(&nonce_xor_key.as_bytes()[0..SECRET_KEY_SIZE]); + + Ok((ret_blind, ret_nonce)) +} + +/// Wallet backend. All functions here expect that the wallet instance +/// has instantiated itself or stored whatever credentials it needs. +pub struct WalletBackend +where + C: NodeClient, + K: Keychain, +{ + db: Store, + data_file_dir: String, + /// Keychain + pub keychain: Option, + /// Check value for XORed keychain seed + pub master_checksum: Box>, + /// Parent path to use by default for output operations + parent_key_id: Identifier, + /// wallet to node client + w2n_client: C, +} + +impl WalletBackend +where + C: NodeClient, + K: Keychain, +{ + /// Create new wallet backend. + pub fn new(data_file_dir: &str, n_client: C) -> Result { + let db_path = Path::new(data_file_dir).join(DB_DIR); + fs::create_dir_all(&db_path)?; + + let stored_tx_path = Path::new(data_file_dir).join(TX_SAVE_DIR); + fs::create_dir_all(&stored_tx_path)?; + + let store = Store::new( + db_path.to_str().unwrap(), + None, + Some(DB_DIR), + DB_PREFIXES.to_vec(), + None, + None, + )?; + + // Make sure default wallet derivation path always exists + // as well as path (so it can be retrieved by batches to know where to store + // completed transactions, for reference + let default_account = AcctPathMapping { + label: "default".to_owned(), + path: WalletBackend::::default_path(), + }; + + { + let mut batch = store.batch()?; + batch.put_ser( + Some(ACCOUNT_PATH_MAPPING_PREFIX), + default_account.label.as_bytes(), + &default_account, + )?; + batch.commit()?; + } + + let res = WalletBackend { + db: store, + data_file_dir: data_file_dir.to_owned(), + keychain: None, + master_checksum: Box::new(None), + parent_key_id: WalletBackend::::default_path(), + w2n_client: n_client, + }; + Ok(res) + } + + /// Return the default parent wallet path, corresponding to the default account + /// in the BIP32 spec. Parent is account 0 at level 2, child output identifiers + /// are all at level 3. + pub fn default_path() -> Identifier { + ExtKeychain::derive_key_id(2, 0, 0, 0, 0) + } + + /// Just test to see if database files exist in the current directory. If + /// so, use a DB backend for all operations. + pub fn exists(data_file_dir: &str) -> bool { + let db_path = Path::new(data_file_dir).join(DB_DIR); + db_path.exists() + } + + /// Set the keychain, which should already be initialized + /// Optionally return a token value used to XOR the stored + /// key value + pub fn set_keychain( + &mut self, + mut k: K, + mask: bool, + use_test_rng: bool, + ) -> Result, Error> { + // store hash of master key, so it can be verified later after unmasking + let root_key = k.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; + let mut hasher = Blake2b::new(SECRET_KEY_SIZE); + hasher.update(&root_key.0[..]); + self.master_checksum = Box::new(Some(hasher.finalize())); + + let mask_value = { + match mask { + true => { + // Random value that must be XORed against the stored wallet seed + // before it is used + let mask_value = match use_test_rng { + true => { + let mut test_rng = StepRng::new(1_234_567_890_u64, 1); + SecretKey::new(&k.secp(), &mut test_rng) + } + false => SecretKey::new(&k.secp(), &mut thread_rng()), + }; + k.mask_master_key(&mask_value)?; + Some(mask_value) + } + false => None, + } + }; + + self.keychain = Some(k); + Ok(mask_value) + } + + /// Close wallet by removing stored keychain. + pub fn close(&mut self) -> Result<(), Error> { + self.keychain = None; + Ok(()) + } + + /// Return the keychain being used, cloned with XORed token value + /// for temporary use + /// Can optionally take a mask value + pub fn keychain(&self, mask: Option<&SecretKey>) -> Result { + match self.keychain.as_ref() { + Some(k) => { + let mut k_masked = k.clone(); + if let Some(m) = mask { + k_masked.mask_master_key(m)?; + } + // Check if master seed is what is expected (especially if it's been xored) + let root_key = + k_masked.derive_key(0, &K::root_key_id(), SwitchCommitmentType::Regular)?; + let mut hasher = Blake2b::new(SECRET_KEY_SIZE); + hasher.update(&root_key.0[..]); + if *self.master_checksum != Some(hasher.finalize()) { + error!("Supplied keychain mask is invalid"); + return Err(Error::InvalidKeychainMask); + } + Ok(k_masked) + } + None => Err(Error::KeychainDoesntExist), + } + } + + /// Return the client being used to communicate with the node. + pub fn w2n_client(&mut self) -> &mut C { + &mut self.w2n_client + } + + /// Return the version of the commit for caching if allowed. + pub fn calc_commit_for_cache( + &mut self, + keychain_mask: Option<&SecretKey>, + amount: u64, + id: &Identifier, + ) -> Result, Error> { + //TODO: Check if this is really necessary, it's the only thing + //preventing removing the need for config in the wallet backend + /*if self.config.no_commit_cache == Some(true) { + Ok(None) + } else {*/ + Ok(Some( + self.keychain(keychain_mask)? + .commit(amount, &id, SwitchCommitmentType::Regular)? + .0 + .to_vec() + .to_hex(), // TODO: proper support for different switch commitment schemes + )) + /*}*/ + } + + /// Set parent key id by stored account name. + pub fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error> { + let label = label.to_owned(); + let res = self.acct_path_iter()?.find(|l| l.label == label); + if let Some(a) = res { + self.set_parent_key_id(a.path); + Ok(()) + } else { + Err(Error::UnknownAccountLabel(label)) + } + } + + /// The BIP32 path of the parent path to use for all output-related + /// functions, essentially 'accounts' within a wallet. + pub fn set_parent_key_id(&mut self, id: Identifier) { + self.parent_key_id = id; + } + + /// Get the parent path. + pub fn parent_key_id(&mut self) -> Identifier { + self.parent_key_id.clone() + } + + /// Get output data by id. + pub fn get(&self, id: &Identifier, mmr_index: &Option) -> Result { + let key = match mmr_index { + Some(i) => to_key_u64(id.to_bytes(), *i), + None => id.to_bytes().to_vec(), + }; + option_to_not_found(self.db.get_ser(Some(OUTPUT_PREFIX), &key, None), || { + format!("Key Id: {}", id) + }) + .map_err(|e| e.into()) + } + + /// Iterate over all output data stored by the backend. + pub fn iter(&self) -> Result, Error> { + let protocol_version = self.db.protocol_version(); + let prefix_iter = self.db.iter(Some(OUTPUT_PREFIX), move |_, mut v| { + ser::deserialize( + &mut v, + protocol_version, + ser::DeserializationMode::default(), + ) + .map_err(From::from) + }); + let iter = prefix_iter? + .into_iter() + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()); + Ok(iter) + } + + /// Get an (Optional) tx log entry by uuid. + pub fn get_tx_log_entry(&self, u: &Uuid) -> Result, Error> { + self.db + .get_ser(Some(TX_LOG_ENTRY_PREFIX), u.as_bytes(), None) + .map_err(|e| e.into()) + } + + /// Iterate over all tx log data stored by the backend. + pub fn tx_log_iter(&self) -> Result, Error> { + let protocol_version = self.db.protocol_version(); + let prefix_iter = self.db.iter(Some(TX_LOG_ENTRY_PREFIX), move |_, mut v| { + ser::deserialize( + &mut v, + protocol_version, + ser::DeserializationMode::default(), + ) + .map_err(From::from) + }); + let iter = prefix_iter? + .into_iter() + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()); + Ok(iter) + } + + /// Retrieve the private context associated with a given slate id. + pub fn get_private_context( + &mut self, + keychain_mask: Option<&SecretKey>, + slate_id: &[u8], + ) -> Result { + let ctx_key = to_key_u64(slate_id, 0); + let (blind_xor_key, nonce_xor_key) = + private_ctx_xor_keys(&self.keychain(keychain_mask)?, slate_id)?; + + let mut ctx: Context = option_to_not_found( + self.db + .get_ser(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key, None), + || format!("Slate id: {:x?}", slate_id.to_vec()), + )?; + + for i in 0..SECRET_KEY_SIZE { + ctx.sec_key.0[i] ^= blind_xor_key[i]; + ctx.sec_nonce.0[i] ^= nonce_xor_key[i]; + } + + Ok(ctx) + } + + /// Iterate over all stored account paths. + pub fn acct_path_iter(&self) -> Result, Error> { + let protocol_version = self.db.protocol_version(); + let prefix_iter = self + .db + .iter(Some(ACCOUNT_PATH_MAPPING_PREFIX), move |_, mut v| { + ser::deserialize( + &mut v, + protocol_version, + ser::DeserializationMode::default(), + ) + .map_err(From::from) + }); + let iter = prefix_iter? + .into_iter() + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()); + Ok(iter) + } + + /// Gets an account path for a given label. + pub fn get_acct_path(&self, label: String) -> Result, Error> { + self.db + .get_ser(Some(ACCOUNT_PATH_MAPPING_PREFIX), label.as_bytes(), None) + .map_err(|e| e.into()) + } + + /// Stores a transaction. + pub fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error> { + let filename = format!("{}.grintx", uuid); + let path = Path::new(&self.data_file_dir) + .join(TX_SAVE_DIR) + .join(filename); + let path_buf = Path::new(&path).to_path_buf(); + let mut stored_tx = File::create(path_buf)?; + let tx_hex = ser::ser_vec(tx, ser::ProtocolVersion(1)).unwrap().to_hex(); + stored_tx.write_all(&tx_hex.as_bytes())?; + stored_tx.sync_all()?; + Ok(()) + } + + /// Retrieves a stored transaction. + //TODO: Store content of .grintx file at TxLogEntry? + pub fn get_stored_tx(&self, uuid: &str) -> Result, Error> { + let filename = format!("{}.grintx", uuid); + let path = Path::new(&self.data_file_dir) + .join(TX_SAVE_DIR) + .join(filename); + let tx_file = Path::new(&path).to_path_buf(); + let mut tx_f = File::open(tx_file)?; + let mut content = String::new(); + tx_f.read_to_string(&mut content)?; + let tx_bin = grin_util::from_hex(&content).unwrap(); + Ok(Some( + ser::deserialize( + &mut &tx_bin[..], + ser::ProtocolVersion(1), + ser::DeserializationMode::default(), + ) + .unwrap(), + )) + } + + /// Create a new write batch to update or remove output data. + pub fn batch( + &mut self, + keychain_mask: Option<&SecretKey>, + ) -> Result, Error> { + Ok(WalletBatch { + db: self.db.batch()?, + keychain: Some(self.keychain(keychain_mask)?), + }) + } + + /// Batch for use when keychain isn't available or required. + pub fn batch_no_mask(&mut self) -> Result, Error> { + Ok(WalletBatch { + db: self.db.batch()?, + keychain: None, + }) + } + + /// Return the current child index. + pub fn current_child_index(&mut self, parent_key_id: &Identifier) -> Result { + let index = { + let batch = self.db.batch()?; + batch + .get_ser(Some(DERIV_PREFIX), &parent_key_id.to_bytes(), None)? + .unwrap_or_else(|| 0) + }; + Ok(index) + } + + /// Next child ID when we want to create a new output, based on current parent. + pub fn next_child(&mut self, keychain_mask: Option<&SecretKey>) -> Result { + let parent_key_id = self.parent_key_id.clone(); + let mut deriv_idx = { + let batch = self.db.batch()?; + batch + .get_ser(Some(DERIV_PREFIX), &self.parent_key_id.to_bytes(), None)? + .unwrap_or_else(|| 0) + }; + let mut return_path = self.parent_key_id.to_path(); + return_path.depth += 1; + return_path.path[return_path.depth as usize - 1] = ChildNumber::from(deriv_idx); + deriv_idx += 1; + let mut batch = self.batch(keychain_mask)?; + batch.save_child_index(&parent_key_id, deriv_idx)?; + batch.commit()?; + Ok(Identifier::from_path(&return_path)) + } + + /// Last verified height of outputs directly descending from the given parent key. + pub fn last_confirmed_height(&mut self) -> Result { + let batch = self.db.batch()?; + let last_confirmed_height = batch + .get_ser( + Some(CONFIRMED_HEIGHT_PREFIX), + &self.parent_key_id.to_bytes(), + None, + )? + .unwrap_or_else(|| 0); + Ok(last_confirmed_height) + } + + /// Last block scanned during scan or restore. + pub fn last_scanned_block(&mut self) -> Result { + let batch = self.db.batch()?; + let last_scanned_block = batch + .get_ser(Some(LAST_SCANNED_BLOCK), LAST_SCANNED_KEY.as_bytes(), None)? + .unwrap_or_else(|| ScannedBlockInfo { + height: 0, + hash: "".to_owned(), + start_pmmr_index: 0, + last_pmmr_index: 0, + }); + Ok(last_scanned_block) + } + + /// Flag whether the wallet needs a full UTXO scan on next update attempt. + pub fn init_status(&mut self) -> Result { + let batch = self.db.batch()?; + let status = batch + .get_ser( + Some(WALLET_INIT_STATUS), + WALLET_INIT_STATUS_KEY.as_bytes(), + None, + )? + .unwrap_or_else(|| WalletInitStatus::InitComplete); + Ok(status) + } +} + +/// An atomic batch in which all changes can be committed all at once or +/// discarded on error. +pub struct WalletBatch<'a, K> +where + K: Keychain, +{ + db: grin_store::Batch<'a>, + /// Keychain + keychain: Option, +} + +#[allow(missing_docs)] +impl<'a, K> WalletBatch<'a, K> +where + K: Keychain, +{ + /// Return the keychain being used. + pub fn keychain(&mut self) -> &mut K { + self.keychain.as_mut().unwrap() + } + + /// Add or update data about an output to the backend. + pub fn save(&mut self, out: OutputData) -> Result<(), Error> { + let key = match out.mmr_index { + Some(i) => to_key_u64(out.key_id.to_bytes(), i), + None => out.key_id.to_bytes().to_vec(), + }; + self.db.put_ser(Some(OUTPUT_PREFIX), &key, &out)?; + Ok(()) + } + + /// Gets output data by id + pub fn get(&self, id: &Identifier, mmr_index: &Option) -> Result { + let key = match mmr_index { + Some(i) => to_key_u64(id.to_bytes(), *i), + None => id.to_bytes().to_vec(), + }; + option_to_not_found(self.db.get_ser(Some(OUTPUT_PREFIX), &key, None), || { + format!("Key ID: {}", id) + }) + .map_err(|e| e.into()) + } + + /// Iterate over all output data stored by the backend. + pub fn iter(&'a self) -> Result + 'a, Error> { + let protocol_version = self.db.protocol_version(); + let prefix_iter = self.db.iter(Some(OUTPUT_PREFIX), move |_, mut v| { + ser::deserialize( + &mut v, + protocol_version, + ser::DeserializationMode::default(), + ) + .map_err(From::from) + }); + let iter = prefix_iter? + .into_iter() + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()); + Ok(iter) + } + + /// Delete data about an output from the backend. + pub fn delete(&mut self, id: &Identifier, mmr_index: &Option) -> Result<(), Error> { + // Delete the output data. + let key = match mmr_index { + Some(i) => to_key_u64(id.to_bytes(), *i), + None => id.to_bytes().to_vec(), + }; + self.db.delete(Some(OUTPUT_PREFIX), &key)?; + Ok(()) + } + + /// Save last stored child index of a given parent. + pub fn save_child_index(&mut self, parent_id: &Identifier, child_n: u32) -> Result<(), Error> { + self.db + .put_ser(Some(DERIV_PREFIX), &parent_id.to_bytes(), &child_n)?; + Ok(()) + } + + /// Save last confirmed height of outputs for a given parent. + pub fn save_last_confirmed_height( + &mut self, + parent_key_id: &Identifier, + height: u64, + ) -> Result<(), Error> { + self.db.put_ser( + Some(CONFIRMED_HEIGHT_PREFIX), + &parent_key_id.to_bytes(), + &height, + )?; + Ok(()) + } + + /// Save the last PMMR index that was scanned via a scan operation. + pub fn save_last_scanned_block(&mut self, block_info: ScannedBlockInfo) -> Result<(), Error> { + self.db.put_ser( + Some(LAST_SCANNED_BLOCK), + LAST_SCANNED_KEY.as_bytes(), + &block_info, + )?; + Ok(()) + } + + /// Save flag indicating whether wallet needs a full UTXO scan. + pub fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error> { + self.db.put_ser( + Some(WALLET_INIT_STATUS), + WALLET_INIT_STATUS_KEY.as_bytes(), + &value, + )?; + Ok(()) + } + + /// Get next transaction log entry for the parent. + pub fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result { + let last_tx_log_id = self + .db + .get_ser(Some(TX_LOG_ID_PREFIX), &parent_key_id.to_bytes(), None)? + .unwrap_or_else(|| 0); + self.db.put_ser( + Some(TX_LOG_ID_PREFIX), + &parent_key_id.to_bytes(), + &(last_tx_log_id + 1), + )?; + Ok(last_tx_log_id) + } + + /// Iterate over transactions data stored by the backend. + pub fn tx_log_iter(&'a self) -> Result + 'a, Error> { + let protocol_version = self.db.protocol_version(); + let prefix_iter = self.db.iter(Some(TX_LOG_ENTRY_PREFIX), move |_, mut v| { + ser::deserialize( + &mut v, + protocol_version, + ser::DeserializationMode::default(), + ) + .map_err(From::from) + }); + let iter = prefix_iter? + .into_iter() + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()); + Ok(iter) + } + + /// Save a transaction log entry. + pub fn save_tx_log_entry( + &mut self, + tx_in: TxLogEntry, + parent_id: &Identifier, + ) -> Result<(), Error> { + let tx_log_key = to_key_u64(parent_id.to_bytes(), tx_in.id as u64); + self.db + .put_ser(Some(TX_LOG_ENTRY_PREFIX), &tx_log_key, &tx_in)?; + Ok(()) + } + + /// Delete a transaction log entry. + pub fn delete_tx_log_entry(&mut self, tx_id: u32, parent_id: &Identifier) -> Result<(), Error> { + let tx_log_key = to_key_u64(parent_id.to_bytes(), tx_id as u64); + self.db.delete(Some(TX_LOG_ENTRY_PREFIX), &tx_log_key)?; + Ok(()) + } + + /// Save an account label -> path mapping. + pub fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error> { + self.db.put_ser( + Some(ACCOUNT_PATH_MAPPING_PREFIX), + mapping.label.as_bytes(), + &mapping, + )?; + Ok(()) + } + + /// Iterate over account names stored in backend. + pub fn acct_path_iter(&'a self) -> Result + 'a, Error> { + let protocol_version = self.db.protocol_version(); + let prefix_iter = self + .db + .iter(Some(ACCOUNT_PATH_MAPPING_PREFIX), move |_, mut v| { + ser::deserialize( + &mut v, + protocol_version, + ser::DeserializationMode::default(), + ) + .map_err(From::from) + }); + let iter = prefix_iter? + .into_iter() + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()); + Ok(iter) + } + + /// Save an output as locked in the backend. + pub fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error> { + out.lock(); + self.save(out.clone()) + } + + /// Save the private context associated with a slate id. + pub fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error> { + let ctx_key = to_key_u64(slate_id, 0); + let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain(), slate_id)?; + + let mut s_ctx = ctx.clone(); + for i in 0..SECRET_KEY_SIZE { + s_ctx.sec_key.0[i] ^= blind_xor_key[i]; + s_ctx.sec_nonce.0[i] ^= nonce_xor_key[i]; + } + + self.db + .put_ser(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key, &s_ctx)?; + Ok(()) + } + + /// Delete the private context associated with the slate id. + pub fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error> { + let ctx_key = to_key_u64(slate_id, 0); + self.db + .delete(Some(PRIVATE_TX_CONTEXT_PREFIX), &ctx_key) + .map_err(|e| e.into()) + } + + /// Write the wallet data to backend file. + pub fn commit(self) -> Result<(), Error> { + self.db.commit()?; + Ok(()) + } +} + +/// Build a db key from a byte vector identifier and numeric identifier +fn to_key_u64>(k: K, val: u64) -> Vec { + let mut res = k.as_ref().to_vec(); + res.write_u64::(val).unwrap(); + res +} diff --git a/libwallet/src/internal/keys.rs b/libwallet/src/internal/keys.rs index 9d8f4cb..21c937a 100644 --- a/libwallet/src/internal/keys.rs +++ b/libwallet/src/internal/keys.rs @@ -16,32 +16,31 @@ use crate::error::Error; use crate::grin_keychain::{ChildNumber, ExtKeychain, Identifier, Keychain}; use crate::grin_util::secp::key::SecretKey; -use crate::types::{AcctPathMapping, NodeClient, WalletBackend}; +use crate::types::{AcctPathMapping, NodeClient}; +use crate::WalletBackend; /// Get next available key in the wallet for a given parent -pub fn next_available_key<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn next_available_key( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let child = wallet.next_child(keychain_mask)?; Ok(child) } /// Retrieve an existing key from a wallet -pub fn retrieve_existing_key<'a, T: ?Sized, C, K>( - wallet: &T, +pub fn retrieve_existing_key( + wallet: &WalletBackend, key_id: Identifier, mmr_index: Option, ) -> Result<(Identifier, u32), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let existing = wallet.get(&key_id, &mmr_index)?; let key_id = existing.key_id.clone(); @@ -50,28 +49,26 @@ where } /// Returns a list of account to BIP32 path mappings -pub fn accounts<'a, T: ?Sized, C, K>(wallet: &mut T) -> Result, Error> +pub fn accounts(wallet: &WalletBackend) -> Result, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { - Ok(wallet.acct_path_iter().collect()) + Ok(wallet.acct_path_iter()?.collect()) } -/// Adds an new parent account path with a given label -pub fn new_acct_path<'a, T: ?Sized, C, K>( - wallet: &mut T, +/// Adds a new parent account path with a given label +pub fn new_acct_path( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, label: &str, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let label = label.to_owned(); - if wallet.acct_path_iter().any(|l| l.label == label) { + if wallet.acct_path_iter()?.any(|l| l.label == label) { return Err(Error::AccountLabelAlreadyExists(label)); } @@ -79,7 +76,7 @@ where // so find the highest of those, then increment (to conform with external/internal // derivation chains in BIP32 spec) - let highest_entry = wallet.acct_path_iter().max_by(|a, b| { + let highest_entry = wallet.acct_path_iter()?.max_by(|a, b| { ::from(a.path.to_path().path[0]).cmp(&::from(b.path.to_path().path[0])) }); @@ -94,7 +91,7 @@ where }; let save_path = AcctPathMapping { - label: label, + label, path: return_id.clone(), }; @@ -105,20 +102,19 @@ where } /// Adds/sets a particular account path with a given label -pub fn set_acct_path<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn set_acct_path( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, label: &str, path: &Identifier, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let label = label.to_owned(); let save_path = AcctPathMapping { - label: label, + label, path: path.clone(), }; diff --git a/libwallet/src/internal/scan.rs b/libwallet/src/internal/scan.rs index cc30e4c..85f0e9a 100644 --- a/libwallet/src/internal/scan.rs +++ b/libwallet/src/internal/scan.rs @@ -66,14 +66,14 @@ struct RestoredTxStats { pub num_outputs: usize, } -fn identify_utxo_outputs<'a, K>( +fn identify_utxo_outputs( keychain: &K, outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>, status_send_channel: &Option>, percentage_complete: u8, ) -> Result, Error> where - K: Keychain + 'a, + K: Keychain, { let mut wallet_outputs: Vec = Vec::new(); @@ -137,7 +137,7 @@ where n_child: key_id.to_path().last_path_index(), value: amount, height: *height, - lock_height: lock_height, + lock_height, is_coinbase: *is_coinbase, mmr_index: *mmr_index, }); @@ -145,7 +145,7 @@ where Ok(wallet_outputs) } -fn collect_chain_outputs_rewind_hash<'a, C>( +fn collect_chain_outputs_rewind_hash( client: C, rewind_hash: String, start_index: u64, @@ -153,13 +153,13 @@ fn collect_chain_outputs_rewind_hash<'a, C>( status_send_channel: &Option>, ) -> Result where - C: NodeClient + 'a, + C: NodeClient, { let batch_size = 1000; let start_index_stat = start_index; let mut start_index = start_index; let mut vw = ViewWallet { - rewind_hash: rewind_hash, + rewind_hash, output_result: vec![], total_balance: 0, last_pmmr_index: 0, @@ -198,7 +198,7 @@ where continue; } - let info = info.unwrap(); + let info = info?; vw.total_balance += info.value; let lock_height = if *is_coinbase { *height + global::coinbase_maturity() @@ -212,7 +212,7 @@ where height: *height, mmr_index: *mmr_index, is_coinbase: *is_coinbase, - lock_height: lock_height, + lock_height, }; vw.output_result.push(output_info); @@ -285,7 +285,6 @@ where Ok((result_vec, last_retrieved_return_index, perc_complete)) } -/// fn restore_missing_output<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, @@ -384,7 +383,7 @@ where wallet_lock!(wallet_inst, w); let updated_tx_entry = if output.tx_log_entry.is_some() { let entries = updater::retrieve_txs( - &mut **w, + w, output.tx_log_entry, None, None, @@ -460,9 +459,19 @@ where Ok(chain_outs) } -/// Check / repair wallet contents by scanning against chain -/// assume wallet contents have been freshly updated with contents -/// of latest block +/// Scan chain outputs and repair the wallet state where needed. +/// +/// This is the low-level worker used by the owner API. Callers should normally +/// use the owner scan/update methods instead of calling this directly, unless +/// they need to drive a scan in batches and save progress between those batches. +/// +/// `batch_start_height` and `batch_end_height` describe the part of the chain +/// scanned by this call. `start_height` and `end_height` describe the full scan +/// range, so progress and PMMR bounds still reflect the whole scan. +/// +/// The returned `ScannedBlockInfo` is the progress marker to persist after the +/// batch. The returned PMMR range can be passed into the next batch to avoid +/// looking up the full range again. pub fn scan<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, @@ -526,7 +535,7 @@ where // Now, get all outputs owned by this wallet (regardless of account) let wallet_outputs = { wallet_lock!(wallet_inst, w); - updater::retrieve_outputs(&mut **w, keychain_mask, true, None, None)? + updater::retrieve_outputs(w, keychain_mask, true, None, None)? }; let mut missing_outs = vec![]; @@ -636,7 +645,7 @@ where // restore labels, account paths and child derivation indices wallet_lock!(wallet_inst, w); let label_base = "account"; - let accounts: Vec = w.acct_path_iter().map(|m| m.path).collect(); + let accounts: Vec = w.acct_path_iter()?.map(|m| m.path).collect(); let mut acct_index = accounts.len(); for (path, max_child_index) in found_parents.iter() { // Only restore paths that don't exist @@ -646,7 +655,7 @@ where if let Some(ref s) = status_send_channel { let _ = s.send(StatusMessage::Scanning(msg, perc_complete)); } - keys::set_acct_path(&mut **w, keychain_mask, &label, path)?; + keys::set_acct_path(w, keychain_mask, &label, path)?; acct_index += 1; } let current_child_index = w.current_child_index(&path)?; diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 5bc4500..3b420bc 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -14,7 +14,6 @@ //! Selection of inputs for building transactions -use crate::address; use crate::error::Error; use crate::grin_core::core::amount_to_hr_string; use crate::grin_core::libtx::{ @@ -29,6 +28,7 @@ use crate::internal::keys; use crate::slate::Slate; use crate::types::*; use crate::util::OnionV3Address; +use crate::{address, WalletBackend}; use std::collections::HashMap; use std::convert::TryInto; @@ -37,8 +37,8 @@ use std::convert::TryInto; /// and saves the private wallet identifiers of our selected outputs /// into our transaction context -pub fn build_send_tx<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn build_send_tx( + wallet: &mut WalletBackend, keychain: &K, keychain_mask: Option<&SecretKey>, slate: &mut Slate, @@ -54,9 +54,8 @@ pub fn build_send_tx<'a, T: ?Sized, C, K>( amount_includes_fee: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let (elems, inputs, change_amounts_derivations, fee) = select_send_tx( wallet, @@ -73,7 +72,7 @@ where )?; if amount_includes_fee { slate.amount = slate.amount.checked_sub(fee).ok_or(Error::GenericError( - format!("Transaction amount is too small to include fee").into(), + "Transaction amount is too small to include fee".to_string(), ))?; }; @@ -119,8 +118,8 @@ where /// Locks all corresponding outputs in the context, creates /// change outputs and tx log entry -pub fn lock_tx_context<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn lock_tx_context( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &Slate, current_height: u64, @@ -128,9 +127,8 @@ pub fn lock_tx_context<'a, T: ?Sized, C, K>( excess_override: Option, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut output_commits: HashMap, u64)> = HashMap::new(); // Store cached commits before locking wallet @@ -178,7 +176,7 @@ where let mut amount_debited = 0; t.num_inputs = lock_inputs.len(); for id in lock_inputs { - let mut coin = batch.get(&id.0, &id.1).unwrap(); + let mut coin = batch.get(&id.0, &id.1)?; coin.tx_log_entry = Some(log_id); amount_debited += coin.value; batch.lock_output(&mut coin)?; @@ -221,11 +219,11 @@ where root_key_id: parent_key_id.clone(), key_id: id.clone(), n_child: id.to_path().last_path_index(), - commit: commit, + commit, mmr_index: None, value: change_amount, status: OutputStatus::Unconfirmed, - height: height, + height, lock_height: 0, is_coinbase: false, tx_log_entry: Some(log_id), @@ -245,8 +243,8 @@ where /// Creates a new output in the wallet for the recipient, /// returning the key of the fresh output /// Also creates a new transaction containing the output -pub fn build_recipient_output<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn build_recipient_output( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &mut Slate, current_height: u64, @@ -255,9 +253,8 @@ pub fn build_recipient_output<'a, T: ?Sized, C, K>( is_initiator: bool, ) -> Result<(Identifier, Context, TxLogEntry), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // Create a potential output for this transaction let key_id = keys::next_available_key(wallet, keychain_mask).unwrap(); @@ -300,10 +297,10 @@ where key_id: key_id_inner.clone(), mmr_index: None, n_child: key_id_inner.to_path().last_path_index(), - commit: commit, + commit, value: amount, status: OutputStatus::Unconfirmed, - height: height, + height, lock_height: 0, is_coinbase: false, tx_log_entry: Some(log_id), @@ -317,8 +314,8 @@ where /// Builds a transaction to send to someone from the HD seed associated with the /// wallet and the amount to send. Handles reading through the wallet data file, /// selecting outputs to spend and building the change. -pub fn select_send_tx<'a, T: ?Sized, C, K, B>( - wallet: &mut T, +pub fn select_send_tx( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, amount: u64, amount_includes_fee: bool, @@ -339,9 +336,8 @@ pub fn select_send_tx<'a, T: ?Sized, C, K, B>( Error, > where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, B: ProofBuild, { let (coins, _total, amount, fee) = select_coins_and_fee( @@ -371,8 +367,8 @@ where } /// Select outputs and calculating fee. -pub fn select_coins_and_fee<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn select_coins_and_fee( + wallet: &mut WalletBackend, amount: u64, amount_includes_fee: bool, current_height: u64, @@ -391,9 +387,8 @@ pub fn select_coins_and_fee<'a, T: ?Sized, C, K>( Error, > where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // select some spendable coins from the wallet let (max_outputs, mut coins) = select_coins( @@ -404,10 +399,10 @@ where max_outputs, selection_strategy_is_use_all, parent_key_id, - ); + )?; // sender is responsible for setting the fee on the partial tx - // recipient should double check the fee calculation and not blindly trust the + // recipient should double-check the fee calculation and not blindly trust the // sender // First attempt to spend without change @@ -422,8 +417,8 @@ where return Err(Error::NotEnoughFunds { available: 0, available_disp: amount_to_hr_string(0, false), - needed: amount_with_fee as u64, - needed_disp: amount_to_hr_string(amount_with_fee as u64, false), + needed: amount_with_fee, + needed_disp: amount_to_hr_string(amount_with_fee, false), }); } @@ -432,8 +427,8 @@ where return Err(Error::NotEnoughFunds { available: total, available_disp: amount_to_hr_string(total, false), - needed: amount_with_fee as u64, - needed_disp: amount_to_hr_string(amount_with_fee as u64, false), + needed: amount_with_fee, + needed_disp: amount_to_hr_string(amount_with_fee, false), }); } @@ -453,10 +448,10 @@ where // End the loop if we have selected all the outputs and still not enough funds if coins.len() == max_outputs { return Err(Error::NotEnoughFunds { - available: total as u64, + available: total, available_disp: amount_to_hr_string(total, false), - needed: amount_with_fee as u64, - needed_disp: amount_to_hr_string(amount_with_fee as u64, false), + needed: amount_with_fee, + needed_disp: amount_to_hr_string(amount_with_fee, false), }); } @@ -469,7 +464,7 @@ where max_outputs, selection_strategy_is_use_all, parent_key_id, - ) + )? .1; fee = tx_fee(coins.len(), num_outputs, 1); total = coins.iter().map(|c| c.value).sum(); @@ -483,7 +478,7 @@ where // be reduced, to accommodate the fee. let new_amount = match amount_includes_fee { true => amount.checked_sub(fee).ok_or(Error::GenericError( - format!("Transaction amount is too small to include fee").into(), + "Transaction amount is too small to include fee".to_string(), ))?, false => amount, }; @@ -491,9 +486,9 @@ where } /// Selects inputs and change for a transaction -pub fn inputs_and_change<'a, T: ?Sized, C, K, B>( +pub fn inputs_and_change( coins: &[OutputData], - wallet: &mut T, + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, amount: u64, fee: u64, @@ -507,9 +502,8 @@ pub fn inputs_and_change<'a, T: ?Sized, C, K, B>( Error, > where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, B: ProofBuild, { let mut parts = vec![]; @@ -554,7 +548,7 @@ where part_change }; - let change_key = wallet.next_child(keychain_mask).unwrap(); + let change_key = wallet.next_child(keychain_mask)?; change_amounts_derivations.push((change_amount, change_key.clone(), None)); parts.push(build::output(change_amount, change_key)); @@ -569,26 +563,23 @@ where /// max_outputs). Alternative strategy is to spend smallest outputs first /// but only as many as necessary. When we introduce additional strategies /// we should pass something other than a bool in. -/// TODO: Possibly move this into another trait to be owned by a wallet? - -pub fn select_coins<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn select_coins( + wallet: &WalletBackend, amount: u64, current_height: u64, minimum_confirmations: u64, max_outputs: usize, select_all: bool, parent_key_id: &Identifier, -) -> (usize, Vec) +) -> Result<(usize, Vec), Error> // max_outputs_available, Outputs where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // first find all eligible outputs based on number of confirmations let mut eligible = wallet - .iter() + .iter()? .filter(|out| { out.root_key_id == *parent_key_id && out.eligible_to_spend(current_height, minimum_confirmations) @@ -612,7 +603,7 @@ where for window in eligible.windows(max_outputs) { let windowed_eligibles = window.to_vec(); if let Some(outputs) = select_from(amount, select_all, windowed_eligibles) { - return (max_available, outputs); + return Ok((max_available, outputs)); } } // Not exist in any window of which total amount >= amount. @@ -623,20 +614,20 @@ where "Extending maximum number of outputs. {} outputs selected.", outputs.len() ); - return (max_available, outputs); + return Ok((max_available, outputs)); } } else if let Some(outputs) = select_from(amount, select_all, eligible.clone()) { - return (max_available, outputs); + return Ok((max_available, outputs)); } // we failed to find a suitable set of outputs to spend, // so return the largest amount we can so we can provide guidance on what is // possible eligible.reverse(); - ( + Ok(( max_available, eligible.iter().take(max_outputs).cloned().collect(), - ) + )) } fn select_from(amount: u64, select_all: bool, outputs: Vec) -> Option> { @@ -663,21 +654,20 @@ fn select_from(amount: u64, select_all: bool, outputs: Vec) -> Optio } } -/// Repopulates output in the slate's tranacstion +/// Repopulates output in the slate's transaction /// with outputs from the stored context /// change outputs and tx log entry /// Remove the explicitly stored excess -pub fn repopulate_tx<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn repopulate_tx( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &mut Slate, context: &Context, update_fee: bool, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // restore the original amount, fee slate.amount = context.amount; @@ -694,7 +684,7 @@ where let mut parts = vec![]; for (id, _, value) in &context.get_inputs() { - let input = wallet.iter().find(|out| out.key_id == *id); + let input = wallet.iter()?.find(|out| out.key_id == *id); if let Some(i) = input { if i.is_coinbase { parts.push(build::coinbase_input(*value, i.key_id.clone())); @@ -704,7 +694,7 @@ where } } for (id, _, value) in &context.get_outputs() { - let output = wallet.iter().find(|out| out.key_id == *id); + let output = wallet.iter()?.find(|out| out.key_id == *id); if let Some(i) = output { parts.push(build::output(*value, i.key_id.clone())); } diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index a5dde1a..57e2fec 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -26,10 +26,10 @@ use crate::grin_util::secp::pedersen; use crate::grin_util::Mutex; use crate::internal::{selection, updater}; use crate::slate::Slate; -use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend}; +use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType}; use crate::util::OnionV3Address; -use crate::InitTxArgs; use crate::{address, Error}; +use crate::{InitTxArgs, WalletBackend}; use ed25519_dalek::Keypair as DalekKeypair; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::SecretKey as DalekSecretKey; @@ -44,8 +44,8 @@ lazy_static! { /// Creates a new slate for a transaction, can be called by anyone involved in /// the transaction (sender(s), receiver(s)) -pub fn new_tx_slate<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn new_tx_slate( + wallet: &mut WalletBackend, amount: u64, is_invoice: bool, num_participants: u8, @@ -53,9 +53,8 @@ pub fn new_tx_slate<'a, T: ?Sized, C, K>( ttl_blocks: Option, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let current_height = wallet.w2n_client().get_chain_tip()?.0; let mut slate = Slate::blank(num_participants, is_invoice); @@ -92,8 +91,8 @@ where } /// Estimates locked amount and fee for the transaction without creating one -pub fn estimate_send_tx<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn estimate_send_tx( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, amount: u64, amount_includes_fee: bool, @@ -110,9 +109,8 @@ pub fn estimate_send_tx<'a, T: ?Sized, C, K>( Error, > where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // Get lock height let current_height = wallet.w2n_client().get_chain_tip()?.0; @@ -141,8 +139,8 @@ where } /// Add inputs to the slate (effectively becoming the sender) -pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn add_inputs_to_slate( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &mut Slate, current_height: u64, @@ -156,9 +154,8 @@ pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>( amount_includes_fee: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // sender should always refresh outputs updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?; @@ -207,8 +204,8 @@ where } /// Add receiver output to the slate -pub fn add_output_to_slate<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn add_output_to_slate( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &mut Slate, current_height: u64, @@ -217,9 +214,8 @@ pub fn add_output_to_slate<'a, T: ?Sized, C, K>( use_test_rng: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let keychain = wallet.keychain(keychain_mask)?; // create an output using the amount in the slate @@ -252,8 +248,8 @@ where } /// Create context, without adding inputs to slate -pub fn create_late_lock_context<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn create_late_lock_context( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &mut Slate, current_height: u64, @@ -262,9 +258,8 @@ pub fn create_late_lock_context<'a, T: ?Sized, C, K>( use_test_rng: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // sender should always refresh outputs updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?; @@ -300,16 +295,15 @@ where } /// Complete a transaction -pub fn complete_tx<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn complete_tx( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, slate: &mut Slate, context: &Context, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // when self sending invoice tx, use initiator nonce to finalize let (sec_key, sec_nonce) = { @@ -333,17 +327,16 @@ where } /// Rollback outputs associated with a transaction in the wallet -pub fn cancel_tx<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn cancel_tx( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, parent_key_id: &Identifier, tx_id: Option, tx_slate_id: Option, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut tx_id_string = String::new(); if let Some(tx_id) = tx_id { @@ -384,17 +377,16 @@ where } /// Update the stored transaction (this update needs to happen when the TX is finalised) -pub fn update_stored_tx<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn update_stored_tx( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, context: &Context, slate: &Slate, is_invoiced: bool, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // finalize command let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, None, false)?; @@ -421,10 +413,7 @@ where } if let Some(ref p) = slate.clone().payment_proof { - let derivation_index = match context.payment_proof_derivation_index { - Some(i) => i, - None => 0, - }; + let derivation_index = context.payment_proof_derivation_index.unwrap_or_else(|| 0); let keychain = wallet.keychain(keychain_mask)?; let parent_key_id = wallet.parent_key_id(); let excess = slate.calc_excess(keychain.secp())?; @@ -506,17 +495,16 @@ pub fn create_payment_proof_signature( } /// Verify all aspects of a completed payment proof on the current slate -pub fn verify_slate_payment_proof<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn verify_slate_payment_proof( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, parent_key_id: &Identifier, context: &Context, slate: &Slate, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let tx_vec = updater::retrieve_txs( wallet, diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index 09c8943..f7b053d 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -30,32 +30,29 @@ use crate::grin_util::secp::key::SecretKey; use crate::grin_util::secp::pedersen; use crate::grin_util::static_secp_instance; use crate::internal::keys; -use crate::types::{ - NodeClient, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, WalletBackend, WalletInfo, -}; +use crate::types::{NodeClient, OutputData, OutputStatus, TxLogEntry, TxLogEntryType, WalletInfo}; use crate::{ BlockFees, CbData, OutputCommitMapping, RetrieveTxQueryArgs, RetrieveTxQuerySortField, - RetrieveTxQuerySortOrder, + RetrieveTxQuerySortOrder, WalletBackend, }; use num_bigint::BigInt; -/// Retrieve all of the outputs (doesn't attempt to update from node) -pub fn retrieve_outputs<'a, T: ?Sized, C, K>( - wallet: &mut T, +/// Retrieve all the outputs (don't attempt to update from node) +pub fn retrieve_outputs( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, show_spent: bool, tx_id: Option, parent_key_id: Option<&Identifier>, ) -> Result, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // just read the wallet here, no need for a write lock let mut outputs = wallet - .iter() + .iter()? .filter(|out| show_spent || out.status != OutputStatus::Spent) .collect::>(); @@ -94,20 +91,19 @@ where } /// Apply advanced filtering to resultset from retrieve_txs below -pub fn apply_advanced_tx_list_filtering<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn apply_advanced_tx_list_filtering( + wallet: &mut WalletBackend, parent_key_id: Option<&Identifier>, query_args: &RetrieveTxQueryArgs, -) -> Vec +) -> Result, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // Apply simple bool, GTE or LTE fields let txs_iter: Box> = Box::new( wallet - .tx_log_iter() + .tx_log_iter()? .filter(|tx_entry| match parent_key_id { Some(k) => tx_entry.parent_key_id == *k, None => true, @@ -326,13 +322,13 @@ where return_txs = return_txs.into_iter().take(l as usize).collect() } - return_txs + Ok(return_txs) } -/// Retrieve all of the transaction entries, or a particular entry +/// Retrieve all the transaction entries, or a particular entry /// if `parent_key_id` is set, only return entries from that key -pub fn retrieve_txs<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn retrieve_txs( + wallet: &mut WalletBackend, tx_id: Option, tx_slate_id: Option, query_args: Option, @@ -340,18 +336,17 @@ pub fn retrieve_txs<'a, T: ?Sized, C, K>( outstanding_only: bool, ) -> Result, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut txs; // Adding in new transaction list query logic. If `tx_id` or `tx_slate_id` // is provided, then `query_args` is ignored and old logic is followed. if query_args.is_some() && tx_id.is_none() && tx_slate_id.is_none() { - txs = apply_advanced_tx_list_filtering(wallet, parent_key_id, &query_args.unwrap()) + txs = apply_advanced_tx_list_filtering(wallet, parent_key_id, &query_args.unwrap())? } else { txs = wallet - .tx_log_iter() + .tx_log_iter()? .filter(|tx_entry| { let f_pk = match parent_key_id { Some(k) => tx_entry.parent_key_id == *k, @@ -384,16 +379,15 @@ where /// Refreshes the outputs in a wallet with the latest information /// from a node -pub fn refresh_outputs<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn refresh_outputs( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, parent_key_id: &Identifier, update_all: bool, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let height = wallet.w2n_client().get_chain_tip()?.0; refresh_output_state(wallet, keychain_mask, height, parent_key_id, update_all)?; @@ -402,21 +396,20 @@ where /// build a local map of wallet outputs keyed by commit /// and a list of outputs we want to query the node for -pub fn map_wallet_outputs<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn map_wallet_outputs( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, parent_key_id: &Identifier, update_all: bool, ) -> Result, Option, bool)>, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut wallet_outputs = HashMap::new(); let keychain = wallet.keychain(keychain_mask)?; let unspents: Vec = wallet - .iter() + .iter()? .filter(|x| x.root_key_id == *parent_key_id && x.status != OutputStatus::Spent) .collect(); @@ -453,17 +446,16 @@ where } /// Cancel transaction and associated outputs -pub fn cancel_tx_and_outputs<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn cancel_tx_and_outputs( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, mut tx: TxLogEntry, outputs: Vec, parent_key_id: &Identifier, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut batch = wallet.batch(keychain_mask)?; @@ -490,8 +482,8 @@ where } /// Apply refreshed API output data to the wallet -pub fn apply_api_outputs<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn apply_api_outputs( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, wallet_outputs: &HashMap, Option, bool)>, api_outputs: &HashMap, @@ -500,9 +492,8 @@ pub fn apply_api_outputs<'a, T: ?Sized, C, K>( parent_key_id: &Identifier, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { // now for each commit, find the output in the wallet and the corresponding // api output (if it exists) and refresh it in-place in the wallet. @@ -556,7 +547,7 @@ where && (output.status == OutputStatus::Unconfirmed || output.status == OutputStatus::Reverted) { - let tx = batch.tx_log_iter().find(|t| { + let tx = batch.tx_log_iter()?.find(|t| { Some(t.id) == output.tx_log_entry && t.parent_key_id == *parent_key_id }); @@ -590,7 +581,8 @@ where } } - for mut tx in batch.tx_log_iter() { + let mut txs_to_save = vec![]; + for mut tx in batch.tx_log_iter()? { if reverted_kernels.contains(&tx.id) && tx.parent_key_id == *parent_key_id { tx.tx_type = TxLogEntryType::TxReverted; tx.reverted_after = tx.confirmation_ts.clone().and_then(|t| { @@ -598,9 +590,12 @@ where (now - t).to_std().ok() }); tx.confirmed = false; - batch.save_tx_log_entry(tx, &parent_key_id)?; + txs_to_save.push(tx); } } + for tx in txs_to_save { + batch.save_tx_log_entry(tx, &parent_key_id)?; + } { batch.save_last_confirmed_height(parent_key_id, height)?; @@ -612,17 +607,16 @@ where /// Builds a single api query to retrieve the latest output data from the node. /// So we can refresh the local wallet outputs. -fn refresh_output_state<'a, T: ?Sized, C, K>( - wallet: &mut T, +fn refresh_output_state( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, height: u64, parent_key_id: &Identifier, update_all: bool, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { debug!("Refreshing wallet outputs"); @@ -654,16 +648,15 @@ where Ok(()) } -fn find_reverted_kernels<'a, T: ?Sized, C, K>( - wallet: &mut T, +fn find_reverted_kernels( + wallet: &mut WalletBackend, wallet_outputs: &HashMap, Option, bool)>, api_outputs: &HashMap, parent_key_id: &Identifier, ) -> Result, Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let mut client = wallet.w2n_client().clone(); let mut ids = HashSet::new(); @@ -679,7 +672,7 @@ where // Get corresponding kernels let kernels = wallet - .tx_log_iter() + .tx_log_iter()? .filter(|t| { ids.contains(&t.id) && t.parent_key_id == *parent_key_id @@ -701,21 +694,20 @@ where Ok(reverted) } -fn clean_old_unconfirmed<'a, T: ?Sized, C, K>( - wallet: &mut T, +fn clean_old_unconfirmed( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, height: u64, ) -> Result<(), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { if height < 50 { return Ok(()); } let mut ids_to_del = vec![]; - for out in wallet.iter() { + for out in wallet.iter()? { if out.status == OutputStatus::Unconfirmed && out.height > 0 && out.height < height - 50 @@ -734,19 +726,18 @@ where /// Retrieve summary info about the wallet /// caller should refresh first if desired -pub fn retrieve_info<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn retrieve_info( + wallet: &mut WalletBackend, parent_key_id: &Identifier, minimum_confirmations: u64, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let current_height = wallet.last_confirmed_height()?; let outputs = wallet - .iter() + .iter()? .filter(|out| out.root_key_id == *parent_key_id); let mut unspent_total = 0; @@ -800,16 +791,15 @@ where } /// Build a coinbase output and insert into wallet -pub fn build_coinbase<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn build_coinbase( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, block_fees: &BlockFees, test_mode: bool, ) -> Result where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let (out, kern, block_fees) = receive_coinbase(wallet, keychain_mask, block_fees, test_mode)?; @@ -822,16 +812,15 @@ where //TODO: Split up the output creation and the wallet insertion /// Build a coinbase output and the corresponding kernel -pub fn receive_coinbase<'a, T: ?Sized, C, K>( - wallet: &mut T, +pub fn receive_coinbase( + wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, block_fees: &BlockFees, test_mode: bool, ) -> Result<(Output, TxKernel, BlockFees), Error> where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, + C: NodeClient, + K: Keychain, { let height = block_fees.height; let lock_height = height + global::coinbase_maturity(); @@ -856,11 +845,11 @@ where key_id: key_id.clone(), n_child: key_id.to_path().last_path_index(), mmr_index: None, - commit: commit, + commit, value: amount, status: OutputStatus::Unconfirmed, - height: height, - lock_height: lock_height, + height, + lock_height, is_coinbase: true, tx_log_entry: None, })?; diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 95185c5..a73cdb4 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -45,6 +45,7 @@ extern crate strum_macros; pub mod address; pub mod api_impl; +mod backend; mod error; mod internal; pub mod mwixnet; @@ -69,13 +70,13 @@ pub use api_impl::types::{ NodeHeightResult, OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, VersionInfo, }; +pub use backend::{WalletBackend, WalletBatch}; pub use internal::scan::scan; pub use slate_versions::ser as dalek_ser; pub use types::{ AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData, OutputStatus, ScannedBlockInfo, StoredProofInfo, TxLogEntry, TxLogEntryType, TxWrapper, - ViewWallet, WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, - WalletOutputBatch, + ViewWallet, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider, }; /// Helper for taking a lock on the wallet instance diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 12b552e..dff9a66 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -28,7 +28,7 @@ use crate::grin_util::secp::key::{PublicKey, SecretKey}; use crate::grin_util::secp::{self, pedersen, Secp256k1}; use crate::grin_util::{ToHex, ZeroingString}; use crate::slate_versions::ser as dalek_ser; -use crate::InitTxArgs; +use crate::{InitTxArgs, WalletBackend}; use chrono::prelude::*; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::Signature as DalekSignature; @@ -49,7 +49,7 @@ where K: Keychain + 'a, { /// Return the stored instance - fn lc_provider(&mut self) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), Error>; + fn lc_provider(&mut self) -> Result<&mut dyn WalletLCProvider<'a, C, K>, Error>; } /// Trait for a provider of wallet lifecycle methods @@ -131,182 +131,7 @@ where fn delete_wallet(&self, name: Option<&str>) -> Result<(), Error>; /// return wallet instance - fn wallet_inst(&mut self) -> Result<&mut Box + 'a>, Error>; -} - -/// TODO: -/// Wallets should implement this backend for their storage. All functions -/// here expect that the wallet instance has instantiated itself or stored -/// whatever credentials it needs -pub trait WalletBackend<'ck, C, K>: Send + Sync -where - C: NodeClient + 'ck, - K: Keychain + 'ck, -{ - /// Set the keychain, which should already be initialized - /// Optionally return a token value used to XOR the stored - /// key value - fn set_keychain( - &mut self, - k: Box, - mask: bool, - use_test_rng: bool, - ) -> Result, Error>; - - /// Close wallet and remove any stored credentials (TBD) - fn close(&mut self) -> Result<(), Error>; - - /// Return the keychain being used. Ensure a cloned copy so it will be dropped - /// and zeroized by the caller - /// Can optionally take a mask value - fn keychain(&self, mask: Option<&SecretKey>) -> Result; - - /// Return the client being used to communicate with the node - fn w2n_client(&mut self) -> &mut C; - - /// return the commit for caching if allowed, none otherwise - fn calc_commit_for_cache( - &mut self, - keychain_mask: Option<&SecretKey>, - amount: u64, - id: &Identifier, - ) -> Result, Error>; - - /// Set parent key id by stored account name - fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error>; - - /// The BIP32 path of the parent path to use for all output-related - /// functions, (essentially 'accounts' within a wallet. - fn set_parent_key_id(&mut self, _: Identifier); - - /// return the parent path - fn parent_key_id(&mut self) -> Identifier; - - /// Iterate over all output data stored by the backend - fn iter<'a>(&'a self) -> Box + 'a>; - - /// Get output data by id - fn get(&self, id: &Identifier, mmr_index: &Option) -> Result; - - /// Get an (Optional) tx log entry by uuid - fn get_tx_log_entry(&self, uuid: &Uuid) -> Result, Error>; - - /// Retrieves the private context associated with a given slate id - fn get_private_context( - &mut self, - keychain_mask: Option<&SecretKey>, - slate_id: &[u8], - ) -> Result; - - /// Iterate over all output data stored by the backend - fn tx_log_iter<'a>(&'a self) -> Box + 'a>; - - /// Iterate over all stored account paths - fn acct_path_iter<'a>(&'a self) -> Box + 'a>; - - /// Gets an account path for a given label - fn get_acct_path(&self, label: String) -> Result, Error>; - - /// Stores a transaction - fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error>; - - /// Retrieves a stored transaction from a TxLogEntry - fn get_stored_tx(&self, uuid: &str) -> Result, Error>; - - /// Create a new write batch to update or remove output data - fn batch<'a>( - &'a mut self, - keychain_mask: Option<&SecretKey>, - ) -> Result + 'a>, Error>; - - /// Batch for use when keychain isn't available or required - fn batch_no_mask<'a>(&'a mut self) -> Result + 'a>, Error>; - - /// Return the current child Index - fn current_child_index(&mut self, parent_key_id: &Identifier) -> Result; - - /// Next child ID when we want to create a new output, based on current parent - fn next_child(&mut self, keychain_mask: Option<&SecretKey>) -> Result; - - /// last verified height of outputs directly descending from the given parent key - fn last_confirmed_height(&mut self) -> Result; - - /// last block scanned during scan or restore - fn last_scanned_block(&mut self) -> Result; - - /// Flag whether the wallet needs a full UTXO scan on next update attempt - fn init_status(&mut self) -> Result; -} - -/// Batch trait to update the output data backend atomically. Trying to use a -/// batch after commit MAY result in a panic. Due to this being a trait, the -/// commit method can't take ownership. -/// TODO: Should these be split into separate batch objects, for outputs, -/// tx_log entries and meta/details? -pub trait WalletOutputBatch -where - K: Keychain, -{ - /// Return the keychain being used - fn keychain(&mut self) -> &mut K; - - /// Add or update data about an output to the backend - fn save(&mut self, out: OutputData) -> Result<(), Error>; - - /// Gets output data by id - fn get(&self, id: &Identifier, mmr_index: &Option) -> Result; - - /// Iterate over all output data stored by the backend - fn iter(&self) -> Box>; - - /// Delete data about an output from the backend - fn delete(&mut self, id: &Identifier, mmr_index: &Option) -> Result<(), Error>; - - /// Save last stored child index of a given parent - fn save_child_index(&mut self, parent_key_id: &Identifier, child_n: u32) -> Result<(), Error>; - - /// Save last confirmed height of outputs for a given parent - fn save_last_confirmed_height( - &mut self, - parent_key_id: &Identifier, - height: u64, - ) -> Result<(), Error>; - - /// Save the last PMMR index that was scanned via a scan operation - fn save_last_scanned_block(&mut self, block: ScannedBlockInfo) -> Result<(), Error>; - - /// Save flag indicating whether wallet needs a full UTXO scan - fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error>; - - /// get next tx log entry for the parent - fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result; - - /// Iterate over tx log data stored by the backend - fn tx_log_iter(&self) -> Box>; - - /// save a tx log entry - fn save_tx_log_entry(&mut self, t: TxLogEntry, parent_id: &Identifier) -> Result<(), Error>; - - /// delete a tx log entry - fn delete_tx_log_entry(&mut self, tx_id: u32, parent_id: &Identifier) -> Result<(), Error>; - - /// save an account label -> path mapping - fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error>; - - /// Iterate over account names stored in backend - fn acct_path_iter(&self) -> Box>; - - /// Save an output as locked in the backend - fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error>; - - /// Saves the private context associated with a slate id - fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error>; - - /// Delete the private context associated with the slate id - fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error>; - - /// Write the wallet data to backend file - fn commit(&self) -> Result<(), Error>; + fn wallet_inst(&mut self) -> Result<&mut WalletBackend, Error>; } /// Encapsulate all wallet-node communication functions. No functions within libwallet diff --git a/src/cli/cli.rs b/src/cli/cli.rs index c14f3d4..cf01dd7 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -115,7 +115,7 @@ pub fn command_loop( test_mode: bool, ) -> Result<(), Error> where - DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + DefaultWalletImpl: WalletInst<'static, L, C, K>, L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: keychain::Keychain + 'static, diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 205cc0e..1345cdd 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -92,7 +92,7 @@ fn prompt_recovery_phrase( wallet: Arc>>>, ) -> Result where - DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + DefaultWalletImpl: WalletInst<'static, L, C, K>, L: WalletLCProvider<'static, C, K>, C: NodeClient + 'static, K: keychain::Keychain + 'static, @@ -224,12 +224,12 @@ pub fn inst_wallet( node_client: C, ) -> Result>>>, ParseError> where - DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + DefaultWalletImpl: WalletInst<'static, L, C, K>, L: WalletLCProvider<'static, C, K>, C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let mut wallet = Box::new(DefaultWalletImpl::<'static, C>::new(node_client.clone()).unwrap()) + let mut wallet = Box::new(DefaultWalletImpl::::new(node_client.clone()).unwrap()) as Box>; let lc = wallet.lc_provider().unwrap(); let _ = lc.set_top_level_directory(&config.data_file_dir); @@ -335,7 +335,7 @@ pub fn parse_init_args( _test_mode: bool, ) -> Result where - DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + DefaultWalletImpl: WalletInst<'static, L, C, K>, L: WalletLCProvider<'static, C, K>, C: NodeClient + 'static, K: keychain::Keychain + 'static, @@ -355,16 +355,16 @@ where println!("Please enter a password for your new wallet"); } - let password = match g_args.password.clone() { - Some(p) => p, - None => prompt_password_confirm(), - }; + let password = g_args + .password + .clone() + .unwrap_or_else(|| prompt_password_confirm()); Ok(command::InitArgs { - list_length: list_length, - password: password, + list_length, + password, config: config.clone(), - recovery_phrase: recovery_phrase, + recovery_phrase, restore: false, }) } @@ -375,9 +375,7 @@ pub fn parse_recover_args( where { let passphrase = prompt_password(&g_args.password); - Ok(command::RecoverArgs { - passphrase: passphrase, - }) + Ok(command::RecoverArgs { passphrase }) } pub fn parse_listen_args( @@ -977,7 +975,7 @@ where Box< dyn WalletInst< 'static, - DefaultLCProvider<'static, C, keychain::ExtKeychain>, + DefaultLCProvider, C, keychain::ExtKeychain, >, @@ -1112,7 +1110,7 @@ pub fn parse_and_execute( cli_mode: bool, ) -> Result<(), Error> where - DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>, + DefaultWalletImpl: WalletInst<'static, L, C, K>, L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: keychain::Keychain + 'static, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 2116457..ee01a40 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -226,7 +226,7 @@ pub fn instantiate_wallet( Box< dyn WalletInst< 'static, - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, @@ -241,7 +241,7 @@ pub fn instantiate_wallet( let mut wallet = Box::new(DefaultWalletImpl::::new(node_client).unwrap()) as Box< dyn WalletInst< - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, @@ -302,18 +302,7 @@ pub fn execute_command_no_setup( where C: NodeClient + 'static + Clone, F: FnOnce( - Arc< - Mutex< - Box< - dyn WalletInst< - 'static, - DefaultLCProvider<'static, C, ExtKeychain>, - C, - ExtKeychain, - >, - >, - >, - >, + Arc, C, ExtKeychain>>>>, ), { let args = app.clone().get_matches_from(arg_vec); diff --git a/tests/owner_v3_lifecycle.rs b/tests/owner_v3_lifecycle.rs index 42295d5..fd27a50 100644 --- a/tests/owner_v3_lifecycle.rs +++ b/tests/owner_v3_lifecycle.rs @@ -57,7 +57,7 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { let wallet_proxy_a: Arc< Mutex< WalletProxy< - DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>, + DefaultLCProvider, LocalWalletClient, ExtKeychain, >, diff --git a/util/Cargo.toml b/util/Cargo.toml index 5343818..ff53b7c 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -28,7 +28,7 @@ thiserror = "1" # grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } # For bleeding edge -# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" } +grin_util = { git = "https://github.com/mimblewimble/grin", rev = "110e0e143fdf188b69ac56c2f378c4121859703b" } # For local testing