Merge remote-tracking branch 'ardocrat/update_lmdb' into grim

# Conflicts:
#	Cargo.lock
#	api/src/foreign.rs
#	api/src/owner.rs
#	controller/Cargo.toml
#	controller/tests/common/mod.rs
#	impls/src/backends/lmdb.rs
#	libwallet/Cargo.toml
This commit is contained in:
ardocrat
2026-06-04 17:40:30 +03:00
38 changed files with 1571 additions and 1722 deletions
Generated
+84 -114
View File
@@ -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"
+4 -4
View File
@@ -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"}
+3 -3
View File
@@ -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"}
+11 -12
View File
@@ -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::<HTTPNodeClient>::new(node_client.clone()).unwrap())
/// as Box<dyn WalletInst<'static, DefaultLCProvider<HTTPNodeClient, ExtKeychain>, 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>,
HTTPNodeClient,
ExtKeychain,
>,
>;
let mut wallet =
Box::new(DefaultWalletImpl::<HTTPNodeClient>::new(node_client.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
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);
+3 -6
View File
@@ -381,7 +381,6 @@ pub fn run_doctest_foreign(
Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client1.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
@@ -416,7 +415,6 @@ pub fn run_doctest_foreign(
Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client2.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
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());
+34 -36
View File
@@ -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::<HTTPNodeClient>::new(node_client.clone()).unwrap())
/// as Box<dyn WalletInst<'static, DefaultLCProvider<HTTPNodeClient, ExtKeychain>, 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<Identifier, Error> {
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<Slate, Error> {
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<Slate, Error> {
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<BuiltOutput, Error> {
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<HttpSlateSender>,
send_to_finalize: bool,
test_mode: bool,
) -> Result<Option<Slate>, libwallet::Error> {
) -> Result<Option<Slate>, 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>,
HTTPNodeClient,
ExtKeychain,
>,
>;
let mut wallet =
Box::new(DefaultWalletImpl::<HTTPNodeClient>::new(node_client.clone()).unwrap())
as Box<
dyn WalletInst<
'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
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);
+17 -18
View File
@@ -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<VersionedSlate, Error> {
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<String, Error> {
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());
}
+2 -2
View File
@@ -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"}
+10 -26
View File
@@ -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"}
#####
+215
View File
@@ -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::<std::collections::HashSet<_>>();
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);
}
+5 -5
View File
@@ -98,7 +98,7 @@ pub fn create_wallet_proxy(
test_dir: &str,
) -> WalletProxy<
'_,
DefaultLCProvider<'_, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
> {
@@ -117,7 +117,7 @@ pub fn create_local_wallet(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -129,7 +129,7 @@ pub fn create_local_wallet(
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -156,7 +156,7 @@ pub fn open_local_wallet(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -168,7 +168,7 @@ pub fn open_local_wallet(
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
+1 -1
View File
@@ -41,7 +41,7 @@ type Wallet = Arc<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
+1 -1
View File
@@ -45,7 +45,7 @@ fn test_wallet_tx_filtering(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
+4 -11
View File
@@ -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<T: ?Sized, C, K>(
!·wallet: &mut T,
!·wallet: &mut WalletBackend<C, K>,
!·show_spent: bool,
!·tx_id: Option<u32>,
) -> Result<Vec<OutputData>, Error>
where
!·T: WalletBackend<C, K>,
!·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.
matter of dropping in different `NodeClient` implementations.
+6 -6
View File
@@ -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"}
-803
View File
@@ -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<K>(
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<K>,
/// Check value for XORed keychain seed
pub master_checksum: Box<Option<Blake2bResult>>,
/// Parent path to use by default for output operations
parent_key_id: Identifier,
/// wallet to node client
w2n_client: C,
///phantom
_phantom: &'ck PhantomData<C>,
}
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<Self, Error> {
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::<C, K>::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::<C, K>::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<K>,
mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, 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<K, Error> {
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<Option<String>, 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<u64>) -> Result<OutputData, Error> {
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<dyn Iterator>
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + '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<Option<TxLogEntry>, 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<dyn Iterator>
fn tx_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = TxLogEntry> + '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<Context, Error> {
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<dyn Iterator>
fn acct_path_iter<'a>(&'a self) -> Box<dyn Iterator<Item = AcctPathMapping> + '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<Option<AcctPathMapping>, 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<Option<Transaction>, 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<Box<dyn WalletOutputBatch<K> + '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<Box<dyn WalletOutputBatch<K> + '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<u32, Error> {
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<Identifier, Error> {
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<u64, Error> {
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<ScannedBlockInfo, Error> {
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<WalletInitStatus, Error> {
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<Option<store::Batch<'a>>>,
/// Keychain
keychain: Option<K>,
}
#[allow(missing_docs)]
impl<'a, C, K> WalletOutputBatch<K> 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<u64>) -> Result<OutputData, Error> {
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<dyn Iterator>
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>> {
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<u64>) -> 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<u32, Error> {
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<dyn Iterator>
fn tx_log_iter(&self) -> Box<dyn Iterator<Item = TxLogEntry>> {
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<dyn Iterator>
fn acct_path_iter(&self) -> Box<dyn Iterator<Item = AcctPathMapping>> {
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: AsRef<[u8]>>(k: K, val: u64) -> Vec<u8> {
let mut res = k.as_ref().to_vec();
res.write_u64::<BigEndian>(val).unwrap();
res
}
-17
View File
@@ -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};
+9 -16
View File
@@ -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<C>
where
C: NodeClient + 'a,
C: NodeClient,
{
lc_provider: DefaultLCProvider<'a, C, ExtKeychain>,
lc_provider: DefaultLCProvider<C, ExtKeychain>,
}
impl<'a, C> DefaultWalletImpl<'a, C>
impl<C> DefaultWalletImpl<C>
where
C: NodeClient + 'a,
C: NodeClient,
{
pub fn new(node_client: C) -> Result<Self, Error> {
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<C>
where
DefaultLCProvider<'a, C, ExtKeychain>: WalletLCProvider<'a, C, K>,
DefaultLCProvider<C, ExtKeychain>: 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)
}
}
+19 -21
View File
@@ -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<C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
data_dir: String,
node_client: C,
backend: Option<Box<dyn WalletBackend<'a, C, K> + 'a>>,
backend: Option<WalletBackend<C, K>>,
}
impl<'a, C, K> DefaultLCProvider<'a, C, K>
impl<C, K> DefaultLCProvider<C, K>
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<C, K>
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<C, K> =
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<C, K> =
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<dyn WalletBackend<'a, C, K> + 'a>, Error> {
fn wallet_inst(&mut self) -> Result<&mut WalletBackend<C, K>, 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),
}
}
}
+7 -13
View File
@@ -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<String, Error> {
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<bool, 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,
);
@@ -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,
);
+17 -17
View File
@@ -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<api::Output> {
fn get_output_local(chain: &Chain, commit: pedersen::Commitment) -> Option<api::Output> {
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::Chain>,
chain: Arc<Chain>,
excess: &pedersen::Commitment,
min_height: Option<u64>,
max_height: Option<u64>,
@@ -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::Chain>,
chain: Arc<Chain>,
start_index: u64,
end_index: Option<u64>,
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::Chain>,
chain: Arc<Chain>,
start_index: u64,
end_index: Option<u64>,
) -> 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<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<&SecretKey>,
) -> Result<core::core::Block, libwallet::Error>
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<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
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<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
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)?;
+6 -6
View File
@@ -70,7 +70,7 @@ where
String,
(
Sender<WalletProxyMessage>,
Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
Option<SecretKey>,
),
>,
@@ -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<WalletProxyMessage>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<SecretKey>,
) {
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,
+1 -1
View File
@@ -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"
+14 -18
View File
@@ -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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
block_fees: &BlockFees,
test_mode: bool,
) -> Result<CbData, Error>
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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
dest_acct_name: Option<&str>,
use_test_rng: bool,
) -> Result<Slate, Error>
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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
post_automatically: bool,
) -> Result<Slate, Error>
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())?;
+75 -96
View File
@@ -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<Vec<AcctPathMapping>, Error>
pub fn accounts<C, K>(w: &mut WalletBackend<C, K>) -> Result<Vec<AcctPathMapping>, 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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
label: &str,
) -> Result<Identifier, Error>
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<C, K>(w: &mut WalletBackend<C, K>, 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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
args: InitTxArgs,
use_test_rng: bool,
) -> Result<Slate, Error>
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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
args: IssueInvoiceTxArgs,
use_test_rng: bool,
) -> Result<Slate, Error>
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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
args: InitTxArgs,
use_test_rng: bool,
) -> Result<Slate, Error>
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<C, K>(
w: &mut WalletBackend<C, K>,
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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
slate: &Slate,
) -> Result<Slate, Error>
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<C, K>(
w: &WalletBackend<C, K>,
tx_id: Option<u32>,
slate_id: Option<&Uuid>,
) -> Result<Option<Slate>, 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<C>(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<C, K>(w: &mut WalletBackend<C, K>, 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<C, K>(
w: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
features: OutputFeatures,
amount: u64,
) -> Result<BuiltOutput, Error>
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<C, K>(
w: &mut WalletBackend<C, K>,
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<SwapReq, Error>
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)?;
+769
View File
@@ -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<K>(
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<C, K>
where
C: NodeClient,
K: Keychain,
{
db: Store,
data_file_dir: String,
/// Keychain
pub keychain: Option<K>,
/// Check value for XORed keychain seed
pub master_checksum: Box<Option<Blake2bResult>>,
/// Parent path to use by default for output operations
parent_key_id: Identifier,
/// wallet to node client
w2n_client: C,
}
impl<C, K> WalletBackend<C, K>
where
C: NodeClient,
K: Keychain,
{
/// Create new wallet backend.
pub fn new(data_file_dir: &str, n_client: C) -> Result<Self, Error> {
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::<C, K>::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::<C, K>::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<Option<SecretKey>, 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<K, Error> {
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<Option<String>, 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<u64>) -> Result<OutputData, Error> {
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<impl Iterator<Item = OutputData>, 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<Option<TxLogEntry>, 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<impl Iterator<Item = TxLogEntry>, 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<Context, Error> {
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<impl Iterator<Item = AcctPathMapping>, 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<Option<AcctPathMapping>, 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<Option<Transaction>, 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<WalletBatch<'_, K>, 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<WalletBatch<'_, K>, 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<u32, Error> {
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<Identifier, Error> {
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<u64, Error> {
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<ScannedBlockInfo, Error> {
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<WalletInitStatus, Error> {
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<K>,
}
#[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<u64>) -> Result<OutputData, Error> {
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<impl Iterator<Item = OutputData> + '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<u64>) -> 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<u32, Error> {
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<impl Iterator<Item = TxLogEntry> + '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<impl Iterator<Item = AcctPathMapping> + '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: AsRef<[u8]>>(k: K, val: u64) -> Vec<u8> {
let mut res = k.as_ref().to_vec();
res.write_u64::<BigEndian>(val).unwrap();
res
}
+27 -31
View File
@@ -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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
) -> Result<Identifier, Error>
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<C, K>(
wallet: &WalletBackend<C, K>,
key_id: Identifier,
mmr_index: Option<u64>,
) -> 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<Vec<AcctPathMapping>, Error>
pub fn accounts<C, K>(wallet: &WalletBackend<C, K>) -> Result<Vec<AcctPathMapping>, 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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
label: &str,
) -> Result<Identifier, Error>
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| {
<u32>::from(a.path.to_path().path[0]).cmp(&<u32>::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<C, K>(
wallet: &mut WalletBackend<C, K>,
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(),
};
+25 -16
View File
@@ -66,14 +66,14 @@ struct RestoredTxStats {
pub num_outputs: usize,
}
fn identify_utxo_outputs<'a, K>(
fn identify_utxo_outputs<K>(
keychain: &K,
outputs: Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
status_send_channel: &Option<Sender<StatusMessage>>,
percentage_complete: u8,
) -> Result<Vec<OutputResult>, Error>
where
K: Keychain + 'a,
K: Keychain,
{
let mut wallet_outputs: Vec<OutputResult> = 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<C>(
client: C,
rewind_hash: String,
start_index: u64,
@@ -153,13 +153,13 @@ fn collect_chain_outputs_rewind_hash<'a, C>(
status_send_channel: &Option<Sender<StatusMessage>>,
) -> Result<ViewWallet, Error>
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<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
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<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
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<Identifier> = w.acct_path_iter().map(|m| m.path).collect();
let accounts: Vec<Identifier> = 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)?;
+61 -71
View File
@@ -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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<Context, Error>
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<pedersen::Commitment>,
) -> Result<(), Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
C: NodeClient,
K: Keychain,
{
let mut output_commits: HashMap<Identifier, (Option<String>, 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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K, B>(
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K, B>(
coins: &[OutputData],
wallet: &mut T,
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &WalletBackend<C, K>,
amount: u64,
current_height: u64,
minimum_confirmations: u64,
max_outputs: usize,
select_all: bool,
parent_key_id: &Identifier,
) -> (usize, Vec<OutputData>)
) -> Result<(usize, Vec<OutputData>), 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<OutputData>) -> Option<Vec<OutputData>> {
@@ -663,21 +654,20 @@ fn select_from(amount: u64, select_all: bool, outputs: Vec<OutputData>) -> 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<C, K>(
wallet: &mut WalletBackend<C, K>,
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()));
}
+39 -51
View File
@@ -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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<u64>,
) -> Result<Slate, Error>
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<Context, Error>
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<Context, Error>
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<Context, Error>
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
parent_key_id: &Identifier,
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
) -> 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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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,
+76 -87
View File
@@ -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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
show_spent: bool,
tx_id: Option<u32>,
parent_key_id: Option<&Identifier>,
) -> Result<Vec<OutputCommitMapping>, 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::<Vec<_>>();
@@ -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<C, K>(
wallet: &mut WalletBackend<C, K>,
parent_key_id: Option<&Identifier>,
query_args: &RetrieveTxQueryArgs,
) -> Vec<TxLogEntry>
) -> Result<Vec<TxLogEntry>, 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<dyn Iterator<Item = TxLogEntry>> = 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<C, K>(
wallet: &mut WalletBackend<C, K>,
tx_id: Option<u32>,
tx_slate_id: Option<Uuid>,
query_args: Option<RetrieveTxQueryArgs>,
@@ -340,18 +336,17 @@ pub fn retrieve_txs<'a, T: ?Sized, C, K>(
outstanding_only: bool,
) -> Result<Vec<TxLogEntry>, 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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
parent_key_id: &Identifier,
update_all: bool,
) -> Result<HashMap<pedersen::Commitment, (Identifier, Option<u64>, Option<u32>, 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<OutputData> = 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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
mut tx: TxLogEntry,
outputs: Vec<OutputData>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
wallet_outputs: &HashMap<pedersen::Commitment, (Identifier, Option<u64>, Option<u32>, bool)>,
api_outputs: &HashMap<pedersen::Commitment, (String, u64, u64)>,
@@ -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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
wallet_outputs: &HashMap<pedersen::Commitment, (Identifier, Option<u64>, Option<u32>, bool)>,
api_outputs: &HashMap<pedersen::Commitment, (String, u64, u64)>,
parent_key_id: &Identifier,
) -> Result<HashSet<u32>, 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<C, K>(
wallet: &mut WalletBackend<C, K>,
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<C, K>(
wallet: &mut WalletBackend<C, K>,
parent_key_id: &Identifier,
minimum_confirmations: u64,
) -> Result<WalletInfo, Error>
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<C, K>(
wallet: &mut WalletBackend<C, K>,
keychain_mask: Option<&SecretKey>,
block_fees: &BlockFees,
test_mode: bool,
) -> Result<CbData, Error>
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<C, K>(
wallet: &mut WalletBackend<C, K>,
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,
})?;
+3 -2
View File
@@ -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
+3 -178
View File
@@ -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<dyn WalletBackend<'a, C, K> + '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<K>,
mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, 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<K, Error>;
/// 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<Option<String>, 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<dyn Iterator<Item = OutputData> + 'a>;
/// Get output data by id
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
/// Get an (Optional) tx log entry by uuid
fn get_tx_log_entry(&self, uuid: &Uuid) -> Result<Option<TxLogEntry>, 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<Context, Error>;
/// Iterate over all output data stored by the backend
fn tx_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = TxLogEntry> + 'a>;
/// Iterate over all stored account paths
fn acct_path_iter<'a>(&'a self) -> Box<dyn Iterator<Item = AcctPathMapping> + 'a>;
/// Gets an account path for a given label
fn get_acct_path(&self, label: String) -> Result<Option<AcctPathMapping>, 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<Option<Transaction>, Error>;
/// Create a new write batch to update or remove output data
fn batch<'a>(
&'a mut self,
keychain_mask: Option<&SecretKey>,
) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
/// Batch for use when keychain isn't available or required
fn batch_no_mask<'a>(&'a mut self) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
/// Return the current child Index
fn current_child_index(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
/// 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<Identifier, Error>;
/// last verified height of outputs directly descending from the given parent key
fn last_confirmed_height(&mut self) -> Result<u64, Error>;
/// last block scanned during scan or restore
fn last_scanned_block(&mut self) -> Result<ScannedBlockInfo, Error>;
/// Flag whether the wallet needs a full UTXO scan on next update attempt
fn init_status(&mut self) -> Result<WalletInitStatus, Error>;
}
/// 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<K>
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<u64>) -> Result<OutputData, Error>;
/// Iterate over all output data stored by the backend
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>>;
/// Delete data about an output from the backend
fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> 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<u32, Error>;
/// Iterate over tx log data stored by the backend
fn tx_log_iter(&self) -> Box<dyn Iterator<Item = TxLogEntry>>;
/// 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<dyn Iterator<Item = AcctPathMapping>>;
/// 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<C, K>, Error>;
}
/// Encapsulate all wallet-node communication functions. No functions within libwallet
+1 -1
View File
@@ -115,7 +115,7 @@ pub fn command_loop<L, C, K>(
test_mode: bool,
) -> Result<(), Error>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: WalletInst<'static, L, C, K>,
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
+14 -16
View File
@@ -92,7 +92,7 @@ fn prompt_recovery_phrase<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
) -> Result<ZeroingString, ParseError>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: 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<L, C, K>(
node_client: C,
) -> Result<Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>, ParseError>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: 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::<C>::new(node_client.clone()).unwrap())
as Box<dyn WalletInst<'static, L, C, K>>;
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<L, C, K>(
_test_mode: bool,
) -> Result<command::InitArgs, ParseError>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: 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>,
C,
keychain::ExtKeychain,
>,
@@ -1112,7 +1110,7 @@ pub fn parse_and_execute<L, C, K>(
cli_mode: bool,
) -> Result<(), Error>
where
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
DefaultWalletImpl<C>: WalletInst<'static, L, C, K>,
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
+3 -14
View File
@@ -226,7 +226,7 @@ pub fn instantiate_wallet(
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -241,7 +241,7 @@ pub fn instantiate_wallet(
let mut wallet = Box::new(DefaultWalletImpl::<LocalWalletClient>::new(node_client).unwrap())
as Box<
dyn WalletInst<
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
@@ -302,18 +302,7 @@ pub fn execute_command_no_setup<C, F>(
where
C: NodeClient + 'static + Clone,
F: FnOnce(
Arc<
Mutex<
Box<
dyn WalletInst<
'static,
DefaultLCProvider<'static, C, ExtKeychain>,
C,
ExtKeychain,
>,
>,
>,
>,
Arc<Mutex<Box<dyn WalletInst<'static, DefaultLCProvider<C, ExtKeychain>, C, ExtKeychain>>>>,
),
{
let args = app.clone().get_matches_from(arg_vec);
+1 -1
View File
@@ -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>,
LocalWalletClient,
ExtKeychain,
>,
+1 -1
View File
@@ -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